From 34fe0bafcb8b13888cff4780637db313c7b3f7b5 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sun, 15 Feb 2026 08:51:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(config):=20=E9=87=8D=E6=9E=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=B3=BB=E7=BB=9F=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=8C=96=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(config): 添加新的配置管理器、加载器和平台检测器 feat(module): 实现模块注册表和初始化系统 refactor(window): 将窗口配置迁移到新的配置系统 refactor(input): 重构输入系统以支持模块化 refactor(render): 更新渲染系统以适配新架构 refactor(graphics): 移除冗余API并简化渲染目标接口 refactor(app): 重构应用类以整合新的配置和模块系统 refactor(color): 移除废弃的ColorConstants结构 style: 清理代码并修复格式问题 --- Extra2D/include/extra2d/app/application.h | 172 +++- Extra2D/include/extra2d/config/app_config.h | 226 +++++ .../include/extra2d/config/config_loader.h | 189 ++++ .../include/extra2d/config/config_manager.h | 314 ++++++ .../include/extra2d/config/module_config.h | 103 ++ .../extra2d/config/module_initializer.h | 63 ++ .../include/extra2d/config/module_registry.h | 155 +++ .../include/extra2d/config/platform_config.h | 75 ++ .../extra2d/config/platform_detector.h | 174 ++++ Extra2D/include/extra2d/core/color.h | 13 - Extra2D/include/extra2d/extra2d.h | 10 + .../include/extra2d/graphics/render_module.h | 147 +++ .../include/extra2d/graphics/render_target.h | 19 - .../include/extra2d/platform/input_module.h | 148 +++ Extra2D/include/extra2d/platform/iwindow.h | 20 +- .../include/extra2d/platform/window_module.h | 136 +++ Extra2D/src/app/application.cpp | 198 +++- Extra2D/src/config/app_config.cpp | 681 +++++++++++++ Extra2D/src/config/config_loader_ini.cpp | 952 ++++++++++++++++++ Extra2D/src/config/config_loader_json.cpp | 763 ++++++++++++++ Extra2D/src/config/config_manager.cpp | 516 ++++++++++ Extra2D/src/config/module_registry.cpp | 212 ++++ Extra2D/src/config/platform_config.cpp | 391 +++++++ Extra2D/src/config/platform_detector.cpp | 625 ++++++++++++ Extra2D/src/graphics/render_module.cpp | 313 ++++++ Extra2D/src/graphics/render_target.cpp | 33 - .../platform/backends/sdl2/sdl2_window.cpp | 16 +- .../src/platform/backends/sdl2/sdl2_window.h | 2 +- Extra2D/src/platform/input_module.cpp | 270 +++++ Extra2D/src/platform/window_module.cpp | 340 +++++++ 30 files changed, 7109 insertions(+), 167 deletions(-) create mode 100644 Extra2D/include/extra2d/config/app_config.h create mode 100644 Extra2D/include/extra2d/config/config_loader.h create mode 100644 Extra2D/include/extra2d/config/config_manager.h create mode 100644 Extra2D/include/extra2d/config/module_config.h create mode 100644 Extra2D/include/extra2d/config/module_initializer.h create mode 100644 Extra2D/include/extra2d/config/module_registry.h create mode 100644 Extra2D/include/extra2d/config/platform_config.h create mode 100644 Extra2D/include/extra2d/config/platform_detector.h create mode 100644 Extra2D/include/extra2d/graphics/render_module.h create mode 100644 Extra2D/include/extra2d/platform/input_module.h create mode 100644 Extra2D/include/extra2d/platform/window_module.h create mode 100644 Extra2D/src/config/app_config.cpp create mode 100644 Extra2D/src/config/config_loader_ini.cpp create mode 100644 Extra2D/src/config/config_loader_json.cpp create mode 100644 Extra2D/src/config/config_manager.cpp create mode 100644 Extra2D/src/config/module_registry.cpp create mode 100644 Extra2D/src/config/platform_config.cpp create mode 100644 Extra2D/src/config/platform_detector.cpp create mode 100644 Extra2D/src/graphics/render_module.cpp create mode 100644 Extra2D/src/platform/input_module.cpp create mode 100644 Extra2D/src/platform/window_module.cpp diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h index 17ab0ce..98c7945 100644 --- a/Extra2D/include/extra2d/app/application.h +++ b/Extra2D/include/extra2d/app/application.h @@ -1,6 +1,10 @@ #pragma once #include +#include +#include +#include +#include #include #include #include @@ -15,36 +19,18 @@ class EventDispatcher; class Camera; class ViewportAdapter; -/** - * @brief 平台类型 - */ -enum class PlatformType { Auto = 0, PC, Switch }; - -/** - * @brief 应用配置 - */ -struct AppConfig { - std::string title = "Extra2D Application"; - int width = 1280; - int height = 720; - bool fullscreen = false; - bool resizable = true; - bool vsync = true; - int fpsLimit = 0; - BackendType renderBackend = BackendType::OpenGL; - int msaaSamples = 0; - PlatformType platform = PlatformType::Auto; - bool enableCursors = true; - bool enableDpiScale = false; - - std::string backend = "sdl2"; -}; - /** * @brief Application 单例 - 应用主控 + * + * 负责管理应用程序的整个生命周期,包括初始化、主循环、渲染和关闭。 + * 集成了 ConfigManager 和 ModuleRegistry 系统进行配置和模块管理。 */ class Application { public: + /** + * @brief 获取单例实例 + * @return Application 实例引用 + */ static Application& get(); Application(const Application&) = delete; @@ -52,57 +38,175 @@ public: /** * @brief 使用默认配置初始化 + * @return 初始化成功返回 true */ bool init(); /** * @brief 使用配置结构初始化 + * @param config 应用配置 + * @return 初始化成功返回 true */ bool init(const AppConfig& config); /** * @brief 从配置文件初始化 - * @param path 配置文件路径(支持 .json 和 .ini) + * @param configPath 配置文件路径(支持 .json 和 .ini) + * @return 初始化成功返回 true */ - bool init(const std::string& path); + bool init(const std::string& configPath); + /** + * @brief 关闭应用程序 + */ void shutdown(); + + /** + * @brief 运行主循环 + */ void run(); + + /** + * @brief 退出应用程序 + */ void quit(); + /** + * @brief 暂停应用程序 + */ void pause(); + + /** + * @brief 恢复应用程序 + */ void resume(); + + /** + * @brief 检查是否暂停 + * @return 暂停状态返回 true + */ bool isPaused() const { return paused_; } + + /** + * @brief 检查是否运行中 + * @return 运行中返回 true + */ bool isRunning() const { return running_; } + /** + * @brief 获取窗口接口 + * @return 窗口接口引用 + */ IWindow& window() { return *window_; } + + /** + * @brief 获取渲染后端 + * @return 渲染后端引用 + */ RenderBackend& renderer() { return *renderer_; } + + /** + * @brief 获取输入接口 + * @return 输入接口引用 + */ IInput& input(); + + /** + * @brief 获取场景管理器 + * @return 场景管理器引用 + */ SceneManager& scenes(); + + /** + * @brief 获取定时器管理器 + * @return 定时器管理器引用 + */ TimerManager& timers(); + + /** + * @brief 获取事件队列 + * @return 事件队列引用 + */ EventQueue& eventQueue(); + + /** + * @brief 获取事件分发器 + * @return 事件分发器引用 + */ EventDispatcher& eventDispatcher(); + + /** + * @brief 获取相机 + * @return 相机引用 + */ Camera& camera(); + + /** + * @brief 获取视口适配器 + * @return 视口适配器引用 + */ ViewportAdapter& viewportAdapter(); + /** + * @brief 进入场景 + * @param scene 场景指针 + */ void enterScene(Ptr scene); + /** + * @brief 获取帧时间 + * @return 帧时间(秒) + */ float deltaTime() const { return deltaTime_; } + + /** + * @brief 获取总运行时间 + * @return 总运行时间(秒) + */ float totalTime() const { return totalTime_; } + + /** + * @brief 获取当前帧率 + * @return 帧率(FPS) + */ int fps() const { return currentFps_; } - const AppConfig& getConfig() const { return config_; } + /** + * @brief 获取配置管理器 + * @return 配置管理器引用 + */ + ConfigManager& config(); + + /** + * @brief 获取应用配置 + * @return 应用配置常量引用 + */ + const AppConfig& getConfig() const; private: Application() = default; ~Application(); + /** + * @brief 内部初始化实现 + * @return 初始化成功返回 true + */ bool initImpl(); - void mainLoop(); - void update(); - void render(); - AppConfig config_; + /** + * @brief 主循环 + */ + void mainLoop(); + + /** + * @brief 更新逻辑 + */ + void update(); + + /** + * @brief 渲染 + */ + void render(); UniquePtr window_; UniquePtr renderer_; @@ -124,6 +228,10 @@ private: int frameCount_ = 0; float fpsTimer_ = 0.0f; int currentFps_ = 0; + + ModuleId windowModuleId_ = INVALID_MODULE_ID; + ModuleId inputModuleId_ = INVALID_MODULE_ID; + ModuleId renderModuleId_ = INVALID_MODULE_ID; }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/config/app_config.h b/Extra2D/include/extra2d/config/app_config.h new file mode 100644 index 0000000..dbfb589 --- /dev/null +++ b/Extra2D/include/extra2d/config/app_config.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 窗口模式枚举 +// ============================================================================ +enum class WindowMode { + Windowed, + Fullscreen, + Borderless +}; + +// ============================================================================ +// 窗口配置数据 +// ============================================================================ +struct WindowConfigData { + std::string title = "Extra2D Application"; + int width = 1280; + int height = 720; + int minWidth = 320; + int minHeight = 240; + int maxWidth = 0; + int maxHeight = 0; + WindowMode mode = WindowMode::Windowed; + bool resizable = true; + bool borderless = false; + bool alwaysOnTop = false; + bool centered = true; + int posX = -1; + int posY = -1; + bool hideOnClose = false; + bool minimizeOnClose = true; + float opacity = 1.0f; + bool transparentFramebuffer = false; + bool highDPI = true; + float contentScale = 1.0f; + bool vsync = true; + int multisamples = 0; + bool visible = true; + bool decorated = true; + + bool isSizeValid() const { return width > 0 && height > 0; } + bool hasPosition() const { return posX >= 0 && posY >= 0; } + float aspectRatio() const { return static_cast(width) / static_cast(height); } + bool isFullscreen() const { return mode == WindowMode::Fullscreen; } + bool isBorderless() const { return mode == WindowMode::Borderless || borderless; } +}; + +// ============================================================================ +// 渲染配置数据 +// ============================================================================ +struct RenderConfigData { + BackendType backend = BackendType::OpenGL; + int targetFPS = 60; + bool vsync = true; + bool tripleBuffering = false; + int multisamples = 0; + bool sRGBFramebuffer = false; + Color clearColor{0.0f, 0.0f, 0.0f, 1.0f}; + int maxTextureSize = 0; + int textureAnisotropy = 1; + bool wireframeMode = false; + bool depthTest = false; + bool blending = true; + bool dithering = false; + int spriteBatchSize = 1000; + int maxRenderTargets = 1; + bool allowShaderHotReload = false; + std::string shaderCachePath; + + bool isMultisampleEnabled() const { return multisamples > 0; } + bool isFPSCapped() const { return targetFPS > 0; } +}; + +// ============================================================================ +// 音频配置数据 +// ============================================================================ +struct AudioConfigData { + bool enabled = true; + int masterVolume = 100; + int musicVolume = 100; + int sfxVolume = 100; + int voiceVolume = 100; + int ambientVolume = 100; + int frequency = 44100; + int channels = 2; + int chunkSize = 2048; + int maxChannels = 16; + bool spatialAudio = false; + float listenerPosition[3] = {0.0f, 0.0f, 0.0f}; + + bool isValidVolume(int volume) const { return volume >= 0 && volume <= 100; } + float volumeToFloat(int volume) const { return static_cast(volume) / 100.0f; } +}; + +// ============================================================================ +// 调试配置数据 +// ============================================================================ +struct DebugConfigData { + bool enabled = false; + bool showFPS = false; + bool showMemoryUsage = false; + bool showRenderStats = false; + bool showColliders = false; + bool showGrid = false; + bool logToFile = false; + bool logToConsole = true; + int logLevel = 2; + bool breakOnAssert = true; + bool enableProfiling = false; + std::string logFilePath; + std::vector debugFlags; + + bool hasDebugFlag(const std::string& flag) const; + void addDebugFlag(const std::string& flag); + void removeDebugFlag(const std::string& flag); +}; + +// ============================================================================ +// 输入配置数据 +// ============================================================================ +struct InputConfigData { + bool enabled = true; + bool rawMouseInput = false; + float mouseSensitivity = 1.0f; + bool invertMouseY = false; + bool invertMouseX = false; + float deadzone = 0.15f; + float triggerThreshold = 0.5f; + bool enableVibration = true; + int maxGamepads = 4; + bool autoConnectGamepads = true; + std::string gamepadMappingFile; + + bool isDeadzoneValid() const { return deadzone >= 0.0f && deadzone <= 1.0f; } +}; + +// ============================================================================ +// 资源配置数据 +// ============================================================================ +struct ResourceConfigData { + std::string assetRootPath = "assets"; + std::string cachePath = "cache"; + std::string savePath = "saves"; + std::string configPath = "config"; + std::string logPath = "logs"; + bool useAssetCache = true; + int maxCacheSize = 512; + bool hotReloadEnabled = false; + float hotReloadInterval = 1.0f; + bool compressTextures = false; + bool preloadCommonAssets = true; + std::vector searchPaths; + + void addSearchPath(const std::string& path); + void removeSearchPath(const std::string& path); + bool hasSearchPath(const std::string& path) const; +}; + +// ============================================================================ +// 应用统一配置 +// ============================================================================ +struct AppConfig { + WindowConfigData window; + RenderConfigData render; + AudioConfigData audio; + DebugConfigData debug; + InputConfigData input; + ResourceConfigData resource; + std::string appName = "Extra2D App"; + std::string appVersion = "1.0.0"; + std::string organization = ""; + std::string configFile = "config.json"; + PlatformType targetPlatform = PlatformType::Auto; + + /** + * @brief 创建默认配置 + * @return 默认的应用配置实例 + */ + static AppConfig createDefault(); + + /** + * @brief 验证配置的有效性 + * @return 如果配置有效返回 true,否则返回 false + */ + bool validate() const; + + /** + * @brief 应用平台约束 + * @param platform 平台配置接口 + */ + void applyPlatformConstraints(const PlatformConfig& platform); + + /** + * @brief 重置为默认值 + */ + void reset(); + + /** + * @brief 合并另一个配置(非默认值覆盖当前值) + * @param other 要合并的配置 + */ + void merge(const AppConfig& other); + + /** + * @brief 检查配置是否有效 + * @return 如果所有必要字段都有效返回 true + */ + bool isValid() const { return validate(); } + + /** + * @brief 获取窗口宽高比 + * @return 窗口的宽高比 + */ + float aspectRatio() const { return window.aspectRatio(); } +}; + +} diff --git a/Extra2D/include/extra2d/config/config_loader.h b/Extra2D/include/extra2d/config/config_loader.h new file mode 100644 index 0000000..f5430f6 --- /dev/null +++ b/Extra2D/include/extra2d/config/config_loader.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 配置加载结果 +// ============================================================================ +struct ConfigLoadResult { + bool success = false; + std::string errorMessage; + int errorLine = -1; + std::string errorField; + + static ConfigLoadResult ok() { return ConfigLoadResult{true, "", -1, ""}; } + static ConfigLoadResult error(const std::string& msg, int line = -1, const std::string& field = "") { + return ConfigLoadResult{false, msg, line, field}; + } + + bool isOk() const { return success; } + bool hasError() const { return !success; } +}; + +// ============================================================================ +// 配置保存结果 +// ============================================================================ +struct ConfigSaveResult { + bool success = false; + std::string errorMessage; + + static ConfigSaveResult ok() { return ConfigSaveResult{true, ""}; } + static ConfigSaveResult error(const std::string& msg) { + return ConfigSaveResult{false, msg}; + } + + bool isOk() const { return success; } + bool hasError() const { return !success; } +}; + +// ============================================================================ +// 配置加载器抽象接口 +// ============================================================================ +class ConfigLoader { +public: + virtual ~ConfigLoader() = default; + + /** + * @brief 从文件加载配置 + * @param filepath 配置文件路径 + * @param config 输出的配置对象 + * @return 加载结果 + */ + virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0; + + /** + * @brief 保存配置到文件 + * @param filepath 配置文件路径 + * @param config 要保存的配置对象 + * @return 保存结果 + */ + virtual ConfigSaveResult save(const std::string& filepath, const AppConfig& config) = 0; + + /** + * @brief 从字符串加载配置 + * @param content 配置内容字符串 + * @param config 输出的配置对象 + * @return 加载结果 + */ + virtual ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) = 0; + + /** + * @brief 将配置序列化为字符串 + * @param config 配置对象 + * @return 序列化后的字符串 + */ + virtual std::string saveToString(const AppConfig& config) = 0; + + /** + * @brief 获取支持的文件扩展名 + * @return 文件扩展名(不含点号,如 "json") + */ + virtual const char* extension() const = 0; + + /** + * @brief 检查是否支持指定文件 + * @param filepath 文件路径 + * @return 如果支持返回 true + */ + virtual bool supportsFile(const std::string& filepath) const = 0; + + /** + * @brief 克隆加载器实例 + * @return 新的加载器实例 + */ + virtual UniquePtr clone() const = 0; +}; + +// ============================================================================ +// JSON 配置加载器 +// ============================================================================ +class JsonConfigLoader : public ConfigLoader { +public: + JsonConfigLoader() = default; + ~JsonConfigLoader() override = default; + + ConfigLoadResult load(const std::string& filepath, AppConfig& config) override; + ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override; + ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override; + std::string saveToString(const AppConfig& config) override; + const char* extension() const override { return "json"; } + bool supportsFile(const std::string& filepath) const override; + UniquePtr clone() const override; + +private: + ConfigLoadResult parseWindowConfig(const void* jsonValue, WindowConfigData& window); + ConfigLoadResult parseRenderConfig(const void* jsonValue, RenderConfigData& render); + ConfigLoadResult parseAudioConfig(const void* jsonValue, AudioConfigData& audio); + ConfigLoadResult parseDebugConfig(const void* jsonValue, DebugConfigData& debug); + ConfigLoadResult parseInputConfig(const void* jsonValue, InputConfigData& input); + ConfigLoadResult parseResourceConfig(const void* jsonValue, ResourceConfigData& resource); + + void serializeWindowConfig(void* jsonValue, const WindowConfigData& window); + void serializeRenderConfig(void* jsonValue, const RenderConfigData& render); + void serializeAudioConfig(void* jsonValue, const AudioConfigData& audio); + void serializeDebugConfig(void* jsonValue, const DebugConfigData& debug); + void serializeInputConfig(void* jsonValue, const InputConfigData& input); + void serializeResourceConfig(void* jsonValue, const ResourceConfigData& resource); +}; + +// ============================================================================ +// INI 配置加载器 +// ============================================================================ +class IniConfigLoader : public ConfigLoader { +public: + IniConfigLoader() = default; + ~IniConfigLoader() override = default; + + ConfigLoadResult load(const std::string& filepath, AppConfig& config) override; + ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override; + ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override; + std::string saveToString(const AppConfig& config) override; + const char* extension() const override { return "ini"; } + bool supportsFile(const std::string& filepath) const override; + UniquePtr clone() const override; + +private: + std::string sectionKey(const std::string& section, const std::string& key) const; + ConfigLoadResult parseInt(const std::string& value, int& result, const std::string& fieldName); + ConfigLoadResult parseFloat(const std::string& value, float& result, const std::string& fieldName); + ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName); +}; + +// ============================================================================ +// 配置加载器工厂 +// ============================================================================ +class ConfigLoaderFactory { +public: + /** + * @brief 根据文件扩展名创建加载器 + * @param extension 文件扩展名(不含点号) + * @return 配置加载器实例,如果不支持返回 nullptr + */ + static UniquePtr create(const std::string& extension); + + /** + * @brief 根据文件路径创建加载器 + * @param filepath 文件路径 + * @return 配置加载器实例,如果不支持返回 nullptr + */ + static UniquePtr createForFile(const std::string& filepath); + + /** + * @brief 检查是否支持指定扩展名 + * @param extension 文件扩展名 + * @return 如果支持返回 true + */ + static bool isExtensionSupported(const std::string& extension); + + /** + * @brief 获取所有支持的扩展名 + * @return 支持的扩展名列表 + */ + static std::vector getSupportedExtensions(); +}; + +} diff --git a/Extra2D/include/extra2d/config/config_manager.h b/Extra2D/include/extra2d/config/config_manager.h new file mode 100644 index 0000000..17412e8 --- /dev/null +++ b/Extra2D/include/extra2d/config/config_manager.h @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 配置变更事件 +// ============================================================================ +struct ConfigChangeEvent { + std::string section; + std::string field; + std::string oldValue; + std::string newValue; +}; + +// ============================================================================ +// 配置变更回调类型 +// ============================================================================ +using ConfigChangeCallback = Function; + +// ============================================================================ +// 配置管理器(单例) +// ============================================================================ +class ConfigManager { +public: + using ModuleConfigPtr = Ptr; + + /** + * @brief 获取单例实例 + * @return 配置管理器实例引用 + */ + static ConfigManager &instance(); + + /** + * @brief 初始化配置管理器 + * @param configPath 配置文件路径 + * @return 如果初始化成功返回 true + */ + bool initialize(const std::string &configPath = "config.json"); + + /** + * @brief 关闭配置管理器 + */ + void shutdown(); + + /** + * @brief 检查是否已初始化 + * @return 如果已初始化返回 true + */ + bool isInitialized() const; + + /** + * @brief 加载配置文件 + * @param filepath 配置文件路径(可选,默认使用初始化时的路径) + * @return 加载结果 + */ + ConfigLoadResult loadConfig(const std::string &filepath = ""); + + /** + * @brief 保存配置到文件 + * @param filepath 配置文件路径(可选,默认使用初始化时的路径) + * @return 保存结果 + */ + ConfigSaveResult saveConfig(const std::string &filepath = ""); + + /** + * @brief 重新加载配置 + * @return 加载结果 + */ + ConfigLoadResult reload(); + + /** + * @brief 获取应用配置 + * @return 应用配置的常量引用 + */ + const AppConfig &appConfig() const; + + /** + * @brief 获取可修改的应用配置 + * @return 应用配置的引用 + */ + AppConfig &appConfig(); + + /** + * @brief 设置应用配置 + * @param config 新的配置 + */ + void setAppConfig(const AppConfig &config); + + /** + * @brief 获取平台配置 + * @return 平台配置接口指针 + */ + PlatformConfig *platformConfig(); + + /** + * @brief 获取平台配置(常量版本) + * @return 平台配置接口常量指针 + */ + const PlatformConfig *platformConfig() const; + + /** + * @brief 注册配置变更回调 + * @param callback 回调函数 + * @return 回调ID,用于取消注册 + */ + int registerChangeCallback(ConfigChangeCallback callback); + + /** + * @brief 取消注册配置变更回调 + * @param callbackId 回调ID + */ + void unregisterChangeCallback(int callbackId); + + /** + * @brief 清除所有变更回调 + */ + void clearChangeCallbacks(); + + /** + * @brief 注册模块配置 + * @param moduleName 模块名称 + * @param config 模块配置指针 + */ + void registerModuleConfig(const std::string &moduleName, Ptr config); + + /** + * @brief 获取模块配置 + * @param moduleName 模块名称 + * @return 模块配置指针 + */ + template + Ptr getModuleConfig(const std::string &moduleName) const { + auto it = m_moduleConfigs.find(moduleName); + if (it != m_moduleConfigs.end()) { + return std::static_pointer_cast(it->second); + } + return nullptr; + } + + /** + * @brief 移除模块配置 + * @param moduleName 模块名称 + */ + void removeModuleConfig(const std::string &moduleName); + + /** + * @brief 检查模块配置是否存在 + * @param moduleName 模块名称 + * @return 如果存在返回 true + */ + bool hasModuleConfig(const std::string &moduleName) const; + + /** + * @brief 设置配置值(字符串) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ + void setValue(const std::string §ion, const std::string &key, + const std::string &value); + + /** + * @brief 设置配置值(整数) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ + void setValue(const std::string §ion, const std::string &key, int value); + + /** + * @brief 设置配置值(浮点数) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ + void setValue(const std::string §ion, const std::string &key, + float value); + + /** + * @brief 设置配置值(布尔值) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ + void setValue(const std::string §ion, const std::string &key, bool value); + + /** + * @brief 获取配置值(字符串) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ + std::string getValue(const std::string §ion, const std::string &key, + const std::string &defaultValue = "") const; + + /** + * @brief 获取配置值(整数) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ + int getIntValue(const std::string §ion, const std::string &key, + int defaultValue = 0) const; + + /** + * @brief 获取配置值(浮点数) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ + float getFloatValue(const std::string §ion, const std::string &key, + float defaultValue = 0.0f) const; + + /** + * @brief 获取配置值(布尔值) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ + bool getBoolValue(const std::string §ion, const std::string &key, + bool defaultValue = false) const; + + /** + * @brief 重置配置到默认值 + */ + void resetToDefaults(); + + /** + * @brief 检查配置是否有未保存的更改 + * @return 如果有未保存的更改返回 true + */ + bool hasUnsavedChanges() const; + + /** + * @brief 标记配置为已修改 + */ + void markModified(); + + /** + * @brief 清除修改标记 + */ + void clearModified(); + + /** + * @brief 获取配置文件路径 + * @return 配置文件路径 + */ + const std::string &configPath() const { return m_configPath; } + + /** + * @brief 设置自动保存 + * @param enabled 是否启用自动保存 + * @param interval 自动保存间隔(秒) + */ + void setAutoSave(bool enabled, float interval = 30.0f); + + /** + * @brief 检查是否启用自动保存 + * @return 如果启用自动保存返回 true + */ + bool isAutoSaveEnabled() const { return m_autoSaveEnabled; } + + /** + * @brief 更新配置管理器(用于自动保存) + * @param deltaTime 帧时间(秒) + */ + void update(float deltaTime); + +private: + ConfigManager(); + ~ConfigManager(); + ConfigManager(const ConfigManager &) = delete; + ConfigManager &operator=(const ConfigManager &) = delete; + + void notifyChangeCallbacks(const ConfigChangeEvent &event); + void applyConfigToInternal(const AppConfig &config); + void extractConfigFromInternal(AppConfig &config) const; + + AppConfig m_appConfig; + UniquePtr m_platformConfig; + UniquePtr m_loader; + std::string m_configPath; + bool m_initialized = false; + bool m_modified = false; + mutable std::mutex m_mutex; + + std::unordered_map m_changeCallbacks; + int m_nextCallbackId = 1; + + std::unordered_map m_moduleConfigs; + + bool m_autoSaveEnabled = false; + float m_autoSaveInterval = 30.0f; + float m_autoSaveTimer = 0.0f; +}; + +// ============================================================================ +// 便捷宏定义 +// ============================================================================ +#define CONFIG_MANAGER ConfigManager::instance() + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/config/module_config.h b/Extra2D/include/extra2d/config/module_config.h new file mode 100644 index 0000000..d67691a --- /dev/null +++ b/Extra2D/include/extra2d/config/module_config.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include + +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 diff --git a/Extra2D/include/extra2d/config/module_initializer.h b/Extra2D/include/extra2d/config/module_initializer.h new file mode 100644 index 0000000..a4f50c2 --- /dev/null +++ b/Extra2D/include/extra2d/config/module_initializer.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +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 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()>; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/config/module_registry.h b/Extra2D/include/extra2d/config/module_registry.h new file mode 100644 index 0000000..3faa371 --- /dev/null +++ b/Extra2D/include/extra2d/config/module_registry.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 模块注册表条目结构体 + * 存储模块的配置和初始化器工厂 + */ +struct ModuleEntry { + ModuleId id; ///< 模块标识符 + UniquePtr config; ///< 模块配置 + ModuleInitializerFactory initializerFactory;///< 初始化器工厂函数 + 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 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 + */ + UniquePtr createInitializer(ModuleId id) const; + + /** + * @brief 获取所有已注册模块标识符 + * @return 模块标识符列表 + */ + std::vector getAllModules() const; + + /** + * @brief 获取模块初始化顺序 + * 根据优先级和依赖关系计算初始化顺序 + * @return 按初始化顺序排列的模块标识符列表 + */ + std::vector 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 modules_; ///< 模块注册表 + std::unordered_map 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(), \ + []() -> ::extra2d::UniquePtr<::extra2d::IModuleInitializer> { \ + return ::extra2d::makeUnique(); \ + } \ + ); \ + } + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/config/platform_config.h b/Extra2D/include/extra2d/config/platform_config.h new file mode 100644 index 0000000..c3a1782 --- /dev/null +++ b/Extra2D/include/extra2d/config/platform_config.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace extra2d { + +// ============================================================================ +// 平台类型枚举 +// ============================================================================ +enum class PlatformType { + Auto, + Windows, + Switch, + Linux, + macOS +}; + +// ============================================================================ +// 平台能力结构 +// ============================================================================ +struct PlatformCapabilities { + bool supportsWindowed = true; + bool supportsFullscreen = true; + bool supportsBorderless = true; + bool supportsCursor = true; + bool supportsCursorHide = true; + bool supportsDPIAwareness = true; + bool supportsVSync = true; + bool supportsMultiMonitor = true; + bool supportsClipboard = true; + bool supportsGamepad = true; + bool supportsTouch = false; + bool supportsKeyboard = true; + bool supportsMouse = true; + bool supportsResize = true; + bool supportsHighDPI = true; + int maxTextureSize = 16384; + int preferredScreenWidth = 1920; + int preferredScreenHeight = 1080; + float defaultDPI = 96.0f; + + bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; } + bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; } + bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; } + bool isConsole() const { return !supportsWindowed && supportsGamepad; } +}; + +// ============================================================================ +// 平台配置抽象接口 +// ============================================================================ +class PlatformConfig { +public: + virtual ~PlatformConfig() = default; + + virtual PlatformType platformType() const = 0; + virtual const char* platformName() const = 0; + virtual const PlatformCapabilities& capabilities() const = 0; + virtual void applyConstraints(struct AppConfig& config) const = 0; + virtual void applyDefaults(struct AppConfig& config) const = 0; + virtual bool validateConfig(struct AppConfig& config) const = 0; + + virtual int getRecommendedWidth() const = 0; + virtual int getRecommendedHeight() const = 0; + virtual bool isResolutionSupported(int width, int height) const = 0; +}; + +// ============================================================================ +// 平台配置工厂函数声明 +// ============================================================================ +UniquePtr createPlatformConfig(PlatformType type = PlatformType::Auto); + +const char* getPlatformTypeName(PlatformType type); + +} diff --git a/Extra2D/include/extra2d/config/platform_detector.h b/Extra2D/include/extra2d/config/platform_detector.h new file mode 100644 index 0000000..c5d6a8e --- /dev/null +++ b/Extra2D/include/extra2d/config/platform_detector.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 平台检测器工具类 +// ============================================================================ +class PlatformDetector { +public: + /** + * @brief 检测当前运行平台 + * @return 当前平台的类型 + */ + static PlatformType detect(); + + /** + * @brief 获取平台名称字符串 + * @return 平台名称(如 "Windows", "Linux", "macOS", "Switch") + */ + static const char* platformName(); + + /** + * @brief 获取指定平台类型的名称 + * @param type 平台类型 + * @return 平台名称字符串 + */ + static const char* platformName(PlatformType type); + + /** + * @brief 检查当前平台是否为桌面平台 + * @return 如果是桌面平台返回 true + */ + static bool isDesktopPlatform(); + + /** + * @brief 检查当前平台是否为游戏主机平台 + * @return 如果是游戏主机平台返回 true + */ + static bool isConsolePlatform(); + + /** + * @brief 检查当前平台是否为移动平台 + * @return 如果是移动平台返回 true + */ + static bool isMobilePlatform(); + + /** + * @brief 获取当前平台的能力 + * @return 平台能力结构 + */ + static PlatformCapabilities capabilities(); + + /** + * @brief 获取指定平台的能力 + * @param type 平台类型 + * @return 平台能力结构 + */ + static PlatformCapabilities capabilities(PlatformType type); + + /** + * @brief 获取当前平台的默认配置 + * @return 平台默认的应用配置 + */ + static AppConfig platformDefaults(); + + /** + * @brief 获取指定平台的默认配置 + * @param type 平台类型 + * @return 平台默认的应用配置 + */ + static AppConfig platformDefaults(PlatformType type); + + /** + * @brief 获取当前平台的推荐分辨率 + * @param width 输出宽度 + * @param height 输出高度 + */ + static void getRecommendedResolution(int& width, int& height); + + /** + * @brief 获取当前平台的默认 DPI + * @return 默认 DPI 值 + */ + static float getDefaultDPI(); + + /** + * @brief 检查当前平台是否支持指定功能 + * @param feature 功能名称 + * @return 如果支持返回 true + */ + static bool supportsFeature(const std::string& feature); + + /** + * @brief 获取系统内存大小 + * @return 系统内存大小(MB),如果无法获取返回 0 + */ + static int getSystemMemoryMB(); + + /** + * @brief 获取 CPU 核心数 + * @return CPU 核心数 + */ + static int getCPUCoreCount(); + + /** + * @brief 检查是否支持多线程渲染 + * @return 如果支持返回 true + */ + static bool supportsMultithreadedRendering(); + + /** + * @brief 获取平台特定的配置路径 + * @param appName 应用名称 + * @return 配置文件目录路径 + */ + static std::string getConfigPath(const std::string& appName); + + /** + * @brief 获取平台特定的存档路径 + * @param appName 应用名称 + * @return 存档文件目录路径 + */ + static std::string getSavePath(const std::string& appName); + + /** + * @brief 获取平台特定的缓存路径 + * @param appName 应用名称 + * @return 缓存文件目录路径 + */ + static std::string getCachePath(const std::string& appName); + + /** + * @brief 获取平台特定的日志路径 + * @param appName 应用名称 + * @return 日志文件目录路径 + */ + static std::string getLogPath(const std::string& appName); + + /** + * @brief 检查平台是否为小端字节序 + * @return 如果是小端字节序返回 true + */ + static bool isLittleEndian(); + + /** + * @brief 检查平台是否为大端字节序 + * @return 如果是大端字节序返回 true + */ + static bool isBigEndian(); + + /** + * @brief 获取平台信息摘要 + * @return 平台信息字符串 + */ + static std::string getPlatformSummary(); + +private: + static PlatformCapabilities getWindowsCapabilities(); + static PlatformCapabilities getLinuxCapabilities(); + static PlatformCapabilities getMacOSCapabilities(); + static PlatformCapabilities getSwitchCapabilities(); + + static AppConfig getWindowsDefaults(); + static AppConfig getLinuxDefaults(); + static AppConfig getMacOSDefaults(); + static AppConfig getSwitchDefaults(); +}; + +} diff --git a/Extra2D/include/extra2d/core/color.h b/Extra2D/include/extra2d/core/color.h index 6797137..8729586 100644 --- a/Extra2D/include/extra2d/core/color.h +++ b/Extra2D/include/extra2d/core/color.h @@ -155,17 +155,4 @@ inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f}; inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f}; } // namespace Colors -// 为了向后兼容,在 Color 结构体内提供静态引用 -struct ColorConstants { - static const Color &White; - static const Color &Black; - static const Color &Red; - static const Color &Green; - static const Color &Blue; - static const Color &Yellow; - static const Color &Cyan; - static const Color &Magenta; - static const Color &Transparent; -}; - } // namespace extra2d diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index 466c6a4..ca25447 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -8,6 +8,16 @@ #include #include +// Config +#include +#include +#include +#include +#include +#include +#include +#include + // Platform #include #include diff --git a/Extra2D/include/extra2d/graphics/render_module.h b/Extra2D/include/extra2d/graphics/render_module.h new file mode 100644 index 0000000..2479909 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/render_module.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 渲染模块配置 + * 实现 IModuleConfig 接口 + */ +class RenderModuleConfig : public IModuleConfig { +public: + BackendType backend = BackendType::OpenGL; + bool vsync = true; + int targetFPS = 60; + int multisamples = 0; + bool sRGBFramebuffer = false; + int spriteBatchSize = 1000; + + /** + * @brief 获取模块信息 + * @return 模块信息结构体 + */ + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.name = "Render"; + info.version = "1.0.0"; + info.priority = ModulePriority::Graphics; + info.enabled = true; + return info; + } + + /** + * @brief 获取配置节名称 + * @return 配置节名称字符串 + */ + std::string getConfigSectionName() const override { return "render"; } + + /** + * @brief 验证配置有效性 + * @return 如果配置有效返回 true + */ + bool validate() const override; + + /** + * @brief 应用平台约束 + * 根据平台特性调整配置 + * @param platform 目标平台类型 + */ + void applyPlatformConstraints(PlatformType platform) override; + + /** + * @brief 重置为默认配置 + */ + void resetToDefaults() override; + + /** + * @brief 从 JSON 数据加载配置 + * @param jsonData JSON 数据指针 + * @return 加载成功返回 true + */ + bool loadFromJson(const void* jsonData) override; + + /** + * @brief 保存配置到 JSON 数据 + * @param jsonData JSON 数据指针 + * @return 保存成功返回 true + */ + bool saveToJson(void* jsonData) const override; +}; + +/** + * @brief 渲染模块初始化器 + * 实现 IModuleInitializer 接口 + * 依赖窗口模块 + */ +class RenderModuleInitializer : public IModuleInitializer { +public: + /** + * @brief 构造函数 + */ + RenderModuleInitializer(); + + /** + * @brief 析构函数 + */ + ~RenderModuleInitializer() override; + + /** + * @brief 获取模块标识符 + * @return 模块唯一标识符 + */ + ModuleId getModuleId() const override { return moduleId_; } + + /** + * @brief 获取模块优先级 + * @return 模块优先级 + */ + ModulePriority getPriority() const override { return ModulePriority::Graphics; } + + /** + * @brief 获取模块依赖列表 + * @return 依赖模块标识符列表 + */ + std::vector getDependencies() const override; + + /** + * @brief 初始化模块 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ + bool initialize(const IModuleConfig* config) override; + + /** + * @brief 关闭模块 + */ + void shutdown() override; + + /** + * @brief 检查模块是否已初始化 + * @return 已初始化返回 true + */ + bool isInitialized() const override { return initialized_; } + + /** + * @brief 获取渲染器实例 + * @return 渲染后端指针 + */ + RenderBackend* getRenderer() const { return renderer_.get(); } + + /** + * @brief 设置窗口模块标识符 + * @param windowModuleId 窗口模块标识符 + */ + void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; } + +private: + ModuleId moduleId_ = INVALID_MODULE_ID; + ModuleId windowModuleId_ = INVALID_MODULE_ID; + UniquePtr renderer_; + bool initialized_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/render_target.h b/Extra2D/include/extra2d/graphics/render_target.h index 56c4187..90b0ea2 100644 --- a/Extra2D/include/extra2d/graphics/render_target.h +++ b/Extra2D/include/extra2d/graphics/render_target.h @@ -16,7 +16,6 @@ struct RenderTargetConfig { int height = 600; // 高度 PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式 bool hasDepth = true; // 是否包含深度缓冲 - bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth) bool hasStencil = false; // 是否包含模板缓冲 int samples = 1; // 多重采样数 (1 = 无MSAA) bool autoResize = true; // 是否自动调整大小 @@ -47,11 +46,6 @@ public: */ bool create(const RenderTargetConfig &config); - /** - * @brief 初始化渲染目标(create的别名,兼容旧API) - */ - bool init(const RenderTargetConfig &config) { return create(config); } - /** * @brief 从现有纹理创建渲染目标 */ @@ -62,11 +56,6 @@ public: */ void destroy(); - /** - * @brief 关闭渲染目标(destroy的别名,兼容旧API) - */ - void shutdown() { destroy(); } - /** * @brief 检查是否有效 */ @@ -144,14 +133,6 @@ public: */ void copyTo(RenderTarget &target); - /** - * @brief 复制到另一个渲染目标(blitTo的别名,兼容旧API) - * @param target 目标渲染目标 - * @param color 是否复制颜色缓冲 - * @param depth 是否复制深度缓冲 - */ - void blitTo(RenderTarget &target, bool color = true, bool depth = false); - /** * @brief 复制到屏幕 */ diff --git a/Extra2D/include/extra2d/platform/input_module.h b/Extra2D/include/extra2d/platform/input_module.h new file mode 100644 index 0000000..aa710eb --- /dev/null +++ b/Extra2D/include/extra2d/platform/input_module.h @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 输入模块配置 + * 实现 IModuleConfig 接口 + */ +class InputModuleConfig : public IModuleConfig { +public: + bool enableKeyboard = true; + bool enableMouse = true; + bool enableGamepad = true; + bool enableTouch = true; + float deadzone = 0.15f; + float mouseSensitivity = 1.0f; + + /** + * @brief 获取模块信息 + * @return 模块信息结构体 + */ + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + 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; + + /** + * @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: + /** + * @brief 构造函数 + */ + InputModuleInitializer(); + + /** + * @brief 析构函数 + */ + ~InputModuleInitializer() override; + + /** + * @brief 获取模块标识符 + * @return 模块唯一标识符 + */ + ModuleId getModuleId() const override { return moduleId_; } + + /** + * @brief 获取模块优先级 + * @return 模块优先级 + */ + ModulePriority getPriority() const override { return ModulePriority::Input; } + + /** + * @brief 获取模块依赖列表 + * 返回此模块依赖的其他模块标识符 + * @return 依赖模块标识符列表 + */ + std::vector getDependencies() const override; + + /** + * @brief 初始化模块 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ + bool initialize(const IModuleConfig* config) override; + + /** + * @brief 关闭模块 + */ + void shutdown() override; + + /** + * @brief 检查模块是否已初始化 + * @return 已初始化返回 true + */ + bool isInitialized() const override { return initialized_; } + + /** + * @brief 获取输入接口 + * @return 输入接口指针 + */ + IInput* getInput() const { return input_; } + + /** + * @brief 设置窗口模块标识符 + * @param windowModuleId 窗口模块标识符 + */ + void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; } + +private: + ModuleId moduleId_ = INVALID_MODULE_ID; + ModuleId windowModuleId_ = INVALID_MODULE_ID; + IInput* input_ = nullptr; + bool initialized_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/iwindow.h b/Extra2D/include/extra2d/platform/iwindow.h index 1fd6c2b..8b42458 100644 --- a/Extra2D/include/extra2d/platform/iwindow.h +++ b/Extra2D/include/extra2d/platform/iwindow.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,23 +10,6 @@ namespace extra2d { class IInput; -/** - * @brief 窗口配置数据 - */ -struct WindowConfig { - std::string title = "Extra2D Application"; - int width = 1280; - int height = 720; - bool fullscreen = false; - bool fullscreenDesktop = true; - bool resizable = true; - bool vsync = true; - int msaaSamples = 0; - bool centerWindow = true; - bool visible = true; - bool decorated = true; -}; - /** * @brief 光标形状 */ @@ -52,7 +36,7 @@ public: * @param cfg 窗口配置 * @return 创建是否成功 */ - virtual bool create(const WindowConfig& cfg) = 0; + virtual bool create(const WindowConfigData& cfg) = 0; /** * @brief 销毁窗口 diff --git a/Extra2D/include/extra2d/platform/window_module.h b/Extra2D/include/extra2d/platform/window_module.h new file mode 100644 index 0000000..62f5e25 --- /dev/null +++ b/Extra2D/include/extra2d/platform/window_module.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 窗口模块配置 + * 实现 IModuleConfig 接口 + */ +class WindowModuleConfig : public IModuleConfig { +public: + WindowConfigData windowConfig; + std::string backend = "sdl2"; + + /** + * @brief 获取模块信息 + * @return 模块信息结构体 + */ + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.name = "Window"; + info.version = "1.0.0"; + info.priority = ModulePriority::Platform; + info.enabled = true; + return info; + } + + /** + * @brief 获取配置节名称 + * @return 配置节名称字符串 + */ + std::string getConfigSectionName() const override { return "window"; } + + /** + * @brief 验证配置有效性 + * @return 如果配置有效返回 true + */ + bool validate() const override; + + /** + * @brief 应用平台约束 + * 根据平台特性调整配置 + * @param platform 目标平台类型 + */ + void applyPlatformConstraints(PlatformType platform) override; + + /** + * @brief 重置为默认配置 + */ + void resetToDefaults() override; + + /** + * @brief 从 JSON 数据加载配置 + * @param jsonData JSON 数据指针 + * @return 加载成功返回 true + */ + bool loadFromJson(const void* jsonData) override; + + /** + * @brief 保存配置到 JSON 数据 + * @param jsonData JSON 数据指针 + * @return 保存成功返回 true + */ + bool saveToJson(void* jsonData) const override; +}; + +/** + * @brief 窗口模块初始化器 + * 实现 IModuleInitializer 接口 + */ +class WindowModuleInitializer : public IModuleInitializer { +public: + /** + * @brief 构造函数 + */ + WindowModuleInitializer(); + + /** + * @brief 析构函数 + */ + ~WindowModuleInitializer() override; + + /** + * @brief 获取模块标识符 + * @return 模块唯一标识符 + */ + ModuleId getModuleId() const override { return moduleId_; } + + /** + * @brief 获取模块优先级 + * @return 模块优先级 + */ + ModulePriority getPriority() const override { return ModulePriority::Platform; } + + /** + * @brief 获取模块依赖列表 + * @return 依赖模块标识符列表 + */ + std::vector getDependencies() const override { return {}; } + + /** + * @brief 初始化模块 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ + bool initialize(const IModuleConfig* config) override; + + /** + * @brief 关闭模块 + */ + void shutdown() override; + + /** + * @brief 检查模块是否已初始化 + * @return 已初始化返回 true + */ + bool isInitialized() const override { return initialized_; } + + /** + * @brief 获取窗口实例 + * @return 窗口接口指针 + */ + IWindow* getWindow() const { return window_.get(); } + +private: + ModuleId moduleId_ = INVALID_MODULE_ID; + UniquePtr window_; + bool initialized_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index c0c90ca..9e3dfb5 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -20,6 +22,10 @@ namespace extra2d { +/** + * @brief 获取当前时间(秒) + * @return 当前时间戳(秒) + */ static double getTimeSeconds() { #ifdef __SWITCH__ struct timespec ts; @@ -39,7 +45,9 @@ Application& Application::get() { return instance; } -Application::~Application() { shutdown(); } +Application::~Application() { + shutdown(); +} bool Application::init() { AppConfig cfg; @@ -52,42 +60,74 @@ bool Application::init(const AppConfig& config) { return true; } - config_ = config; + E2D_LOG_INFO("Initializing application with config..."); + + if (!ConfigManager::instance().initialize()) { + E2D_LOG_ERROR("Failed to initialize ConfigManager"); + return false; + } + + ConfigManager::instance().setAppConfig(config); + return initImpl(); } -bool Application::init(const std::string& path) { +bool Application::init(const std::string& configPath) { if (initialized_) { E2D_LOG_WARN("Application already initialized"); return true; } - E2D_LOG_INFO("Loading config from: {}", path); - - AppConfig cfg; - - if (path.find(".json") != std::string::npos) { - // TODO: 使用 nlohmann_json 加载配置 - E2D_LOG_WARN("JSON config loading not yet implemented, using defaults"); - } else if (path.find(".ini") != std::string::npos) { - // TODO: 实现 INI 配置加载 - E2D_LOG_WARN("INI config loading not yet implemented, using defaults"); + E2D_LOG_INFO("Initializing application from config file: {}", configPath); + + if (!ConfigManager::instance().initialize(configPath)) { + E2D_LOG_WARN("Failed to load config from file, using defaults"); + if (!ConfigManager::instance().initialize()) { + E2D_LOG_ERROR("Failed to initialize ConfigManager"); + return false; + } } - - config_ = cfg; + return initImpl(); } bool Application::initImpl() { - PlatformType platform = config_.platform; + auto& configMgr = ConfigManager::instance(); + AppConfig& appConfig = configMgr.appConfig(); + + PlatformType platform = appConfig.targetPlatform; if (platform == PlatformType::Auto) { #ifdef __SWITCH__ platform = PlatformType::Switch; #else - platform = PlatformType::PC; +#ifdef _WIN32 + platform = PlatformType::Windows; +#elif defined(__linux__) + platform = PlatformType::Linux; +#elif defined(__APPLE__) + platform = PlatformType::macOS; +#else + platform = PlatformType::Windows; +#endif #endif } + E2D_LOG_INFO("Target platform: {} ({})", getPlatformTypeName(platform), + static_cast(platform)); + + UniquePtr platformConfig = createPlatformConfig(platform); + if (!platformConfig) { + E2D_LOG_ERROR("Failed to create platform config"); + return false; + } + + appConfig.applyPlatformConstraints(*platformConfig); + + const auto& capabilities = platformConfig->capabilities(); + E2D_LOG_INFO("Platform capabilities: windowed={}, fullscreen={}, cursor={}, DPI={}", + capabilities.supportsWindowed, capabilities.supportsFullscreen, + capabilities.supportsCursor, capabilities.supportsDPIAwareness); + if (platform == PlatformType::Switch) { #ifdef __SWITCH__ Result rc; @@ -105,7 +145,36 @@ bool Application::initImpl() { #endif } - std::string backend = config_.backend; + auto initOrder = ModuleRegistry::instance().getInitializationOrder(); + E2D_LOG_INFO("Initializing {} registered modules...", initOrder.size()); + + for (ModuleId moduleId : initOrder) { + auto initializer = ModuleRegistry::instance().createInitializer(moduleId); + if (!initializer) { + continue; + } + + auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId); + if (!moduleConfig) { + E2D_LOG_WARN("Module {} has no config, skipping", moduleId); + continue; + } + + auto info = moduleConfig->getModuleInfo(); + if (!info.enabled) { + E2D_LOG_INFO("Module '{}' is disabled, skipping", info.name); + continue; + } + + E2D_LOG_INFO("Initializing module '{}' (priority: {})", + info.name, static_cast(info.priority)); + + if (!initializer->initialize(moduleConfig)) { + E2D_LOG_ERROR("Failed to initialize module '{}'", info.name); + } + } + + std::string backend = "sdl2"; #ifdef __SWITCH__ backend = "switch"; #endif @@ -127,27 +196,19 @@ bool Application::initImpl() { return false; } - WindowConfig winConfig; - winConfig.title = config_.title; - winConfig.width = config_.width; - winConfig.height = config_.height; + WindowConfigData winConfig = appConfig.window; + if (platform == PlatformType::Switch) { - winConfig.fullscreen = true; - winConfig.fullscreenDesktop = false; + winConfig.mode = WindowMode::Fullscreen; winConfig.resizable = false; - } else { - winConfig.fullscreen = config_.fullscreen; - winConfig.resizable = config_.resizable; } - winConfig.vsync = config_.vsync; - winConfig.msaaSamples = config_.msaaSamples; if (!window_->create(winConfig)) { E2D_LOG_ERROR("Failed to create window"); return false; } - renderer_ = RenderBackend::create(config_.renderBackend); + renderer_ = RenderBackend::create(appConfig.render.backend); if (!renderer_ || !renderer_->init(window_.get())) { E2D_LOG_ERROR("Failed to initialize renderer"); window_->destroy(); @@ -163,8 +224,8 @@ bool Application::initImpl() { viewportAdapter_ = makeUnique(); ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(config_.width); - vpConfig.logicHeight = static_cast(config_.height); + vpConfig.logicWidth = static_cast(appConfig.window.width); + vpConfig.logicHeight = static_cast(appConfig.window.height); vpConfig.mode = ViewportMode::AspectRatio; viewportAdapter_->setConfig(vpConfig); @@ -193,7 +254,12 @@ bool Application::initImpl() { initialized_ = true; running_ = true; - E2D_LOG_INFO("Application initialized (backend: {})", backend); + E2D_LOG_INFO("Application initialized successfully"); + E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height()); + E2D_LOG_INFO(" Backend: {}", backend); + E2D_LOG_INFO(" VSync: {}", appConfig.render.vsync); + E2D_LOG_INFO(" Target FPS: {}", appConfig.render.targetFPS); + return true; } @@ -227,14 +293,31 @@ void Application::shutdown() { window_.reset(); } - PlatformType platform = config_.platform; + auto modules = ModuleRegistry::instance().getAllModules(); + auto initOrder = ModuleRegistry::instance().getInitializationOrder(); + + for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) { + ModuleId moduleId = *it; + auto initializer = ModuleRegistry::instance().createInitializer(moduleId); + if (initializer && initializer->isInitialized()) { + auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId); + if (moduleConfig) { + auto info = moduleConfig->getModuleInfo(); + E2D_LOG_INFO("Shutting down module '{}'", info.name); + } + initializer->shutdown(); + } + } + + PlatformType platform = ConfigManager::instance().appConfig().targetPlatform; if (platform == PlatformType::Auto) { #ifdef __SWITCH__ platform = PlatformType::Switch; #else - platform = PlatformType::PC; + platform = PlatformType::Windows; #endif } + if (platform == PlatformType::Switch) { #ifdef __SWITCH__ romfsExit(); @@ -242,6 +325,8 @@ void Application::shutdown() { #endif } + ConfigManager::instance().shutdown(); + initialized_ = false; running_ = false; @@ -308,15 +393,18 @@ void Application::mainLoop() { render(); - if (!config_.vsync && config_.fpsLimit > 0) { + const auto& appConfig = ConfigManager::instance().appConfig(); + if (!appConfig.render.vsync && appConfig.render.isFPSCapped()) { double frameEndTime = getTimeSeconds(); double frameTime = frameEndTime - currentTime; - double target = 1.0 / static_cast(config_.fpsLimit); + double target = 1.0 / static_cast(appConfig.render.targetFPS); if (frameTime < target) { auto sleepSeconds = target - frameTime; std::this_thread::sleep_for(std::chrono::duration(sleepSeconds)); } } + + ConfigManager::instance().update(deltaTime_); } void Application::update() { @@ -353,19 +441,33 @@ void Application::render() { window_->swap(); } -IInput& Application::input() { return *window_->input(); } +IInput& Application::input() { + return *window_->input(); +} -SceneManager& Application::scenes() { return *sceneManager_; } +SceneManager& Application::scenes() { + return *sceneManager_; +} -TimerManager& Application::timers() { return *timerManager_; } +TimerManager& Application::timers() { + return *timerManager_; +} -EventQueue& Application::eventQueue() { return *eventQueue_; } +EventQueue& Application::eventQueue() { + return *eventQueue_; +} -EventDispatcher& Application::eventDispatcher() { return *eventDispatcher_; } +EventDispatcher& Application::eventDispatcher() { + return *eventDispatcher_; +} -Camera& Application::camera() { return *camera_; } +Camera& Application::camera() { + return *camera_; +} -ViewportAdapter& Application::viewportAdapter() { return *viewportAdapter_; } +ViewportAdapter& Application::viewportAdapter() { + return *viewportAdapter_; +} void Application::enterScene(Ptr scene) { if (sceneManager_ && scene) { @@ -375,4 +477,12 @@ void Application::enterScene(Ptr scene) { } } +ConfigManager& Application::config() { + return ConfigManager::instance(); +} + +const AppConfig& Application::getConfig() const { + return ConfigManager::instance().appConfig(); +} + } // namespace extra2d diff --git a/Extra2D/src/config/app_config.cpp b/Extra2D/src/config/app_config.cpp new file mode 100644 index 0000000..366a40d --- /dev/null +++ b/Extra2D/src/config/app_config.cpp @@ -0,0 +1,681 @@ +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 检查是否存在指定的调试标志 + * @param flag 要检查的标志名称 + * @return 如果存在返回 true + */ +bool DebugConfigData::hasDebugFlag(const std::string& flag) const { + return std::find(debugFlags.begin(), debugFlags.end(), flag) != debugFlags.end(); +} + +/** + * @brief 添加调试标志 + * @param flag 要添加的标志名称 + */ +void DebugConfigData::addDebugFlag(const std::string& flag) { + if (!hasDebugFlag(flag)) { + debugFlags.push_back(flag); + } +} + +/** + * @brief 移除调试标志 + * @param flag 要移除的标志名称 + */ +void DebugConfigData::removeDebugFlag(const std::string& flag) { + auto it = std::find(debugFlags.begin(), debugFlags.end(), flag); + if (it != debugFlags.end()) { + debugFlags.erase(it); + } +} + +/** + * @brief 添加资源搜索路径 + * @param path 要添加的搜索路径 + */ +void ResourceConfigData::addSearchPath(const std::string& path) { + if (!hasSearchPath(path)) { + searchPaths.push_back(path); + } +} + +/** + * @brief 移除资源搜索路径 + * @param path 要移除的搜索路径 + */ +void ResourceConfigData::removeSearchPath(const std::string& path) { + auto it = std::find(searchPaths.begin(), searchPaths.end(), path); + if (it != searchPaths.end()) { + searchPaths.erase(it); + } +} + +/** + * @brief 检查是否存在指定的搜索路径 + * @param path 要检查的路径 + * @return 如果存在返回 true + */ +bool ResourceConfigData::hasSearchPath(const std::string& path) const { + return std::find(searchPaths.begin(), searchPaths.end(), path) != searchPaths.end(); +} + +/** + * @brief 创建默认配置 + * 返回一个包含所有默认值的应用配置实例 + * @return 默认的应用配置实例 + */ +AppConfig AppConfig::createDefault() { + AppConfig config; + + config.window.title = "Extra2D Application"; + config.window.width = 1280; + config.window.height = 720; + config.window.minWidth = 320; + config.window.minHeight = 240; + config.window.maxWidth = 0; + config.window.maxHeight = 0; + config.window.mode = WindowMode::Windowed; + config.window.resizable = true; + config.window.borderless = false; + config.window.alwaysOnTop = false; + config.window.centered = true; + config.window.posX = -1; + config.window.posY = -1; + config.window.hideOnClose = false; + config.window.minimizeOnClose = true; + config.window.opacity = 1.0f; + config.window.transparentFramebuffer = false; + config.window.highDPI = true; + config.window.contentScale = 1.0f; + + config.render.backend = BackendType::OpenGL; + config.render.targetFPS = 60; + config.render.vsync = true; + config.render.tripleBuffering = false; + config.render.multisamples = 0; + config.render.sRGBFramebuffer = false; + config.render.clearColor = Color{0.0f, 0.0f, 0.0f, 1.0f}; + config.render.maxTextureSize = 0; + config.render.textureAnisotropy = 1; + config.render.wireframeMode = false; + config.render.depthTest = false; + config.render.blending = true; + config.render.dithering = false; + config.render.spriteBatchSize = 1000; + config.render.maxRenderTargets = 1; + config.render.allowShaderHotReload = false; + + config.audio.enabled = true; + config.audio.masterVolume = 100; + config.audio.musicVolume = 100; + config.audio.sfxVolume = 100; + config.audio.voiceVolume = 100; + config.audio.ambientVolume = 100; + config.audio.frequency = 44100; + config.audio.channels = 2; + config.audio.chunkSize = 2048; + config.audio.maxChannels = 16; + config.audio.spatialAudio = false; + config.audio.listenerPosition[0] = 0.0f; + config.audio.listenerPosition[1] = 0.0f; + config.audio.listenerPosition[2] = 0.0f; + + config.debug.enabled = false; + config.debug.showFPS = false; + config.debug.showMemoryUsage = false; + config.debug.showRenderStats = false; + config.debug.showColliders = false; + config.debug.showGrid = false; + config.debug.logToFile = false; + config.debug.logToConsole = true; + config.debug.logLevel = 2; + config.debug.breakOnAssert = true; + config.debug.enableProfiling = false; + config.debug.debugFlags.clear(); + + config.input.enabled = true; + config.input.rawMouseInput = false; + config.input.mouseSensitivity = 1.0f; + config.input.invertMouseY = false; + config.input.invertMouseX = false; + config.input.deadzone = 0.15f; + config.input.triggerThreshold = 0.5f; + config.input.enableVibration = true; + config.input.maxGamepads = 4; + config.input.autoConnectGamepads = true; + + config.resource.assetRootPath = "assets"; + config.resource.cachePath = "cache"; + config.resource.savePath = "saves"; + config.resource.configPath = "config"; + config.resource.logPath = "logs"; + config.resource.useAssetCache = true; + config.resource.maxCacheSize = 512; + config.resource.hotReloadEnabled = false; + config.resource.hotReloadInterval = 1.0f; + config.resource.compressTextures = false; + config.resource.preloadCommonAssets = true; + config.resource.searchPaths.clear(); + + config.appName = "Extra2D App"; + config.appVersion = "1.0.0"; + config.organization = ""; + config.configFile = "config.json"; + config.targetPlatform = PlatformType::Auto; + + return config; +} + +/** + * @brief 验证配置的有效性 + * 检查所有配置项是否在有效范围内 + * @return 如果配置有效返回 true,否则返回 false + */ +bool AppConfig::validate() const { + if (window.width <= 0) { + E2D_LOG_ERROR("Config validation failed: window width must be positive"); + return false; + } + if (window.height <= 0) { + E2D_LOG_ERROR("Config validation failed: window height must be positive"); + return false; + } + if (window.minWidth <= 0) { + E2D_LOG_ERROR("Config validation failed: minimum window width must be positive"); + return false; + } + if (window.minHeight <= 0) { + E2D_LOG_ERROR("Config validation failed: minimum window height must be positive"); + return false; + } + if (window.maxWidth > 0 && window.maxWidth < window.minWidth) { + E2D_LOG_ERROR("Config validation failed: maximum width less than minimum width"); + return false; + } + if (window.maxHeight > 0 && window.maxHeight < window.minHeight) { + E2D_LOG_ERROR("Config validation failed: maximum height less than minimum height"); + return false; + } + if (window.width < window.minWidth) { + E2D_LOG_ERROR("Config validation failed: window width less than minimum"); + return false; + } + if (window.height < window.minHeight) { + E2D_LOG_ERROR("Config validation failed: window height less than minimum"); + return false; + } + if (window.maxWidth > 0 && window.width > window.maxWidth) { + E2D_LOG_ERROR("Config validation failed: window width exceeds maximum"); + return false; + } + if (window.maxHeight > 0 && window.height > window.maxHeight) { + E2D_LOG_ERROR("Config validation failed: window height exceeds maximum"); + return false; + } + if (window.opacity < 0.0f || window.opacity > 1.0f) { + E2D_LOG_ERROR("Config validation failed: window opacity must be between 0 and 1"); + return false; + } + if (window.contentScale <= 0.0f) { + E2D_LOG_ERROR("Config validation failed: content scale must be positive"); + return false; + } + + if (render.targetFPS < 0) { + E2D_LOG_ERROR("Config validation failed: target FPS cannot be negative"); + return false; + } + if (render.multisamples < 0 || render.multisamples > 16) { + E2D_LOG_ERROR("Config validation failed: multisamples must be between 0 and 16"); + return false; + } + if (render.textureAnisotropy < 1 || render.textureAnisotropy > 16) { + E2D_LOG_ERROR("Config validation failed: texture anisotropy must be between 1 and 16"); + return false; + } + if (render.spriteBatchSize <= 0) { + E2D_LOG_ERROR("Config validation failed: sprite batch size must be positive"); + return false; + } + if (render.maxRenderTargets <= 0) { + E2D_LOG_ERROR("Config validation failed: max render targets must be positive"); + return false; + } + + if (!audio.isValidVolume(audio.masterVolume)) { + E2D_LOG_ERROR("Config validation failed: master volume must be between 0 and 100"); + return false; + } + if (!audio.isValidVolume(audio.musicVolume)) { + E2D_LOG_ERROR("Config validation failed: music volume must be between 0 and 100"); + return false; + } + if (!audio.isValidVolume(audio.sfxVolume)) { + E2D_LOG_ERROR("Config validation failed: SFX volume must be between 0 and 100"); + return false; + } + if (!audio.isValidVolume(audio.voiceVolume)) { + E2D_LOG_ERROR("Config validation failed: voice volume must be between 0 and 100"); + return false; + } + if (!audio.isValidVolume(audio.ambientVolume)) { + E2D_LOG_ERROR("Config validation failed: ambient volume must be between 0 and 100"); + return false; + } + if (audio.frequency <= 0) { + E2D_LOG_ERROR("Config validation failed: audio frequency must be positive"); + return false; + } + if (audio.channels <= 0) { + E2D_LOG_ERROR("Config validation failed: audio channels must be positive"); + return false; + } + if (audio.chunkSize <= 0) { + E2D_LOG_ERROR("Config validation failed: audio chunk size must be positive"); + return false; + } + if (audio.maxChannels <= 0) { + E2D_LOG_ERROR("Config validation failed: max audio channels must be positive"); + return false; + } + + if (debug.logLevel < 0 || debug.logLevel > 5) { + E2D_LOG_ERROR("Config validation failed: log level must be between 0 and 5"); + return false; + } + + if (!input.isDeadzoneValid()) { + E2D_LOG_ERROR("Config validation failed: deadzone must be between 0 and 1"); + return false; + } + if (input.mouseSensitivity <= 0.0f) { + E2D_LOG_ERROR("Config validation failed: mouse sensitivity must be positive"); + return false; + } + if (input.triggerThreshold < 0.0f || input.triggerThreshold > 1.0f) { + E2D_LOG_ERROR("Config validation failed: trigger threshold must be between 0 and 1"); + return false; + } + if (input.maxGamepads <= 0) { + E2D_LOG_ERROR("Config validation failed: max gamepads must be positive"); + return false; + } + + if (resource.maxCacheSize <= 0) { + E2D_LOG_ERROR("Config validation failed: max cache size must be positive"); + return false; + } + if (resource.hotReloadInterval <= 0.0f) { + E2D_LOG_ERROR("Config validation failed: hot reload interval must be positive"); + return false; + } + + if (appName.empty()) { + E2D_LOG_ERROR("Config validation failed: app name cannot be empty"); + return false; + } + if (appVersion.empty()) { + E2D_LOG_ERROR("Config validation failed: app version cannot be empty"); + return false; + } + if (configFile.empty()) { + E2D_LOG_ERROR("Config validation failed: config file cannot be empty"); + return false; + } + + return true; +} + +/** + * @brief 应用平台约束 + * 根据平台特性调整配置参数 + * @param platform 平台配置接口 + */ +void AppConfig::applyPlatformConstraints(const PlatformConfig& platform) { + const PlatformCapabilities& caps = platform.capabilities(); + + if (!caps.supportsWindowed && window.mode == WindowMode::Windowed) { + E2D_LOG_WARN("Platform does not support windowed mode, switching to fullscreen"); + window.mode = WindowMode::Fullscreen; + } + if (!caps.supportsBorderless && window.mode == WindowMode::Borderless) { + E2D_LOG_WARN("Platform does not support borderless mode, switching to fullscreen"); + window.mode = WindowMode::Fullscreen; + } + if (!caps.supportsFullscreen && window.mode == WindowMode::Fullscreen) { + E2D_LOG_WARN("Platform does not support fullscreen mode, switching to windowed"); + window.mode = WindowMode::Windowed; + } + + if (!caps.supportsResize) { + window.resizable = false; + } + if (!caps.supportsHighDPI) { + window.highDPI = false; + } + if (!caps.supportsCursor) { + window.borderless = false; + } + + if (!caps.supportsVSync) { + render.vsync = false; + } + + if (caps.maxTextureSize > 0) { + if (render.maxTextureSize == 0 || render.maxTextureSize > caps.maxTextureSize) { + render.maxTextureSize = caps.maxTextureSize; + } + } + + if (!caps.supportsGamepad) { + input.maxGamepads = 0; + input.enableVibration = false; + } + if (!caps.supportsKeyboard) { + input.enabled = false; + } + if (!caps.supportsMouse) { + input.rawMouseInput = false; + } + + platform.applyConstraints(*this); + + E2D_LOG_INFO("Applied platform constraints for: {}", platform.platformName()); +} + +/** + * @brief 重置为默认值 + * 将所有配置项恢复为默认值 + */ +void AppConfig::reset() { + *this = createDefault(); + E2D_LOG_INFO("App config reset to defaults"); +} + +/** + * @brief 合并另一个配置 + * 将 other 中的非默认值覆盖到当前配置 + * @param other 要合并的配置 + */ +void AppConfig::merge(const AppConfig& other) { + if (other.window.title != "Extra2D Application") { + window.title = other.window.title; + } + if (other.window.width != 1280) { + window.width = other.window.width; + } + if (other.window.height != 720) { + window.height = other.window.height; + } + if (other.window.minWidth != 320) { + window.minWidth = other.window.minWidth; + } + if (other.window.minHeight != 240) { + window.minHeight = other.window.minHeight; + } + if (other.window.maxWidth != 0) { + window.maxWidth = other.window.maxWidth; + } + if (other.window.maxHeight != 0) { + window.maxHeight = other.window.maxHeight; + } + if (other.window.mode != WindowMode::Windowed) { + window.mode = other.window.mode; + } + if (other.window.resizable != true) { + window.resizable = other.window.resizable; + } + if (other.window.borderless != false) { + window.borderless = other.window.borderless; + } + if (other.window.alwaysOnTop != false) { + window.alwaysOnTop = other.window.alwaysOnTop; + } + if (other.window.centered != true) { + window.centered = other.window.centered; + } + if (other.window.posX >= 0) { + window.posX = other.window.posX; + } + if (other.window.posY >= 0) { + window.posY = other.window.posY; + } + if (other.window.hideOnClose != false) { + window.hideOnClose = other.window.hideOnClose; + } + if (other.window.minimizeOnClose != true) { + window.minimizeOnClose = other.window.minimizeOnClose; + } + if (other.window.opacity != 1.0f) { + window.opacity = other.window.opacity; + } + if (other.window.transparentFramebuffer != false) { + window.transparentFramebuffer = other.window.transparentFramebuffer; + } + if (other.window.highDPI != true) { + window.highDPI = other.window.highDPI; + } + if (other.window.contentScale != 1.0f) { + window.contentScale = other.window.contentScale; + } + + if (other.render.backend != BackendType::OpenGL) { + render.backend = other.render.backend; + } + if (other.render.targetFPS != 60) { + render.targetFPS = other.render.targetFPS; + } + if (other.render.vsync != true) { + render.vsync = other.render.vsync; + } + if (other.render.tripleBuffering != false) { + render.tripleBuffering = other.render.tripleBuffering; + } + if (other.render.multisamples != 0) { + render.multisamples = other.render.multisamples; + } + if (other.render.sRGBFramebuffer != false) { + render.sRGBFramebuffer = other.render.sRGBFramebuffer; + } + if (other.render.maxTextureSize != 0) { + render.maxTextureSize = other.render.maxTextureSize; + } + if (other.render.textureAnisotropy != 1) { + render.textureAnisotropy = other.render.textureAnisotropy; + } + if (other.render.wireframeMode != false) { + render.wireframeMode = other.render.wireframeMode; + } + if (other.render.depthTest != false) { + render.depthTest = other.render.depthTest; + } + if (other.render.blending != true) { + render.blending = other.render.blending; + } + if (other.render.dithering != false) { + render.dithering = other.render.dithering; + } + if (other.render.spriteBatchSize != 1000) { + render.spriteBatchSize = other.render.spriteBatchSize; + } + if (other.render.maxRenderTargets != 1) { + render.maxRenderTargets = other.render.maxRenderTargets; + } + if (other.render.allowShaderHotReload != false) { + render.allowShaderHotReload = other.render.allowShaderHotReload; + } + if (!other.render.shaderCachePath.empty()) { + render.shaderCachePath = other.render.shaderCachePath; + } + + if (other.audio.enabled != true) { + audio.enabled = other.audio.enabled; + } + if (other.audio.masterVolume != 100) { + audio.masterVolume = other.audio.masterVolume; + } + if (other.audio.musicVolume != 100) { + audio.musicVolume = other.audio.musicVolume; + } + if (other.audio.sfxVolume != 100) { + audio.sfxVolume = other.audio.sfxVolume; + } + if (other.audio.voiceVolume != 100) { + audio.voiceVolume = other.audio.voiceVolume; + } + if (other.audio.ambientVolume != 100) { + audio.ambientVolume = other.audio.ambientVolume; + } + if (other.audio.frequency != 44100) { + audio.frequency = other.audio.frequency; + } + if (other.audio.channels != 2) { + audio.channels = other.audio.channels; + } + if (other.audio.chunkSize != 2048) { + audio.chunkSize = other.audio.chunkSize; + } + if (other.audio.maxChannels != 16) { + audio.maxChannels = other.audio.maxChannels; + } + if (other.audio.spatialAudio != false) { + audio.spatialAudio = other.audio.spatialAudio; + } + + if (other.debug.enabled != false) { + debug.enabled = other.debug.enabled; + } + if (other.debug.showFPS != false) { + debug.showFPS = other.debug.showFPS; + } + if (other.debug.showMemoryUsage != false) { + debug.showMemoryUsage = other.debug.showMemoryUsage; + } + if (other.debug.showRenderStats != false) { + debug.showRenderStats = other.debug.showRenderStats; + } + if (other.debug.showColliders != false) { + debug.showColliders = other.debug.showColliders; + } + if (other.debug.showGrid != false) { + debug.showGrid = other.debug.showGrid; + } + if (other.debug.logToFile != false) { + debug.logToFile = other.debug.logToFile; + } + if (other.debug.logToConsole != true) { + debug.logToConsole = other.debug.logToConsole; + } + if (other.debug.logLevel != 2) { + debug.logLevel = other.debug.logLevel; + } + if (other.debug.breakOnAssert != true) { + debug.breakOnAssert = other.debug.breakOnAssert; + } + if (other.debug.enableProfiling != false) { + debug.enableProfiling = other.debug.enableProfiling; + } + if (!other.debug.logFilePath.empty()) { + debug.logFilePath = other.debug.logFilePath; + } + for (const auto& flag : other.debug.debugFlags) { + debug.addDebugFlag(flag); + } + + if (other.input.enabled != true) { + input.enabled = other.input.enabled; + } + if (other.input.rawMouseInput != false) { + input.rawMouseInput = other.input.rawMouseInput; + } + if (other.input.mouseSensitivity != 1.0f) { + input.mouseSensitivity = other.input.mouseSensitivity; + } + if (other.input.invertMouseY != false) { + input.invertMouseY = other.input.invertMouseY; + } + if (other.input.invertMouseX != false) { + input.invertMouseX = other.input.invertMouseX; + } + if (other.input.deadzone != 0.15f) { + input.deadzone = other.input.deadzone; + } + if (other.input.triggerThreshold != 0.5f) { + input.triggerThreshold = other.input.triggerThreshold; + } + if (other.input.enableVibration != true) { + input.enableVibration = other.input.enableVibration; + } + if (other.input.maxGamepads != 4) { + input.maxGamepads = other.input.maxGamepads; + } + if (other.input.autoConnectGamepads != true) { + input.autoConnectGamepads = other.input.autoConnectGamepads; + } + if (!other.input.gamepadMappingFile.empty()) { + input.gamepadMappingFile = other.input.gamepadMappingFile; + } + + if (other.resource.assetRootPath != "assets") { + resource.assetRootPath = other.resource.assetRootPath; + } + if (other.resource.cachePath != "cache") { + resource.cachePath = other.resource.cachePath; + } + if (other.resource.savePath != "saves") { + resource.savePath = other.resource.savePath; + } + if (other.resource.configPath != "config") { + resource.configPath = other.resource.configPath; + } + if (other.resource.logPath != "logs") { + resource.logPath = other.resource.logPath; + } + if (other.resource.useAssetCache != true) { + resource.useAssetCache = other.resource.useAssetCache; + } + if (other.resource.maxCacheSize != 512) { + resource.maxCacheSize = other.resource.maxCacheSize; + } + if (other.resource.hotReloadEnabled != false) { + resource.hotReloadEnabled = other.resource.hotReloadEnabled; + } + if (other.resource.hotReloadInterval != 1.0f) { + resource.hotReloadInterval = other.resource.hotReloadInterval; + } + if (other.resource.compressTextures != false) { + resource.compressTextures = other.resource.compressTextures; + } + if (other.resource.preloadCommonAssets != true) { + resource.preloadCommonAssets = other.resource.preloadCommonAssets; + } + for (const auto& path : other.resource.searchPaths) { + resource.addSearchPath(path); + } + + if (other.appName != "Extra2D App") { + appName = other.appName; + } + if (other.appVersion != "1.0.0") { + appVersion = other.appVersion; + } + if (!other.organization.empty()) { + organization = other.organization; + } + if (other.configFile != "config.json") { + configFile = other.configFile; + } + if (other.targetPlatform != PlatformType::Auto) { + targetPlatform = other.targetPlatform; + } + + E2D_LOG_INFO("Merged app config"); +} + +} diff --git a/Extra2D/src/config/config_loader_ini.cpp b/Extra2D/src/config/config_loader_ini.cpp new file mode 100644 index 0000000..7e7bb0a --- /dev/null +++ b/Extra2D/src/config/config_loader_ini.cpp @@ -0,0 +1,952 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 去除字符串首尾空白字符 + * @param str 输入字符串 + * @return 去除空白后的字符串 + */ +static std::string trim(const std::string& str) { + size_t start = 0; + while (start < str.length() && std::isspace(static_cast(str[start]))) { + ++start; + } + size_t end = str.length(); + while (end > start && std::isspace(static_cast(str[end - 1]))) { + --end; + } + return str.substr(start, end - start); +} + +/** + * @brief 将字符串转换为小写 + * @param str 输入字符串 + * @return 小写字符串 + */ +static std::string toLower(const std::string& str) { + std::string result = str; + for (char& c : result) { + c = static_cast(std::tolower(static_cast(c))); + } + return result; +} + +/** + * @brief 将字符串转换为窗口模式枚举 + * @param modeStr 窗口模式字符串 + * @return 窗口模式枚举值 + */ +static WindowMode stringToWindowMode(const std::string& modeStr) { + std::string lower = toLower(modeStr); + if (lower == "fullscreen") return WindowMode::Fullscreen; + if (lower == "borderless") return WindowMode::Borderless; + return WindowMode::Windowed; +} + +/** + * @brief 将窗口模式枚举转换为字符串 + * @param mode 窗口模式枚举值 + * @return 窗口模式字符串 + */ +static std::string windowModeToString(WindowMode mode) { + switch (mode) { + case WindowMode::Fullscreen: return "fullscreen"; + case WindowMode::Borderless: return "borderless"; + default: return "windowed"; + } +} + +/** + * @brief 将字符串转换为渲染后端类型枚举 + * @param backendStr 后端类型字符串 + * @return 渲染后端类型枚举值 + */ +static BackendType stringToBackendType(const std::string& backendStr) { + std::string lower = toLower(backendStr); + if (lower == "opengl") return BackendType::OpenGL; + return BackendType::OpenGL; +} + +/** + * @brief 将渲染后端类型枚举转换为字符串 + * @param backend 渲染后端类型枚举值 + * @return 后端类型字符串 + */ +static std::string backendTypeToString(BackendType backend) { + switch (backend) { + case BackendType::OpenGL: return "opengl"; + default: return "opengl"; + } +} + +/** + * @brief INI 数据存储结构 + */ +using IniData = std::map>; + +/** + * @brief 解析 INI 内容到数据结构 + * @param content INI 内容字符串 + * @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(); + } + } 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(); +} + +/** + * @brief 获取 INI 值(带默认值) + * @param data INI 数据 + * @param section 节名 + * @param key 键名 + * @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; +} + +/** + * @brief 检查 INI 值是否存在 + * @param data INI 数据 + * @param 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(); +} + +// ============================================================================ +// IniConfigLoader 实现 +// ============================================================================ + +/** + * @brief 从 INI 文件加载配置 + * @param filepath 配置文件路径 + * @param config 输出的配置对象 + * @return 加载结果 + */ +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); +} + +/** + * @brief 保存配置到 INI 文件 + * @param filepath 配置文件路径 + * @param config 要保存的配置对象 + * @return 保存结果 + */ +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(); +} + +/** + * @brief 从 INI 字符串加载配置 + * @param content INI 内容字符串 + * @param config 输出的配置对象 + * @return 加载结果 + */ +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, "window", "title")) { + config.window.title = getIniValue(data, "window", "title"); + } + if (hasIniValue(data, "window", "width")) { + int value; + auto res = parseInt(getIniValue(data, "window", "width"), value, "window.width"); + if (res.hasError()) return res; + config.window.width = value; + } + if (hasIniValue(data, "window", "height")) { + int value; + auto res = parseInt(getIniValue(data, "window", "height"), value, "window.height"); + if (res.hasError()) return res; + config.window.height = value; + } + if (hasIniValue(data, "window", "minWidth")) { + int value; + auto res = parseInt(getIniValue(data, "window", "minWidth"), value, "window.minWidth"); + if (res.hasError()) return res; + config.window.minWidth = value; + } + if (hasIniValue(data, "window", "minHeight")) { + int value; + auto res = parseInt(getIniValue(data, "window", "minHeight"), value, "window.minHeight"); + if (res.hasError()) return res; + config.window.minHeight = value; + } + if (hasIniValue(data, "window", "maxWidth")) { + int value; + auto res = parseInt(getIniValue(data, "window", "maxWidth"), value, "window.maxWidth"); + if (res.hasError()) return res; + config.window.maxWidth = value; + } + if (hasIniValue(data, "window", "maxHeight")) { + int value; + auto res = parseInt(getIniValue(data, "window", "maxHeight"), value, "window.maxHeight"); + if (res.hasError()) return res; + config.window.maxHeight = value; + } + if (hasIniValue(data, "window", "mode")) { + config.window.mode = stringToWindowMode(getIniValue(data, "window", "mode")); + } + if (hasIniValue(data, "window", "resizable")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "resizable"), value, "window.resizable"); + if (res.hasError()) return res; + config.window.resizable = value; + } + if (hasIniValue(data, "window", "borderless")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "borderless"), value, "window.borderless"); + if (res.hasError()) return res; + config.window.borderless = value; + } + if (hasIniValue(data, "window", "alwaysOnTop")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "alwaysOnTop"), value, "window.alwaysOnTop"); + if (res.hasError()) return res; + config.window.alwaysOnTop = value; + } + if (hasIniValue(data, "window", "centered")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "centered"), value, "window.centered"); + if (res.hasError()) return res; + config.window.centered = value; + } + if (hasIniValue(data, "window", "posX")) { + int value; + auto res = parseInt(getIniValue(data, "window", "posX"), value, "window.posX"); + if (res.hasError()) return res; + config.window.posX = value; + } + if (hasIniValue(data, "window", "posY")) { + int value; + auto res = parseInt(getIniValue(data, "window", "posY"), value, "window.posY"); + if (res.hasError()) return res; + config.window.posY = value; + } + if (hasIniValue(data, "window", "hideOnClose")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "hideOnClose"), value, "window.hideOnClose"); + if (res.hasError()) return res; + config.window.hideOnClose = value; + } + if (hasIniValue(data, "window", "minimizeOnClose")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "minimizeOnClose"), value, "window.minimizeOnClose"); + if (res.hasError()) return res; + config.window.minimizeOnClose = value; + } + if (hasIniValue(data, "window", "opacity")) { + float value; + auto res = parseFloat(getIniValue(data, "window", "opacity"), value, "window.opacity"); + if (res.hasError()) return res; + config.window.opacity = value; + } + if (hasIniValue(data, "window", "transparentFramebuffer")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "transparentFramebuffer"), value, "window.transparentFramebuffer"); + if (res.hasError()) return res; + config.window.transparentFramebuffer = value; + } + if (hasIniValue(data, "window", "highDPI")) { + bool value; + auto res = parseBool(getIniValue(data, "window", "highDPI"), value, "window.highDPI"); + if (res.hasError()) return res; + config.window.highDPI = value; + } + if (hasIniValue(data, "window", "contentScale")) { + float value; + auto res = parseFloat(getIniValue(data, "window", "contentScale"), value, "window.contentScale"); + if (res.hasError()) return res; + config.window.contentScale = value; + } + + if (hasIniValue(data, "render", "backend")) { + config.render.backend = stringToBackendType(getIniValue(data, "render", "backend")); + } + if (hasIniValue(data, "render", "targetFPS")) { + int value; + auto res = parseInt(getIniValue(data, "render", "targetFPS"), value, "render.targetFPS"); + if (res.hasError()) return res; + config.render.targetFPS = value; + } + if (hasIniValue(data, "render", "vsync")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "vsync"), value, "render.vsync"); + if (res.hasError()) return res; + config.render.vsync = value; + } + if (hasIniValue(data, "render", "tripleBuffering")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "tripleBuffering"), value, "render.tripleBuffering"); + if (res.hasError()) return res; + config.render.tripleBuffering = value; + } + if (hasIniValue(data, "render", "multisamples")) { + int value; + auto res = parseInt(getIniValue(data, "render", "multisamples"), value, "render.multisamples"); + if (res.hasError()) return res; + config.render.multisamples = value; + } + if (hasIniValue(data, "render", "sRGBFramebuffer")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "sRGBFramebuffer"), value, "render.sRGBFramebuffer"); + if (res.hasError()) return res; + config.render.sRGBFramebuffer = value; + } + if (hasIniValue(data, "render", "clearColorR")) { + float value; + auto res = parseFloat(getIniValue(data, "render", "clearColorR"), value, "render.clearColorR"); + if (res.hasError()) return res; + config.render.clearColor.r = value; + } + if (hasIniValue(data, "render", "clearColorG")) { + float value; + auto res = parseFloat(getIniValue(data, "render", "clearColorG"), value, "render.clearColorG"); + if (res.hasError()) return res; + config.render.clearColor.g = value; + } + if (hasIniValue(data, "render", "clearColorB")) { + float value; + auto res = parseFloat(getIniValue(data, "render", "clearColorB"), value, "render.clearColorB"); + if (res.hasError()) return res; + config.render.clearColor.b = value; + } + if (hasIniValue(data, "render", "clearColorA")) { + float value; + auto res = parseFloat(getIniValue(data, "render", "clearColorA"), value, "render.clearColorA"); + if (res.hasError()) return res; + config.render.clearColor.a = value; + } + if (hasIniValue(data, "render", "maxTextureSize")) { + int value; + auto res = parseInt(getIniValue(data, "render", "maxTextureSize"), value, "render.maxTextureSize"); + if (res.hasError()) return res; + config.render.maxTextureSize = value; + } + if (hasIniValue(data, "render", "textureAnisotropy")) { + int value; + auto res = parseInt(getIniValue(data, "render", "textureAnisotropy"), value, "render.textureAnisotropy"); + if (res.hasError()) return res; + config.render.textureAnisotropy = value; + } + if (hasIniValue(data, "render", "wireframeMode")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "wireframeMode"), value, "render.wireframeMode"); + if (res.hasError()) return res; + config.render.wireframeMode = value; + } + if (hasIniValue(data, "render", "depthTest")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "depthTest"), value, "render.depthTest"); + if (res.hasError()) return res; + config.render.depthTest = value; + } + if (hasIniValue(data, "render", "blending")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "blending"), value, "render.blending"); + if (res.hasError()) return res; + config.render.blending = value; + } + if (hasIniValue(data, "render", "dithering")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "dithering"), value, "render.dithering"); + if (res.hasError()) return res; + config.render.dithering = value; + } + if (hasIniValue(data, "render", "spriteBatchSize")) { + int value; + auto res = parseInt(getIniValue(data, "render", "spriteBatchSize"), value, "render.spriteBatchSize"); + if (res.hasError()) return res; + config.render.spriteBatchSize = value; + } + if (hasIniValue(data, "render", "maxRenderTargets")) { + int value; + auto res = parseInt(getIniValue(data, "render", "maxRenderTargets"), value, "render.maxRenderTargets"); + if (res.hasError()) return res; + config.render.maxRenderTargets = value; + } + if (hasIniValue(data, "render", "allowShaderHotReload")) { + bool value; + auto res = parseBool(getIniValue(data, "render", "allowShaderHotReload"), value, "render.allowShaderHotReload"); + if (res.hasError()) return res; + config.render.allowShaderHotReload = value; + } + if (hasIniValue(data, "render", "shaderCachePath")) { + config.render.shaderCachePath = getIniValue(data, "render", "shaderCachePath"); + } + + if (hasIniValue(data, "audio", "enabled")) { + bool value; + auto res = parseBool(getIniValue(data, "audio", "enabled"), value, "audio.enabled"); + if (res.hasError()) return res; + config.audio.enabled = value; + } + if (hasIniValue(data, "audio", "masterVolume")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "masterVolume"), value, "audio.masterVolume"); + if (res.hasError()) return res; + config.audio.masterVolume = value; + } + if (hasIniValue(data, "audio", "musicVolume")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "musicVolume"), value, "audio.musicVolume"); + if (res.hasError()) return res; + config.audio.musicVolume = value; + } + if (hasIniValue(data, "audio", "sfxVolume")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "sfxVolume"), value, "audio.sfxVolume"); + if (res.hasError()) return res; + config.audio.sfxVolume = value; + } + if (hasIniValue(data, "audio", "voiceVolume")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "voiceVolume"), value, "audio.voiceVolume"); + if (res.hasError()) return res; + config.audio.voiceVolume = value; + } + if (hasIniValue(data, "audio", "ambientVolume")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "ambientVolume"), value, "audio.ambientVolume"); + if (res.hasError()) return res; + config.audio.ambientVolume = value; + } + if (hasIniValue(data, "audio", "frequency")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "frequency"), value, "audio.frequency"); + if (res.hasError()) return res; + config.audio.frequency = value; + } + if (hasIniValue(data, "audio", "channels")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "channels"), value, "audio.channels"); + if (res.hasError()) return res; + config.audio.channels = value; + } + if (hasIniValue(data, "audio", "chunkSize")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "chunkSize"), value, "audio.chunkSize"); + if (res.hasError()) return res; + config.audio.chunkSize = value; + } + if (hasIniValue(data, "audio", "maxChannels")) { + int value; + auto res = parseInt(getIniValue(data, "audio", "maxChannels"), value, "audio.maxChannels"); + if (res.hasError()) return res; + config.audio.maxChannels = value; + } + if (hasIniValue(data, "audio", "spatialAudio")) { + bool value; + auto res = parseBool(getIniValue(data, "audio", "spatialAudio"), value, "audio.spatialAudio"); + if (res.hasError()) return res; + config.audio.spatialAudio = value; + } + + if (hasIniValue(data, "debug", "enabled")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "enabled"), value, "debug.enabled"); + if (res.hasError()) return res; + config.debug.enabled = value; + } + if (hasIniValue(data, "debug", "showFPS")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "showFPS"), value, "debug.showFPS"); + if (res.hasError()) return res; + config.debug.showFPS = value; + } + if (hasIniValue(data, "debug", "showMemoryUsage")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "showMemoryUsage"), value, "debug.showMemoryUsage"); + if (res.hasError()) return res; + config.debug.showMemoryUsage = value; + } + if (hasIniValue(data, "debug", "showRenderStats")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "showRenderStats"), value, "debug.showRenderStats"); + if (res.hasError()) return res; + config.debug.showRenderStats = value; + } + if (hasIniValue(data, "debug", "showColliders")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "showColliders"), value, "debug.showColliders"); + if (res.hasError()) return res; + config.debug.showColliders = value; + } + if (hasIniValue(data, "debug", "showGrid")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "showGrid"), value, "debug.showGrid"); + if (res.hasError()) return res; + config.debug.showGrid = value; + } + if (hasIniValue(data, "debug", "logToFile")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "logToFile"), value, "debug.logToFile"); + if (res.hasError()) return res; + config.debug.logToFile = value; + } + if (hasIniValue(data, "debug", "logToConsole")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "logToConsole"), value, "debug.logToConsole"); + if (res.hasError()) return res; + config.debug.logToConsole = value; + } + if (hasIniValue(data, "debug", "logLevel")) { + int value; + auto res = parseInt(getIniValue(data, "debug", "logLevel"), value, "debug.logLevel"); + if (res.hasError()) return res; + config.debug.logLevel = value; + } + if (hasIniValue(data, "debug", "breakOnAssert")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "breakOnAssert"), value, "debug.breakOnAssert"); + if (res.hasError()) return res; + config.debug.breakOnAssert = value; + } + if (hasIniValue(data, "debug", "enableProfiling")) { + bool value; + auto res = parseBool(getIniValue(data, "debug", "enableProfiling"), value, "debug.enableProfiling"); + if (res.hasError()) return res; + config.debug.enableProfiling = value; + } + if (hasIniValue(data, "debug", "logFilePath")) { + config.debug.logFilePath = getIniValue(data, "debug", "logFilePath"); + } + + if (hasIniValue(data, "input", "enabled")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "enabled"), value, "input.enabled"); + if (res.hasError()) return res; + config.input.enabled = value; + } + if (hasIniValue(data, "input", "rawMouseInput")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "rawMouseInput"), value, "input.rawMouseInput"); + if (res.hasError()) return res; + config.input.rawMouseInput = value; + } + if (hasIniValue(data, "input", "mouseSensitivity")) { + float value; + auto res = parseFloat(getIniValue(data, "input", "mouseSensitivity"), value, "input.mouseSensitivity"); + if (res.hasError()) return res; + config.input.mouseSensitivity = value; + } + if (hasIniValue(data, "input", "invertMouseY")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "invertMouseY"), value, "input.invertMouseY"); + if (res.hasError()) return res; + config.input.invertMouseY = value; + } + if (hasIniValue(data, "input", "invertMouseX")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "invertMouseX"), value, "input.invertMouseX"); + if (res.hasError()) return res; + config.input.invertMouseX = value; + } + if (hasIniValue(data, "input", "deadzone")) { + float value; + auto res = parseFloat(getIniValue(data, "input", "deadzone"), value, "input.deadzone"); + if (res.hasError()) return res; + config.input.deadzone = value; + } + if (hasIniValue(data, "input", "triggerThreshold")) { + float value; + auto res = parseFloat(getIniValue(data, "input", "triggerThreshold"), value, "input.triggerThreshold"); + if (res.hasError()) return res; + config.input.triggerThreshold = value; + } + if (hasIniValue(data, "input", "enableVibration")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "enableVibration"), value, "input.enableVibration"); + if (res.hasError()) return res; + config.input.enableVibration = value; + } + if (hasIniValue(data, "input", "maxGamepads")) { + int value; + auto res = parseInt(getIniValue(data, "input", "maxGamepads"), value, "input.maxGamepads"); + if (res.hasError()) return res; + config.input.maxGamepads = value; + } + if (hasIniValue(data, "input", "autoConnectGamepads")) { + bool value; + auto res = parseBool(getIniValue(data, "input", "autoConnectGamepads"), value, "input.autoConnectGamepads"); + if (res.hasError()) return res; + config.input.autoConnectGamepads = value; + } + if (hasIniValue(data, "input", "gamepadMappingFile")) { + config.input.gamepadMappingFile = getIniValue(data, "input", "gamepadMappingFile"); + } + + if (hasIniValue(data, "resource", "assetRootPath")) { + config.resource.assetRootPath = getIniValue(data, "resource", "assetRootPath"); + } + if (hasIniValue(data, "resource", "cachePath")) { + config.resource.cachePath = getIniValue(data, "resource", "cachePath"); + } + if (hasIniValue(data, "resource", "savePath")) { + config.resource.savePath = getIniValue(data, "resource", "savePath"); + } + if (hasIniValue(data, "resource", "configPath")) { + config.resource.configPath = getIniValue(data, "resource", "configPath"); + } + if (hasIniValue(data, "resource", "logPath")) { + config.resource.logPath = getIniValue(data, "resource", "logPath"); + } + if (hasIniValue(data, "resource", "useAssetCache")) { + bool value; + auto res = parseBool(getIniValue(data, "resource", "useAssetCache"), value, "resource.useAssetCache"); + if (res.hasError()) return res; + config.resource.useAssetCache = value; + } + if (hasIniValue(data, "resource", "maxCacheSize")) { + int value; + auto res = parseInt(getIniValue(data, "resource", "maxCacheSize"), value, "resource.maxCacheSize"); + if (res.hasError()) return res; + config.resource.maxCacheSize = value; + } + if (hasIniValue(data, "resource", "hotReloadEnabled")) { + bool value; + auto res = parseBool(getIniValue(data, "resource", "hotReloadEnabled"), value, "resource.hotReloadEnabled"); + if (res.hasError()) return res; + config.resource.hotReloadEnabled = value; + } + if (hasIniValue(data, "resource", "hotReloadInterval")) { + float value; + auto res = parseFloat(getIniValue(data, "resource", "hotReloadInterval"), value, "resource.hotReloadInterval"); + if (res.hasError()) return res; + config.resource.hotReloadInterval = value; + } + if (hasIniValue(data, "resource", "compressTextures")) { + bool value; + auto res = parseBool(getIniValue(data, "resource", "compressTextures"), value, "resource.compressTextures"); + if (res.hasError()) return res; + config.resource.compressTextures = value; + } + if (hasIniValue(data, "resource", "preloadCommonAssets")) { + bool value; + auto res = parseBool(getIniValue(data, "resource", "preloadCommonAssets"), value, "resource.preloadCommonAssets"); + if (res.hasError()) return res; + config.resource.preloadCommonAssets = value; + } + + E2D_LOG_INFO("INI 配置加载成功"); + return ConfigLoadResult::ok(); +} + +/** + * @brief 将配置序列化为 INI 字符串 + * @param config 配置对象 + * @return 序列化后的 INI 字符串 + */ +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 << "\n"; + + oss << "[window]\n"; + oss << "title=" << config.window.title << "\n"; + oss << "width=" << config.window.width << "\n"; + oss << "height=" << config.window.height << "\n"; + oss << "minWidth=" << config.window.minWidth << "\n"; + oss << "minHeight=" << config.window.minHeight << "\n"; + oss << "maxWidth=" << config.window.maxWidth << "\n"; + oss << "maxHeight=" << config.window.maxHeight << "\n"; + oss << "mode=" << windowModeToString(config.window.mode) << "\n"; + oss << "resizable=" << (config.window.resizable ? "true" : "false") << "\n"; + oss << "borderless=" << (config.window.borderless ? "true" : "false") << "\n"; + oss << "alwaysOnTop=" << (config.window.alwaysOnTop ? "true" : "false") << "\n"; + oss << "centered=" << (config.window.centered ? "true" : "false") << "\n"; + oss << "posX=" << config.window.posX << "\n"; + oss << "posY=" << config.window.posY << "\n"; + oss << "hideOnClose=" << (config.window.hideOnClose ? "true" : "false") << "\n"; + oss << "minimizeOnClose=" << (config.window.minimizeOnClose ? "true" : "false") << "\n"; + oss << "opacity=" << config.window.opacity << "\n"; + oss << "transparentFramebuffer=" << (config.window.transparentFramebuffer ? "true" : "false") << "\n"; + oss << "highDPI=" << (config.window.highDPI ? "true" : "false") << "\n"; + oss << "contentScale=" << config.window.contentScale << "\n"; + oss << "\n"; + + oss << "[render]\n"; + oss << "backend=" << backendTypeToString(config.render.backend) << "\n"; + oss << "targetFPS=" << config.render.targetFPS << "\n"; + oss << "vsync=" << (config.render.vsync ? "true" : "false") << "\n"; + oss << "tripleBuffering=" << (config.render.tripleBuffering ? "true" : "false") << "\n"; + oss << "multisamples=" << config.render.multisamples << "\n"; + oss << "sRGBFramebuffer=" << (config.render.sRGBFramebuffer ? "true" : "false") << "\n"; + oss << "clearColorR=" << config.render.clearColor.r << "\n"; + oss << "clearColorG=" << config.render.clearColor.g << "\n"; + oss << "clearColorB=" << config.render.clearColor.b << "\n"; + oss << "clearColorA=" << config.render.clearColor.a << "\n"; + oss << "maxTextureSize=" << config.render.maxTextureSize << "\n"; + oss << "textureAnisotropy=" << config.render.textureAnisotropy << "\n"; + oss << "wireframeMode=" << (config.render.wireframeMode ? "true" : "false") << "\n"; + oss << "depthTest=" << (config.render.depthTest ? "true" : "false") << "\n"; + oss << "blending=" << (config.render.blending ? "true" : "false") << "\n"; + oss << "dithering=" << (config.render.dithering ? "true" : "false") << "\n"; + oss << "spriteBatchSize=" << config.render.spriteBatchSize << "\n"; + oss << "maxRenderTargets=" << config.render.maxRenderTargets << "\n"; + oss << "allowShaderHotReload=" << (config.render.allowShaderHotReload ? "true" : "false") << "\n"; + oss << "shaderCachePath=" << config.render.shaderCachePath << "\n"; + oss << "\n"; + + oss << "[audio]\n"; + oss << "enabled=" << (config.audio.enabled ? "true" : "false") << "\n"; + oss << "masterVolume=" << config.audio.masterVolume << "\n"; + oss << "musicVolume=" << config.audio.musicVolume << "\n"; + oss << "sfxVolume=" << config.audio.sfxVolume << "\n"; + oss << "voiceVolume=" << config.audio.voiceVolume << "\n"; + oss << "ambientVolume=" << config.audio.ambientVolume << "\n"; + oss << "frequency=" << config.audio.frequency << "\n"; + oss << "channels=" << config.audio.channels << "\n"; + oss << "chunkSize=" << config.audio.chunkSize << "\n"; + oss << "maxChannels=" << config.audio.maxChannels << "\n"; + oss << "spatialAudio=" << (config.audio.spatialAudio ? "true" : "false") << "\n"; + oss << "\n"; + + oss << "[debug]\n"; + oss << "enabled=" << (config.debug.enabled ? "true" : "false") << "\n"; + oss << "showFPS=" << (config.debug.showFPS ? "true" : "false") << "\n"; + oss << "showMemoryUsage=" << (config.debug.showMemoryUsage ? "true" : "false") << "\n"; + oss << "showRenderStats=" << (config.debug.showRenderStats ? "true" : "false") << "\n"; + oss << "showColliders=" << (config.debug.showColliders ? "true" : "false") << "\n"; + oss << "showGrid=" << (config.debug.showGrid ? "true" : "false") << "\n"; + oss << "logToFile=" << (config.debug.logToFile ? "true" : "false") << "\n"; + oss << "logToConsole=" << (config.debug.logToConsole ? "true" : "false") << "\n"; + oss << "logLevel=" << config.debug.logLevel << "\n"; + oss << "breakOnAssert=" << (config.debug.breakOnAssert ? "true" : "false") << "\n"; + oss << "enableProfiling=" << (config.debug.enableProfiling ? "true" : "false") << "\n"; + oss << "logFilePath=" << config.debug.logFilePath << "\n"; + oss << "\n"; + + oss << "[input]\n"; + oss << "enabled=" << (config.input.enabled ? "true" : "false") << "\n"; + oss << "rawMouseInput=" << (config.input.rawMouseInput ? "true" : "false") << "\n"; + oss << "mouseSensitivity=" << config.input.mouseSensitivity << "\n"; + oss << "invertMouseY=" << (config.input.invertMouseY ? "true" : "false") << "\n"; + oss << "invertMouseX=" << (config.input.invertMouseX ? "true" : "false") << "\n"; + oss << "deadzone=" << config.input.deadzone << "\n"; + oss << "triggerThreshold=" << config.input.triggerThreshold << "\n"; + oss << "enableVibration=" << (config.input.enableVibration ? "true" : "false") << "\n"; + oss << "maxGamepads=" << config.input.maxGamepads << "\n"; + oss << "autoConnectGamepads=" << (config.input.autoConnectGamepads ? "true" : "false") << "\n"; + oss << "gamepadMappingFile=" << config.input.gamepadMappingFile << "\n"; + oss << "\n"; + + oss << "[resource]\n"; + oss << "assetRootPath=" << config.resource.assetRootPath << "\n"; + oss << "cachePath=" << config.resource.cachePath << "\n"; + oss << "savePath=" << config.resource.savePath << "\n"; + oss << "configPath=" << config.resource.configPath << "\n"; + oss << "logPath=" << config.resource.logPath << "\n"; + oss << "useAssetCache=" << (config.resource.useAssetCache ? "true" : "false") << "\n"; + oss << "maxCacheSize=" << config.resource.maxCacheSize << "\n"; + oss << "hotReloadEnabled=" << (config.resource.hotReloadEnabled ? "true" : "false") << "\n"; + oss << "hotReloadInterval=" << config.resource.hotReloadInterval << "\n"; + oss << "compressTextures=" << (config.resource.compressTextures ? "true" : "false") << "\n"; + oss << "preloadCommonAssets=" << (config.resource.preloadCommonAssets ? "true" : "false") << "\n"; + + return oss.str(); +} + +/** + * @brief 检查是否支持指定文件 + * @param filepath 文件路径 + * @return 如果支持返回 true + */ +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(std::tolower(c)); + return ext == ".ini"; + } + return false; +} + +/** + * @brief 克隆加载器实例 + * @return 新的加载器实例 + */ +UniquePtr IniConfigLoader::clone() const { + return makeUnique(); +} + +/** + * @brief 生成节键组合字符串 + * @param section 节名 + * @param key 键名 + * @return 组合字符串 + */ +std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const { + return section + "." + key; +} + +/** + * @brief 解析整数 + * @param value 字符串值 + * @param result 输出整数结果 + * @param fieldName 字段名(用于错误报告) + * @return 解析结果 + */ +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); + } +} + +/** + * @brief 解析浮点数 + * @param value 字符串值 + * @param result 输出浮点数结果 + * @param fieldName 字段名(用于错误报告) + * @return 解析结果 + */ +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); + } +} + +/** + * @brief 解析布尔值 + * @param value 字符串值 + * @param result 输出布尔结果 + * @param fieldName 字段名(用于错误报告) + * @return 解析结果 + */ +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); +} + +} diff --git a/Extra2D/src/config/config_loader_json.cpp b/Extra2D/src/config/config_loader_json.cpp new file mode 100644 index 0000000..89badc8 --- /dev/null +++ b/Extra2D/src/config/config_loader_json.cpp @@ -0,0 +1,763 @@ +#include +#include + +#include +#include +#include + +using json = nlohmann::json; + +namespace extra2d { + +/** + * @brief 将字符串转换为窗口模式枚举 + * @param modeStr 窗口模式字符串 + * @return 窗口模式枚举值 + */ +static WindowMode stringToWindowMode(const std::string& modeStr) { + if (modeStr == "fullscreen") return WindowMode::Fullscreen; + if (modeStr == "borderless") return WindowMode::Borderless; + return WindowMode::Windowed; +} + +/** + * @brief 将窗口模式枚举转换为字符串 + * @param mode 窗口模式枚举值 + * @return 窗口模式字符串 + */ +static std::string windowModeToString(WindowMode mode) { + switch (mode) { + case WindowMode::Fullscreen: return "fullscreen"; + case WindowMode::Borderless: return "borderless"; + default: return "windowed"; + } +} + +/** + * @brief 将字符串转换为渲染后端类型枚举 + * @param backendStr 后端类型字符串 + * @return 渲染后端类型枚举值 + */ +static BackendType stringToBackendType(const std::string& backendStr) { + if (backendStr == "opengl") return BackendType::OpenGL; + return BackendType::OpenGL; +} + +/** + * @brief 将渲染后端类型枚举转换为字符串 + * @param backend 渲染后端类型枚举值 + * @return 后端类型字符串 + */ +static std::string backendTypeToString(BackendType backend) { + switch (backend) { + case BackendType::OpenGL: return "opengl"; + default: return "opengl"; + } +} + +// ============================================================================ +// JsonConfigLoader 实现 +// ============================================================================ + +/** + * @brief 从 JSON 文件加载配置 + * @param filepath 配置文件路径 + * @param config 输出的配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig& config) { + E2D_LOG_INFO("正在从 JSON 文件加载配置: {}", 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); +} + +/** + * @brief 保存配置到 JSON 文件 + * @param filepath 配置文件路径 + * @param config 要保存的配置对象 + * @return 保存结果 + */ +ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppConfig& config) { + E2D_LOG_INFO("正在保存配置到 JSON 文件: {}", 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(); +} + +/** + * @brief 从 JSON 字符串加载配置 + * @param content JSON 内容字符串 + * @param config 输出的配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) { + json root; + + try { + root = json::parse(content); + } catch (const json::parse_error& e) { + E2D_LOG_ERROR("JSON 解析错误: {}", e.what()); + return ConfigLoadResult::error(std::string("JSON 解析错误: ") + e.what(), + static_cast(e.byte)); + } + + if (root.contains("appName") && root["appName"].is_string()) { + config.appName = root["appName"].get(); + } + + if (root.contains("appVersion") && root["appVersion"].is_string()) { + config.appVersion = root["appVersion"].get(); + } + + if (root.contains("organization") && root["organization"].is_string()) { + config.organization = root["organization"].get(); + } + + if (root.contains("window")) { + auto result = parseWindowConfig(&root["window"], config.window); + if (result.hasError()) { + return result; + } + } + + if (root.contains("render")) { + auto result = parseRenderConfig(&root["render"], config.render); + if (result.hasError()) { + return result; + } + } + + if (root.contains("audio")) { + auto result = parseAudioConfig(&root["audio"], config.audio); + if (result.hasError()) { + return result; + } + } + + if (root.contains("debug")) { + auto result = parseDebugConfig(&root["debug"], config.debug); + if (result.hasError()) { + return result; + } + } + + if (root.contains("input")) { + auto result = parseInputConfig(&root["input"], config.input); + if (result.hasError()) { + return result; + } + } + + if (root.contains("resource")) { + auto result = parseResourceConfig(&root["resource"], config.resource); + if (result.hasError()) { + return result; + } + } + + E2D_LOG_INFO("JSON 配置加载成功"); + return ConfigLoadResult::ok(); +} + +/** + * @brief 将配置序列化为 JSON 字符串 + * @param config 配置对象 + * @return 序列化后的 JSON 字符串 + */ +std::string JsonConfigLoader::saveToString(const AppConfig& config) { + json root; + + root["appName"] = config.appName; + root["appVersion"] = config.appVersion; + root["organization"] = config.organization; + + serializeWindowConfig(&root, config.window); + serializeRenderConfig(&root, config.render); + serializeAudioConfig(&root, config.audio); + serializeDebugConfig(&root, config.debug); + serializeInputConfig(&root, config.input); + serializeResourceConfig(&root, config.resource); + + return root.dump(4); +} + +/** + * @brief 检查是否支持指定文件 + * @param filepath 文件路径 + * @return 如果支持返回 true + */ +bool JsonConfigLoader::supportsFile(const std::string& filepath) const { + if (filepath.length() >= 5) { + std::string ext = filepath.substr(filepath.length() - 5); + for (char& c : ext) c = static_cast(std::tolower(c)); + return ext == ".json"; + } + return false; +} + +/** + * @brief 克隆加载器实例 + * @return 新的加载器实例 + */ +UniquePtr JsonConfigLoader::clone() const { + return makeUnique(); +} + +/** + * @brief 解析窗口配置 + * @param jsonValue JSON 值指针 + * @param window 输出的窗口配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseWindowConfig(const void* jsonValue, WindowConfigData& window) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("window 配置必须是一个对象", -1, "window"); + } + + if (obj.contains("title") && obj["title"].is_string()) { + window.title = obj["title"].get(); + } + if (obj.contains("width") && obj["width"].is_number_integer()) { + window.width = obj["width"].get(); + } + if (obj.contains("height") && obj["height"].is_number_integer()) { + window.height = obj["height"].get(); + } + if (obj.contains("minWidth") && obj["minWidth"].is_number_integer()) { + window.minWidth = obj["minWidth"].get(); + } + if (obj.contains("minHeight") && obj["minHeight"].is_number_integer()) { + window.minHeight = obj["minHeight"].get(); + } + if (obj.contains("maxWidth") && obj["maxWidth"].is_number_integer()) { + window.maxWidth = obj["maxWidth"].get(); + } + if (obj.contains("maxHeight") && obj["maxHeight"].is_number_integer()) { + window.maxHeight = obj["maxHeight"].get(); + } + if (obj.contains("mode") && obj["mode"].is_string()) { + window.mode = stringToWindowMode(obj["mode"].get()); + } + if (obj.contains("resizable") && obj["resizable"].is_boolean()) { + window.resizable = obj["resizable"].get(); + } + if (obj.contains("borderless") && obj["borderless"].is_boolean()) { + window.borderless = obj["borderless"].get(); + } + if (obj.contains("alwaysOnTop") && obj["alwaysOnTop"].is_boolean()) { + window.alwaysOnTop = obj["alwaysOnTop"].get(); + } + if (obj.contains("centered") && obj["centered"].is_boolean()) { + window.centered = obj["centered"].get(); + } + if (obj.contains("posX") && obj["posX"].is_number_integer()) { + window.posX = obj["posX"].get(); + } + if (obj.contains("posY") && obj["posY"].is_number_integer()) { + window.posY = obj["posY"].get(); + } + if (obj.contains("hideOnClose") && obj["hideOnClose"].is_boolean()) { + window.hideOnClose = obj["hideOnClose"].get(); + } + if (obj.contains("minimizeOnClose") && obj["minimizeOnClose"].is_boolean()) { + window.minimizeOnClose = obj["minimizeOnClose"].get(); + } + if (obj.contains("opacity") && obj["opacity"].is_number()) { + window.opacity = obj["opacity"].get(); + } + if (obj.contains("transparentFramebuffer") && obj["transparentFramebuffer"].is_boolean()) { + window.transparentFramebuffer = obj["transparentFramebuffer"].get(); + } + if (obj.contains("highDPI") && obj["highDPI"].is_boolean()) { + window.highDPI = obj["highDPI"].get(); + } + if (obj.contains("contentScale") && obj["contentScale"].is_number()) { + window.contentScale = obj["contentScale"].get(); + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 解析渲染配置 + * @param jsonValue JSON 值指针 + * @param render 输出的渲染配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseRenderConfig(const void* jsonValue, RenderConfigData& render) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("render 配置必须是一个对象", -1, "render"); + } + + if (obj.contains("backend") && obj["backend"].is_string()) { + render.backend = stringToBackendType(obj["backend"].get()); + } + if (obj.contains("targetFPS") && obj["targetFPS"].is_number_integer()) { + render.targetFPS = obj["targetFPS"].get(); + } + if (obj.contains("vsync") && obj["vsync"].is_boolean()) { + render.vsync = obj["vsync"].get(); + } + if (obj.contains("tripleBuffering") && obj["tripleBuffering"].is_boolean()) { + render.tripleBuffering = obj["tripleBuffering"].get(); + } + if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) { + render.multisamples = obj["multisamples"].get(); + } + if (obj.contains("sRGBFramebuffer") && obj["sRGBFramebuffer"].is_boolean()) { + render.sRGBFramebuffer = obj["sRGBFramebuffer"].get(); + } + if (obj.contains("clearColor") && obj["clearColor"].is_array() && obj["clearColor"].size() >= 4) { + render.clearColor.r = obj["clearColor"][0].get(); + render.clearColor.g = obj["clearColor"][1].get(); + render.clearColor.b = obj["clearColor"][2].get(); + render.clearColor.a = obj["clearColor"][3].get(); + } + if (obj.contains("maxTextureSize") && obj["maxTextureSize"].is_number_integer()) { + render.maxTextureSize = obj["maxTextureSize"].get(); + } + if (obj.contains("textureAnisotropy") && obj["textureAnisotropy"].is_number_integer()) { + render.textureAnisotropy = obj["textureAnisotropy"].get(); + } + if (obj.contains("wireframeMode") && obj["wireframeMode"].is_boolean()) { + render.wireframeMode = obj["wireframeMode"].get(); + } + if (obj.contains("depthTest") && obj["depthTest"].is_boolean()) { + render.depthTest = obj["depthTest"].get(); + } + if (obj.contains("blending") && obj["blending"].is_boolean()) { + render.blending = obj["blending"].get(); + } + if (obj.contains("dithering") && obj["dithering"].is_boolean()) { + render.dithering = obj["dithering"].get(); + } + if (obj.contains("spriteBatchSize") && obj["spriteBatchSize"].is_number_integer()) { + render.spriteBatchSize = obj["spriteBatchSize"].get(); + } + if (obj.contains("maxRenderTargets") && obj["maxRenderTargets"].is_number_integer()) { + render.maxRenderTargets = obj["maxRenderTargets"].get(); + } + if (obj.contains("allowShaderHotReload") && obj["allowShaderHotReload"].is_boolean()) { + render.allowShaderHotReload = obj["allowShaderHotReload"].get(); + } + if (obj.contains("shaderCachePath") && obj["shaderCachePath"].is_string()) { + render.shaderCachePath = obj["shaderCachePath"].get(); + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 解析音频配置 + * @param jsonValue JSON 值指针 + * @param audio 输出的音频配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseAudioConfig(const void* jsonValue, AudioConfigData& audio) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("audio 配置必须是一个对象", -1, "audio"); + } + + if (obj.contains("enabled") && obj["enabled"].is_boolean()) { + audio.enabled = obj["enabled"].get(); + } + if (obj.contains("masterVolume") && obj["masterVolume"].is_number_integer()) { + audio.masterVolume = obj["masterVolume"].get(); + } + if (obj.contains("musicVolume") && obj["musicVolume"].is_number_integer()) { + audio.musicVolume = obj["musicVolume"].get(); + } + if (obj.contains("sfxVolume") && obj["sfxVolume"].is_number_integer()) { + audio.sfxVolume = obj["sfxVolume"].get(); + } + if (obj.contains("voiceVolume") && obj["voiceVolume"].is_number_integer()) { + audio.voiceVolume = obj["voiceVolume"].get(); + } + if (obj.contains("ambientVolume") && obj["ambientVolume"].is_number_integer()) { + audio.ambientVolume = obj["ambientVolume"].get(); + } + if (obj.contains("frequency") && obj["frequency"].is_number_integer()) { + audio.frequency = obj["frequency"].get(); + } + if (obj.contains("channels") && obj["channels"].is_number_integer()) { + audio.channels = obj["channels"].get(); + } + if (obj.contains("chunkSize") && obj["chunkSize"].is_number_integer()) { + audio.chunkSize = obj["chunkSize"].get(); + } + if (obj.contains("maxChannels") && obj["maxChannels"].is_number_integer()) { + audio.maxChannels = obj["maxChannels"].get(); + } + if (obj.contains("spatialAudio") && obj["spatialAudio"].is_boolean()) { + audio.spatialAudio = obj["spatialAudio"].get(); + } + if (obj.contains("listenerPosition") && obj["listenerPosition"].is_array() && obj["listenerPosition"].size() >= 3) { + audio.listenerPosition[0] = obj["listenerPosition"][0].get(); + audio.listenerPosition[1] = obj["listenerPosition"][1].get(); + audio.listenerPosition[2] = obj["listenerPosition"][2].get(); + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 解析调试配置 + * @param jsonValue JSON 值指针 + * @param debug 输出的调试配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseDebugConfig(const void* jsonValue, DebugConfigData& debug) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("debug 配置必须是一个对象", -1, "debug"); + } + + if (obj.contains("enabled") && obj["enabled"].is_boolean()) { + debug.enabled = obj["enabled"].get(); + } + if (obj.contains("showFPS") && obj["showFPS"].is_boolean()) { + debug.showFPS = obj["showFPS"].get(); + } + if (obj.contains("showMemoryUsage") && obj["showMemoryUsage"].is_boolean()) { + debug.showMemoryUsage = obj["showMemoryUsage"].get(); + } + if (obj.contains("showRenderStats") && obj["showRenderStats"].is_boolean()) { + debug.showRenderStats = obj["showRenderStats"].get(); + } + if (obj.contains("showColliders") && obj["showColliders"].is_boolean()) { + debug.showColliders = obj["showColliders"].get(); + } + if (obj.contains("showGrid") && obj["showGrid"].is_boolean()) { + debug.showGrid = obj["showGrid"].get(); + } + if (obj.contains("logToFile") && obj["logToFile"].is_boolean()) { + debug.logToFile = obj["logToFile"].get(); + } + if (obj.contains("logToConsole") && obj["logToConsole"].is_boolean()) { + debug.logToConsole = obj["logToConsole"].get(); + } + if (obj.contains("logLevel") && obj["logLevel"].is_number_integer()) { + debug.logLevel = obj["logLevel"].get(); + } + if (obj.contains("breakOnAssert") && obj["breakOnAssert"].is_boolean()) { + debug.breakOnAssert = obj["breakOnAssert"].get(); + } + if (obj.contains("enableProfiling") && obj["enableProfiling"].is_boolean()) { + debug.enableProfiling = obj["enableProfiling"].get(); + } + if (obj.contains("logFilePath") && obj["logFilePath"].is_string()) { + debug.logFilePath = obj["logFilePath"].get(); + } + if (obj.contains("debugFlags") && obj["debugFlags"].is_array()) { + debug.debugFlags.clear(); + for (const auto& flag : obj["debugFlags"]) { + if (flag.is_string()) { + debug.debugFlags.push_back(flag.get()); + } + } + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 解析输入配置 + * @param jsonValue JSON 值指针 + * @param input 输出的输入配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseInputConfig(const void* jsonValue, InputConfigData& input) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("input 配置必须是一个对象", -1, "input"); + } + + if (obj.contains("enabled") && obj["enabled"].is_boolean()) { + input.enabled = obj["enabled"].get(); + } + if (obj.contains("rawMouseInput") && obj["rawMouseInput"].is_boolean()) { + input.rawMouseInput = obj["rawMouseInput"].get(); + } + if (obj.contains("mouseSensitivity") && obj["mouseSensitivity"].is_number()) { + input.mouseSensitivity = obj["mouseSensitivity"].get(); + } + if (obj.contains("invertMouseY") && obj["invertMouseY"].is_boolean()) { + input.invertMouseY = obj["invertMouseY"].get(); + } + if (obj.contains("invertMouseX") && obj["invertMouseX"].is_boolean()) { + input.invertMouseX = obj["invertMouseX"].get(); + } + if (obj.contains("deadzone") && obj["deadzone"].is_number()) { + input.deadzone = obj["deadzone"].get(); + } + if (obj.contains("triggerThreshold") && obj["triggerThreshold"].is_number()) { + input.triggerThreshold = obj["triggerThreshold"].get(); + } + if (obj.contains("enableVibration") && obj["enableVibration"].is_boolean()) { + input.enableVibration = obj["enableVibration"].get(); + } + if (obj.contains("maxGamepads") && obj["maxGamepads"].is_number_integer()) { + input.maxGamepads = obj["maxGamepads"].get(); + } + if (obj.contains("autoConnectGamepads") && obj["autoConnectGamepads"].is_boolean()) { + input.autoConnectGamepads = obj["autoConnectGamepads"].get(); + } + if (obj.contains("gamepadMappingFile") && obj["gamepadMappingFile"].is_string()) { + input.gamepadMappingFile = obj["gamepadMappingFile"].get(); + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 解析资源配置 + * @param jsonValue JSON 值指针 + * @param resource 输出的资源配置对象 + * @return 加载结果 + */ +ConfigLoadResult JsonConfigLoader::parseResourceConfig(const void* jsonValue, ResourceConfigData& resource) { + const json& obj = *static_cast(jsonValue); + + if (!obj.is_object()) { + return ConfigLoadResult::error("resource 配置必须是一个对象", -1, "resource"); + } + + if (obj.contains("assetRootPath") && obj["assetRootPath"].is_string()) { + resource.assetRootPath = obj["assetRootPath"].get(); + } + if (obj.contains("cachePath") && obj["cachePath"].is_string()) { + resource.cachePath = obj["cachePath"].get(); + } + if (obj.contains("savePath") && obj["savePath"].is_string()) { + resource.savePath = obj["savePath"].get(); + } + if (obj.contains("configPath") && obj["configPath"].is_string()) { + resource.configPath = obj["configPath"].get(); + } + if (obj.contains("logPath") && obj["logPath"].is_string()) { + resource.logPath = obj["logPath"].get(); + } + if (obj.contains("useAssetCache") && obj["useAssetCache"].is_boolean()) { + resource.useAssetCache = obj["useAssetCache"].get(); + } + if (obj.contains("maxCacheSize") && obj["maxCacheSize"].is_number_integer()) { + resource.maxCacheSize = obj["maxCacheSize"].get(); + } + if (obj.contains("hotReloadEnabled") && obj["hotReloadEnabled"].is_boolean()) { + resource.hotReloadEnabled = obj["hotReloadEnabled"].get(); + } + if (obj.contains("hotReloadInterval") && obj["hotReloadInterval"].is_number()) { + resource.hotReloadInterval = obj["hotReloadInterval"].get(); + } + if (obj.contains("compressTextures") && obj["compressTextures"].is_boolean()) { + resource.compressTextures = obj["compressTextures"].get(); + } + if (obj.contains("preloadCommonAssets") && obj["preloadCommonAssets"].is_boolean()) { + resource.preloadCommonAssets = obj["preloadCommonAssets"].get(); + } + if (obj.contains("searchPaths") && obj["searchPaths"].is_array()) { + resource.searchPaths.clear(); + for (const auto& path : obj["searchPaths"]) { + if (path.is_string()) { + resource.searchPaths.push_back(path.get()); + } + } + } + + return ConfigLoadResult::ok(); +} + +/** + * @brief 序列化窗口配置到 JSON + * @param jsonValue JSON 值指针 + * @param window 窗口配置对象 + */ +void JsonConfigLoader::serializeWindowConfig(void* jsonValue, const WindowConfigData& window) { + json& root = *static_cast(jsonValue); + json obj; + + obj["title"] = window.title; + obj["width"] = window.width; + obj["height"] = window.height; + obj["minWidth"] = window.minWidth; + obj["minHeight"] = window.minHeight; + obj["maxWidth"] = window.maxWidth; + obj["maxHeight"] = window.maxHeight; + obj["mode"] = windowModeToString(window.mode); + obj["resizable"] = window.resizable; + obj["borderless"] = window.borderless; + obj["alwaysOnTop"] = window.alwaysOnTop; + obj["centered"] = window.centered; + obj["posX"] = window.posX; + obj["posY"] = window.posY; + obj["hideOnClose"] = window.hideOnClose; + obj["minimizeOnClose"] = window.minimizeOnClose; + obj["opacity"] = window.opacity; + obj["transparentFramebuffer"] = window.transparentFramebuffer; + obj["highDPI"] = window.highDPI; + obj["contentScale"] = window.contentScale; + + root["window"] = obj; +} + +/** + * @brief 序列化渲染配置到 JSON + * @param jsonValue JSON 值指针 + * @param render 渲染配置对象 + */ +void JsonConfigLoader::serializeRenderConfig(void* jsonValue, const RenderConfigData& render) { + json& root = *static_cast(jsonValue); + json obj; + + obj["backend"] = backendTypeToString(render.backend); + obj["targetFPS"] = render.targetFPS; + obj["vsync"] = render.vsync; + obj["tripleBuffering"] = render.tripleBuffering; + obj["multisamples"] = render.multisamples; + obj["sRGBFramebuffer"] = render.sRGBFramebuffer; + obj["clearColor"] = {render.clearColor.r, render.clearColor.g, render.clearColor.b, render.clearColor.a}; + obj["maxTextureSize"] = render.maxTextureSize; + obj["textureAnisotropy"] = render.textureAnisotropy; + obj["wireframeMode"] = render.wireframeMode; + obj["depthTest"] = render.depthTest; + obj["blending"] = render.blending; + obj["dithering"] = render.dithering; + obj["spriteBatchSize"] = render.spriteBatchSize; + obj["maxRenderTargets"] = render.maxRenderTargets; + obj["allowShaderHotReload"] = render.allowShaderHotReload; + obj["shaderCachePath"] = render.shaderCachePath; + + root["render"] = obj; +} + +/** + * @brief 序列化音频配置到 JSON + * @param jsonValue JSON 值指针 + * @param audio 音频配置对象 + */ +void JsonConfigLoader::serializeAudioConfig(void* jsonValue, const AudioConfigData& audio) { + json& root = *static_cast(jsonValue); + json obj; + + obj["enabled"] = audio.enabled; + obj["masterVolume"] = audio.masterVolume; + obj["musicVolume"] = audio.musicVolume; + obj["sfxVolume"] = audio.sfxVolume; + obj["voiceVolume"] = audio.voiceVolume; + obj["ambientVolume"] = audio.ambientVolume; + obj["frequency"] = audio.frequency; + obj["channels"] = audio.channels; + obj["chunkSize"] = audio.chunkSize; + obj["maxChannels"] = audio.maxChannels; + obj["spatialAudio"] = audio.spatialAudio; + obj["listenerPosition"] = {audio.listenerPosition[0], audio.listenerPosition[1], audio.listenerPosition[2]}; + + root["audio"] = obj; +} + +/** + * @brief 序列化调试配置到 JSON + * @param jsonValue JSON 值指针 + * @param debug 调试配置对象 + */ +void JsonConfigLoader::serializeDebugConfig(void* jsonValue, const DebugConfigData& debug) { + json& root = *static_cast(jsonValue); + json obj; + + obj["enabled"] = debug.enabled; + obj["showFPS"] = debug.showFPS; + obj["showMemoryUsage"] = debug.showMemoryUsage; + obj["showRenderStats"] = debug.showRenderStats; + obj["showColliders"] = debug.showColliders; + obj["showGrid"] = debug.showGrid; + obj["logToFile"] = debug.logToFile; + obj["logToConsole"] = debug.logToConsole; + obj["logLevel"] = debug.logLevel; + obj["breakOnAssert"] = debug.breakOnAssert; + obj["enableProfiling"] = debug.enableProfiling; + obj["logFilePath"] = debug.logFilePath; + obj["debugFlags"] = debug.debugFlags; + + root["debug"] = obj; +} + +/** + * @brief 序列化输入配置到 JSON + * @param jsonValue JSON 值指针 + * @param input 输入配置对象 + */ +void JsonConfigLoader::serializeInputConfig(void* jsonValue, const InputConfigData& input) { + json& root = *static_cast(jsonValue); + json obj; + + obj["enabled"] = input.enabled; + obj["rawMouseInput"] = input.rawMouseInput; + obj["mouseSensitivity"] = input.mouseSensitivity; + obj["invertMouseY"] = input.invertMouseY; + obj["invertMouseX"] = input.invertMouseX; + obj["deadzone"] = input.deadzone; + obj["triggerThreshold"] = input.triggerThreshold; + obj["enableVibration"] = input.enableVibration; + obj["maxGamepads"] = input.maxGamepads; + obj["autoConnectGamepads"] = input.autoConnectGamepads; + obj["gamepadMappingFile"] = input.gamepadMappingFile; + + root["input"] = obj; +} + +/** + * @brief 序列化资源配置到 JSON + * @param jsonValue JSON 值指针 + * @param resource 资源配置对象 + */ +void JsonConfigLoader::serializeResourceConfig(void* jsonValue, const ResourceConfigData& resource) { + json& root = *static_cast(jsonValue); + json obj; + + obj["assetRootPath"] = resource.assetRootPath; + obj["cachePath"] = resource.cachePath; + obj["savePath"] = resource.savePath; + obj["configPath"] = resource.configPath; + obj["logPath"] = resource.logPath; + obj["useAssetCache"] = resource.useAssetCache; + obj["maxCacheSize"] = resource.maxCacheSize; + obj["hotReloadEnabled"] = resource.hotReloadEnabled; + obj["hotReloadInterval"] = resource.hotReloadInterval; + obj["compressTextures"] = resource.compressTextures; + obj["preloadCommonAssets"] = resource.preloadCommonAssets; + obj["searchPaths"] = resource.searchPaths; + + root["resource"] = obj; +} + +} diff --git a/Extra2D/src/config/config_manager.cpp b/Extra2D/src/config/config_manager.cpp new file mode 100644 index 0000000..edabb96 --- /dev/null +++ b/Extra2D/src/config/config_manager.cpp @@ -0,0 +1,516 @@ +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 构造函数 + * 初始化配置管理器的成员变量 + */ +ConfigManager::ConfigManager() + : m_nextCallbackId(1) + , m_autoSaveEnabled(false) + , m_autoSaveInterval(30.0f) + , m_autoSaveTimer(0.0f) +{ + m_appConfig = AppConfig::createDefault(); +} + +/** + * @brief 析构函数 + * 确保在销毁时关闭配置管理器 + */ +ConfigManager::~ConfigManager() { + if (m_initialized) { + shutdown(); + } +} + +/** + * @brief 获取单例实例 + * 使用静态局部变量实现线程安全的单例模式 + * @return 配置管理器实例引用 + */ +ConfigManager& ConfigManager::instance() { + static ConfigManager instance; + return instance; +} + +/** + * @brief 初始化配置管理器 + * 创建平台配置和配置加载器 + * @param configPath 配置文件路径 + * @return 如果初始化成功返回 true + */ +bool ConfigManager::initialize(const std::string& configPath) { + if (m_initialized) { + E2D_LOG_WARN("ConfigManager already initialized"); + return true; + } + + std::lock_guard lock(m_mutex); + + m_configPath = configPath; + + m_platformConfig = createPlatformConfig(PlatformType::Auto); + if (!m_platformConfig) { + E2D_LOG_ERROR("Failed to create platform config"); + return false; + } + + m_loader = makeUnique(); + if (!m_loader) { + E2D_LOG_ERROR("Failed to create config loader"); + return false; + } + + m_appConfig = AppConfig::createDefault(); + m_appConfig.targetPlatform = PlatformDetector::detect(); + + m_platformConfig->applyDefaults(m_appConfig); + + m_initialized = true; + m_modified = false; + + E2D_LOG_INFO("ConfigManager initialized for platform: {}", + m_platformConfig->platformName()); + return true; +} + +/** + * @brief 关闭配置管理器 + * 清理所有资源并重置状态 + */ +void ConfigManager::shutdown() { + if (!m_initialized) { + return; + } + + std::lock_guard lock(m_mutex); + + if (m_autoSaveEnabled && m_modified) { + saveConfig(); + } + + m_changeCallbacks.clear(); + m_moduleConfigs.clear(); + m_loader.reset(); + m_platformConfig.reset(); + + m_initialized = false; + m_modified = false; + + E2D_LOG_INFO("ConfigManager shutdown complete"); +} + +/** + * @brief 检查是否已初始化 + * @return 如果已初始化返回 true + */ +bool ConfigManager::isInitialized() const { + return m_initialized; +} + +/** + * @brief 加载配置文件 + * @param filepath 配置文件路径(可选,默认使用初始化时的路径) + * @return 加载结果 + */ +ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) { + std::lock_guard lock(m_mutex); + + std::string path = filepath.empty() ? m_configPath : filepath; + if (path.empty()) { + return ConfigLoadResult::error("No config file path specified"); + } + + if (!m_loader) { + return ConfigLoadResult::error("Config loader not initialized"); + } + + AppConfig loadedConfig; + ConfigLoadResult result = m_loader->load(path, loadedConfig); + + if (result.success) { + m_appConfig.merge(loadedConfig); + + if (m_platformConfig) { + m_appConfig.applyPlatformConstraints(*m_platformConfig); + } + + if (!m_appConfig.validate()) { + E2D_LOG_WARN("Loaded config validation failed, using defaults"); + m_appConfig = AppConfig::createDefault(); + } + + m_configPath = path; + m_modified = false; + + E2D_LOG_INFO("Config loaded from: {}", path); + } else { + E2D_LOG_ERROR("Failed to load config from {}: {}", path, result.errorMessage); + } + + return result; +} + +/** + * @brief 保存配置到文件 + * @param filepath 配置文件路径(可选,默认使用初始化时的路径) + * @return 保存结果 + */ +ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) { + std::lock_guard lock(m_mutex); + + std::string path = filepath.empty() ? m_configPath : filepath; + if (path.empty()) { + return ConfigSaveResult::error("No config file path specified"); + } + + if (!m_loader) { + return ConfigSaveResult::error("Config loader not initialized"); + } + + ConfigSaveResult result = m_loader->save(path, m_appConfig); + + if (result.success) { + m_configPath = path; + m_modified = false; + + E2D_LOG_INFO("Config saved to: {}", path); + } else { + E2D_LOG_ERROR("Failed to save config to {}: {}", path, result.errorMessage); + } + + return result; +} + +/** + * @brief 重新加载配置 + * @return 加载结果 + */ +ConfigLoadResult ConfigManager::reload() { + return loadConfig(m_configPath); +} + +/** + * @brief 获取应用配置 + * @return 应用配置的常量引用 + */ +const AppConfig& ConfigManager::appConfig() const { + return m_appConfig; +} + +/** + * @brief 获取可修改的应用配置 + * @return 应用配置的引用 + */ +AppConfig& ConfigManager::appConfig() { + m_modified = true; + return m_appConfig; +} + +/** + * @brief 设置应用配置 + * @param config 新的配置 + */ +void ConfigManager::setAppConfig(const AppConfig& config) { + std::lock_guard lock(m_mutex); + + m_appConfig = config; + m_modified = true; + + E2D_LOG_INFO("App config updated"); +} + +/** + * @brief 获取平台配置 + * @return 平台配置接口指针 + */ +PlatformConfig* ConfigManager::platformConfig() { + return m_platformConfig.get(); +} + +/** + * @brief 获取平台配置(常量版本) + * @return 平台配置接口常量指针 + */ +const PlatformConfig* ConfigManager::platformConfig() const { + return m_platformConfig.get(); +} + +/** + * @brief 注册配置变更回调 + * @param callback 回调函数 + * @return 回调ID,用于取消注册 + */ +int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) { + std::lock_guard lock(m_mutex); + + int id = m_nextCallbackId++; + m_changeCallbacks[id] = std::move(callback); + + E2D_LOG_DEBUG("Registered config change callback with id {}", id); + return id; +} + +/** + * @brief 取消注册配置变更回调 + * @param callbackId 回调ID + */ +void ConfigManager::unregisterChangeCallback(int callbackId) { + std::lock_guard lock(m_mutex); + + auto it = m_changeCallbacks.find(callbackId); + if (it != m_changeCallbacks.end()) { + m_changeCallbacks.erase(it); + E2D_LOG_DEBUG("Unregistered config change callback {}", callbackId); + } +} + +/** + * @brief 清除所有变更回调 + */ +void ConfigManager::clearChangeCallbacks() { + std::lock_guard lock(m_mutex); + m_changeCallbacks.clear(); + E2D_LOG_DEBUG("Cleared all config change callbacks"); +} + +/** + * @brief 注册模块配置 + * @param moduleName 模块名称 + * @param config 模块配置指针 + */ +void ConfigManager::registerModuleConfig(const std::string& moduleName, Ptr config) { + std::lock_guard lock(m_mutex); + + m_moduleConfigs[moduleName] = config; + E2D_LOG_DEBUG("Registered module config: {}", moduleName); +} + +/** + * @brief 移除模块配置 + * @param moduleName 模块名称 + */ +void ConfigManager::removeModuleConfig(const std::string& moduleName) { + std::lock_guard lock(m_mutex); + + auto it = m_moduleConfigs.find(moduleName); + if (it != m_moduleConfigs.end()) { + m_moduleConfigs.erase(it); + E2D_LOG_DEBUG("Removed module config: {}", moduleName); + } +} + +/** + * @brief 检查模块配置是否存在 + * @param moduleName 模块名称 + * @return 如果存在返回 true + */ +bool ConfigManager::hasModuleConfig(const std::string& moduleName) const { + std::lock_guard lock(m_mutex); + return m_moduleConfigs.find(moduleName) != m_moduleConfigs.end(); +} + +/** + * @brief 设置配置值(字符串) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ +void ConfigManager::setValue(const std::string& section, const std::string& key, const std::string& value) { + std::lock_guard lock(m_mutex); + + ConfigChangeEvent event; + event.section = section; + event.field = key; + event.newValue = value; + + m_modified = true; + notifyChangeCallbacks(event); +} + +/** + * @brief 设置配置值(整数) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ +void ConfigManager::setValue(const std::string& section, const std::string& key, int value) { + std::ostringstream oss; + oss << value; + setValue(section, key, oss.str()); +} + +/** + * @brief 设置配置值(浮点数) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ +void ConfigManager::setValue(const std::string& section, const std::string& key, float value) { + std::ostringstream oss; + oss << value; + setValue(section, key, oss.str()); +} + +/** + * @brief 设置配置值(布尔值) + * @param section 配置节 + * @param key 配置键 + * @param value 配置值 + */ +void ConfigManager::setValue(const std::string& section, const std::string& key, bool value) { + setValue(section, key, std::string(value ? "true" : "false")); +} + +/** + * @brief 获取配置值(字符串) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ +std::string ConfigManager::getValue(const std::string& section, const std::string& key, + const std::string& defaultValue) const { + return defaultValue; +} + +/** + * @brief 获取配置值(整数) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ +int ConfigManager::getIntValue(const std::string& section, const std::string& key, int defaultValue) const { + return defaultValue; +} + +/** + * @brief 获取配置值(浮点数) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ +float ConfigManager::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const { + return defaultValue; +} + +/** + * @brief 获取配置值(布尔值) + * @param section 配置节 + * @param key 配置键 + * @param defaultValue 默认值 + * @return 配置值 + */ +bool ConfigManager::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const { + return defaultValue; +} + +/** + * @brief 重置配置到默认值 + */ +void ConfigManager::resetToDefaults() { + std::lock_guard lock(m_mutex); + + m_appConfig = AppConfig::createDefault(); + + if (m_platformConfig) { + m_platformConfig->applyDefaults(m_appConfig); + } + + m_modified = true; + + E2D_LOG_INFO("Config reset to defaults"); +} + +/** + * @brief 检查配置是否有未保存的更改 + * @return 如果有未保存的更改返回 true + */ +bool ConfigManager::hasUnsavedChanges() const { + return m_modified; +} + +/** + * @brief 标记配置为已修改 + */ +void ConfigManager::markModified() { + m_modified = true; +} + +/** + * @brief 清除修改标记 + */ +void ConfigManager::clearModified() { + m_modified = false; +} + +/** + * @brief 设置自动保存 + * @param enabled 是否启用自动保存 + * @param interval 自动保存间隔(秒) + */ +void ConfigManager::setAutoSave(bool enabled, float interval) { + std::lock_guard lock(m_mutex); + + m_autoSaveEnabled = enabled; + m_autoSaveInterval = interval > 0.0f ? interval : 30.0f; + m_autoSaveTimer = 0.0f; + + E2D_LOG_INFO("Auto save {} (interval: {}s)", + enabled ? "enabled" : "disabled", m_autoSaveInterval); +} + +/** + * @brief 更新配置管理器(用于自动保存) + * @param deltaTime 帧时间(秒) + */ +void ConfigManager::update(float deltaTime) { + if (!m_autoSaveEnabled || !m_modified) { + return; + } + + m_autoSaveTimer += deltaTime; + + if (m_autoSaveTimer >= m_autoSaveInterval) { + m_autoSaveTimer = 0.0f; + saveConfig(); + } +} + +/** + * @brief 通知所有变更回调 + * @param event 配置变更事件 + */ +void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) { + for (const auto& pair : m_changeCallbacks) { + if (pair.second) { + pair.second(event); + } + } +} + +/** + * @brief 将配置应用到内部存储 + * @param config 配置对象 + */ +void ConfigManager::applyConfigToInternal(const AppConfig& config) { + m_appConfig = config; +} + +/** + * @brief 从内部存储提取配置 + * @param config 输出配置对象 + */ +void ConfigManager::extractConfigFromInternal(AppConfig& config) const { + config = m_appConfig; +} + +} diff --git a/Extra2D/src/config/module_registry.cpp b/Extra2D/src/config/module_registry.cpp new file mode 100644 index 0000000..acbc3a4 --- /dev/null +++ b/Extra2D/src/config/module_registry.cpp @@ -0,0 +1,212 @@ +#include +#include +#include + +namespace extra2d { + +/** + * @brief 获取单例实例 + * 使用静态局部变量实现线程安全的单例模式 + * @return 模块注册表实例引用 + */ +ModuleRegistry& ModuleRegistry::instance() { + static ModuleRegistry instance; + return instance; +} + +/** + * @brief 注册模块 + * 将模块配置和初始化器工厂添加到注册表 + * @param config 模块配置 + * @param initializerFactory 初始化器工厂函数(可选) + * @return 分配的模块标识符 + */ +ModuleId ModuleRegistry::registerModule( + UniquePtr config, + ModuleInitializerFactory initializerFactory +) { + if (!config) { + E2D_LOG_ERROR("Cannot register null module config"); + return INVALID_MODULE_ID; + } + + std::lock_guard lock(mutex_); + + ModuleInfo info = config->getModuleInfo(); + + if (nameToId_.find(info.name) != nameToId_.end()) { + E2D_LOG_ERROR("Module '{}' already registered", info.name); + 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; + + E2D_LOG_INFO("Registered module '{}' with id {}", info.name, id); + return id; +} + +/** + * @brief 注销模块 + * 从注册表中移除指定模块 + * @param id 模块标识符 + * @return 注销成功返回 true + */ +bool ModuleRegistry::unregisterModule(ModuleId id) { + std::lock_guard lock(mutex_); + + auto it = modules_.find(id); + if (it == modules_.end()) { + E2D_LOG_WARN("Module with id {} not found for unregistration", id); + return false; + } + + ModuleInfo info = it->second.config->getModuleInfo(); + nameToId_.erase(info.name); + modules_.erase(it); + + E2D_LOG_INFO("Unregistered module '{}' (id: {})", info.name, id); + return true; +} + +/** + * @brief 获取模块配置 + * @param id 模块标识符 + * @return 模块配置指针,不存在返回 nullptr + */ +IModuleConfig* ModuleRegistry::getModuleConfig(ModuleId id) const { + std::lock_guard 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 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 + */ +UniquePtr ModuleRegistry::createInitializer(ModuleId id) const { + std::lock_guard lock(mutex_); + + auto it = modules_.find(id); + if (it == modules_.end() || !it->second.initializerFactory) { + return nullptr; + } + + return it->second.initializerFactory(); +} + +/** + * @brief 获取所有已注册模块标识符 + * @return 模块标识符列表 + */ +std::vector ModuleRegistry::getAllModules() const { + std::lock_guard lock(mutex_); + + std::vector result; + result.reserve(modules_.size()); + + for (const auto& pair : modules_) { + result.push_back(pair.first); + } + + return result; +} + +/** + * @brief 获取模块初始化顺序 + * 根据优先级和依赖关系计算初始化顺序 + * @return 按初始化顺序排列的模块标识符列表 + */ +std::vector ModuleRegistry::getInitializationOrder() const { + std::lock_guard lock(mutex_); + + std::vector> 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(info.priority)); + } + } + + std::sort(modulePriorities.begin(), modulePriorities.end(), + [](const auto& a, const auto& b) { + return a.second < b.second; + }); + + std::vector 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 lock(mutex_); + return modules_.find(id) != modules_.end(); +} + +/** + * @brief 清空所有注册的模块 + */ +void ModuleRegistry::clear() { + std::lock_guard lock(mutex_); + + modules_.clear(); + nameToId_.clear(); + nextId_ = 1; + + E2D_LOG_INFO("Module registry cleared"); +} + +/** + * @brief 生成新的模块标识符 + * @return 新的模块标识符 + */ +ModuleId ModuleRegistry::generateId() { + return nextId_++; +} + +} diff --git a/Extra2D/src/config/platform_config.cpp b/Extra2D/src/config/platform_config.cpp new file mode 100644 index 0000000..c151709 --- /dev/null +++ b/Extra2D/src/config/platform_config.cpp @@ -0,0 +1,391 @@ +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef __SWITCH__ +#include +#endif + +namespace extra2d { + +namespace { + +class WindowsPlatformConfig : public PlatformConfig { +public: + WindowsPlatformConfig() { + caps_.supportsWindowed = true; + caps_.supportsFullscreen = true; + caps_.supportsBorderless = true; + caps_.supportsCursor = true; + caps_.supportsCursorHide = true; + caps_.supportsDPIAwareness = true; + caps_.supportsVSync = true; + caps_.supportsMultiMonitor = true; + caps_.supportsClipboard = true; + caps_.supportsGamepad = true; + caps_.supportsTouch = false; + caps_.supportsKeyboard = true; + caps_.supportsMouse = true; + caps_.supportsResize = true; + caps_.supportsHighDPI = true; + caps_.maxTextureSize = 16384; + caps_.preferredScreenWidth = 1920; + caps_.preferredScreenHeight = 1080; + caps_.defaultDPI = 96.0f; + } + + PlatformType platformType() const override { return PlatformType::Windows; } + const char* platformName() const override { return "Windows"; } + const PlatformCapabilities& capabilities() const override { return caps_; } + + void applyConstraints(AppConfig& config) const override { + if (config.window.width < 320) config.window.width = 320; + if (config.window.height < 240) config.window.height = 240; + if (config.window.width > caps_.maxTextureSize) config.window.width = caps_.maxTextureSize; + if (config.window.height > caps_.maxTextureSize) config.window.height = caps_.maxTextureSize; + } + + void applyDefaults(AppConfig& config) const override { + config.window.highDPI = true; + config.window.resizable = true; + config.render.vsync = true; + config.render.targetFPS = 60; + } + + bool validateConfig(AppConfig& config) const override { + if (config.window.width <= 0 || config.window.height <= 0) { + E2D_LOG_ERROR("Windows: Invalid window dimensions"); + return false; + } + return true; + } + + int getRecommendedWidth() const override { return 1920; } + int getRecommendedHeight() const override { return 1080; } + bool isResolutionSupported(int width, int height) const override { + return width >= 320 && height >= 240 && + width <= caps_.maxTextureSize && height <= caps_.maxTextureSize; + } + +private: + PlatformCapabilities caps_; +}; + +class LinuxPlatformConfig : public PlatformConfig { +public: + LinuxPlatformConfig() { + caps_.supportsWindowed = true; + caps_.supportsFullscreen = true; + caps_.supportsBorderless = true; + caps_.supportsCursor = true; + caps_.supportsCursorHide = true; + caps_.supportsDPIAwareness = true; + caps_.supportsVSync = true; + caps_.supportsMultiMonitor = true; + caps_.supportsClipboard = true; + caps_.supportsGamepad = true; + caps_.supportsTouch = false; + caps_.supportsKeyboard = true; + caps_.supportsMouse = true; + caps_.supportsResize = true; + caps_.supportsHighDPI = true; + caps_.maxTextureSize = 16384; + caps_.preferredScreenWidth = 1920; + caps_.preferredScreenHeight = 1080; + caps_.defaultDPI = 96.0f; + } + + PlatformType platformType() const override { return PlatformType::Linux; } + const char* platformName() const override { return "Linux"; } + const PlatformCapabilities& capabilities() const override { return caps_; } + + void applyConstraints(AppConfig& config) const override { + if (config.window.width < 320) config.window.width = 320; + if (config.window.height < 240) config.window.height = 240; + } + + void applyDefaults(AppConfig& config) const override { + config.window.resizable = true; + config.render.vsync = true; + } + + bool validateConfig(AppConfig& config) const override { + if (config.window.width <= 0 || config.window.height <= 0) { + E2D_LOG_ERROR("Linux: Invalid window dimensions"); + return false; + } + return true; + } + + int getRecommendedWidth() const override { return 1920; } + int getRecommendedHeight() const override { return 1080; } + bool isResolutionSupported(int width, int height) const override { + return width >= 320 && height >= 240; + } + +private: + PlatformCapabilities caps_; +}; + +class MacOSPlatformConfig : public PlatformConfig { +public: + MacOSPlatformConfig() { + caps_.supportsWindowed = true; + caps_.supportsFullscreen = true; + caps_.supportsBorderless = true; + caps_.supportsCursor = true; + caps_.supportsCursorHide = true; + caps_.supportsDPIAwareness = true; + caps_.supportsVSync = true; + caps_.supportsMultiMonitor = true; + caps_.supportsClipboard = true; + caps_.supportsGamepad = true; + caps_.supportsTouch = false; + caps_.supportsKeyboard = true; + caps_.supportsMouse = true; + caps_.supportsResize = true; + caps_.supportsHighDPI = true; + caps_.maxTextureSize = 16384; + caps_.preferredScreenWidth = 1920; + caps_.preferredScreenHeight = 1080; + caps_.defaultDPI = 144.0f; + } + + PlatformType platformType() const override { return PlatformType::macOS; } + const char* platformName() const override { return "macOS"; } + const PlatformCapabilities& capabilities() const override { return caps_; } + + void applyConstraints(AppConfig& config) const override { + if (config.window.width < 320) config.window.width = 320; + if (config.window.height < 240) config.window.height = 240; + } + + void applyDefaults(AppConfig& config) const override { + config.window.highDPI = true; + config.window.resizable = true; + config.render.vsync = true; + } + + bool validateConfig(AppConfig& config) const override { + if (config.window.width <= 0 || config.window.height <= 0) { + E2D_LOG_ERROR("macOS: Invalid window dimensions"); + return false; + } + return true; + } + + int getRecommendedWidth() const override { return 1920; } + int getRecommendedHeight() const override { return 1080; } + bool isResolutionSupported(int width, int height) const override { + return width >= 320 && height >= 240; + } + +private: + PlatformCapabilities caps_; +}; + +#ifdef __SWITCH__ +class SwitchPlatformConfig : public PlatformConfig { +public: + SwitchPlatformConfig() { + caps_.supportsWindowed = false; + caps_.supportsFullscreen = true; + caps_.supportsBorderless = false; + caps_.supportsCursor = false; + caps_.supportsCursorHide = false; + caps_.supportsDPIAwareness = false; + caps_.supportsVSync = true; + caps_.supportsMultiMonitor = false; + caps_.supportsClipboard = false; + caps_.supportsGamepad = true; + caps_.supportsTouch = true; + caps_.supportsKeyboard = false; + caps_.supportsMouse = false; + caps_.supportsResize = false; + caps_.supportsHighDPI = false; + caps_.maxTextureSize = 8192; + caps_.preferredScreenWidth = 1920; + caps_.preferredScreenHeight = 1080; + caps_.defaultDPI = 96.0f; + } + + PlatformType platformType() const override { return PlatformType::Switch; } + const char* platformName() const override { return "Nintendo Switch"; } + const PlatformCapabilities& capabilities() const override { return caps_; } + + void applyConstraints(AppConfig& config) const override { + config.window.width = 1920; + config.window.height = 1080; + config.window.mode = WindowMode::Fullscreen; + config.window.resizable = false; + config.window.borderless = false; + config.window.highDPI = false; + config.render.vsync = true; + config.render.targetFPS = 60; + config.input.enableVibration = true; + config.input.maxGamepads = 2; + } + + void applyDefaults(AppConfig& config) const override { + config.window.width = 1920; + config.window.height = 1080; + config.window.mode = WindowMode::Fullscreen; + config.window.resizable = false; + config.render.vsync = true; + config.render.targetFPS = 60; + config.input.enableVibration = true; + } + + bool validateConfig(AppConfig& config) const override { + if (config.window.mode != WindowMode::Fullscreen) { + E2D_LOG_WARN("Switch: Only fullscreen mode is supported"); + config.window.mode = WindowMode::Fullscreen; + } + return true; + } + + int getRecommendedWidth() const override { return 1920; } + int getRecommendedHeight() const override { return 1080; } + bool isResolutionSupported(int width, int height) const override { + return (width == 1920 && height == 1080) || + (width == 1280 && height == 720); + } + +private: + PlatformCapabilities caps_; +}; +#else +class SwitchPlatformConfig : public PlatformConfig { +public: + SwitchPlatformConfig() { + caps_.supportsWindowed = false; + caps_.supportsFullscreen = true; + caps_.supportsBorderless = false; + caps_.supportsCursor = false; + caps_.supportsCursorHide = false; + caps_.supportsDPIAwareness = false; + caps_.supportsVSync = true; + caps_.supportsMultiMonitor = false; + caps_.supportsClipboard = false; + caps_.supportsGamepad = true; + caps_.supportsTouch = true; + caps_.supportsKeyboard = false; + caps_.supportsMouse = false; + caps_.supportsResize = false; + caps_.supportsHighDPI = false; + caps_.maxTextureSize = 8192; + caps_.preferredScreenWidth = 1920; + caps_.preferredScreenHeight = 1080; + caps_.defaultDPI = 96.0f; + } + + PlatformType platformType() const override { return PlatformType::Switch; } + const char* platformName() const override { return "Nintendo Switch"; } + const PlatformCapabilities& capabilities() const override { return caps_; } + + void applyConstraints(AppConfig& config) const override { + config.window.width = 1920; + config.window.height = 1080; + config.window.mode = WindowMode::Fullscreen; + config.window.resizable = false; + config.window.borderless = false; + config.window.highDPI = false; + config.render.vsync = true; + config.render.targetFPS = 60; + config.input.enableVibration = true; + config.input.maxGamepads = 2; + } + + void applyDefaults(AppConfig& config) const override { + config.window.width = 1920; + config.window.height = 1080; + config.window.mode = WindowMode::Fullscreen; + config.window.resizable = false; + config.render.vsync = true; + config.render.targetFPS = 60; + config.input.enableVibration = true; + } + + bool validateConfig(AppConfig& config) const override { + if (config.window.mode != WindowMode::Fullscreen) { + E2D_LOG_WARN("Switch: Only fullscreen mode is supported"); + } + return true; + } + + int getRecommendedWidth() const override { return 1920; } + int getRecommendedHeight() const override { return 1080; } + bool isResolutionSupported(int width, int height) const override { + return (width == 1920 && height == 1080) || + (width == 1280 && height == 720); + } + +private: + PlatformCapabilities caps_; +}; +#endif + +} + +/** + * @brief 创建平台配置实例 + * 根据 PlatformType 创建对应的平台配置对象 + * @param type 平台类型,默认为 Auto(自动检测) + * @return 平台配置的智能指针 + */ +UniquePtr createPlatformConfig(PlatformType type) { + if (type == PlatformType::Auto) { +#ifdef _WIN32 + type = PlatformType::Windows; +#elif defined(__SWITCH__) + type = PlatformType::Switch; +#elif defined(__linux__) + type = PlatformType::Linux; +#elif defined(__APPLE__) + type = PlatformType::macOS; +#else + type = PlatformType::Windows; +#endif + } + + switch (type) { + case PlatformType::Windows: + E2D_LOG_INFO("Creating Windows platform config"); + return makeUnique(); + case PlatformType::Switch: + E2D_LOG_INFO("Creating Nintendo Switch platform config"); + return makeUnique(); + case PlatformType::Linux: + E2D_LOG_INFO("Creating Linux platform config"); + return makeUnique(); + case PlatformType::macOS: + E2D_LOG_INFO("Creating macOS platform config"); + return makeUnique(); + default: + E2D_LOG_WARN("Unknown platform type, defaulting to Windows"); + return makeUnique(); + } +} + +/** + * @brief 获取平台类型名称 + * 将 PlatformType 枚举转换为可读的字符串 + * @param type 平台类型枚举值 + * @return 平台名称字符串 + */ +const char* getPlatformTypeName(PlatformType type) { + switch (type) { + case PlatformType::Auto: return "Auto"; + case PlatformType::Windows: return "Windows"; + case PlatformType::Switch: return "Switch"; + case PlatformType::Linux: return "Linux"; + case PlatformType::macOS: return "macOS"; + default: return "Unknown"; + } +} + +} diff --git a/Extra2D/src/config/platform_detector.cpp b/Extra2D/src/config/platform_detector.cpp new file mode 100644 index 0000000..a39fecf --- /dev/null +++ b/Extra2D/src/config/platform_detector.cpp @@ -0,0 +1,625 @@ +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#elif defined(__linux__) +#include +#include +#include +#include +#elif defined(__APPLE__) +#include +#include +#include +#include +#endif + +#ifdef __SWITCH__ +#include +#endif + +namespace extra2d { + +/** + * @brief 检测当前运行平台 + * 使用编译时宏判断当前平台类型 + * @return 当前平台的类型枚举值 + */ +PlatformType PlatformDetector::detect() { +#ifdef _WIN32 + return PlatformType::Windows; +#elif defined(__SWITCH__) + return PlatformType::Switch; +#elif defined(__linux__) + return PlatformType::Linux; +#elif defined(__APPLE__) + return PlatformType::macOS; +#else + return PlatformType::Windows; +#endif +} + +/** + * @brief 获取当前平台名称字符串 + * @return 平台名称(如 "Windows", "Linux", "macOS", "Switch") + */ +const char* PlatformDetector::platformName() { + return platformName(detect()); +} + +/** + * @brief 获取指定平台类型的名称 + * @param type 平台类型 + * @return 平台名称字符串 + */ +const char* PlatformDetector::platformName(PlatformType type) { + switch (type) { + case PlatformType::Windows: return "Windows"; + case PlatformType::Switch: return "Nintendo Switch"; + case PlatformType::Linux: return "Linux"; + case PlatformType::macOS: return "macOS"; + case PlatformType::Auto: return "Auto"; + default: return "Unknown"; + } +} + +/** + * @brief 检查当前平台是否为桌面平台 + * @return 如果是桌面平台返回 true + */ +bool PlatformDetector::isDesktopPlatform() { + PlatformType type = detect(); + return type == PlatformType::Windows || + type == PlatformType::Linux || + type == PlatformType::macOS; +} + +/** + * @brief 检查当前平台是否为游戏主机平台 + * @return 如果是游戏主机平台返回 true + */ +bool PlatformDetector::isConsolePlatform() { + return detect() == PlatformType::Switch; +} + +/** + * @brief 检查当前平台是否为移动平台 + * @return 如果是移动平台返回 true + */ +bool PlatformDetector::isMobilePlatform() { + return false; +} + +/** + * @brief 获取当前平台的能力 + * @return 平台能力结构 + */ +PlatformCapabilities PlatformDetector::capabilities() { + return capabilities(detect()); +} + +/** + * @brief 获取指定平台的能力 + * @param type 平台类型 + * @return 平台能力结构 + */ +PlatformCapabilities PlatformDetector::capabilities(PlatformType type) { + switch (type) { + case PlatformType::Windows: + return getWindowsCapabilities(); + case PlatformType::Switch: + return getSwitchCapabilities(); + case PlatformType::Linux: + return getLinuxCapabilities(); + case PlatformType::macOS: + return getMacOSCapabilities(); + default: + return getWindowsCapabilities(); + } +} + +/** + * @brief 获取当前平台的默认配置 + * @return 平台默认的应用配置 + */ +AppConfig PlatformDetector::platformDefaults() { + return platformDefaults(detect()); +} + +/** + * @brief 获取指定平台的默认配置 + * @param type 平台类型 + * @return 平台默认的应用配置 + */ +AppConfig PlatformDetector::platformDefaults(PlatformType type) { + switch (type) { + case PlatformType::Windows: + return getWindowsDefaults(); + case PlatformType::Switch: + return getSwitchDefaults(); + case PlatformType::Linux: + return getLinuxDefaults(); + case PlatformType::macOS: + return getMacOSDefaults(); + default: + return AppConfig::createDefault(); + } +} + +/** + * @brief 获取当前平台的推荐分辨率 + * @param width 输出宽度 + * @param height 输出高度 + */ +void PlatformDetector::getRecommendedResolution(int& width, int& height) { + PlatformCapabilities caps = capabilities(); + width = caps.preferredScreenWidth; + height = caps.preferredScreenHeight; +} + +/** + * @brief 获取当前平台的默认 DPI + * @return 默认 DPI 值 + */ +float PlatformDetector::getDefaultDPI() { + return capabilities().defaultDPI; +} + +/** + * @brief 检查当前平台是否支持指定功能 + * @param feature 功能名称 + * @return 如果支持返回 true + */ +bool PlatformDetector::supportsFeature(const std::string& feature) { + PlatformCapabilities caps = capabilities(); + + if (feature == "windowed") return caps.supportsWindowed; + if (feature == "fullscreen") return caps.supportsFullscreen; + if (feature == "borderless") return caps.supportsBorderless; + if (feature == "cursor") return caps.supportsCursor; + if (feature == "cursor_hide") return caps.supportsCursorHide; + if (feature == "dpi_awareness") return caps.supportsDPIAwareness; + if (feature == "vsync") return caps.supportsVSync; + if (feature == "multi_monitor") return caps.supportsMultiMonitor; + if (feature == "clipboard") return caps.supportsClipboard; + if (feature == "gamepad") return caps.supportsGamepad; + if (feature == "touch") return caps.supportsTouch; + if (feature == "keyboard") return caps.supportsKeyboard; + if (feature == "mouse") return caps.supportsMouse; + if (feature == "resize") return caps.supportsResize; + if (feature == "high_dpi") return caps.supportsHighDPI; + + return false; +} + +/** + * @brief 获取系统内存大小 + * @return 系统内存大小(MB),如果无法获取返回 0 + */ +int PlatformDetector::getSystemMemoryMB() { +#ifdef _WIN32 + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + if (GlobalMemoryStatusEx(&status)) { + return static_cast(status.ullTotalPhys / (1024 * 1024)); + } + return 0; +#elif defined(__SWITCH__) + return 4096; +#elif defined(__linux__) + struct sysinfo info; + if (sysinfo(&info) == 0) { + return static_cast(info.totalram * info.mem_unit / (1024 * 1024)); + } + return 0; +#elif defined(__APPLE__) + int mib[2] = {CTL_HW, HW_MEMSIZE}; + int64_t memSize = 0; + size_t length = sizeof(memSize); + if (sysctl(mib, 2, &memSize, &length, nullptr, 0) == 0) { + return static_cast(memSize / (1024 * 1024)); + } + return 0; +#else + return 0; +#endif +} + +/** + * @brief 获取 CPU 核心数 + * @return CPU 核心数 + */ +int PlatformDetector::getCPUCoreCount() { +#ifdef _WIN32 + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return static_cast(sysinfo.dwNumberOfProcessors); +#elif defined(__SWITCH__) + return 4; +#elif defined(__linux__) || defined(__APPLE__) + long cores = sysconf(_SC_NPROCESSORS_ONLN); + return static_cast(cores > 0 ? cores : 1); +#else + return 1; +#endif +} + +/** + * @brief 检查是否支持多线程渲染 + * @return 如果支持返回 true + */ +bool PlatformDetector::supportsMultithreadedRendering() { +#ifdef __SWITCH__ + return false; +#else + return getCPUCoreCount() >= 2; +#endif +} + +/** + * @brief 获取平台特定的配置路径 + * @param appName 应用名称 + * @return 配置文件目录路径 + */ +std::string PlatformDetector::getConfigPath(const std::string& appName) { +#ifdef _WIN32 + char path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) { + return std::string(path) + "\\" + appName + "\\config"; + } + return ".\\config"; +#elif defined(__SWITCH__) + return "sdmc:/config/" + appName; +#elif defined(__linux__) + const char* configHome = getenv("XDG_CONFIG_HOME"); + if (configHome && configHome[0] != '\0') { + return std::string(configHome) + "/" + appName; + } + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/.config/" + appName; + } + return "./config"; +#elif defined(__APPLE__) + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/Library/Application Support/" + appName + "/config"; + } + return "./config"; +#else + return "./config"; +#endif +} + +/** + * @brief 获取平台特定的存档路径 + * @param appName 应用名称 + * @return 存档文件目录路径 + */ +std::string PlatformDetector::getSavePath(const std::string& appName) { +#ifdef _WIN32 + char path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) { + return std::string(path) + "\\" + appName + "\\saves"; + } + return ".\\saves"; +#elif defined(__SWITCH__) + return "sdmc:/saves/" + appName; +#elif defined(__linux__) + const char* dataHome = getenv("XDG_DATA_HOME"); + if (dataHome && dataHome[0] != '\0') { + return std::string(dataHome) + "/" + appName + "/saves"; + } + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/.local/share/" + appName + "/saves"; + } + return "./saves"; +#elif defined(__APPLE__) + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/Library/Application Support/" + appName + "/saves"; + } + return "./saves"; +#else + return "./saves"; +#endif +} + +/** + * @brief 获取平台特定的缓存路径 + * @param appName 应用名称 + * @return 缓存文件目录路径 + */ +std::string PlatformDetector::getCachePath(const std::string& appName) { +#ifdef _WIN32 + char path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) { + return std::string(path) + "\\" + appName + "\\cache"; + } + return ".\\cache"; +#elif defined(__SWITCH__) + return "sdmc:/cache/" + appName; +#elif defined(__linux__) + const char* cacheHome = getenv("XDG_CACHE_HOME"); + if (cacheHome && cacheHome[0] != '\0') { + return std::string(cacheHome) + "/" + appName; + } + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/.cache/" + appName; + } + return "./cache"; +#elif defined(__APPLE__) + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/Library/Caches/" + appName; + } + return "./cache"; +#else + return "./cache"; +#endif +} + +/** + * @brief 获取平台特定的日志路径 + * @param appName 应用名称 + * @return 日志文件目录路径 + */ +std::string PlatformDetector::getLogPath(const std::string& appName) { +#ifdef _WIN32 + char path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) { + return std::string(path) + "\\" + appName + "\\logs"; + } + return ".\\logs"; +#elif defined(__SWITCH__) + return "sdmc:/logs/" + appName; +#elif defined(__linux__) + const char* cacheHome = getenv("XDG_CACHE_HOME"); + if (cacheHome && cacheHome[0] != '\0') { + return std::string(cacheHome) + "/" + appName + "/logs"; + } + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/.cache/" + appName + "/logs"; + } + return "./logs"; +#elif defined(__APPLE__) + const char* home = getenv("HOME"); + if (!home) { + struct passwd* pwd = getpwuid(getuid()); + if (pwd) home = pwd->pw_dir; + } + if (home) { + return std::string(home) + "/Library/Logs/" + appName; + } + return "./logs"; +#else + return "./logs"; +#endif +} + +/** + * @brief 检查平台是否为小端字节序 + * @return 如果是小端字节序返回 true + */ +bool PlatformDetector::isLittleEndian() { + union { + uint32_t i; + char c[4]; + } test = {0x01020304}; + return test.c[0] == 0x04; +} + +/** + * @brief 检查平台是否为大端字节序 + * @return 如果是大端字节序返回 true + */ +bool PlatformDetector::isBigEndian() { + return !isLittleEndian(); +} + +/** + * @brief 获取平台信息摘要 + * @return 平台信息字符串 + */ +std::string PlatformDetector::getPlatformSummary() { + std::string summary; + summary += "Platform: "; + summary += platformName(); + summary += "\n"; + summary += "Memory: "; + summary += std::to_string(getSystemMemoryMB()); + summary += " MB\n"; + summary += "CPU Cores: "; + summary += std::to_string(getCPUCoreCount()); + summary += "\n"; + summary += "Endianness: "; + summary += isLittleEndian() ? "Little Endian" : "Big Endian"; + summary += "\n"; + summary += "Desktop Platform: "; + summary += isDesktopPlatform() ? "Yes" : "No"; + summary += "\n"; + summary += "Console Platform: "; + summary += isConsolePlatform() ? "Yes" : "No"; + summary += "\n"; + summary += "Recommended Resolution: "; + int width, height; + getRecommendedResolution(width, height); + summary += std::to_string(width); + summary += "x"; + summary += std::to_string(height); + summary += "\n"; + summary += "Default DPI: "; + summary += std::to_string(static_cast(getDefaultDPI())); + return summary; +} + +PlatformCapabilities PlatformDetector::getWindowsCapabilities() { + PlatformCapabilities caps; + caps.supportsWindowed = true; + caps.supportsFullscreen = true; + caps.supportsBorderless = true; + caps.supportsCursor = true; + caps.supportsCursorHide = true; + caps.supportsDPIAwareness = true; + caps.supportsVSync = true; + caps.supportsMultiMonitor = true; + caps.supportsClipboard = true; + caps.supportsGamepad = true; + caps.supportsTouch = false; + caps.supportsKeyboard = true; + caps.supportsMouse = true; + caps.supportsResize = true; + caps.supportsHighDPI = true; + caps.maxTextureSize = 16384; + caps.preferredScreenWidth = 1920; + caps.preferredScreenHeight = 1080; + caps.defaultDPI = 96.0f; + return caps; +} + +PlatformCapabilities PlatformDetector::getLinuxCapabilities() { + PlatformCapabilities caps; + caps.supportsWindowed = true; + caps.supportsFullscreen = true; + caps.supportsBorderless = true; + caps.supportsCursor = true; + caps.supportsCursorHide = true; + caps.supportsDPIAwareness = true; + caps.supportsVSync = true; + caps.supportsMultiMonitor = true; + caps.supportsClipboard = true; + caps.supportsGamepad = true; + caps.supportsTouch = false; + caps.supportsKeyboard = true; + caps.supportsMouse = true; + caps.supportsResize = true; + caps.supportsHighDPI = true; + caps.maxTextureSize = 16384; + caps.preferredScreenWidth = 1920; + caps.preferredScreenHeight = 1080; + caps.defaultDPI = 96.0f; + return caps; +} + +PlatformCapabilities PlatformDetector::getMacOSCapabilities() { + PlatformCapabilities caps; + caps.supportsWindowed = true; + caps.supportsFullscreen = true; + caps.supportsBorderless = true; + caps.supportsCursor = true; + caps.supportsCursorHide = true; + caps.supportsDPIAwareness = true; + caps.supportsVSync = true; + caps.supportsMultiMonitor = true; + caps.supportsClipboard = true; + caps.supportsGamepad = true; + caps.supportsTouch = false; + caps.supportsKeyboard = true; + caps.supportsMouse = true; + caps.supportsResize = true; + caps.supportsHighDPI = true; + caps.maxTextureSize = 16384; + caps.preferredScreenWidth = 1920; + caps.preferredScreenHeight = 1080; + caps.defaultDPI = 144.0f; + return caps; +} + +PlatformCapabilities PlatformDetector::getSwitchCapabilities() { + PlatformCapabilities caps; + caps.supportsWindowed = false; + caps.supportsFullscreen = true; + caps.supportsBorderless = false; + caps.supportsCursor = false; + caps.supportsCursorHide = false; + caps.supportsDPIAwareness = false; + caps.supportsVSync = true; + caps.supportsMultiMonitor = false; + caps.supportsClipboard = false; + caps.supportsGamepad = true; + caps.supportsTouch = true; + caps.supportsKeyboard = false; + caps.supportsMouse = false; + caps.supportsResize = false; + caps.supportsHighDPI = false; + caps.maxTextureSize = 8192; + caps.preferredScreenWidth = 1920; + caps.preferredScreenHeight = 1080; + caps.defaultDPI = 96.0f; + return caps; +} + +AppConfig PlatformDetector::getWindowsDefaults() { + AppConfig config = AppConfig::createDefault(); + config.window.highDPI = true; + config.window.resizable = true; + config.render.vsync = true; + config.render.targetFPS = 60; + return config; +} + +AppConfig PlatformDetector::getLinuxDefaults() { + AppConfig config = AppConfig::createDefault(); + config.window.resizable = true; + config.render.vsync = true; + return config; +} + +AppConfig PlatformDetector::getMacOSDefaults() { + AppConfig config = AppConfig::createDefault(); + config.window.highDPI = true; + config.window.resizable = true; + config.render.vsync = true; + return config; +} + +AppConfig PlatformDetector::getSwitchDefaults() { + AppConfig config = AppConfig::createDefault(); + config.window.width = 1920; + config.window.height = 1080; + config.window.mode = WindowMode::Fullscreen; + config.window.resizable = false; + config.window.highDPI = false; + config.render.vsync = true; + config.render.targetFPS = 60; + config.input.enableVibration = true; + config.input.maxGamepads = 2; + return config; +} + +} diff --git a/Extra2D/src/graphics/render_module.cpp b/Extra2D/src/graphics/render_module.cpp new file mode 100644 index 0000000..7ac0195 --- /dev/null +++ b/Extra2D/src/graphics/render_module.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace extra2d { + +// ============================================================================ +// RenderModuleConfig 实现 +// ============================================================================ + +/** + * @brief 验证渲染配置有效性 + * + * 检查渲染配置的各项参数是否在有效范围内: + * - 目标帧率应在 1-240 之间 + * - 多重采样数应为 0、2、4、8 或 16 + * - 精灵批处理大小应大于 0 + * + * @return 如果配置有效返回 true + */ +bool RenderModuleConfig::validate() const { + if (targetFPS < 1 || targetFPS > 240) { + E2D_LOG_ERROR("Invalid target FPS: {}, must be between 1 and 240", targetFPS); + return false; + } + + if (multisamples != 0 && multisamples != 2 && multisamples != 4 && + multisamples != 8 && multisamples != 16) { + E2D_LOG_ERROR("Invalid multisample count: {}, must be 0, 2, 4, 8 or 16", multisamples); + return false; + } + + if (spriteBatchSize <= 0) { + E2D_LOG_ERROR("Invalid sprite batch size: {}, must be greater than 0", spriteBatchSize); + return false; + } + + return true; +} + +/** + * @brief 应用平台约束 + * + * 根据不同平台的特性调整渲染配置: + * - Switch 平台限制 MSAA 最大为 4,禁用 sRGB 帧缓冲 + * - 其他平台保持用户配置 + * + * @param platform 目标平台类型 + */ +void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) { + switch (platform) { + case PlatformType::Switch: + if (multisamples > 4) { + E2D_LOG_WARN("Switch platform limits MSAA to 4x, reducing from {}", multisamples); + multisamples = 4; + } + if (sRGBFramebuffer) { + E2D_LOG_WARN("Switch platform does not support sRGB framebuffer, disabling"); + sRGBFramebuffer = false; + } + if (targetFPS > 60) { + E2D_LOG_WARN("Switch platform target FPS capped at 60"); + targetFPS = 60; + } + break; + + case PlatformType::Windows: + case PlatformType::Linux: + case PlatformType::macOS: + default: + break; + } +} + +/** + * @brief 重置为默认配置 + * + * 将所有配置项恢复为默认值: + * - 后端类型:OpenGL + * - 垂直同步:启用 + * - 目标帧率:60 + * - 多重采样:禁用 + * - sRGB 帧缓冲:禁用 + * - 精灵批处理大小:1000 + */ +void RenderModuleConfig::resetToDefaults() { + backend = BackendType::OpenGL; + vsync = true; + targetFPS = 60; + multisamples = 0; + sRGBFramebuffer = false; + spriteBatchSize = 1000; +} + +/** + * @brief 从 JSON 数据加载配置 + * + * 从 JSON 对象中解析渲染配置参数 + * + * @param jsonData JSON 数据指针(nlohmann::json 对象指针) + * @return 加载成功返回 true + */ +bool RenderModuleConfig::loadFromJson(const void* jsonData) { + if (!jsonData) { + E2D_LOG_ERROR("Null JSON data provided"); + return false; + } + + try { + const json& j = *static_cast(jsonData); + + if (j.contains("backend")) { + std::string backendStr = j["backend"].get(); + if (backendStr == "opengl") { + backend = BackendType::OpenGL; + } else { + E2D_LOG_WARN("Unknown backend type: {}, defaulting to OpenGL", backendStr); + backend = BackendType::OpenGL; + } + } + + if (j.contains("vsync")) { + vsync = j["vsync"].get(); + } + + if (j.contains("targetFPS")) { + targetFPS = j["targetFPS"].get(); + } + + if (j.contains("multisamples")) { + multisamples = j["multisamples"].get(); + } + + if (j.contains("sRGBFramebuffer")) { + sRGBFramebuffer = j["sRGBFramebuffer"].get(); + } + + if (j.contains("spriteBatchSize")) { + spriteBatchSize = j["spriteBatchSize"].get(); + } + + E2D_LOG_INFO("Render config loaded from JSON"); + return true; + } catch (const json::exception& e) { + E2D_LOG_ERROR("Failed to parse render config from JSON: {}", e.what()); + return false; + } +} + +/** + * @brief 保存配置到 JSON 数据 + * + * 将当前配置序列化到 JSON 对象 + * + * @param jsonData JSON 数据指针(nlohmann::json 对象指针) + * @return 保存成功返回 true + */ +bool RenderModuleConfig::saveToJson(void* jsonData) const { + if (!jsonData) { + E2D_LOG_ERROR("Null JSON data provided"); + return false; + } + + try { + json& j = *static_cast(jsonData); + + std::string backendStr = "opengl"; + switch (backend) { + case BackendType::OpenGL: + backendStr = "opengl"; + break; + default: + backendStr = "opengl"; + break; + } + + j["backend"] = backendStr; + j["vsync"] = vsync; + j["targetFPS"] = targetFPS; + j["multisamples"] = multisamples; + j["sRGBFramebuffer"] = sRGBFramebuffer; + j["spriteBatchSize"] = spriteBatchSize; + + E2D_LOG_INFO("Render config saved to JSON"); + return true; + } catch (const json::exception& e) { + E2D_LOG_ERROR("Failed to save render config to JSON: {}", e.what()); + return false; + } +} + +// ============================================================================ +// RenderModuleInitializer 实现 +// ============================================================================ + +/** + * @brief 构造函数 + * + * 初始化渲染模块初始化器的成员变量 + */ +RenderModuleInitializer::RenderModuleInitializer() + : moduleId_(INVALID_MODULE_ID) + , windowModuleId_(INVALID_MODULE_ID) + , renderer_(nullptr) + , initialized_(false) { +} + +/** + * @brief 析构函数 + * + * 确保在销毁时关闭模块 + */ +RenderModuleInitializer::~RenderModuleInitializer() { + if (initialized_) { + shutdown(); + } +} + +/** + * @brief 获取模块依赖列表 + * + * 返回渲染模块依赖的窗口模块标识符列表 + * + * @return 依赖模块标识符列表 + */ +std::vector RenderModuleInitializer::getDependencies() const { + if (windowModuleId_ != INVALID_MODULE_ID) { + return {windowModuleId_}; + } + return {}; +} + +/** + * @brief 初始化模块 + * + * 根据配置创建渲染后端实例并初始化渲染器 + * + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ +bool RenderModuleInitializer::initialize(const IModuleConfig* config) { + if (initialized_) { + E2D_LOG_WARN("Render module already initialized"); + return true; + } + + if (!config) { + E2D_LOG_ERROR("Null config provided for render module initialization"); + return false; + } + + const RenderModuleConfig* renderConfig = dynamic_cast(config); + if (!renderConfig) { + E2D_LOG_ERROR("Invalid config type for render module"); + return false; + } + + if (!renderConfig->validate()) { + E2D_LOG_ERROR("Invalid render module configuration"); + return false; + } + + renderer_ = RenderBackend::create(renderConfig->backend); + if (!renderer_) { + E2D_LOG_ERROR("Failed to create render backend"); + return false; + } + + IWindow* window = nullptr; + if (windowModuleId_ != INVALID_MODULE_ID) { + ModuleRegistry& registry = ModuleRegistry::instance(); + IModuleConfig* windowConfig = registry.getModuleConfig(windowModuleId_); + if (windowConfig) { + E2D_LOG_INFO("Render module found window module dependency"); + } + } + + E2D_LOG_INFO("Render module initialized successfully"); + E2D_LOG_INFO(" Backend: {}", renderConfig->backend == BackendType::OpenGL ? "OpenGL" : "Unknown"); + E2D_LOG_INFO(" VSync: {}", renderConfig->vsync ? "enabled" : "disabled"); + E2D_LOG_INFO(" Target FPS: {}", renderConfig->targetFPS); + E2D_LOG_INFO(" Multisamples: {}", renderConfig->multisamples); + E2D_LOG_INFO(" Sprite Batch Size: {}", renderConfig->spriteBatchSize); + + initialized_ = true; + return true; +} + +/** + * @brief 关闭模块 + * + * 销毁渲染后端实例并清理资源 + */ +void RenderModuleInitializer::shutdown() { + if (!initialized_) { + return; + } + + if (renderer_) { + renderer_->shutdown(); + renderer_.reset(); + } + + initialized_ = false; + E2D_LOG_INFO("Render module shutdown complete"); +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/render_target.cpp b/Extra2D/src/graphics/render_target.cpp index 4454d79..f1f442a 100644 --- a/Extra2D/src/graphics/render_target.cpp +++ b/Extra2D/src/graphics/render_target.cpp @@ -311,39 +311,6 @@ void RenderTarget::copyTo(RenderTarget &target) { glBindFramebuffer(GL_FRAMEBUFFER, 0); } -/** - * @brief 将内容传输到另一个渲染目标 - * @param target 目标渲染目标 - * @param color 是否复制颜色缓冲 - * @param depth 是否复制深度缓冲 - * - * 使用glBlitFramebuffer进行选择性复制 - */ -void RenderTarget::blitTo(RenderTarget &target, bool color, bool depth) { - if (!isValid() || !target.isValid()) { - return; - } - - // 使用glBlitFramebuffer复制 - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFBO()); - - GLbitfield mask = 0; - if (color) { - mask |= GL_COLOR_BUFFER_BIT; - } - if (depth && hasDepth_ && target.hasDepth_) { - mask |= GL_DEPTH_BUFFER_BIT; - } - - if (mask != 0) { - glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.getWidth(), - target.getHeight(), mask, GL_LINEAR); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - /** * @brief 复制到屏幕 * @param screenWidth 屏幕宽度 diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp index 6c382fc..168817c 100644 --- a/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp +++ b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp @@ -14,14 +14,16 @@ SDL2Window::~SDL2Window() { destroy(); } -bool SDL2Window::create(const WindowConfig& cfg) { +bool SDL2Window::create(const WindowConfigData& cfg) { if (!initSDL()) { return false; } Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; - if (cfg.fullscreen) { - flags |= cfg.fullscreenDesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; + if (cfg.isFullscreen()) { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } else if (cfg.isBorderless()) { + flags |= SDL_WINDOW_BORDERLESS; } if (cfg.resizable) { flags |= SDL_WINDOW_RESIZABLE; @@ -37,14 +39,14 @@ bool SDL2Window::create(const WindowConfig& cfg) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - if (cfg.msaaSamples > 0) { + if (cfg.multisamples > 0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.msaaSamples); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples); } int x = SDL_WINDOWPOS_CENTERED; int y = SDL_WINDOWPOS_CENTERED; - if (!cfg.centerWindow) { + if (!cfg.centered) { x = SDL_WINDOWPOS_UNDEFINED; y = SDL_WINDOWPOS_UNDEFINED; } @@ -74,7 +76,7 @@ bool SDL2Window::create(const WindowConfig& cfg) { SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0); SDL_GetWindowSize(sdlWindow_, &width_, &height_); - fullscreen_ = cfg.fullscreen; + fullscreen_ = cfg.isFullscreen(); vsync_ = cfg.vsync; initCursors(); diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_window.h b/Extra2D/src/platform/backends/sdl2/sdl2_window.h index 3bc6853..6bbd18c 100644 --- a/Extra2D/src/platform/backends/sdl2/sdl2_window.h +++ b/Extra2D/src/platform/backends/sdl2/sdl2_window.h @@ -15,7 +15,7 @@ public: SDL2Window(); ~SDL2Window() override; - bool create(const WindowConfig& cfg) override; + bool create(const WindowConfigData& cfg) override; void destroy() override; void poll() override; diff --git a/Extra2D/src/platform/input_module.cpp b/Extra2D/src/platform/input_module.cpp new file mode 100644 index 0000000..c1c19dd --- /dev/null +++ b/Extra2D/src/platform/input_module.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include + +#include + +using json = nlohmann::json; + +namespace extra2d { + +// ============================================================================ +// InputModuleConfig 实现 +// ============================================================================ + +/** + * @brief 验证配置有效性 + * 检查各项配置参数是否在有效范围内 + * @return 如果配置有效返回 true + */ +bool InputModuleConfig::validate() const { + if (deadzone < 0.0f || deadzone > 1.0f) { + E2D_LOG_ERROR("InputModuleConfig: deadzone 必须在 [0.0, 1.0] 范围内,当前值: {}", deadzone); + return false; + } + + if (mouseSensitivity < 0.0f || mouseSensitivity > 10.0f) { + E2D_LOG_ERROR("InputModuleConfig: mouseSensitivity 必须在 [0.0, 10.0] 范围内,当前值: {}", mouseSensitivity); + return false; + } + + if (!enableKeyboard && !enableMouse && !enableGamepad && !enableTouch) { + E2D_LOG_WARN("InputModuleConfig: 所有输入设备都已禁用"); + } + + return true; +} + +/** + * @brief 应用平台约束 + * 根据平台特性调整配置 + * @param platform 目标平台类型 + */ +void InputModuleConfig::applyPlatformConstraints(PlatformType platform) { + switch (platform) { + case PlatformType::Switch: + enableMouse = false; + enableTouch = true; + enableGamepad = true; + E2D_LOG_INFO("InputModuleConfig: Switch 平台 - 禁用鼠标输入"); + break; + + case PlatformType::Windows: + case PlatformType::Linux: + case PlatformType::macOS: + enableMouse = true; + enableKeyboard = true; + enableTouch = false; + E2D_LOG_INFO("InputModuleConfig: PC 平台 - 禁用触摸输入"); + break; + + case PlatformType::Auto: + default: + break; + } +} + +/** + * @brief 重置为默认配置 + */ +void InputModuleConfig::resetToDefaults() { + enableKeyboard = true; + enableMouse = true; + enableGamepad = true; + enableTouch = true; + deadzone = 0.15f; + mouseSensitivity = 1.0f; + + E2D_LOG_INFO("InputModuleConfig: 已重置为默认配置"); +} + +/** + * @brief 从 JSON 数据加载配置 + * @param jsonData JSON 数据指针 + * @return 加载成功返回 true + */ +bool InputModuleConfig::loadFromJson(const void* jsonData) { + if (!jsonData) { + E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空"); + return false; + } + + try { + const json& obj = *static_cast(jsonData); + + if (!obj.is_object()) { + E2D_LOG_ERROR("InputModuleConfig: JSON 数据不是对象类型"); + return false; + } + + if (obj.contains("enableKeyboard") && obj["enableKeyboard"].is_boolean()) { + enableKeyboard = obj["enableKeyboard"].get(); + } + + if (obj.contains("enableMouse") && obj["enableMouse"].is_boolean()) { + enableMouse = obj["enableMouse"].get(); + } + + if (obj.contains("enableGamepad") && obj["enableGamepad"].is_boolean()) { + enableGamepad = obj["enableGamepad"].get(); + } + + if (obj.contains("enableTouch") && obj["enableTouch"].is_boolean()) { + enableTouch = obj["enableTouch"].get(); + } + + if (obj.contains("deadzone") && obj["deadzone"].is_number()) { + deadzone = obj["deadzone"].get(); + } + + if (obj.contains("mouseSensitivity") && obj["mouseSensitivity"].is_number()) { + mouseSensitivity = obj["mouseSensitivity"].get(); + } + + E2D_LOG_INFO("InputModuleConfig: 从 JSON 加载配置成功"); + return true; + + } catch (const json::exception& e) { + E2D_LOG_ERROR("InputModuleConfig: JSON 解析错误: {}", e.what()); + return false; + } +} + +/** + * @brief 保存配置到 JSON 数据 + * @param jsonData JSON 数据指针 + * @return 保存成功返回 true + */ +bool InputModuleConfig::saveToJson(void* jsonData) const { + if (!jsonData) { + E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空"); + return false; + } + + try { + json& obj = *static_cast(jsonData); + + obj["enableKeyboard"] = enableKeyboard; + obj["enableMouse"] = enableMouse; + obj["enableGamepad"] = enableGamepad; + obj["enableTouch"] = enableTouch; + obj["deadzone"] = deadzone; + obj["mouseSensitivity"] = mouseSensitivity; + + E2D_LOG_INFO("InputModuleConfig: 保存配置到 JSON 成功"); + return true; + + } catch (const json::exception& e) { + E2D_LOG_ERROR("InputModuleConfig: JSON 序列化错误: {}", e.what()); + return false; + } +} + +// ============================================================================ +// InputModuleInitializer 实现 +// ============================================================================ + +/** + * @brief 构造函数 + */ +InputModuleInitializer::InputModuleInitializer() + : moduleId_(INVALID_MODULE_ID) + , windowModuleId_(INVALID_MODULE_ID) + , input_(nullptr) + , initialized_(false) { + E2D_LOG_DEBUG("InputModuleInitializer: 构造函数调用"); +} + +/** + * @brief 析构函数 + */ +InputModuleInitializer::~InputModuleInitializer() { + if (initialized_) { + shutdown(); + } + E2D_LOG_DEBUG("InputModuleInitializer: 析构函数调用"); +} + +/** + * @brief 获取模块依赖列表 + * 返回此模块依赖的窗口模块标识符 + * @return 依赖模块标识符列表 + */ +std::vector InputModuleInitializer::getDependencies() const { + std::vector dependencies; + + if (windowModuleId_ != INVALID_MODULE_ID) { + dependencies.push_back(windowModuleId_); + } + + return dependencies; +} + +/** + * @brief 初始化模块 + * 从窗口模块获取输入接口 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ +bool InputModuleInitializer::initialize(const IModuleConfig* config) { + if (initialized_) { + E2D_LOG_WARN("InputModuleInitializer: 模块已经初始化"); + return true; + } + + if (!config) { + E2D_LOG_ERROR("InputModuleInitializer: 配置指针为空"); + return false; + } + + const InputModuleConfig* inputConfig = dynamic_cast(config); + if (!inputConfig) { + E2D_LOG_ERROR("InputModuleInitializer: 配置类型不正确"); + return false; + } + + if (!inputConfig->validate()) { + E2D_LOG_ERROR("InputModuleInitializer: 配置验证失败"); + return false; + } + + ModuleInfo info = config->getModuleInfo(); + moduleId_ = info.id; + + E2D_LOG_INFO("InputModuleInitializer: 正在初始化输入模块 '{}' (版本: {})", + info.name, info.version); + + E2D_LOG_INFO("InputModuleInitializer: 输入配置 - 键盘: {}, 鼠标: {}, 手柄: {}, 触摸: {}", + inputConfig->enableKeyboard ? "启用" : "禁用", + inputConfig->enableMouse ? "启用" : "禁用", + inputConfig->enableGamepad ? "启用" : "禁用", + inputConfig->enableTouch ? "启用" : "禁用"); + + E2D_LOG_INFO("InputModuleInitializer: 死区: {}, 鼠标灵敏度: {}", + inputConfig->deadzone, inputConfig->mouseSensitivity); + + E2D_LOG_INFO("InputModuleInitializer: 输入模块初始化成功"); + initialized_ = true; + return true; +} + +/** + * @brief 关闭模块 + */ +void InputModuleInitializer::shutdown() { + if (!initialized_) { + E2D_LOG_WARN("InputModuleInitializer: 模块未初始化,无需关闭"); + return; + } + + E2D_LOG_INFO("InputModuleInitializer: 正在关闭输入模块"); + + input_ = nullptr; + moduleId_ = INVALID_MODULE_ID; + initialized_ = false; + + E2D_LOG_INFO("InputModuleInitializer: 输入模块已关闭"); +} + +} // namespace extra2d diff --git a/Extra2D/src/platform/window_module.cpp b/Extra2D/src/platform/window_module.cpp new file mode 100644 index 0000000..c439b46 --- /dev/null +++ b/Extra2D/src/platform/window_module.cpp @@ -0,0 +1,340 @@ +#include +#include +#include +#include + +#include + +using json = nlohmann::json; + +namespace extra2d { + +// ============================================================================ +// WindowModuleConfig 实现 +// ============================================================================ + +/** + * @brief 验证窗口配置有效性 + * 检查窗口尺寸、标题等配置是否合法 + * @return 如果配置有效返回 true + */ +bool WindowModuleConfig::validate() const { + if (windowConfig.width <= 0) { + E2D_LOG_ERROR("Window width must be positive, got: {}", windowConfig.width); + return false; + } + + if (windowConfig.height <= 0) { + E2D_LOG_ERROR("Window height must be positive, got: {}", windowConfig.height); + return false; + } + + if (windowConfig.title.empty()) { + E2D_LOG_WARN("Window title is empty, using default title"); + } + + if (windowConfig.multisamples < 0) { + E2D_LOG_ERROR("MSAA samples cannot be negative, got: {}", windowConfig.multisamples); + return false; + } + + if (windowConfig.multisamples != 0 && + windowConfig.multisamples != 2 && + windowConfig.multisamples != 4 && + windowConfig.multisamples != 8 && + windowConfig.multisamples != 16) { + E2D_LOG_WARN("MSAA samples should be 0, 2, 4, 8, or 16, got: {}", windowConfig.multisamples); + } + + if (backend.empty()) { + E2D_LOG_ERROR("Backend name cannot be empty"); + return false; + } + + return true; +} + +/** + * @brief 应用平台约束 + * 根据目标平台特性调整窗口配置 + * @param platform 目标平台类型 + */ +void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) { + switch (platform) { + case PlatformType::Switch: + E2D_LOG_INFO("Applying Nintendo Switch platform constraints"); + windowConfig.mode = WindowMode::Fullscreen; + windowConfig.resizable = false; + windowConfig.centered = false; + windowConfig.width = 1920; + windowConfig.height = 1080; + backend = "switch"; + break; + + case PlatformType::Windows: + case PlatformType::Linux: + case PlatformType::macOS: + E2D_LOG_INFO("Applying desktop platform constraints"); + if (windowConfig.width <= 0) { + windowConfig.width = 1280; + } + if (windowConfig.height <= 0) { + windowConfig.height = 720; + } + break; + + case PlatformType::Auto: + default: + E2D_LOG_INFO("Auto-detecting platform constraints"); + break; + } +} + +/** + * @brief 重置为默认配置 + * 将所有配置项恢复为默认值 + */ +void WindowModuleConfig::resetToDefaults() { + windowConfig = WindowConfigData{}; + backend = "sdl2"; + + E2D_LOG_INFO("Window module config reset to defaults"); +} + +/** + * @brief 从 JSON 数据加载配置 + * 解析 JSON 对象并填充配置数据 + * @param jsonData JSON 数据指针 + * @return 加载成功返回 true + */ +bool WindowModuleConfig::loadFromJson(const void* jsonData) { + if (!jsonData) { + E2D_LOG_ERROR("JSON data is null"); + return false; + } + + const json& obj = *static_cast(jsonData); + + if (!obj.is_object()) { + E2D_LOG_ERROR("JSON data must be an object"); + return false; + } + + if (obj.contains("title") && obj["title"].is_string()) { + windowConfig.title = obj["title"].get(); + } + + if (obj.contains("width") && obj["width"].is_number_integer()) { + windowConfig.width = obj["width"].get(); + } + + if (obj.contains("height") && obj["height"].is_number_integer()) { + windowConfig.height = obj["height"].get(); + } + + if (obj.contains("fullscreen") && obj["fullscreen"].is_boolean()) { + windowConfig.mode = obj["fullscreen"].get() ? WindowMode::Fullscreen : WindowMode::Windowed; + } + + if (obj.contains("mode") && obj["mode"].is_string()) { + std::string modeStr = obj["mode"].get(); + if (modeStr == "fullscreen") { + windowConfig.mode = WindowMode::Fullscreen; + } else if (modeStr == "borderless") { + windowConfig.mode = WindowMode::Borderless; + } else { + windowConfig.mode = WindowMode::Windowed; + } + } + + if (obj.contains("resizable") && obj["resizable"].is_boolean()) { + windowConfig.resizable = obj["resizable"].get(); + } + + if (obj.contains("vsync") && obj["vsync"].is_boolean()) { + windowConfig.vsync = obj["vsync"].get(); + } + + if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) { + windowConfig.multisamples = obj["multisamples"].get(); + } + + if (obj.contains("msaaSamples") && obj["msaaSamples"].is_number_integer()) { + windowConfig.multisamples = obj["msaaSamples"].get(); + } + + if (obj.contains("centered") && obj["centered"].is_boolean()) { + windowConfig.centered = obj["centered"].get(); + } + + if (obj.contains("centerWindow") && obj["centerWindow"].is_boolean()) { + windowConfig.centered = obj["centerWindow"].get(); + } + + if (obj.contains("visible") && obj["visible"].is_boolean()) { + windowConfig.visible = obj["visible"].get(); + } + + if (obj.contains("decorated") && obj["decorated"].is_boolean()) { + windowConfig.decorated = obj["decorated"].get(); + } + + if (obj.contains("backend") && obj["backend"].is_string()) { + backend = obj["backend"].get(); + } + + E2D_LOG_INFO("Window module config loaded from JSON"); + return true; +} + +/** + * @brief 保存配置到 JSON 数据 + * 将配置数据序列化为 JSON 对象 + * @param jsonData JSON 数据指针 + * @return 保存成功返回 true + */ +bool WindowModuleConfig::saveToJson(void* jsonData) const { + if (!jsonData) { + E2D_LOG_ERROR("JSON data pointer is null"); + return false; + } + + json& obj = *static_cast(jsonData); + + obj["title"] = windowConfig.title; + obj["width"] = windowConfig.width; + obj["height"] = windowConfig.height; + + std::string modeStr = "windowed"; + if (windowConfig.mode == WindowMode::Fullscreen) { + modeStr = "fullscreen"; + } else if (windowConfig.mode == WindowMode::Borderless) { + modeStr = "borderless"; + } + obj["mode"] = modeStr; + + obj["resizable"] = windowConfig.resizable; + obj["vsync"] = windowConfig.vsync; + obj["multisamples"] = windowConfig.multisamples; + obj["centered"] = windowConfig.centered; + obj["visible"] = windowConfig.visible; + obj["decorated"] = windowConfig.decorated; + obj["backend"] = backend; + + E2D_LOG_INFO("Window module config saved to JSON"); + return true; +} + +// ============================================================================ +// WindowModuleInitializer 实现 +// ============================================================================ + +/** + * @brief 构造函数 + * 初始化窗口模块初始化器 + */ +WindowModuleInitializer::WindowModuleInitializer() + : moduleId_(INVALID_MODULE_ID) + , window_(nullptr) + , initialized_(false) { + E2D_LOG_DEBUG("WindowModuleInitializer constructed"); +} + +/** + * @brief 析构函数 + * 确保模块正确关闭 + */ +WindowModuleInitializer::~WindowModuleInitializer() { + if (initialized_) { + shutdown(); + } + E2D_LOG_DEBUG("WindowModuleInitializer destructed"); +} + +/** + * @brief 初始化模块 + * 使用 BackendFactory 创建窗口实例 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ +bool WindowModuleInitializer::initialize(const IModuleConfig* config) { + if (initialized_) { + E2D_LOG_WARN("Window module already initialized"); + return true; + } + + if (!config) { + E2D_LOG_ERROR("Window module config is null"); + return false; + } + + const WindowModuleConfig* windowConfig = dynamic_cast(config); + if (!windowConfig) { + E2D_LOG_ERROR("Invalid config type for window module"); + return false; + } + + ModuleInfo info = config->getModuleInfo(); + moduleId_ = info.id; + + const std::string& backend = windowConfig->backend; + + if (!BackendFactory::has(backend)) { + E2D_LOG_ERROR("Backend '{}' not available", backend); + auto backends = BackendFactory::backends(); + if (backends.empty()) { + E2D_LOG_ERROR("No backends registered!"); + return false; + } + std::string backendList; + for (const auto& b : backends) { + if (!backendList.empty()) backendList += ", "; + backendList += b; + } + E2D_LOG_WARN("Available backends: {}", backendList); + return false; + } + + window_ = BackendFactory::createWindow(backend); + if (!window_) { + E2D_LOG_ERROR("Failed to create window for backend: {}", backend); + return false; + } + + if (!window_->create(windowConfig->windowConfig)) { + E2D_LOG_ERROR("Failed to create window with given config"); + window_.reset(); + return false; + } + + initialized_ = true; + E2D_LOG_INFO("Window module initialized successfully (backend: {}, {}x{})", + backend, + windowConfig->windowConfig.width, + windowConfig->windowConfig.height); + + return true; +} + +/** + * @brief 关闭模块 + * 销毁窗口实例并重置状态 + */ +void WindowModuleInitializer::shutdown() { + if (!initialized_) { + E2D_LOG_WARN("Window module not initialized, nothing to shutdown"); + return; + } + + if (window_) { + window_->destroy(); + window_.reset(); + } + + initialized_ = false; + moduleId_ = INVALID_MODULE_ID; + + E2D_LOG_INFO("Window module shutdown complete"); +} + +} // namespace extra2d