refactor(platform): 重构平台和模块配置系统

将平台相关配置分离到独立头文件
移除AppConfig中的模块配置,改为模块自行管理
统一使用SDL2作为窗口后端
优化Switch平台支持
添加模块配置接口
重构配置加载器以支持模块配置
This commit is contained in:
ChestnutYueyue 2026-02-15 12:36:36 +08:00
parent 453a057c7d
commit 8c56c29cd2
33 changed files with 1914 additions and 3489 deletions

View File

@ -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<float>(volume) / 100.0f; }
};
}

View File

@ -1,180 +1,27 @@
#pragma once #pragma once
#include <extra2d/config/platform_config.h> #include <extra2d/config/platform_config.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/render_backend.h>
#include <string> #include <string>
#include <vector>
namespace extra2d { namespace extra2d {
// ============================================================================ /**
// 窗口模式枚举 * @file app_config.h
// ============================================================================ * @brief
enum class WindowMode { *
Windowed, *
Fullscreen, * IModuleConfig
Borderless *
}; * ModuleRegistry ConfigManager
*
*/
// ============================================================================ /**
// 窗口配置数据 * @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;
bool isSizeValid() const { return width > 0 && height > 0; }
bool hasPosition() const { return posX >= 0 && posY >= 0; }
float aspectRatio() const { return static_cast<float>(width) / static_cast<float>(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<float>(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<std::string> 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<std::string> searchPaths;
void addSearchPath(const std::string& path);
void removeSearchPath(const std::string& path);
bool hasSearchPath(const std::string& path) const;
};
// ============================================================================
// 应用统一配置
// ============================================================================
struct AppConfig { struct AppConfig {
WindowConfigData window;
RenderConfigData render;
AudioConfigData audio;
DebugConfigData debug;
InputConfigData input;
ResourceConfigData resource;
std::string appName = "Extra2D App"; std::string appName = "Extra2D App";
std::string appVersion = "1.0.0"; std::string appVersion = "1.0.0";
std::string organization = ""; std::string organization = "";
@ -193,12 +40,6 @@ struct AppConfig {
*/ */
bool validate() const; bool validate() const;
/**
* @brief
* @param platform
*/
void applyPlatformConstraints(const PlatformConfig& platform);
/** /**
* @brief * @brief
*/ */
@ -215,12 +56,6 @@ struct AppConfig {
* @return true * @return true
*/ */
bool isValid() const { return validate(); } bool isValid() const { return validate(); }
/**
* @brief
* @return
*/
float aspectRatio() const { return window.aspectRatio(); }
}; };
} }

View File

@ -6,9 +6,17 @@
namespace extra2d { namespace extra2d {
// ============================================================================ /**
// 配置加载结果 * @file config_loader.h
// ============================================================================ * @brief
*
* AppConfig
* ModuleRegistry IModuleConfig
*/
/**
* @brief
*/
struct ConfigLoadResult { struct ConfigLoadResult {
bool success = false; bool success = false;
std::string errorMessage; std::string errorMessage;
@ -24,9 +32,9 @@ struct ConfigLoadResult {
bool hasError() const { return !success; } bool hasError() const { return !success; }
}; };
// ============================================================================ /**
// 配置保存结果 * @brief
// ============================================================================ */
struct ConfigSaveResult { struct ConfigSaveResult {
bool success = false; bool success = false;
std::string errorMessage; std::string errorMessage;
@ -40,15 +48,15 @@ struct ConfigSaveResult {
bool hasError() const { return !success; } bool hasError() const { return !success; }
}; };
// ============================================================================ /**
// 配置加载器抽象接口 * @brief
// ============================================================================ */
class ConfigLoader { class ConfigLoader {
public: public:
virtual ~ConfigLoader() = default; virtual ~ConfigLoader() = default;
/** /**
* @brief * @brief
* @param filepath * @param filepath
* @param config * @param config
* @return * @return
@ -56,7 +64,7 @@ public:
virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0; virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0;
/** /**
* @brief * @brief
* @param filepath * @param filepath
* @param config * @param config
* @return * @return
@ -78,6 +86,20 @@ public:
*/ */
virtual std::string saveToString(const AppConfig& config) = 0; 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 * @brief
* @return "json" * @return "json"
@ -98,9 +120,9 @@ public:
virtual UniquePtr<ConfigLoader> clone() const = 0; virtual UniquePtr<ConfigLoader> clone() const = 0;
}; };
// ============================================================================ /**
// JSON 配置加载器 * @brief JSON
// ============================================================================ */
class JsonConfigLoader : public ConfigLoader { class JsonConfigLoader : public ConfigLoader {
public: public:
JsonConfigLoader() = default; JsonConfigLoader() = default;
@ -110,29 +132,16 @@ public:
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override; ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override; ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
std::string saveToString(const 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"; } const char* extension() const override { return "json"; }
bool supportsFile(const std::string& filepath) const override; bool supportsFile(const std::string& filepath) const override;
UniquePtr<ConfigLoader> clone() const override; UniquePtr<ConfigLoader> 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 { class IniConfigLoader : public ConfigLoader {
public: public:
IniConfigLoader() = default; IniConfigLoader() = default;
@ -142,6 +151,8 @@ public:
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override; ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override; ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
std::string saveToString(const 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"; } const char* extension() const override { return "ini"; }
bool supportsFile(const std::string& filepath) const override; bool supportsFile(const std::string& filepath) const override;
UniquePtr<ConfigLoader> clone() const override; UniquePtr<ConfigLoader> clone() const override;
@ -153,9 +164,9 @@ private:
ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName); ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName);
}; };
// ============================================================================ /**
// 配置加载器工厂 * @brief
// ============================================================================ */
class ConfigLoaderFactory { class ConfigLoaderFactory {
public: public:
/** /**

View File

@ -10,9 +10,17 @@
namespace extra2d { namespace extra2d {
// ============================================================================ /**
// 配置变更事件 * @file config_manager.h
// ============================================================================ * @brief
*
* AppConfig
* ModuleRegistry IModuleConfig
*/
/**
* @brief
*/
struct ConfigChangeEvent { struct ConfigChangeEvent {
std::string section; std::string section;
std::string field; std::string field;
@ -20,18 +28,16 @@ struct ConfigChangeEvent {
std::string newValue; std::string newValue;
}; };
// ============================================================================ /**
// 配置变更回调类型 * @brief
// ============================================================================ */
using ConfigChangeCallback = Function<void(const ConfigChangeEvent &)>; using ConfigChangeCallback = Function<void(const ConfigChangeEvent &)>;
// ============================================================================ /**
// 配置管理器(单例) * @brief
// ============================================================================ */
class ConfigManager { class ConfigManager {
public: public:
using ModuleConfigPtr = Ptr<void>;
/** /**
* @brief * @brief
* @return * @return
@ -70,6 +76,20 @@ public:
*/ */
ConfigSaveResult saveConfig(const std::string &filepath = ""); 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 * @brief
* @return * @return
@ -124,40 +144,6 @@ public:
*/ */
void clearChangeCallbacks(); void clearChangeCallbacks();
/**
* @brief
* @param moduleName
* @param config
*/
void registerModuleConfig(const std::string &moduleName, Ptr<void> config);
/**
* @brief
* @param moduleName
* @return
*/
template <typename T>
Ptr<T> getModuleConfig(const std::string &moduleName) const {
auto it = m_moduleConfigs.find(moduleName);
if (it != m_moduleConfigs.end()) {
return std::static_pointer_cast<T>(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 * @brief
* @param section * @param section
@ -285,8 +271,6 @@ private:
ConfigManager &operator=(const ConfigManager &) = delete; ConfigManager &operator=(const ConfigManager &) = delete;
void notifyChangeCallbacks(const ConfigChangeEvent &event); void notifyChangeCallbacks(const ConfigChangeEvent &event);
void applyConfigToInternal(const AppConfig &config);
void extractConfigFromInternal(AppConfig &config) const;
AppConfig m_appConfig; AppConfig m_appConfig;
UniquePtr<PlatformConfig> m_platformConfig; UniquePtr<PlatformConfig> m_platformConfig;
@ -299,16 +283,13 @@ private:
std::unordered_map<int, ConfigChangeCallback> m_changeCallbacks; std::unordered_map<int, ConfigChangeCallback> m_changeCallbacks;
int m_nextCallbackId = 1; int m_nextCallbackId = 1;
std::unordered_map<std::string, ModuleConfigPtr> m_moduleConfigs; std::unordered_map<std::string, std::string> m_rawValues;
bool m_autoSaveEnabled = false; bool m_autoSaveEnabled = false;
float m_autoSaveInterval = 30.0f; float m_autoSaveInterval = 30.0f;
float m_autoSaveTimer = 0.0f; float m_autoSaveTimer = 0.0f;
}; };
// ============================================================================
// 便捷宏定义
// ============================================================================
#define CONFIG_MANAGER ConfigManager::instance() #define CONFIG_MANAGER ConfigManager::instance()
} // namespace extra2d }

View File

@ -5,9 +5,17 @@
namespace extra2d { namespace extra2d {
// ============================================================================ /**
// 平台类型枚举 * @file platform_config.h
// ============================================================================ * @brief
*
*
* IModuleConfig::applyPlatformConstraints()
*/
/**
* @brief
*/
enum class PlatformType { enum class PlatformType {
Auto, Auto,
Windows, Windows,
@ -16,9 +24,9 @@ enum class PlatformType {
macOS macOS
}; };
// ============================================================================ /**
// 平台能力结构 * @brief
// ============================================================================ */
struct PlatformCapabilities { struct PlatformCapabilities {
bool supportsWindowed = true; bool supportsWindowed = true;
bool supportsFullscreen = true; bool supportsFullscreen = true;
@ -46,9 +54,9 @@ struct PlatformCapabilities {
bool isConsole() const { return !supportsWindowed && supportsGamepad; } bool isConsole() const { return !supportsWindowed && supportsGamepad; }
}; };
// ============================================================================ /**
// 平台配置抽象接口 * @brief
// ============================================================================ */
class PlatformConfig { class PlatformConfig {
public: public:
virtual ~PlatformConfig() = default; virtual ~PlatformConfig() = default;
@ -56,20 +64,24 @@ public:
virtual PlatformType platformType() const = 0; virtual PlatformType platformType() const = 0;
virtual const char* platformName() const = 0; virtual const char* platformName() const = 0;
virtual const PlatformCapabilities& capabilities() 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 getRecommendedWidth() const = 0;
virtual int getRecommendedHeight() const = 0; virtual int getRecommendedHeight() const = 0;
virtual bool isResolutionSupported(int width, int height) const = 0; virtual bool isResolutionSupported(int width, int height) const = 0;
}; };
// ============================================================================ /**
// 平台配置工厂函数声明 * @brief
// ============================================================================ * @param type Auto
* @return
*/
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto); UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
/**
* @brief
* @param type
* @return
*/
const char* getPlatformTypeName(PlatformType type); const char* getPlatformTypeName(PlatformType type);
} }

View File

@ -0,0 +1,53 @@
#pragma once
#include <string>
#include <vector>
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<std::string> 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);
};
}

View File

@ -0,0 +1,51 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <extra2d/graphics/render_backend.h>
#include <string>
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; }
};
}

View File

@ -2,6 +2,7 @@
#include <extra2d/config/module_config.h> #include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h> #include <extra2d/config/module_initializer.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/render_backend.h> #include <extra2d/graphics/render_backend.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>

View File

@ -0,0 +1,37 @@
#pragma once
#include <string>
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; }
};
}

View File

@ -2,23 +2,27 @@
#include <extra2d/config/module_config.h> #include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h> #include <extra2d/config/module_initializer.h>
#include <extra2d/input/input_config.h>
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
namespace extra2d { namespace extra2d {
/**
* @file input_module.h
* @brief
*
*
*
*/
/** /**
* @brief * @brief
* IModuleConfig * IModuleConfig
*/ */
class InputModuleConfig : public IModuleConfig { class InputModuleConfig : public IModuleConfig {
public: public:
bool enableKeyboard = true; InputConfigData inputConfig;
bool enableMouse = true;
bool enableGamepad = true;
bool enableTouch = true;
float deadzone = 0.15f;
float mouseSensitivity = 1.0f;
/** /**
* @brief * @brief
@ -26,6 +30,7 @@ public:
*/ */
ModuleInfo getModuleInfo() const override { ModuleInfo getModuleInfo() const override {
ModuleInfo info; ModuleInfo info;
info.id = 0;
info.name = "Input"; info.name = "Input";
info.version = "1.0.0"; info.version = "1.0.0";
info.priority = ModulePriority::Input; info.priority = ModulePriority::Input;
@ -47,7 +52,6 @@ public:
/** /**
* @brief * @brief
*
* @param platform * @param platform
*/ */
void applyPlatformConstraints(PlatformType platform) override; void applyPlatformConstraints(PlatformType platform) override;
@ -55,7 +59,9 @@ public:
/** /**
* @brief * @brief
*/ */
void resetToDefaults() override; void resetToDefaults() override {
inputConfig = InputConfigData{};
}
/** /**
* @brief JSON * @brief JSON
@ -75,57 +81,21 @@ public:
/** /**
* @brief * @brief
* IModuleInitializer * IModuleInitializer
*
*/ */
class InputModuleInitializer : public IModuleInitializer { class InputModuleInitializer : public IModuleInitializer {
public: public:
/**
* @brief
*/
InputModuleInitializer(); InputModuleInitializer();
/**
* @brief
*/
~InputModuleInitializer() override; ~InputModuleInitializer() override;
/**
* @brief
* @return
*/
ModuleId getModuleId() const override { return moduleId_; } ModuleId getModuleId() const override { return moduleId_; }
/**
* @brief
* @return
*/
ModulePriority getPriority() const override { return ModulePriority::Input; } ModulePriority getPriority() const override { return ModulePriority::Input; }
/**
* @brief
*
* @return
*/
std::vector<ModuleId> getDependencies() const override; std::vector<ModuleId> getDependencies() const override;
/**
* @brief
* @param config
* @return true
*/
bool initialize(const IModuleConfig* config) override; bool initialize(const IModuleConfig* config) override;
/**
* @brief
*/
void shutdown() override; void shutdown() override;
/**
* @brief
* @return true
*/
bool isInitialized() const override { return initialized_; } bool isInitialized() const override { return initialized_; }
void setModuleId(ModuleId id) { moduleId_ = id; }
/** /**
* @brief * @brief
* @return * @return
@ -133,16 +103,27 @@ public:
IInput* getInput() const { return input_; } IInput* getInput() const { return input_; }
/** /**
* @brief * @brief
* @param windowModuleId *
*/ */
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; } void update();
private: private:
ModuleId moduleId_ = INVALID_MODULE_ID; ModuleId moduleId_ = INVALID_MODULE_ID;
ModuleId windowModuleId_ = INVALID_MODULE_ID;
IInput* input_ = nullptr; IInput* input_ = nullptr;
bool initialized_ = false; bool initialized_ = false;
InputConfigData config_;
}; };
} // namespace extra2d /**
* @brief
* @return
*/
ModuleId get_input_module_id();
/**
* @brief
*/
void register_input_module();
}

View File

@ -2,7 +2,7 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/config/app_config.h> #include <extra2d/platform/window_config.h>
#include <functional> #include <functional>
#include <string> #include <string>

View File

@ -0,0 +1,84 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <string>
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<float>(width) / static_cast<float>(height); }
/**
* @brief
* @return true
*/
bool isFullscreen() const { return mode == WindowMode::Fullscreen; }
/**
* @brief
* @return true
*/
bool isBorderless() const { return mode == WindowMode::Borderless || borderless; }
};
}

View File

@ -2,17 +2,35 @@
#include <extra2d/config/module_config.h> #include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h> #include <extra2d/config/module_initializer.h>
#include <extra2d/config/app_config.h> #include <extra2d/platform/window_config.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
#include <string> #include <string>
namespace extra2d { namespace extra2d {
/**
* @file window_module.h
* @brief
*
* 使 SDL2
* - Windows
* - Linux
* - macOS
* - Nintendo Switch
*/
/**
* @brief
* IModuleConfig
*/
class WindowModuleConfig : public IModuleConfig { class WindowModuleConfig : public IModuleConfig {
public: public:
std::string backend = "sdl2";
WindowConfigData windowConfig; WindowConfigData windowConfig;
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override { ModuleInfo getModuleInfo() const override {
ModuleInfo info; ModuleInfo info;
info.id = 0; info.id = 0;
@ -23,55 +41,155 @@ public:
return info; return info;
} }
/**
* @brief
* @return
*/
std::string getConfigSectionName() const override { std::string getConfigSectionName() const override {
return "window"; return "window";
} }
/**
* @brief
* @return true
*/
bool validate() const override { bool validate() const override {
return windowConfig.width > 0 && windowConfig.height > 0; return windowConfig.width > 0 && windowConfig.height > 0;
} }
/**
* @brief
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override { void resetToDefaults() override {
backend = "sdl2";
windowConfig = WindowConfigData{}; windowConfig = WindowConfigData{};
} }
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool loadFromJson(const void* jsonData) override; bool loadFromJson(const void* jsonData) override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool saveToJson(void* jsonData) const override; bool saveToJson(void* jsonData) const override;
}; };
/**
* @brief
* IModuleInitializer
*/
class WindowModuleInitializer : public IModuleInitializer { class WindowModuleInitializer : public IModuleInitializer {
public: public:
/**
* @brief
*/
WindowModuleInitializer(); WindowModuleInitializer();
/**
* @brief
*/
~WindowModuleInitializer() override; ~WindowModuleInitializer() override;
/**
* @brief
* @return
*/
ModuleId getModuleId() const override { return moduleId_; } ModuleId getModuleId() const override { return moduleId_; }
/**
* @brief
* @return
*/
ModulePriority getPriority() const override { return ModulePriority::Core; } ModulePriority getPriority() const override { return ModulePriority::Core; }
/**
* @brief
* @return
*/
std::vector<ModuleId> getDependencies() const override { return {}; } std::vector<ModuleId> getDependencies() const override { return {}; }
/**
* @brief
* @param config
* @return true
*/
bool initialize(const IModuleConfig* config) override; bool initialize(const IModuleConfig* config) override;
/**
* @brief
*/
void shutdown() override; void shutdown() override;
/**
* @brief
* @return true
*/
bool isInitialized() const override { return initialized_; } bool isInitialized() const override { return initialized_; }
/**
* @brief
* @param id
*/
void setModuleId(ModuleId id) { moduleId_ = id; } void setModuleId(ModuleId id) { moduleId_ = id; }
/**
* @brief
* @param config
*/
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; } void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
/**
* @brief
* @return
*/
IWindow* getWindow() const { return window_.get(); } IWindow* getWindow() const { return window_.get(); }
private: private:
bool initBackend(); /**
bool createWindow(const std::string& backend, const WindowConfigData& config); * @brief SDL2
void shutdownBackend(); * @return true
*/
bool initSDL2();
/**
* @brief SDL2
*/
void shutdownSDL2();
/**
* @brief
* @param config
* @return true
*/
bool createWindow(const WindowConfigData& config);
ModuleId moduleId_ = INVALID_MODULE_ID; ModuleId moduleId_ = INVALID_MODULE_ID;
bool initialized_ = false; bool initialized_ = false;
bool backendInitialized_ = false; bool sdl2Initialized_ = false;
std::string backend_;
WindowConfigData windowConfig_; WindowConfigData windowConfig_;
UniquePtr<IWindow> window_; UniquePtr<IWindow> window_;
}; };
/**
* @brief
* @return
*/
ModuleId get_window_module_id(); ModuleId get_window_module_id();
/**
* @brief
*/
void register_window_module(); void register_window_module();
} // namespace extra2d }

View File

@ -0,0 +1,52 @@
#pragma once
#include <string>
#include <vector>
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<std::string> 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;
};
}

View File

@ -2,6 +2,7 @@
#include <extra2d/config/config_module.h> #include <extra2d/config/config_module.h>
#include <extra2d/config/module_registry.h> #include <extra2d/config/module_registry.h>
#include <extra2d/graphics/render_module.h> #include <extra2d/graphics/render_module.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/platform/platform_init_module.h> #include <extra2d/platform/platform_init_module.h>
@ -287,10 +288,14 @@ void Application::mainLoop() {
render(); render();
const auto& appConfig = ConfigManager::instance().appConfig(); 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<const RenderModuleConfig*>(renderConfig);
if (renderModuleConfig && !renderModuleConfig->vsync && renderModuleConfig->targetFPS > 0) {
double frameEndTime = getTimeSeconds(); double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime; double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(appConfig.render.targetFPS); double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
if (frameTime < target) { if (frameTime < target) {
auto sleepSeconds = target - frameTime; auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds)); std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));

View File

@ -1,168 +1,11 @@
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <algorithm>
namespace extra2d { 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 AppConfig::createDefault() {
AppConfig config; 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.appName = "Extra2D App";
config.appVersion = "1.0.0"; config.appVersion = "1.0.0";
config.organization = ""; config.organization = "";
@ -172,150 +15,7 @@ AppConfig AppConfig::createDefault() {
return config; return config;
} }
/**
* @brief
*
* @return true false
*/
bool AppConfig::validate() const { 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()) { if (appName.empty()) {
E2D_LOG_ERROR("Config validation failed: app name cannot be empty"); E2D_LOG_ERROR("Config validation failed: app name cannot be empty");
return false; return false;
@ -332,333 +32,12 @@ bool AppConfig::validate() const {
return true; 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() { void AppConfig::reset() {
*this = createDefault(); *this = createDefault();
E2D_LOG_INFO("App config reset to defaults"); E2D_LOG_INFO("App config reset to defaults");
} }
/**
* @brief
* other
* @param other
*/
void AppConfig::merge(const AppConfig& 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") { if (other.appName != "Extra2D App") {
appName = other.appName; appName = other.appName;
} }

View File

@ -1,4 +1,6 @@
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <fstream> #include <fstream>
@ -39,54 +41,6 @@ static std::string toLower(const std::string& str) {
return result; 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 * @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(); 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) { ConfigLoadResult IniConfigLoader::load(const std::string& filepath, AppConfig& config) {
E2D_LOG_INFO("正在从 INI 文件加载配置: {}", filepath); E2D_LOG_INFO("正在从 INI 文件加载应用配置: {}", filepath);
std::ifstream file(filepath); std::ifstream file(filepath);
if (!file.is_open()) { if (!file.is_open()) {
@ -204,14 +148,8 @@ ConfigLoadResult IniConfigLoader::load(const std::string& filepath, AppConfig& c
return loadFromString(content, config); return loadFromString(content, config);
} }
/**
* @brief INI
* @param filepath
* @param config
* @return
*/
ConfigSaveResult IniConfigLoader::save(const std::string& filepath, const AppConfig& config) { 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); std::string content = saveToString(config);
@ -228,12 +166,6 @@ ConfigSaveResult IniConfigLoader::save(const std::string& filepath, const AppCon
return ConfigSaveResult::ok(); return ConfigSaveResult::ok();
} }
/**
* @brief INI
* @param content INI
* @param config
* @return
*/
ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, AppConfig& config) { ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
IniData data; IniData data;
auto result = parseIniContent(content, data); auto result = parseIniContent(content, data);
@ -250,499 +182,21 @@ ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, App
if (hasIniValue(data, "app", "organization")) { if (hasIniValue(data, "app", "organization")) {
config.organization = getIniValue(data, "app", "organization"); config.organization = getIniValue(data, "app", "organization");
} }
if (hasIniValue(data, "app", "configFile")) {
if (hasIniValue(data, "window", "title")) { config.configFile = getIniValue(data, "app", "configFile");
config.window.title = getIniValue(data, "window", "title");
} }
if (hasIniValue(data, "window", "width")) { if (hasIniValue(data, "app", "targetPlatform")) {
int value; int value;
auto res = parseInt(getIniValue(data, "window", "width"), value, "window.width"); auto res = parseInt(getIniValue(data, "app", "targetPlatform"), value, "app.targetPlatform");
if (res.hasError()) return res; if (res.isOk()) {
config.window.width = value; config.targetPlatform = static_cast<PlatformType>(value);
} }
if (hasIniValue(data, "window", "height")) {
int value;
auto res = parseInt(getIniValue(data, "window", "height"), value, "window.height");
if (res.hasError()) return res;
config.window.height = value;
}
if (hasIniValue(data, "window", "minWidth")) {
int value;
auto res = parseInt(getIniValue(data, "window", "minWidth"), value, "window.minWidth");
if (res.hasError()) return res;
config.window.minWidth = value;
}
if (hasIniValue(data, "window", "minHeight")) {
int value;
auto res = parseInt(getIniValue(data, "window", "minHeight"), value, "window.minHeight");
if (res.hasError()) return res;
config.window.minHeight = value;
}
if (hasIniValue(data, "window", "maxWidth")) {
int value;
auto res = parseInt(getIniValue(data, "window", "maxWidth"), value, "window.maxWidth");
if (res.hasError()) return res;
config.window.maxWidth = value;
}
if (hasIniValue(data, "window", "maxHeight")) {
int value;
auto res = parseInt(getIniValue(data, "window", "maxHeight"), value, "window.maxHeight");
if (res.hasError()) return res;
config.window.maxHeight = value;
}
if (hasIniValue(data, "window", "mode")) {
config.window.mode = stringToWindowMode(getIniValue(data, "window", "mode"));
}
if (hasIniValue(data, "window", "resizable")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "resizable"), value, "window.resizable");
if (res.hasError()) return res;
config.window.resizable = value;
}
if (hasIniValue(data, "window", "borderless")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "borderless"), value, "window.borderless");
if (res.hasError()) return res;
config.window.borderless = value;
}
if (hasIniValue(data, "window", "alwaysOnTop")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "alwaysOnTop"), value, "window.alwaysOnTop");
if (res.hasError()) return res;
config.window.alwaysOnTop = value;
}
if (hasIniValue(data, "window", "centered")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "centered"), value, "window.centered");
if (res.hasError()) return res;
config.window.centered = value;
}
if (hasIniValue(data, "window", "posX")) {
int value;
auto res = parseInt(getIniValue(data, "window", "posX"), value, "window.posX");
if (res.hasError()) return res;
config.window.posX = value;
}
if (hasIniValue(data, "window", "posY")) {
int value;
auto res = parseInt(getIniValue(data, "window", "posY"), value, "window.posY");
if (res.hasError()) return res;
config.window.posY = value;
}
if (hasIniValue(data, "window", "hideOnClose")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "hideOnClose"), value, "window.hideOnClose");
if (res.hasError()) return res;
config.window.hideOnClose = value;
}
if (hasIniValue(data, "window", "minimizeOnClose")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "minimizeOnClose"), value, "window.minimizeOnClose");
if (res.hasError()) return res;
config.window.minimizeOnClose = value;
}
if (hasIniValue(data, "window", "opacity")) {
float value;
auto res = parseFloat(getIniValue(data, "window", "opacity"), value, "window.opacity");
if (res.hasError()) return res;
config.window.opacity = value;
}
if (hasIniValue(data, "window", "transparentFramebuffer")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "transparentFramebuffer"), value, "window.transparentFramebuffer");
if (res.hasError()) return res;
config.window.transparentFramebuffer = value;
}
if (hasIniValue(data, "window", "highDPI")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "highDPI"), value, "window.highDPI");
if (res.hasError()) return res;
config.window.highDPI = value;
}
if (hasIniValue(data, "window", "contentScale")) {
float value;
auto res = parseFloat(getIniValue(data, "window", "contentScale"), value, "window.contentScale");
if (res.hasError()) return res;
config.window.contentScale = value;
} }
if (hasIniValue(data, "render", "backend")) { E2D_LOG_INFO("INI 应用配置加载成功");
config.render.backend = stringToBackendType(getIniValue(data, "render", "backend"));
}
if (hasIniValue(data, "render", "targetFPS")) {
int value;
auto res = parseInt(getIniValue(data, "render", "targetFPS"), value, "render.targetFPS");
if (res.hasError()) return res;
config.render.targetFPS = value;
}
if (hasIniValue(data, "render", "vsync")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "vsync"), value, "render.vsync");
if (res.hasError()) return res;
config.render.vsync = value;
}
if (hasIniValue(data, "render", "tripleBuffering")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "tripleBuffering"), value, "render.tripleBuffering");
if (res.hasError()) return res;
config.render.tripleBuffering = value;
}
if (hasIniValue(data, "render", "multisamples")) {
int value;
auto res = parseInt(getIniValue(data, "render", "multisamples"), value, "render.multisamples");
if (res.hasError()) return res;
config.render.multisamples = value;
}
if (hasIniValue(data, "render", "sRGBFramebuffer")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "sRGBFramebuffer"), value, "render.sRGBFramebuffer");
if (res.hasError()) return res;
config.render.sRGBFramebuffer = value;
}
if (hasIniValue(data, "render", "clearColorR")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorR"), value, "render.clearColorR");
if (res.hasError()) return res;
config.render.clearColor.r = value;
}
if (hasIniValue(data, "render", "clearColorG")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorG"), value, "render.clearColorG");
if (res.hasError()) return res;
config.render.clearColor.g = value;
}
if (hasIniValue(data, "render", "clearColorB")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorB"), value, "render.clearColorB");
if (res.hasError()) return res;
config.render.clearColor.b = value;
}
if (hasIniValue(data, "render", "clearColorA")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorA"), value, "render.clearColorA");
if (res.hasError()) return res;
config.render.clearColor.a = value;
}
if (hasIniValue(data, "render", "maxTextureSize")) {
int value;
auto res = parseInt(getIniValue(data, "render", "maxTextureSize"), value, "render.maxTextureSize");
if (res.hasError()) return res;
config.render.maxTextureSize = value;
}
if (hasIniValue(data, "render", "textureAnisotropy")) {
int value;
auto res = parseInt(getIniValue(data, "render", "textureAnisotropy"), value, "render.textureAnisotropy");
if (res.hasError()) return res;
config.render.textureAnisotropy = value;
}
if (hasIniValue(data, "render", "wireframeMode")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "wireframeMode"), value, "render.wireframeMode");
if (res.hasError()) return res;
config.render.wireframeMode = value;
}
if (hasIniValue(data, "render", "depthTest")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "depthTest"), value, "render.depthTest");
if (res.hasError()) return res;
config.render.depthTest = value;
}
if (hasIniValue(data, "render", "blending")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "blending"), value, "render.blending");
if (res.hasError()) return res;
config.render.blending = value;
}
if (hasIniValue(data, "render", "dithering")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "dithering"), value, "render.dithering");
if (res.hasError()) return res;
config.render.dithering = value;
}
if (hasIniValue(data, "render", "spriteBatchSize")) {
int value;
auto res = parseInt(getIniValue(data, "render", "spriteBatchSize"), value, "render.spriteBatchSize");
if (res.hasError()) return res;
config.render.spriteBatchSize = value;
}
if (hasIniValue(data, "render", "maxRenderTargets")) {
int value;
auto res = parseInt(getIniValue(data, "render", "maxRenderTargets"), value, "render.maxRenderTargets");
if (res.hasError()) return res;
config.render.maxRenderTargets = value;
}
if (hasIniValue(data, "render", "allowShaderHotReload")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "allowShaderHotReload"), value, "render.allowShaderHotReload");
if (res.hasError()) return res;
config.render.allowShaderHotReload = value;
}
if (hasIniValue(data, "render", "shaderCachePath")) {
config.render.shaderCachePath = getIniValue(data, "render", "shaderCachePath");
}
if (hasIniValue(data, "audio", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "audio", "enabled"), value, "audio.enabled");
if (res.hasError()) return res;
config.audio.enabled = value;
}
if (hasIniValue(data, "audio", "masterVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "masterVolume"), value, "audio.masterVolume");
if (res.hasError()) return res;
config.audio.masterVolume = value;
}
if (hasIniValue(data, "audio", "musicVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "musicVolume"), value, "audio.musicVolume");
if (res.hasError()) return res;
config.audio.musicVolume = value;
}
if (hasIniValue(data, "audio", "sfxVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "sfxVolume"), value, "audio.sfxVolume");
if (res.hasError()) return res;
config.audio.sfxVolume = value;
}
if (hasIniValue(data, "audio", "voiceVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "voiceVolume"), value, "audio.voiceVolume");
if (res.hasError()) return res;
config.audio.voiceVolume = value;
}
if (hasIniValue(data, "audio", "ambientVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "ambientVolume"), value, "audio.ambientVolume");
if (res.hasError()) return res;
config.audio.ambientVolume = value;
}
if (hasIniValue(data, "audio", "frequency")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "frequency"), value, "audio.frequency");
if (res.hasError()) return res;
config.audio.frequency = value;
}
if (hasIniValue(data, "audio", "channels")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "channels"), value, "audio.channels");
if (res.hasError()) return res;
config.audio.channels = value;
}
if (hasIniValue(data, "audio", "chunkSize")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "chunkSize"), value, "audio.chunkSize");
if (res.hasError()) return res;
config.audio.chunkSize = value;
}
if (hasIniValue(data, "audio", "maxChannels")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "maxChannels"), value, "audio.maxChannels");
if (res.hasError()) return res;
config.audio.maxChannels = value;
}
if (hasIniValue(data, "audio", "spatialAudio")) {
bool value;
auto res = parseBool(getIniValue(data, "audio", "spatialAudio"), value, "audio.spatialAudio");
if (res.hasError()) return res;
config.audio.spatialAudio = value;
}
if (hasIniValue(data, "debug", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "enabled"), value, "debug.enabled");
if (res.hasError()) return res;
config.debug.enabled = value;
}
if (hasIniValue(data, "debug", "showFPS")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showFPS"), value, "debug.showFPS");
if (res.hasError()) return res;
config.debug.showFPS = value;
}
if (hasIniValue(data, "debug", "showMemoryUsage")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showMemoryUsage"), value, "debug.showMemoryUsage");
if (res.hasError()) return res;
config.debug.showMemoryUsage = value;
}
if (hasIniValue(data, "debug", "showRenderStats")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showRenderStats"), value, "debug.showRenderStats");
if (res.hasError()) return res;
config.debug.showRenderStats = value;
}
if (hasIniValue(data, "debug", "showColliders")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showColliders"), value, "debug.showColliders");
if (res.hasError()) return res;
config.debug.showColliders = value;
}
if (hasIniValue(data, "debug", "showGrid")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showGrid"), value, "debug.showGrid");
if (res.hasError()) return res;
config.debug.showGrid = value;
}
if (hasIniValue(data, "debug", "logToFile")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "logToFile"), value, "debug.logToFile");
if (res.hasError()) return res;
config.debug.logToFile = value;
}
if (hasIniValue(data, "debug", "logToConsole")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "logToConsole"), value, "debug.logToConsole");
if (res.hasError()) return res;
config.debug.logToConsole = value;
}
if (hasIniValue(data, "debug", "logLevel")) {
int value;
auto res = parseInt(getIniValue(data, "debug", "logLevel"), value, "debug.logLevel");
if (res.hasError()) return res;
config.debug.logLevel = value;
}
if (hasIniValue(data, "debug", "breakOnAssert")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "breakOnAssert"), value, "debug.breakOnAssert");
if (res.hasError()) return res;
config.debug.breakOnAssert = value;
}
if (hasIniValue(data, "debug", "enableProfiling")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "enableProfiling"), value, "debug.enableProfiling");
if (res.hasError()) return res;
config.debug.enableProfiling = value;
}
if (hasIniValue(data, "debug", "logFilePath")) {
config.debug.logFilePath = getIniValue(data, "debug", "logFilePath");
}
if (hasIniValue(data, "input", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "enabled"), value, "input.enabled");
if (res.hasError()) return res;
config.input.enabled = value;
}
if (hasIniValue(data, "input", "rawMouseInput")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "rawMouseInput"), value, "input.rawMouseInput");
if (res.hasError()) return res;
config.input.rawMouseInput = value;
}
if (hasIniValue(data, "input", "mouseSensitivity")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "mouseSensitivity"), value, "input.mouseSensitivity");
if (res.hasError()) return res;
config.input.mouseSensitivity = value;
}
if (hasIniValue(data, "input", "invertMouseY")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "invertMouseY"), value, "input.invertMouseY");
if (res.hasError()) return res;
config.input.invertMouseY = value;
}
if (hasIniValue(data, "input", "invertMouseX")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "invertMouseX"), value, "input.invertMouseX");
if (res.hasError()) return res;
config.input.invertMouseX = value;
}
if (hasIniValue(data, "input", "deadzone")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "deadzone"), value, "input.deadzone");
if (res.hasError()) return res;
config.input.deadzone = value;
}
if (hasIniValue(data, "input", "triggerThreshold")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "triggerThreshold"), value, "input.triggerThreshold");
if (res.hasError()) return res;
config.input.triggerThreshold = value;
}
if (hasIniValue(data, "input", "enableVibration")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "enableVibration"), value, "input.enableVibration");
if (res.hasError()) return res;
config.input.enableVibration = value;
}
if (hasIniValue(data, "input", "maxGamepads")) {
int value;
auto res = parseInt(getIniValue(data, "input", "maxGamepads"), value, "input.maxGamepads");
if (res.hasError()) return res;
config.input.maxGamepads = value;
}
if (hasIniValue(data, "input", "autoConnectGamepads")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "autoConnectGamepads"), value, "input.autoConnectGamepads");
if (res.hasError()) return res;
config.input.autoConnectGamepads = value;
}
if (hasIniValue(data, "input", "gamepadMappingFile")) {
config.input.gamepadMappingFile = getIniValue(data, "input", "gamepadMappingFile");
}
if (hasIniValue(data, "resource", "assetRootPath")) {
config.resource.assetRootPath = getIniValue(data, "resource", "assetRootPath");
}
if (hasIniValue(data, "resource", "cachePath")) {
config.resource.cachePath = getIniValue(data, "resource", "cachePath");
}
if (hasIniValue(data, "resource", "savePath")) {
config.resource.savePath = getIniValue(data, "resource", "savePath");
}
if (hasIniValue(data, "resource", "configPath")) {
config.resource.configPath = getIniValue(data, "resource", "configPath");
}
if (hasIniValue(data, "resource", "logPath")) {
config.resource.logPath = getIniValue(data, "resource", "logPath");
}
if (hasIniValue(data, "resource", "useAssetCache")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "useAssetCache"), value, "resource.useAssetCache");
if (res.hasError()) return res;
config.resource.useAssetCache = value;
}
if (hasIniValue(data, "resource", "maxCacheSize")) {
int value;
auto res = parseInt(getIniValue(data, "resource", "maxCacheSize"), value, "resource.maxCacheSize");
if (res.hasError()) return res;
config.resource.maxCacheSize = value;
}
if (hasIniValue(data, "resource", "hotReloadEnabled")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "hotReloadEnabled"), value, "resource.hotReloadEnabled");
if (res.hasError()) return res;
config.resource.hotReloadEnabled = value;
}
if (hasIniValue(data, "resource", "hotReloadInterval")) {
float value;
auto res = parseFloat(getIniValue(data, "resource", "hotReloadInterval"), value, "resource.hotReloadInterval");
if (res.hasError()) return res;
config.resource.hotReloadInterval = value;
}
if (hasIniValue(data, "resource", "compressTextures")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "compressTextures"), value, "resource.compressTextures");
if (res.hasError()) return res;
config.resource.compressTextures = value;
}
if (hasIniValue(data, "resource", "preloadCommonAssets")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "preloadCommonAssets"), value, "resource.preloadCommonAssets");
if (res.hasError()) return res;
config.resource.preloadCommonAssets = value;
}
E2D_LOG_INFO("INI 配置加载成功");
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
} }
/**
* @brief INI
* @param config
* @return INI
*/
std::string IniConfigLoader::saveToString(const AppConfig& config) { std::string IniConfigLoader::saveToString(const AppConfig& config) {
std::ostringstream oss; std::ostringstream oss;
@ -750,118 +204,84 @@ std::string IniConfigLoader::saveToString(const AppConfig& config) {
oss << "name=" << config.appName << "\n"; oss << "name=" << config.appName << "\n";
oss << "version=" << config.appVersion << "\n"; oss << "version=" << config.appVersion << "\n";
oss << "organization=" << config.organization << "\n"; oss << "organization=" << config.organization << "\n";
oss << "\n"; oss << "configFile=" << config.configFile << "\n";
oss << "targetPlatform=" << static_cast<int>(config.targetPlatform) << "\n";
oss << "[window]\n";
oss << "title=" << config.window.title << "\n";
oss << "width=" << config.window.width << "\n";
oss << "height=" << config.window.height << "\n";
oss << "minWidth=" << config.window.minWidth << "\n";
oss << "minHeight=" << config.window.minHeight << "\n";
oss << "maxWidth=" << config.window.maxWidth << "\n";
oss << "maxHeight=" << config.window.maxHeight << "\n";
oss << "mode=" << windowModeToString(config.window.mode) << "\n";
oss << "resizable=" << (config.window.resizable ? "true" : "false") << "\n";
oss << "borderless=" << (config.window.borderless ? "true" : "false") << "\n";
oss << "alwaysOnTop=" << (config.window.alwaysOnTop ? "true" : "false") << "\n";
oss << "centered=" << (config.window.centered ? "true" : "false") << "\n";
oss << "posX=" << config.window.posX << "\n";
oss << "posY=" << config.window.posY << "\n";
oss << "hideOnClose=" << (config.window.hideOnClose ? "true" : "false") << "\n";
oss << "minimizeOnClose=" << (config.window.minimizeOnClose ? "true" : "false") << "\n";
oss << "opacity=" << config.window.opacity << "\n";
oss << "transparentFramebuffer=" << (config.window.transparentFramebuffer ? "true" : "false") << "\n";
oss << "highDPI=" << (config.window.highDPI ? "true" : "false") << "\n";
oss << "contentScale=" << config.window.contentScale << "\n";
oss << "\n";
oss << "[render]\n";
oss << "backend=" << backendTypeToString(config.render.backend) << "\n";
oss << "targetFPS=" << config.render.targetFPS << "\n";
oss << "vsync=" << (config.render.vsync ? "true" : "false") << "\n";
oss << "tripleBuffering=" << (config.render.tripleBuffering ? "true" : "false") << "\n";
oss << "multisamples=" << config.render.multisamples << "\n";
oss << "sRGBFramebuffer=" << (config.render.sRGBFramebuffer ? "true" : "false") << "\n";
oss << "clearColorR=" << config.render.clearColor.r << "\n";
oss << "clearColorG=" << config.render.clearColor.g << "\n";
oss << "clearColorB=" << config.render.clearColor.b << "\n";
oss << "clearColorA=" << config.render.clearColor.a << "\n";
oss << "maxTextureSize=" << config.render.maxTextureSize << "\n";
oss << "textureAnisotropy=" << config.render.textureAnisotropy << "\n";
oss << "wireframeMode=" << (config.render.wireframeMode ? "true" : "false") << "\n";
oss << "depthTest=" << (config.render.depthTest ? "true" : "false") << "\n";
oss << "blending=" << (config.render.blending ? "true" : "false") << "\n";
oss << "dithering=" << (config.render.dithering ? "true" : "false") << "\n";
oss << "spriteBatchSize=" << config.render.spriteBatchSize << "\n";
oss << "maxRenderTargets=" << config.render.maxRenderTargets << "\n";
oss << "allowShaderHotReload=" << (config.render.allowShaderHotReload ? "true" : "false") << "\n";
oss << "shaderCachePath=" << config.render.shaderCachePath << "\n";
oss << "\n";
oss << "[audio]\n";
oss << "enabled=" << (config.audio.enabled ? "true" : "false") << "\n";
oss << "masterVolume=" << config.audio.masterVolume << "\n";
oss << "musicVolume=" << config.audio.musicVolume << "\n";
oss << "sfxVolume=" << config.audio.sfxVolume << "\n";
oss << "voiceVolume=" << config.audio.voiceVolume << "\n";
oss << "ambientVolume=" << config.audio.ambientVolume << "\n";
oss << "frequency=" << config.audio.frequency << "\n";
oss << "channels=" << config.audio.channels << "\n";
oss << "chunkSize=" << config.audio.chunkSize << "\n";
oss << "maxChannels=" << config.audio.maxChannels << "\n";
oss << "spatialAudio=" << (config.audio.spatialAudio ? "true" : "false") << "\n";
oss << "\n";
oss << "[debug]\n";
oss << "enabled=" << (config.debug.enabled ? "true" : "false") << "\n";
oss << "showFPS=" << (config.debug.showFPS ? "true" : "false") << "\n";
oss << "showMemoryUsage=" << (config.debug.showMemoryUsage ? "true" : "false") << "\n";
oss << "showRenderStats=" << (config.debug.showRenderStats ? "true" : "false") << "\n";
oss << "showColliders=" << (config.debug.showColliders ? "true" : "false") << "\n";
oss << "showGrid=" << (config.debug.showGrid ? "true" : "false") << "\n";
oss << "logToFile=" << (config.debug.logToFile ? "true" : "false") << "\n";
oss << "logToConsole=" << (config.debug.logToConsole ? "true" : "false") << "\n";
oss << "logLevel=" << config.debug.logLevel << "\n";
oss << "breakOnAssert=" << (config.debug.breakOnAssert ? "true" : "false") << "\n";
oss << "enableProfiling=" << (config.debug.enableProfiling ? "true" : "false") << "\n";
oss << "logFilePath=" << config.debug.logFilePath << "\n";
oss << "\n";
oss << "[input]\n";
oss << "enabled=" << (config.input.enabled ? "true" : "false") << "\n";
oss << "rawMouseInput=" << (config.input.rawMouseInput ? "true" : "false") << "\n";
oss << "mouseSensitivity=" << config.input.mouseSensitivity << "\n";
oss << "invertMouseY=" << (config.input.invertMouseY ? "true" : "false") << "\n";
oss << "invertMouseX=" << (config.input.invertMouseX ? "true" : "false") << "\n";
oss << "deadzone=" << config.input.deadzone << "\n";
oss << "triggerThreshold=" << config.input.triggerThreshold << "\n";
oss << "enableVibration=" << (config.input.enableVibration ? "true" : "false") << "\n";
oss << "maxGamepads=" << config.input.maxGamepads << "\n";
oss << "autoConnectGamepads=" << (config.input.autoConnectGamepads ? "true" : "false") << "\n";
oss << "gamepadMappingFile=" << config.input.gamepadMappingFile << "\n";
oss << "\n";
oss << "[resource]\n";
oss << "assetRootPath=" << config.resource.assetRootPath << "\n";
oss << "cachePath=" << config.resource.cachePath << "\n";
oss << "savePath=" << config.resource.savePath << "\n";
oss << "configPath=" << config.resource.configPath << "\n";
oss << "logPath=" << config.resource.logPath << "\n";
oss << "useAssetCache=" << (config.resource.useAssetCache ? "true" : "false") << "\n";
oss << "maxCacheSize=" << config.resource.maxCacheSize << "\n";
oss << "hotReloadEnabled=" << (config.resource.hotReloadEnabled ? "true" : "false") << "\n";
oss << "hotReloadInterval=" << config.resource.hotReloadInterval << "\n";
oss << "compressTextures=" << (config.resource.compressTextures ? "true" : "false") << "\n";
oss << "preloadCommonAssets=" << (config.resource.preloadCommonAssets ? "true" : "false") << "\n";
return oss.str(); return oss.str();
} }
/** ConfigLoadResult IniConfigLoader::loadWithModules(const std::string& filepath) {
* @brief E2D_LOG_INFO("正在从 INI 文件加载完整配置(含模块): {}", filepath);
* @param filepath
* @return true 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 { bool IniConfigLoader::supportsFile(const std::string& filepath) const {
if (filepath.length() >= 4) { if (filepath.length() >= 4) {
std::string ext = filepath.substr(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; return false;
} }
/**
* @brief
* @return
*/
UniquePtr<ConfigLoader> IniConfigLoader::clone() const { UniquePtr<ConfigLoader> IniConfigLoader::clone() const {
return makeUnique<IniConfigLoader>(); return makeUnique<IniConfigLoader>();
} }
/**
* @brief
* @param section
* @param key
* @return
*/
std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const { std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const {
return section + "." + key; return section + "." + key;
} }
/**
* @brief
* @param value
* @param result
* @param fieldName
* @return
*/
ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result, const std::string& fieldName) { ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result, const std::string& fieldName) {
try { try {
size_t pos; 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) { ConfigLoadResult IniConfigLoader::parseFloat(const std::string& value, float& result, const std::string& fieldName) {
try { try {
size_t pos; 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) { ConfigLoadResult IniConfigLoader::parseBool(const std::string& value, bool& result, const std::string& fieldName) {
std::string lower = toLower(value); std::string lower = toLower(value);
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") { if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {

View File

@ -1,4 +1,5 @@
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -9,64 +10,8 @@ using json = nlohmann::json;
namespace extra2d { 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) { ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig& config) {
E2D_LOG_INFO("正在从 JSON 文件加载配置: {}", filepath); E2D_LOG_INFO("正在从 JSON 文件加载应用配置: {}", filepath);
std::ifstream file(filepath); std::ifstream file(filepath);
if (!file.is_open()) { if (!file.is_open()) {
@ -82,14 +27,8 @@ ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig&
return loadFromString(content, config); return loadFromString(content, config);
} }
/**
* @brief JSON
* @param filepath
* @param config
* @return
*/
ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppConfig& config) { 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); std::string content = saveToString(config);
@ -106,12 +45,6 @@ ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppCo
return ConfigSaveResult::ok(); return ConfigSaveResult::ok();
} }
/**
* @brief JSON
* @param content JSON
* @param config
* @return
*/
ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) { ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
json root; json root;
@ -135,79 +68,110 @@ ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, Ap
config.organization = root["organization"].get<std::string>(); config.organization = root["organization"].get<std::string>();
} }
if (root.contains("window")) { if (root.contains("configFile") && root["configFile"].is_string()) {
auto result = parseWindowConfig(&root["window"], config.window); config.configFile = root["configFile"].get<std::string>();
if (result.hasError()) {
return result;
}
} }
if (root.contains("render")) { if (root.contains("targetPlatform") && root["targetPlatform"].is_number_integer()) {
auto result = parseRenderConfig(&root["render"], config.render); config.targetPlatform = static_cast<PlatformType>(root["targetPlatform"].get<int>());
if (result.hasError()) {
return result;
}
} }
if (root.contains("audio")) { E2D_LOG_INFO("JSON 应用配置加载成功");
auto result = parseAudioConfig(&root["audio"], config.audio);
if (result.hasError()) {
return result;
}
}
if (root.contains("debug")) {
auto result = parseDebugConfig(&root["debug"], config.debug);
if (result.hasError()) {
return result;
}
}
if (root.contains("input")) {
auto result = parseInputConfig(&root["input"], config.input);
if (result.hasError()) {
return result;
}
}
if (root.contains("resource")) {
auto result = parseResourceConfig(&root["resource"], config.resource);
if (result.hasError()) {
return result;
}
}
E2D_LOG_INFO("JSON 配置加载成功");
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
} }
/**
* @brief JSON
* @param config
* @return JSON
*/
std::string JsonConfigLoader::saveToString(const AppConfig& config) { std::string JsonConfigLoader::saveToString(const AppConfig& config) {
json root; json root;
root["appName"] = config.appName; root["appName"] = config.appName;
root["appVersion"] = config.appVersion; root["appVersion"] = config.appVersion;
root["organization"] = config.organization; root["organization"] = config.organization;
root["configFile"] = config.configFile;
serializeWindowConfig(&root, config.window); root["targetPlatform"] = static_cast<int>(config.targetPlatform);
serializeRenderConfig(&root, config.render);
serializeAudioConfig(&root, config.audio);
serializeDebugConfig(&root, config.debug);
serializeInputConfig(&root, config.input);
serializeResourceConfig(&root, config.resource);
return root.dump(4); return root.dump(4);
} }
/** ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) {
* @brief E2D_LOG_INFO("正在从 JSON 文件加载完整配置(含模块): {}", filepath);
* @param filepath
* @return true 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<int>(e.byte));
}
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
if (root.contains(sectionName)) {
if (!moduleConfig->loadFromJson(&root[sectionName])) {
E2D_LOG_WARN("模块 {} 配置加载失败", moduleConfig->getModuleInfo().name);
} else {
E2D_LOG_DEBUG("模块 {} 配置加载成功", moduleConfig->getModuleInfo().name);
}
}
}
E2D_LOG_INFO("完整配置加载成功");
return ConfigLoadResult::ok();
}
ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) {
E2D_LOG_INFO("正在保存完整配置(含模块)到 JSON 文件: {}", filepath);
json root;
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
json sectionJson;
if (moduleConfig->saveToJson(&sectionJson)) {
root[sectionName] = sectionJson;
}
}
std::ofstream file(filepath);
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 { bool JsonConfigLoader::supportsFile(const std::string& filepath) const {
if (filepath.length() >= 5) { if (filepath.length() >= 5) {
std::string ext = filepath.substr(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; return false;
} }
/**
* @brief
* @return
*/
UniquePtr<ConfigLoader> JsonConfigLoader::clone() const { UniquePtr<ConfigLoader> JsonConfigLoader::clone() const {
return makeUnique<JsonConfigLoader>(); return makeUnique<JsonConfigLoader>();
} }
/**
* @brief
* @param jsonValue JSON
* @param window
* @return
*/
ConfigLoadResult JsonConfigLoader::parseWindowConfig(const void* jsonValue, WindowConfigData& window) {
const json& obj = *static_cast<const json*>(jsonValue);
if (!obj.is_object()) {
return ConfigLoadResult::error("window 配置必须是一个对象", -1, "window");
}
if (obj.contains("title") && obj["title"].is_string()) {
window.title = obj["title"].get<std::string>();
}
if (obj.contains("width") && obj["width"].is_number_integer()) {
window.width = obj["width"].get<int>();
}
if (obj.contains("height") && obj["height"].is_number_integer()) {
window.height = obj["height"].get<int>();
}
if (obj.contains("minWidth") && obj["minWidth"].is_number_integer()) {
window.minWidth = obj["minWidth"].get<int>();
}
if (obj.contains("minHeight") && obj["minHeight"].is_number_integer()) {
window.minHeight = obj["minHeight"].get<int>();
}
if (obj.contains("maxWidth") && obj["maxWidth"].is_number_integer()) {
window.maxWidth = obj["maxWidth"].get<int>();
}
if (obj.contains("maxHeight") && obj["maxHeight"].is_number_integer()) {
window.maxHeight = obj["maxHeight"].get<int>();
}
if (obj.contains("mode") && obj["mode"].is_string()) {
window.mode = stringToWindowMode(obj["mode"].get<std::string>());
}
if (obj.contains("resizable") && obj["resizable"].is_boolean()) {
window.resizable = obj["resizable"].get<bool>();
}
if (obj.contains("borderless") && obj["borderless"].is_boolean()) {
window.borderless = obj["borderless"].get<bool>();
}
if (obj.contains("alwaysOnTop") && obj["alwaysOnTop"].is_boolean()) {
window.alwaysOnTop = obj["alwaysOnTop"].get<bool>();
}
if (obj.contains("centered") && obj["centered"].is_boolean()) {
window.centered = obj["centered"].get<bool>();
}
if (obj.contains("posX") && obj["posX"].is_number_integer()) {
window.posX = obj["posX"].get<int>();
}
if (obj.contains("posY") && obj["posY"].is_number_integer()) {
window.posY = obj["posY"].get<int>();
}
if (obj.contains("hideOnClose") && obj["hideOnClose"].is_boolean()) {
window.hideOnClose = obj["hideOnClose"].get<bool>();
}
if (obj.contains("minimizeOnClose") && obj["minimizeOnClose"].is_boolean()) {
window.minimizeOnClose = obj["minimizeOnClose"].get<bool>();
}
if (obj.contains("opacity") && obj["opacity"].is_number()) {
window.opacity = obj["opacity"].get<float>();
}
if (obj.contains("transparentFramebuffer") && obj["transparentFramebuffer"].is_boolean()) {
window.transparentFramebuffer = obj["transparentFramebuffer"].get<bool>();
}
if (obj.contains("highDPI") && obj["highDPI"].is_boolean()) {
window.highDPI = obj["highDPI"].get<bool>();
}
if (obj.contains("contentScale") && obj["contentScale"].is_number()) {
window.contentScale = obj["contentScale"].get<float>();
}
return ConfigLoadResult::ok();
}
/**
* @brief
* @param jsonValue JSON
* @param render
* @return
*/
ConfigLoadResult JsonConfigLoader::parseRenderConfig(const void* jsonValue, RenderConfigData& render) {
const json& obj = *static_cast<const json*>(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<std::string>());
}
if (obj.contains("targetFPS") && obj["targetFPS"].is_number_integer()) {
render.targetFPS = obj["targetFPS"].get<int>();
}
if (obj.contains("vsync") && obj["vsync"].is_boolean()) {
render.vsync = obj["vsync"].get<bool>();
}
if (obj.contains("tripleBuffering") && obj["tripleBuffering"].is_boolean()) {
render.tripleBuffering = obj["tripleBuffering"].get<bool>();
}
if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) {
render.multisamples = obj["multisamples"].get<int>();
}
if (obj.contains("sRGBFramebuffer") && obj["sRGBFramebuffer"].is_boolean()) {
render.sRGBFramebuffer = obj["sRGBFramebuffer"].get<bool>();
}
if (obj.contains("clearColor") && obj["clearColor"].is_array() && obj["clearColor"].size() >= 4) {
render.clearColor.r = obj["clearColor"][0].get<float>();
render.clearColor.g = obj["clearColor"][1].get<float>();
render.clearColor.b = obj["clearColor"][2].get<float>();
render.clearColor.a = obj["clearColor"][3].get<float>();
}
if (obj.contains("maxTextureSize") && obj["maxTextureSize"].is_number_integer()) {
render.maxTextureSize = obj["maxTextureSize"].get<int>();
}
if (obj.contains("textureAnisotropy") && obj["textureAnisotropy"].is_number_integer()) {
render.textureAnisotropy = obj["textureAnisotropy"].get<int>();
}
if (obj.contains("wireframeMode") && obj["wireframeMode"].is_boolean()) {
render.wireframeMode = obj["wireframeMode"].get<bool>();
}
if (obj.contains("depthTest") && obj["depthTest"].is_boolean()) {
render.depthTest = obj["depthTest"].get<bool>();
}
if (obj.contains("blending") && obj["blending"].is_boolean()) {
render.blending = obj["blending"].get<bool>();
}
if (obj.contains("dithering") && obj["dithering"].is_boolean()) {
render.dithering = obj["dithering"].get<bool>();
}
if (obj.contains("spriteBatchSize") && obj["spriteBatchSize"].is_number_integer()) {
render.spriteBatchSize = obj["spriteBatchSize"].get<int>();
}
if (obj.contains("maxRenderTargets") && obj["maxRenderTargets"].is_number_integer()) {
render.maxRenderTargets = obj["maxRenderTargets"].get<int>();
}
if (obj.contains("allowShaderHotReload") && obj["allowShaderHotReload"].is_boolean()) {
render.allowShaderHotReload = obj["allowShaderHotReload"].get<bool>();
}
if (obj.contains("shaderCachePath") && obj["shaderCachePath"].is_string()) {
render.shaderCachePath = obj["shaderCachePath"].get<std::string>();
}
return ConfigLoadResult::ok();
}
/**
* @brief
* @param jsonValue JSON
* @param audio
* @return
*/
ConfigLoadResult JsonConfigLoader::parseAudioConfig(const void* jsonValue, AudioConfigData& audio) {
const json& obj = *static_cast<const json*>(jsonValue);
if (!obj.is_object()) {
return ConfigLoadResult::error("audio 配置必须是一个对象", -1, "audio");
}
if (obj.contains("enabled") && obj["enabled"].is_boolean()) {
audio.enabled = obj["enabled"].get<bool>();
}
if (obj.contains("masterVolume") && obj["masterVolume"].is_number_integer()) {
audio.masterVolume = obj["masterVolume"].get<int>();
}
if (obj.contains("musicVolume") && obj["musicVolume"].is_number_integer()) {
audio.musicVolume = obj["musicVolume"].get<int>();
}
if (obj.contains("sfxVolume") && obj["sfxVolume"].is_number_integer()) {
audio.sfxVolume = obj["sfxVolume"].get<int>();
}
if (obj.contains("voiceVolume") && obj["voiceVolume"].is_number_integer()) {
audio.voiceVolume = obj["voiceVolume"].get<int>();
}
if (obj.contains("ambientVolume") && obj["ambientVolume"].is_number_integer()) {
audio.ambientVolume = obj["ambientVolume"].get<int>();
}
if (obj.contains("frequency") && obj["frequency"].is_number_integer()) {
audio.frequency = obj["frequency"].get<int>();
}
if (obj.contains("channels") && obj["channels"].is_number_integer()) {
audio.channels = obj["channels"].get<int>();
}
if (obj.contains("chunkSize") && obj["chunkSize"].is_number_integer()) {
audio.chunkSize = obj["chunkSize"].get<int>();
}
if (obj.contains("maxChannels") && obj["maxChannels"].is_number_integer()) {
audio.maxChannels = obj["maxChannels"].get<int>();
}
if (obj.contains("spatialAudio") && obj["spatialAudio"].is_boolean()) {
audio.spatialAudio = obj["spatialAudio"].get<bool>();
}
if (obj.contains("listenerPosition") && obj["listenerPosition"].is_array() && obj["listenerPosition"].size() >= 3) {
audio.listenerPosition[0] = obj["listenerPosition"][0].get<float>();
audio.listenerPosition[1] = obj["listenerPosition"][1].get<float>();
audio.listenerPosition[2] = obj["listenerPosition"][2].get<float>();
}
return ConfigLoadResult::ok();
}
/**
* @brief
* @param jsonValue JSON
* @param debug
* @return
*/
ConfigLoadResult JsonConfigLoader::parseDebugConfig(const void* jsonValue, DebugConfigData& debug) {
const json& obj = *static_cast<const json*>(jsonValue);
if (!obj.is_object()) {
return ConfigLoadResult::error("debug 配置必须是一个对象", -1, "debug");
}
if (obj.contains("enabled") && obj["enabled"].is_boolean()) {
debug.enabled = obj["enabled"].get<bool>();
}
if (obj.contains("showFPS") && obj["showFPS"].is_boolean()) {
debug.showFPS = obj["showFPS"].get<bool>();
}
if (obj.contains("showMemoryUsage") && obj["showMemoryUsage"].is_boolean()) {
debug.showMemoryUsage = obj["showMemoryUsage"].get<bool>();
}
if (obj.contains("showRenderStats") && obj["showRenderStats"].is_boolean()) {
debug.showRenderStats = obj["showRenderStats"].get<bool>();
}
if (obj.contains("showColliders") && obj["showColliders"].is_boolean()) {
debug.showColliders = obj["showColliders"].get<bool>();
}
if (obj.contains("showGrid") && obj["showGrid"].is_boolean()) {
debug.showGrid = obj["showGrid"].get<bool>();
}
if (obj.contains("logToFile") && obj["logToFile"].is_boolean()) {
debug.logToFile = obj["logToFile"].get<bool>();
}
if (obj.contains("logToConsole") && obj["logToConsole"].is_boolean()) {
debug.logToConsole = obj["logToConsole"].get<bool>();
}
if (obj.contains("logLevel") && obj["logLevel"].is_number_integer()) {
debug.logLevel = obj["logLevel"].get<int>();
}
if (obj.contains("breakOnAssert") && obj["breakOnAssert"].is_boolean()) {
debug.breakOnAssert = obj["breakOnAssert"].get<bool>();
}
if (obj.contains("enableProfiling") && obj["enableProfiling"].is_boolean()) {
debug.enableProfiling = obj["enableProfiling"].get<bool>();
}
if (obj.contains("logFilePath") && obj["logFilePath"].is_string()) {
debug.logFilePath = obj["logFilePath"].get<std::string>();
}
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<std::string>());
}
}
}
return ConfigLoadResult::ok();
}
/**
* @brief
* @param jsonValue JSON
* @param input
* @return
*/
ConfigLoadResult JsonConfigLoader::parseInputConfig(const void* jsonValue, InputConfigData& input) {
const json& obj = *static_cast<const json*>(jsonValue);
if (!obj.is_object()) {
return ConfigLoadResult::error("input 配置必须是一个对象", -1, "input");
}
if (obj.contains("enabled") && obj["enabled"].is_boolean()) {
input.enabled = obj["enabled"].get<bool>();
}
if (obj.contains("rawMouseInput") && obj["rawMouseInput"].is_boolean()) {
input.rawMouseInput = obj["rawMouseInput"].get<bool>();
}
if (obj.contains("mouseSensitivity") && obj["mouseSensitivity"].is_number()) {
input.mouseSensitivity = obj["mouseSensitivity"].get<float>();
}
if (obj.contains("invertMouseY") && obj["invertMouseY"].is_boolean()) {
input.invertMouseY = obj["invertMouseY"].get<bool>();
}
if (obj.contains("invertMouseX") && obj["invertMouseX"].is_boolean()) {
input.invertMouseX = obj["invertMouseX"].get<bool>();
}
if (obj.contains("deadzone") && obj["deadzone"].is_number()) {
input.deadzone = obj["deadzone"].get<float>();
}
if (obj.contains("triggerThreshold") && obj["triggerThreshold"].is_number()) {
input.triggerThreshold = obj["triggerThreshold"].get<float>();
}
if (obj.contains("enableVibration") && obj["enableVibration"].is_boolean()) {
input.enableVibration = obj["enableVibration"].get<bool>();
}
if (obj.contains("maxGamepads") && obj["maxGamepads"].is_number_integer()) {
input.maxGamepads = obj["maxGamepads"].get<int>();
}
if (obj.contains("autoConnectGamepads") && obj["autoConnectGamepads"].is_boolean()) {
input.autoConnectGamepads = obj["autoConnectGamepads"].get<bool>();
}
if (obj.contains("gamepadMappingFile") && obj["gamepadMappingFile"].is_string()) {
input.gamepadMappingFile = obj["gamepadMappingFile"].get<std::string>();
}
return ConfigLoadResult::ok();
}
/**
* @brief
* @param jsonValue JSON
* @param resource
* @return
*/
ConfigLoadResult JsonConfigLoader::parseResourceConfig(const void* jsonValue, ResourceConfigData& resource) {
const json& obj = *static_cast<const json*>(jsonValue);
if (!obj.is_object()) {
return ConfigLoadResult::error("resource 配置必须是一个对象", -1, "resource");
}
if (obj.contains("assetRootPath") && obj["assetRootPath"].is_string()) {
resource.assetRootPath = obj["assetRootPath"].get<std::string>();
}
if (obj.contains("cachePath") && obj["cachePath"].is_string()) {
resource.cachePath = obj["cachePath"].get<std::string>();
}
if (obj.contains("savePath") && obj["savePath"].is_string()) {
resource.savePath = obj["savePath"].get<std::string>();
}
if (obj.contains("configPath") && obj["configPath"].is_string()) {
resource.configPath = obj["configPath"].get<std::string>();
}
if (obj.contains("logPath") && obj["logPath"].is_string()) {
resource.logPath = obj["logPath"].get<std::string>();
}
if (obj.contains("useAssetCache") && obj["useAssetCache"].is_boolean()) {
resource.useAssetCache = obj["useAssetCache"].get<bool>();
}
if (obj.contains("maxCacheSize") && obj["maxCacheSize"].is_number_integer()) {
resource.maxCacheSize = obj["maxCacheSize"].get<int>();
}
if (obj.contains("hotReloadEnabled") && obj["hotReloadEnabled"].is_boolean()) {
resource.hotReloadEnabled = obj["hotReloadEnabled"].get<bool>();
}
if (obj.contains("hotReloadInterval") && obj["hotReloadInterval"].is_number()) {
resource.hotReloadInterval = obj["hotReloadInterval"].get<float>();
}
if (obj.contains("compressTextures") && obj["compressTextures"].is_boolean()) {
resource.compressTextures = obj["compressTextures"].get<bool>();
}
if (obj.contains("preloadCommonAssets") && obj["preloadCommonAssets"].is_boolean()) {
resource.preloadCommonAssets = obj["preloadCommonAssets"].get<bool>();
}
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<std::string>());
}
}
}
return ConfigLoadResult::ok();
}
/**
* @brief JSON
* @param jsonValue JSON
* @param window
*/
void JsonConfigLoader::serializeWindowConfig(void* jsonValue, const WindowConfigData& window) {
json& root = *static_cast<json*>(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<json*>(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<json*>(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<json*>(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<json*>(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<json*>(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;
}
} }

View File

@ -6,10 +6,6 @@
namespace extra2d { namespace extra2d {
/**
* @brief
*
*/
ConfigManager::ConfigManager() ConfigManager::ConfigManager()
: m_nextCallbackId(1) : m_nextCallbackId(1)
, m_autoSaveEnabled(false) , m_autoSaveEnabled(false)
@ -19,32 +15,17 @@ ConfigManager::ConfigManager()
m_appConfig = AppConfig::createDefault(); m_appConfig = AppConfig::createDefault();
} }
/**
* @brief
*
*/
ConfigManager::~ConfigManager() { ConfigManager::~ConfigManager() {
if (m_initialized) { if (m_initialized) {
shutdown(); shutdown();
} }
} }
/**
* @brief
* 使线
* @return
*/
ConfigManager& ConfigManager::instance() { ConfigManager& ConfigManager::instance() {
static ConfigManager instance; static ConfigManager instance;
return instance; return instance;
} }
/**
* @brief
*
* @param configPath
* @return true
*/
bool ConfigManager::initialize(const std::string& configPath) { bool ConfigManager::initialize(const std::string& configPath) {
if (m_initialized) { if (m_initialized) {
E2D_LOG_WARN("ConfigManager already initialized"); E2D_LOG_WARN("ConfigManager already initialized");
@ -70,8 +51,6 @@ bool ConfigManager::initialize(const std::string& configPath) {
m_appConfig = AppConfig::createDefault(); m_appConfig = AppConfig::createDefault();
m_appConfig.targetPlatform = PlatformDetector::detect(); m_appConfig.targetPlatform = PlatformDetector::detect();
m_platformConfig->applyDefaults(m_appConfig);
m_initialized = true; m_initialized = true;
m_modified = false; m_modified = false;
@ -80,10 +59,6 @@ bool ConfigManager::initialize(const std::string& configPath) {
return true; return true;
} }
/**
* @brief
*
*/
void ConfigManager::shutdown() { void ConfigManager::shutdown() {
if (!m_initialized) { if (!m_initialized) {
return; return;
@ -96,7 +71,7 @@ void ConfigManager::shutdown() {
} }
m_changeCallbacks.clear(); m_changeCallbacks.clear();
m_moduleConfigs.clear(); m_rawValues.clear();
m_loader.reset(); m_loader.reset();
m_platformConfig.reset(); m_platformConfig.reset();
@ -106,19 +81,10 @@ void ConfigManager::shutdown() {
E2D_LOG_INFO("ConfigManager shutdown complete"); E2D_LOG_INFO("ConfigManager shutdown complete");
} }
/**
* @brief
* @return true
*/
bool ConfigManager::isInitialized() const { bool ConfigManager::isInitialized() const {
return m_initialized; return m_initialized;
} }
/**
* @brief
* @param filepath 使
* @return
*/
ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) { ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -137,10 +103,6 @@ ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) {
if (result.success) { if (result.success) {
m_appConfig.merge(loadedConfig); m_appConfig.merge(loadedConfig);
if (m_platformConfig) {
m_appConfig.applyPlatformConstraints(*m_platformConfig);
}
if (!m_appConfig.validate()) { if (!m_appConfig.validate()) {
E2D_LOG_WARN("Loaded config validation failed, using defaults"); E2D_LOG_WARN("Loaded config validation failed, using defaults");
m_appConfig = AppConfig::createDefault(); m_appConfig = AppConfig::createDefault();
@ -157,11 +119,6 @@ ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) {
return result; return result;
} }
/**
* @brief
* @param filepath 使
* @return
*/
ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) { ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -188,35 +145,69 @@ ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) {
return result; return result;
} }
/** ConfigLoadResult ConfigManager::loadConfigWithModules(const std::string& filepath) {
* @brief std::lock_guard<std::mutex> lock(m_mutex);
* @return
*/ 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<std::mutex> 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() { ConfigLoadResult ConfigManager::reload() {
return loadConfig(m_configPath); return loadConfig(m_configPath);
} }
/**
* @brief
* @return
*/
const AppConfig& ConfigManager::appConfig() const { const AppConfig& ConfigManager::appConfig() const {
return m_appConfig; return m_appConfig;
} }
/**
* @brief
* @return
*/
AppConfig& ConfigManager::appConfig() { AppConfig& ConfigManager::appConfig() {
m_modified = true; m_modified = true;
return m_appConfig; return m_appConfig;
} }
/**
* @brief
* @param config
*/
void ConfigManager::setAppConfig(const AppConfig& config) { void ConfigManager::setAppConfig(const AppConfig& config) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -226,27 +217,14 @@ void ConfigManager::setAppConfig(const AppConfig& config) {
E2D_LOG_INFO("App config updated"); E2D_LOG_INFO("App config updated");
} }
/**
* @brief
* @return
*/
PlatformConfig* ConfigManager::platformConfig() { PlatformConfig* ConfigManager::platformConfig() {
return m_platformConfig.get(); return m_platformConfig.get();
} }
/**
* @brief
* @return
*/
const PlatformConfig* ConfigManager::platformConfig() const { const PlatformConfig* ConfigManager::platformConfig() const {
return m_platformConfig.get(); return m_platformConfig.get();
} }
/**
* @brief
* @param callback
* @return ID
*/
int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) { int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -257,10 +235,6 @@ int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
return id; return id;
} }
/**
* @brief
* @param callbackId ID
*/
void ConfigManager::unregisterChangeCallback(int callbackId) { void ConfigManager::unregisterChangeCallback(int callbackId) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -271,192 +245,111 @@ void ConfigManager::unregisterChangeCallback(int callbackId) {
} }
} }
/**
* @brief
*/
void ConfigManager::clearChangeCallbacks() { void ConfigManager::clearChangeCallbacks() {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
m_changeCallbacks.clear(); m_changeCallbacks.clear();
E2D_LOG_DEBUG("Cleared all config change callbacks"); E2D_LOG_DEBUG("Cleared all config change callbacks");
} }
/**
* @brief
* @param moduleName
* @param config
*/
void ConfigManager::registerModuleConfig(const std::string& moduleName, Ptr<void> config) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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) { void ConfigManager::setValue(const std::string& section, const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
std::string fullKey = section + "." + key;
ConfigChangeEvent event; ConfigChangeEvent event;
event.section = section; event.section = section;
event.field = key; event.field = key;
event.oldValue = m_rawValues[fullKey];
event.newValue = value; event.newValue = value;
m_rawValues[fullKey] = value;
m_modified = true; m_modified = true;
notifyChangeCallbacks(event); notifyChangeCallbacks(event);
} }
/**
* @brief
* @param section
* @param key
* @param value
*/
void ConfigManager::setValue(const std::string& section, const std::string& key, int value) { void ConfigManager::setValue(const std::string& section, const std::string& key, int value) {
std::ostringstream oss; std::ostringstream oss;
oss << value; oss << value;
setValue(section, key, oss.str()); 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) { void ConfigManager::setValue(const std::string& section, const std::string& key, float value) {
std::ostringstream oss; std::ostringstream oss;
oss << value; oss << value;
setValue(section, key, oss.str()); 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) { void ConfigManager::setValue(const std::string& section, const std::string& key, bool value) {
setValue(section, key, std::string(value ? "true" : "false")); 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, std::string ConfigManager::getValue(const std::string& section, const std::string& key,
const std::string& defaultValue) const { const std::string& defaultValue) const {
std::lock_guard<std::mutex> lock(m_mutex);
std::string fullKey = section + "." + key;
auto it = m_rawValues.find(fullKey);
if (it != m_rawValues.end()) {
return it->second;
}
return defaultValue; return defaultValue;
} }
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
int ConfigManager::getIntValue(const std::string& section, const std::string& key, int defaultValue) const { 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; return defaultValue;
} }
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
float ConfigManager::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const { 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; return defaultValue;
} }
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
bool ConfigManager::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const { 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; return defaultValue;
} }
/**
* @brief
*/
void ConfigManager::resetToDefaults() { void ConfigManager::resetToDefaults() {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
m_appConfig = AppConfig::createDefault(); m_appConfig = AppConfig::createDefault();
m_rawValues.clear();
if (m_platformConfig) {
m_platformConfig->applyDefaults(m_appConfig);
}
m_modified = true; m_modified = true;
E2D_LOG_INFO("Config reset to defaults"); E2D_LOG_INFO("Config reset to defaults");
} }
/**
* @brief
* @return true
*/
bool ConfigManager::hasUnsavedChanges() const { bool ConfigManager::hasUnsavedChanges() const {
return m_modified; return m_modified;
} }
/**
* @brief
*/
void ConfigManager::markModified() { void ConfigManager::markModified() {
m_modified = true; m_modified = true;
} }
/**
* @brief
*/
void ConfigManager::clearModified() { void ConfigManager::clearModified() {
m_modified = false; m_modified = false;
} }
/**
* @brief
* @param enabled
* @param interval
*/
void ConfigManager::setAutoSave(bool enabled, float interval) { void ConfigManager::setAutoSave(bool enabled, float interval) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@ -468,10 +361,6 @@ void ConfigManager::setAutoSave(bool enabled, float interval) {
enabled ? "enabled" : "disabled", m_autoSaveInterval); enabled ? "enabled" : "disabled", m_autoSaveInterval);
} }
/**
* @brief
* @param deltaTime
*/
void ConfigManager::update(float deltaTime) { void ConfigManager::update(float deltaTime) {
if (!m_autoSaveEnabled || !m_modified) { if (!m_autoSaveEnabled || !m_modified) {
return; return;
@ -485,10 +374,6 @@ void ConfigManager::update(float deltaTime) {
} }
} }
/**
* @brief
* @param event
*/
void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) { void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) {
for (const auto& pair : m_changeCallbacks) { for (const auto& pair : m_changeCallbacks) {
if (pair.second) { 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;
}
} }

View File

@ -42,32 +42,6 @@ public:
const char *platformName() const override { return "Windows"; } const char *platformName() const override { return "Windows"; }
const PlatformCapabilities &capabilities() const override { return caps_; } 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 getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; } int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override { bool isResolutionSupported(int width, int height) const override {
@ -107,26 +81,6 @@ public:
const char *platformName() const override { return "Linux"; } const char *platformName() const override { return "Linux"; }
const PlatformCapabilities &capabilities() const override { return caps_; } 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 getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; } int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override { bool isResolutionSupported(int width, int height) const override {
@ -165,27 +119,6 @@ public:
const char *platformName() const override { return "macOS"; } const char *platformName() const override { return "macOS"; }
const PlatformCapabilities &capabilities() const override { return caps_; } 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 getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; } int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override { bool isResolutionSupported(int width, int height) const override {
@ -196,7 +129,6 @@ private:
PlatformCapabilities caps_; PlatformCapabilities caps_;
}; };
#ifdef __SWITCH__
class SwitchPlatformConfig : public PlatformConfig { class SwitchPlatformConfig : public PlatformConfig {
public: public:
SwitchPlatformConfig() { SwitchPlatformConfig() {
@ -225,37 +157,6 @@ public:
const char *platformName() const override { return "Nintendo Switch"; } const char *platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities &capabilities() const override { return caps_; } 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 getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; } int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override { bool isResolutionSupported(int width, int height) const override {
@ -266,85 +167,9 @@ public:
private: private:
PlatformCapabilities caps_; 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<PlatformConfig> createPlatformConfig(PlatformType type) { UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
if (type == PlatformType::Auto) { if (type == PlatformType::Auto) {
#ifdef _WIN32 #ifdef _WIN32
@ -379,12 +204,6 @@ UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
} }
} }
/**
* @brief
* PlatformType
* @param type
* @return
*/
const char *getPlatformTypeName(PlatformType type) { const char *getPlatformTypeName(PlatformType type) {
switch (type) { switch (type) {
case PlatformType::Auto: case PlatformType::Auto:
@ -402,4 +221,4 @@ const char *getPlatformTypeName(PlatformType type) {
} }
} }
} // namespace extra2d }

View File

@ -657,39 +657,21 @@ PlatformCapabilities PlatformDetector::getSwitchCapabilities() {
AppConfig PlatformDetector::getWindowsDefaults() { AppConfig PlatformDetector::getWindowsDefaults() {
AppConfig config = AppConfig::createDefault(); AppConfig config = AppConfig::createDefault();
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
config.render.targetFPS = 60;
return config; return config;
} }
AppConfig PlatformDetector::getLinuxDefaults() { AppConfig PlatformDetector::getLinuxDefaults() {
AppConfig config = AppConfig::createDefault(); AppConfig config = AppConfig::createDefault();
config.window.resizable = true;
config.render.vsync = true;
return config; return config;
} }
AppConfig PlatformDetector::getMacOSDefaults() { AppConfig PlatformDetector::getMacOSDefaults() {
AppConfig config = AppConfig::createDefault(); AppConfig config = AppConfig::createDefault();
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
return config; return config;
} }
AppConfig PlatformDetector::getSwitchDefaults() { AppConfig PlatformDetector::getSwitchDefaults() {
AppConfig config = AppConfig::createDefault(); 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; return config;
} }

View File

@ -0,0 +1,23 @@
#include <extra2d/debug/debug_config.h>
#include <algorithm>
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);
}
}
}

View File

@ -18,55 +18,227 @@ SDL2Input::~SDL2Input() {
} }
void SDL2Input::init() { 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(); openGamepad();
E2D_LOG_DEBUG("SDL2 input initialized");
} }
void SDL2Input::shutdown() { void SDL2Input::shutdown() {
closeGamepad(); closeGamepad();
E2D_LOG_INFO("SDL2Input shutdown");
} }
void SDL2Input::update() { void SDL2Input::update() {
keyPrevious_ = keyCurrent_; keyPrevious_ = keyCurrent_;
mousePrevious_ = mouseCurrent_; mousePrevious_ = mouseCurrent_;
gamepadPrevious_ = gamepadCurrent_; gamepadPrevious_ = gamepadCurrent_;
scrollDelta_ = 0.0f;
mouseDelta_ = Vec2::Zero();
updateKeyboard(); scrollDelta_ = 0.0f;
updateMouse(); mouseDelta_ = Vec2{0.0f, 0.0f};
updateGamepad(); 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<int>(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<int>(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<int>(Mouse::Count)) {
mouseCurrent_[btn] = true;
Vec2 pos{static_cast<float>(event.button.x),
static_cast<float>(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<int>(Mouse::Count)) {
mouseCurrent_[btn] = false;
Vec2 pos{static_cast<float>(event.button.x),
static_cast<float>(event.button.y)};
Event e = Event::createMouseButtonRelease(btn, 0, pos);
dispatchEvent(e);
}
break;
}
case SDL_MOUSEMOTION: {
Vec2 newPos{static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y)};
Vec2 delta{static_cast<float>(event.motion.xrel),
static_cast<float>(event.motion.yrel)};
mouseDelta_ = mouseDelta_ + delta;
mousePos_ = newPos;
Event e = Event::createMouseMove(newPos, delta);
dispatchEvent(e);
break;
}
case SDL_MOUSEWHEEL: {
Vec2 offset{static_cast<float>(event.wheel.x),
static_cast<float>(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<int>(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<int>(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 { bool SDL2Input::down(Key key) const {
size_t idx = static_cast<size_t>(key); size_t idx = static_cast<size_t>(key);
return idx < keyCurrent_.size() ? keyCurrent_[idx] : false; if (idx < keyCurrent_.size()) {
return keyCurrent_[idx];
}
return false;
} }
bool SDL2Input::pressed(Key key) const { bool SDL2Input::pressed(Key key) const {
size_t idx = static_cast<size_t>(key); size_t idx = static_cast<size_t>(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 { bool SDL2Input::released(Key key) const {
size_t idx = static_cast<size_t>(key); size_t idx = static_cast<size_t>(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 { bool SDL2Input::down(Mouse btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(btn);
return idx < mouseCurrent_.size() ? mouseCurrent_[idx] : false; if (idx < mouseCurrent_.size()) {
return mouseCurrent_[idx];
}
return false;
} }
bool SDL2Input::pressed(Mouse btn) const { bool SDL2Input::pressed(Mouse btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(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 { bool SDL2Input::released(Mouse btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(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 { Vec2 SDL2Input::mouse() const {
@ -95,17 +267,26 @@ bool SDL2Input::gamepad() const {
bool SDL2Input::down(Gamepad btn) const { bool SDL2Input::down(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(btn);
return idx < gamepadCurrent_.size() ? gamepadCurrent_[idx] : false; if (idx < gamepadCurrent_.size()) {
return gamepadCurrent_[idx];
}
return false;
} }
bool SDL2Input::pressed(Gamepad btn) const { bool SDL2Input::pressed(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(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 { bool SDL2Input::released(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn); size_t idx = static_cast<size_t>(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 { Vec2 SDL2Input::leftStick() const {
@ -126,9 +307,9 @@ float SDL2Input::rightTrigger() const {
void SDL2Input::vibrate(float left, float right) { void SDL2Input::vibrate(float left, float right) {
if (gamepad_) { if (gamepad_) {
Uint16 lowFreq = static_cast<Uint16>(std::clamp(left, 0.0f, 1.0f) * 65535); Uint16 lowFreq = static_cast<Uint16>(left * 65535.0f);
Uint16 highFreq = static_cast<Uint16>(std::clamp(right, 0.0f, 1.0f) * 65535); Uint16 highFreq = static_cast<Uint16>(right * 65535.0f);
SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 0); SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 500);
} }
} }
@ -142,7 +323,7 @@ int SDL2Input::touchCount() const {
Vec2 SDL2Input::touch(int index) const { Vec2 SDL2Input::touch(int index) const {
(void)index; (void)index;
return Vec2::Zero(); return Vec2{0.0f, 0.0f};
} }
TouchPoint SDL2Input::touchPoint(int index) const { TouchPoint SDL2Input::touchPoint(int index) const {
@ -151,160 +332,53 @@ TouchPoint SDL2Input::touchPoint(int index) const {
} }
void SDL2Input::updateKeyboard() { void SDL2Input::updateKeyboard() {
int numKeys = 0;
const Uint8* state = SDL_GetKeyboardState(&numKeys);
auto updateKey = [&](Key key, int sdlScancode) {
size_t idx = static_cast<size_t>(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() { void SDL2Input::updateMouse() {
int x, y; int x = 0, y = 0;
Uint32 state = SDL_GetMouseState(&x, &y); SDL_GetMouseState(&x, &y);
mousePos_ = Vec2{static_cast<float>(x), static_cast<float>(y)};
Vec2 newPos(static_cast<float>(x), static_cast<float>(y));
mouseDelta_ = newPos - mousePos_;
mousePos_ = newPos;
mouseCurrent_[static_cast<size_t>(Mouse::Left)] = (state & SDL_BUTTON_LMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::Right)] = (state & SDL_BUTTON_RMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::Middle)] = (state & SDL_BUTTON_MMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::X1)] = (state & SDL_BUTTON_X1MASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::X2)] = (state & SDL_BUTTON_X2MASK) != 0;
} }
void SDL2Input::updateGamepad() { void SDL2Input::updateGamepad() {
if (!gamepad_) { if (!gamepad_) {
openGamepad(); return;
if (!gamepad_) return;
} }
auto applyDeadzone = [this](float value) -> float { auto applyDeadzone = [this](float value) -> float {
if (std::abs(value) < deadzone_) return 0.0f; if (std::abs(value) < deadzone_) {
float sign = value >= 0 ? 1.0f : -1.0f; return 0.0f;
}
float sign = value >= 0.0f ? 1.0f : -1.0f;
return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_); return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_);
}; };
auto getAxis = [this](SDL_GameControllerAxis axis) -> float { int lx = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_LEFTX);
if (!gamepad_) return 0.0f; int ly = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_LEFTY);
Sint16 value = SDL_GameControllerGetAxis(gamepad_, axis); int rx = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_RIGHTX);
return static_cast<float>(value) / 32767.0f; int ry = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_RIGHTY);
};
auto getButton = [this](SDL_GameControllerButton btn) -> bool { leftStick_.x = applyDeadzone(lx / 32767.0f);
return gamepad_ ? SDL_GameControllerGetButton(gamepad_, btn) != 0 : false; leftStick_.y = applyDeadzone(ly / 32767.0f);
}; rightStick_.x = applyDeadzone(rx / 32767.0f);
rightStick_.y = applyDeadzone(ry / 32767.0f);
leftStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTX)); int lt = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
leftStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTY)); int rt = SDL_GameControllerGetAxis(gamepad_, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
rightStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTX));
rightStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTY));
leftTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERLEFT); leftTrigger_ = lt / 32767.0f;
rightTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT); rightTrigger_ = rt / 32767.0f;
gamepadCurrent_[static_cast<size_t>(Gamepad::A)] = getButton(SDL_CONTROLLER_BUTTON_A);
gamepadCurrent_[static_cast<size_t>(Gamepad::B)] = getButton(SDL_CONTROLLER_BUTTON_B);
gamepadCurrent_[static_cast<size_t>(Gamepad::X)] = getButton(SDL_CONTROLLER_BUTTON_X);
gamepadCurrent_[static_cast<size_t>(Gamepad::Y)] = getButton(SDL_CONTROLLER_BUTTON_Y);
gamepadCurrent_[static_cast<size_t>(Gamepad::LB)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
gamepadCurrent_[static_cast<size_t>(Gamepad::RB)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
gamepadCurrent_[static_cast<size_t>(Gamepad::Back)] = getButton(SDL_CONTROLLER_BUTTON_BACK);
gamepadCurrent_[static_cast<size_t>(Gamepad::Start)] = getButton(SDL_CONTROLLER_BUTTON_START);
gamepadCurrent_[static_cast<size_t>(Gamepad::Guide)] = getButton(SDL_CONTROLLER_BUTTON_GUIDE);
gamepadCurrent_[static_cast<size_t>(Gamepad::LStick)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSTICK);
gamepadCurrent_[static_cast<size_t>(Gamepad::RStick)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK);
gamepadCurrent_[static_cast<size_t>(Gamepad::DUp)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_UP);
gamepadCurrent_[static_cast<size_t>(Gamepad::DDown)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
gamepadCurrent_[static_cast<size_t>(Gamepad::DLeft)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
gamepadCurrent_[static_cast<size_t>(Gamepad::DRight)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
} }
void SDL2Input::openGamepad() { 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)) { if (SDL_IsGameController(i)) {
gamepad_ = SDL_GameControllerOpen(i); gamepad_ = SDL_GameControllerOpen(i);
if (gamepad_) { if (gamepad_) {
gamepadIndex_ = i; gamepadIndex_ = i;
E2D_LOG_INFO("Gamepad connected: {}", SDL_GameControllerName(gamepad_)); E2D_LOG_INFO("Gamepad opened: {}", SDL_GameControllerName(gamepad_));
break; return;
} }
} }
} }
@ -315,47 +389,20 @@ void SDL2Input::closeGamepad() {
SDL_GameControllerClose(gamepad_); SDL_GameControllerClose(gamepad_);
gamepad_ = nullptr; gamepad_ = nullptr;
gamepadIndex_ = -1; gamepadIndex_ = -1;
E2D_LOG_INFO("Gamepad disconnected"); gamepadCurrent_.fill(false);
gamepadPrevious_.fill(false);
} }
} }
int SDL2Input::keyToSDL(Key key) { int SDL2Input::keyToSDL(Key key) {
switch (key) { return static_cast<int>(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;
}
} }
int SDL2Input::mouseToSDL(Mouse btn) { int SDL2Input::mouseToSDL(Mouse btn) {
switch (btn) { switch (btn) {
case Mouse::Left: return SDL_BUTTON_LEFT; case Mouse::Left: return SDL_BUTTON_LEFT;
case Mouse::Right: return SDL_BUTTON_RIGHT;
case Mouse::Middle: return SDL_BUTTON_MIDDLE; case Mouse::Middle: return SDL_BUTTON_MIDDLE;
case Mouse::Right: return SDL_BUTTON_RIGHT;
case Mouse::X1: return SDL_BUTTON_X1; case Mouse::X1: return SDL_BUTTON_X1;
case Mouse::X2: return SDL_BUTTON_X2; case Mouse::X2: return SDL_BUTTON_X2;
default: return 0; default: return 0;
@ -368,19 +415,37 @@ int SDL2Input::gamepadToSDL(Gamepad btn) {
case Gamepad::B: return SDL_CONTROLLER_BUTTON_B; case Gamepad::B: return SDL_CONTROLLER_BUTTON_B;
case Gamepad::X: return SDL_CONTROLLER_BUTTON_X; case Gamepad::X: return SDL_CONTROLLER_BUTTON_X;
case Gamepad::Y: return SDL_CONTROLLER_BUTTON_Y; 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::Back: return SDL_CONTROLLER_BUTTON_BACK;
case Gamepad::Start: return SDL_CONTROLLER_BUTTON_START; 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::LStick: return SDL_CONTROLLER_BUTTON_LEFTSTICK;
case Gamepad::RStick: return SDL_CONTROLLER_BUTTON_RIGHTSTICK; 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::DUp: return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN; case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT; case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Gamepad::DRight: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT; 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<int>(Key::Count)) {
return static_cast<Key>(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;
}
}
}

View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/event/event.h>
#include <SDL.h> #include <SDL.h>
#include <array> #include <array>
#include <functional>
namespace extra2d { namespace extra2d {
@ -11,6 +13,8 @@ namespace extra2d {
*/ */
class SDL2Input : public IInput { class SDL2Input : public IInput {
public: public:
using EventCallback = std::function<void(const Event&)>;
SDL2Input(); SDL2Input();
~SDL2Input() override; ~SDL2Input() override;
@ -46,6 +50,18 @@ public:
Vec2 touch(int index) const override; Vec2 touch(int index) const override;
TouchPoint touchPoint(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: private:
void updateKeyboard(); void updateKeyboard();
void updateMouse(); void updateMouse();
@ -56,6 +72,10 @@ private:
static int keyToSDL(Key key); static int keyToSDL(Key key);
static int mouseToSDL(Mouse btn); static int mouseToSDL(Mouse btn);
static int gamepadToSDL(Gamepad btn); static int gamepadToSDL(Gamepad btn);
static Key sdlToKey(int sdlKey);
static Mouse sdlToMouse(int sdlButton);
void dispatchEvent(const Event& event);
std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_{}; std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_{};
std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_{}; std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_{};
@ -77,6 +97,8 @@ private:
float leftTrigger_ = 0.0f; float leftTrigger_ = 0.0f;
float rightTrigger_ = 0.0f; float rightTrigger_ = 0.0f;
float deadzone_ = 0.15f; float deadzone_ = 0.15f;
EventCallback eventCallback_;
}; };
} // namespace extra2d }

View File

@ -21,6 +21,10 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
} }
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#ifdef __SWITCH__
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
if (cfg.isFullscreen()) { if (cfg.isFullscreen()) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if (cfg.isBorderless()) { } else if (cfg.isBorderless()) {
@ -32,10 +36,12 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
if (!cfg.decorated) { if (!cfg.decorated) {
flags |= SDL_WINDOW_BORDERLESS; flags |= SDL_WINDOW_BORDERLESS;
} }
#endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 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_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
@ -75,7 +81,7 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
} }
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) { 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_); SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr; glContext_ = nullptr;
SDL_DestroyWindow(sdlWindow_); SDL_DestroyWindow(sdlWindow_);
@ -87,16 +93,19 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0); SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_); SDL_GetWindowSize(sdlWindow_, &width_, &height_);
fullscreen_ = cfg.isFullscreen(); fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
vsync_ = cfg.vsync; vsync_ = cfg.vsync;
#ifndef __SWITCH__
initCursors(); initCursors();
#endif
updateContentScale(); updateContentScale();
input_ = makeUnique<SDL2Input>(); input_ = makeUnique<SDL2Input>();
input_->init(); input_->init();
E2D_LOG_INFO("SDL2 window created: {}x{}", width_, height_); E2D_LOG_INFO("SDL2 window created: {}x{}", width_, height_);
E2D_LOG_INFO(" Platform: OpenGL ES 3.2");
return true; return true;
} }
@ -106,7 +115,9 @@ void SDL2Window::destroy() {
input_.reset(); input_.reset();
} }
#ifndef __SWITCH__
deinitCursors(); deinitCursors();
#endif
if (glContext_) { if (glContext_) {
SDL_GL_DeleteContext(glContext_); SDL_GL_DeleteContext(glContext_);
@ -163,16 +174,25 @@ void SDL2Window::setSize(int w, int h) {
} }
void SDL2Window::setPos(int x, int y) { void SDL2Window::setPos(int x, int y) {
#ifndef __SWITCH__
if (sdlWindow_) { if (sdlWindow_) {
SDL_SetWindowPosition(sdlWindow_, x, y); SDL_SetWindowPosition(sdlWindow_, x, y);
} }
#else
(void)x;
(void)y;
#endif
} }
void SDL2Window::setFullscreen(bool fs) { void SDL2Window::setFullscreen(bool fs) {
#ifndef __SWITCH__
if (sdlWindow_) { if (sdlWindow_) {
SDL_SetWindowFullscreen(sdlWindow_, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); SDL_SetWindowFullscreen(sdlWindow_, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
fullscreen_ = fs; fullscreen_ = fs;
} }
#else
(void)fs;
#endif
} }
void SDL2Window::setVSync(bool vsync) { void SDL2Window::setVSync(bool vsync) {
@ -183,6 +203,7 @@ void SDL2Window::setVSync(bool vsync) {
} }
void SDL2Window::setVisible(bool visible) { void SDL2Window::setVisible(bool visible) {
#ifndef __SWITCH__
if (sdlWindow_) { if (sdlWindow_) {
if (visible) { if (visible) {
SDL_ShowWindow(sdlWindow_); SDL_ShowWindow(sdlWindow_);
@ -190,6 +211,9 @@ void SDL2Window::setVisible(bool visible) {
SDL_HideWindow(sdlWindow_); SDL_HideWindow(sdlWindow_);
} }
} }
#else
(void)visible;
#endif
} }
int SDL2Window::width() const { int SDL2Window::width() const {
@ -205,12 +229,12 @@ Size SDL2Window::size() const {
} }
Vec2 SDL2Window::pos() const { Vec2 SDL2Window::pos() const {
int x, y; int x = 0, y = 0;
#ifndef __SWITCH__
if (sdlWindow_) { if (sdlWindow_) {
SDL_GetWindowPosition(sdlWindow_, &x, &y); SDL_GetWindowPosition(sdlWindow_, &x, &y);
} else {
x = y = 0;
} }
#endif
return Vec2(static_cast<float>(x), static_cast<float>(y)); return Vec2(static_cast<float>(x), static_cast<float>(y));
} }
@ -239,6 +263,7 @@ float SDL2Window::scaleY() const {
} }
void SDL2Window::setCursor(Cursor cursor) { void SDL2Window::setCursor(Cursor cursor) {
#ifndef __SWITCH__
if (cursor == Cursor::Hidden) { if (cursor == Cursor::Hidden) {
SDL_ShowCursor(SDL_DISABLE); SDL_ShowCursor(SDL_DISABLE);
return; return;
@ -251,18 +276,29 @@ void SDL2Window::setCursor(Cursor cursor) {
SDL_SetCursor(sdlCursors_[idx]); SDL_SetCursor(sdlCursors_[idx]);
currentCursor_ = idx; currentCursor_ = idx;
} }
#else
(void)cursor;
#endif
} }
void SDL2Window::showCursor(bool show) { void SDL2Window::showCursor(bool show) {
#ifndef __SWITCH__
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE); SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
cursorVisible_ = show; cursorVisible_ = show;
#else
(void)show;
#endif
} }
void SDL2Window::lockCursor(bool lock) { void SDL2Window::lockCursor(bool lock) {
#ifndef __SWITCH__
if (sdlWindow_) { if (sdlWindow_) {
SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE); SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
cursorLocked_ = lock; cursorLocked_ = lock;
} }
#else
(void)lock;
#endif
} }
IInput* SDL2Window::input() const { IInput* SDL2Window::input() const {
@ -288,7 +324,8 @@ void* SDL2Window::native() const {
bool SDL2Window::initSDL() { bool SDL2Window::initSDL() {
static int sdlInitCount = 0; static int sdlInitCount = 0;
if (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()); E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
return false; return false;
} }
@ -305,6 +342,7 @@ void SDL2Window::deinitSDL() {
} }
} }
#ifndef __SWITCH__
void SDL2Window::initCursors() { void SDL2Window::initCursors() {
sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
@ -323,6 +361,7 @@ void SDL2Window::deinitCursors() {
} }
} }
} }
#endif
void SDL2Window::updateContentScale() { void SDL2Window::updateContentScale() {
if (sdlWindow_) { if (sdlWindow_) {
@ -335,6 +374,10 @@ void SDL2Window::updateContentScale() {
} }
void SDL2Window::handleEvent(const SDL_Event& event) { void SDL2Window::handleEvent(const SDL_Event& event) {
if (input_) {
input_->handleSDLEvent(event);
}
switch (event.type) { switch (event.type) {
case SDL_QUIT: case SDL_QUIT:
shouldClose_ = true; shouldClose_ = true;
@ -378,4 +421,4 @@ void SDL2Window::handleEvent(const SDL_Event& event) {
} }
} }
} // namespace extra2d }

View File

@ -1,270 +1,196 @@
#include <extra2d/platform/input_module.h> #include <extra2d/platform/input_module.h>
#include <extra2d/config/module_registry.h> #include <extra2d/config/module_registry.h>
#include <extra2d/platform/platform_module.h> #include <extra2d/platform/window_module.h>
#include <extra2d/services/event_service.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using json = nlohmann::json; using json = nlohmann::json;
namespace extra2d { namespace extra2d {
// ============================================================================ static ModuleId s_inputModuleId = INVALID_MODULE_ID;
// InputModuleConfig 实现
// ============================================================================ ModuleId get_input_module_id() {
return s_inputModuleId;
}
/**
* @brief
*
* @return true
*/
bool InputModuleConfig::validate() const { bool InputModuleConfig::validate() const {
if (deadzone < 0.0f || deadzone > 1.0f) { return inputConfig.isDeadzoneValid();
E2D_LOG_ERROR("InputModuleConfig: deadzone 必须在 [0.0, 1.0] 范围内,当前值: {}", deadzone);
return false;
} }
if (mouseSensitivity < 0.0f || mouseSensitivity > 10.0f) {
E2D_LOG_ERROR("InputModuleConfig: mouseSensitivity 必须在 [0.0, 10.0] 范围内,当前值: {}", mouseSensitivity);
return false;
}
if (!enableKeyboard && !enableMouse && !enableGamepad && !enableTouch) {
E2D_LOG_WARN("InputModuleConfig: 所有输入设备都已禁用");
}
return true;
}
/**
* @brief
*
* @param platform
*/
void InputModuleConfig::applyPlatformConstraints(PlatformType platform) { void InputModuleConfig::applyPlatformConstraints(PlatformType platform) {
switch (platform) { #ifdef __SWITCH__
case PlatformType::Switch: (void)platform;
enableMouse = false; inputConfig.enableVibration = true;
enableTouch = true; inputConfig.maxGamepads = 2;
enableGamepad = true; #else
E2D_LOG_INFO("InputModuleConfig: Switch 平台 - 禁用鼠标输入"); (void)platform;
break; #endif
case PlatformType::Windows:
case PlatformType::Linux:
case PlatformType::macOS:
enableMouse = true;
enableKeyboard = true;
enableTouch = false;
E2D_LOG_INFO("InputModuleConfig: PC 平台 - 禁用触摸输入");
break;
case PlatformType::Auto:
default:
break;
}
} }
/**
* @brief
*/
void InputModuleConfig::resetToDefaults() {
enableKeyboard = true;
enableMouse = true;
enableGamepad = true;
enableTouch = true;
deadzone = 0.15f;
mouseSensitivity = 1.0f;
E2D_LOG_INFO("InputModuleConfig: 已重置为默认配置");
}
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool InputModuleConfig::loadFromJson(const void* jsonData) { bool InputModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) { if (!jsonData) return false;
E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空");
return false;
}
try { try {
const json& obj = *static_cast<const json*>(jsonData); const json& j = *static_cast<const json*>(jsonData);
if (!obj.is_object()) { if (j.contains("enabled")) {
E2D_LOG_ERROR("InputModuleConfig: JSON 数据不是对象类型"); inputConfig.enabled = j["enabled"].get<bool>();
return false; }
if (j.contains("rawMouseInput")) {
inputConfig.rawMouseInput = j["rawMouseInput"].get<bool>();
}
if (j.contains("mouseSensitivity")) {
inputConfig.mouseSensitivity = j["mouseSensitivity"].get<float>();
}
if (j.contains("invertMouseY")) {
inputConfig.invertMouseY = j["invertMouseY"].get<bool>();
}
if (j.contains("invertMouseX")) {
inputConfig.invertMouseX = j["invertMouseX"].get<bool>();
}
if (j.contains("deadzone")) {
inputConfig.deadzone = j["deadzone"].get<float>();
}
if (j.contains("triggerThreshold")) {
inputConfig.triggerThreshold = j["triggerThreshold"].get<float>();
}
if (j.contains("enableVibration")) {
inputConfig.enableVibration = j["enableVibration"].get<bool>();
}
if (j.contains("maxGamepads")) {
inputConfig.maxGamepads = j["maxGamepads"].get<int>();
}
if (j.contains("autoConnectGamepads")) {
inputConfig.autoConnectGamepads = j["autoConnectGamepads"].get<bool>();
}
if (j.contains("gamepadMappingFile")) {
inputConfig.gamepadMappingFile = j["gamepadMappingFile"].get<std::string>();
} }
if (obj.contains("enableKeyboard") && obj["enableKeyboard"].is_boolean()) {
enableKeyboard = obj["enableKeyboard"].get<bool>();
}
if (obj.contains("enableMouse") && obj["enableMouse"].is_boolean()) {
enableMouse = obj["enableMouse"].get<bool>();
}
if (obj.contains("enableGamepad") && obj["enableGamepad"].is_boolean()) {
enableGamepad = obj["enableGamepad"].get<bool>();
}
if (obj.contains("enableTouch") && obj["enableTouch"].is_boolean()) {
enableTouch = obj["enableTouch"].get<bool>();
}
if (obj.contains("deadzone") && obj["deadzone"].is_number()) {
deadzone = obj["deadzone"].get<float>();
}
if (obj.contains("mouseSensitivity") && obj["mouseSensitivity"].is_number()) {
mouseSensitivity = obj["mouseSensitivity"].get<float>();
}
E2D_LOG_INFO("InputModuleConfig: 从 JSON 加载配置成功");
return true; return true;
} catch (...) {
} catch (const json::exception& e) {
E2D_LOG_ERROR("InputModuleConfig: JSON 解析错误: {}", e.what());
return false; return false;
} }
} }
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool InputModuleConfig::saveToJson(void* jsonData) const { bool InputModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) { if (!jsonData) return false;
E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空");
return false;
}
try { try {
json& obj = *static_cast<json*>(jsonData); json& j = *static_cast<json*>(jsonData);
obj["enableKeyboard"] = enableKeyboard; j["enabled"] = inputConfig.enabled;
obj["enableMouse"] = enableMouse; j["rawMouseInput"] = inputConfig.rawMouseInput;
obj["enableGamepad"] = enableGamepad; j["mouseSensitivity"] = inputConfig.mouseSensitivity;
obj["enableTouch"] = enableTouch; j["invertMouseY"] = inputConfig.invertMouseY;
obj["deadzone"] = deadzone; j["invertMouseX"] = inputConfig.invertMouseX;
obj["mouseSensitivity"] = mouseSensitivity; 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; return true;
} catch (...) {
} catch (const json::exception& e) {
E2D_LOG_ERROR("InputModuleConfig: JSON 序列化错误: {}", e.what());
return false; return false;
} }
} }
// ============================================================================
// InputModuleInitializer 实现
// ============================================================================
/**
* @brief
*/
InputModuleInitializer::InputModuleInitializer() InputModuleInitializer::InputModuleInitializer()
: moduleId_(INVALID_MODULE_ID) : moduleId_(INVALID_MODULE_ID)
, windowModuleId_(INVALID_MODULE_ID)
, input_(nullptr) , input_(nullptr)
, initialized_(false) { , initialized_(false) {
E2D_LOG_DEBUG("InputModuleInitializer: 构造函数调用");
} }
/**
* @brief
*/
InputModuleInitializer::~InputModuleInitializer() { InputModuleInitializer::~InputModuleInitializer() {
if (initialized_) { if (initialized_) {
shutdown(); shutdown();
} }
E2D_LOG_DEBUG("InputModuleInitializer: 析构函数调用");
} }
/**
* @brief
*
* @return
*/
std::vector<ModuleId> InputModuleInitializer::getDependencies() const { std::vector<ModuleId> InputModuleInitializer::getDependencies() const {
std::vector<ModuleId> dependencies; return { get_window_module_id() };
if (windowModuleId_ != INVALID_MODULE_ID) {
dependencies.push_back(windowModuleId_);
} }
return dependencies;
}
/**
* @brief
*
* @param config
* @return true
*/
bool InputModuleInitializer::initialize(const IModuleConfig* config) { bool InputModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) { if (initialized_) return true;
E2D_LOG_WARN("InputModuleInitializer: 模块已经初始化");
return true;
}
if (!config) {
E2D_LOG_ERROR("InputModuleInitializer: 配置指针为空");
return false;
}
const InputModuleConfig* inputConfig = dynamic_cast<const InputModuleConfig*>(config); const InputModuleConfig* inputConfig = dynamic_cast<const InputModuleConfig*>(config);
if (!inputConfig) { if (!inputConfig) {
E2D_LOG_ERROR("InputModuleInitializer: 配置类型不正确"); E2D_LOG_ERROR("Invalid input module config");
return false; return false;
} }
if (!inputConfig->validate()) { config_ = inputConfig->inputConfig;
E2D_LOG_ERROR("InputModuleInitializer: 配置验证失败");
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; return false;
} }
ModuleInfo info = config->getModuleInfo(); auto* windowModule = static_cast<WindowModuleInitializer*>(windowInitializer);
moduleId_ = info.id; IWindow* window = windowModule->getWindow();
if (!window) {
E2D_LOG_ERROR("Window not created - cannot get input interface");
return false;
}
E2D_LOG_INFO("InputModuleInitializer: 正在初始化输入模块 '{}' (版本: {})", input_ = window->input();
info.name, info.version); 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; 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; return true;
} }
/**
* @brief
*/
void InputModuleInitializer::shutdown() { void InputModuleInitializer::shutdown() {
if (!initialized_) { if (!initialized_) return;
E2D_LOG_WARN("InputModuleInitializer: 模块未初始化,无需关闭");
return;
}
E2D_LOG_INFO("InputModuleInitializer: 正在关闭输入模块"); E2D_LOG_INFO("Input module shutting down");
input_ = nullptr; input_ = nullptr;
moduleId_ = INVALID_MODULE_ID;
initialized_ = false; 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<InputModuleConfig>(),
[]() -> UniquePtr<IModuleInitializer> {
auto initializer = makeUnique<InputModuleInitializer>();
initializer->setModuleId(s_inputModuleId);
return initializer;
}
);
}
namespace {
struct InputModuleAutoRegister {
InputModuleAutoRegister() {
register_input_module();
}
};
static InputModuleAutoRegister s_autoRegister;
}
}

View File

@ -93,9 +93,6 @@ bool PlatformModuleInitializer::initialize(const IModuleConfig* config) {
return false; return false;
} }
auto& appConfig = ConfigManager::instance().appConfig();
appConfig.applyPlatformConstraints(*platformConfig_);
if (resolvedPlatform_ == PlatformType::Switch) { if (resolvedPlatform_ == PlatformType::Switch) {
if (!initSwitch()) { if (!initSwitch()) {
return false; return false;

View File

@ -1,17 +1,9 @@
#include <extra2d/platform/window_module.h> #include <extra2d/platform/window_module.h>
#include <extra2d/config/module_registry.h> #include <extra2d/config/module_registry.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/platform/platform_module.h> #include <extra2d/platform/platform_module.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#ifdef E2D_BACKEND_SDL2
#include <SDL.h> #include <SDL.h>
#endif
#ifdef E2D_BACKEND_GLFW
#include <GLFW/glfw3.h>
#endif
#ifdef __SWITCH__ #ifdef __SWITCH__
#include <switch.h> #include <switch.h>
@ -27,16 +19,25 @@ ModuleId get_window_module_id() {
return s_windowModuleId; 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) { bool WindowModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) return false; if (!jsonData) return false;
try { try {
const json& j = *static_cast<const json*>(jsonData); const json& j = *static_cast<const json*>(jsonData);
if (j.contains("backend")) {
backend = j["backend"].get<std::string>();
}
if (j.contains("title")) { if (j.contains("title")) {
windowConfig.title = j["title"].get<std::string>(); windowConfig.title = j["title"].get<std::string>();
} }
@ -46,15 +47,37 @@ bool WindowModuleConfig::loadFromJson(const void* jsonData) {
if (j.contains("height")) { if (j.contains("height")) {
windowConfig.height = j["height"].get<int>(); windowConfig.height = j["height"].get<int>();
} }
if (j.contains("minWidth")) {
windowConfig.minWidth = j["minWidth"].get<int>();
}
if (j.contains("minHeight")) {
windowConfig.minHeight = j["minHeight"].get<int>();
}
if (j.contains("fullscreen")) { if (j.contains("fullscreen")) {
windowConfig.mode = j["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed; windowConfig.mode = j["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
} }
if (j.contains("mode")) {
std::string modeStr = j["mode"].get<std::string>();
if (modeStr == "fullscreen") {
windowConfig.mode = WindowMode::Fullscreen;
} else if (modeStr == "borderless") {
windowConfig.mode = WindowMode::Borderless;
} else {
windowConfig.mode = WindowMode::Windowed;
}
}
if (j.contains("vsync")) { if (j.contains("vsync")) {
windowConfig.vsync = j["vsync"].get<bool>(); windowConfig.vsync = j["vsync"].get<bool>();
} }
if (j.contains("resizable")) { if (j.contains("resizable")) {
windowConfig.resizable = j["resizable"].get<bool>(); windowConfig.resizable = j["resizable"].get<bool>();
} }
if (j.contains("highDPI")) {
windowConfig.highDPI = j["highDPI"].get<bool>();
}
if (j.contains("multisamples")) {
windowConfig.multisamples = j["multisamples"].get<int>();
}
return true; return true;
} catch (...) { } catch (...) {
@ -67,13 +90,28 @@ bool WindowModuleConfig::saveToJson(void* jsonData) const {
try { try {
json& j = *static_cast<json*>(jsonData); json& j = *static_cast<json*>(jsonData);
j["backend"] = backend;
j["title"] = windowConfig.title; j["title"] = windowConfig.title;
j["width"] = windowConfig.width; j["width"] = windowConfig.width;
j["height"] = windowConfig.height; 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["vsync"] = windowConfig.vsync;
j["resizable"] = windowConfig.resizable; j["resizable"] = windowConfig.resizable;
j["highDPI"] = windowConfig.highDPI;
j["multisamples"] = windowConfig.multisamples;
return true; return true;
} catch (...) { } catch (...) {
return false; return false;
@ -83,7 +121,7 @@ bool WindowModuleConfig::saveToJson(void* jsonData) const {
WindowModuleInitializer::WindowModuleInitializer() WindowModuleInitializer::WindowModuleInitializer()
: moduleId_(INVALID_MODULE_ID) : moduleId_(INVALID_MODULE_ID)
, initialized_(false) , initialized_(false)
, backendInitialized_(false) { , sdl2Initialized_(false) {
} }
WindowModuleInitializer::~WindowModuleInitializer() { WindowModuleInitializer::~WindowModuleInitializer() {
@ -92,51 +130,29 @@ WindowModuleInitializer::~WindowModuleInitializer() {
} }
} }
bool WindowModuleInitializer::initBackend() { bool WindowModuleInitializer::initSDL2() {
#ifdef E2D_BACKEND_SDL2 Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
#ifdef __SWITCH__
initFlags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
#endif
if (SDL_Init(initFlags) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError()); E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
return false; return false;
} }
E2D_LOG_INFO("SDL2 backend initialized");
backendInitialized_ = true;
return true;
#endif
#ifdef E2D_BACKEND_GLFW sdl2Initialized_ = true;
if (!glfwInit()) { E2D_LOG_INFO("SDL2 initialized successfully");
E2D_LOG_ERROR("Failed to initialize GLFW");
return false;
}
E2D_LOG_INFO("GLFW backend initialized");
backendInitialized_ = true;
return 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() { void WindowModuleInitializer::shutdownSDL2() {
if (!backendInitialized_) return; if (!sdl2Initialized_) return;
#ifdef E2D_BACKEND_SDL2
SDL_Quit(); SDL_Quit();
E2D_LOG_INFO("SDL2 backend shutdown"); sdl2Initialized_ = false;
#endif E2D_LOG_INFO("SDL2 shutdown");
#ifdef E2D_BACKEND_GLFW
glfwTerminate();
E2D_LOG_INFO("GLFW backend shutdown");
#endif
backendInitialized_ = false;
} }
bool WindowModuleInitializer::initialize(const IModuleConfig* config) { bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
@ -148,60 +164,53 @@ bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
return false; return false;
} }
backend_ = windowConfig->backend;
windowConfig_ = windowConfig->windowConfig; windowConfig_ = windowConfig->windowConfig;
#ifdef __SWITCH__ #ifdef __SWITCH__
backend_ = "switch";
windowConfig_.mode = WindowMode::Fullscreen; windowConfig_.mode = WindowMode::Fullscreen;
windowConfig_.resizable = false; windowConfig_.resizable = false;
windowConfig_.highDPI = false;
E2D_LOG_INFO("Switch platform: forcing fullscreen mode");
#endif #endif
if (!initBackend()) { if (!initSDL2()) {
return false; return false;
} }
#ifdef E2D_BACKEND_SDL2
extern void initSDL2Backend(); extern void initSDL2Backend();
initSDL2Backend(); initSDL2Backend();
#endif
if (!BackendFactory::has(backend_)) { if (!BackendFactory::has("sdl2")) {
E2D_LOG_ERROR("Backend '{}' not available", backend_); E2D_LOG_ERROR("SDL2 backend not registered!");
auto backends = BackendFactory::backends(); shutdownSDL2();
if (backends.empty()) {
E2D_LOG_ERROR("No backends registered!");
shutdownBackend();
return false; return false;
} }
backend_ = backends[0];
E2D_LOG_WARN("Using fallback backend: {}", backend_);
}
if (!createWindow(backend_, windowConfig_)) { if (!createWindow(windowConfig_)) {
E2D_LOG_ERROR("Failed to create window"); E2D_LOG_ERROR("Failed to create window");
shutdownBackend(); shutdownSDL2();
return false; return false;
} }
initialized_ = true; initialized_ = true;
E2D_LOG_INFO("Window module initialized"); E2D_LOG_INFO("Window module initialized");
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height()); 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(" VSync: {}", windowConfig_.vsync);
E2D_LOG_INFO(" Fullscreen: {}", windowConfig_.isFullscreen());
return true; return true;
} }
bool WindowModuleInitializer::createWindow(const std::string& backend, const WindowConfigData& config) { bool WindowModuleInitializer::createWindow(const WindowConfigData& config) {
window_ = BackendFactory::createWindow(backend); window_ = BackendFactory::createWindow("sdl2");
if (!window_) { if (!window_) {
E2D_LOG_ERROR("Failed to create window for backend: {}", backend); E2D_LOG_ERROR("Failed to create SDL2 window");
return false; return false;
} }
if (!window_->create(config)) { if (!window_->create(config)) {
E2D_LOG_ERROR("Failed to create window"); E2D_LOG_ERROR("Failed to create window with specified config");
return false; return false;
} }
@ -218,7 +227,7 @@ void WindowModuleInitializer::shutdown() {
window_.reset(); window_.reset();
} }
shutdownBackend(); shutdownSDL2();
initialized_ = false; initialized_ = false;
} }
@ -246,4 +255,4 @@ namespace {
static WindowModuleAutoRegister s_autoRegister; static WindowModuleAutoRegister s_autoRegister;
} }
} // namespace extra2d }

View File

@ -0,0 +1,23 @@
#include <extra2d/resource/resource_config.h>
#include <algorithm>
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();
}
}

View File

@ -2,48 +2,55 @@
## 概述 ## 概述
Extra2D 采用模块化架构设计,所有核心功能通过模块系统管理。模块系统提供: Extra2D 采用模块化架构设计,所有核心功能通过模块系统和服务系统管理。系统提供:
- **统一的生命周期管理**:初始化、关闭、依赖处理 - **统一的生命周期管理**:初始化、关闭、依赖处理
- **优先级排序**:确保模块按正确顺序初始化 - **优先级排序**:确保模块/服务按正确顺序初始化
- **配置驱动**:每个模块可独立配置 - **配置驱动**:每个模块可独立配置
- **解耦设计**Application 只协调模块,不直接管理任何依赖 - **依赖注入**:通过服务定位器解耦模块间依赖
- **可测试性**:支持 Mock 服务进行单元测试
## 架构图 ## 架构图
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ Application │ │ Application │
│ (只负责协调模块,不直接管理任何依赖) │ (协调模块和服务,通过服务定位器获取依赖)
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐
┌─────────────────────────────────────────────────────────────┐ ▼ ▼
│ ModuleRegistry │ ┌─────────────────────────────┐ ┌─────────────────────────────┐
│ (模块注册表,管理所有模块的生命周期) │ │ ModuleRegistry │ │ ServiceLocator │
└─────────────────────────────────────────────────────────────┘ │ (模块注册表,管理平台级模块) │ │ (服务定位器,管理运行时服务) │
└─────────────────────────────┘ └─────────────────────────────┘
┌─────────────────────┼─────────────────────┐ │ │
▼ ▼ ▼ ┌─────┴─────┐ ┌───────┴───────┐
┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ▼ ▼ ▼ ▼
│ Logger Module │ │ Config Module │ │Platform Module│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ (日志系统) │ │ (配置管理) │ │ (平台初始化) │ │ Config │ │ Window │ │ Scene │ │ Timer │
└───────────────┘ └───────────────┘ └───────────────┘ │ Module │ │ Module │ │ Service │ │ Service │
│ │ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘
└─────────────────────┼─────────────────────┘ │ │
┌─────┴─────┐ ┌─────┴─────┐
┌───────────────┐ ▼ ▼ ▼ ▼
│ Window Module │ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ (窗口管理) │ │ Event │ │ Camera │ │ ... │
└───────────────┘ │ Service │ │ Service │ │ │
└──────────┘ └──────────┘ └──────────┘
┌───────────────┐
│ Render Module │
│ (渲染系统) │
└───────────────┘
``` ```
## 模块 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 | | 0 | `Core` | 核心模块,最先初始化 | Logger, Config, Platform, Window |
| 50 | `Input` | 输入处理 | Input |
| 100 | `Graphics` | 图形渲染 | Render | | 100 | `Graphics` | 图形渲染 | Render |
| 200 | `Audio` | 音频系统 | Audio | | 200 | `Audio` | 音频系统 | Audio |
| 300 | `Physics` | 物理系统 | Physics | | 500 | `Resource` | 资源管理 | Resource |
| 400 | `Gameplay` | 游戏逻辑 | Gameplay |
| 500 | `UI` | 用户界面 | UI |
## 核心接口 ## 服务优先级
服务按优先级从小到大初始化:
| 优先级值 | 枚举名称 | 用途 | 服务示例 |
|---------|---------|------|---------|
| 0 | `Core` | 核心服务 | - |
| 100 | `Event` | 事件系统 | EventService |
| 200 | `Timer` | 计时器 | TimerService |
| 300 | `Scene` | 场景管理 | SceneService |
| 400 | `Camera` | 相机系统 | CameraService |
| 500 | `Resource` | 资源管理 | - |
| 600 | `Audio` | 音频系统 | - |
---
## 模块系统
### IModuleConfig ### IModuleConfig
@ -69,17 +89,12 @@ class IModuleConfig {
public: public:
virtual ~IModuleConfig() = default; virtual ~IModuleConfig() = default;
// 模块信息
virtual ModuleInfo getModuleInfo() const = 0; virtual ModuleInfo getModuleInfo() const = 0;
// 配置管理
virtual std::string getConfigSectionName() const = 0; virtual std::string getConfigSectionName() const = 0;
virtual bool validate() const = 0; virtual bool validate() const = 0;
virtual void resetToDefaults() = 0; virtual void resetToDefaults() = 0;
virtual bool loadFromJson(const void* jsonData) = 0; virtual bool loadFromJson(const void* jsonData) = 0;
virtual bool saveToJson(void* jsonData) const = 0; virtual bool saveToJson(void* jsonData) const = 0;
// 平台约束
virtual void applyPlatformConstraints(PlatformType platform) {} virtual void applyPlatformConstraints(PlatformType platform) {}
}; };
``` ```
@ -93,12 +108,10 @@ class IModuleInitializer {
public: public:
virtual ~IModuleInitializer() = default; virtual ~IModuleInitializer() = default;
// 模块标识
virtual ModuleId getModuleId() const = 0; virtual ModuleId getModuleId() const = 0;
virtual ModulePriority getPriority() const = 0; virtual ModulePriority getPriority() const = 0;
virtual std::vector<ModuleId> getDependencies() const = 0; virtual std::vector<ModuleId> getDependencies() const = 0;
// 生命周期
virtual bool initialize(const IModuleConfig* config) = 0; virtual bool initialize(const IModuleConfig* config) = 0;
virtual void shutdown() = 0; virtual void shutdown() = 0;
virtual bool isInitialized() const = 0; virtual bool isInitialized() const = 0;
@ -114,58 +127,292 @@ class ModuleRegistry {
public: public:
static ModuleRegistry& instance(); static ModuleRegistry& instance();
// 注册模块
ModuleId registerModule( ModuleId registerModule(
UniquePtr<IModuleConfig> config, UniquePtr<IModuleConfig> config,
ModuleInitializerFactory factory ModuleInitializerFactory factory
); );
// 获取模块
IModuleConfig* getModuleConfig(ModuleId id); IModuleConfig* getModuleConfig(ModuleId id);
IModuleInitializer* getInitializer(ModuleId id); IModuleInitializer* getInitializer(ModuleId id);
// 初始化顺序
std::vector<ModuleId> getInitializationOrder() const; std::vector<ModuleId> getInitializationOrder() const;
// 查询
bool hasModule(const std::string& name) const;
std::vector<std::string> 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<typename T>
void registerService(SharedPtr<T> service);
// 注册服务工厂(延迟创建)
template<typename T>
void registerFactory(ServiceFactory<T> factory);
// 获取服务
template<typename T>
SharedPtr<T> getService() const;
// 检查服务是否存在
template<typename T>
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> scene) = 0;
virtual void replaceScene(Ptr<Scene> scene) = 0;
virtual void pushScene(Ptr<Scene> scene) = 0;
virtual void popScene() = 0;
virtual Ptr<Scene> 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<IAudioService>(makeShared<AudioService>());
// 使用服务
auto audio = Application::get().getService<IAudioService>();
audio->playSound("explosion.wav");
```
### 测试时注入 Mock 服务
```cpp
// 创建 Mock 服务
class MockSceneService : public ISceneService {
public:
std::vector<std::string> sceneHistory;
void pushScene(Ptr<Scene> scene) override {
sceneHistory.push_back("push:" + scene->getName());
}
void popScene() override {
sceneHistory.push_back("pop");
}
// 实现其他必要方法...
};
// 测试代码
void testSceneNavigation() {
// 注入 Mock 服务
auto mockService = makeShared<MockSceneService>();
ServiceLocator::instance().registerService<ISceneService>(mockService);
// 执行测试
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
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定义配置类 ### 步骤 1定义配置类
```cpp ```cpp
// my_module.h
class MyModuleConfig : public IModuleConfig { class MyModuleConfig : public IModuleConfig {
public: public:
int someSetting = 42; int someSetting = 42;
std::string somePath;
ModuleInfo getModuleInfo() const override { ModuleInfo getModuleInfo() const override {
ModuleInfo info; ModuleInfo info;
info.name = "MyModule"; info.name = "MyModule";
info.version = "1.0.0"; info.version = "1.0.0";
info.priority = ModulePriority::Gameplay; info.priority = ModulePriority::Graphics;
info.enabled = true; info.enabled = true;
return info; return info;
} }
std::string getConfigSectionName() const override { std::string getConfigSectionName() const override { return "my_module"; }
return "my_module"; bool validate() const override { return someSetting > 0; }
} void resetToDefaults() override { someSetting = 42; }
bool validate() const override {
return someSetting > 0;
}
void resetToDefaults() override {
someSetting = 42;
somePath.clear();
}
bool loadFromJson(const void* jsonData) override; bool loadFromJson(const void* jsonData) override;
bool saveToJson(void* jsonData) const override; bool saveToJson(void* jsonData) const override;
}; };
@ -174,11 +421,10 @@ public:
### 步骤 2定义初始化器 ### 步骤 2定义初始化器
```cpp ```cpp
// my_module.h
class MyModuleInitializer : public IModuleInitializer { class MyModuleInitializer : public IModuleInitializer {
public: public:
ModuleId getModuleId() const override { return moduleId_; } ModuleId getModuleId() const override { return moduleId_; }
ModulePriority getPriority() const override { return ModulePriority::Gameplay; } ModulePriority getPriority() const override { return ModulePriority::Graphics; }
std::vector<ModuleId> getDependencies() const override { return {}; } std::vector<ModuleId> getDependencies() const override { return {}; }
bool initialize(const IModuleConfig* config) override { bool initialize(const IModuleConfig* config) override {
@ -196,9 +442,7 @@ public:
void shutdown() override { void shutdown() override {
if (!initialized_) return; if (!initialized_) return;
// 执行清理逻辑... // 执行清理逻辑...
initialized_ = false; initialized_ = false;
E2D_LOG_INFO("MyModule shutdown"); E2D_LOG_INFO("MyModule shutdown");
} }
@ -222,9 +466,7 @@ namespace extra2d {
static ModuleId s_myModuleId = INVALID_MODULE_ID; static ModuleId s_myModuleId = INVALID_MODULE_ID;
ModuleId get_my_module_id() { ModuleId get_my_module_id() { return s_myModuleId; }
return s_myModuleId;
}
void register_my_module() { void register_my_module() {
if (s_myModuleId != INVALID_MODULE_ID) return; 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 } // namespace extra2d
``` ```
### 步骤 4在 Application 中使用 ---
```cpp
// application.cpp
#include <extra2d/my_module.h>
bool Application::init(const AppConfig& config) {
// 注册模块
register_my_module();
// 初始化所有模块
return initModules();
}
```
## 内置模块 ## 内置模块
@ -283,11 +501,6 @@ config.fileOutput = true;
config.logFilePath = "app.log"; config.logFilePath = "app.log";
``` ```
**平台支持**
- Windows彩色控制台输出
- Linux/macOSANSI 彩色输出
- Switchlibnx console
--- ---
### Config 模块 ### Config 模块
@ -310,12 +523,6 @@ config.appConfig = AppConfig::createDefault();
**平台特定操作** **平台特定操作**
- Switch初始化 romfs 和 socket - Switch初始化 romfs 和 socket
**配置**
```cpp
PlatformModuleConfig config;
config.targetPlatform = PlatformType::Auto; // 自动检测
```
--- ---
### Window 模块 ### Window 模块
@ -330,7 +537,6 @@ config.targetPlatform = PlatformType::Auto; // 自动检测
**配置** **配置**
```cpp ```cpp
WindowModuleConfig config; WindowModuleConfig config;
config.backend = "sdl2";
config.windowConfig.title = "My App"; config.windowConfig.title = "My App";
config.windowConfig.width = 1280; config.windowConfig.width = 1280;
config.windowConfig.height = 720; config.windowConfig.height = 720;
@ -352,17 +558,7 @@ config.targetFPS = 60;
config.multisamples = 4; config.multisamples = 4;
``` ```
## 模块依赖 ---
模块可以声明依赖关系:
```cpp
std::vector<ModuleId> getDependencies() const override {
return { get_window_module_id(), get_config_module_id() };
}
```
ModuleRegistry 会自动解析依赖并按正确顺序初始化。
## 配置文件格式 ## 配置文件格式
@ -376,73 +572,70 @@ ModuleRegistry 会自动解析依赖并按正确顺序初始化。
"fileOutput": false "fileOutput": false
}, },
"window": { "window": {
"backend": "sdl2",
"title": "My Application", "title": "My Application",
"width": 1280, "width": 1280,
"height": 720, "height": 720,
"vsync": true "vsync": true
}, },
"render": { "render": {
"backend": "opengl",
"targetFPS": 60, "targetFPS": 60,
"multisamples": 4 "multisamples": 4
} }
} }
``` ```
---
## 最佳实践 ## 最佳实践
### 1. 单一职责 ### 1. 模块用于平台初始化,服务用于运行时功能
每个模块只负责一个明确的功能领域:
```
✅ Logger Module → 只管理日志
✅ Window Module → 只管理窗口
❌ Game Module → 管理输入、渲染、音频(职责过多)
```
### 2. 依赖最小化
模块应尽量减少对其他模块的依赖:
```cpp
// 好的做法:通过接口获取信息
std::vector<ModuleId> getDependencies() const override {
return { get_window_module_id() }; // 只依赖窗口
}
// 不好的做法:依赖过多
std::vector<ModuleId> getDependencies() const override {
return {
get_window_module_id(),
get_config_module_id(),
get_logger_module_id(),
get_render_module_id()
};
}
```
### 3. 延迟初始化
资源在需要时才初始化,不是在构造函数中:
```cpp ```cpp
// 模块:平台级初始化
class WindowModule : public IModuleInitializer {
bool initialize(const IModuleConfig* config) override { bool initialize(const IModuleConfig* config) override {
if (initialized_) return true; // 创建窗口、初始化 OpenGL 上下文
// 在这里初始化资源
resource_ = createResource();
initialized_ = true;
return true;
} }
};
// 服务:运行时功能
class SceneService : public ISceneService {
void update(float deltaTime) override {
// 每帧更新场景
}
};
```
### 2. 通过服务定位器解耦依赖
```cpp
// 好的做法:通过服务定位器获取依赖
void MyNode::onUpdate(float dt) {
auto camera = ServiceLocator::instance().getService<ICameraService>();
auto pos = camera->screenToWorld(mousePos);
}
// 不好的做法:直接依赖 Application
void MyNode::onUpdate(float dt) {
auto& camera = Application::get().camera(); // 耦合度高
}
```
### 3. 使用接口便于测试
```cpp
// 定义接口
class IAudioService : public IService { ... };
// 生产环境使用真实实现
auto audio = makeShared<AudioService>();
// 测试环境使用 Mock
auto audio = makeShared<MockAudioService>();
``` ```
### 4. 安全关闭 ### 4. 安全关闭
关闭时要检查状态,避免重复关闭:
```cpp ```cpp
void shutdown() override { void shutdown() override {
if (!initialized_) return; // 避免重复关闭 if (!initialized_) return; // 避免重复关闭
@ -450,27 +643,12 @@ void shutdown() override {
// 清理资源 // 清理资源
resource_.reset(); resource_.reset();
setState(ServiceState::Stopped);
initialized_ = false; 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 ```cpp
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId); auto services = ServiceLocator::instance().getAllServices();
if (initializer && initializer->isInitialized()) { for (const auto& service : services) {
E2D_LOG_INFO("Module is initialized"); auto info = service->getServiceInfo();
E2D_LOG_INFO("Service: {} (state: {})",
info.name, static_cast<int>(info.state));
} }
``` ```
---
## 示例 ## 示例
完整示例请参考: 完整示例请参考:
- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例 - [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/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现
- [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现 - [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现

View File

@ -6,6 +6,8 @@
*/ */
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/platform/window_config.h>
#include <iostream> #include <iostream>
using namespace extra2d; using namespace extra2d;
@ -24,13 +26,6 @@ int main(int argc, char* argv[]) {
AppConfig config = AppConfig::createDefault(); AppConfig config = AppConfig::createDefault();
config.appName = "Extra2D Demo"; config.appName = "Extra2D Demo";
config.appVersion = "1.0.0"; 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;
Application &app = Application::get(); Application &app = Application::get();
@ -40,13 +35,15 @@ int main(int argc, char* argv[]) {
} }
std::cout << "Application initialized successfully!" << std::endl; std::cout << "Application initialized successfully!" << std::endl;
std::cout << "Window: " << app.window().width() << "x" << app.window().height() << std::endl; std::cout << "Window: " << app.window().width() << "x"
std::cout << "Running main loop. Press ESC or close window to exit." << std::endl; << app.window().height() << std::endl;
std::cout << "Running main loop. Press ESC or close window to exit."
<< std::endl;
auto scene = Scene::create(); auto scene = Scene::create();
scene->setBackgroundColor(Colors::SkyBlue); scene->setBackgroundColor(Colors::SkyBlue);
scene->setViewportSize(static_cast<float>(config.window.width), scene->setViewportSize(static_cast<float>(app.window().width()),
static_cast<float>(config.window.height)); static_cast<float>(app.window().height()));
app.enterScene(scene); app.enterScene(scene);
app.run(); app.run();

View File

@ -1,7 +1,14 @@
-- ============================================== -- ==============================================
-- Extra2D - 2D Game Engine -- Extra2D - 2D Game Engine
-- Build System: Xmake -- 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") set_description("Enable debug logging")
option_end() 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 host_plat = os.host()
local target_plat = get_config("plat") or host_plat 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 not supported_plats[target_plat] then
if host_plat == "windows" then if host_plat == "windows" then
target_plat = "mingw" target_plat = "mingw"
elseif host_plat == "linux" then
target_plat = "linux"
elseif host_plat == "macosx" then
target_plat = "macosx"
else else
error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, switch") error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, windows, linux, macosx, switch")
end end
end end
set_plat(target_plat) set_plat(target_plat)
-- 设置架构
if target_plat == "switch" then if target_plat == "switch" then
set_arch("arm64") 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") set_arch("x86_64")
end end
@ -69,20 +79,13 @@ elseif target_plat == "mingw" then
end end
-- ============================================== -- ==============================================
-- 添加依赖包 (MinGW) -- 添加依赖包
-- ============================================== -- ==============================================
if target_plat == "mingw" then if target_plat ~= "switch" then
local backend = get_config("backend") or "sdl2"
add_requires("glm") add_requires("glm")
add_requires("nlohmann_json") add_requires("nlohmann_json")
if backend == "sdl2" then
add_requires("libsdl2") add_requires("libsdl2")
elseif backend == "glfw" then
add_requires("glfw")
end
end end
-- ============================================== -- ==============================================
@ -134,10 +137,16 @@ target("demo_basic")
add_files("examples/basic/main.cpp") add_files("examples/basic/main.cpp")
-- 平台配置 -- 平台配置
local target_plat = get_config("plat") or os.host() local plat = get_config("plat") or os.host()
if target_plat == "mingw" then if plat == "mingw" or plat == "windows" then
add_packages("glm", "nlohmann_json", "libsdl2") add_packages("glm", "nlohmann_json", "libsdl2")
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") 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 end
-- 构建后安装Shader文件 -- 构建后安装Shader文件

View File

@ -1,6 +1,12 @@
-- ============================================== -- ==============================================
-- Extra2D 引擎库共享配置 -- Extra2D 引擎库共享配置
-- 被主项目和示例共享使用 -- 被主项目和示例共享使用
--
-- 窗口后端统一使用 SDL2支持以下平台
-- - Windows (MinGW)
-- - Linux
-- - macOS
-- - Nintendo Switch
-- ============================================== -- ==============================================
-- 获取当前平台 -- 获取当前平台
@ -8,11 +14,6 @@ local function get_current_plat()
return get_config("plat") or os.host() return get_config("plat") or os.host()
end end
-- 获取后端配置
local function get_backend()
return get_config("backend") or "sdl2"
end
-- 定义 Extra2D 引擎库目标 -- 定义 Extra2D 引擎库目标
function define_extra2d_engine() function define_extra2d_engine()
target("extra2d") target("extra2d")
@ -22,42 +23,40 @@ function define_extra2d_engine()
add_files("Extra2D/src/**.cpp") add_files("Extra2D/src/**.cpp")
add_files("Extra2D/src/glad/glad.c") add_files("Extra2D/src/glad/glad.c")
-- 平台后端源文件 -- SDL2 后端源文件(所有平台统一使用)
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_files("Extra2D/src/platform/backends/sdl2/*.cpp")
add_defines("E2D_BACKEND_SDL2") add_defines("E2D_BACKEND_SDL2")
elseif backend == "glfw" then
add_files("Extra2D/src/platform/backends/glfw/*.cpp")
add_defines("E2D_BACKEND_GLFW")
end
-- 头文件路径 -- 头文件路径
add_includedirs("Extra2D/include", {public = true}) add_includedirs("Extra2D/include", {public = true})
add_includedirs("Extra2D/include/extra2d/platform", {public = true}) add_includedirs("Extra2D/include/extra2d/platform", {public = true})
-- 平台配置 -- 平台配置
local plat = get_current_plat()
if plat == "switch" then if plat == "switch" then
-- Nintendo Switch 平台配置
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true}) add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
add_linkdirs(devkitPro .. "/portlibs/switch/lib") 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}) {public = true})
elseif plat == "mingw" then elseif plat == "mingw" or plat == "windows" then
-- Windows (MinGW) 平台配置
add_packages("glm", "nlohmann_json", {public = true}) add_packages("glm", "nlohmann_json", {public = true})
if backend == "sdl2" then
add_packages("libsdl2", {public = true}) add_packages("libsdl2", {public = true})
elseif backend == "glfw" then add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi",
add_packages("glfw", {public = true}) {public = true})
end elseif plat == "linux" then
-- Linux 平台配置
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true}) 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 end
-- 编译器标志 (C 和 C++ 共用) -- 编译器标志 (C 和 C++ 共用)