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:
parent
6273f3235d
commit
f8a7fab2e7
|
|
@ -1,12 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/config/module_config.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -28,6 +30,18 @@ public:
|
|||
Application(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 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
|
|
@ -188,10 +202,20 @@ private:
|
|||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
* @brief 初始化核心模块
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initModules();
|
||||
bool initCoreModules();
|
||||
|
||||
/**
|
||||
* @brief 设置所有模块
|
||||
*/
|
||||
void setupAllModules();
|
||||
|
||||
/**
|
||||
* @brief 销毁所有模块
|
||||
*/
|
||||
void destroyAllModules();
|
||||
|
||||
/**
|
||||
* @brief 注册核心服务
|
||||
|
|
@ -213,6 +237,7 @@ private:
|
|||
*/
|
||||
void render();
|
||||
|
||||
std::vector<Module*> modules_;
|
||||
IWindow* window_ = nullptr;
|
||||
|
||||
bool initialized_ = false;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -7,24 +7,28 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
|
||||
// Config
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_loader.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_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
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/platform/window_config.h>
|
||||
|
||||
// Graphics
|
||||
#include <extra2d/graphics/camera.h>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -151,6 +151,31 @@ public:
|
|||
// ------------------------------------------------------------------------
|
||||
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();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内部方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
#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/platform/iinput.h>
|
||||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/platform/platform_init_module.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/modules/platform_module.h>
|
||||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -38,6 +36,23 @@ Application &Application::get() {
|
|||
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() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
|
|
@ -48,140 +63,71 @@ bool Application::init(const AppConfig &config) {
|
|||
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->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()) {
|
||||
if (!initCoreModules()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *windowModule = dynamic_cast<WindowModuleInitializer *>(windowInit);
|
||||
if (!windowModule) {
|
||||
ConfigModule *configModule = nullptr;
|
||||
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;
|
||||
}
|
||||
|
||||
window_ = windowModule->getWindow();
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Window not created");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *renderInit =
|
||||
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||
if (renderInit) {
|
||||
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
registerCoreServices();
|
||||
|
||||
auto *renderConfig =
|
||||
ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||
if (renderConfig && !renderInit->initialize(renderConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (inputModule) {
|
||||
inputModule->setWindow(window_);
|
||||
inputModule->setupModule();
|
||||
}
|
||||
|
||||
registerCoreServices();
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
renderModule->setupModule();
|
||||
}
|
||||
|
||||
if (!ServiceLocator::instance().initializeAll()) {
|
||||
return false;
|
||||
|
|
@ -211,6 +157,204 @@ bool Application::initModules() {
|
|||
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() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
|
@ -221,15 +365,7 @@ void Application::shutdown() {
|
|||
|
||||
window_ = nullptr;
|
||||
|
||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||
|
||||
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
||||
ModuleId moduleId = *it;
|
||||
auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||
if (initializer && initializer->isInitialized()) {
|
||||
initializer->shutdown();
|
||||
}
|
||||
}
|
||||
destroyAllModules();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
|
@ -301,37 +437,34 @@ void Application::mainLoop() {
|
|||
|
||||
render();
|
||||
|
||||
const auto &appConfig = ConfigManager::instance().appConfig();
|
||||
|
||||
auto *renderConfig =
|
||||
ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||
auto *renderModuleConfig =
|
||||
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));
|
||||
RenderModule *renderModule = nullptr;
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderModule = rm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(void)renderModule;
|
||||
|
||||
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() {
|
||||
auto *renderInit =
|
||||
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||
RenderBackend *renderer = nullptr;
|
||||
if (renderInit) {
|
||||
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||
if (renderModule) {
|
||||
renderer = renderModule->getRenderer();
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderer = rm->getRenderer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,23 +484,41 @@ void Application::render() {
|
|||
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>();
|
||||
if (sceneService) {
|
||||
sceneService->render(*renderer);
|
||||
}
|
||||
|
||||
window_->swap();
|
||||
{
|
||||
auto ctx = RenderContext(modules_, RenderContext::Phase::After);
|
||||
if (!modules_.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IInput &Application::input() { return *window_->input(); }
|
||||
|
||||
RenderBackend &Application::renderer() {
|
||||
auto *renderInit =
|
||||
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||
if (renderInit) {
|
||||
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||
if (renderModule && renderModule->getRenderer()) {
|
||||
return *renderModule->getRenderer();
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
if (rm->getRenderer()) {
|
||||
return *rm->getRenderer();
|
||||
}
|
||||
}
|
||||
}
|
||||
static RenderBackend *dummy = nullptr;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/config/module_registry.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -16,16 +14,18 @@ namespace extra2d {
|
|||
* @param str 输入字符串
|
||||
* @return 去除空白后的字符串
|
||||
*/
|
||||
static std::string trim(const std::string& str) {
|
||||
size_t start = 0;
|
||||
while (start < str.length() && 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]))) {
|
||||
--end;
|
||||
}
|
||||
return str.substr(start, end - start);
|
||||
static std::string trim(const std::string &str) {
|
||||
size_t start = 0;
|
||||
while (start < str.length() &&
|
||||
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]))) {
|
||||
--end;
|
||||
}
|
||||
return str.substr(start, end - start);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,12 +33,12 @@ static std::string trim(const std::string& str) {
|
|||
* @param str 输入字符串
|
||||
* @return 小写字符串
|
||||
*/
|
||||
static std::string toLower(const std::string& str) {
|
||||
std::string result = str;
|
||||
for (char& c : result) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return result;
|
||||
static std::string toLower(const std::string &str) {
|
||||
std::string result = str;
|
||||
for (char &c : result) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,47 +52,49 @@ using IniData = std::map<std::string, std::map<std::string, std::string>>;
|
|||
* @param data 输出的 INI 数据
|
||||
* @return 解析结果
|
||||
*/
|
||||
static ConfigLoadResult parseIniContent(const std::string& content, IniData& data) {
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
std::string currentSection;
|
||||
int lineNumber = 0;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
++lineNumber;
|
||||
line = trim(line);
|
||||
|
||||
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;
|
||||
}
|
||||
static ConfigLoadResult parseIniContent(const std::string &content,
|
||||
IniData &data) {
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
std::string currentSection;
|
||||
int lineNumber = 0;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
++lineNumber;
|
||||
line = trim(line);
|
||||
|
||||
if (line.empty() || line[0] == ';' || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 默认值
|
||||
* @return 值字符串
|
||||
*/
|
||||
static std::string getIniValue(const IniData& data, const std::string& section,
|
||||
const std::string& key, const std::string& defaultValue = "") {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
auto keyIt = sectionIt->second.find(key);
|
||||
if (keyIt == sectionIt->second.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return keyIt->second;
|
||||
static std::string getIniValue(const IniData &data, const std::string §ion,
|
||||
const std::string &key,
|
||||
const std::string &defaultValue = "") {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
auto keyIt = sectionIt->second.find(key);
|
||||
if (keyIt == sectionIt->second.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return keyIt->second;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,219 +126,207 @@ static std::string getIniValue(const IniData& data, const std::string& section,
|
|||
* @param key 键名
|
||||
* @return 是否存在
|
||||
*/
|
||||
static bool hasIniValue(const IniData& data, const std::string& section, const std::string& key) {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
static bool hasIniValue(const IniData &data, const std::string §ion,
|
||||
const std::string &key) {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
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 {
|
||||
return makeUnique<IniConfigLoader>();
|
||||
return makeUnique<IniConfigLoader>();
|
||||
}
|
||||
|
||||
std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const {
|
||||
return section + "." + key;
|
||||
std::string IniConfigLoader::sectionKey(const std::string §ion,
|
||||
const std::string &key) const {
|
||||
return section + "." + key;
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result, const std::string& fieldName) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stoi(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析整数值: " + value, -1, fieldName);
|
||||
}
|
||||
return ConfigLoadResult::ok();
|
||||
} catch (const std::exception& e) {
|
||||
return ConfigLoadResult::error(std::string("解析整数失败: ") + e.what(), -1, fieldName);
|
||||
ConfigLoadResult IniConfigLoader::parseInt(const std::string &value,
|
||||
int &result,
|
||||
const std::string &fieldName) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stoi(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析整数值: " + value, -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) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stof(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析浮点数值: " + value, -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) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stof(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析浮点数值: " + value, -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) {
|
||||
std::string lower = toLower(value);
|
||||
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
|
||||
result = true;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
|
||||
result = false;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
|
||||
ConfigLoadResult IniConfigLoader::parseBool(const std::string &value,
|
||||
bool &result,
|
||||
const std::string &fieldName) {
|
||||
std::string lower = toLower(value);
|
||||
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
|
||||
result = true;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
|
||||
result = false;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/config/module_registry.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
|
@ -93,7 +92,7 @@ std::string JsonConfigLoader::saveToString(const AppConfig& config) {
|
|||
}
|
||||
|
||||
ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) {
|
||||
E2D_LOG_INFO("正在从 JSON 文件加载完整配置(含模块): {}", filepath);
|
||||
E2D_LOG_INFO("正在从 JSON 文件加载完整配置: {}", filepath);
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
|
|
@ -115,50 +114,15 @@ ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath)
|
|||
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("完整配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) {
|
||||
E2D_LOG_INFO("正在保存完整配置(含模块)到 JSON 文件: {}", filepath);
|
||||
E2D_LOG_INFO("正在保存完整配置到 JSON 文件: {}", filepath);
|
||||
|
||||
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(§ionJson)) {
|
||||
root[sectionName] = sectionJson;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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_++;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
93
README.md
93
README.md
|
|
@ -39,12 +39,11 @@
|
|||
|
||||
- **跨平台支持**:Windows、Linux、macOS、Nintendo Switch
|
||||
- **模块化架构**:模块系统 + 服务系统,灵活可扩展
|
||||
- **显式注册**:通过 `Application::use()` 注册模块,参考 Kiwano 设计
|
||||
- **场景图系统**:树形节点结构,支持变换继承
|
||||
- **输入系统**:键盘、鼠标、手柄、触摸,事件驱动
|
||||
- **渲染系统**:OpenGL ES 3.2,支持自定义着色器
|
||||
- **视口适配**:多种适配模式,自动响应窗口大小变化
|
||||
- **音频系统**:高质量音频播放(规划中)
|
||||
- **UI 系统**:完整的 UI 控件支持(规划中)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -59,18 +58,19 @@ flowchart TB
|
|||
|
||||
subgraph Core["Core Layer (核心层)"]
|
||||
direction LR
|
||||
MR[ModuleRegistry<br/>模块注册表]
|
||||
MOD[Module<br/>模块基类]
|
||||
SL[ServiceLocator<br/>服务定位器]
|
||||
end
|
||||
|
||||
subgraph Modules["Modules (模块系统)"]
|
||||
direction TB
|
||||
subgraph CoreModules["Core Modules"]
|
||||
LOGGER[Logger Module<br/>日志系统]
|
||||
CONFIG[Config Module<br/>配置管理]
|
||||
PLATFORM[Platform Module<br/>平台检测]
|
||||
WINDOW[Window Module<br/>窗口管理]
|
||||
end
|
||||
subgraph OtherModules["Feature Modules"]
|
||||
subgraph FeatureModules["Feature Modules"]
|
||||
INPUT[Input Module<br/>输入处理]
|
||||
RENDER[Render Module<br/>渲染系统]
|
||||
end
|
||||
|
|
@ -106,9 +106,9 @@ flowchart TB
|
|||
INPUT_SYS[Input System<br/>输入系统]
|
||||
end
|
||||
|
||||
APP --> MR
|
||||
APP --> MOD
|
||||
APP --> SL
|
||||
MR --> Modules
|
||||
MOD --> Modules
|
||||
SL --> Services
|
||||
SCENE_SVC --> SCENE
|
||||
SCENE --> NODE
|
||||
|
|
@ -127,11 +127,12 @@ flowchart TB
|
|||
|
||||
| 模块 | 职责 | 优先级 |
|
||||
|-----|------|-------|
|
||||
| Config | 配置管理 | Core (0) |
|
||||
| Platform | 平台检测 | Core (0) |
|
||||
| Window | 窗口管理 | Core (0) |
|
||||
| Input | 输入处理 | Input (50) |
|
||||
| Render | 渲染系统 | Graphics (100) |
|
||||
| Logger | 日志系统 | -1 |
|
||||
| Config | 配置管理 | 0 |
|
||||
| Platform | 平台检测 | 10 |
|
||||
| Window | 窗口管理 | 20 |
|
||||
| Input | 输入处理 | 30 |
|
||||
| Render | 渲染系统 | 40 |
|
||||
|
||||
### 服务系统
|
||||
|
||||
|
|
@ -213,8 +214,10 @@ int main() {
|
|||
config.appName = "My Game";
|
||||
config.appVersion = "1.0.0";
|
||||
|
||||
// 初始化
|
||||
// 获取应用实例
|
||||
Application& app = Application::get();
|
||||
|
||||
// 初始化
|
||||
if (!app.init(config)) {
|
||||
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
|
||||
|
|
@ -300,11 +343,13 @@ eventService->addListener(EventType::GamepadButtonPressed, [](Event& e) {
|
|||
| 示例 | 说明 |
|
||||
|-----|------|
|
||||
| `demo_basic` | 基础示例:场景图、输入事件、视口适配 |
|
||||
| `demo_hello_module` | 自定义模块示例:展示如何创建和注册模块 |
|
||||
|
||||
运行示例:
|
||||
|
||||
```bash
|
||||
xmake run demo_basic
|
||||
xmake run demo_hello_module
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -339,46 +384,36 @@ Extra2D/
|
|||
│ │ ├── KHR/ # KHR 平台头文件
|
||||
│ │ ├── extra2d/ # 引擎公共头文件
|
||||
│ │ │ ├── app/ # 应用程序
|
||||
│ │ │ ├── audio/ # 音频配置
|
||||
│ │ │ ├── config/ # 配置系统
|
||||
│ │ │ ├── core/ # 核心类型
|
||||
│ │ │ ├── debug/ # 调试配置
|
||||
│ │ │ ├── core/ # 核心类型、模块基类
|
||||
│ │ │ ├── event/ # 事件系统
|
||||
│ │ │ ├── graphics/ # 图形渲染
|
||||
│ │ │ │ └── opengl/ # OpenGL 实现
|
||||
│ │ │ ├── input/ # 输入配置
|
||||
│ │ │ ├── modules/ # 内置模块
|
||||
│ │ │ ├── platform/ # 平台抽象
|
||||
│ │ │ ├── resource/ # 资源配置
|
||||
│ │ │ ├── scene/ # 场景系统
|
||||
│ │ │ ├── script/ # 脚本系统
|
||||
│ │ │ ├── services/ # 服务接口
|
||||
│ │ │ └── utils/ # 工具库
|
||||
│ │ ├── glad/ # OpenGL 加载器
|
||||
│ │ └── stb/ # STB 单文件库
|
||||
│ ├── shaders/ # 着色器文件
|
||||
│ │ ├── builtin/ # 内置着色器
|
||||
│ │ ├── common/ # 公共着色器代码
|
||||
│ │ └── effects/ # 特效着色器
|
||||
│ └── src/ # 源文件
|
||||
│ ├── app/ # 应用实现
|
||||
│ ├── config/ # 配置实现
|
||||
│ ├── core/ # 核心实现
|
||||
│ ├── debug/ # 调试实现
|
||||
│ ├── event/ # 事件实现
|
||||
│ ├── glad/ # GLAD 实现
|
||||
│ ├── graphics/ # 图形实现
|
||||
│ │ └── opengl/ # OpenGL 实现
|
||||
│ ├── modules/ # 模块实现
|
||||
│ ├── platform/ # 平台实现
|
||||
│ │ └── backends/ # 后端实现
|
||||
│ │ └── sdl2/ # SDL2 后端
|
||||
│ ├── resource/ # 资源实现
|
||||
│ ├── scene/ # 场景实现
|
||||
│ ├── script/ # 脚本实现
|
||||
│ ├── services/ # 服务实现
|
||||
│ └── utils/ # 工具实现
|
||||
├── docs/ # 文档
|
||||
├── examples/ # 示例程序
|
||||
│ └── basic/ # 基础示例
|
||||
│ ├── basic/ # 基础示例
|
||||
│ └── hello_module/ # 自定义模块示例
|
||||
└── xmake/ # 构建配置
|
||||
└── toolchains/ # 工具链配置
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,183 +1,79 @@
|
|||
#include "hello_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_helloModuleId = INVALID_MODULE_ID;
|
||||
|
||||
/**
|
||||
* @brief 获取Hello模块标识符
|
||||
*/
|
||||
ModuleId get_hello_module_id() {
|
||||
return s_helloModuleId;
|
||||
HelloModule::HelloModule()
|
||||
: Module()
|
||||
, config_()
|
||||
, time_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从JSON加载配置
|
||||
*/
|
||||
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;
|
||||
HelloModule::~HelloModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存配置到JSON
|
||||
*/
|
||||
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;
|
||||
void HelloModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
HelloModuleInitializer::HelloModuleInitializer()
|
||||
: moduleId_(INVALID_MODULE_ID)
|
||||
, initialized_(false) {
|
||||
}
|
||||
if (config_.greeting.empty()) {
|
||||
config_.greeting = "Hello, Extra2D!";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
HelloModuleInitializer::~HelloModuleInitializer() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
if (config_.repeatCount <= 0) {
|
||||
config_.repeatCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
setInitialized(true);
|
||||
|
||||
E2D_LOG_INFO("HelloModule initialized");
|
||||
E2D_LOG_INFO(" Greeting: {}", config_.greeting);
|
||||
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
|
||||
E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging);
|
||||
|
||||
|
||||
sayHello();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
*/
|
||||
void HelloModuleInitializer::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
void HelloModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config_.enableLogging) {
|
||||
E2D_LOG_INFO("HelloModule shutdown - Goodbye!");
|
||||
}
|
||||
|
||||
initialized_ = false;
|
||||
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行问候操作
|
||||
*/
|
||||
void HelloModuleInitializer::sayHello() const {
|
||||
if (!config_.enableLogging) return;
|
||||
|
||||
void HelloModule::onUpdate(UpdateContext& ctx) {
|
||||
if (!isInitialized()) {
|
||||
ctx.next();
|
||||
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) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/module_config.h>
|
||||
#include <extra2d/config/module_initializer.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -16,113 +15,54 @@ struct HelloModuleConfigData {
|
|||
};
|
||||
|
||||
/**
|
||||
* @brief Hello模块配置类
|
||||
* @brief Hello模块
|
||||
*
|
||||
* 这是一个简单的自定义模块示例,展示如何:
|
||||
* 1. 定义模块配置数据结构
|
||||
* 2. 实现IModuleConfig接口
|
||||
* 3. 支持JSON配置加载/保存
|
||||
* 1. 继承 Module 基类
|
||||
* 2. 实现生命周期方法
|
||||
* 3. 使用 Application::use() 注册模块
|
||||
*/
|
||||
class HelloModuleConfig : public IModuleConfig {
|
||||
class HelloModule : public Module {
|
||||
public:
|
||||
HelloModuleConfigData config;
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
HelloModule();
|
||||
|
||||
/**
|
||||
* @brief 获取模块信息
|
||||
* @brief 析构函数
|
||||
*/
|
||||
ModuleInfo getModuleInfo() const override {
|
||||
ModuleInfo info;
|
||||
info.id = 0;
|
||||
info.name = "HelloModule";
|
||||
info.version = "1.0.0";
|
||||
info.priority = ModulePriority::User;
|
||||
info.enabled = true;
|
||||
return info;
|
||||
}
|
||||
~HelloModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取配置节名称
|
||||
* @brief 获取模块名称
|
||||
*/
|
||||
std::string getConfigSectionName() const override {
|
||||
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_; }
|
||||
const char* getName() const override { return "HelloModule"; }
|
||||
|
||||
/**
|
||||
* @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_; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块标识符
|
||||
*/
|
||||
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||
void setConfig(const HelloModuleConfigData& config) { config_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 执行问候操作
|
||||
|
|
@ -130,19 +70,8 @@ public:
|
|||
void sayHello() const;
|
||||
|
||||
private:
|
||||
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||
bool initialized_ = false;
|
||||
HelloModuleConfigData config_;
|
||||
float time_ = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取Hello模块标识符
|
||||
*/
|
||||
ModuleId get_hello_module_id();
|
||||
|
||||
/**
|
||||
* @brief 注册Hello模块
|
||||
*/
|
||||
void register_hello_module();
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#include "hello_module.h"
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/config/module_registry.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
|
@ -21,54 +20,21 @@ public:
|
|||
E2D_LOG_INFO("HelloScene entered");
|
||||
|
||||
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:
|
||||
float time_ = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 应用程序入口
|
||||
*/
|
||||
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();
|
||||
|
||||
HelloModule helloModule;
|
||||
app.use(helloModule);
|
||||
|
||||
AppConfig appConfig;
|
||||
appConfig.appName = "HelloModule Example";
|
||||
appConfig.appVersion = "1.0.0";
|
||||
|
|
@ -78,24 +44,12 @@ int main(int argc, char *argv[]) {
|
|||
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();
|
||||
app.enterScene(scene);
|
||||
|
||||
E2D_LOG_INFO("Starting main loop...");
|
||||
E2D_LOG_INFO("Press ESC or close window to exit");
|
||||
E2D_LOG_INFO("");
|
||||
|
||||
app.run();
|
||||
|
||||
E2D_LOG_INFO("Application shutting down...");
|
||||
|
||||
app.shutdown();
|
||||
|
||||
E2D_LOG_INFO("Application shutdown complete");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue