diff --git a/Extra2D/include/extra2d/audio/audio_config.h b/Extra2D/include/extra2d/audio/audio_config.h new file mode 100644 index 0000000..719e97f --- /dev/null +++ b/Extra2D/include/extra2d/audio/audio_config.h @@ -0,0 +1,44 @@ +#pragma once + +namespace extra2d { + +/** + * @file audio_config.h + * @brief 音频模块配置 + * + * 定义音频相关的配置数据结构,由 AudioModule 管理。 + */ + +/** + * @brief 音频配置数据结构 + */ +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}; + + /** + * @brief 验证音量值是否有效 + * @param volume 要验证的音量值 + * @return 如果音量在0-100范围内返回 true + */ + bool isValidVolume(int volume) const { return volume >= 0 && volume <= 100; } + + /** + * @brief 将音量值转换为浮点数 + * @param volume 音量值(0-100) + * @return 浮点数音量值(0.0-1.0) + */ + float volumeToFloat(int volume) const { return static_cast(volume) / 100.0f; } +}; + +} diff --git a/Extra2D/include/extra2d/config/app_config.h b/Extra2D/include/extra2d/config/app_config.h index dbfb589..a7a3812 100644 --- a/Extra2D/include/extra2d/config/app_config.h +++ b/Extra2D/include/extra2d/config/app_config.h @@ -1,180 +1,27 @@ #pragma once #include -#include #include -#include #include -#include namespace extra2d { -// ============================================================================ -// 窗口模式枚举 -// ============================================================================ -enum class WindowMode { - Windowed, - Fullscreen, - Borderless -}; +/** + * @file app_config.h + * @brief 应用级别配置 + * + * 本文件仅包含应用级别的配置项,不包含任何模块特定配置。 + * 各模块应该在自己的模块文件中定义配置结构,并实现 IModuleConfig 接口。 + * + * 模块配置通过 ModuleRegistry 注册,由 ConfigManager 统一管理。 + * 这种设计遵循开闭原则,新增模块无需修改引擎核心代码。 + */ -// ============================================================================ -// 窗口配置数据 -// ============================================================================ -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; -}; - -// ============================================================================ -// 应用统一配置 -// ============================================================================ +/** + * @brief 应用配置结构体 + * 仅包含应用级别的配置项,模块配置由各模块自行管理 + */ 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 = ""; @@ -193,12 +40,6 @@ struct AppConfig { */ bool validate() const; - /** - * @brief 应用平台约束 - * @param platform 平台配置接口 - */ - void applyPlatformConstraints(const PlatformConfig& platform); - /** * @brief 重置为默认值 */ @@ -215,12 +56,6 @@ struct AppConfig { * @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 index f5430f6..50af30c 100644 --- a/Extra2D/include/extra2d/config/config_loader.h +++ b/Extra2D/include/extra2d/config/config_loader.h @@ -6,9 +6,17 @@ namespace extra2d { -// ============================================================================ -// 配置加载结果 -// ============================================================================ +/** + * @file config_loader.h + * @brief 配置加载器接口 + * + * 配置加载器只负责加载应用级别的配置(AppConfig)。 + * 模块配置通过 ModuleRegistry 和各模块的 IModuleConfig 接口加载。 + */ + +/** + * @brief 配置加载结果 + */ struct ConfigLoadResult { bool success = false; std::string errorMessage; @@ -24,9 +32,9 @@ struct ConfigLoadResult { bool hasError() const { return !success; } }; -// ============================================================================ -// 配置保存结果 -// ============================================================================ +/** + * @brief 配置保存结果 + */ struct ConfigSaveResult { bool success = false; std::string errorMessage; @@ -40,15 +48,15 @@ struct ConfigSaveResult { bool hasError() const { return !success; } }; -// ============================================================================ -// 配置加载器抽象接口 -// ============================================================================ +/** + * @brief 配置加载器抽象接口 + */ class ConfigLoader { public: virtual ~ConfigLoader() = default; /** - * @brief 从文件加载配置 + * @brief 从文件加载应用配置 * @param filepath 配置文件路径 * @param config 输出的配置对象 * @return 加载结果 @@ -56,7 +64,7 @@ public: virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0; /** - * @brief 保存配置到文件 + * @brief 保存应用配置到文件 * @param filepath 配置文件路径 * @param config 要保存的配置对象 * @return 保存结果 @@ -78,6 +86,20 @@ public: */ virtual std::string saveToString(const AppConfig& config) = 0; + /** + * @brief 从文件加载完整配置(包括模块配置) + * @param filepath 配置文件路径 + * @return 加载结果 + */ + virtual ConfigLoadResult loadWithModules(const std::string& filepath) = 0; + + /** + * @brief 保存完整配置(包括模块配置)到文件 + * @param filepath 配置文件路径 + * @return 保存结果 + */ + virtual ConfigSaveResult saveWithModules(const std::string& filepath) = 0; + /** * @brief 获取支持的文件扩展名 * @return 文件扩展名(不含点号,如 "json") @@ -98,9 +120,9 @@ public: virtual UniquePtr clone() const = 0; }; -// ============================================================================ -// JSON 配置加载器 -// ============================================================================ +/** + * @brief JSON 配置加载器 + */ class JsonConfigLoader : public ConfigLoader { public: JsonConfigLoader() = default; @@ -110,29 +132,16 @@ public: 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; + ConfigLoadResult loadWithModules(const std::string& filepath) override; + ConfigSaveResult saveWithModules(const std::string& filepath) 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 配置加载器 -// ============================================================================ +/** + * @brief INI 配置加载器 + */ class IniConfigLoader : public ConfigLoader { public: IniConfigLoader() = default; @@ -142,6 +151,8 @@ public: 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; + ConfigLoadResult loadWithModules(const std::string& filepath) override; + ConfigSaveResult saveWithModules(const std::string& filepath) override; const char* extension() const override { return "ini"; } bool supportsFile(const std::string& filepath) const override; UniquePtr clone() const override; @@ -153,9 +164,9 @@ private: ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName); }; -// ============================================================================ -// 配置加载器工厂 -// ============================================================================ +/** + * @brief 配置加载器工厂 + */ class ConfigLoaderFactory { public: /** diff --git a/Extra2D/include/extra2d/config/config_manager.h b/Extra2D/include/extra2d/config/config_manager.h index 17412e8..18da0c2 100644 --- a/Extra2D/include/extra2d/config/config_manager.h +++ b/Extra2D/include/extra2d/config/config_manager.h @@ -10,9 +10,17 @@ namespace extra2d { -// ============================================================================ -// 配置变更事件 -// ============================================================================ +/** + * @file config_manager.h + * @brief 配置管理器 + * + * 配置管理器只管理应用级别的配置(AppConfig)。 + * 模块配置通过 ModuleRegistry 管理,各模块实现 IModuleConfig 接口。 + */ + +/** + * @brief 配置变更事件 + */ struct ConfigChangeEvent { std::string section; std::string field; @@ -20,18 +28,16 @@ struct ConfigChangeEvent { std::string newValue; }; -// ============================================================================ -// 配置变更回调类型 -// ============================================================================ +/** + * @brief 配置变更回调类型 + */ using ConfigChangeCallback = Function; -// ============================================================================ -// 配置管理器(单例) -// ============================================================================ +/** + * @brief 配置管理器(单例) + */ class ConfigManager { public: - using ModuleConfigPtr = Ptr; - /** * @brief 获取单例实例 * @return 配置管理器实例引用 @@ -70,6 +76,20 @@ public: */ ConfigSaveResult saveConfig(const std::string &filepath = ""); + /** + * @brief 加载完整配置(包括模块配置) + * @param filepath 配置文件路径 + * @return 加载结果 + */ + ConfigLoadResult loadConfigWithModules(const std::string &filepath = ""); + + /** + * @brief 保存完整配置(包括模块配置) + * @param filepath 配置文件路径 + * @return 保存结果 + */ + ConfigSaveResult saveConfigWithModules(const std::string &filepath = ""); + /** * @brief 重新加载配置 * @return 加载结果 @@ -124,40 +144,6 @@ public: */ 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 配置节 @@ -285,8 +271,6 @@ private: 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; @@ -299,16 +283,13 @@ private: std::unordered_map m_changeCallbacks; int m_nextCallbackId = 1; - std::unordered_map m_moduleConfigs; + std::unordered_map m_rawValues; 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/platform_config.h b/Extra2D/include/extra2d/config/platform_config.h index c3a1782..2ac7648 100644 --- a/Extra2D/include/extra2d/config/platform_config.h +++ b/Extra2D/include/extra2d/config/platform_config.h @@ -5,9 +5,17 @@ namespace extra2d { -// ============================================================================ -// 平台类型枚举 -// ============================================================================ +/** + * @file platform_config.h + * @brief 平台配置接口 + * + * 平台配置只提供平台能力信息,不再直接修改应用配置。 + * 各模块通过 IModuleConfig::applyPlatformConstraints() 处理平台约束。 + */ + +/** + * @brief 平台类型枚举 + */ enum class PlatformType { Auto, Windows, @@ -16,9 +24,9 @@ enum class PlatformType { macOS }; -// ============================================================================ -// 平台能力结构 -// ============================================================================ +/** + * @brief 平台能力结构 + */ struct PlatformCapabilities { bool supportsWindowed = true; bool supportsFullscreen = true; @@ -46,9 +54,9 @@ struct PlatformCapabilities { bool isConsole() const { return !supportsWindowed && supportsGamepad; } }; -// ============================================================================ -// 平台配置抽象接口 -// ============================================================================ +/** + * @brief 平台配置抽象接口 + */ class PlatformConfig { public: virtual ~PlatformConfig() = default; @@ -56,20 +64,24 @@ public: 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; }; -// ============================================================================ -// 平台配置工厂函数声明 -// ============================================================================ +/** + * @brief 创建平台配置实例 + * @param type 平台类型,默认为 Auto(自动检测) + * @return 平台配置的智能指针 + */ UniquePtr createPlatformConfig(PlatformType type = PlatformType::Auto); +/** + * @brief 获取平台类型名称 + * @param type 平台类型枚举值 + * @return 平台名称字符串 + */ const char* getPlatformTypeName(PlatformType type); } diff --git a/Extra2D/include/extra2d/debug/debug_config.h b/Extra2D/include/extra2d/debug/debug_config.h new file mode 100644 index 0000000..0f3a5a7 --- /dev/null +++ b/Extra2D/include/extra2d/debug/debug_config.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @file debug_config.h + * @brief 调试模块配置 + * + * 定义调试相关的配置数据结构,由 DebugModule 管理。 + */ + +/** + * @brief 调试配置数据结构 + */ +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; + + /** + * @brief 检查是否存在指定的调试标志 + * @param flag 要检查的标志名称 + * @return 如果存在返回 true + */ + bool hasDebugFlag(const std::string& flag) const; + + /** + * @brief 添加调试标志 + * @param flag 要添加的标志名称 + */ + void addDebugFlag(const std::string& flag); + + /** + * @brief 移除调试标志 + * @param flag 要移除的标志名称 + */ + void removeDebugFlag(const std::string& flag); +}; + +} diff --git a/Extra2D/include/extra2d/graphics/render_config.h b/Extra2D/include/extra2d/graphics/render_config.h new file mode 100644 index 0000000..4af4996 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/render_config.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @file render_config.h + * @brief 渲染模块配置 + * + * 定义渲染相关的配置数据结构,由 RenderModule 管理。 + */ + +/** + * @brief 渲染配置数据结构 + */ +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; + + /** + * @brief 检查是否启用多重采样 + * @return 如果多重采样数大于0返回 true + */ + bool isMultisampleEnabled() const { return multisamples > 0; } + + /** + * @brief 检查是否限制帧率 + * @return 如果设置了目标帧率返回 true + */ + bool isFPSCapped() const { return targetFPS > 0; } +}; + +} diff --git a/Extra2D/include/extra2d/graphics/render_module.h b/Extra2D/include/extra2d/graphics/render_module.h index 6be473a..544bad1 100644 --- a/Extra2D/include/extra2d/graphics/render_module.h +++ b/Extra2D/include/extra2d/graphics/render_module.h @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/Extra2D/include/extra2d/input/input_config.h b/Extra2D/include/extra2d/input/input_config.h new file mode 100644 index 0000000..98924a6 --- /dev/null +++ b/Extra2D/include/extra2d/input/input_config.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace extra2d { + +/** + * @file input_config.h + * @brief 输入模块配置 + * + * 定义输入相关的配置数据结构,由 InputModule 管理。 + */ + +/** + * @brief 输入配置数据结构 + */ +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; + + /** + * @brief 验证死区值是否有效 + * @return 如果死区值在0-1范围内返回 true + */ + bool isDeadzoneValid() const { return deadzone >= 0.0f && deadzone <= 1.0f; } +}; + +} diff --git a/Extra2D/include/extra2d/platform/input_module.h b/Extra2D/include/extra2d/platform/input_module.h index aa710eb..d4e71ca 100644 --- a/Extra2D/include/extra2d/platform/input_module.h +++ b/Extra2D/include/extra2d/platform/input_module.h @@ -2,30 +2,35 @@ #include #include +#include #include #include namespace extra2d { +/** + * @file input_module.h + * @brief 输入模块 + * + * 输入模块管理键盘、鼠标、手柄和触摸输入。 + * 通过事件系统分发输入事件。 + */ + /** * @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; - + InputConfigData inputConfig; + /** * @brief 获取模块信息 * @return 模块信息结构体 */ ModuleInfo getModuleInfo() const override { ModuleInfo info; + info.id = 0; info.name = "Input"; info.version = "1.0.0"; info.priority = ModulePriority::Input; @@ -47,7 +52,6 @@ public: /** * @brief 应用平台约束 - * 根据平台特性调整配置 * @param platform 目标平台类型 */ void applyPlatformConstraints(PlatformType platform) override; @@ -55,7 +59,9 @@ public: /** * @brief 重置为默认配置 */ - void resetToDefaults() override; + void resetToDefaults() override { + inputConfig = InputConfigData{}; + } /** * @brief 从 JSON 数据加载配置 @@ -75,57 +81,21 @@ public: /** * @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_; } + void setModuleId(ModuleId id) { moduleId_ = id; } + /** * @brief 获取输入接口 * @return 输入接口指针 @@ -133,16 +103,27 @@ public: IInput* getInput() const { return input_; } /** - * @brief 设置窗口模块标识符 - * @param windowModuleId 窗口模块标识符 + * @brief 更新输入状态 + * 每帧调用,更新输入状态并分发事件 */ - void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; } - + void update(); + private: ModuleId moduleId_ = INVALID_MODULE_ID; - ModuleId windowModuleId_ = INVALID_MODULE_ID; IInput* input_ = nullptr; bool initialized_ = false; + InputConfigData config_; }; -} // namespace extra2d +/** + * @brief 获取输入模块标识符 + * @return 输入模块标识符 + */ +ModuleId get_input_module_id(); + +/** + * @brief 注册输入模块 + */ +void register_input_module(); + +} diff --git a/Extra2D/include/extra2d/platform/iwindow.h b/Extra2D/include/extra2d/platform/iwindow.h index 8b42458..b5fbff8 100644 --- a/Extra2D/include/extra2d/platform/iwindow.h +++ b/Extra2D/include/extra2d/platform/iwindow.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/Extra2D/include/extra2d/platform/window_config.h b/Extra2D/include/extra2d/platform/window_config.h new file mode 100644 index 0000000..ab2a4cb --- /dev/null +++ b/Extra2D/include/extra2d/platform/window_config.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @file window_config.h + * @brief 窗口模块配置 + * + * 定义窗口相关的配置数据结构,由 WindowModule 管理。 + */ + +/** + * @brief 窗口模式枚举 + */ +enum class WindowMode { + Windowed, + Fullscreen, + Borderless +}; + +/** + * @brief 窗口配置数据结构 + */ +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; + + /** + * @brief 检查窗口尺寸是否有效 + * @return 如果宽高都大于0返回 true + */ + bool isSizeValid() const { return width > 0 && height > 0; } + + /** + * @brief 检查是否设置了窗口位置 + * @return 如果设置了有效位置返回 true + */ + bool hasPosition() const { return posX >= 0 && posY >= 0; } + + /** + * @brief 获取窗口宽高比 + * @return 宽高比值 + */ + float aspectRatio() const { return static_cast(width) / static_cast(height); } + + /** + * @brief 检查是否为全屏模式 + * @return 如果是全屏模式返回 true + */ + bool isFullscreen() const { return mode == WindowMode::Fullscreen; } + + /** + * @brief 检查是否为无边框模式 + * @return 如果是无边框模式返回 true + */ + bool isBorderless() const { return mode == WindowMode::Borderless || borderless; } +}; + +} diff --git a/Extra2D/include/extra2d/platform/window_module.h b/Extra2D/include/extra2d/platform/window_module.h index 9f19871..3a11b09 100644 --- a/Extra2D/include/extra2d/platform/window_module.h +++ b/Extra2D/include/extra2d/platform/window_module.h @@ -2,17 +2,35 @@ #include #include -#include +#include #include #include namespace extra2d { +/** + * @file window_module.h + * @brief 窗口模块 + * + * 窗口模块使用 SDL2 作为唯一后端,支持以下平台: + * - Windows + * - Linux + * - macOS + * - Nintendo Switch + */ + +/** + * @brief 窗口模块配置 + * 实现 IModuleConfig 接口 + */ class WindowModuleConfig : public IModuleConfig { public: - std::string backend = "sdl2"; WindowConfigData windowConfig; + /** + * @brief 获取模块信息 + * @return 模块信息结构体 + */ ModuleInfo getModuleInfo() const override { ModuleInfo info; info.id = 0; @@ -23,55 +41,155 @@ public: return info; } + /** + * @brief 获取配置节名称 + * @return 配置节名称字符串 + */ std::string getConfigSectionName() const override { return "window"; } + /** + * @brief 验证配置有效性 + * @return 如果配置有效返回 true + */ bool validate() const override { return windowConfig.width > 0 && windowConfig.height > 0; } + /** + * @brief 应用平台约束 + * @param platform 目标平台类型 + */ + void applyPlatformConstraints(PlatformType platform) override; + + /** + * @brief 重置为默认配置 + */ void resetToDefaults() override { - backend = "sdl2"; windowConfig = WindowConfigData{}; } + /** + * @brief 从 JSON 数据加载配置 + * @param jsonData JSON 数据指针 + * @return 加载成功返回 true + */ bool loadFromJson(const void* jsonData) override; + + /** + * @brief 保存配置到 JSON 数据 + * @param jsonData JSON 数据指针 + * @return 保存成功返回 true + */ bool saveToJson(void* jsonData) const override; }; +/** + * @brief 窗口模块初始化器 + * 实现 IModuleInitializer 接口 + */ class WindowModuleInitializer : public IModuleInitializer { public: + /** + * @brief 构造函数 + */ WindowModuleInitializer(); + + /** + * @brief 析构函数 + */ ~WindowModuleInitializer() override; + /** + * @brief 获取模块标识符 + * @return 模块唯一标识符 + */ ModuleId getModuleId() const override { return moduleId_; } + + /** + * @brief 获取模块优先级 + * @return 模块优先级 + */ ModulePriority getPriority() const override { return ModulePriority::Core; } + + /** + * @brief 获取模块依赖列表 + * @return 依赖模块标识符列表(窗口模块无依赖) + */ std::vector getDependencies() const override { return {}; } + /** + * @brief 初始化模块 + * @param config 模块配置指针 + * @return 初始化成功返回 true + */ bool initialize(const IModuleConfig* config) override; + + /** + * @brief 关闭模块 + */ void shutdown() override; + + /** + * @brief 检查模块是否已初始化 + * @return 已初始化返回 true + */ bool isInitialized() const override { return initialized_; } + /** + * @brief 设置模块标识符 + * @param id 模块标识符 + */ void setModuleId(ModuleId id) { moduleId_ = id; } + + /** + * @brief 设置窗口配置 + * @param config 窗口配置数据 + */ void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; } + /** + * @brief 获取窗口接口 + * @return 窗口接口指针 + */ IWindow* getWindow() const { return window_.get(); } private: - bool initBackend(); - bool createWindow(const std::string& backend, const WindowConfigData& config); - void shutdownBackend(); + /** + * @brief 初始化 SDL2 后端 + * @return 初始化成功返回 true + */ + bool initSDL2(); + + /** + * @brief 关闭 SDL2 后端 + */ + void shutdownSDL2(); + + /** + * @brief 创建窗口 + * @param config 窗口配置数据 + * @return 创建成功返回 true + */ + bool createWindow(const WindowConfigData& config); ModuleId moduleId_ = INVALID_MODULE_ID; bool initialized_ = false; - bool backendInitialized_ = false; - std::string backend_; + bool sdl2Initialized_ = false; WindowConfigData windowConfig_; UniquePtr window_; }; +/** + * @brief 获取窗口模块标识符 + * @return 窗口模块标识符 + */ ModuleId get_window_module_id(); + +/** + * @brief 注册窗口模块 + */ void register_window_module(); -} // namespace extra2d +} diff --git a/Extra2D/include/extra2d/resource/resource_config.h b/Extra2D/include/extra2d/resource/resource_config.h new file mode 100644 index 0000000..65aa2fe --- /dev/null +++ b/Extra2D/include/extra2d/resource/resource_config.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @file resource_config.h + * @brief 资源模块配置 + * + * 定义资源相关的配置数据结构,由 ResourceModule 管理。 + */ + +/** + * @brief 资源配置数据结构 + */ +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; + + /** + * @brief 添加资源搜索路径 + * @param path 要添加的搜索路径 + */ + void addSearchPath(const std::string& path); + + /** + * @brief 移除资源搜索路径 + * @param path 要移除的搜索路径 + */ + void removeSearchPath(const std::string& path); + + /** + * @brief 检查是否存在指定的搜索路径 + * @param path 要检查的路径 + * @return 如果存在返回 true + */ + bool hasSearchPath(const std::string& path) const; +}; + +} diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index 8a3105c..89ea356 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -287,10 +288,14 @@ void Application::mainLoop() { render(); const auto& appConfig = ConfigManager::instance().appConfig(); - if (!appConfig.render.vsync && appConfig.render.isFPSCapped()) { + + auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id()); + auto* renderModuleConfig = dynamic_cast(renderConfig); + + if (renderModuleConfig && !renderModuleConfig->vsync && renderModuleConfig->targetFPS > 0) { double frameEndTime = getTimeSeconds(); double frameTime = frameEndTime - currentTime; - double target = 1.0 / static_cast(appConfig.render.targetFPS); + double target = 1.0 / static_cast(renderModuleConfig->targetFPS); if (frameTime < target) { auto sleepSeconds = target - frameTime; std::this_thread::sleep_for(std::chrono::duration(sleepSeconds)); diff --git a/Extra2D/src/config/app_config.cpp b/Extra2D/src/config/app_config.cpp index 366a40d..9531559 100644 --- a/Extra2D/src/config/app_config.cpp +++ b/Extra2D/src/config/app_config.cpp @@ -1,168 +1,11 @@ #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 = ""; @@ -172,150 +15,7 @@ AppConfig AppConfig::createDefault() { 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; @@ -332,333 +32,12 @@ bool AppConfig::validate() const { 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; } @@ -678,4 +57,4 @@ void AppConfig::merge(const AppConfig& other) { E2D_LOG_INFO("Merged app config"); } -} +} diff --git a/Extra2D/src/config/config_loader_ini.cpp b/Extra2D/src/config/config_loader_ini.cpp index 7e7bb0a..4826251 100644 --- a/Extra2D/src/config/config_loader_ini.cpp +++ b/Extra2D/src/config/config_loader_ini.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -39,54 +41,6 @@ static std::string toLower(const std::string& str) { 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 数据存储结构 */ @@ -177,18 +131,8 @@ static bool hasIniValue(const IniData& data, const std::string& section, const s 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); + E2D_LOG_INFO("正在从 INI 文件加载应用配置: {}", filepath); std::ifstream file(filepath); if (!file.is_open()) { @@ -204,14 +148,8 @@ ConfigLoadResult IniConfigLoader::load(const std::string& filepath, AppConfig& c 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); + E2D_LOG_INFO("正在保存应用配置到 INI 文件: {}", filepath); std::string content = saveToString(config); @@ -228,12 +166,6 @@ ConfigSaveResult IniConfigLoader::save(const std::string& filepath, const AppCon 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); @@ -250,499 +182,21 @@ ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, App 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, "app", "configFile")) { + config.configFile = getIniValue(data, "app", "configFile"); } - if (hasIniValue(data, "window", "width")) { + if (hasIniValue(data, "app", "targetPlatform")) { 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; + auto res = parseInt(getIniValue(data, "app", "targetPlatform"), value, "app.targetPlatform"); + if (res.isOk()) { + config.targetPlatform = static_cast(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 配置加载成功"); + E2D_LOG_INFO("INI 应用配置加载成功"); return ConfigLoadResult::ok(); } -/** - * @brief 将配置序列化为 INI 字符串 - * @param config 配置对象 - * @return 序列化后的 INI 字符串 - */ std::string IniConfigLoader::saveToString(const AppConfig& config) { std::ostringstream oss; @@ -750,118 +204,84 @@ std::string IniConfigLoader::saveToString(const AppConfig& config) { 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"; + oss << "configFile=" << config.configFile << "\n"; + oss << "targetPlatform=" << static_cast(config.targetPlatform) << "\n"; return oss.str(); } -/** - * @brief 检查是否支持指定文件 - * @param filepath 文件路径 - * @return 如果支持返回 true - */ +ConfigLoadResult IniConfigLoader::loadWithModules(const std::string& filepath) { + E2D_LOG_INFO("正在从 INI 文件加载完整配置(含模块): {}", filepath); + + std::ifstream file(filepath); + if (!file.is_open()) { + E2D_LOG_ERROR("无法打开配置文件: {}", filepath); + return ConfigLoadResult::error("无法打开配置文件: " + filepath); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + file.close(); + + IniData data; + auto result = parseIniContent(content, data); + if (result.hasError()) { + return result; + } + + auto& registry = ModuleRegistry::instance(); + auto moduleIds = registry.getAllModules(); + + for (ModuleId moduleId : moduleIds) { + IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId); + if (!moduleConfig) continue; + + std::string sectionName = moduleConfig->getConfigSectionName(); + if (sectionName.empty()) continue; + + if (data.find(sectionName) != data.end()) { + E2D_LOG_DEBUG("加载模块 {} 的 INI 配置", moduleConfig->getModuleInfo().name); + } + } + + E2D_LOG_INFO("完整配置加载成功"); + return ConfigLoadResult::ok(); +} + +ConfigSaveResult IniConfigLoader::saveWithModules(const std::string& filepath) { + E2D_LOG_INFO("正在保存完整配置(含模块)到 INI 文件: {}", filepath); + + std::ostringstream oss; + + oss << saveToString(ConfigManager::instance().appConfig()); + + auto& registry = ModuleRegistry::instance(); + auto moduleIds = registry.getAllModules(); + + for (ModuleId moduleId : moduleIds) { + IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId); + if (!moduleConfig) continue; + + std::string sectionName = moduleConfig->getConfigSectionName(); + if (sectionName.empty()) continue; + + oss << "\n[" << sectionName << "]\n"; + } + + std::ofstream file(filepath); + if (!file.is_open()) { + E2D_LOG_ERROR("无法创建配置文件: {}", filepath); + return ConfigSaveResult::error("无法创建配置文件: " + filepath); + } + + file << oss.str(); + file.close(); + + E2D_LOG_INFO("完整配置已成功保存到: {}", filepath); + return ConfigSaveResult::ok(); +} + bool IniConfigLoader::supportsFile(const std::string& filepath) const { if (filepath.length() >= 4) { std::string ext = filepath.substr(filepath.length() - 4); @@ -871,31 +291,14 @@ bool IniConfigLoader::supportsFile(const std::string& filepath) const { 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; @@ -909,13 +312,6 @@ ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result } } -/** - * @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; @@ -929,13 +325,6 @@ ConfigLoadResult IniConfigLoader::parseFloat(const std::string& value, float& re } } -/** - * @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") { diff --git a/Extra2D/src/config/config_loader_json.cpp b/Extra2D/src/config/config_loader_json.cpp index 89badc8..03dcca6 100644 --- a/Extra2D/src/config/config_loader_json.cpp +++ b/Extra2D/src/config/config_loader_json.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -9,64 +10,8 @@ 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); + E2D_LOG_INFO("正在从 JSON 文件加载应用配置: {}", filepath); std::ifstream file(filepath); if (!file.is_open()) { @@ -82,14 +27,8 @@ ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig& 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); + E2D_LOG_INFO("正在保存应用配置到 JSON 文件: {}", filepath); std::string content = saveToString(config); @@ -106,12 +45,6 @@ ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppCo return ConfigSaveResult::ok(); } -/** - * @brief 从 JSON 字符串加载配置 - * @param content JSON 内容字符串 - * @param config 输出的配置对象 - * @return 加载结果 - */ ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) { json root; @@ -135,79 +68,110 @@ ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, Ap config.organization = root["organization"].get(); } - if (root.contains("window")) { - auto result = parseWindowConfig(&root["window"], config.window); - if (result.hasError()) { - return result; - } + if (root.contains("configFile") && root["configFile"].is_string()) { + config.configFile = root["configFile"].get(); } - if (root.contains("render")) { - auto result = parseRenderConfig(&root["render"], config.render); - if (result.hasError()) { - return result; - } + if (root.contains("targetPlatform") && root["targetPlatform"].is_number_integer()) { + config.targetPlatform = static_cast(root["targetPlatform"].get()); } - 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 配置加载成功"); + 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); + root["configFile"] = config.configFile; + root["targetPlatform"] = static_cast(config.targetPlatform); return root.dump(4); } -/** - * @brief 检查是否支持指定文件 - * @param filepath 文件路径 - * @return 如果支持返回 true - */ +ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) { + 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(); + + 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)); + } + + auto& registry = ModuleRegistry::instance(); + auto moduleIds = registry.getAllModules(); + + for (ModuleId moduleId : moduleIds) { + IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId); + if (!moduleConfig) continue; + + std::string sectionName = moduleConfig->getConfigSectionName(); + if (sectionName.empty()) continue; + + if (root.contains(sectionName)) { + if (!moduleConfig->loadFromJson(&root[sectionName])) { + E2D_LOG_WARN("模块 {} 配置加载失败", moduleConfig->getModuleInfo().name); + } else { + E2D_LOG_DEBUG("模块 {} 配置加载成功", moduleConfig->getModuleInfo().name); + } + } + } + + E2D_LOG_INFO("完整配置加载成功"); + return ConfigLoadResult::ok(); +} + +ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) { + E2D_LOG_INFO("正在保存完整配置(含模块)到 JSON 文件: {}", filepath); + + json root; + + auto& registry = ModuleRegistry::instance(); + auto moduleIds = registry.getAllModules(); + + for (ModuleId moduleId : moduleIds) { + IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId); + if (!moduleConfig) continue; + + std::string sectionName = moduleConfig->getConfigSectionName(); + if (sectionName.empty()) continue; + + json sectionJson; + if (moduleConfig->saveToJson(§ionJson)) { + root[sectionName] = sectionJson; + } + } + + std::ofstream file(filepath); + if (!file.is_open()) { + E2D_LOG_ERROR("无法创建配置文件: {}", filepath); + return ConfigSaveResult::error("无法创建配置文件: " + filepath); + } + + file << root.dump(4); + file.close(); + + E2D_LOG_INFO("完整配置已成功保存到: {}", filepath); + return ConfigSaveResult::ok(); +} + bool JsonConfigLoader::supportsFile(const std::string& filepath) const { if (filepath.length() >= 5) { std::string ext = filepath.substr(filepath.length() - 5); @@ -217,547 +181,8 @@ bool JsonConfigLoader::supportsFile(const std::string& filepath) const { 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 index edabb96..2094b22 100644 --- a/Extra2D/src/config/config_manager.cpp +++ b/Extra2D/src/config/config_manager.cpp @@ -6,10 +6,6 @@ namespace extra2d { -/** - * @brief 构造函数 - * 初始化配置管理器的成员变量 - */ ConfigManager::ConfigManager() : m_nextCallbackId(1) , m_autoSaveEnabled(false) @@ -19,32 +15,17 @@ ConfigManager::ConfigManager() 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"); @@ -70,8 +51,6 @@ bool ConfigManager::initialize(const std::string& configPath) { m_appConfig = AppConfig::createDefault(); m_appConfig.targetPlatform = PlatformDetector::detect(); - m_platformConfig->applyDefaults(m_appConfig); - m_initialized = true; m_modified = false; @@ -80,10 +59,6 @@ bool ConfigManager::initialize(const std::string& configPath) { return true; } -/** - * @brief 关闭配置管理器 - * 清理所有资源并重置状态 - */ void ConfigManager::shutdown() { if (!m_initialized) { return; @@ -96,7 +71,7 @@ void ConfigManager::shutdown() { } m_changeCallbacks.clear(); - m_moduleConfigs.clear(); + m_rawValues.clear(); m_loader.reset(); m_platformConfig.reset(); @@ -106,19 +81,10 @@ void ConfigManager::shutdown() { 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); @@ -136,10 +102,6 @@ ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) { 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"); @@ -157,11 +119,6 @@ ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) { return result; } -/** - * @brief 保存配置到文件 - * @param filepath 配置文件路径(可选,默认使用初始化时的路径) - * @return 保存结果 - */ ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) { std::lock_guard lock(m_mutex); @@ -188,35 +145,69 @@ ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) { return result; } -/** - * @brief 重新加载配置 - * @return 加载结果 - */ +ConfigLoadResult ConfigManager::loadConfigWithModules(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"); + } + + ConfigLoadResult result = m_loader->loadWithModules(path); + + if (result.success) { + m_configPath = path; + m_modified = false; + E2D_LOG_INFO("Full config (with modules) loaded from: {}", path); + } else { + E2D_LOG_ERROR("Failed to load full config from {}: {}", path, result.errorMessage); + } + + return result; +} + +ConfigSaveResult ConfigManager::saveConfigWithModules(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->saveWithModules(path); + + if (result.success) { + m_configPath = path; + m_modified = false; + E2D_LOG_INFO("Full config (with modules) saved to: {}", path); + } else { + E2D_LOG_ERROR("Failed to save full config to {}: {}", path, result.errorMessage); + } + + return result; +} + 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); @@ -226,27 +217,14 @@ void ConfigManager::setAppConfig(const AppConfig& config) { 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); @@ -257,10 +235,6 @@ int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) { return id; } -/** - * @brief 取消注册配置变更回调 - * @param callbackId 回调ID - */ void ConfigManager::unregisterChangeCallback(int callbackId) { std::lock_guard lock(m_mutex); @@ -271,192 +245,111 @@ void ConfigManager::unregisterChangeCallback(int 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); + std::string fullKey = section + "." + key; + ConfigChangeEvent event; event.section = section; event.field = key; + event.oldValue = m_rawValues[fullKey]; event.newValue = value; + m_rawValues[fullKey] = 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 { + std::lock_guard lock(m_mutex); + + std::string fullKey = section + "." + key; + auto it = m_rawValues.find(fullKey); + if (it != m_rawValues.end()) { + return it->second; + } return defaultValue; } -/** - * @brief 获取配置值(整数) - * @param section 配置节 - * @param key 配置键 - * @param defaultValue 默认值 - * @return 配置值 - */ int ConfigManager::getIntValue(const std::string& section, const std::string& key, int defaultValue) const { + std::string value = getValue(section, key); + if (!value.empty()) { + try { + return std::stoi(value); + } catch (...) { + return defaultValue; + } + } return defaultValue; } -/** - * @brief 获取配置值(浮点数) - * @param section 配置节 - * @param key 配置键 - * @param defaultValue 默认值 - * @return 配置值 - */ float ConfigManager::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const { + std::string value = getValue(section, key); + if (!value.empty()) { + try { + return std::stof(value); + } catch (...) { + return defaultValue; + } + } return defaultValue; } -/** - * @brief 获取配置值(布尔值) - * @param section 配置节 - * @param key 配置键 - * @param defaultValue 默认值 - * @return 配置值 - */ bool ConfigManager::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const { + std::string value = getValue(section, key); + if (!value.empty()) { + if (value == "true" || value == "1") return true; + if (value == "false" || value == "0") return false; + } 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_rawValues.clear(); 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); @@ -468,10 +361,6 @@ void ConfigManager::setAutoSave(bool enabled, float interval) { enabled ? "enabled" : "disabled", m_autoSaveInterval); } -/** - * @brief 更新配置管理器(用于自动保存) - * @param deltaTime 帧时间(秒) - */ void ConfigManager::update(float deltaTime) { if (!m_autoSaveEnabled || !m_modified) { return; @@ -485,10 +374,6 @@ void ConfigManager::update(float deltaTime) { } } -/** - * @brief 通知所有变更回调 - * @param event 配置变更事件 - */ void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) { for (const auto& pair : m_changeCallbacks) { if (pair.second) { @@ -497,20 +382,4 @@ void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& 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/platform_config.cpp b/Extra2D/src/config/platform_config.cpp index 262078a..85cac6e 100644 --- a/Extra2D/src/config/platform_config.cpp +++ b/Extra2D/src/config/platform_config.cpp @@ -42,32 +42,6 @@ public: 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 { @@ -107,26 +81,6 @@ public: 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 { @@ -165,27 +119,6 @@ public: 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 { @@ -196,7 +129,6 @@ private: PlatformCapabilities caps_; }; -#ifdef __SWITCH__ class SwitchPlatformConfig : public PlatformConfig { public: SwitchPlatformConfig() { @@ -225,37 +157,6 @@ public: 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 { @@ -266,85 +167,9 @@ public: 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 - -} // namespace - -/** - * @brief 创建平台配置实例 - * 根据 PlatformType 创建对应的平台配置对象 - * @param type 平台类型,默认为 Auto(自动检测) - * @return 平台配置的智能指针 - */ UniquePtr createPlatformConfig(PlatformType type) { if (type == PlatformType::Auto) { #ifdef _WIN32 @@ -379,12 +204,6 @@ UniquePtr createPlatformConfig(PlatformType type) { } } -/** - * @brief 获取平台类型名称 - * 将 PlatformType 枚举转换为可读的字符串 - * @param type 平台类型枚举值 - * @return 平台名称字符串 - */ const char *getPlatformTypeName(PlatformType type) { switch (type) { case PlatformType::Auto: @@ -402,4 +221,4 @@ const char *getPlatformTypeName(PlatformType type) { } } -} // namespace extra2d +} diff --git a/Extra2D/src/config/platform_detector.cpp b/Extra2D/src/config/platform_detector.cpp index dc6b7c3..32d1ddb 100644 --- a/Extra2D/src/config/platform_detector.cpp +++ b/Extra2D/src/config/platform_detector.cpp @@ -657,39 +657,21 @@ PlatformCapabilities PlatformDetector::getSwitchCapabilities() { 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/debug/debug_config.cpp b/Extra2D/src/debug/debug_config.cpp new file mode 100644 index 0000000..8345832 --- /dev/null +++ b/Extra2D/src/debug/debug_config.cpp @@ -0,0 +1,23 @@ +#include +#include + +namespace extra2d { + +bool DebugConfigData::hasDebugFlag(const std::string& flag) const { + return std::find(debugFlags.begin(), debugFlags.end(), flag) != debugFlags.end(); +} + +void DebugConfigData::addDebugFlag(const std::string& flag) { + if (!hasDebugFlag(flag)) { + debugFlags.push_back(flag); + } +} + +void DebugConfigData::removeDebugFlag(const std::string& flag) { + auto it = std::find(debugFlags.begin(), debugFlags.end(), flag); + if (it != debugFlags.end()) { + debugFlags.erase(it); + } +} + +} diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp index 8699f49..6a73715 100644 --- a/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp +++ b/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp @@ -18,55 +18,227 @@ SDL2Input::~SDL2Input() { } void SDL2Input::init() { - SDL_GameControllerEventState(SDL_ENABLE); + E2D_LOG_INFO("SDL2Input initialized"); + + if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) { + E2D_LOG_WARN("Failed to init gamecontroller subsystem: {}", SDL_GetError()); + } + openGamepad(); - E2D_LOG_DEBUG("SDL2 input initialized"); } void SDL2Input::shutdown() { closeGamepad(); + E2D_LOG_INFO("SDL2Input shutdown"); } void SDL2Input::update() { keyPrevious_ = keyCurrent_; mousePrevious_ = mouseCurrent_; gamepadPrevious_ = gamepadCurrent_; + scrollDelta_ = 0.0f; - mouseDelta_ = Vec2::Zero(); - - updateKeyboard(); - updateMouse(); + mouseDelta_ = Vec2{0.0f, 0.0f}; + updateGamepad(); } +void SDL2Input::setEventCallback(EventCallback callback) { + eventCallback_ = std::move(callback); +} + +void SDL2Input::handleSDLEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_KEYDOWN: { + int key = event.key.keysym.scancode; + if (key >= 0 && key < static_cast(Key::Count)) { + if (!keyCurrent_[key]) { + keyCurrent_[key] = true; + + Event e = Event::createKeyPress( + event.key.keysym.sym, + event.key.keysym.scancode, + event.key.keysym.mod + ); + dispatchEvent(e); + } + } + break; + } + + case SDL_KEYUP: { + int key = event.key.keysym.scancode; + if (key >= 0 && key < static_cast(Key::Count)) { + keyCurrent_[key] = false; + + Event e = Event::createKeyRelease( + event.key.keysym.sym, + event.key.keysym.scancode, + event.key.keysym.mod + ); + dispatchEvent(e); + } + break; + } + + case SDL_MOUSEBUTTONDOWN: { + int btn = event.button.button - 1; + if (btn >= 0 && btn < static_cast(Mouse::Count)) { + mouseCurrent_[btn] = true; + + Vec2 pos{static_cast(event.button.x), + static_cast(event.button.y)}; + Event e = Event::createMouseButtonPress(btn, 0, pos); + dispatchEvent(e); + } + break; + } + + case SDL_MOUSEBUTTONUP: { + int btn = event.button.button - 1; + if (btn >= 0 && btn < static_cast(Mouse::Count)) { + mouseCurrent_[btn] = false; + + Vec2 pos{static_cast(event.button.x), + static_cast(event.button.y)}; + Event e = Event::createMouseButtonRelease(btn, 0, pos); + dispatchEvent(e); + } + break; + } + + case SDL_MOUSEMOTION: { + Vec2 newPos{static_cast(event.motion.x), + static_cast(event.motion.y)}; + Vec2 delta{static_cast(event.motion.xrel), + static_cast(event.motion.yrel)}; + + mouseDelta_ = mouseDelta_ + delta; + mousePos_ = newPos; + + Event e = Event::createMouseMove(newPos, delta); + dispatchEvent(e); + break; + } + + case SDL_MOUSEWHEEL: { + Vec2 offset{static_cast(event.wheel.x), + static_cast(event.wheel.y)}; + Vec2 pos = mousePos_; + + scroll_ += event.wheel.y; + scrollDelta_ += event.wheel.y; + + Event e = Event::createMouseScroll(offset, pos); + dispatchEvent(e); + break; + } + + case SDL_CONTROLLERDEVICEADDED: + E2D_LOG_INFO("Gamepad connected: index {}", event.cdevice.which); + openGamepad(); + break; + + case SDL_CONTROLLERDEVICEREMOVED: + if (gamepad_ && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad_))) { + E2D_LOG_INFO("Gamepad disconnected"); + closeGamepad(); + } + break; + + case SDL_CONTROLLERBUTTONDOWN: + if (gamepad_) { + int btn = event.cbutton.button; + if (btn >= 0 && btn < static_cast(Gamepad::Count)) { + gamepadCurrent_[btn] = true; + + GamepadButtonEvent btnEvent; + btnEvent.gamepadId = gamepadIndex_; + btnEvent.button = btn; + + Event e; + e.type = EventType::GamepadButtonPressed; + e.data = btnEvent; + dispatchEvent(e); + } + } + break; + + case SDL_CONTROLLERBUTTONUP: + if (gamepad_) { + int btn = event.cbutton.button; + if (btn >= 0 && btn < static_cast(Gamepad::Count)) { + gamepadCurrent_[btn] = false; + + GamepadButtonEvent btnEvent; + btnEvent.gamepadId = gamepadIndex_; + btnEvent.button = btn; + + Event e; + e.type = EventType::GamepadButtonReleased; + e.data = btnEvent; + dispatchEvent(e); + } + } + break; + + default: + break; + } +} + +void SDL2Input::dispatchEvent(const Event& event) { + if (eventCallback_) { + eventCallback_(event); + } +} + bool SDL2Input::down(Key key) const { size_t idx = static_cast(key); - return idx < keyCurrent_.size() ? keyCurrent_[idx] : false; + if (idx < keyCurrent_.size()) { + return keyCurrent_[idx]; + } + return false; } bool SDL2Input::pressed(Key key) const { size_t idx = static_cast(key); - return idx < keyCurrent_.size() ? (keyCurrent_[idx] && !keyPrevious_[idx]) : false; + if (idx < keyCurrent_.size()) { + return keyCurrent_[idx] && !keyPrevious_[idx]; + } + return false; } bool SDL2Input::released(Key key) const { size_t idx = static_cast(key); - return idx < keyCurrent_.size() ? (!keyCurrent_[idx] && keyPrevious_[idx]) : false; + if (idx < keyCurrent_.size()) { + return !keyCurrent_[idx] && keyPrevious_[idx]; + } + return false; } bool SDL2Input::down(Mouse btn) const { size_t idx = static_cast(btn); - return idx < mouseCurrent_.size() ? mouseCurrent_[idx] : false; + if (idx < mouseCurrent_.size()) { + return mouseCurrent_[idx]; + } + return false; } bool SDL2Input::pressed(Mouse btn) const { size_t idx = static_cast(btn); - return idx < mouseCurrent_.size() ? (mouseCurrent_[idx] && !mousePrevious_[idx]) : false; + if (idx < mouseCurrent_.size()) { + return mouseCurrent_[idx] && !mousePrevious_[idx]; + } + return false; } bool SDL2Input::released(Mouse btn) const { size_t idx = static_cast(btn); - return idx < mouseCurrent_.size() ? (!mouseCurrent_[idx] && mousePrevious_[idx]) : false; + if (idx < mouseCurrent_.size()) { + return !mouseCurrent_[idx] && mousePrevious_[idx]; + } + return false; } Vec2 SDL2Input::mouse() const { @@ -95,17 +267,26 @@ bool SDL2Input::gamepad() const { bool SDL2Input::down(Gamepad btn) const { size_t idx = static_cast(btn); - return idx < gamepadCurrent_.size() ? gamepadCurrent_[idx] : false; + if (idx < gamepadCurrent_.size()) { + return gamepadCurrent_[idx]; + } + return false; } bool SDL2Input::pressed(Gamepad btn) const { size_t idx = static_cast(btn); - return idx < gamepadCurrent_.size() ? (gamepadCurrent_[idx] && !gamepadPrevious_[idx]) : false; + if (idx < gamepadCurrent_.size()) { + return gamepadCurrent_[idx] && !gamepadPrevious_[idx]; + } + return false; } bool SDL2Input::released(Gamepad btn) const { size_t idx = static_cast(btn); - return idx < gamepadCurrent_.size() ? (!gamepadCurrent_[idx] && gamepadPrevious_[idx]) : false; + if (idx < gamepadCurrent_.size()) { + return !gamepadCurrent_[idx] && gamepadPrevious_[idx]; + } + return false; } Vec2 SDL2Input::leftStick() const { @@ -126,9 +307,9 @@ float SDL2Input::rightTrigger() const { void SDL2Input::vibrate(float left, float right) { if (gamepad_) { - Uint16 lowFreq = static_cast(std::clamp(left, 0.0f, 1.0f) * 65535); - Uint16 highFreq = static_cast(std::clamp(right, 0.0f, 1.0f) * 65535); - SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 0); + Uint16 lowFreq = static_cast(left * 65535.0f); + Uint16 highFreq = static_cast(right * 65535.0f); + SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 500); } } @@ -142,7 +323,7 @@ int SDL2Input::touchCount() const { Vec2 SDL2Input::touch(int index) const { (void)index; - return Vec2::Zero(); + return Vec2{0.0f, 0.0f}; } TouchPoint SDL2Input::touchPoint(int index) const { @@ -151,160 +332,53 @@ TouchPoint SDL2Input::touchPoint(int index) const { } void SDL2Input::updateKeyboard() { - int numKeys = 0; - const Uint8* state = SDL_GetKeyboardState(&numKeys); - - auto updateKey = [&](Key key, int sdlScancode) { - size_t idx = static_cast(key); - if (idx < keyCurrent_.size() && sdlScancode < numKeys) { - keyCurrent_[idx] = state[sdlScancode] != 0; - } - }; - - updateKey(Key::A, SDL_SCANCODE_A); - updateKey(Key::B, SDL_SCANCODE_B); - updateKey(Key::C, SDL_SCANCODE_C); - updateKey(Key::D, SDL_SCANCODE_D); - updateKey(Key::E, SDL_SCANCODE_E); - updateKey(Key::F, SDL_SCANCODE_F); - updateKey(Key::G, SDL_SCANCODE_G); - updateKey(Key::H, SDL_SCANCODE_H); - updateKey(Key::I, SDL_SCANCODE_I); - updateKey(Key::J, SDL_SCANCODE_J); - updateKey(Key::K, SDL_SCANCODE_K); - updateKey(Key::L, SDL_SCANCODE_L); - updateKey(Key::M, SDL_SCANCODE_M); - updateKey(Key::N, SDL_SCANCODE_N); - updateKey(Key::O, SDL_SCANCODE_O); - updateKey(Key::P, SDL_SCANCODE_P); - updateKey(Key::Q, SDL_SCANCODE_Q); - updateKey(Key::R, SDL_SCANCODE_R); - updateKey(Key::S, SDL_SCANCODE_S); - updateKey(Key::T, SDL_SCANCODE_T); - updateKey(Key::U, SDL_SCANCODE_U); - updateKey(Key::V, SDL_SCANCODE_V); - updateKey(Key::W, SDL_SCANCODE_W); - updateKey(Key::X, SDL_SCANCODE_X); - updateKey(Key::Y, SDL_SCANCODE_Y); - updateKey(Key::Z, SDL_SCANCODE_Z); - updateKey(Key::Num0, SDL_SCANCODE_0); - updateKey(Key::Num1, SDL_SCANCODE_1); - updateKey(Key::Num2, SDL_SCANCODE_2); - updateKey(Key::Num3, SDL_SCANCODE_3); - updateKey(Key::Num4, SDL_SCANCODE_4); - updateKey(Key::Num5, SDL_SCANCODE_5); - updateKey(Key::Num6, SDL_SCANCODE_6); - updateKey(Key::Num7, SDL_SCANCODE_7); - updateKey(Key::Num8, SDL_SCANCODE_8); - updateKey(Key::Num9, SDL_SCANCODE_9); - updateKey(Key::F1, SDL_SCANCODE_F1); - updateKey(Key::F2, SDL_SCANCODE_F2); - updateKey(Key::F3, SDL_SCANCODE_F3); - updateKey(Key::F4, SDL_SCANCODE_F4); - updateKey(Key::F5, SDL_SCANCODE_F5); - updateKey(Key::F6, SDL_SCANCODE_F6); - updateKey(Key::F7, SDL_SCANCODE_F7); - updateKey(Key::F8, SDL_SCANCODE_F8); - updateKey(Key::F9, SDL_SCANCODE_F9); - updateKey(Key::F10, SDL_SCANCODE_F10); - updateKey(Key::F11, SDL_SCANCODE_F11); - updateKey(Key::F12, SDL_SCANCODE_F12); - updateKey(Key::Space, SDL_SCANCODE_SPACE); - updateKey(Key::Enter, SDL_SCANCODE_RETURN); - updateKey(Key::Escape, SDL_SCANCODE_ESCAPE); - updateKey(Key::Tab, SDL_SCANCODE_TAB); - updateKey(Key::Backspace, SDL_SCANCODE_BACKSPACE); - updateKey(Key::Insert, SDL_SCANCODE_INSERT); - updateKey(Key::Delete, SDL_SCANCODE_DELETE); - updateKey(Key::Home, SDL_SCANCODE_HOME); - updateKey(Key::End, SDL_SCANCODE_END); - updateKey(Key::PageUp, SDL_SCANCODE_PAGEUP); - updateKey(Key::PageDown, SDL_SCANCODE_PAGEDOWN); - updateKey(Key::Up, SDL_SCANCODE_UP); - updateKey(Key::Down, SDL_SCANCODE_DOWN); - updateKey(Key::Left, SDL_SCANCODE_LEFT); - updateKey(Key::Right, SDL_SCANCODE_RIGHT); - updateKey(Key::LShift, SDL_SCANCODE_LSHIFT); - updateKey(Key::RShift, SDL_SCANCODE_RSHIFT); - updateKey(Key::LCtrl, SDL_SCANCODE_LCTRL); - updateKey(Key::RCtrl, SDL_SCANCODE_RCTRL); - updateKey(Key::LAlt, SDL_SCANCODE_LALT); - updateKey(Key::RAlt, SDL_SCANCODE_RALT); - updateKey(Key::CapsLock, SDL_SCANCODE_CAPSLOCK); - updateKey(Key::NumLock, SDL_SCANCODE_NUMLOCKCLEAR); - updateKey(Key::ScrollLock, SDL_SCANCODE_SCROLLLOCK); } void SDL2Input::updateMouse() { - int x, y; - Uint32 state = SDL_GetMouseState(&x, &y); - - Vec2 newPos(static_cast(x), static_cast(y)); - mouseDelta_ = newPos - mousePos_; - mousePos_ = newPos; - - mouseCurrent_[static_cast(Mouse::Left)] = (state & SDL_BUTTON_LMASK) != 0; - mouseCurrent_[static_cast(Mouse::Right)] = (state & SDL_BUTTON_RMASK) != 0; - mouseCurrent_[static_cast(Mouse::Middle)] = (state & SDL_BUTTON_MMASK) != 0; - mouseCurrent_[static_cast(Mouse::X1)] = (state & SDL_BUTTON_X1MASK) != 0; - mouseCurrent_[static_cast(Mouse::X2)] = (state & SDL_BUTTON_X2MASK) != 0; + int x = 0, y = 0; + SDL_GetMouseState(&x, &y); + mousePos_ = Vec2{static_cast(x), static_cast(y)}; } void SDL2Input::updateGamepad() { if (!gamepad_) { - openGamepad(); - if (!gamepad_) return; + return; } - + auto applyDeadzone = [this](float value) -> float { - if (std::abs(value) < deadzone_) return 0.0f; - float sign = value >= 0 ? 1.0f : -1.0f; + if (std::abs(value) < deadzone_) { + return 0.0f; + } + float sign = value >= 0.0f ? 1.0f : -1.0f; return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_); }; - - auto getAxis = [this](SDL_GameControllerAxis axis) -> float { - if (!gamepad_) return 0.0f; - Sint16 value = SDL_GameControllerGetAxis(gamepad_, axis); - return static_cast(value) / 32767.0f; - }; - - auto getButton = [this](SDL_GameControllerButton btn) -> bool { - return gamepad_ ? SDL_GameControllerGetButton(gamepad_, btn) != 0 : false; - }; - - leftStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTX)); - leftStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTY)); - rightStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTX)); - rightStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTY)); - - leftTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERLEFT); - rightTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT); - - gamepadCurrent_[static_cast(Gamepad::A)] = getButton(SDL_CONTROLLER_BUTTON_A); - gamepadCurrent_[static_cast(Gamepad::B)] = getButton(SDL_CONTROLLER_BUTTON_B); - gamepadCurrent_[static_cast(Gamepad::X)] = getButton(SDL_CONTROLLER_BUTTON_X); - gamepadCurrent_[static_cast(Gamepad::Y)] = getButton(SDL_CONTROLLER_BUTTON_Y); - gamepadCurrent_[static_cast(Gamepad::LB)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER); - gamepadCurrent_[static_cast(Gamepad::RB)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); - gamepadCurrent_[static_cast(Gamepad::Back)] = getButton(SDL_CONTROLLER_BUTTON_BACK); - gamepadCurrent_[static_cast(Gamepad::Start)] = getButton(SDL_CONTROLLER_BUTTON_START); - gamepadCurrent_[static_cast(Gamepad::Guide)] = getButton(SDL_CONTROLLER_BUTTON_GUIDE); - gamepadCurrent_[static_cast(Gamepad::LStick)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSTICK); - gamepadCurrent_[static_cast(Gamepad::RStick)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK); - gamepadCurrent_[static_cast(Gamepad::DUp)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_UP); - gamepadCurrent_[static_cast(Gamepad::DDown)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN); - gamepadCurrent_[static_cast(Gamepad::DLeft)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT); - gamepadCurrent_[static_cast(Gamepad::DRight)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + + int lx = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_LEFTX); + int ly = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_LEFTY); + int rx = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_RIGHTX); + int ry = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_RIGHTY); + + leftStick_.x = applyDeadzone(lx / 32767.0f); + leftStick_.y = applyDeadzone(ly / 32767.0f); + rightStick_.x = applyDeadzone(rx / 32767.0f); + rightStick_.y = applyDeadzone(ry / 32767.0f); + + int lt = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + int rt = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + + leftTrigger_ = lt / 32767.0f; + rightTrigger_ = rt / 32767.0f; } void SDL2Input::openGamepad() { - for (int i = 0; i < SDL_NumJoysticks(); ++i) { + int numJoysticks = SDL_NumJoysticks(); + for (int i = 0; i < numJoysticks; ++i) { if (SDL_IsGameController(i)) { gamepad_ = SDL_GameControllerOpen(i); if (gamepad_) { gamepadIndex_ = i; - E2D_LOG_INFO("Gamepad connected: {}", SDL_GameControllerName(gamepad_)); - break; + E2D_LOG_INFO("Gamepad opened: {}", SDL_GameControllerName(gamepad_)); + return; } } } @@ -315,47 +389,20 @@ void SDL2Input::closeGamepad() { SDL_GameControllerClose(gamepad_); gamepad_ = nullptr; gamepadIndex_ = -1; - E2D_LOG_INFO("Gamepad disconnected"); + gamepadCurrent_.fill(false); + gamepadPrevious_.fill(false); } } int SDL2Input::keyToSDL(Key key) { - switch (key) { - case Key::A: return SDL_SCANCODE_A; - case Key::B: return SDL_SCANCODE_B; - case Key::C: return SDL_SCANCODE_C; - case Key::D: return SDL_SCANCODE_D; - case Key::E: return SDL_SCANCODE_E; - case Key::F: return SDL_SCANCODE_F; - case Key::G: return SDL_SCANCODE_G; - case Key::H: return SDL_SCANCODE_H; - case Key::I: return SDL_SCANCODE_I; - case Key::J: return SDL_SCANCODE_J; - case Key::K: return SDL_SCANCODE_K; - case Key::L: return SDL_SCANCODE_L; - case Key::M: return SDL_SCANCODE_M; - case Key::N: return SDL_SCANCODE_N; - case Key::O: return SDL_SCANCODE_O; - case Key::P: return SDL_SCANCODE_P; - case Key::Q: return SDL_SCANCODE_Q; - case Key::R: return SDL_SCANCODE_R; - case Key::S: return SDL_SCANCODE_S; - case Key::T: return SDL_SCANCODE_T; - case Key::U: return SDL_SCANCODE_U; - case Key::V: return SDL_SCANCODE_V; - case Key::W: return SDL_SCANCODE_W; - case Key::X: return SDL_SCANCODE_X; - case Key::Y: return SDL_SCANCODE_Y; - case Key::Z: return SDL_SCANCODE_Z; - default: return SDL_SCANCODE_UNKNOWN; - } + return static_cast(key); } int SDL2Input::mouseToSDL(Mouse btn) { switch (btn) { case Mouse::Left: return SDL_BUTTON_LEFT; - case Mouse::Right: return SDL_BUTTON_RIGHT; case Mouse::Middle: return SDL_BUTTON_MIDDLE; + case Mouse::Right: return SDL_BUTTON_RIGHT; case Mouse::X1: return SDL_BUTTON_X1; case Mouse::X2: return SDL_BUTTON_X2; default: return 0; @@ -368,19 +415,37 @@ int SDL2Input::gamepadToSDL(Gamepad btn) { case Gamepad::B: return SDL_CONTROLLER_BUTTON_B; case Gamepad::X: return SDL_CONTROLLER_BUTTON_X; case Gamepad::Y: return SDL_CONTROLLER_BUTTON_Y; - case Gamepad::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - case Gamepad::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; case Gamepad::Back: return SDL_CONTROLLER_BUTTON_BACK; case Gamepad::Start: return SDL_CONTROLLER_BUTTON_START; - case Gamepad::Guide: return SDL_CONTROLLER_BUTTON_GUIDE; case Gamepad::LStick: return SDL_CONTROLLER_BUTTON_LEFTSTICK; case Gamepad::RStick: return SDL_CONTROLLER_BUTTON_RIGHTSTICK; + case Gamepad::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + case Gamepad::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; case Gamepad::DUp: return SDL_CONTROLLER_BUTTON_DPAD_UP; case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN; case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT; case Gamepad::DRight: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT; - default: return SDL_CONTROLLER_BUTTON_INVALID; + case Gamepad::Guide: return SDL_CONTROLLER_BUTTON_GUIDE; + default: return 0; } } -} // namespace extra2d +Key SDL2Input::sdlToKey(int sdlKey) { + if (sdlKey >= 0 && sdlKey < static_cast(Key::Count)) { + return static_cast(sdlKey); + } + return Key::None; +} + +Mouse SDL2Input::sdlToMouse(int sdlButton) { + switch (sdlButton) { + case SDL_BUTTON_LEFT: return Mouse::Left; + case SDL_BUTTON_MIDDLE: return Mouse::Middle; + case SDL_BUTTON_RIGHT: return Mouse::Right; + case SDL_BUTTON_X1: return Mouse::X1; + case SDL_BUTTON_X2: return Mouse::X2; + default: return Mouse::Count; + } +} + +} diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_input.h b/Extra2D/src/platform/backends/sdl2/sdl2_input.h index 6c7a617..df52122 100644 --- a/Extra2D/src/platform/backends/sdl2/sdl2_input.h +++ b/Extra2D/src/platform/backends/sdl2/sdl2_input.h @@ -1,8 +1,10 @@ #pragma once #include +#include #include #include +#include namespace extra2d { @@ -11,6 +13,8 @@ namespace extra2d { */ class SDL2Input : public IInput { public: + using EventCallback = std::function; + SDL2Input(); ~SDL2Input() override; @@ -46,6 +50,18 @@ public: Vec2 touch(int index) const override; TouchPoint touchPoint(int index) const override; + /** + * @brief 设置事件回调 + * @param callback 事件回调函数 + */ + void setEventCallback(EventCallback callback); + + /** + * @brief 处理 SDL 事件 + * @param event SDL 事件 + */ + void handleSDLEvent(const SDL_Event& event); + private: void updateKeyboard(); void updateMouse(); @@ -56,6 +72,10 @@ private: static int keyToSDL(Key key); static int mouseToSDL(Mouse btn); static int gamepadToSDL(Gamepad btn); + static Key sdlToKey(int sdlKey); + static Mouse sdlToMouse(int sdlButton); + + void dispatchEvent(const Event& event); std::array(Key::Count)> keyCurrent_{}; std::array(Key::Count)> keyPrevious_{}; @@ -77,6 +97,8 @@ private: float leftTrigger_ = 0.0f; float rightTrigger_ = 0.0f; float deadzone_ = 0.15f; + + EventCallback eventCallback_; }; -} // namespace extra2d +} diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp index 7fae4f3..123c007 100644 --- a/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp +++ b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp @@ -21,6 +21,10 @@ bool SDL2Window::create(const WindowConfigData& cfg) { } Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; + +#ifdef __SWITCH__ + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; +#else if (cfg.isFullscreen()) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else if (cfg.isBorderless()) { @@ -32,10 +36,12 @@ bool SDL2Window::create(const WindowConfigData& cfg) { if (!cfg.decorated) { flags |= SDL_WINDOW_BORDERLESS; } +#endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); @@ -75,7 +81,7 @@ bool SDL2Window::create(const WindowConfigData& cfg) { } if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) { - E2D_LOG_ERROR("Failed to initialize GLAD"); + E2D_LOG_ERROR("Failed to initialize GLAD GLES2"); SDL_GL_DeleteContext(glContext_); glContext_ = nullptr; SDL_DestroyWindow(sdlWindow_); @@ -87,16 +93,19 @@ bool SDL2Window::create(const WindowConfigData& cfg) { SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0); SDL_GetWindowSize(sdlWindow_, &width_, &height_); - fullscreen_ = cfg.isFullscreen(); + fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; vsync_ = cfg.vsync; +#ifndef __SWITCH__ initCursors(); +#endif updateContentScale(); input_ = makeUnique(); input_->init(); E2D_LOG_INFO("SDL2 window created: {}x{}", width_, height_); + E2D_LOG_INFO(" Platform: OpenGL ES 3.2"); return true; } @@ -106,7 +115,9 @@ void SDL2Window::destroy() { input_.reset(); } +#ifndef __SWITCH__ deinitCursors(); +#endif if (glContext_) { SDL_GL_DeleteContext(glContext_); @@ -163,16 +174,25 @@ void SDL2Window::setSize(int w, int h) { } void SDL2Window::setPos(int x, int y) { +#ifndef __SWITCH__ if (sdlWindow_) { SDL_SetWindowPosition(sdlWindow_, x, y); } +#else + (void)x; + (void)y; +#endif } void SDL2Window::setFullscreen(bool fs) { +#ifndef __SWITCH__ if (sdlWindow_) { SDL_SetWindowFullscreen(sdlWindow_, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); fullscreen_ = fs; } +#else + (void)fs; +#endif } void SDL2Window::setVSync(bool vsync) { @@ -183,6 +203,7 @@ void SDL2Window::setVSync(bool vsync) { } void SDL2Window::setVisible(bool visible) { +#ifndef __SWITCH__ if (sdlWindow_) { if (visible) { SDL_ShowWindow(sdlWindow_); @@ -190,6 +211,9 @@ void SDL2Window::setVisible(bool visible) { SDL_HideWindow(sdlWindow_); } } +#else + (void)visible; +#endif } int SDL2Window::width() const { @@ -205,12 +229,12 @@ Size SDL2Window::size() const { } Vec2 SDL2Window::pos() const { - int x, y; + int x = 0, y = 0; +#ifndef __SWITCH__ if (sdlWindow_) { SDL_GetWindowPosition(sdlWindow_, &x, &y); - } else { - x = y = 0; } +#endif return Vec2(static_cast(x), static_cast(y)); } @@ -239,6 +263,7 @@ float SDL2Window::scaleY() const { } void SDL2Window::setCursor(Cursor cursor) { +#ifndef __SWITCH__ if (cursor == Cursor::Hidden) { SDL_ShowCursor(SDL_DISABLE); return; @@ -251,18 +276,29 @@ void SDL2Window::setCursor(Cursor cursor) { SDL_SetCursor(sdlCursors_[idx]); currentCursor_ = idx; } +#else + (void)cursor; +#endif } void SDL2Window::showCursor(bool show) { +#ifndef __SWITCH__ SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE); cursorVisible_ = show; +#else + (void)show; +#endif } void SDL2Window::lockCursor(bool lock) { +#ifndef __SWITCH__ if (sdlWindow_) { SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE); cursorLocked_ = lock; } +#else + (void)lock; +#endif } IInput* SDL2Window::input() const { @@ -288,7 +324,8 @@ void* SDL2Window::native() const { bool SDL2Window::initSDL() { static int sdlInitCount = 0; if (sdlInitCount == 0) { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) { + Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER; + if (SDL_Init(initFlags) != 0) { E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError()); return false; } @@ -305,6 +342,7 @@ void SDL2Window::deinitSDL() { } } +#ifndef __SWITCH__ void SDL2Window::initCursors() { sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); @@ -323,6 +361,7 @@ void SDL2Window::deinitCursors() { } } } +#endif void SDL2Window::updateContentScale() { if (sdlWindow_) { @@ -335,6 +374,10 @@ void SDL2Window::updateContentScale() { } void SDL2Window::handleEvent(const SDL_Event& event) { + if (input_) { + input_->handleSDLEvent(event); + } + switch (event.type) { case SDL_QUIT: shouldClose_ = true; @@ -378,4 +421,4 @@ void SDL2Window::handleEvent(const SDL_Event& event) { } } -} // namespace extra2d +} diff --git a/Extra2D/src/platform/input_module.cpp b/Extra2D/src/platform/input_module.cpp index c1c19dd..9888efc 100644 --- a/Extra2D/src/platform/input_module.cpp +++ b/Extra2D/src/platform/input_module.cpp @@ -1,270 +1,196 @@ #include #include -#include +#include +#include #include - #include using json = nlohmann::json; namespace extra2d { -// ============================================================================ -// InputModuleConfig 实现 -// ============================================================================ +static ModuleId s_inputModuleId = INVALID_MODULE_ID; + +ModuleId get_input_module_id() { + return s_inputModuleId; +} -/** - * @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; + return inputConfig.isDeadzoneValid(); } -/** - * @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; - } +#ifdef __SWITCH__ + (void)platform; + inputConfig.enableVibration = true; + inputConfig.maxGamepads = 2; +#else + (void)platform; +#endif } -/** - * @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; - } + if (!jsonData) return false; try { - const json& obj = *static_cast(jsonData); + const json& j = *static_cast(jsonData); - if (!obj.is_object()) { - E2D_LOG_ERROR("InputModuleConfig: JSON 数据不是对象类型"); - return false; + if (j.contains("enabled")) { + inputConfig.enabled = j["enabled"].get(); + } + if (j.contains("rawMouseInput")) { + inputConfig.rawMouseInput = j["rawMouseInput"].get(); + } + if (j.contains("mouseSensitivity")) { + inputConfig.mouseSensitivity = j["mouseSensitivity"].get(); + } + if (j.contains("invertMouseY")) { + inputConfig.invertMouseY = j["invertMouseY"].get(); + } + if (j.contains("invertMouseX")) { + inputConfig.invertMouseX = j["invertMouseX"].get(); + } + if (j.contains("deadzone")) { + inputConfig.deadzone = j["deadzone"].get(); + } + if (j.contains("triggerThreshold")) { + inputConfig.triggerThreshold = j["triggerThreshold"].get(); + } + if (j.contains("enableVibration")) { + inputConfig.enableVibration = j["enableVibration"].get(); + } + if (j.contains("maxGamepads")) { + inputConfig.maxGamepads = j["maxGamepads"].get(); + } + if (j.contains("autoConnectGamepads")) { + inputConfig.autoConnectGamepads = j["autoConnectGamepads"].get(); + } + if (j.contains("gamepadMappingFile")) { + inputConfig.gamepadMappingFile = j["gamepadMappingFile"].get(); } - 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()); + } catch (...) { return false; } } -/** - * @brief 保存配置到 JSON 数据 - * @param jsonData JSON 数据指针 - * @return 保存成功返回 true - */ bool InputModuleConfig::saveToJson(void* jsonData) const { - if (!jsonData) { - E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空"); - return false; - } + if (!jsonData) return false; try { - json& obj = *static_cast(jsonData); + json& j = *static_cast(jsonData); - obj["enableKeyboard"] = enableKeyboard; - obj["enableMouse"] = enableMouse; - obj["enableGamepad"] = enableGamepad; - obj["enableTouch"] = enableTouch; - obj["deadzone"] = deadzone; - obj["mouseSensitivity"] = mouseSensitivity; + j["enabled"] = inputConfig.enabled; + j["rawMouseInput"] = inputConfig.rawMouseInput; + j["mouseSensitivity"] = inputConfig.mouseSensitivity; + j["invertMouseY"] = inputConfig.invertMouseY; + j["invertMouseX"] = inputConfig.invertMouseX; + j["deadzone"] = inputConfig.deadzone; + j["triggerThreshold"] = inputConfig.triggerThreshold; + j["enableVibration"] = inputConfig.enableVibration; + j["maxGamepads"] = inputConfig.maxGamepads; + j["autoConnectGamepads"] = inputConfig.autoConnectGamepads; + j["gamepadMappingFile"] = inputConfig.gamepadMappingFile; - E2D_LOG_INFO("InputModuleConfig: 保存配置到 JSON 成功"); return true; - - } catch (const json::exception& e) { - E2D_LOG_ERROR("InputModuleConfig: JSON 序列化错误: {}", e.what()); + } catch (...) { 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; + return { get_window_module_id() }; } -/** - * @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; - } + if (initialized_) return true; const InputModuleConfig* inputConfig = dynamic_cast(config); if (!inputConfig) { - E2D_LOG_ERROR("InputModuleInitializer: 配置类型不正确"); + E2D_LOG_ERROR("Invalid input module config"); return false; } - if (!inputConfig->validate()) { - E2D_LOG_ERROR("InputModuleInitializer: 配置验证失败"); + config_ = inputConfig->inputConfig; + + auto& registry = ModuleRegistry::instance(); + auto* windowInitializer = registry.getInitializer(get_window_module_id()); + if (!windowInitializer) { + E2D_LOG_ERROR("Window module not found - Input module depends on it"); return false; } - ModuleInfo info = config->getModuleInfo(); - moduleId_ = info.id; + auto* windowModule = static_cast(windowInitializer); + IWindow* window = windowModule->getWindow(); + if (!window) { + E2D_LOG_ERROR("Window not created - cannot get input interface"); + return false; + } - E2D_LOG_INFO("InputModuleInitializer: 正在初始化输入模块 '{}' (版本: {})", - info.name, info.version); + input_ = window->input(); + if (!input_) { + E2D_LOG_ERROR("Input interface not available from window"); + return false; + } - 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; + E2D_LOG_INFO("Input module initialized"); + E2D_LOG_INFO(" Deadzone: {}", config_.deadzone); + E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity); + E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled"); + return true; } -/** - * @brief 关闭模块 - */ void InputModuleInitializer::shutdown() { - if (!initialized_) { - E2D_LOG_WARN("InputModuleInitializer: 模块未初始化,无需关闭"); - return; - } + if (!initialized_) return; - E2D_LOG_INFO("InputModuleInitializer: 正在关闭输入模块"); + E2D_LOG_INFO("Input module shutting down"); input_ = nullptr; - moduleId_ = INVALID_MODULE_ID; initialized_ = false; - - E2D_LOG_INFO("InputModuleInitializer: 输入模块已关闭"); } -} // namespace extra2d +void InputModuleInitializer::update() { + if (!initialized_ || !input_) return; + + input_->update(); +} + +void register_input_module() { + if (s_inputModuleId != INVALID_MODULE_ID) return; + + s_inputModuleId = ModuleRegistry::instance().registerModule( + makeUnique(), + []() -> UniquePtr { + auto initializer = makeUnique(); + initializer->setModuleId(s_inputModuleId); + return initializer; + } + ); +} + +namespace { + struct InputModuleAutoRegister { + InputModuleAutoRegister() { + register_input_module(); + } + }; + + static InputModuleAutoRegister s_autoRegister; +} + +} diff --git a/Extra2D/src/platform/platform_init_module.cpp b/Extra2D/src/platform/platform_init_module.cpp index 5e9f523..08efe8d 100644 --- a/Extra2D/src/platform/platform_init_module.cpp +++ b/Extra2D/src/platform/platform_init_module.cpp @@ -93,9 +93,6 @@ bool PlatformModuleInitializer::initialize(const IModuleConfig* config) { return false; } - auto& appConfig = ConfigManager::instance().appConfig(); - appConfig.applyPlatformConstraints(*platformConfig_); - if (resolvedPlatform_ == PlatformType::Switch) { if (!initSwitch()) { return false; diff --git a/Extra2D/src/platform/window_module.cpp b/Extra2D/src/platform/window_module.cpp index 2e5779e..82b3400 100644 --- a/Extra2D/src/platform/window_module.cpp +++ b/Extra2D/src/platform/window_module.cpp @@ -1,17 +1,9 @@ #include #include -#include #include #include #include - -#ifdef E2D_BACKEND_SDL2 #include -#endif - -#ifdef E2D_BACKEND_GLFW -#include -#endif #ifdef __SWITCH__ #include @@ -27,16 +19,25 @@ ModuleId get_window_module_id() { return s_windowModuleId; } +void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) { +#ifdef __SWITCH__ + (void)platform; + windowConfig.mode = WindowMode::Fullscreen; + windowConfig.resizable = false; + windowConfig.highDPI = false; + windowConfig.width = 1920; + windowConfig.height = 1080; +#else + (void)platform; +#endif +} + bool WindowModuleConfig::loadFromJson(const void* jsonData) { if (!jsonData) return false; try { const json& j = *static_cast(jsonData); - if (j.contains("backend")) { - backend = j["backend"].get(); - } - if (j.contains("title")) { windowConfig.title = j["title"].get(); } @@ -46,15 +47,37 @@ bool WindowModuleConfig::loadFromJson(const void* jsonData) { if (j.contains("height")) { windowConfig.height = j["height"].get(); } + if (j.contains("minWidth")) { + windowConfig.minWidth = j["minWidth"].get(); + } + if (j.contains("minHeight")) { + windowConfig.minHeight = j["minHeight"].get(); + } if (j.contains("fullscreen")) { windowConfig.mode = j["fullscreen"].get() ? WindowMode::Fullscreen : WindowMode::Windowed; } + if (j.contains("mode")) { + std::string modeStr = j["mode"].get(); + if (modeStr == "fullscreen") { + windowConfig.mode = WindowMode::Fullscreen; + } else if (modeStr == "borderless") { + windowConfig.mode = WindowMode::Borderless; + } else { + windowConfig.mode = WindowMode::Windowed; + } + } if (j.contains("vsync")) { windowConfig.vsync = j["vsync"].get(); } if (j.contains("resizable")) { windowConfig.resizable = j["resizable"].get(); } + if (j.contains("highDPI")) { + windowConfig.highDPI = j["highDPI"].get(); + } + if (j.contains("multisamples")) { + windowConfig.multisamples = j["multisamples"].get(); + } return true; } catch (...) { @@ -67,13 +90,28 @@ bool WindowModuleConfig::saveToJson(void* jsonData) const { try { json& j = *static_cast(jsonData); - j["backend"] = backend; j["title"] = windowConfig.title; j["width"] = windowConfig.width; j["height"] = windowConfig.height; - j["fullscreen"] = (windowConfig.mode == WindowMode::Fullscreen); + j["minWidth"] = windowConfig.minWidth; + j["minHeight"] = windowConfig.minHeight; + + switch (windowConfig.mode) { + case WindowMode::Fullscreen: + j["mode"] = "fullscreen"; + break; + case WindowMode::Borderless: + j["mode"] = "borderless"; + break; + default: + j["mode"] = "windowed"; + break; + } + j["vsync"] = windowConfig.vsync; j["resizable"] = windowConfig.resizable; + j["highDPI"] = windowConfig.highDPI; + j["multisamples"] = windowConfig.multisamples; return true; } catch (...) { return false; @@ -83,7 +121,7 @@ bool WindowModuleConfig::saveToJson(void* jsonData) const { WindowModuleInitializer::WindowModuleInitializer() : moduleId_(INVALID_MODULE_ID) , initialized_(false) - , backendInitialized_(false) { + , sdl2Initialized_(false) { } WindowModuleInitializer::~WindowModuleInitializer() { @@ -92,51 +130,29 @@ WindowModuleInitializer::~WindowModuleInitializer() { } } -bool WindowModuleInitializer::initBackend() { -#ifdef E2D_BACKEND_SDL2 - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) { +bool WindowModuleInitializer::initSDL2() { + Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER; + +#ifdef __SWITCH__ + initFlags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER; +#endif + + if (SDL_Init(initFlags) != 0) { E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError()); return false; } - E2D_LOG_INFO("SDL2 backend initialized"); - backendInitialized_ = true; + + sdl2Initialized_ = true; + E2D_LOG_INFO("SDL2 initialized successfully"); return true; -#endif - -#ifdef E2D_BACKEND_GLFW - if (!glfwInit()) { - E2D_LOG_ERROR("Failed to initialize GLFW"); - return false; - } - E2D_LOG_INFO("GLFW backend initialized"); - backendInitialized_ = true; - return true; -#endif - -#ifdef E2D_BACKEND_SWITCH - E2D_LOG_INFO("Switch backend (no init required)"); - backendInitialized_ = true; - return true; -#endif - - E2D_LOG_ERROR("No backend available"); - return false; } -void WindowModuleInitializer::shutdownBackend() { - if (!backendInitialized_) return; +void WindowModuleInitializer::shutdownSDL2() { + if (!sdl2Initialized_) return; -#ifdef E2D_BACKEND_SDL2 SDL_Quit(); - E2D_LOG_INFO("SDL2 backend shutdown"); -#endif - -#ifdef E2D_BACKEND_GLFW - glfwTerminate(); - E2D_LOG_INFO("GLFW backend shutdown"); -#endif - - backendInitialized_ = false; + sdl2Initialized_ = false; + E2D_LOG_INFO("SDL2 shutdown"); } bool WindowModuleInitializer::initialize(const IModuleConfig* config) { @@ -148,60 +164,53 @@ bool WindowModuleInitializer::initialize(const IModuleConfig* config) { return false; } - backend_ = windowConfig->backend; windowConfig_ = windowConfig->windowConfig; - + #ifdef __SWITCH__ - backend_ = "switch"; windowConfig_.mode = WindowMode::Fullscreen; windowConfig_.resizable = false; + windowConfig_.highDPI = false; + E2D_LOG_INFO("Switch platform: forcing fullscreen mode"); #endif - if (!initBackend()) { + if (!initSDL2()) { return false; } -#ifdef E2D_BACKEND_SDL2 extern void initSDL2Backend(); initSDL2Backend(); -#endif - if (!BackendFactory::has(backend_)) { - E2D_LOG_ERROR("Backend '{}' not available", backend_); - auto backends = BackendFactory::backends(); - if (backends.empty()) { - E2D_LOG_ERROR("No backends registered!"); - shutdownBackend(); - return false; - } - backend_ = backends[0]; - E2D_LOG_WARN("Using fallback backend: {}", backend_); + if (!BackendFactory::has("sdl2")) { + E2D_LOG_ERROR("SDL2 backend not registered!"); + shutdownSDL2(); + return false; } - if (!createWindow(backend_, windowConfig_)) { + if (!createWindow(windowConfig_)) { E2D_LOG_ERROR("Failed to create window"); - shutdownBackend(); + shutdownSDL2(); return false; } initialized_ = true; E2D_LOG_INFO("Window module initialized"); E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height()); - E2D_LOG_INFO(" Backend: {}", backend_); + E2D_LOG_INFO(" Backend: SDL2"); E2D_LOG_INFO(" VSync: {}", windowConfig_.vsync); + E2D_LOG_INFO(" Fullscreen: {}", windowConfig_.isFullscreen()); return true; } -bool WindowModuleInitializer::createWindow(const std::string& backend, const WindowConfigData& config) { - window_ = BackendFactory::createWindow(backend); +bool WindowModuleInitializer::createWindow(const WindowConfigData& config) { + window_ = BackendFactory::createWindow("sdl2"); if (!window_) { - E2D_LOG_ERROR("Failed to create window for backend: {}", backend); + E2D_LOG_ERROR("Failed to create SDL2 window"); return false; } if (!window_->create(config)) { - E2D_LOG_ERROR("Failed to create window"); + E2D_LOG_ERROR("Failed to create window with specified config"); return false; } @@ -218,7 +227,7 @@ void WindowModuleInitializer::shutdown() { window_.reset(); } - shutdownBackend(); + shutdownSDL2(); initialized_ = false; } @@ -246,4 +255,4 @@ namespace { static WindowModuleAutoRegister s_autoRegister; } -} // namespace extra2d +} diff --git a/Extra2D/src/resource/resource_config.cpp b/Extra2D/src/resource/resource_config.cpp new file mode 100644 index 0000000..2631b5a --- /dev/null +++ b/Extra2D/src/resource/resource_config.cpp @@ -0,0 +1,23 @@ +#include +#include + +namespace extra2d { + +void ResourceConfigData::addSearchPath(const std::string& path) { + if (!hasSearchPath(path)) { + searchPaths.push_back(path); + } +} + +void ResourceConfigData::removeSearchPath(const std::string& path) { + auto it = std::find(searchPaths.begin(), searchPaths.end(), path); + if (it != searchPaths.end()) { + searchPaths.erase(it); + } +} + +bool ResourceConfigData::hasSearchPath(const std::string& path) const { + return std::find(searchPaths.begin(), searchPaths.end(), path) != searchPaths.end(); +} + +} diff --git a/docs/module_system.md b/docs/module_system.md index b6f523d..8087ffc 100644 --- a/docs/module_system.md +++ b/docs/module_system.md @@ -2,48 +2,55 @@ ## 概述 -Extra2D 采用模块化架构设计,所有核心功能都通过模块系统管理。模块系统提供: +Extra2D 采用模块化架构设计,所有核心功能通过模块系统和服务系统管理。系统提供: - **统一的生命周期管理**:初始化、关闭、依赖处理 -- **优先级排序**:确保模块按正确顺序初始化 +- **优先级排序**:确保模块/服务按正确顺序初始化 - **配置驱动**:每个模块可独立配置 -- **解耦设计**:Application 只协调模块,不直接管理任何依赖 +- **依赖注入**:通过服务定位器解耦模块间依赖 +- **可测试性**:支持 Mock 服务进行单元测试 ## 架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ Application │ -│ (只负责协调模块,不直接管理任何依赖) │ +│ (协调模块和服务,通过服务定位器获取依赖) │ └─────────────────────────────────────────────────────────────┘ │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ ModuleRegistry │ -│ (模块注册表,管理所有模块的生命周期) │ -└─────────────────────────────────────────────────────────────┘ - │ - ┌─────────────────────┼─────────────────────┐ - ▼ ▼ ▼ -┌───────────────┐ ┌───────────────┐ ┌───────────────┐ -│ Logger Module │ │ Config Module │ │Platform Module│ -│ (日志系统) │ │ (配置管理) │ │ (平台初始化) │ -└───────────────┘ └───────────────┘ └───────────────┘ - │ │ │ - └─────────────────────┼─────────────────────┘ - ▼ - ┌───────────────┐ - │ Window Module │ - │ (窗口管理) │ - └───────────────┘ - │ - ▼ - ┌───────────────┐ - │ Render Module │ - │ (渲染系统) │ - └───────────────┘ + ┌───────────────┴───────────────┐ + ▼ ▼ +┌─────────────────────────────┐ ┌─────────────────────────────┐ +│ ModuleRegistry │ │ ServiceLocator │ +│ (模块注册表,管理平台级模块) │ │ (服务定位器,管理运行时服务) │ +└─────────────────────────────┘ └─────────────────────────────┘ + │ │ + ┌─────┴─────┐ ┌───────┴───────┐ + ▼ ▼ ▼ ▼ +┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ +│ Config │ │ Window │ │ Scene │ │ Timer │ +│ Module │ │ Module │ │ Service │ │ Service │ +└───────────┘ └───────────┘ └───────────┘ └───────────┘ + │ │ + ┌─────┴─────┐ ┌─────┴─────┐ + ▼ ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ Event │ │ Camera │ │ ... │ + │ Service │ │ Service │ │ │ + └──────────┘ └──────────┘ └──────────┘ ``` +## 模块 vs 服务 + +| 特性 | 模块 (Module) | 服务 (Service) | +|-----|--------------|---------------| +| 用途 | 平台级初始化 | 运行时功能 | +| 生命周期 | Application 管理 | ServiceLocator 管理 | +| 依赖方式 | 通过 ModuleRegistry | 通过 ServiceLocator | +| 可替换性 | 编译时确定 | 运行时可替换 | +| 测试支持 | 需要重构 | 原生支持 Mock | +| 示例 | Window, Render, Config | Scene, Timer, Event, Camera | + ## 模块优先级 模块按优先级从小到大初始化,关闭时逆序执行: @@ -51,14 +58,27 @@ Extra2D 采用模块化架构设计,所有核心功能都通过模块系统管 | 优先级值 | 枚举名称 | 用途 | 模块示例 | |---------|---------|------|---------| | 0 | `Core` | 核心模块,最先初始化 | Logger, Config, Platform, Window | -| 50 | `Input` | 输入处理 | Input | | 100 | `Graphics` | 图形渲染 | Render | | 200 | `Audio` | 音频系统 | Audio | -| 300 | `Physics` | 物理系统 | Physics | -| 400 | `Gameplay` | 游戏逻辑 | Gameplay | -| 500 | `UI` | 用户界面 | UI | +| 500 | `Resource` | 资源管理 | Resource | -## 核心接口 +## 服务优先级 + +服务按优先级从小到大初始化: + +| 优先级值 | 枚举名称 | 用途 | 服务示例 | +|---------|---------|------|---------| +| 0 | `Core` | 核心服务 | - | +| 100 | `Event` | 事件系统 | EventService | +| 200 | `Timer` | 计时器 | TimerService | +| 300 | `Scene` | 场景管理 | SceneService | +| 400 | `Camera` | 相机系统 | CameraService | +| 500 | `Resource` | 资源管理 | - | +| 600 | `Audio` | 音频系统 | - | + +--- + +## 模块系统 ### IModuleConfig @@ -69,17 +89,12 @@ class IModuleConfig { public: virtual ~IModuleConfig() = default; - // 模块信息 virtual ModuleInfo getModuleInfo() const = 0; - - // 配置管理 virtual std::string getConfigSectionName() const = 0; virtual bool validate() const = 0; virtual void resetToDefaults() = 0; virtual bool loadFromJson(const void* jsonData) = 0; virtual bool saveToJson(void* jsonData) const = 0; - - // 平台约束 virtual void applyPlatformConstraints(PlatformType platform) {} }; ``` @@ -93,12 +108,10 @@ class IModuleInitializer { public: virtual ~IModuleInitializer() = default; - // 模块标识 virtual ModuleId getModuleId() const = 0; virtual ModulePriority getPriority() const = 0; virtual std::vector getDependencies() const = 0; - // 生命周期 virtual bool initialize(const IModuleConfig* config) = 0; virtual void shutdown() = 0; virtual bool isInitialized() const = 0; @@ -114,58 +127,292 @@ class ModuleRegistry { public: static ModuleRegistry& instance(); - // 注册模块 ModuleId registerModule( UniquePtr config, ModuleInitializerFactory factory ); - // 获取模块 IModuleConfig* getModuleConfig(ModuleId id); IModuleInitializer* getInitializer(ModuleId id); - - // 初始化顺序 std::vector getInitializationOrder() const; - - // 查询 - bool hasModule(const std::string& name) const; - std::vector getModuleNames() const; }; ``` +--- + +## 服务系统 + +### IService + +服务接口基类,所有服务必须实现: + +```cpp +class IService { +public: + virtual ~IService() = default; + + virtual ServiceInfo getServiceInfo() const = 0; + virtual bool initialize() = 0; + virtual void shutdown() = 0; + virtual void pause(); + virtual void resume(); + virtual void update(float deltaTime); + virtual bool isInitialized() const; + + ServiceState getState() const; + const std::string& getName() const; +}; +``` + +### ServiceLocator + +服务定位器,实现依赖注入和服务发现: + +```cpp +class ServiceLocator { +public: + static ServiceLocator& instance(); + + // 注册服务实例 + template + void registerService(SharedPtr service); + + // 注册服务工厂(延迟创建) + template + void registerFactory(ServiceFactory factory); + + // 获取服务 + template + SharedPtr getService() const; + + // 检查服务是否存在 + template + bool hasService() const; + + // 批量操作 + bool initializeAll(); + void shutdownAll(); + void updateAll(float deltaTime); + void pauseAll(); + void resumeAll(); + void clear(); +}; +``` + +### 内置服务 + +#### SceneService + +场景管理服务,包装 SceneManager: + +```cpp +class ISceneService : public IService { +public: + virtual void runWithScene(Ptr scene) = 0; + virtual void replaceScene(Ptr scene) = 0; + virtual void pushScene(Ptr scene) = 0; + virtual void popScene() = 0; + + virtual Ptr getCurrentScene() const = 0; + virtual size_t getSceneCount() const = 0; + + virtual void render(RenderBackend& renderer) = 0; +}; +``` + +#### TimerService + +计时器服务,包装 TimerManager: + +```cpp +class ITimerService : public IService { +public: + virtual uint32 addTimer(float delay, Timer::Callback callback) = 0; + virtual uint32 addRepeatingTimer(float interval, Timer::Callback callback) = 0; + virtual void cancelTimer(uint32 timerId) = 0; + virtual void pauseTimer(uint32 timerId) = 0; + virtual void resumeTimer(uint32 timerId) = 0; +}; +``` + +#### EventService + +事件服务,整合 EventQueue 和 EventDispatcher: + +```cpp +class IEventService : public IService { +public: + virtual void pushEvent(const Event& event) = 0; + virtual bool pollEvent(Event& event) = 0; + + virtual ListenerId addListener(EventType type, EventCallback callback) = 0; + virtual void removeListener(ListenerId id) = 0; + + virtual void dispatch(Event& event) = 0; + virtual void processQueue() = 0; +}; +``` + +#### CameraService + +相机服务,整合 Camera 和 ViewportAdapter: + +```cpp +class ICameraService : public IService { +public: + virtual void setPosition(const Vec2& position) = 0; + virtual void setZoom(float zoom) = 0; + virtual void setRotation(float degrees) = 0; + + virtual glm::mat4 getViewProjectionMatrix() const = 0; + virtual Vec2 screenToWorld(const Vec2& screenPos) const = 0; + + virtual void setViewportConfig(const ViewportConfig& config) = 0; + virtual void updateViewport(int width, int height) = 0; +}; +``` + +--- + +## 使用示例 + +### 在 Application 中使用服务 + +```cpp +// 获取服务 +auto sceneService = Application::get().scenes(); +auto timerService = Application::get().timers(); +auto eventService = Application::get().events(); +auto cameraService = Application::get().camera(); + +// 使用场景服务 +sceneService->pushScene(myScene); + +// 使用计时器服务 +timerService->addTimer(1.0f, []() { + E2D_LOG_INFO("Timer fired!"); +}); + +// 使用事件服务 +eventService->addListener(EventType::KeyDown, [](Event& e) { + E2D_LOG_INFO("Key pressed: {}", e.key.keycode); +}); + +// 使用相机服务 +cameraService->setPosition(Vec2(100.0f, 200.0f)); +cameraService->setZoom(2.0f); +``` + +### 注册自定义服务 + +```cpp +// 定义服务接口 +class IAudioService : public IService { +public: + virtual void playSound(const std::string& path) = 0; + virtual void stopAll() = 0; +}; + +// 实现服务 +class AudioService : public IAudioService { +public: + ServiceInfo getServiceInfo() const override { + ServiceInfo info; + info.name = "AudioService"; + info.priority = ServicePriority::Audio; + return info; + } + + bool initialize() override { + // 初始化音频系统... + setState(ServiceState::Running); + return true; + } + + void shutdown() override { + // 清理音频系统... + setState(ServiceState::Stopped); + } + + void playSound(const std::string& path) override { + // 播放音效... + } + + void stopAll() override { + // 停止所有音效... + } +}; + +// 注册服务 +Application::get().registerService(makeShared()); + +// 使用服务 +auto audio = Application::get().getService(); +audio->playSound("explosion.wav"); +``` + +### 测试时注入 Mock 服务 + +```cpp +// 创建 Mock 服务 +class MockSceneService : public ISceneService { +public: + std::vector sceneHistory; + + void pushScene(Ptr scene) override { + sceneHistory.push_back("push:" + scene->getName()); + } + + void popScene() override { + sceneHistory.push_back("pop"); + } + + // 实现其他必要方法... +}; + +// 测试代码 +void testSceneNavigation() { + // 注入 Mock 服务 + auto mockService = makeShared(); + ServiceLocator::instance().registerService(mockService); + + // 执行测试 + auto sceneService = ServiceLocator::instance().getService(); + sceneService->pushScene(createTestScene("level1")); + sceneService->pushScene(createTestScene("level2")); + sceneService->popScene(); + + // 验证结果 + assert(mockService->sceneHistory.size() == 3); + assert(mockService->sceneHistory[0] == "push:level1"); + assert(mockService->sceneHistory[1] == "push:level2"); + assert(mockService->sceneHistory[2] == "pop"); +} +``` + +--- + ## 创建新模块 ### 步骤 1:定义配置类 ```cpp -// my_module.h class MyModuleConfig : public IModuleConfig { public: int someSetting = 42; - std::string somePath; ModuleInfo getModuleInfo() const override { ModuleInfo info; info.name = "MyModule"; info.version = "1.0.0"; - info.priority = ModulePriority::Gameplay; + info.priority = ModulePriority::Graphics; info.enabled = true; return info; } - std::string getConfigSectionName() const override { - return "my_module"; - } - - bool validate() const override { - return someSetting > 0; - } - - void resetToDefaults() override { - someSetting = 42; - somePath.clear(); - } - + std::string getConfigSectionName() const override { return "my_module"; } + bool validate() const override { return someSetting > 0; } + void resetToDefaults() override { someSetting = 42; } bool loadFromJson(const void* jsonData) override; bool saveToJson(void* jsonData) const override; }; @@ -174,11 +421,10 @@ public: ### 步骤 2:定义初始化器 ```cpp -// my_module.h class MyModuleInitializer : public IModuleInitializer { public: ModuleId getModuleId() const override { return moduleId_; } - ModulePriority getPriority() const override { return ModulePriority::Gameplay; } + ModulePriority getPriority() const override { return ModulePriority::Graphics; } std::vector getDependencies() const override { return {}; } bool initialize(const IModuleConfig* config) override { @@ -196,9 +442,7 @@ public: void shutdown() override { if (!initialized_) return; - // 执行清理逻辑... - initialized_ = false; E2D_LOG_INFO("MyModule shutdown"); } @@ -222,9 +466,7 @@ namespace extra2d { static ModuleId s_myModuleId = INVALID_MODULE_ID; -ModuleId get_my_module_id() { - return s_myModuleId; -} +ModuleId get_my_module_id() { return s_myModuleId; } void register_my_module() { if (s_myModuleId != INVALID_MODULE_ID) return; @@ -239,34 +481,10 @@ void register_my_module() { ); } -// 自动注册 -namespace { - struct MyModuleAutoRegister { - MyModuleAutoRegister() { - register_my_module(); - } - }; - - static MyModuleAutoRegister s_autoRegister; -} - } // namespace extra2d ``` -### 步骤 4:在 Application 中使用 - -```cpp -// application.cpp -#include - -bool Application::init(const AppConfig& config) { - // 注册模块 - register_my_module(); - - // 初始化所有模块 - return initModules(); -} -``` +--- ## 内置模块 @@ -283,11 +501,6 @@ config.fileOutput = true; config.logFilePath = "app.log"; ``` -**平台支持**: -- Windows:彩色控制台输出 -- Linux/macOS:ANSI 彩色输出 -- Switch:libnx console - --- ### Config 模块 @@ -310,12 +523,6 @@ config.appConfig = AppConfig::createDefault(); **平台特定操作**: - Switch:初始化 romfs 和 socket -**配置**: -```cpp -PlatformModuleConfig config; -config.targetPlatform = PlatformType::Auto; // 自动检测 -``` - --- ### Window 模块 @@ -330,7 +537,6 @@ config.targetPlatform = PlatformType::Auto; // 自动检测 **配置**: ```cpp WindowModuleConfig config; -config.backend = "sdl2"; config.windowConfig.title = "My App"; config.windowConfig.width = 1280; config.windowConfig.height = 720; @@ -352,17 +558,7 @@ config.targetFPS = 60; config.multisamples = 4; ``` -## 模块依赖 - -模块可以声明依赖关系: - -```cpp -std::vector getDependencies() const override { - return { get_window_module_id(), get_config_module_id() }; -} -``` - -ModuleRegistry 会自动解析依赖并按正确顺序初始化。 +--- ## 配置文件格式 @@ -376,73 +572,70 @@ ModuleRegistry 会自动解析依赖并按正确顺序初始化。 "fileOutput": false }, "window": { - "backend": "sdl2", "title": "My Application", "width": 1280, "height": 720, "vsync": true }, "render": { - "backend": "opengl", "targetFPS": 60, "multisamples": 4 } } ``` +--- + ## 最佳实践 -### 1. 单一职责 - -每个模块只负责一个明确的功能领域: - -``` -✅ Logger Module → 只管理日志 -✅ Window Module → 只管理窗口 -❌ Game Module → 管理输入、渲染、音频(职责过多) -``` - -### 2. 依赖最小化 - -模块应尽量减少对其他模块的依赖: +### 1. 模块用于平台初始化,服务用于运行时功能 ```cpp -// 好的做法:通过接口获取信息 -std::vector getDependencies() const override { - return { get_window_module_id() }; // 只依赖窗口 +// 模块:平台级初始化 +class WindowModule : public IModuleInitializer { + bool initialize(const IModuleConfig* config) override { + // 创建窗口、初始化 OpenGL 上下文 + } +}; + +// 服务:运行时功能 +class SceneService : public ISceneService { + void update(float deltaTime) override { + // 每帧更新场景 + } +}; +``` + +### 2. 通过服务定位器解耦依赖 + +```cpp +// 好的做法:通过服务定位器获取依赖 +void MyNode::onUpdate(float dt) { + auto camera = ServiceLocator::instance().getService(); + auto pos = camera->screenToWorld(mousePos); } -// 不好的做法:依赖过多 -std::vector getDependencies() const override { - return { - get_window_module_id(), - get_config_module_id(), - get_logger_module_id(), - get_render_module_id() - }; +// 不好的做法:直接依赖 Application +void MyNode::onUpdate(float dt) { + auto& camera = Application::get().camera(); // 耦合度高 } ``` -### 3. 延迟初始化 - -资源在需要时才初始化,不是在构造函数中: +### 3. 使用接口便于测试 ```cpp -bool initialize(const IModuleConfig* config) override { - if (initialized_) return true; - - // 在这里初始化资源 - resource_ = createResource(); - - initialized_ = true; - return true; -} +// 定义接口 +class IAudioService : public IService { ... }; + +// 生产环境使用真实实现 +auto audio = makeShared(); + +// 测试环境使用 Mock +auto audio = makeShared(); ``` ### 4. 安全关闭 -关闭时要检查状态,避免重复关闭: - ```cpp void shutdown() override { if (!initialized_) return; // 避免重复关闭 @@ -450,27 +643,12 @@ void shutdown() override { // 清理资源 resource_.reset(); + setState(ServiceState::Stopped); initialized_ = false; } ``` -### 5. 使用日志 - -在关键操作处添加日志: - -```cpp -bool initialize(const IModuleConfig* config) override { - E2D_LOG_INFO("Initializing MyModule..."); - - if (!doSomething()) { - E2D_LOG_ERROR("Failed to do something"); - return false; - } - - E2D_LOG_INFO("MyModule initialized successfully"); - return true; -} -``` +--- ## 调试 @@ -488,18 +666,24 @@ for (ModuleId id : order) { } ``` -### 检查模块状态 +### 查看服务状态 ```cpp -auto* initializer = ModuleRegistry::instance().getInitializer(moduleId); -if (initializer && initializer->isInitialized()) { - E2D_LOG_INFO("Module is initialized"); +auto services = ServiceLocator::instance().getAllServices(); +for (const auto& service : services) { + auto info = service->getServiceInfo(); + E2D_LOG_INFO("Service: {} (state: {})", + info.name, static_cast(info.state)); } ``` +--- + ## 示例 完整示例请参考: - [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例 +- [Extra2D/src/services/scene_service.cpp](../../Extra2D/src/services/scene_service.cpp) - Scene 服务实现 +- [Extra2D/src/services/event_service.cpp](../../Extra2D/src/services/event_service.cpp) - Event 服务实现 - [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现 - [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现 diff --git a/examples/basic/main.cpp b/examples/basic/main.cpp index 2cd8471..379ccc0 100644 --- a/examples/basic/main.cpp +++ b/examples/basic/main.cpp @@ -1,59 +1,56 @@ /** * @file main.cpp * @brief Extra2D 基础示例程序 - * + * * 演示如何使用 Extra2D 引擎创建一个简单的窗口应用程序。 */ #include +#include +#include #include using namespace extra2d; /** * @brief 主函数 - * + * * 初始化应用程序,创建场景,运行主循环。 */ -int main(int argc, char* argv[]) { - (void)argc; - (void)argv; +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; - std::cout << "Extra2D Demo - Starting..." << std::endl; + std::cout << "Extra2D Demo - Starting..." << std::endl; - AppConfig config = AppConfig::createDefault(); - config.appName = "Extra2D Demo"; - config.appVersion = "1.0.0"; - config.window.title = "Extra2D Demo"; - config.window.width = 800; - config.window.height = 600; - config.window.mode = WindowMode::Windowed; - config.window.resizable = true; - config.window.vsync = true; - config.render.targetFPS = 60; + AppConfig config = AppConfig::createDefault(); + config.appName = "Extra2D Demo"; + config.appVersion = "1.0.0"; - Application& app = Application::get(); + Application &app = Application::get(); - if (!app.init(config)) { - std::cerr << "Failed to initialize application!" << std::endl; - return -1; - } + if (!app.init(config)) { + std::cerr << "Failed to initialize application!" << std::endl; + return -1; + } - std::cout << "Application initialized successfully!" << std::endl; - std::cout << "Window: " << app.window().width() << "x" << app.window().height() << std::endl; - std::cout << "Running main loop. Press ESC or close window to exit." << std::endl; + std::cout << "Application initialized successfully!" << std::endl; + std::cout << "Window: " << app.window().width() << "x" + << app.window().height() << std::endl; + std::cout << "Running main loop. Press ESC or close window to exit." + << std::endl; - auto scene = Scene::create(); - scene->setBackgroundColor(Colors::SkyBlue); - scene->setViewportSize(static_cast(config.window.width), - static_cast(config.window.height)); - app.enterScene(scene); + auto scene = Scene::create(); + scene->setBackgroundColor(Colors::SkyBlue); + scene->setViewportSize(static_cast(app.window().width()), + static_cast(app.window().height())); + app.enterScene(scene); - app.run(); + app.run(); - std::cout << "Shutting down..." << std::endl; - app.shutdown(); + std::cout << "Shutting down..." << std::endl; + app.shutdown(); - std::cout << "Goodbye!" << std::endl; - return 0; + std::cout << "Goodbye!" << std::endl; + return 0; } diff --git a/xmake.lua b/xmake.lua index f418400..11db356 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,7 +1,14 @@ -- ============================================== -- Extra2D - 2D Game Engine -- Build System: Xmake --- Platforms: MinGW (Windows), Nintendo Switch +-- +-- 支持平台: +-- - Windows (MinGW) +-- - Linux +-- - macOS +-- - Nintendo Switch +-- +-- 窗口后端: SDL2 (统一) -- ============================================== -- 项目元信息 @@ -26,34 +33,37 @@ option("debug_logs") set_description("Enable debug logging") option_end() -option("backend") - set_default("sdl2") - set_showmenu(true) - set_values("sdl2", "glfw") - set_description("Platform backend (sdl2, glfw)") -option_end() - -- ============================================== -- 平台检测与配置 -- ============================================== local host_plat = os.host() local target_plat = get_config("plat") or host_plat -local supported_plats = {mingw = true, switch = true} +local supported_plats = {mingw = true, windows = true, linux = true, macosx = true, switch = true} +-- 自动选择平台 if not supported_plats[target_plat] then if host_plat == "windows" then target_plat = "mingw" + elseif host_plat == "linux" then + target_plat = "linux" + elseif host_plat == "macosx" then + target_plat = "macosx" else - error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, switch") + error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, windows, linux, macosx, switch") end end set_plat(target_plat) +-- 设置架构 if target_plat == "switch" then set_arch("arm64") -elseif target_plat == "mingw" then +elseif target_plat == "mingw" or target_plat == "windows" then + set_arch("x86_64") +elseif target_plat == "linux" then + set_arch("x86_64") +elseif target_plat == "macosx" then set_arch("x86_64") end @@ -69,20 +79,13 @@ elseif target_plat == "mingw" then end -- ============================================== --- 添加依赖包 (MinGW) +-- 添加依赖包 -- ============================================== -if target_plat == "mingw" then - local backend = get_config("backend") or "sdl2" - +if target_plat ~= "switch" then add_requires("glm") add_requires("nlohmann_json") - - if backend == "sdl2" then - add_requires("libsdl2") - elseif backend == "glfw" then - add_requires("glfw") - end + add_requires("libsdl2") end -- ============================================== @@ -134,10 +137,16 @@ target("demo_basic") add_files("examples/basic/main.cpp") -- 平台配置 - local target_plat = get_config("plat") or os.host() - if target_plat == "mingw" then + local plat = get_config("plat") or os.host() + if plat == "mingw" or plat == "windows" then add_packages("glm", "nlohmann_json", "libsdl2") add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") + elseif plat == "linux" then + add_packages("glm", "nlohmann_json", "libsdl2") + add_syslinks("GL", "dl", "pthread") + elseif plat == "macosx" then + add_packages("glm", "nlohmann_json", "libsdl2") + add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") end -- 构建后安装Shader文件 diff --git a/xmake/engine.lua b/xmake/engine.lua index ad83a72..8dcc08c 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -1,6 +1,12 @@ -- ============================================== -- Extra2D 引擎库共享配置 -- 被主项目和示例共享使用 +-- +-- 窗口后端统一使用 SDL2,支持以下平台: +-- - Windows (MinGW) +-- - Linux +-- - macOS +-- - Nintendo Switch -- ============================================== -- 获取当前平台 @@ -8,11 +14,6 @@ local function get_current_plat() return get_config("plat") or os.host() end --- 获取后端配置 -local function get_backend() - return get_config("backend") or "sdl2" -end - -- 定义 Extra2D 引擎库目标 function define_extra2d_engine() target("extra2d") @@ -22,42 +23,40 @@ function define_extra2d_engine() add_files("Extra2D/src/**.cpp") add_files("Extra2D/src/glad/glad.c") - -- 平台后端源文件 - local plat = get_current_plat() - local backend = get_backend() - - if plat == "switch" then - add_files("Extra2D/src/platform/backends/switch/*.cpp") - add_defines("E2D_BACKEND_SWITCH") - elseif backend == "sdl2" then - add_files("Extra2D/src/platform/backends/sdl2/*.cpp") - add_defines("E2D_BACKEND_SDL2") - elseif backend == "glfw" then - add_files("Extra2D/src/platform/backends/glfw/*.cpp") - add_defines("E2D_BACKEND_GLFW") - end + -- SDL2 后端源文件(所有平台统一使用) + add_files("Extra2D/src/platform/backends/sdl2/*.cpp") + add_defines("E2D_BACKEND_SDL2") -- 头文件路径 add_includedirs("Extra2D/include", {public = true}) add_includedirs("Extra2D/include/extra2d/platform", {public = true}) -- 平台配置 + local plat = get_current_plat() + if plat == "switch" then + -- Nintendo Switch 平台配置 local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true}) add_linkdirs(devkitPro .. "/portlibs/switch/lib") - add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau", + add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau", "nx", "m", {public = true}) - elseif plat == "mingw" then + elseif plat == "mingw" or plat == "windows" then + -- Windows (MinGW) 平台配置 add_packages("glm", "nlohmann_json", {public = true}) - - if backend == "sdl2" then - add_packages("libsdl2", {public = true}) - elseif backend == "glfw" then - add_packages("glfw", {public = true}) - end - - add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true}) + add_packages("libsdl2", {public = true}) + add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", + {public = true}) + elseif plat == "linux" then + -- Linux 平台配置 + add_packages("glm", "nlohmann_json", {public = true}) + add_packages("libsdl2", {public = true}) + add_syslinks("GL", "dl", "pthread", {public = true}) + elseif plat == "macosx" then + -- macOS 平台配置 + add_packages("glm", "nlohmann_json", {public = true}) + add_packages("libsdl2", {public = true}) + add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo", {public = true}) end -- 编译器标志 (C 和 C++ 共用)