refactor(config): 重构配置系统并添加模块化架构

feat(config): 添加新的配置管理器、加载器和平台检测器
feat(module): 实现模块注册表和初始化系统
refactor(window): 将窗口配置迁移到新的配置系统
refactor(input): 重构输入系统以支持模块化
refactor(render): 更新渲染系统以适配新架构
refactor(graphics): 移除冗余API并简化渲染目标接口
refactor(app): 重构应用类以整合新的配置和模块系统
refactor(color): 移除废弃的ColorConstants结构
style: 清理代码并修复格式问题
This commit is contained in:
ChestnutYueyue 2026-02-15 08:51:31 +08:00
parent 9439e200d7
commit 34fe0bafcb
30 changed files with 7109 additions and 167 deletions

View File

@ -1,6 +1,10 @@
#pragma once
#include <extra2d/core/types.h>
#include <extra2d/config/app_config.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/config/module_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/platform/iwindow.h>
#include <string>
@ -15,36 +19,18 @@ class EventDispatcher;
class Camera;
class ViewportAdapter;
/**
* @brief
*/
enum class PlatformType { Auto = 0, PC, Switch };
/**
* @brief
*/
struct AppConfig {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
bool fullscreen = false;
bool resizable = true;
bool vsync = true;
int fpsLimit = 0;
BackendType renderBackend = BackendType::OpenGL;
int msaaSamples = 0;
PlatformType platform = PlatformType::Auto;
bool enableCursors = true;
bool enableDpiScale = false;
std::string backend = "sdl2";
};
/**
* @brief Application -
*
*
* ConfigManager ModuleRegistry
*/
class Application {
public:
/**
* @brief
* @return Application
*/
static Application& get();
Application(const Application&) = delete;
@ -52,57 +38,175 @@ public:
/**
* @brief 使
* @return true
*/
bool init();
/**
* @brief 使
* @param config
* @return true
*/
bool init(const AppConfig& config);
/**
* @brief
* @param path .json .ini
* @param configPath .json .ini
* @return true
*/
bool init(const std::string& path);
bool init(const std::string& configPath);
/**
* @brief
*/
void shutdown();
/**
* @brief
*/
void run();
/**
* @brief 退
*/
void quit();
/**
* @brief
*/
void pause();
/**
* @brief
*/
void resume();
/**
* @brief
* @return true
*/
bool isPaused() const { return paused_; }
/**
* @brief
* @return true
*/
bool isRunning() const { return running_; }
/**
* @brief
* @return
*/
IWindow& window() { return *window_; }
/**
* @brief
* @return
*/
RenderBackend& renderer() { return *renderer_; }
/**
* @brief
* @return
*/
IInput& input();
/**
* @brief
* @return
*/
SceneManager& scenes();
/**
* @brief
* @return
*/
TimerManager& timers();
/**
* @brief
* @return
*/
EventQueue& eventQueue();
/**
* @brief
* @return
*/
EventDispatcher& eventDispatcher();
/**
* @brief
* @return
*/
Camera& camera();
/**
* @brief
* @return
*/
ViewportAdapter& viewportAdapter();
/**
* @brief
* @param scene
*/
void enterScene(Ptr<class Scene> scene);
/**
* @brief
* @return
*/
float deltaTime() const { return deltaTime_; }
/**
* @brief
* @return
*/
float totalTime() const { return totalTime_; }
/**
* @brief
* @return FPS
*/
int fps() const { return currentFps_; }
const AppConfig& getConfig() const { return config_; }
/**
* @brief
* @return
*/
ConfigManager& config();
/**
* @brief
* @return
*/
const AppConfig& getConfig() const;
private:
Application() = default;
~Application();
/**
* @brief
* @return true
*/
bool initImpl();
void mainLoop();
void update();
void render();
AppConfig config_;
/**
* @brief
*/
void mainLoop();
/**
* @brief
*/
void update();
/**
* @brief
*/
void render();
UniquePtr<IWindow> window_;
UniquePtr<RenderBackend> renderer_;
@ -124,6 +228,10 @@ private:
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
ModuleId windowModuleId_ = INVALID_MODULE_ID;
ModuleId inputModuleId_ = INVALID_MODULE_ID;
ModuleId renderModuleId_ = INVALID_MODULE_ID;
};
} // namespace extra2d

View File

@ -0,0 +1,226 @@
#pragma once
#include <extra2d/config/platform_config.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/render_backend.h>
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// 窗口模式枚举
// ============================================================================
enum class WindowMode {
Windowed,
Fullscreen,
Borderless
};
// ============================================================================
// 窗口配置数据
// ============================================================================
struct WindowConfigData {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
int minWidth = 320;
int minHeight = 240;
int maxWidth = 0;
int maxHeight = 0;
WindowMode mode = WindowMode::Windowed;
bool resizable = true;
bool borderless = false;
bool alwaysOnTop = false;
bool centered = true;
int posX = -1;
int posY = -1;
bool hideOnClose = false;
bool minimizeOnClose = true;
float opacity = 1.0f;
bool transparentFramebuffer = false;
bool highDPI = true;
float contentScale = 1.0f;
bool vsync = true;
int multisamples = 0;
bool visible = true;
bool decorated = true;
bool isSizeValid() const { return width > 0 && height > 0; }
bool hasPosition() const { return posX >= 0 && posY >= 0; }
float aspectRatio() const { return static_cast<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 {
WindowConfigData window;
RenderConfigData render;
AudioConfigData audio;
DebugConfigData debug;
InputConfigData input;
ResourceConfigData resource;
std::string appName = "Extra2D App";
std::string appVersion = "1.0.0";
std::string organization = "";
std::string configFile = "config.json";
PlatformType targetPlatform = PlatformType::Auto;
/**
* @brief
* @return
*/
static AppConfig createDefault();
/**
* @brief
* @return true false
*/
bool validate() const;
/**
* @brief
* @param platform
*/
void applyPlatformConstraints(const PlatformConfig& platform);
/**
* @brief
*/
void reset();
/**
* @brief
* @param other
*/
void merge(const AppConfig& other);
/**
* @brief
* @return true
*/
bool isValid() const { return validate(); }
/**
* @brief
* @return
*/
float aspectRatio() const { return window.aspectRatio(); }
};
}

View File

@ -0,0 +1,189 @@
#pragma once
#include <extra2d/config/app_config.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
// ============================================================================
// 配置加载结果
// ============================================================================
struct ConfigLoadResult {
bool success = false;
std::string errorMessage;
int errorLine = -1;
std::string errorField;
static ConfigLoadResult ok() { return ConfigLoadResult{true, "", -1, ""}; }
static ConfigLoadResult error(const std::string& msg, int line = -1, const std::string& field = "") {
return ConfigLoadResult{false, msg, line, field};
}
bool isOk() const { return success; }
bool hasError() const { return !success; }
};
// ============================================================================
// 配置保存结果
// ============================================================================
struct ConfigSaveResult {
bool success = false;
std::string errorMessage;
static ConfigSaveResult ok() { return ConfigSaveResult{true, ""}; }
static ConfigSaveResult error(const std::string& msg) {
return ConfigSaveResult{false, msg};
}
bool isOk() const { return success; }
bool hasError() const { return !success; }
};
// ============================================================================
// 配置加载器抽象接口
// ============================================================================
class ConfigLoader {
public:
virtual ~ConfigLoader() = default;
/**
* @brief
* @param filepath
* @param config
* @return
*/
virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0;
/**
* @brief
* @param filepath
* @param config
* @return
*/
virtual ConfigSaveResult save(const std::string& filepath, const AppConfig& config) = 0;
/**
* @brief
* @param content
* @param config
* @return
*/
virtual ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) = 0;
/**
* @brief
* @param config
* @return
*/
virtual std::string saveToString(const AppConfig& config) = 0;
/**
* @brief
* @return "json"
*/
virtual const char* extension() const = 0;
/**
* @brief
* @param filepath
* @return true
*/
virtual bool supportsFile(const std::string& filepath) const = 0;
/**
* @brief
* @return
*/
virtual UniquePtr<ConfigLoader> clone() const = 0;
};
// ============================================================================
// JSON 配置加载器
// ============================================================================
class JsonConfigLoader : public ConfigLoader {
public:
JsonConfigLoader() = default;
~JsonConfigLoader() override = default;
ConfigLoadResult load(const std::string& filepath, AppConfig& config) override;
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
std::string saveToString(const AppConfig& config) override;
const char* extension() const override { return "json"; }
bool supportsFile(const std::string& filepath) const override;
UniquePtr<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 配置加载器
// ============================================================================
class IniConfigLoader : public ConfigLoader {
public:
IniConfigLoader() = default;
~IniConfigLoader() override = default;
ConfigLoadResult load(const std::string& filepath, AppConfig& config) override;
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
std::string saveToString(const AppConfig& config) override;
const char* extension() const override { return "ini"; }
bool supportsFile(const std::string& filepath) const override;
UniquePtr<ConfigLoader> clone() const override;
private:
std::string sectionKey(const std::string& section, const std::string& key) const;
ConfigLoadResult parseInt(const std::string& value, int& result, const std::string& fieldName);
ConfigLoadResult parseFloat(const std::string& value, float& result, const std::string& fieldName);
ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName);
};
// ============================================================================
// 配置加载器工厂
// ============================================================================
class ConfigLoaderFactory {
public:
/**
* @brief
* @param extension
* @return nullptr
*/
static UniquePtr<ConfigLoader> create(const std::string& extension);
/**
* @brief
* @param filepath
* @return nullptr
*/
static UniquePtr<ConfigLoader> createForFile(const std::string& filepath);
/**
* @brief
* @param extension
* @return true
*/
static bool isExtensionSupported(const std::string& extension);
/**
* @brief
* @return
*/
static std::vector<std::string> getSupportedExtensions();
};
}

View File

@ -0,0 +1,314 @@
#pragma once
#include <extra2d/config/app_config.h>
#include <extra2d/config/config_loader.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/core/types.h>
#include <mutex>
#include <string>
#include <unordered_map>
namespace extra2d {
// ============================================================================
// 配置变更事件
// ============================================================================
struct ConfigChangeEvent {
std::string section;
std::string field;
std::string oldValue;
std::string newValue;
};
// ============================================================================
// 配置变更回调类型
// ============================================================================
using ConfigChangeCallback = Function<void(const ConfigChangeEvent &)>;
// ============================================================================
// 配置管理器(单例)
// ============================================================================
class ConfigManager {
public:
using ModuleConfigPtr = Ptr<void>;
/**
* @brief
* @return
*/
static ConfigManager &instance();
/**
* @brief
* @param configPath
* @return true
*/
bool initialize(const std::string &configPath = "config.json");
/**
* @brief
*/
void shutdown();
/**
* @brief
* @return true
*/
bool isInitialized() const;
/**
* @brief
* @param filepath 使
* @return
*/
ConfigLoadResult loadConfig(const std::string &filepath = "");
/**
* @brief
* @param filepath 使
* @return
*/
ConfigSaveResult saveConfig(const std::string &filepath = "");
/**
* @brief
* @return
*/
ConfigLoadResult reload();
/**
* @brief
* @return
*/
const AppConfig &appConfig() const;
/**
* @brief
* @return
*/
AppConfig &appConfig();
/**
* @brief
* @param config
*/
void setAppConfig(const AppConfig &config);
/**
* @brief
* @return
*/
PlatformConfig *platformConfig();
/**
* @brief
* @return
*/
const PlatformConfig *platformConfig() const;
/**
* @brief
* @param callback
* @return ID
*/
int registerChangeCallback(ConfigChangeCallback callback);
/**
* @brief
* @param callbackId ID
*/
void unregisterChangeCallback(int callbackId);
/**
* @brief
*/
void clearChangeCallbacks();
/**
* @brief
* @param moduleName
* @param config
*/
void registerModuleConfig(const std::string &moduleName, Ptr<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
* @param section
* @param key
* @param value
*/
void setValue(const std::string &section, const std::string &key,
const std::string &value);
/**
* @brief
* @param section
* @param key
* @param value
*/
void setValue(const std::string &section, const std::string &key, int value);
/**
* @brief
* @param section
* @param key
* @param value
*/
void setValue(const std::string &section, const std::string &key,
float value);
/**
* @brief
* @param section
* @param key
* @param value
*/
void setValue(const std::string &section, const std::string &key, bool value);
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
std::string getValue(const std::string &section, const std::string &key,
const std::string &defaultValue = "") const;
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
int getIntValue(const std::string &section, const std::string &key,
int defaultValue = 0) const;
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
float getFloatValue(const std::string &section, const std::string &key,
float defaultValue = 0.0f) const;
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
bool getBoolValue(const std::string &section, const std::string &key,
bool defaultValue = false) const;
/**
* @brief
*/
void resetToDefaults();
/**
* @brief
* @return true
*/
bool hasUnsavedChanges() const;
/**
* @brief
*/
void markModified();
/**
* @brief
*/
void clearModified();
/**
* @brief
* @return
*/
const std::string &configPath() const { return m_configPath; }
/**
* @brief
* @param enabled
* @param interval
*/
void setAutoSave(bool enabled, float interval = 30.0f);
/**
* @brief
* @return true
*/
bool isAutoSaveEnabled() const { return m_autoSaveEnabled; }
/**
* @brief
* @param deltaTime
*/
void update(float deltaTime);
private:
ConfigManager();
~ConfigManager();
ConfigManager(const ConfigManager &) = delete;
ConfigManager &operator=(const ConfigManager &) = delete;
void notifyChangeCallbacks(const ConfigChangeEvent &event);
void applyConfigToInternal(const AppConfig &config);
void extractConfigFromInternal(AppConfig &config) const;
AppConfig m_appConfig;
UniquePtr<PlatformConfig> m_platformConfig;
UniquePtr<ConfigLoader> m_loader;
std::string m_configPath;
bool m_initialized = false;
bool m_modified = false;
mutable std::mutex m_mutex;
std::unordered_map<int, ConfigChangeCallback> m_changeCallbacks;
int m_nextCallbackId = 1;
std::unordered_map<std::string, ModuleConfigPtr> m_moduleConfigs;
bool m_autoSaveEnabled = false;
float m_autoSaveInterval = 30.0f;
float m_autoSaveTimer = 0.0f;
};
// ============================================================================
// 便捷宏定义
// ============================================================================
#define CONFIG_MANAGER ConfigManager::instance()
} // namespace extra2d

View File

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

View File

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

View File

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

View File

@ -0,0 +1,75 @@
#pragma once
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
// ============================================================================
// 平台类型枚举
// ============================================================================
enum class PlatformType {
Auto,
Windows,
Switch,
Linux,
macOS
};
// ============================================================================
// 平台能力结构
// ============================================================================
struct PlatformCapabilities {
bool supportsWindowed = true;
bool supportsFullscreen = true;
bool supportsBorderless = true;
bool supportsCursor = true;
bool supportsCursorHide = true;
bool supportsDPIAwareness = true;
bool supportsVSync = true;
bool supportsMultiMonitor = true;
bool supportsClipboard = true;
bool supportsGamepad = true;
bool supportsTouch = false;
bool supportsKeyboard = true;
bool supportsMouse = true;
bool supportsResize = true;
bool supportsHighDPI = true;
int maxTextureSize = 16384;
int preferredScreenWidth = 1920;
int preferredScreenHeight = 1080;
float defaultDPI = 96.0f;
bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; }
bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; }
bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; }
bool isConsole() const { return !supportsWindowed && supportsGamepad; }
};
// ============================================================================
// 平台配置抽象接口
// ============================================================================
class PlatformConfig {
public:
virtual ~PlatformConfig() = default;
virtual PlatformType platformType() const = 0;
virtual const char* platformName() const = 0;
virtual const PlatformCapabilities& capabilities() const = 0;
virtual void applyConstraints(struct AppConfig& config) const = 0;
virtual void applyDefaults(struct AppConfig& config) const = 0;
virtual bool validateConfig(struct AppConfig& config) const = 0;
virtual int getRecommendedWidth() const = 0;
virtual int getRecommendedHeight() const = 0;
virtual bool isResolutionSupported(int width, int height) const = 0;
};
// ============================================================================
// 平台配置工厂函数声明
// ============================================================================
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
const char* getPlatformTypeName(PlatformType type);
}

View File

@ -0,0 +1,174 @@
#pragma once
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
// ============================================================================
// 平台检测器工具类
// ============================================================================
class PlatformDetector {
public:
/**
* @brief
* @return
*/
static PlatformType detect();
/**
* @brief
* @return "Windows", "Linux", "macOS", "Switch"
*/
static const char* platformName();
/**
* @brief
* @param type
* @return
*/
static const char* platformName(PlatformType type);
/**
* @brief
* @return true
*/
static bool isDesktopPlatform();
/**
* @brief
* @return true
*/
static bool isConsolePlatform();
/**
* @brief
* @return true
*/
static bool isMobilePlatform();
/**
* @brief
* @return
*/
static PlatformCapabilities capabilities();
/**
* @brief
* @param type
* @return
*/
static PlatformCapabilities capabilities(PlatformType type);
/**
* @brief
* @return
*/
static AppConfig platformDefaults();
/**
* @brief
* @param type
* @return
*/
static AppConfig platformDefaults(PlatformType type);
/**
* @brief
* @param width
* @param height
*/
static void getRecommendedResolution(int& width, int& height);
/**
* @brief DPI
* @return DPI
*/
static float getDefaultDPI();
/**
* @brief
* @param feature
* @return true
*/
static bool supportsFeature(const std::string& feature);
/**
* @brief
* @return MB 0
*/
static int getSystemMemoryMB();
/**
* @brief CPU
* @return CPU
*/
static int getCPUCoreCount();
/**
* @brief 线
* @return true
*/
static bool supportsMultithreadedRendering();
/**
* @brief
* @param appName
* @return
*/
static std::string getConfigPath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getSavePath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getCachePath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getLogPath(const std::string& appName);
/**
* @brief
* @return true
*/
static bool isLittleEndian();
/**
* @brief
* @return true
*/
static bool isBigEndian();
/**
* @brief
* @return
*/
static std::string getPlatformSummary();
private:
static PlatformCapabilities getWindowsCapabilities();
static PlatformCapabilities getLinuxCapabilities();
static PlatformCapabilities getMacOSCapabilities();
static PlatformCapabilities getSwitchCapabilities();
static AppConfig getWindowsDefaults();
static AppConfig getLinuxDefaults();
static AppConfig getMacOSDefaults();
static AppConfig getSwitchDefaults();
};
}

View File

@ -155,17 +155,4 @@ inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f};
inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
} // namespace Colors
// 为了向后兼容,在 Color 结构体内提供静态引用
struct ColorConstants {
static const Color &White;
static const Color &Black;
static const Color &Red;
static const Color &Green;
static const Color &Blue;
static const Color &Yellow;
static const Color &Cyan;
static const Color &Magenta;
static const Color &Transparent;
};
} // namespace extra2d

View File

@ -8,6 +8,16 @@
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
// Config
#include <extra2d/config/app_config.h>
#include <extra2d/config/config_loader.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
// Platform
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>

View File

@ -0,0 +1,147 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/types.h>
namespace extra2d {
/**
* @brief
* IModuleConfig
*/
class RenderModuleConfig : public IModuleConfig {
public:
BackendType backend = BackendType::OpenGL;
bool vsync = true;
int targetFPS = 60;
int multisamples = 0;
bool sRGBFramebuffer = false;
int spriteBatchSize = 1000;
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.name = "Render";
info.version = "1.0.0";
info.priority = ModulePriority::Graphics;
info.enabled = true;
return info;
}
/**
* @brief
* @return
*/
std::string getConfigSectionName() const override { return "render"; }
/**
* @brief
* @return true
*/
bool validate() const override;
/**
* @brief
*
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool loadFromJson(const void* jsonData) override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool saveToJson(void* jsonData) const override;
};
/**
* @brief
* IModuleInitializer
*
*/
class RenderModuleInitializer : public IModuleInitializer {
public:
/**
* @brief
*/
RenderModuleInitializer();
/**
* @brief
*/
~RenderModuleInitializer() override;
/**
* @brief
* @return
*/
ModuleId getModuleId() const override { return moduleId_; }
/**
* @brief
* @return
*/
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
/**
* @brief
* @return
*/
std::vector<ModuleId> getDependencies() const override;
/**
* @brief
* @param config
* @return true
*/
bool initialize(const IModuleConfig* config) override;
/**
* @brief
*/
void shutdown() override;
/**
* @brief
* @return true
*/
bool isInitialized() const override { return initialized_; }
/**
* @brief
* @return
*/
RenderBackend* getRenderer() const { return renderer_.get(); }
/**
* @brief
* @param windowModuleId
*/
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; }
private:
ModuleId moduleId_ = INVALID_MODULE_ID;
ModuleId windowModuleId_ = INVALID_MODULE_ID;
UniquePtr<RenderBackend> renderer_;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -16,7 +16,6 @@ struct RenderTargetConfig {
int height = 600; // 高度
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
bool hasDepth = true; // 是否包含深度缓冲
bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth)
bool hasStencil = false; // 是否包含模板缓冲
int samples = 1; // 多重采样数 (1 = 无MSAA)
bool autoResize = true; // 是否自动调整大小
@ -47,11 +46,6 @@ public:
*/
bool create(const RenderTargetConfig &config);
/**
* @brief create的别名API
*/
bool init(const RenderTargetConfig &config) { return create(config); }
/**
* @brief
*/
@ -62,11 +56,6 @@ public:
*/
void destroy();
/**
* @brief destroy的别名API
*/
void shutdown() { destroy(); }
/**
* @brief
*/
@ -144,14 +133,6 @@ public:
*/
void copyTo(RenderTarget &target);
/**
* @brief blitTo的别名API
* @param target
* @param color
* @param depth
*/
void blitTo(RenderTarget &target, bool color = true, bool depth = false);
/**
* @brief
*/

View File

@ -0,0 +1,148 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/core/types.h>
namespace extra2d {
/**
* @brief
* IModuleConfig
*/
class InputModuleConfig : public IModuleConfig {
public:
bool enableKeyboard = true;
bool enableMouse = true;
bool enableGamepad = true;
bool enableTouch = true;
float deadzone = 0.15f;
float mouseSensitivity = 1.0f;
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.name = "Input";
info.version = "1.0.0";
info.priority = ModulePriority::Input;
info.enabled = true;
return info;
}
/**
* @brief
* @return
*/
std::string getConfigSectionName() const override { return "input"; }
/**
* @brief
* @return true
*/
bool validate() const override;
/**
* @brief
*
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool loadFromJson(const void* jsonData) override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool saveToJson(void* jsonData) const override;
};
/**
* @brief
* IModuleInitializer
*
*/
class InputModuleInitializer : public IModuleInitializer {
public:
/**
* @brief
*/
InputModuleInitializer();
/**
* @brief
*/
~InputModuleInitializer() override;
/**
* @brief
* @return
*/
ModuleId getModuleId() const override { return moduleId_; }
/**
* @brief
* @return
*/
ModulePriority getPriority() const override { return ModulePriority::Input; }
/**
* @brief
*
* @return
*/
std::vector<ModuleId> getDependencies() const override;
/**
* @brief
* @param config
* @return true
*/
bool initialize(const IModuleConfig* config) override;
/**
* @brief
*/
void shutdown() override;
/**
* @brief
* @return true
*/
bool isInitialized() const override { return initialized_; }
/**
* @brief
* @return
*/
IInput* getInput() const { return input_; }
/**
* @brief
* @param windowModuleId
*/
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; }
private:
ModuleId moduleId_ = INVALID_MODULE_ID;
ModuleId windowModuleId_ = INVALID_MODULE_ID;
IInput* input_ = nullptr;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -2,6 +2,7 @@
#include <extra2d/core/types.h>
#include <extra2d/core/math_types.h>
#include <extra2d/config/app_config.h>
#include <functional>
#include <string>
@ -9,23 +10,6 @@ namespace extra2d {
class IInput;
/**
* @brief
*/
struct WindowConfig {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
bool fullscreen = false;
bool fullscreenDesktop = true;
bool resizable = true;
bool vsync = true;
int msaaSamples = 0;
bool centerWindow = true;
bool visible = true;
bool decorated = true;
};
/**
* @brief
*/
@ -52,7 +36,7 @@ public:
* @param cfg
* @return
*/
virtual bool create(const WindowConfig& cfg) = 0;
virtual bool create(const WindowConfigData& cfg) = 0;
/**
* @brief

View File

@ -0,0 +1,136 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/config/app_config.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/core/types.h>
namespace extra2d {
/**
* @brief
* IModuleConfig
*/
class WindowModuleConfig : public IModuleConfig {
public:
WindowConfigData windowConfig;
std::string backend = "sdl2";
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.name = "Window";
info.version = "1.0.0";
info.priority = ModulePriority::Platform;
info.enabled = true;
return info;
}
/**
* @brief
* @return
*/
std::string getConfigSectionName() const override { return "window"; }
/**
* @brief
* @return true
*/
bool validate() const override;
/**
* @brief
*
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool loadFromJson(const void* jsonData) override;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool saveToJson(void* jsonData) const override;
};
/**
* @brief
* IModuleInitializer
*/
class WindowModuleInitializer : public IModuleInitializer {
public:
/**
* @brief
*/
WindowModuleInitializer();
/**
* @brief
*/
~WindowModuleInitializer() override;
/**
* @brief
* @return
*/
ModuleId getModuleId() const override { return moduleId_; }
/**
* @brief
* @return
*/
ModulePriority getPriority() const override { return ModulePriority::Platform; }
/**
* @brief
* @return
*/
std::vector<ModuleId> getDependencies() const override { return {}; }
/**
* @brief
* @param config
* @return true
*/
bool initialize(const IModuleConfig* config) override;
/**
* @brief
*/
void shutdown() override;
/**
* @brief
* @return true
*/
bool isInitialized() const override { return initialized_; }
/**
* @brief
* @return
*/
IWindow* getWindow() const { return window_.get(); }
private:
ModuleId moduleId_ = INVALID_MODULE_ID;
UniquePtr<IWindow> window_;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -1,4 +1,6 @@
#include <extra2d/app/application.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h>
#include <extra2d/graphics/camera.h>
@ -20,6 +22,10 @@
namespace extra2d {
/**
* @brief
* @return
*/
static double getTimeSeconds() {
#ifdef __SWITCH__
struct timespec ts;
@ -39,7 +45,9 @@ Application& Application::get() {
return instance;
}
Application::~Application() { shutdown(); }
Application::~Application() {
shutdown();
}
bool Application::init() {
AppConfig cfg;
@ -52,42 +60,74 @@ bool Application::init(const AppConfig& config) {
return true;
}
config_ = config;
E2D_LOG_INFO("Initializing application with config...");
if (!ConfigManager::instance().initialize()) {
E2D_LOG_ERROR("Failed to initialize ConfigManager");
return false;
}
ConfigManager::instance().setAppConfig(config);
return initImpl();
}
bool Application::init(const std::string& path) {
bool Application::init(const std::string& configPath) {
if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true;
}
E2D_LOG_INFO("Loading config from: {}", path);
AppConfig cfg;
if (path.find(".json") != std::string::npos) {
// TODO: 使用 nlohmann_json 加载配置
E2D_LOG_WARN("JSON config loading not yet implemented, using defaults");
} else if (path.find(".ini") != std::string::npos) {
// TODO: 实现 INI 配置加载
E2D_LOG_WARN("INI config loading not yet implemented, using defaults");
E2D_LOG_INFO("Initializing application from config file: {}", configPath);
if (!ConfigManager::instance().initialize(configPath)) {
E2D_LOG_WARN("Failed to load config from file, using defaults");
if (!ConfigManager::instance().initialize()) {
E2D_LOG_ERROR("Failed to initialize ConfigManager");
return false;
}
}
config_ = cfg;
return initImpl();
}
bool Application::initImpl() {
PlatformType platform = config_.platform;
auto& configMgr = ConfigManager::instance();
AppConfig& appConfig = configMgr.appConfig();
PlatformType platform = appConfig.targetPlatform;
if (platform == PlatformType::Auto) {
#ifdef __SWITCH__
platform = PlatformType::Switch;
#else
platform = PlatformType::PC;
#ifdef _WIN32
platform = PlatformType::Windows;
#elif defined(__linux__)
platform = PlatformType::Linux;
#elif defined(__APPLE__)
platform = PlatformType::macOS;
#else
platform = PlatformType::Windows;
#endif
#endif
}
E2D_LOG_INFO("Target platform: {} ({})", getPlatformTypeName(platform),
static_cast<int>(platform));
UniquePtr<PlatformConfig> platformConfig = createPlatformConfig(platform);
if (!platformConfig) {
E2D_LOG_ERROR("Failed to create platform config");
return false;
}
appConfig.applyPlatformConstraints(*platformConfig);
const auto& capabilities = platformConfig->capabilities();
E2D_LOG_INFO("Platform capabilities: windowed={}, fullscreen={}, cursor={}, DPI={}",
capabilities.supportsWindowed, capabilities.supportsFullscreen,
capabilities.supportsCursor, capabilities.supportsDPIAwareness);
if (platform == PlatformType::Switch) {
#ifdef __SWITCH__
Result rc;
@ -105,7 +145,36 @@ bool Application::initImpl() {
#endif
}
std::string backend = config_.backend;
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
E2D_LOG_INFO("Initializing {} registered modules...", initOrder.size());
for (ModuleId moduleId : initOrder) {
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
if (!initializer) {
continue;
}
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
if (!moduleConfig) {
E2D_LOG_WARN("Module {} has no config, skipping", moduleId);
continue;
}
auto info = moduleConfig->getModuleInfo();
if (!info.enabled) {
E2D_LOG_INFO("Module '{}' is disabled, skipping", info.name);
continue;
}
E2D_LOG_INFO("Initializing module '{}' (priority: {})",
info.name, static_cast<int>(info.priority));
if (!initializer->initialize(moduleConfig)) {
E2D_LOG_ERROR("Failed to initialize module '{}'", info.name);
}
}
std::string backend = "sdl2";
#ifdef __SWITCH__
backend = "switch";
#endif
@ -127,27 +196,19 @@ bool Application::initImpl() {
return false;
}
WindowConfig winConfig;
winConfig.title = config_.title;
winConfig.width = config_.width;
winConfig.height = config_.height;
WindowConfigData winConfig = appConfig.window;
if (platform == PlatformType::Switch) {
winConfig.fullscreen = true;
winConfig.fullscreenDesktop = false;
winConfig.mode = WindowMode::Fullscreen;
winConfig.resizable = false;
} else {
winConfig.fullscreen = config_.fullscreen;
winConfig.resizable = config_.resizable;
}
winConfig.vsync = config_.vsync;
winConfig.msaaSamples = config_.msaaSamples;
if (!window_->create(winConfig)) {
E2D_LOG_ERROR("Failed to create window");
return false;
}
renderer_ = RenderBackend::create(config_.renderBackend);
renderer_ = RenderBackend::create(appConfig.render.backend);
if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
@ -163,8 +224,8 @@ bool Application::initImpl() {
viewportAdapter_ = makeUnique<ViewportAdapter>();
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(config_.width);
vpConfig.logicHeight = static_cast<float>(config_.height);
vpConfig.logicWidth = static_cast<float>(appConfig.window.width);
vpConfig.logicHeight = static_cast<float>(appConfig.window.height);
vpConfig.mode = ViewportMode::AspectRatio;
viewportAdapter_->setConfig(vpConfig);
@ -193,7 +254,12 @@ bool Application::initImpl() {
initialized_ = true;
running_ = true;
E2D_LOG_INFO("Application initialized (backend: {})", backend);
E2D_LOG_INFO("Application initialized successfully");
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
E2D_LOG_INFO(" Backend: {}", backend);
E2D_LOG_INFO(" VSync: {}", appConfig.render.vsync);
E2D_LOG_INFO(" Target FPS: {}", appConfig.render.targetFPS);
return true;
}
@ -227,14 +293,31 @@ void Application::shutdown() {
window_.reset();
}
PlatformType platform = config_.platform;
auto modules = ModuleRegistry::instance().getAllModules();
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
ModuleId moduleId = *it;
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
if (initializer && initializer->isInitialized()) {
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
if (moduleConfig) {
auto info = moduleConfig->getModuleInfo();
E2D_LOG_INFO("Shutting down module '{}'", info.name);
}
initializer->shutdown();
}
}
PlatformType platform = ConfigManager::instance().appConfig().targetPlatform;
if (platform == PlatformType::Auto) {
#ifdef __SWITCH__
platform = PlatformType::Switch;
#else
platform = PlatformType::PC;
platform = PlatformType::Windows;
#endif
}
if (platform == PlatformType::Switch) {
#ifdef __SWITCH__
romfsExit();
@ -242,6 +325,8 @@ void Application::shutdown() {
#endif
}
ConfigManager::instance().shutdown();
initialized_ = false;
running_ = false;
@ -308,15 +393,18 @@ void Application::mainLoop() {
render();
if (!config_.vsync && config_.fpsLimit > 0) {
const auto& appConfig = ConfigManager::instance().appConfig();
if (!appConfig.render.vsync && appConfig.render.isFPSCapped()) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(config_.fpsLimit);
double target = 1.0 / static_cast<double>(appConfig.render.targetFPS);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
}
}
ConfigManager::instance().update(deltaTime_);
}
void Application::update() {
@ -353,19 +441,33 @@ void Application::render() {
window_->swap();
}
IInput& Application::input() { return *window_->input(); }
IInput& Application::input() {
return *window_->input();
}
SceneManager& Application::scenes() { return *sceneManager_; }
SceneManager& Application::scenes() {
return *sceneManager_;
}
TimerManager& Application::timers() { return *timerManager_; }
TimerManager& Application::timers() {
return *timerManager_;
}
EventQueue& Application::eventQueue() { return *eventQueue_; }
EventQueue& Application::eventQueue() {
return *eventQueue_;
}
EventDispatcher& Application::eventDispatcher() { return *eventDispatcher_; }
EventDispatcher& Application::eventDispatcher() {
return *eventDispatcher_;
}
Camera& Application::camera() { return *camera_; }
Camera& Application::camera() {
return *camera_;
}
ViewportAdapter& Application::viewportAdapter() { return *viewportAdapter_; }
ViewportAdapter& Application::viewportAdapter() {
return *viewportAdapter_;
}
void Application::enterScene(Ptr<Scene> scene) {
if (sceneManager_ && scene) {
@ -375,4 +477,12 @@ void Application::enterScene(Ptr<Scene> scene) {
}
}
ConfigManager& Application::config() {
return ConfigManager::instance();
}
const AppConfig& Application::getConfig() const {
return ConfigManager::instance().appConfig();
}
} // namespace extra2d

View File

@ -0,0 +1,681 @@
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
namespace extra2d {
/**
* @brief
* @param flag
* @return true
*/
bool DebugConfigData::hasDebugFlag(const std::string& flag) const {
return std::find(debugFlags.begin(), debugFlags.end(), flag) != debugFlags.end();
}
/**
* @brief
* @param flag
*/
void DebugConfigData::addDebugFlag(const std::string& flag) {
if (!hasDebugFlag(flag)) {
debugFlags.push_back(flag);
}
}
/**
* @brief
* @param flag
*/
void DebugConfigData::removeDebugFlag(const std::string& flag) {
auto it = std::find(debugFlags.begin(), debugFlags.end(), flag);
if (it != debugFlags.end()) {
debugFlags.erase(it);
}
}
/**
* @brief
* @param path
*/
void ResourceConfigData::addSearchPath(const std::string& path) {
if (!hasSearchPath(path)) {
searchPaths.push_back(path);
}
}
/**
* @brief
* @param path
*/
void ResourceConfigData::removeSearchPath(const std::string& path) {
auto it = std::find(searchPaths.begin(), searchPaths.end(), path);
if (it != searchPaths.end()) {
searchPaths.erase(it);
}
}
/**
* @brief
* @param path
* @return true
*/
bool ResourceConfigData::hasSearchPath(const std::string& path) const {
return std::find(searchPaths.begin(), searchPaths.end(), path) != searchPaths.end();
}
/**
* @brief
*
* @return
*/
AppConfig AppConfig::createDefault() {
AppConfig config;
config.window.title = "Extra2D Application";
config.window.width = 1280;
config.window.height = 720;
config.window.minWidth = 320;
config.window.minHeight = 240;
config.window.maxWidth = 0;
config.window.maxHeight = 0;
config.window.mode = WindowMode::Windowed;
config.window.resizable = true;
config.window.borderless = false;
config.window.alwaysOnTop = false;
config.window.centered = true;
config.window.posX = -1;
config.window.posY = -1;
config.window.hideOnClose = false;
config.window.minimizeOnClose = true;
config.window.opacity = 1.0f;
config.window.transparentFramebuffer = false;
config.window.highDPI = true;
config.window.contentScale = 1.0f;
config.render.backend = BackendType::OpenGL;
config.render.targetFPS = 60;
config.render.vsync = true;
config.render.tripleBuffering = false;
config.render.multisamples = 0;
config.render.sRGBFramebuffer = false;
config.render.clearColor = Color{0.0f, 0.0f, 0.0f, 1.0f};
config.render.maxTextureSize = 0;
config.render.textureAnisotropy = 1;
config.render.wireframeMode = false;
config.render.depthTest = false;
config.render.blending = true;
config.render.dithering = false;
config.render.spriteBatchSize = 1000;
config.render.maxRenderTargets = 1;
config.render.allowShaderHotReload = false;
config.audio.enabled = true;
config.audio.masterVolume = 100;
config.audio.musicVolume = 100;
config.audio.sfxVolume = 100;
config.audio.voiceVolume = 100;
config.audio.ambientVolume = 100;
config.audio.frequency = 44100;
config.audio.channels = 2;
config.audio.chunkSize = 2048;
config.audio.maxChannels = 16;
config.audio.spatialAudio = false;
config.audio.listenerPosition[0] = 0.0f;
config.audio.listenerPosition[1] = 0.0f;
config.audio.listenerPosition[2] = 0.0f;
config.debug.enabled = false;
config.debug.showFPS = false;
config.debug.showMemoryUsage = false;
config.debug.showRenderStats = false;
config.debug.showColliders = false;
config.debug.showGrid = false;
config.debug.logToFile = false;
config.debug.logToConsole = true;
config.debug.logLevel = 2;
config.debug.breakOnAssert = true;
config.debug.enableProfiling = false;
config.debug.debugFlags.clear();
config.input.enabled = true;
config.input.rawMouseInput = false;
config.input.mouseSensitivity = 1.0f;
config.input.invertMouseY = false;
config.input.invertMouseX = false;
config.input.deadzone = 0.15f;
config.input.triggerThreshold = 0.5f;
config.input.enableVibration = true;
config.input.maxGamepads = 4;
config.input.autoConnectGamepads = true;
config.resource.assetRootPath = "assets";
config.resource.cachePath = "cache";
config.resource.savePath = "saves";
config.resource.configPath = "config";
config.resource.logPath = "logs";
config.resource.useAssetCache = true;
config.resource.maxCacheSize = 512;
config.resource.hotReloadEnabled = false;
config.resource.hotReloadInterval = 1.0f;
config.resource.compressTextures = false;
config.resource.preloadCommonAssets = true;
config.resource.searchPaths.clear();
config.appName = "Extra2D App";
config.appVersion = "1.0.0";
config.organization = "";
config.configFile = "config.json";
config.targetPlatform = PlatformType::Auto;
return config;
}
/**
* @brief
*
* @return true false
*/
bool AppConfig::validate() const {
if (window.width <= 0) {
E2D_LOG_ERROR("Config validation failed: window width must be positive");
return false;
}
if (window.height <= 0) {
E2D_LOG_ERROR("Config validation failed: window height must be positive");
return false;
}
if (window.minWidth <= 0) {
E2D_LOG_ERROR("Config validation failed: minimum window width must be positive");
return false;
}
if (window.minHeight <= 0) {
E2D_LOG_ERROR("Config validation failed: minimum window height must be positive");
return false;
}
if (window.maxWidth > 0 && window.maxWidth < window.minWidth) {
E2D_LOG_ERROR("Config validation failed: maximum width less than minimum width");
return false;
}
if (window.maxHeight > 0 && window.maxHeight < window.minHeight) {
E2D_LOG_ERROR("Config validation failed: maximum height less than minimum height");
return false;
}
if (window.width < window.minWidth) {
E2D_LOG_ERROR("Config validation failed: window width less than minimum");
return false;
}
if (window.height < window.minHeight) {
E2D_LOG_ERROR("Config validation failed: window height less than minimum");
return false;
}
if (window.maxWidth > 0 && window.width > window.maxWidth) {
E2D_LOG_ERROR("Config validation failed: window width exceeds maximum");
return false;
}
if (window.maxHeight > 0 && window.height > window.maxHeight) {
E2D_LOG_ERROR("Config validation failed: window height exceeds maximum");
return false;
}
if (window.opacity < 0.0f || window.opacity > 1.0f) {
E2D_LOG_ERROR("Config validation failed: window opacity must be between 0 and 1");
return false;
}
if (window.contentScale <= 0.0f) {
E2D_LOG_ERROR("Config validation failed: content scale must be positive");
return false;
}
if (render.targetFPS < 0) {
E2D_LOG_ERROR("Config validation failed: target FPS cannot be negative");
return false;
}
if (render.multisamples < 0 || render.multisamples > 16) {
E2D_LOG_ERROR("Config validation failed: multisamples must be between 0 and 16");
return false;
}
if (render.textureAnisotropy < 1 || render.textureAnisotropy > 16) {
E2D_LOG_ERROR("Config validation failed: texture anisotropy must be between 1 and 16");
return false;
}
if (render.spriteBatchSize <= 0) {
E2D_LOG_ERROR("Config validation failed: sprite batch size must be positive");
return false;
}
if (render.maxRenderTargets <= 0) {
E2D_LOG_ERROR("Config validation failed: max render targets must be positive");
return false;
}
if (!audio.isValidVolume(audio.masterVolume)) {
E2D_LOG_ERROR("Config validation failed: master volume must be between 0 and 100");
return false;
}
if (!audio.isValidVolume(audio.musicVolume)) {
E2D_LOG_ERROR("Config validation failed: music volume must be between 0 and 100");
return false;
}
if (!audio.isValidVolume(audio.sfxVolume)) {
E2D_LOG_ERROR("Config validation failed: SFX volume must be between 0 and 100");
return false;
}
if (!audio.isValidVolume(audio.voiceVolume)) {
E2D_LOG_ERROR("Config validation failed: voice volume must be between 0 and 100");
return false;
}
if (!audio.isValidVolume(audio.ambientVolume)) {
E2D_LOG_ERROR("Config validation failed: ambient volume must be between 0 and 100");
return false;
}
if (audio.frequency <= 0) {
E2D_LOG_ERROR("Config validation failed: audio frequency must be positive");
return false;
}
if (audio.channels <= 0) {
E2D_LOG_ERROR("Config validation failed: audio channels must be positive");
return false;
}
if (audio.chunkSize <= 0) {
E2D_LOG_ERROR("Config validation failed: audio chunk size must be positive");
return false;
}
if (audio.maxChannels <= 0) {
E2D_LOG_ERROR("Config validation failed: max audio channels must be positive");
return false;
}
if (debug.logLevel < 0 || debug.logLevel > 5) {
E2D_LOG_ERROR("Config validation failed: log level must be between 0 and 5");
return false;
}
if (!input.isDeadzoneValid()) {
E2D_LOG_ERROR("Config validation failed: deadzone must be between 0 and 1");
return false;
}
if (input.mouseSensitivity <= 0.0f) {
E2D_LOG_ERROR("Config validation failed: mouse sensitivity must be positive");
return false;
}
if (input.triggerThreshold < 0.0f || input.triggerThreshold > 1.0f) {
E2D_LOG_ERROR("Config validation failed: trigger threshold must be between 0 and 1");
return false;
}
if (input.maxGamepads <= 0) {
E2D_LOG_ERROR("Config validation failed: max gamepads must be positive");
return false;
}
if (resource.maxCacheSize <= 0) {
E2D_LOG_ERROR("Config validation failed: max cache size must be positive");
return false;
}
if (resource.hotReloadInterval <= 0.0f) {
E2D_LOG_ERROR("Config validation failed: hot reload interval must be positive");
return false;
}
if (appName.empty()) {
E2D_LOG_ERROR("Config validation failed: app name cannot be empty");
return false;
}
if (appVersion.empty()) {
E2D_LOG_ERROR("Config validation failed: app version cannot be empty");
return false;
}
if (configFile.empty()) {
E2D_LOG_ERROR("Config validation failed: config file cannot be empty");
return false;
}
return true;
}
/**
* @brief
*
* @param platform
*/
void AppConfig::applyPlatformConstraints(const PlatformConfig& platform) {
const PlatformCapabilities& caps = platform.capabilities();
if (!caps.supportsWindowed && window.mode == WindowMode::Windowed) {
E2D_LOG_WARN("Platform does not support windowed mode, switching to fullscreen");
window.mode = WindowMode::Fullscreen;
}
if (!caps.supportsBorderless && window.mode == WindowMode::Borderless) {
E2D_LOG_WARN("Platform does not support borderless mode, switching to fullscreen");
window.mode = WindowMode::Fullscreen;
}
if (!caps.supportsFullscreen && window.mode == WindowMode::Fullscreen) {
E2D_LOG_WARN("Platform does not support fullscreen mode, switching to windowed");
window.mode = WindowMode::Windowed;
}
if (!caps.supportsResize) {
window.resizable = false;
}
if (!caps.supportsHighDPI) {
window.highDPI = false;
}
if (!caps.supportsCursor) {
window.borderless = false;
}
if (!caps.supportsVSync) {
render.vsync = false;
}
if (caps.maxTextureSize > 0) {
if (render.maxTextureSize == 0 || render.maxTextureSize > caps.maxTextureSize) {
render.maxTextureSize = caps.maxTextureSize;
}
}
if (!caps.supportsGamepad) {
input.maxGamepads = 0;
input.enableVibration = false;
}
if (!caps.supportsKeyboard) {
input.enabled = false;
}
if (!caps.supportsMouse) {
input.rawMouseInput = false;
}
platform.applyConstraints(*this);
E2D_LOG_INFO("Applied platform constraints for: {}", platform.platformName());
}
/**
* @brief
*
*/
void AppConfig::reset() {
*this = createDefault();
E2D_LOG_INFO("App config reset to defaults");
}
/**
* @brief
* other
* @param other
*/
void AppConfig::merge(const AppConfig& other) {
if (other.window.title != "Extra2D Application") {
window.title = other.window.title;
}
if (other.window.width != 1280) {
window.width = other.window.width;
}
if (other.window.height != 720) {
window.height = other.window.height;
}
if (other.window.minWidth != 320) {
window.minWidth = other.window.minWidth;
}
if (other.window.minHeight != 240) {
window.minHeight = other.window.minHeight;
}
if (other.window.maxWidth != 0) {
window.maxWidth = other.window.maxWidth;
}
if (other.window.maxHeight != 0) {
window.maxHeight = other.window.maxHeight;
}
if (other.window.mode != WindowMode::Windowed) {
window.mode = other.window.mode;
}
if (other.window.resizable != true) {
window.resizable = other.window.resizable;
}
if (other.window.borderless != false) {
window.borderless = other.window.borderless;
}
if (other.window.alwaysOnTop != false) {
window.alwaysOnTop = other.window.alwaysOnTop;
}
if (other.window.centered != true) {
window.centered = other.window.centered;
}
if (other.window.posX >= 0) {
window.posX = other.window.posX;
}
if (other.window.posY >= 0) {
window.posY = other.window.posY;
}
if (other.window.hideOnClose != false) {
window.hideOnClose = other.window.hideOnClose;
}
if (other.window.minimizeOnClose != true) {
window.minimizeOnClose = other.window.minimizeOnClose;
}
if (other.window.opacity != 1.0f) {
window.opacity = other.window.opacity;
}
if (other.window.transparentFramebuffer != false) {
window.transparentFramebuffer = other.window.transparentFramebuffer;
}
if (other.window.highDPI != true) {
window.highDPI = other.window.highDPI;
}
if (other.window.contentScale != 1.0f) {
window.contentScale = other.window.contentScale;
}
if (other.render.backend != BackendType::OpenGL) {
render.backend = other.render.backend;
}
if (other.render.targetFPS != 60) {
render.targetFPS = other.render.targetFPS;
}
if (other.render.vsync != true) {
render.vsync = other.render.vsync;
}
if (other.render.tripleBuffering != false) {
render.tripleBuffering = other.render.tripleBuffering;
}
if (other.render.multisamples != 0) {
render.multisamples = other.render.multisamples;
}
if (other.render.sRGBFramebuffer != false) {
render.sRGBFramebuffer = other.render.sRGBFramebuffer;
}
if (other.render.maxTextureSize != 0) {
render.maxTextureSize = other.render.maxTextureSize;
}
if (other.render.textureAnisotropy != 1) {
render.textureAnisotropy = other.render.textureAnisotropy;
}
if (other.render.wireframeMode != false) {
render.wireframeMode = other.render.wireframeMode;
}
if (other.render.depthTest != false) {
render.depthTest = other.render.depthTest;
}
if (other.render.blending != true) {
render.blending = other.render.blending;
}
if (other.render.dithering != false) {
render.dithering = other.render.dithering;
}
if (other.render.spriteBatchSize != 1000) {
render.spriteBatchSize = other.render.spriteBatchSize;
}
if (other.render.maxRenderTargets != 1) {
render.maxRenderTargets = other.render.maxRenderTargets;
}
if (other.render.allowShaderHotReload != false) {
render.allowShaderHotReload = other.render.allowShaderHotReload;
}
if (!other.render.shaderCachePath.empty()) {
render.shaderCachePath = other.render.shaderCachePath;
}
if (other.audio.enabled != true) {
audio.enabled = other.audio.enabled;
}
if (other.audio.masterVolume != 100) {
audio.masterVolume = other.audio.masterVolume;
}
if (other.audio.musicVolume != 100) {
audio.musicVolume = other.audio.musicVolume;
}
if (other.audio.sfxVolume != 100) {
audio.sfxVolume = other.audio.sfxVolume;
}
if (other.audio.voiceVolume != 100) {
audio.voiceVolume = other.audio.voiceVolume;
}
if (other.audio.ambientVolume != 100) {
audio.ambientVolume = other.audio.ambientVolume;
}
if (other.audio.frequency != 44100) {
audio.frequency = other.audio.frequency;
}
if (other.audio.channels != 2) {
audio.channels = other.audio.channels;
}
if (other.audio.chunkSize != 2048) {
audio.chunkSize = other.audio.chunkSize;
}
if (other.audio.maxChannels != 16) {
audio.maxChannels = other.audio.maxChannels;
}
if (other.audio.spatialAudio != false) {
audio.spatialAudio = other.audio.spatialAudio;
}
if (other.debug.enabled != false) {
debug.enabled = other.debug.enabled;
}
if (other.debug.showFPS != false) {
debug.showFPS = other.debug.showFPS;
}
if (other.debug.showMemoryUsage != false) {
debug.showMemoryUsage = other.debug.showMemoryUsage;
}
if (other.debug.showRenderStats != false) {
debug.showRenderStats = other.debug.showRenderStats;
}
if (other.debug.showColliders != false) {
debug.showColliders = other.debug.showColliders;
}
if (other.debug.showGrid != false) {
debug.showGrid = other.debug.showGrid;
}
if (other.debug.logToFile != false) {
debug.logToFile = other.debug.logToFile;
}
if (other.debug.logToConsole != true) {
debug.logToConsole = other.debug.logToConsole;
}
if (other.debug.logLevel != 2) {
debug.logLevel = other.debug.logLevel;
}
if (other.debug.breakOnAssert != true) {
debug.breakOnAssert = other.debug.breakOnAssert;
}
if (other.debug.enableProfiling != false) {
debug.enableProfiling = other.debug.enableProfiling;
}
if (!other.debug.logFilePath.empty()) {
debug.logFilePath = other.debug.logFilePath;
}
for (const auto& flag : other.debug.debugFlags) {
debug.addDebugFlag(flag);
}
if (other.input.enabled != true) {
input.enabled = other.input.enabled;
}
if (other.input.rawMouseInput != false) {
input.rawMouseInput = other.input.rawMouseInput;
}
if (other.input.mouseSensitivity != 1.0f) {
input.mouseSensitivity = other.input.mouseSensitivity;
}
if (other.input.invertMouseY != false) {
input.invertMouseY = other.input.invertMouseY;
}
if (other.input.invertMouseX != false) {
input.invertMouseX = other.input.invertMouseX;
}
if (other.input.deadzone != 0.15f) {
input.deadzone = other.input.deadzone;
}
if (other.input.triggerThreshold != 0.5f) {
input.triggerThreshold = other.input.triggerThreshold;
}
if (other.input.enableVibration != true) {
input.enableVibration = other.input.enableVibration;
}
if (other.input.maxGamepads != 4) {
input.maxGamepads = other.input.maxGamepads;
}
if (other.input.autoConnectGamepads != true) {
input.autoConnectGamepads = other.input.autoConnectGamepads;
}
if (!other.input.gamepadMappingFile.empty()) {
input.gamepadMappingFile = other.input.gamepadMappingFile;
}
if (other.resource.assetRootPath != "assets") {
resource.assetRootPath = other.resource.assetRootPath;
}
if (other.resource.cachePath != "cache") {
resource.cachePath = other.resource.cachePath;
}
if (other.resource.savePath != "saves") {
resource.savePath = other.resource.savePath;
}
if (other.resource.configPath != "config") {
resource.configPath = other.resource.configPath;
}
if (other.resource.logPath != "logs") {
resource.logPath = other.resource.logPath;
}
if (other.resource.useAssetCache != true) {
resource.useAssetCache = other.resource.useAssetCache;
}
if (other.resource.maxCacheSize != 512) {
resource.maxCacheSize = other.resource.maxCacheSize;
}
if (other.resource.hotReloadEnabled != false) {
resource.hotReloadEnabled = other.resource.hotReloadEnabled;
}
if (other.resource.hotReloadInterval != 1.0f) {
resource.hotReloadInterval = other.resource.hotReloadInterval;
}
if (other.resource.compressTextures != false) {
resource.compressTextures = other.resource.compressTextures;
}
if (other.resource.preloadCommonAssets != true) {
resource.preloadCommonAssets = other.resource.preloadCommonAssets;
}
for (const auto& path : other.resource.searchPaths) {
resource.addSearchPath(path);
}
if (other.appName != "Extra2D App") {
appName = other.appName;
}
if (other.appVersion != "1.0.0") {
appVersion = other.appVersion;
}
if (!other.organization.empty()) {
organization = other.organization;
}
if (other.configFile != "config.json") {
configFile = other.configFile;
}
if (other.targetPlatform != PlatformType::Auto) {
targetPlatform = other.targetPlatform;
}
E2D_LOG_INFO("Merged app config");
}
}

View File

@ -0,0 +1,952 @@
#include <extra2d/config/config_loader.h>
#include <extra2d/utils/logger.h>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <map>
namespace extra2d {
/**
* @brief
* @param str
* @return
*/
static std::string trim(const std::string& str) {
size_t start = 0;
while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) {
++start;
}
size_t end = str.length();
while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) {
--end;
}
return str.substr(start, end - start);
}
/**
* @brief
* @param str
* @return
*/
static std::string toLower(const std::string& str) {
std::string result = str;
for (char& c : result) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return result;
}
/**
* @brief
* @param modeStr
* @return
*/
static WindowMode stringToWindowMode(const std::string& modeStr) {
std::string lower = toLower(modeStr);
if (lower == "fullscreen") return WindowMode::Fullscreen;
if (lower == "borderless") return WindowMode::Borderless;
return WindowMode::Windowed;
}
/**
* @brief
* @param mode
* @return
*/
static std::string windowModeToString(WindowMode mode) {
switch (mode) {
case WindowMode::Fullscreen: return "fullscreen";
case WindowMode::Borderless: return "borderless";
default: return "windowed";
}
}
/**
* @brief
* @param backendStr
* @return
*/
static BackendType stringToBackendType(const std::string& backendStr) {
std::string lower = toLower(backendStr);
if (lower == "opengl") return BackendType::OpenGL;
return BackendType::OpenGL;
}
/**
* @brief
* @param backend
* @return
*/
static std::string backendTypeToString(BackendType backend) {
switch (backend) {
case BackendType::OpenGL: return "opengl";
default: return "opengl";
}
}
/**
* @brief INI
*/
using IniData = std::map<std::string, std::map<std::string, std::string>>;
/**
* @brief INI
* @param content INI
* @param data INI
* @return
*/
static ConfigLoadResult parseIniContent(const std::string& content, IniData& data) {
std::istringstream stream(content);
std::string line;
std::string currentSection;
int lineNumber = 0;
while (std::getline(stream, line)) {
++lineNumber;
line = trim(line);
if (line.empty() || line[0] == ';' || line[0] == '#') {
continue;
}
if (line[0] == '[') {
size_t endBracket = line.find(']');
if (endBracket == std::string::npos) {
return ConfigLoadResult::error("INI 解析错误: 缺少右括号", lineNumber);
}
currentSection = trim(line.substr(1, endBracket - 1));
if (data.find(currentSection) == data.end()) {
data[currentSection] = std::map<std::string, std::string>();
}
} else {
size_t equalPos = line.find('=');
if (equalPos == std::string::npos) {
continue;
}
std::string key = trim(line.substr(0, equalPos));
std::string value = trim(line.substr(equalPos + 1));
if (currentSection.empty()) {
return ConfigLoadResult::error("INI 解析错误: 键值对不在任何节中", lineNumber);
}
data[currentSection][key] = value;
}
}
return ConfigLoadResult::ok();
}
/**
* @brief INI
* @param data INI
* @param section
* @param key
* @param defaultValue
* @return
*/
static std::string getIniValue(const IniData& data, const std::string& section,
const std::string& key, const std::string& defaultValue = "") {
auto sectionIt = data.find(section);
if (sectionIt == data.end()) {
return defaultValue;
}
auto keyIt = sectionIt->second.find(key);
if (keyIt == sectionIt->second.end()) {
return defaultValue;
}
return keyIt->second;
}
/**
* @brief INI
* @param data INI
* @param section
* @param key
* @return
*/
static bool hasIniValue(const IniData& data, const std::string& section, const std::string& key) {
auto sectionIt = data.find(section);
if (sectionIt == data.end()) {
return false;
}
return sectionIt->second.find(key) != sectionIt->second.end();
}
// ============================================================================
// IniConfigLoader 实现
// ============================================================================
/**
* @brief INI
* @param filepath
* @param config
* @return
*/
ConfigLoadResult IniConfigLoader::load(const std::string& filepath, AppConfig& config) {
E2D_LOG_INFO("正在从 INI 文件加载配置: {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
return loadFromString(content, config);
}
/**
* @brief INI
* @param filepath
* @param config
* @return
*/
ConfigSaveResult IniConfigLoader::save(const std::string& filepath, const AppConfig& config) {
E2D_LOG_INFO("正在保存配置到 INI 文件: {}", filepath);
std::string content = saveToString(config);
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << content;
file.close();
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
/**
* @brief INI
* @param content INI
* @param config
* @return
*/
ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
IniData data;
auto result = parseIniContent(content, data);
if (result.hasError()) {
return result;
}
if (hasIniValue(data, "app", "name")) {
config.appName = getIniValue(data, "app", "name");
}
if (hasIniValue(data, "app", "version")) {
config.appVersion = getIniValue(data, "app", "version");
}
if (hasIniValue(data, "app", "organization")) {
config.organization = getIniValue(data, "app", "organization");
}
if (hasIniValue(data, "window", "title")) {
config.window.title = getIniValue(data, "window", "title");
}
if (hasIniValue(data, "window", "width")) {
int value;
auto res = parseInt(getIniValue(data, "window", "width"), value, "window.width");
if (res.hasError()) return res;
config.window.width = value;
}
if (hasIniValue(data, "window", "height")) {
int value;
auto res = parseInt(getIniValue(data, "window", "height"), value, "window.height");
if (res.hasError()) return res;
config.window.height = value;
}
if (hasIniValue(data, "window", "minWidth")) {
int value;
auto res = parseInt(getIniValue(data, "window", "minWidth"), value, "window.minWidth");
if (res.hasError()) return res;
config.window.minWidth = value;
}
if (hasIniValue(data, "window", "minHeight")) {
int value;
auto res = parseInt(getIniValue(data, "window", "minHeight"), value, "window.minHeight");
if (res.hasError()) return res;
config.window.minHeight = value;
}
if (hasIniValue(data, "window", "maxWidth")) {
int value;
auto res = parseInt(getIniValue(data, "window", "maxWidth"), value, "window.maxWidth");
if (res.hasError()) return res;
config.window.maxWidth = value;
}
if (hasIniValue(data, "window", "maxHeight")) {
int value;
auto res = parseInt(getIniValue(data, "window", "maxHeight"), value, "window.maxHeight");
if (res.hasError()) return res;
config.window.maxHeight = value;
}
if (hasIniValue(data, "window", "mode")) {
config.window.mode = stringToWindowMode(getIniValue(data, "window", "mode"));
}
if (hasIniValue(data, "window", "resizable")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "resizable"), value, "window.resizable");
if (res.hasError()) return res;
config.window.resizable = value;
}
if (hasIniValue(data, "window", "borderless")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "borderless"), value, "window.borderless");
if (res.hasError()) return res;
config.window.borderless = value;
}
if (hasIniValue(data, "window", "alwaysOnTop")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "alwaysOnTop"), value, "window.alwaysOnTop");
if (res.hasError()) return res;
config.window.alwaysOnTop = value;
}
if (hasIniValue(data, "window", "centered")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "centered"), value, "window.centered");
if (res.hasError()) return res;
config.window.centered = value;
}
if (hasIniValue(data, "window", "posX")) {
int value;
auto res = parseInt(getIniValue(data, "window", "posX"), value, "window.posX");
if (res.hasError()) return res;
config.window.posX = value;
}
if (hasIniValue(data, "window", "posY")) {
int value;
auto res = parseInt(getIniValue(data, "window", "posY"), value, "window.posY");
if (res.hasError()) return res;
config.window.posY = value;
}
if (hasIniValue(data, "window", "hideOnClose")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "hideOnClose"), value, "window.hideOnClose");
if (res.hasError()) return res;
config.window.hideOnClose = value;
}
if (hasIniValue(data, "window", "minimizeOnClose")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "minimizeOnClose"), value, "window.minimizeOnClose");
if (res.hasError()) return res;
config.window.minimizeOnClose = value;
}
if (hasIniValue(data, "window", "opacity")) {
float value;
auto res = parseFloat(getIniValue(data, "window", "opacity"), value, "window.opacity");
if (res.hasError()) return res;
config.window.opacity = value;
}
if (hasIniValue(data, "window", "transparentFramebuffer")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "transparentFramebuffer"), value, "window.transparentFramebuffer");
if (res.hasError()) return res;
config.window.transparentFramebuffer = value;
}
if (hasIniValue(data, "window", "highDPI")) {
bool value;
auto res = parseBool(getIniValue(data, "window", "highDPI"), value, "window.highDPI");
if (res.hasError()) return res;
config.window.highDPI = value;
}
if (hasIniValue(data, "window", "contentScale")) {
float value;
auto res = parseFloat(getIniValue(data, "window", "contentScale"), value, "window.contentScale");
if (res.hasError()) return res;
config.window.contentScale = value;
}
if (hasIniValue(data, "render", "backend")) {
config.render.backend = stringToBackendType(getIniValue(data, "render", "backend"));
}
if (hasIniValue(data, "render", "targetFPS")) {
int value;
auto res = parseInt(getIniValue(data, "render", "targetFPS"), value, "render.targetFPS");
if (res.hasError()) return res;
config.render.targetFPS = value;
}
if (hasIniValue(data, "render", "vsync")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "vsync"), value, "render.vsync");
if (res.hasError()) return res;
config.render.vsync = value;
}
if (hasIniValue(data, "render", "tripleBuffering")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "tripleBuffering"), value, "render.tripleBuffering");
if (res.hasError()) return res;
config.render.tripleBuffering = value;
}
if (hasIniValue(data, "render", "multisamples")) {
int value;
auto res = parseInt(getIniValue(data, "render", "multisamples"), value, "render.multisamples");
if (res.hasError()) return res;
config.render.multisamples = value;
}
if (hasIniValue(data, "render", "sRGBFramebuffer")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "sRGBFramebuffer"), value, "render.sRGBFramebuffer");
if (res.hasError()) return res;
config.render.sRGBFramebuffer = value;
}
if (hasIniValue(data, "render", "clearColorR")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorR"), value, "render.clearColorR");
if (res.hasError()) return res;
config.render.clearColor.r = value;
}
if (hasIniValue(data, "render", "clearColorG")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorG"), value, "render.clearColorG");
if (res.hasError()) return res;
config.render.clearColor.g = value;
}
if (hasIniValue(data, "render", "clearColorB")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorB"), value, "render.clearColorB");
if (res.hasError()) return res;
config.render.clearColor.b = value;
}
if (hasIniValue(data, "render", "clearColorA")) {
float value;
auto res = parseFloat(getIniValue(data, "render", "clearColorA"), value, "render.clearColorA");
if (res.hasError()) return res;
config.render.clearColor.a = value;
}
if (hasIniValue(data, "render", "maxTextureSize")) {
int value;
auto res = parseInt(getIniValue(data, "render", "maxTextureSize"), value, "render.maxTextureSize");
if (res.hasError()) return res;
config.render.maxTextureSize = value;
}
if (hasIniValue(data, "render", "textureAnisotropy")) {
int value;
auto res = parseInt(getIniValue(data, "render", "textureAnisotropy"), value, "render.textureAnisotropy");
if (res.hasError()) return res;
config.render.textureAnisotropy = value;
}
if (hasIniValue(data, "render", "wireframeMode")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "wireframeMode"), value, "render.wireframeMode");
if (res.hasError()) return res;
config.render.wireframeMode = value;
}
if (hasIniValue(data, "render", "depthTest")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "depthTest"), value, "render.depthTest");
if (res.hasError()) return res;
config.render.depthTest = value;
}
if (hasIniValue(data, "render", "blending")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "blending"), value, "render.blending");
if (res.hasError()) return res;
config.render.blending = value;
}
if (hasIniValue(data, "render", "dithering")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "dithering"), value, "render.dithering");
if (res.hasError()) return res;
config.render.dithering = value;
}
if (hasIniValue(data, "render", "spriteBatchSize")) {
int value;
auto res = parseInt(getIniValue(data, "render", "spriteBatchSize"), value, "render.spriteBatchSize");
if (res.hasError()) return res;
config.render.spriteBatchSize = value;
}
if (hasIniValue(data, "render", "maxRenderTargets")) {
int value;
auto res = parseInt(getIniValue(data, "render", "maxRenderTargets"), value, "render.maxRenderTargets");
if (res.hasError()) return res;
config.render.maxRenderTargets = value;
}
if (hasIniValue(data, "render", "allowShaderHotReload")) {
bool value;
auto res = parseBool(getIniValue(data, "render", "allowShaderHotReload"), value, "render.allowShaderHotReload");
if (res.hasError()) return res;
config.render.allowShaderHotReload = value;
}
if (hasIniValue(data, "render", "shaderCachePath")) {
config.render.shaderCachePath = getIniValue(data, "render", "shaderCachePath");
}
if (hasIniValue(data, "audio", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "audio", "enabled"), value, "audio.enabled");
if (res.hasError()) return res;
config.audio.enabled = value;
}
if (hasIniValue(data, "audio", "masterVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "masterVolume"), value, "audio.masterVolume");
if (res.hasError()) return res;
config.audio.masterVolume = value;
}
if (hasIniValue(data, "audio", "musicVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "musicVolume"), value, "audio.musicVolume");
if (res.hasError()) return res;
config.audio.musicVolume = value;
}
if (hasIniValue(data, "audio", "sfxVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "sfxVolume"), value, "audio.sfxVolume");
if (res.hasError()) return res;
config.audio.sfxVolume = value;
}
if (hasIniValue(data, "audio", "voiceVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "voiceVolume"), value, "audio.voiceVolume");
if (res.hasError()) return res;
config.audio.voiceVolume = value;
}
if (hasIniValue(data, "audio", "ambientVolume")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "ambientVolume"), value, "audio.ambientVolume");
if (res.hasError()) return res;
config.audio.ambientVolume = value;
}
if (hasIniValue(data, "audio", "frequency")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "frequency"), value, "audio.frequency");
if (res.hasError()) return res;
config.audio.frequency = value;
}
if (hasIniValue(data, "audio", "channels")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "channels"), value, "audio.channels");
if (res.hasError()) return res;
config.audio.channels = value;
}
if (hasIniValue(data, "audio", "chunkSize")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "chunkSize"), value, "audio.chunkSize");
if (res.hasError()) return res;
config.audio.chunkSize = value;
}
if (hasIniValue(data, "audio", "maxChannels")) {
int value;
auto res = parseInt(getIniValue(data, "audio", "maxChannels"), value, "audio.maxChannels");
if (res.hasError()) return res;
config.audio.maxChannels = value;
}
if (hasIniValue(data, "audio", "spatialAudio")) {
bool value;
auto res = parseBool(getIniValue(data, "audio", "spatialAudio"), value, "audio.spatialAudio");
if (res.hasError()) return res;
config.audio.spatialAudio = value;
}
if (hasIniValue(data, "debug", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "enabled"), value, "debug.enabled");
if (res.hasError()) return res;
config.debug.enabled = value;
}
if (hasIniValue(data, "debug", "showFPS")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showFPS"), value, "debug.showFPS");
if (res.hasError()) return res;
config.debug.showFPS = value;
}
if (hasIniValue(data, "debug", "showMemoryUsage")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showMemoryUsage"), value, "debug.showMemoryUsage");
if (res.hasError()) return res;
config.debug.showMemoryUsage = value;
}
if (hasIniValue(data, "debug", "showRenderStats")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showRenderStats"), value, "debug.showRenderStats");
if (res.hasError()) return res;
config.debug.showRenderStats = value;
}
if (hasIniValue(data, "debug", "showColliders")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showColliders"), value, "debug.showColliders");
if (res.hasError()) return res;
config.debug.showColliders = value;
}
if (hasIniValue(data, "debug", "showGrid")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "showGrid"), value, "debug.showGrid");
if (res.hasError()) return res;
config.debug.showGrid = value;
}
if (hasIniValue(data, "debug", "logToFile")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "logToFile"), value, "debug.logToFile");
if (res.hasError()) return res;
config.debug.logToFile = value;
}
if (hasIniValue(data, "debug", "logToConsole")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "logToConsole"), value, "debug.logToConsole");
if (res.hasError()) return res;
config.debug.logToConsole = value;
}
if (hasIniValue(data, "debug", "logLevel")) {
int value;
auto res = parseInt(getIniValue(data, "debug", "logLevel"), value, "debug.logLevel");
if (res.hasError()) return res;
config.debug.logLevel = value;
}
if (hasIniValue(data, "debug", "breakOnAssert")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "breakOnAssert"), value, "debug.breakOnAssert");
if (res.hasError()) return res;
config.debug.breakOnAssert = value;
}
if (hasIniValue(data, "debug", "enableProfiling")) {
bool value;
auto res = parseBool(getIniValue(data, "debug", "enableProfiling"), value, "debug.enableProfiling");
if (res.hasError()) return res;
config.debug.enableProfiling = value;
}
if (hasIniValue(data, "debug", "logFilePath")) {
config.debug.logFilePath = getIniValue(data, "debug", "logFilePath");
}
if (hasIniValue(data, "input", "enabled")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "enabled"), value, "input.enabled");
if (res.hasError()) return res;
config.input.enabled = value;
}
if (hasIniValue(data, "input", "rawMouseInput")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "rawMouseInput"), value, "input.rawMouseInput");
if (res.hasError()) return res;
config.input.rawMouseInput = value;
}
if (hasIniValue(data, "input", "mouseSensitivity")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "mouseSensitivity"), value, "input.mouseSensitivity");
if (res.hasError()) return res;
config.input.mouseSensitivity = value;
}
if (hasIniValue(data, "input", "invertMouseY")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "invertMouseY"), value, "input.invertMouseY");
if (res.hasError()) return res;
config.input.invertMouseY = value;
}
if (hasIniValue(data, "input", "invertMouseX")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "invertMouseX"), value, "input.invertMouseX");
if (res.hasError()) return res;
config.input.invertMouseX = value;
}
if (hasIniValue(data, "input", "deadzone")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "deadzone"), value, "input.deadzone");
if (res.hasError()) return res;
config.input.deadzone = value;
}
if (hasIniValue(data, "input", "triggerThreshold")) {
float value;
auto res = parseFloat(getIniValue(data, "input", "triggerThreshold"), value, "input.triggerThreshold");
if (res.hasError()) return res;
config.input.triggerThreshold = value;
}
if (hasIniValue(data, "input", "enableVibration")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "enableVibration"), value, "input.enableVibration");
if (res.hasError()) return res;
config.input.enableVibration = value;
}
if (hasIniValue(data, "input", "maxGamepads")) {
int value;
auto res = parseInt(getIniValue(data, "input", "maxGamepads"), value, "input.maxGamepads");
if (res.hasError()) return res;
config.input.maxGamepads = value;
}
if (hasIniValue(data, "input", "autoConnectGamepads")) {
bool value;
auto res = parseBool(getIniValue(data, "input", "autoConnectGamepads"), value, "input.autoConnectGamepads");
if (res.hasError()) return res;
config.input.autoConnectGamepads = value;
}
if (hasIniValue(data, "input", "gamepadMappingFile")) {
config.input.gamepadMappingFile = getIniValue(data, "input", "gamepadMappingFile");
}
if (hasIniValue(data, "resource", "assetRootPath")) {
config.resource.assetRootPath = getIniValue(data, "resource", "assetRootPath");
}
if (hasIniValue(data, "resource", "cachePath")) {
config.resource.cachePath = getIniValue(data, "resource", "cachePath");
}
if (hasIniValue(data, "resource", "savePath")) {
config.resource.savePath = getIniValue(data, "resource", "savePath");
}
if (hasIniValue(data, "resource", "configPath")) {
config.resource.configPath = getIniValue(data, "resource", "configPath");
}
if (hasIniValue(data, "resource", "logPath")) {
config.resource.logPath = getIniValue(data, "resource", "logPath");
}
if (hasIniValue(data, "resource", "useAssetCache")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "useAssetCache"), value, "resource.useAssetCache");
if (res.hasError()) return res;
config.resource.useAssetCache = value;
}
if (hasIniValue(data, "resource", "maxCacheSize")) {
int value;
auto res = parseInt(getIniValue(data, "resource", "maxCacheSize"), value, "resource.maxCacheSize");
if (res.hasError()) return res;
config.resource.maxCacheSize = value;
}
if (hasIniValue(data, "resource", "hotReloadEnabled")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "hotReloadEnabled"), value, "resource.hotReloadEnabled");
if (res.hasError()) return res;
config.resource.hotReloadEnabled = value;
}
if (hasIniValue(data, "resource", "hotReloadInterval")) {
float value;
auto res = parseFloat(getIniValue(data, "resource", "hotReloadInterval"), value, "resource.hotReloadInterval");
if (res.hasError()) return res;
config.resource.hotReloadInterval = value;
}
if (hasIniValue(data, "resource", "compressTextures")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "compressTextures"), value, "resource.compressTextures");
if (res.hasError()) return res;
config.resource.compressTextures = value;
}
if (hasIniValue(data, "resource", "preloadCommonAssets")) {
bool value;
auto res = parseBool(getIniValue(data, "resource", "preloadCommonAssets"), value, "resource.preloadCommonAssets");
if (res.hasError()) return res;
config.resource.preloadCommonAssets = value;
}
E2D_LOG_INFO("INI 配置加载成功");
return ConfigLoadResult::ok();
}
/**
* @brief INI
* @param config
* @return INI
*/
std::string IniConfigLoader::saveToString(const AppConfig& config) {
std::ostringstream oss;
oss << "[app]\n";
oss << "name=" << config.appName << "\n";
oss << "version=" << config.appVersion << "\n";
oss << "organization=" << config.organization << "\n";
oss << "\n";
oss << "[window]\n";
oss << "title=" << config.window.title << "\n";
oss << "width=" << config.window.width << "\n";
oss << "height=" << config.window.height << "\n";
oss << "minWidth=" << config.window.minWidth << "\n";
oss << "minHeight=" << config.window.minHeight << "\n";
oss << "maxWidth=" << config.window.maxWidth << "\n";
oss << "maxHeight=" << config.window.maxHeight << "\n";
oss << "mode=" << windowModeToString(config.window.mode) << "\n";
oss << "resizable=" << (config.window.resizable ? "true" : "false") << "\n";
oss << "borderless=" << (config.window.borderless ? "true" : "false") << "\n";
oss << "alwaysOnTop=" << (config.window.alwaysOnTop ? "true" : "false") << "\n";
oss << "centered=" << (config.window.centered ? "true" : "false") << "\n";
oss << "posX=" << config.window.posX << "\n";
oss << "posY=" << config.window.posY << "\n";
oss << "hideOnClose=" << (config.window.hideOnClose ? "true" : "false") << "\n";
oss << "minimizeOnClose=" << (config.window.minimizeOnClose ? "true" : "false") << "\n";
oss << "opacity=" << config.window.opacity << "\n";
oss << "transparentFramebuffer=" << (config.window.transparentFramebuffer ? "true" : "false") << "\n";
oss << "highDPI=" << (config.window.highDPI ? "true" : "false") << "\n";
oss << "contentScale=" << config.window.contentScale << "\n";
oss << "\n";
oss << "[render]\n";
oss << "backend=" << backendTypeToString(config.render.backend) << "\n";
oss << "targetFPS=" << config.render.targetFPS << "\n";
oss << "vsync=" << (config.render.vsync ? "true" : "false") << "\n";
oss << "tripleBuffering=" << (config.render.tripleBuffering ? "true" : "false") << "\n";
oss << "multisamples=" << config.render.multisamples << "\n";
oss << "sRGBFramebuffer=" << (config.render.sRGBFramebuffer ? "true" : "false") << "\n";
oss << "clearColorR=" << config.render.clearColor.r << "\n";
oss << "clearColorG=" << config.render.clearColor.g << "\n";
oss << "clearColorB=" << config.render.clearColor.b << "\n";
oss << "clearColorA=" << config.render.clearColor.a << "\n";
oss << "maxTextureSize=" << config.render.maxTextureSize << "\n";
oss << "textureAnisotropy=" << config.render.textureAnisotropy << "\n";
oss << "wireframeMode=" << (config.render.wireframeMode ? "true" : "false") << "\n";
oss << "depthTest=" << (config.render.depthTest ? "true" : "false") << "\n";
oss << "blending=" << (config.render.blending ? "true" : "false") << "\n";
oss << "dithering=" << (config.render.dithering ? "true" : "false") << "\n";
oss << "spriteBatchSize=" << config.render.spriteBatchSize << "\n";
oss << "maxRenderTargets=" << config.render.maxRenderTargets << "\n";
oss << "allowShaderHotReload=" << (config.render.allowShaderHotReload ? "true" : "false") << "\n";
oss << "shaderCachePath=" << config.render.shaderCachePath << "\n";
oss << "\n";
oss << "[audio]\n";
oss << "enabled=" << (config.audio.enabled ? "true" : "false") << "\n";
oss << "masterVolume=" << config.audio.masterVolume << "\n";
oss << "musicVolume=" << config.audio.musicVolume << "\n";
oss << "sfxVolume=" << config.audio.sfxVolume << "\n";
oss << "voiceVolume=" << config.audio.voiceVolume << "\n";
oss << "ambientVolume=" << config.audio.ambientVolume << "\n";
oss << "frequency=" << config.audio.frequency << "\n";
oss << "channels=" << config.audio.channels << "\n";
oss << "chunkSize=" << config.audio.chunkSize << "\n";
oss << "maxChannels=" << config.audio.maxChannels << "\n";
oss << "spatialAudio=" << (config.audio.spatialAudio ? "true" : "false") << "\n";
oss << "\n";
oss << "[debug]\n";
oss << "enabled=" << (config.debug.enabled ? "true" : "false") << "\n";
oss << "showFPS=" << (config.debug.showFPS ? "true" : "false") << "\n";
oss << "showMemoryUsage=" << (config.debug.showMemoryUsage ? "true" : "false") << "\n";
oss << "showRenderStats=" << (config.debug.showRenderStats ? "true" : "false") << "\n";
oss << "showColliders=" << (config.debug.showColliders ? "true" : "false") << "\n";
oss << "showGrid=" << (config.debug.showGrid ? "true" : "false") << "\n";
oss << "logToFile=" << (config.debug.logToFile ? "true" : "false") << "\n";
oss << "logToConsole=" << (config.debug.logToConsole ? "true" : "false") << "\n";
oss << "logLevel=" << config.debug.logLevel << "\n";
oss << "breakOnAssert=" << (config.debug.breakOnAssert ? "true" : "false") << "\n";
oss << "enableProfiling=" << (config.debug.enableProfiling ? "true" : "false") << "\n";
oss << "logFilePath=" << config.debug.logFilePath << "\n";
oss << "\n";
oss << "[input]\n";
oss << "enabled=" << (config.input.enabled ? "true" : "false") << "\n";
oss << "rawMouseInput=" << (config.input.rawMouseInput ? "true" : "false") << "\n";
oss << "mouseSensitivity=" << config.input.mouseSensitivity << "\n";
oss << "invertMouseY=" << (config.input.invertMouseY ? "true" : "false") << "\n";
oss << "invertMouseX=" << (config.input.invertMouseX ? "true" : "false") << "\n";
oss << "deadzone=" << config.input.deadzone << "\n";
oss << "triggerThreshold=" << config.input.triggerThreshold << "\n";
oss << "enableVibration=" << (config.input.enableVibration ? "true" : "false") << "\n";
oss << "maxGamepads=" << config.input.maxGamepads << "\n";
oss << "autoConnectGamepads=" << (config.input.autoConnectGamepads ? "true" : "false") << "\n";
oss << "gamepadMappingFile=" << config.input.gamepadMappingFile << "\n";
oss << "\n";
oss << "[resource]\n";
oss << "assetRootPath=" << config.resource.assetRootPath << "\n";
oss << "cachePath=" << config.resource.cachePath << "\n";
oss << "savePath=" << config.resource.savePath << "\n";
oss << "configPath=" << config.resource.configPath << "\n";
oss << "logPath=" << config.resource.logPath << "\n";
oss << "useAssetCache=" << (config.resource.useAssetCache ? "true" : "false") << "\n";
oss << "maxCacheSize=" << config.resource.maxCacheSize << "\n";
oss << "hotReloadEnabled=" << (config.resource.hotReloadEnabled ? "true" : "false") << "\n";
oss << "hotReloadInterval=" << config.resource.hotReloadInterval << "\n";
oss << "compressTextures=" << (config.resource.compressTextures ? "true" : "false") << "\n";
oss << "preloadCommonAssets=" << (config.resource.preloadCommonAssets ? "true" : "false") << "\n";
return oss.str();
}
/**
* @brief
* @param filepath
* @return true
*/
bool IniConfigLoader::supportsFile(const std::string& filepath) const {
if (filepath.length() >= 4) {
std::string ext = filepath.substr(filepath.length() - 4);
for (char& c : ext) c = static_cast<char>(std::tolower(c));
return ext == ".ini";
}
return false;
}
/**
* @brief
* @return
*/
UniquePtr<ConfigLoader> IniConfigLoader::clone() const {
return makeUnique<IniConfigLoader>();
}
/**
* @brief
* @param section
* @param key
* @return
*/
std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const {
return section + "." + key;
}
/**
* @brief
* @param value
* @param result
* @param fieldName
* @return
*/
ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result, const std::string& fieldName) {
try {
size_t pos;
result = std::stoi(value, &pos);
if (pos != value.length()) {
return ConfigLoadResult::error("无法解析整数值: " + value, -1, fieldName);
}
return ConfigLoadResult::ok();
} catch (const std::exception& e) {
return ConfigLoadResult::error(std::string("解析整数失败: ") + e.what(), -1, fieldName);
}
}
/**
* @brief
* @param value
* @param result
* @param fieldName
* @return
*/
ConfigLoadResult IniConfigLoader::parseFloat(const std::string& value, float& result, const std::string& fieldName) {
try {
size_t pos;
result = std::stof(value, &pos);
if (pos != value.length()) {
return ConfigLoadResult::error("无法解析浮点数值: " + value, -1, fieldName);
}
return ConfigLoadResult::ok();
} catch (const std::exception& e) {
return ConfigLoadResult::error(std::string("解析浮点数失败: ") + e.what(), -1, fieldName);
}
}
/**
* @brief
* @param value
* @param result
* @param fieldName
* @return
*/
ConfigLoadResult IniConfigLoader::parseBool(const std::string& value, bool& result, const std::string& fieldName) {
std::string lower = toLower(value);
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
result = true;
return ConfigLoadResult::ok();
}
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
result = false;
return ConfigLoadResult::ok();
}
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
}
}

View File

@ -0,0 +1,763 @@
#include <extra2d/config/config_loader.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <sstream>
using json = nlohmann::json;
namespace extra2d {
/**
* @brief
* @param modeStr
* @return
*/
static WindowMode stringToWindowMode(const std::string& modeStr) {
if (modeStr == "fullscreen") return WindowMode::Fullscreen;
if (modeStr == "borderless") return WindowMode::Borderless;
return WindowMode::Windowed;
}
/**
* @brief
* @param mode
* @return
*/
static std::string windowModeToString(WindowMode mode) {
switch (mode) {
case WindowMode::Fullscreen: return "fullscreen";
case WindowMode::Borderless: return "borderless";
default: return "windowed";
}
}
/**
* @brief
* @param backendStr
* @return
*/
static BackendType stringToBackendType(const std::string& backendStr) {
if (backendStr == "opengl") return BackendType::OpenGL;
return BackendType::OpenGL;
}
/**
* @brief
* @param backend
* @return
*/
static std::string backendTypeToString(BackendType backend) {
switch (backend) {
case BackendType::OpenGL: return "opengl";
default: return "opengl";
}
}
// ============================================================================
// JsonConfigLoader 实现
// ============================================================================
/**
* @brief JSON
* @param filepath
* @param config
* @return
*/
ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig& config) {
E2D_LOG_INFO("正在从 JSON 文件加载配置: {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
return loadFromString(content, config);
}
/**
* @brief JSON
* @param filepath
* @param config
* @return
*/
ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppConfig& config) {
E2D_LOG_INFO("正在保存配置到 JSON 文件: {}", filepath);
std::string content = saveToString(config);
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << content;
file.close();
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
/**
* @brief JSON
* @param content JSON
* @param config
* @return
*/
ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
json root;
try {
root = json::parse(content);
} catch (const json::parse_error& e) {
E2D_LOG_ERROR("JSON 解析错误: {}", e.what());
return ConfigLoadResult::error(std::string("JSON 解析错误: ") + e.what(),
static_cast<int>(e.byte));
}
if (root.contains("appName") && root["appName"].is_string()) {
config.appName = root["appName"].get<std::string>();
}
if (root.contains("appVersion") && root["appVersion"].is_string()) {
config.appVersion = root["appVersion"].get<std::string>();
}
if (root.contains("organization") && root["organization"].is_string()) {
config.organization = root["organization"].get<std::string>();
}
if (root.contains("window")) {
auto result = parseWindowConfig(&root["window"], config.window);
if (result.hasError()) {
return result;
}
}
if (root.contains("render")) {
auto result = parseRenderConfig(&root["render"], config.render);
if (result.hasError()) {
return result;
}
}
if (root.contains("audio")) {
auto result = parseAudioConfig(&root["audio"], config.audio);
if (result.hasError()) {
return result;
}
}
if (root.contains("debug")) {
auto result = parseDebugConfig(&root["debug"], config.debug);
if (result.hasError()) {
return result;
}
}
if (root.contains("input")) {
auto result = parseInputConfig(&root["input"], config.input);
if (result.hasError()) {
return result;
}
}
if (root.contains("resource")) {
auto result = parseResourceConfig(&root["resource"], config.resource);
if (result.hasError()) {
return result;
}
}
E2D_LOG_INFO("JSON 配置加载成功");
return ConfigLoadResult::ok();
}
/**
* @brief JSON
* @param config
* @return JSON
*/
std::string JsonConfigLoader::saveToString(const AppConfig& config) {
json root;
root["appName"] = config.appName;
root["appVersion"] = config.appVersion;
root["organization"] = config.organization;
serializeWindowConfig(&root, config.window);
serializeRenderConfig(&root, config.render);
serializeAudioConfig(&root, config.audio);
serializeDebugConfig(&root, config.debug);
serializeInputConfig(&root, config.input);
serializeResourceConfig(&root, config.resource);
return root.dump(4);
}
/**
* @brief
* @param filepath
* @return true
*/
bool JsonConfigLoader::supportsFile(const std::string& filepath) const {
if (filepath.length() >= 5) {
std::string ext = filepath.substr(filepath.length() - 5);
for (char& c : ext) c = static_cast<char>(std::tolower(c));
return ext == ".json";
}
return false;
}
/**
* @brief
* @return
*/
UniquePtr<ConfigLoader> JsonConfigLoader::clone() const {
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

@ -0,0 +1,516 @@
#include <extra2d/config/config_manager.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
#include <extra2d/utils/logger.h>
#include <sstream>
namespace extra2d {
/**
* @brief
*
*/
ConfigManager::ConfigManager()
: m_nextCallbackId(1)
, m_autoSaveEnabled(false)
, m_autoSaveInterval(30.0f)
, m_autoSaveTimer(0.0f)
{
m_appConfig = AppConfig::createDefault();
}
/**
* @brief
*
*/
ConfigManager::~ConfigManager() {
if (m_initialized) {
shutdown();
}
}
/**
* @brief
* 使线
* @return
*/
ConfigManager& ConfigManager::instance() {
static ConfigManager instance;
return instance;
}
/**
* @brief
*
* @param configPath
* @return true
*/
bool ConfigManager::initialize(const std::string& configPath) {
if (m_initialized) {
E2D_LOG_WARN("ConfigManager already initialized");
return true;
}
std::lock_guard<std::mutex> lock(m_mutex);
m_configPath = configPath;
m_platformConfig = createPlatformConfig(PlatformType::Auto);
if (!m_platformConfig) {
E2D_LOG_ERROR("Failed to create platform config");
return false;
}
m_loader = makeUnique<JsonConfigLoader>();
if (!m_loader) {
E2D_LOG_ERROR("Failed to create config loader");
return false;
}
m_appConfig = AppConfig::createDefault();
m_appConfig.targetPlatform = PlatformDetector::detect();
m_platformConfig->applyDefaults(m_appConfig);
m_initialized = true;
m_modified = false;
E2D_LOG_INFO("ConfigManager initialized for platform: {}",
m_platformConfig->platformName());
return true;
}
/**
* @brief
*
*/
void ConfigManager::shutdown() {
if (!m_initialized) {
return;
}
std::lock_guard<std::mutex> lock(m_mutex);
if (m_autoSaveEnabled && m_modified) {
saveConfig();
}
m_changeCallbacks.clear();
m_moduleConfigs.clear();
m_loader.reset();
m_platformConfig.reset();
m_initialized = false;
m_modified = false;
E2D_LOG_INFO("ConfigManager shutdown complete");
}
/**
* @brief
* @return true
*/
bool ConfigManager::isInitialized() const {
return m_initialized;
}
/**
* @brief
* @param filepath 使
* @return
*/
ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) {
std::lock_guard<std::mutex> lock(m_mutex);
std::string path = filepath.empty() ? m_configPath : filepath;
if (path.empty()) {
return ConfigLoadResult::error("No config file path specified");
}
if (!m_loader) {
return ConfigLoadResult::error("Config loader not initialized");
}
AppConfig loadedConfig;
ConfigLoadResult result = m_loader->load(path, loadedConfig);
if (result.success) {
m_appConfig.merge(loadedConfig);
if (m_platformConfig) {
m_appConfig.applyPlatformConstraints(*m_platformConfig);
}
if (!m_appConfig.validate()) {
E2D_LOG_WARN("Loaded config validation failed, using defaults");
m_appConfig = AppConfig::createDefault();
}
m_configPath = path;
m_modified = false;
E2D_LOG_INFO("Config loaded from: {}", path);
} else {
E2D_LOG_ERROR("Failed to load config from {}: {}", path, result.errorMessage);
}
return result;
}
/**
* @brief
* @param filepath 使
* @return
*/
ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) {
std::lock_guard<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->save(path, m_appConfig);
if (result.success) {
m_configPath = path;
m_modified = false;
E2D_LOG_INFO("Config saved to: {}", path);
} else {
E2D_LOG_ERROR("Failed to save config to {}: {}", path, result.errorMessage);
}
return result;
}
/**
* @brief
* @return
*/
ConfigLoadResult ConfigManager::reload() {
return loadConfig(m_configPath);
}
/**
* @brief
* @return
*/
const AppConfig& ConfigManager::appConfig() const {
return m_appConfig;
}
/**
* @brief
* @return
*/
AppConfig& ConfigManager::appConfig() {
m_modified = true;
return m_appConfig;
}
/**
* @brief
* @param config
*/
void ConfigManager::setAppConfig(const AppConfig& config) {
std::lock_guard<std::mutex> lock(m_mutex);
m_appConfig = config;
m_modified = true;
E2D_LOG_INFO("App config updated");
}
/**
* @brief
* @return
*/
PlatformConfig* ConfigManager::platformConfig() {
return m_platformConfig.get();
}
/**
* @brief
* @return
*/
const PlatformConfig* ConfigManager::platformConfig() const {
return m_platformConfig.get();
}
/**
* @brief
* @param callback
* @return ID
*/
int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
std::lock_guard<std::mutex> lock(m_mutex);
int id = m_nextCallbackId++;
m_changeCallbacks[id] = std::move(callback);
E2D_LOG_DEBUG("Registered config change callback with id {}", id);
return id;
}
/**
* @brief
* @param callbackId ID
*/
void ConfigManager::unregisterChangeCallback(int callbackId) {
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_changeCallbacks.find(callbackId);
if (it != m_changeCallbacks.end()) {
m_changeCallbacks.erase(it);
E2D_LOG_DEBUG("Unregistered config change callback {}", callbackId);
}
}
/**
* @brief
*/
void ConfigManager::clearChangeCallbacks() {
std::lock_guard<std::mutex> lock(m_mutex);
m_changeCallbacks.clear();
E2D_LOG_DEBUG("Cleared all config change callbacks");
}
/**
* @brief
* @param moduleName
* @param config
*/
void ConfigManager::registerModuleConfig(const std::string& moduleName, Ptr<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) {
std::lock_guard<std::mutex> lock(m_mutex);
ConfigChangeEvent event;
event.section = section;
event.field = key;
event.newValue = value;
m_modified = true;
notifyChangeCallbacks(event);
}
/**
* @brief
* @param section
* @param key
* @param value
*/
void ConfigManager::setValue(const std::string& section, const std::string& key, int value) {
std::ostringstream oss;
oss << value;
setValue(section, key, oss.str());
}
/**
* @brief
* @param section
* @param key
* @param value
*/
void ConfigManager::setValue(const std::string& section, const std::string& key, float value) {
std::ostringstream oss;
oss << value;
setValue(section, key, oss.str());
}
/**
* @brief
* @param section
* @param key
* @param value
*/
void ConfigManager::setValue(const std::string& section, const std::string& key, bool value) {
setValue(section, key, std::string(value ? "true" : "false"));
}
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
std::string ConfigManager::getValue(const std::string& section, const std::string& key,
const std::string& defaultValue) const {
return defaultValue;
}
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
int ConfigManager::getIntValue(const std::string& section, const std::string& key, int defaultValue) const {
return defaultValue;
}
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
float ConfigManager::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const {
return defaultValue;
}
/**
* @brief
* @param section
* @param key
* @param defaultValue
* @return
*/
bool ConfigManager::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const {
return defaultValue;
}
/**
* @brief
*/
void ConfigManager::resetToDefaults() {
std::lock_guard<std::mutex> lock(m_mutex);
m_appConfig = AppConfig::createDefault();
if (m_platformConfig) {
m_platformConfig->applyDefaults(m_appConfig);
}
m_modified = true;
E2D_LOG_INFO("Config reset to defaults");
}
/**
* @brief
* @return true
*/
bool ConfigManager::hasUnsavedChanges() const {
return m_modified;
}
/**
* @brief
*/
void ConfigManager::markModified() {
m_modified = true;
}
/**
* @brief
*/
void ConfigManager::clearModified() {
m_modified = false;
}
/**
* @brief
* @param enabled
* @param interval
*/
void ConfigManager::setAutoSave(bool enabled, float interval) {
std::lock_guard<std::mutex> lock(m_mutex);
m_autoSaveEnabled = enabled;
m_autoSaveInterval = interval > 0.0f ? interval : 30.0f;
m_autoSaveTimer = 0.0f;
E2D_LOG_INFO("Auto save {} (interval: {}s)",
enabled ? "enabled" : "disabled", m_autoSaveInterval);
}
/**
* @brief
* @param deltaTime
*/
void ConfigManager::update(float deltaTime) {
if (!m_autoSaveEnabled || !m_modified) {
return;
}
m_autoSaveTimer += deltaTime;
if (m_autoSaveTimer >= m_autoSaveInterval) {
m_autoSaveTimer = 0.0f;
saveConfig();
}
}
/**
* @brief
* @param event
*/
void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) {
for (const auto& pair : m_changeCallbacks) {
if (pair.second) {
pair.second(event);
}
}
}
/**
* @brief
* @param config
*/
void ConfigManager::applyConfigToInternal(const AppConfig& config) {
m_appConfig = config;
}
/**
* @brief
* @param config
*/
void ConfigManager::extractConfigFromInternal(AppConfig& config) const {
config = m_appConfig;
}
}

View File

@ -0,0 +1,212 @@
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
namespace extra2d {
/**
* @brief
* 使线
* @return
*/
ModuleRegistry& ModuleRegistry::instance() {
static ModuleRegistry instance;
return instance;
}
/**
* @brief
*
* @param config
* @param initializerFactory
* @return
*/
ModuleId ModuleRegistry::registerModule(
UniquePtr<IModuleConfig> config,
ModuleInitializerFactory initializerFactory
) {
if (!config) {
E2D_LOG_ERROR("Cannot register null module config");
return INVALID_MODULE_ID;
}
std::lock_guard<std::mutex> lock(mutex_);
ModuleInfo info = config->getModuleInfo();
if (nameToId_.find(info.name) != nameToId_.end()) {
E2D_LOG_ERROR("Module '{}' already registered", info.name);
return INVALID_MODULE_ID;
}
ModuleId id = generateId();
ModuleEntry entry;
entry.id = id;
entry.config = std::move(config);
entry.initializerFactory = std::move(initializerFactory);
entry.initialized = false;
modules_[id] = std::move(entry);
nameToId_[info.name] = id;
E2D_LOG_INFO("Registered module '{}' with id {}", info.name, id);
return id;
}
/**
* @brief
*
* @param id
* @return true
*/
bool ModuleRegistry::unregisterModule(ModuleId id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it == modules_.end()) {
E2D_LOG_WARN("Module with id {} not found for unregistration", id);
return false;
}
ModuleInfo info = it->second.config->getModuleInfo();
nameToId_.erase(info.name);
modules_.erase(it);
E2D_LOG_INFO("Unregistered module '{}' (id: {})", info.name, id);
return true;
}
/**
* @brief
* @param id
* @return nullptr
*/
IModuleConfig* ModuleRegistry::getModuleConfig(ModuleId id) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it != modules_.end()) {
return it->second.config.get();
}
return nullptr;
}
/**
* @brief
* @param name
* @return nullptr
*/
IModuleConfig* ModuleRegistry::getModuleConfigByName(const std::string& name) const {
std::lock_guard<std::mutex> lock(mutex_);
auto nameIt = nameToId_.find(name);
if (nameIt == nameToId_.end()) {
return nullptr;
}
auto moduleIt = modules_.find(nameIt->second);
if (moduleIt != modules_.end()) {
return moduleIt->second.config.get();
}
return nullptr;
}
/**
* @brief
* @param id
* @return nullptr
*/
UniquePtr<IModuleInitializer> ModuleRegistry::createInitializer(ModuleId id) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it == modules_.end() || !it->second.initializerFactory) {
return nullptr;
}
return it->second.initializerFactory();
}
/**
* @brief
* @return
*/
std::vector<ModuleId> ModuleRegistry::getAllModules() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<ModuleId> result;
result.reserve(modules_.size());
for (const auto& pair : modules_) {
result.push_back(pair.first);
}
return result;
}
/**
* @brief
*
* @return
*/
std::vector<ModuleId> ModuleRegistry::getInitializationOrder() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::pair<ModuleId, int>> modulePriorities;
modulePriorities.reserve(modules_.size());
for (const auto& pair : modules_) {
ModuleInfo info = pair.second.config->getModuleInfo();
if (info.enabled) {
modulePriorities.emplace_back(pair.first, static_cast<int>(info.priority));
}
}
std::sort(modulePriorities.begin(), modulePriorities.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
std::vector<ModuleId> result;
result.reserve(modulePriorities.size());
for (const auto& pair : modulePriorities) {
result.push_back(pair.first);
}
return result;
}
/**
* @brief
* @param id
* @return true
*/
bool ModuleRegistry::hasModule(ModuleId id) const {
std::lock_guard<std::mutex> lock(mutex_);
return modules_.find(id) != modules_.end();
}
/**
* @brief
*/
void ModuleRegistry::clear() {
std::lock_guard<std::mutex> lock(mutex_);
modules_.clear();
nameToId_.clear();
nextId_ = 1;
E2D_LOG_INFO("Module registry cleared");
}
/**
* @brief
* @return
*/
ModuleId ModuleRegistry::generateId() {
return nextId_++;
}
}

View File

@ -0,0 +1,391 @@
#include <extra2d/config/platform_config.h>
#include <extra2d/config/app_config.h>
#include <extra2d/utils/logger.h>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
namespace {
class WindowsPlatformConfig : public PlatformConfig {
public:
WindowsPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Windows; }
const char* platformName() const override { return "Windows"; }
const PlatformCapabilities& capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override {
if (config.window.width < 320) config.window.width = 320;
if (config.window.height < 240) config.window.height = 240;
if (config.window.width > caps_.maxTextureSize) config.window.width = caps_.maxTextureSize;
if (config.window.height > caps_.maxTextureSize) config.window.height = caps_.maxTextureSize;
}
void applyDefaults(AppConfig& config) const override {
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
config.render.targetFPS = 60;
}
bool validateConfig(AppConfig& config) const override {
if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("Windows: Invalid window dimensions");
return false;
}
return true;
}
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240 &&
width <= caps_.maxTextureSize && height <= caps_.maxTextureSize;
}
private:
PlatformCapabilities caps_;
};
class LinuxPlatformConfig : public PlatformConfig {
public:
LinuxPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Linux; }
const char* platformName() const override { return "Linux"; }
const PlatformCapabilities& capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override {
if (config.window.width < 320) config.window.width = 320;
if (config.window.height < 240) config.window.height = 240;
}
void applyDefaults(AppConfig& config) const override {
config.window.resizable = true;
config.render.vsync = true;
}
bool validateConfig(AppConfig& config) const override {
if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("Linux: Invalid window dimensions");
return false;
}
return true;
}
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240;
}
private:
PlatformCapabilities caps_;
};
class MacOSPlatformConfig : public PlatformConfig {
public:
MacOSPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 144.0f;
}
PlatformType platformType() const override { return PlatformType::macOS; }
const char* platformName() const override { return "macOS"; }
const PlatformCapabilities& capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override {
if (config.window.width < 320) config.window.width = 320;
if (config.window.height < 240) config.window.height = 240;
}
void applyDefaults(AppConfig& config) const override {
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
}
bool validateConfig(AppConfig& config) const override {
if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("macOS: Invalid window dimensions");
return false;
}
return true;
}
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240;
}
private:
PlatformCapabilities caps_;
};
#ifdef __SWITCH__
class SwitchPlatformConfig : public PlatformConfig {
public:
SwitchPlatformConfig() {
caps_.supportsWindowed = false;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = false;
caps_.supportsCursor = false;
caps_.supportsCursorHide = false;
caps_.supportsDPIAwareness = false;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = false;
caps_.supportsClipboard = false;
caps_.supportsGamepad = true;
caps_.supportsTouch = true;
caps_.supportsKeyboard = false;
caps_.supportsMouse = false;
caps_.supportsResize = false;
caps_.supportsHighDPI = false;
caps_.maxTextureSize = 8192;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Switch; }
const char* platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities& capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override {
config.window.width = 1920;
config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen;
config.window.resizable = false;
config.window.borderless = false;
config.window.highDPI = false;
config.render.vsync = true;
config.render.targetFPS = 60;
config.input.enableVibration = true;
config.input.maxGamepads = 2;
}
void applyDefaults(AppConfig& config) const override {
config.window.width = 1920;
config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen;
config.window.resizable = false;
config.render.vsync = true;
config.render.targetFPS = 60;
config.input.enableVibration = true;
}
bool validateConfig(AppConfig& config) const override {
if (config.window.mode != WindowMode::Fullscreen) {
E2D_LOG_WARN("Switch: Only fullscreen mode is supported");
config.window.mode = WindowMode::Fullscreen;
}
return true;
}
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return (width == 1920 && height == 1080) ||
(width == 1280 && height == 720);
}
private:
PlatformCapabilities caps_;
};
#else
class SwitchPlatformConfig : public PlatformConfig {
public:
SwitchPlatformConfig() {
caps_.supportsWindowed = false;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = false;
caps_.supportsCursor = false;
caps_.supportsCursorHide = false;
caps_.supportsDPIAwareness = false;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = false;
caps_.supportsClipboard = false;
caps_.supportsGamepad = true;
caps_.supportsTouch = true;
caps_.supportsKeyboard = false;
caps_.supportsMouse = false;
caps_.supportsResize = false;
caps_.supportsHighDPI = false;
caps_.maxTextureSize = 8192;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Switch; }
const char* platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities& capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override {
config.window.width = 1920;
config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen;
config.window.resizable = false;
config.window.borderless = false;
config.window.highDPI = false;
config.render.vsync = true;
config.render.targetFPS = 60;
config.input.enableVibration = true;
config.input.maxGamepads = 2;
}
void applyDefaults(AppConfig& config) const override {
config.window.width = 1920;
config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen;
config.window.resizable = false;
config.render.vsync = true;
config.render.targetFPS = 60;
config.input.enableVibration = true;
}
bool validateConfig(AppConfig& config) const override {
if (config.window.mode != WindowMode::Fullscreen) {
E2D_LOG_WARN("Switch: Only fullscreen mode is supported");
}
return true;
}
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return (width == 1920 && height == 1080) ||
(width == 1280 && height == 720);
}
private:
PlatformCapabilities caps_;
};
#endif
}
/**
* @brief
* PlatformType
* @param type Auto
* @return
*/
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
if (type == PlatformType::Auto) {
#ifdef _WIN32
type = PlatformType::Windows;
#elif defined(__SWITCH__)
type = PlatformType::Switch;
#elif defined(__linux__)
type = PlatformType::Linux;
#elif defined(__APPLE__)
type = PlatformType::macOS;
#else
type = PlatformType::Windows;
#endif
}
switch (type) {
case PlatformType::Windows:
E2D_LOG_INFO("Creating Windows platform config");
return makeUnique<WindowsPlatformConfig>();
case PlatformType::Switch:
E2D_LOG_INFO("Creating Nintendo Switch platform config");
return makeUnique<SwitchPlatformConfig>();
case PlatformType::Linux:
E2D_LOG_INFO("Creating Linux platform config");
return makeUnique<LinuxPlatformConfig>();
case PlatformType::macOS:
E2D_LOG_INFO("Creating macOS platform config");
return makeUnique<MacOSPlatformConfig>();
default:
E2D_LOG_WARN("Unknown platform type, defaulting to Windows");
return makeUnique<WindowsPlatformConfig>();
}
}
/**
* @brief
* PlatformType
* @param type
* @return
*/
const char* getPlatformTypeName(PlatformType type) {
switch (type) {
case PlatformType::Auto: return "Auto";
case PlatformType::Windows: return "Windows";
case PlatformType::Switch: return "Switch";
case PlatformType::Linux: return "Linux";
case PlatformType::macOS: return "macOS";
default: return "Unknown";
}
}
}

View File

@ -0,0 +1,625 @@
#include <extra2d/config/platform_detector.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#include <psapi.h>
#elif defined(__linux__)
#include <sys/sysinfo.h>
#include <unistd.h>
#include <pwd.h>
#include <cstdlib>
#elif defined(__APPLE__)
#include <sys/sysctl.h>
#include <unistd.h>
#include <pwd.h>
#include <cstdlib>
#endif
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
/**
* @brief
* 使
* @return
*/
PlatformType PlatformDetector::detect() {
#ifdef _WIN32
return PlatformType::Windows;
#elif defined(__SWITCH__)
return PlatformType::Switch;
#elif defined(__linux__)
return PlatformType::Linux;
#elif defined(__APPLE__)
return PlatformType::macOS;
#else
return PlatformType::Windows;
#endif
}
/**
* @brief
* @return "Windows", "Linux", "macOS", "Switch"
*/
const char* PlatformDetector::platformName() {
return platformName(detect());
}
/**
* @brief
* @param type
* @return
*/
const char* PlatformDetector::platformName(PlatformType type) {
switch (type) {
case PlatformType::Windows: return "Windows";
case PlatformType::Switch: return "Nintendo Switch";
case PlatformType::Linux: return "Linux";
case PlatformType::macOS: return "macOS";
case PlatformType::Auto: return "Auto";
default: return "Unknown";
}
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isDesktopPlatform() {
PlatformType type = detect();
return type == PlatformType::Windows ||
type == PlatformType::Linux ||
type == PlatformType::macOS;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isConsolePlatform() {
return detect() == PlatformType::Switch;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isMobilePlatform() {
return false;
}
/**
* @brief
* @return
*/
PlatformCapabilities PlatformDetector::capabilities() {
return capabilities(detect());
}
/**
* @brief
* @param type
* @return
*/
PlatformCapabilities PlatformDetector::capabilities(PlatformType type) {
switch (type) {
case PlatformType::Windows:
return getWindowsCapabilities();
case PlatformType::Switch:
return getSwitchCapabilities();
case PlatformType::Linux:
return getLinuxCapabilities();
case PlatformType::macOS:
return getMacOSCapabilities();
default:
return getWindowsCapabilities();
}
}
/**
* @brief
* @return
*/
AppConfig PlatformDetector::platformDefaults() {
return platformDefaults(detect());
}
/**
* @brief
* @param type
* @return
*/
AppConfig PlatformDetector::platformDefaults(PlatformType type) {
switch (type) {
case PlatformType::Windows:
return getWindowsDefaults();
case PlatformType::Switch:
return getSwitchDefaults();
case PlatformType::Linux:
return getLinuxDefaults();
case PlatformType::macOS:
return getMacOSDefaults();
default:
return AppConfig::createDefault();
}
}
/**
* @brief
* @param width
* @param height
*/
void PlatformDetector::getRecommendedResolution(int& width, int& height) {
PlatformCapabilities caps = capabilities();
width = caps.preferredScreenWidth;
height = caps.preferredScreenHeight;
}
/**
* @brief DPI
* @return DPI
*/
float PlatformDetector::getDefaultDPI() {
return capabilities().defaultDPI;
}
/**
* @brief
* @param feature
* @return true
*/
bool PlatformDetector::supportsFeature(const std::string& feature) {
PlatformCapabilities caps = capabilities();
if (feature == "windowed") return caps.supportsWindowed;
if (feature == "fullscreen") return caps.supportsFullscreen;
if (feature == "borderless") return caps.supportsBorderless;
if (feature == "cursor") return caps.supportsCursor;
if (feature == "cursor_hide") return caps.supportsCursorHide;
if (feature == "dpi_awareness") return caps.supportsDPIAwareness;
if (feature == "vsync") return caps.supportsVSync;
if (feature == "multi_monitor") return caps.supportsMultiMonitor;
if (feature == "clipboard") return caps.supportsClipboard;
if (feature == "gamepad") return caps.supportsGamepad;
if (feature == "touch") return caps.supportsTouch;
if (feature == "keyboard") return caps.supportsKeyboard;
if (feature == "mouse") return caps.supportsMouse;
if (feature == "resize") return caps.supportsResize;
if (feature == "high_dpi") return caps.supportsHighDPI;
return false;
}
/**
* @brief
* @return MB 0
*/
int PlatformDetector::getSystemMemoryMB() {
#ifdef _WIN32
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
if (GlobalMemoryStatusEx(&status)) {
return static_cast<int>(status.ullTotalPhys / (1024 * 1024));
}
return 0;
#elif defined(__SWITCH__)
return 4096;
#elif defined(__linux__)
struct sysinfo info;
if (sysinfo(&info) == 0) {
return static_cast<int>(info.totalram * info.mem_unit / (1024 * 1024));
}
return 0;
#elif defined(__APPLE__)
int mib[2] = {CTL_HW, HW_MEMSIZE};
int64_t memSize = 0;
size_t length = sizeof(memSize);
if (sysctl(mib, 2, &memSize, &length, nullptr, 0) == 0) {
return static_cast<int>(memSize / (1024 * 1024));
}
return 0;
#else
return 0;
#endif
}
/**
* @brief CPU
* @return CPU
*/
int PlatformDetector::getCPUCoreCount() {
#ifdef _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return static_cast<int>(sysinfo.dwNumberOfProcessors);
#elif defined(__SWITCH__)
return 4;
#elif defined(__linux__) || defined(__APPLE__)
long cores = sysconf(_SC_NPROCESSORS_ONLN);
return static_cast<int>(cores > 0 ? cores : 1);
#else
return 1;
#endif
}
/**
* @brief 线
* @return true
*/
bool PlatformDetector::supportsMultithreadedRendering() {
#ifdef __SWITCH__
return false;
#else
return getCPUCoreCount() >= 2;
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getConfigPath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\config";
}
return ".\\config";
#elif defined(__SWITCH__)
return "sdmc:/config/" + appName;
#elif defined(__linux__)
const char* configHome = getenv("XDG_CONFIG_HOME");
if (configHome && configHome[0] != '\0') {
return std::string(configHome) + "/" + appName;
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.config/" + appName;
}
return "./config";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Application Support/" + appName + "/config";
}
return "./config";
#else
return "./config";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getSavePath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\saves";
}
return ".\\saves";
#elif defined(__SWITCH__)
return "sdmc:/saves/" + appName;
#elif defined(__linux__)
const char* dataHome = getenv("XDG_DATA_HOME");
if (dataHome && dataHome[0] != '\0') {
return std::string(dataHome) + "/" + appName + "/saves";
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.local/share/" + appName + "/saves";
}
return "./saves";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Application Support/" + appName + "/saves";
}
return "./saves";
#else
return "./saves";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getCachePath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\cache";
}
return ".\\cache";
#elif defined(__SWITCH__)
return "sdmc:/cache/" + appName;
#elif defined(__linux__)
const char* cacheHome = getenv("XDG_CACHE_HOME");
if (cacheHome && cacheHome[0] != '\0') {
return std::string(cacheHome) + "/" + appName;
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.cache/" + appName;
}
return "./cache";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Caches/" + appName;
}
return "./cache";
#else
return "./cache";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getLogPath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\logs";
}
return ".\\logs";
#elif defined(__SWITCH__)
return "sdmc:/logs/" + appName;
#elif defined(__linux__)
const char* cacheHome = getenv("XDG_CACHE_HOME");
if (cacheHome && cacheHome[0] != '\0') {
return std::string(cacheHome) + "/" + appName + "/logs";
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.cache/" + appName + "/logs";
}
return "./logs";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Logs/" + appName;
}
return "./logs";
#else
return "./logs";
#endif
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isLittleEndian() {
union {
uint32_t i;
char c[4];
} test = {0x01020304};
return test.c[0] == 0x04;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isBigEndian() {
return !isLittleEndian();
}
/**
* @brief
* @return
*/
std::string PlatformDetector::getPlatformSummary() {
std::string summary;
summary += "Platform: ";
summary += platformName();
summary += "\n";
summary += "Memory: ";
summary += std::to_string(getSystemMemoryMB());
summary += " MB\n";
summary += "CPU Cores: ";
summary += std::to_string(getCPUCoreCount());
summary += "\n";
summary += "Endianness: ";
summary += isLittleEndian() ? "Little Endian" : "Big Endian";
summary += "\n";
summary += "Desktop Platform: ";
summary += isDesktopPlatform() ? "Yes" : "No";
summary += "\n";
summary += "Console Platform: ";
summary += isConsolePlatform() ? "Yes" : "No";
summary += "\n";
summary += "Recommended Resolution: ";
int width, height;
getRecommendedResolution(width, height);
summary += std::to_string(width);
summary += "x";
summary += std::to_string(height);
summary += "\n";
summary += "Default DPI: ";
summary += std::to_string(static_cast<int>(getDefaultDPI()));
return summary;
}
PlatformCapabilities PlatformDetector::getWindowsCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getLinuxCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getMacOSCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 144.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getSwitchCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = false;
caps.supportsFullscreen = true;
caps.supportsBorderless = false;
caps.supportsCursor = false;
caps.supportsCursorHide = false;
caps.supportsDPIAwareness = false;
caps.supportsVSync = true;
caps.supportsMultiMonitor = false;
caps.supportsClipboard = false;
caps.supportsGamepad = true;
caps.supportsTouch = true;
caps.supportsKeyboard = false;
caps.supportsMouse = false;
caps.supportsResize = false;
caps.supportsHighDPI = false;
caps.maxTextureSize = 8192;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
AppConfig PlatformDetector::getWindowsDefaults() {
AppConfig config = AppConfig::createDefault();
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
config.render.targetFPS = 60;
return config;
}
AppConfig PlatformDetector::getLinuxDefaults() {
AppConfig config = AppConfig::createDefault();
config.window.resizable = true;
config.render.vsync = true;
return config;
}
AppConfig PlatformDetector::getMacOSDefaults() {
AppConfig config = AppConfig::createDefault();
config.window.highDPI = true;
config.window.resizable = true;
config.render.vsync = true;
return config;
}
AppConfig PlatformDetector::getSwitchDefaults() {
AppConfig config = AppConfig::createDefault();
config.window.width = 1920;
config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen;
config.window.resizable = false;
config.window.highDPI = false;
config.render.vsync = true;
config.render.targetFPS = 60;
config.input.enableVibration = true;
config.input.maxGamepads = 2;
return config;
}
}

View File

@ -0,0 +1,313 @@
#include <extra2d/graphics/render_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include <algorithm>
using json = nlohmann::json;
namespace extra2d {
// ============================================================================
// RenderModuleConfig 实现
// ============================================================================
/**
* @brief
*
*
* - 1-240
* - 0248 16
* - 0
*
* @return true
*/
bool RenderModuleConfig::validate() const {
if (targetFPS < 1 || targetFPS > 240) {
E2D_LOG_ERROR("Invalid target FPS: {}, must be between 1 and 240", targetFPS);
return false;
}
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
multisamples != 8 && multisamples != 16) {
E2D_LOG_ERROR("Invalid multisample count: {}, must be 0, 2, 4, 8 or 16", multisamples);
return false;
}
if (spriteBatchSize <= 0) {
E2D_LOG_ERROR("Invalid sprite batch size: {}, must be greater than 0", spriteBatchSize);
return false;
}
return true;
}
/**
* @brief
*
*
* - Switch MSAA 4 sRGB
* -
*
* @param platform
*/
void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) {
switch (platform) {
case PlatformType::Switch:
if (multisamples > 4) {
E2D_LOG_WARN("Switch platform limits MSAA to 4x, reducing from {}", multisamples);
multisamples = 4;
}
if (sRGBFramebuffer) {
E2D_LOG_WARN("Switch platform does not support sRGB framebuffer, disabling");
sRGBFramebuffer = false;
}
if (targetFPS > 60) {
E2D_LOG_WARN("Switch platform target FPS capped at 60");
targetFPS = 60;
}
break;
case PlatformType::Windows:
case PlatformType::Linux:
case PlatformType::macOS:
default:
break;
}
}
/**
* @brief
*
*
* - OpenGL
* -
* - 60
* -
* - sRGB
* - 1000
*/
void RenderModuleConfig::resetToDefaults() {
backend = BackendType::OpenGL;
vsync = true;
targetFPS = 60;
multisamples = 0;
sRGBFramebuffer = false;
spriteBatchSize = 1000;
}
/**
* @brief JSON
*
* JSON
*
* @param jsonData JSON nlohmann::json
* @return true
*/
bool RenderModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) {
E2D_LOG_ERROR("Null JSON data provided");
return false;
}
try {
const json& j = *static_cast<const json*>(jsonData);
if (j.contains("backend")) {
std::string backendStr = j["backend"].get<std::string>();
if (backendStr == "opengl") {
backend = BackendType::OpenGL;
} else {
E2D_LOG_WARN("Unknown backend type: {}, defaulting to OpenGL", backendStr);
backend = BackendType::OpenGL;
}
}
if (j.contains("vsync")) {
vsync = j["vsync"].get<bool>();
}
if (j.contains("targetFPS")) {
targetFPS = j["targetFPS"].get<int>();
}
if (j.contains("multisamples")) {
multisamples = j["multisamples"].get<int>();
}
if (j.contains("sRGBFramebuffer")) {
sRGBFramebuffer = j["sRGBFramebuffer"].get<bool>();
}
if (j.contains("spriteBatchSize")) {
spriteBatchSize = j["spriteBatchSize"].get<int>();
}
E2D_LOG_INFO("Render config loaded from JSON");
return true;
} catch (const json::exception& e) {
E2D_LOG_ERROR("Failed to parse render config from JSON: {}", e.what());
return false;
}
}
/**
* @brief JSON
*
* JSON
*
* @param jsonData JSON nlohmann::json
* @return true
*/
bool RenderModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) {
E2D_LOG_ERROR("Null JSON data provided");
return false;
}
try {
json& j = *static_cast<json*>(jsonData);
std::string backendStr = "opengl";
switch (backend) {
case BackendType::OpenGL:
backendStr = "opengl";
break;
default:
backendStr = "opengl";
break;
}
j["backend"] = backendStr;
j["vsync"] = vsync;
j["targetFPS"] = targetFPS;
j["multisamples"] = multisamples;
j["sRGBFramebuffer"] = sRGBFramebuffer;
j["spriteBatchSize"] = spriteBatchSize;
E2D_LOG_INFO("Render config saved to JSON");
return true;
} catch (const json::exception& e) {
E2D_LOG_ERROR("Failed to save render config to JSON: {}", e.what());
return false;
}
}
// ============================================================================
// RenderModuleInitializer 实现
// ============================================================================
/**
* @brief
*
*
*/
RenderModuleInitializer::RenderModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, windowModuleId_(INVALID_MODULE_ID)
, renderer_(nullptr)
, initialized_(false) {
}
/**
* @brief
*
*
*/
RenderModuleInitializer::~RenderModuleInitializer() {
if (initialized_) {
shutdown();
}
}
/**
* @brief
*
*
*
* @return
*/
std::vector<ModuleId> RenderModuleInitializer::getDependencies() const {
if (windowModuleId_ != INVALID_MODULE_ID) {
return {windowModuleId_};
}
return {};
}
/**
* @brief
*
*
*
* @param config
* @return true
*/
bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) {
E2D_LOG_WARN("Render module already initialized");
return true;
}
if (!config) {
E2D_LOG_ERROR("Null config provided for render module initialization");
return false;
}
const RenderModuleConfig* renderConfig = dynamic_cast<const RenderModuleConfig*>(config);
if (!renderConfig) {
E2D_LOG_ERROR("Invalid config type for render module");
return false;
}
if (!renderConfig->validate()) {
E2D_LOG_ERROR("Invalid render module configuration");
return false;
}
renderer_ = RenderBackend::create(renderConfig->backend);
if (!renderer_) {
E2D_LOG_ERROR("Failed to create render backend");
return false;
}
IWindow* window = nullptr;
if (windowModuleId_ != INVALID_MODULE_ID) {
ModuleRegistry& registry = ModuleRegistry::instance();
IModuleConfig* windowConfig = registry.getModuleConfig(windowModuleId_);
if (windowConfig) {
E2D_LOG_INFO("Render module found window module dependency");
}
}
E2D_LOG_INFO("Render module initialized successfully");
E2D_LOG_INFO(" Backend: {}", renderConfig->backend == BackendType::OpenGL ? "OpenGL" : "Unknown");
E2D_LOG_INFO(" VSync: {}", renderConfig->vsync ? "enabled" : "disabled");
E2D_LOG_INFO(" Target FPS: {}", renderConfig->targetFPS);
E2D_LOG_INFO(" Multisamples: {}", renderConfig->multisamples);
E2D_LOG_INFO(" Sprite Batch Size: {}", renderConfig->spriteBatchSize);
initialized_ = true;
return true;
}
/**
* @brief
*
*
*/
void RenderModuleInitializer::shutdown() {
if (!initialized_) {
return;
}
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
initialized_ = false;
E2D_LOG_INFO("Render module shutdown complete");
}
} // namespace extra2d

View File

@ -311,39 +311,6 @@ void RenderTarget::copyTo(RenderTarget &target) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
/**
* @brief
* @param target
* @param color
* @param depth
*
* 使glBlitFramebuffer进行选择性复制
*/
void RenderTarget::blitTo(RenderTarget &target, bool color, bool depth) {
if (!isValid() || !target.isValid()) {
return;
}
// 使用glBlitFramebuffer复制
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFBO());
GLbitfield mask = 0;
if (color) {
mask |= GL_COLOR_BUFFER_BIT;
}
if (depth && hasDepth_ && target.hasDepth_) {
mask |= GL_DEPTH_BUFFER_BIT;
}
if (mask != 0) {
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.getWidth(),
target.getHeight(), mask, GL_LINEAR);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
/**
* @brief
* @param screenWidth

View File

@ -14,14 +14,16 @@ SDL2Window::~SDL2Window() {
destroy();
}
bool SDL2Window::create(const WindowConfig& cfg) {
bool SDL2Window::create(const WindowConfigData& cfg) {
if (!initSDL()) {
return false;
}
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
if (cfg.fullscreen) {
flags |= cfg.fullscreenDesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
if (cfg.isFullscreen()) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if (cfg.isBorderless()) {
flags |= SDL_WINDOW_BORDERLESS;
}
if (cfg.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
@ -37,14 +39,14 @@ bool SDL2Window::create(const WindowConfig& cfg) {
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
if (cfg.msaaSamples > 0) {
if (cfg.multisamples > 0) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.msaaSamples);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples);
}
int x = SDL_WINDOWPOS_CENTERED;
int y = SDL_WINDOWPOS_CENTERED;
if (!cfg.centerWindow) {
if (!cfg.centered) {
x = SDL_WINDOWPOS_UNDEFINED;
y = SDL_WINDOWPOS_UNDEFINED;
}
@ -74,7 +76,7 @@ bool SDL2Window::create(const WindowConfig& cfg) {
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
fullscreen_ = cfg.fullscreen;
fullscreen_ = cfg.isFullscreen();
vsync_ = cfg.vsync;
initCursors();

View File

@ -15,7 +15,7 @@ public:
SDL2Window();
~SDL2Window() override;
bool create(const WindowConfig& cfg) override;
bool create(const WindowConfigData& cfg) override;
void destroy() override;
void poll() override;

View File

@ -0,0 +1,270 @@
#include <extra2d/platform/input_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace extra2d {
// ============================================================================
// InputModuleConfig 实现
// ============================================================================
/**
* @brief
*
* @return true
*/
bool InputModuleConfig::validate() const {
if (deadzone < 0.0f || deadzone > 1.0f) {
E2D_LOG_ERROR("InputModuleConfig: deadzone 必须在 [0.0, 1.0] 范围内,当前值: {}", deadzone);
return false;
}
if (mouseSensitivity < 0.0f || mouseSensitivity > 10.0f) {
E2D_LOG_ERROR("InputModuleConfig: mouseSensitivity 必须在 [0.0, 10.0] 范围内,当前值: {}", mouseSensitivity);
return false;
}
if (!enableKeyboard && !enableMouse && !enableGamepad && !enableTouch) {
E2D_LOG_WARN("InputModuleConfig: 所有输入设备都已禁用");
}
return true;
}
/**
* @brief
*
* @param platform
*/
void InputModuleConfig::applyPlatformConstraints(PlatformType platform) {
switch (platform) {
case PlatformType::Switch:
enableMouse = false;
enableTouch = true;
enableGamepad = true;
E2D_LOG_INFO("InputModuleConfig: Switch 平台 - 禁用鼠标输入");
break;
case PlatformType::Windows:
case PlatformType::Linux:
case PlatformType::macOS:
enableMouse = true;
enableKeyboard = true;
enableTouch = false;
E2D_LOG_INFO("InputModuleConfig: PC 平台 - 禁用触摸输入");
break;
case PlatformType::Auto:
default:
break;
}
}
/**
* @brief
*/
void InputModuleConfig::resetToDefaults() {
enableKeyboard = true;
enableMouse = true;
enableGamepad = true;
enableTouch = true;
deadzone = 0.15f;
mouseSensitivity = 1.0f;
E2D_LOG_INFO("InputModuleConfig: 已重置为默认配置");
}
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool InputModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) {
E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空");
return false;
}
try {
const json& obj = *static_cast<const json*>(jsonData);
if (!obj.is_object()) {
E2D_LOG_ERROR("InputModuleConfig: JSON 数据不是对象类型");
return false;
}
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;
} catch (const json::exception& e) {
E2D_LOG_ERROR("InputModuleConfig: JSON 解析错误: {}", e.what());
return false;
}
}
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
bool InputModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) {
E2D_LOG_ERROR("InputModuleConfig: JSON 数据指针为空");
return false;
}
try {
json& obj = *static_cast<json*>(jsonData);
obj["enableKeyboard"] = enableKeyboard;
obj["enableMouse"] = enableMouse;
obj["enableGamepad"] = enableGamepad;
obj["enableTouch"] = enableTouch;
obj["deadzone"] = deadzone;
obj["mouseSensitivity"] = mouseSensitivity;
E2D_LOG_INFO("InputModuleConfig: 保存配置到 JSON 成功");
return true;
} catch (const json::exception& e) {
E2D_LOG_ERROR("InputModuleConfig: JSON 序列化错误: {}", e.what());
return false;
}
}
// ============================================================================
// InputModuleInitializer 实现
// ============================================================================
/**
* @brief
*/
InputModuleInitializer::InputModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, windowModuleId_(INVALID_MODULE_ID)
, input_(nullptr)
, initialized_(false) {
E2D_LOG_DEBUG("InputModuleInitializer: 构造函数调用");
}
/**
* @brief
*/
InputModuleInitializer::~InputModuleInitializer() {
if (initialized_) {
shutdown();
}
E2D_LOG_DEBUG("InputModuleInitializer: 析构函数调用");
}
/**
* @brief
*
* @return
*/
std::vector<ModuleId> InputModuleInitializer::getDependencies() const {
std::vector<ModuleId> dependencies;
if (windowModuleId_ != INVALID_MODULE_ID) {
dependencies.push_back(windowModuleId_);
}
return dependencies;
}
/**
* @brief
*
* @param config
* @return true
*/
bool InputModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) {
E2D_LOG_WARN("InputModuleInitializer: 模块已经初始化");
return true;
}
if (!config) {
E2D_LOG_ERROR("InputModuleInitializer: 配置指针为空");
return false;
}
const InputModuleConfig* inputConfig = dynamic_cast<const InputModuleConfig*>(config);
if (!inputConfig) {
E2D_LOG_ERROR("InputModuleInitializer: 配置类型不正确");
return false;
}
if (!inputConfig->validate()) {
E2D_LOG_ERROR("InputModuleInitializer: 配置验证失败");
return false;
}
ModuleInfo info = config->getModuleInfo();
moduleId_ = info.id;
E2D_LOG_INFO("InputModuleInitializer: 正在初始化输入模块 '{}' (版本: {})",
info.name, info.version);
E2D_LOG_INFO("InputModuleInitializer: 输入配置 - 键盘: {}, 鼠标: {}, 手柄: {}, 触摸: {}",
inputConfig->enableKeyboard ? "启用" : "禁用",
inputConfig->enableMouse ? "启用" : "禁用",
inputConfig->enableGamepad ? "启用" : "禁用",
inputConfig->enableTouch ? "启用" : "禁用");
E2D_LOG_INFO("InputModuleInitializer: 死区: {}, 鼠标灵敏度: {}",
inputConfig->deadzone, inputConfig->mouseSensitivity);
E2D_LOG_INFO("InputModuleInitializer: 输入模块初始化成功");
initialized_ = true;
return true;
}
/**
* @brief
*/
void InputModuleInitializer::shutdown() {
if (!initialized_) {
E2D_LOG_WARN("InputModuleInitializer: 模块未初始化,无需关闭");
return;
}
E2D_LOG_INFO("InputModuleInitializer: 正在关闭输入模块");
input_ = nullptr;
moduleId_ = INVALID_MODULE_ID;
initialized_ = false;
E2D_LOG_INFO("InputModuleInitializer: 输入模块已关闭");
}
} // namespace extra2d

View File

@ -0,0 +1,340 @@
#include <extra2d/platform/window_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace extra2d {
// ============================================================================
// WindowModuleConfig 实现
// ============================================================================
/**
* @brief
*
* @return true
*/
bool WindowModuleConfig::validate() const {
if (windowConfig.width <= 0) {
E2D_LOG_ERROR("Window width must be positive, got: {}", windowConfig.width);
return false;
}
if (windowConfig.height <= 0) {
E2D_LOG_ERROR("Window height must be positive, got: {}", windowConfig.height);
return false;
}
if (windowConfig.title.empty()) {
E2D_LOG_WARN("Window title is empty, using default title");
}
if (windowConfig.multisamples < 0) {
E2D_LOG_ERROR("MSAA samples cannot be negative, got: {}", windowConfig.multisamples);
return false;
}
if (windowConfig.multisamples != 0 &&
windowConfig.multisamples != 2 &&
windowConfig.multisamples != 4 &&
windowConfig.multisamples != 8 &&
windowConfig.multisamples != 16) {
E2D_LOG_WARN("MSAA samples should be 0, 2, 4, 8, or 16, got: {}", windowConfig.multisamples);
}
if (backend.empty()) {
E2D_LOG_ERROR("Backend name cannot be empty");
return false;
}
return true;
}
/**
* @brief
*
* @param platform
*/
void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) {
switch (platform) {
case PlatformType::Switch:
E2D_LOG_INFO("Applying Nintendo Switch platform constraints");
windowConfig.mode = WindowMode::Fullscreen;
windowConfig.resizable = false;
windowConfig.centered = false;
windowConfig.width = 1920;
windowConfig.height = 1080;
backend = "switch";
break;
case PlatformType::Windows:
case PlatformType::Linux:
case PlatformType::macOS:
E2D_LOG_INFO("Applying desktop platform constraints");
if (windowConfig.width <= 0) {
windowConfig.width = 1280;
}
if (windowConfig.height <= 0) {
windowConfig.height = 720;
}
break;
case PlatformType::Auto:
default:
E2D_LOG_INFO("Auto-detecting platform constraints");
break;
}
}
/**
* @brief
*
*/
void WindowModuleConfig::resetToDefaults() {
windowConfig = WindowConfigData{};
backend = "sdl2";
E2D_LOG_INFO("Window module config reset to defaults");
}
/**
* @brief JSON
* JSON
* @param jsonData JSON
* @return true
*/
bool WindowModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) {
E2D_LOG_ERROR("JSON data is null");
return false;
}
const json& obj = *static_cast<const json*>(jsonData);
if (!obj.is_object()) {
E2D_LOG_ERROR("JSON data must be an object");
return false;
}
if (obj.contains("title") && obj["title"].is_string()) {
windowConfig.title = obj["title"].get<std::string>();
}
if (obj.contains("width") && obj["width"].is_number_integer()) {
windowConfig.width = obj["width"].get<int>();
}
if (obj.contains("height") && obj["height"].is_number_integer()) {
windowConfig.height = obj["height"].get<int>();
}
if (obj.contains("fullscreen") && obj["fullscreen"].is_boolean()) {
windowConfig.mode = obj["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
}
if (obj.contains("mode") && obj["mode"].is_string()) {
std::string modeStr = obj["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 (obj.contains("resizable") && obj["resizable"].is_boolean()) {
windowConfig.resizable = obj["resizable"].get<bool>();
}
if (obj.contains("vsync") && obj["vsync"].is_boolean()) {
windowConfig.vsync = obj["vsync"].get<bool>();
}
if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) {
windowConfig.multisamples = obj["multisamples"].get<int>();
}
if (obj.contains("msaaSamples") && obj["msaaSamples"].is_number_integer()) {
windowConfig.multisamples = obj["msaaSamples"].get<int>();
}
if (obj.contains("centered") && obj["centered"].is_boolean()) {
windowConfig.centered = obj["centered"].get<bool>();
}
if (obj.contains("centerWindow") && obj["centerWindow"].is_boolean()) {
windowConfig.centered = obj["centerWindow"].get<bool>();
}
if (obj.contains("visible") && obj["visible"].is_boolean()) {
windowConfig.visible = obj["visible"].get<bool>();
}
if (obj.contains("decorated") && obj["decorated"].is_boolean()) {
windowConfig.decorated = obj["decorated"].get<bool>();
}
if (obj.contains("backend") && obj["backend"].is_string()) {
backend = obj["backend"].get<std::string>();
}
E2D_LOG_INFO("Window module config loaded from JSON");
return true;
}
/**
* @brief JSON
* JSON
* @param jsonData JSON
* @return true
*/
bool WindowModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) {
E2D_LOG_ERROR("JSON data pointer is null");
return false;
}
json& obj = *static_cast<json*>(jsonData);
obj["title"] = windowConfig.title;
obj["width"] = windowConfig.width;
obj["height"] = windowConfig.height;
std::string modeStr = "windowed";
if (windowConfig.mode == WindowMode::Fullscreen) {
modeStr = "fullscreen";
} else if (windowConfig.mode == WindowMode::Borderless) {
modeStr = "borderless";
}
obj["mode"] = modeStr;
obj["resizable"] = windowConfig.resizable;
obj["vsync"] = windowConfig.vsync;
obj["multisamples"] = windowConfig.multisamples;
obj["centered"] = windowConfig.centered;
obj["visible"] = windowConfig.visible;
obj["decorated"] = windowConfig.decorated;
obj["backend"] = backend;
E2D_LOG_INFO("Window module config saved to JSON");
return true;
}
// ============================================================================
// WindowModuleInitializer 实现
// ============================================================================
/**
* @brief
*
*/
WindowModuleInitializer::WindowModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, window_(nullptr)
, initialized_(false) {
E2D_LOG_DEBUG("WindowModuleInitializer constructed");
}
/**
* @brief
*
*/
WindowModuleInitializer::~WindowModuleInitializer() {
if (initialized_) {
shutdown();
}
E2D_LOG_DEBUG("WindowModuleInitializer destructed");
}
/**
* @brief
* 使 BackendFactory
* @param config
* @return true
*/
bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) {
E2D_LOG_WARN("Window module already initialized");
return true;
}
if (!config) {
E2D_LOG_ERROR("Window module config is null");
return false;
}
const WindowModuleConfig* windowConfig = dynamic_cast<const WindowModuleConfig*>(config);
if (!windowConfig) {
E2D_LOG_ERROR("Invalid config type for window module");
return false;
}
ModuleInfo info = config->getModuleInfo();
moduleId_ = info.id;
const std::string& backend = windowConfig->backend;
if (!BackendFactory::has(backend)) {
E2D_LOG_ERROR("Backend '{}' not available", backend);
auto backends = BackendFactory::backends();
if (backends.empty()) {
E2D_LOG_ERROR("No backends registered!");
return false;
}
std::string backendList;
for (const auto& b : backends) {
if (!backendList.empty()) backendList += ", ";
backendList += b;
}
E2D_LOG_WARN("Available backends: {}", backendList);
return false;
}
window_ = BackendFactory::createWindow(backend);
if (!window_) {
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
return false;
}
if (!window_->create(windowConfig->windowConfig)) {
E2D_LOG_ERROR("Failed to create window with given config");
window_.reset();
return false;
}
initialized_ = true;
E2D_LOG_INFO("Window module initialized successfully (backend: {}, {}x{})",
backend,
windowConfig->windowConfig.width,
windowConfig->windowConfig.height);
return true;
}
/**
* @brief
*
*/
void WindowModuleInitializer::shutdown() {
if (!initialized_) {
E2D_LOG_WARN("Window module not initialized, nothing to shutdown");
return;
}
if (window_) {
window_->destroy();
window_.reset();
}
initialized_ = false;
moduleId_ = INVALID_MODULE_ID;
E2D_LOG_INFO("Window module shutdown complete");
}
} // namespace extra2d