refactor(engine): 重构模块系统与平台后端
- 移除PlatformModule和LoggerModule,改为使用E2D_MODULE宏自动注册模块 - 新增ModuleRegistry和ModuleMeta系统实现模块自发现 - 将BackendFactory从PlatformModule移至独立文件 - 添加export.h统一管理导出宏 - 更新README.md添加模块自发现流程图 - 修复SDL2Input初始化状态管理问题 - 清理不再使用的平台配置相关代码 - 示例项目改为静态链接确保模块自动注册 - 添加属性绑定系统支持运行时反射
This commit is contained in:
parent
a78e6f7a05
commit
8fc3b794d2
|
|
@ -1,14 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -17,9 +17,11 @@ class RenderBackend;
|
|||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
* 使用服务定位器模式管理模块依赖,支持依赖注入和测试Mock
|
||||
*
|
||||
* 模块现在通过 E2D_MODULE 宏自动注册,无需手动调用 use()
|
||||
* Application 只负责协调初始化和生命周期管理
|
||||
*/
|
||||
class Application {
|
||||
class E2D_API Application {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
|
|
@ -30,18 +32,6 @@ public:
|
|||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
|
||||
/**
|
||||
* @brief 添加模块
|
||||
* @param m 模块引用
|
||||
*/
|
||||
void use(Module& m);
|
||||
|
||||
/**
|
||||
* @brief 批量添加模块
|
||||
* @param modules 模块指针列表
|
||||
*/
|
||||
void use(std::initializer_list<Module*> modules);
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
|
|
@ -177,13 +167,30 @@ public:
|
|||
*/
|
||||
const AppConfig &getConfig() const;
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按类型)
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
template <typename T> T *getModule() {
|
||||
return ModuleRegistry::instance().getModule<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按名称)
|
||||
* @param name 模块名称
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
Module *getModule(const char *name) {
|
||||
return ModuleRegistry::instance().getModule(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册自定义服务
|
||||
* @tparam T 服务接口类型
|
||||
* @param service 服务实例
|
||||
*/
|
||||
template<typename T>
|
||||
void registerService(SharedPtr<T> service) {
|
||||
template <typename T> void registerService(SharedPtr<T> service) {
|
||||
ServiceLocator::instance().registerService(service);
|
||||
}
|
||||
|
||||
|
|
@ -192,8 +199,7 @@ public:
|
|||
* @tparam T 服务接口类型
|
||||
* @return 服务共享指针
|
||||
*/
|
||||
template<typename T>
|
||||
SharedPtr<T> getService() {
|
||||
template <typename T> SharedPtr<T> getService() {
|
||||
return ServiceLocator::instance().getService<T>();
|
||||
}
|
||||
|
||||
|
|
@ -202,25 +208,17 @@ private:
|
|||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 初始化核心模块
|
||||
* @brief 初始化模块(从注册器创建并初始化)
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initCoreModules();
|
||||
|
||||
/**
|
||||
* @brief 设置所有模块
|
||||
*/
|
||||
void setupAllModules();
|
||||
|
||||
/**
|
||||
* @brief 销毁所有模块
|
||||
*/
|
||||
void destroyAllModules();
|
||||
bool initModules(const AppConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 注册核心服务
|
||||
*/
|
||||
void registerCoreServices();
|
||||
void registerCameraService();
|
||||
|
||||
/**
|
||||
* @brief 主循环
|
||||
|
|
@ -237,7 +235,6 @@ private:
|
|||
*/
|
||||
void render();
|
||||
|
||||
std::vector<Module*> modules_;
|
||||
IWindow *window_ = nullptr;
|
||||
|
||||
bool initialized_ = false;
|
||||
|
|
@ -253,4 +250,4 @@ private:
|
|||
int currentFps_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file audio_config.h
|
||||
* @brief 音频模块配置
|
||||
*
|
||||
* 定义音频相关的配置数据结构,由 AudioModule 管理。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 音频配置数据结构
|
||||
*/
|
||||
struct AudioConfigData {
|
||||
bool enabled = true;
|
||||
int masterVolume = 100;
|
||||
int musicVolume = 100;
|
||||
int sfxVolume = 100;
|
||||
int voiceVolume = 100;
|
||||
int ambientVolume = 100;
|
||||
int frequency = 44100;
|
||||
int channels = 2;
|
||||
int chunkSize = 2048;
|
||||
int maxChannels = 16;
|
||||
bool spatialAudio = false;
|
||||
float listenerPosition[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
/**
|
||||
* @brief 验证音量值是否有效
|
||||
* @param volume 要验证的音量值
|
||||
* @return 如果音量在0-100范围内返回 true
|
||||
*/
|
||||
bool isValidVolume(int volume) const { return volume >= 0 && volume <= 100; }
|
||||
|
||||
/**
|
||||
* @brief 将音量值转换为浮点数
|
||||
* @param volume 音量值(0-100)
|
||||
* @return 浮点数音量值(0.0-1.0)
|
||||
*/
|
||||
float volumeToFloat(int volume) const { return static_cast<float>(volume) / 100.0f; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -21,12 +21,11 @@ namespace extra2d {
|
|||
* @brief 应用配置结构体
|
||||
* 仅包含应用级别的配置项,模块配置由各模块自行管理
|
||||
*/
|
||||
struct AppConfig {
|
||||
struct E2D_API AppConfig {
|
||||
std::string appName = "Extra2D App";
|
||||
std::string appVersion = "1.0.0";
|
||||
std::string organization = "";
|
||||
std::string configFile = "config.json";
|
||||
PlatformType targetPlatform = PlatformType::Auto;
|
||||
|
||||
/**
|
||||
* @brief 创建默认配置
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#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>
|
||||
|
|
@ -114,18 +113,6 @@ public:
|
|||
*/
|
||||
void setAppConfig(const AppConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 获取平台配置
|
||||
* @return 平台配置接口指针
|
||||
*/
|
||||
PlatformConfig *platformConfig();
|
||||
|
||||
/**
|
||||
* @brief 获取平台配置(常量版本)
|
||||
* @return 平台配置接口常量指针
|
||||
*/
|
||||
const PlatformConfig *platformConfig() const;
|
||||
|
||||
/**
|
||||
* @brief 注册配置变更回调
|
||||
* @param callback 回调函数
|
||||
|
|
@ -273,7 +260,6 @@ private:
|
|||
void notifyChangeCallbacks(const ConfigChangeEvent &event);
|
||||
|
||||
AppConfig m_appConfig;
|
||||
UniquePtr<PlatformConfig> m_platformConfig;
|
||||
UniquePtr<ConfigLoader> m_loader;
|
||||
std::string m_configPath;
|
||||
bool m_initialized = false;
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file platform_config.h
|
||||
* @brief 平台配置接口
|
||||
*
|
||||
* 平台配置只提供平台能力信息,不再直接修改应用配置。
|
||||
* 各模块通过 IModuleConfig::applyPlatformConstraints() 处理平台约束。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 平台类型枚举
|
||||
*/
|
||||
enum class PlatformType {
|
||||
Auto,
|
||||
Windows,
|
||||
Switch,
|
||||
Linux,
|
||||
macOS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台能力结构
|
||||
*/
|
||||
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; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台配置抽象接口
|
||||
*/
|
||||
class PlatformConfig {
|
||||
public:
|
||||
virtual ~PlatformConfig() = default;
|
||||
|
||||
virtual PlatformType platformType() const = 0;
|
||||
virtual const char* platformName() const = 0;
|
||||
virtual const PlatformCapabilities& capabilities() const = 0;
|
||||
|
||||
virtual int getRecommendedWidth() const = 0;
|
||||
virtual int getRecommendedHeight() const = 0;
|
||||
virtual bool isResolutionSupported(int width, int height) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 创建平台配置实例
|
||||
* @param type 平台类型,默认为 Auto(自动检测)
|
||||
* @return 平台配置的智能指针
|
||||
*/
|
||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
|
||||
|
||||
/**
|
||||
* @brief 获取平台类型名称
|
||||
* @param type 平台类型枚举值
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
const char* getPlatformTypeName(PlatformType type);
|
||||
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
#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 获取平台特定的资源路径(Shader、纹理等)
|
||||
* Switch平台使用romfs,其他平台使用相对路径
|
||||
* @param appName 应用名称
|
||||
* @return 资源目录路径
|
||||
*/
|
||||
static std::string getResourcePath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader路径
|
||||
* @param appName 应用名称
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
static std::string getShaderPath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader缓存路径
|
||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
||||
* @param appName 应用名称
|
||||
* @return Shader缓存目录路径
|
||||
*/
|
||||
static std::string getShaderCachePath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
||||
* @return 使用romfs返回true
|
||||
*/
|
||||
static bool usesRomfs();
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否支持热重载
|
||||
* Switch平台不支持热重载(romfs只读)
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
static bool supportsHotReload();
|
||||
|
||||
/**
|
||||
* @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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
// 动态库导出宏
|
||||
// 静态库时 E2D_API 为空
|
||||
#ifndef E2D_BUILDING_DLL
|
||||
#define E2D_API
|
||||
#else
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define E2D_API __declspec(dllexport)
|
||||
#else
|
||||
#define E2D_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 模板类导出(不需要导出)
|
||||
#define E2D_TEMPLATE_API
|
||||
|
||||
// 内联函数导出
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define E2D_INLINE __forceinline
|
||||
#else
|
||||
#define E2D_INLINE inline
|
||||
#endif
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/property.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -14,7 +16,7 @@ class EventContext;
|
|||
* @brief 模块上下文基类
|
||||
* 用于遍历模块链,支持链式调用
|
||||
*/
|
||||
class ModuleContext {
|
||||
class E2D_API ModuleContext {
|
||||
public:
|
||||
/**
|
||||
* @brief 析构函数
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/core/property.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 模块元数据模板实现
|
||||
*/
|
||||
template<typename T>
|
||||
class ModuleMeta : public ModuleMetaBase {
|
||||
public:
|
||||
using ModuleType = T;
|
||||
|
||||
const char* name_ = nullptr;
|
||||
int priority_ = 0;
|
||||
std::vector<const char*> dependencies_;
|
||||
std::function<void(Module*, PropertyBinder&)> bindFunc_;
|
||||
|
||||
const char* getName() const override { return name_; }
|
||||
int getPriority() const override { return priority_; }
|
||||
std::vector<const char*> getDependencies() const override { return dependencies_; }
|
||||
|
||||
T* create() override {
|
||||
return new T();
|
||||
}
|
||||
|
||||
void bindProperties(Module* instance, PropertyBinder& binder) override {
|
||||
if (bindFunc_) {
|
||||
bindFunc_(instance, binder);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* @brief 模块注册辅助类(静态自动注册)
|
||||
*/
|
||||
template<typename T>
|
||||
struct ModuleAutoRegister {
|
||||
ModuleMeta<T> meta;
|
||||
|
||||
ModuleAutoRegister(const char* name, int priority, std::initializer_list<const char*> deps) {
|
||||
meta.name_ = name;
|
||||
meta.priority_ = priority;
|
||||
meta.dependencies_ = std::vector<const char*>(deps);
|
||||
ModuleRegistry::instance().registerMeta(&meta);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// ============================================================================
|
||||
// 模块定义宏 - 静态自动注册
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 简化版模块定义(静态自动注册)
|
||||
*
|
||||
* 用于引擎内部模块,在动态库加载时自动注册
|
||||
*/
|
||||
#define E2D_MODULE(ModuleClassName, priorityValue, ...) \
|
||||
__attribute__((used)) \
|
||||
static ::extra2d::detail::ModuleAutoRegister< ::extra2d::ModuleClassName> \
|
||||
E2D_CONCAT(_e2d_auto_reg_, ModuleClassName)( \
|
||||
#ModuleClassName, priorityValue, { __VA_ARGS__ });
|
||||
|
||||
/**
|
||||
* @brief 外部模块定义(自动生成 force_link 函数)
|
||||
*
|
||||
* 用于编译为单独 DLL 的自定义模块
|
||||
*
|
||||
* 使用示例(在模块 cpp 文件末尾):
|
||||
* } // namespace extra2d
|
||||
* E2D_MODULE_EXPORT(HelloModule, 1000)
|
||||
*/
|
||||
#define E2D_MODULE_EXPORT(ModuleClassName, priorityValue, ...) \
|
||||
E2D_MODULE(ModuleClassName, priorityValue, __VA_ARGS__) \
|
||||
extern "C" E2D_API void E2D_CONCAT(e2d_force_link_, ModuleClassName)() {}
|
||||
|
||||
/**
|
||||
* @brief 声明外部模块的 force_link 函数
|
||||
*/
|
||||
#define E2D_DECLARE_FORCE_LINK(ModuleClassName) \
|
||||
extern "C" void E2D_CONCAT(e2d_force_link_, ModuleClassName)()
|
||||
|
||||
/**
|
||||
* @brief 调用 force_link 函数
|
||||
*/
|
||||
#define E2D_CALL_FORCE_LINK(ModuleClassName) \
|
||||
E2D_CONCAT(e2d_force_link_, ModuleClassName)()
|
||||
|
||||
/**
|
||||
* @brief 强制链接外部模块(声明 + 调用)
|
||||
*
|
||||
* 在 main.cpp 开头调用,触发 DLL 静态初始化
|
||||
*/
|
||||
#define E2D_FORCE_LINK(ModuleClassName) \
|
||||
E2D_DECLARE_FORCE_LINK(ModuleClassName); \
|
||||
E2D_CALL_FORCE_LINK(ModuleClassName)
|
||||
|
||||
/**
|
||||
* @brief 带属性的模块定义开始
|
||||
*/
|
||||
#define E2D_MODULE_BEGIN(ModuleClassName) \
|
||||
namespace { \
|
||||
static ::extra2d::ModuleMeta< ::extra2d::ModuleClassName>& E2D_CONCAT(_e2d_get_meta_, ModuleClassName)() { \
|
||||
static ::extra2d::ModuleMeta< ::extra2d::ModuleClassName> meta; \
|
||||
return meta; \
|
||||
} \
|
||||
struct E2D_CONCAT(_E2D_ModuleCfg_, ModuleClassName) { \
|
||||
E2D_CONCAT(_E2D_ModuleCfg_, ModuleClassName)()
|
||||
|
||||
/**
|
||||
* @brief 定义模块优先级
|
||||
*/
|
||||
#define E2D_PRIORITY(value) \
|
||||
{ auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); m.priority_ = value; }
|
||||
|
||||
/**
|
||||
* @brief 定义模块依赖
|
||||
*/
|
||||
#define E2D_DEPENDENCIES(...) \
|
||||
{ \
|
||||
auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); \
|
||||
m.dependencies_ = { __VA_ARGS__ }; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定义属性
|
||||
*/
|
||||
#define E2D_PROPERTY(name, type) \
|
||||
{ \
|
||||
auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); \
|
||||
auto oldFunc = m.bindFunc_; \
|
||||
m.bindFunc_ = [oldFunc](::extra2d::Module* inst, ::extra2d::PropertyBinder& binder) { \
|
||||
if (oldFunc) oldFunc(inst, binder); \
|
||||
auto* module = static_cast< ::extra2d::ModuleClassName*>(inst); \
|
||||
binder.bind<type>(#name, module->name, #name, ""); \
|
||||
}; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束模块定义
|
||||
*/
|
||||
#define E2D_MODULE_END() \
|
||||
} E2D_CONCAT(_e2d_cfg_inst_, ModuleClassName); \
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/property.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Module;
|
||||
|
||||
/**
|
||||
* @brief 模块元数据基类
|
||||
*
|
||||
* 提供模块的类型信息、依赖关系和创建工厂
|
||||
*/
|
||||
struct E2D_API ModuleMetaBase {
|
||||
virtual ~ModuleMetaBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
*/
|
||||
virtual const char* getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
*/
|
||||
virtual int getPriority() const { return 0; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖
|
||||
*/
|
||||
virtual std::vector<const char*> getDependencies() const { return {}; }
|
||||
|
||||
/**
|
||||
* @brief 创建模块实例
|
||||
*/
|
||||
virtual Module* create() = 0;
|
||||
|
||||
/**
|
||||
* @brief 绑定模块属性
|
||||
*/
|
||||
virtual void bindProperties(Module* instance, PropertyBinder& binder) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 模块注册器(自动发现)
|
||||
*
|
||||
* 单例类,管理所有模块的注册、创建和生命周期
|
||||
*/
|
||||
class E2D_API ModuleRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
*/
|
||||
static ModuleRegistry& instance();
|
||||
|
||||
/**
|
||||
* @brief 注册模块元数据
|
||||
* @param meta 模块元数据指针
|
||||
*/
|
||||
void registerMeta(ModuleMetaBase* meta);
|
||||
|
||||
/**
|
||||
* @brief 获取所有已注册模块元数据
|
||||
*/
|
||||
std::vector<ModuleMetaBase*> getAllMetas() const;
|
||||
|
||||
/**
|
||||
* @brief 按名称获取模块元数据
|
||||
*/
|
||||
ModuleMetaBase* getMeta(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 创建并初始化所有模块
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool createAndInitAll();
|
||||
|
||||
/**
|
||||
* @brief 销毁所有模块
|
||||
*/
|
||||
void destroyAll();
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按类型)
|
||||
*/
|
||||
template<typename T>
|
||||
T* getModule() const {
|
||||
for (const auto& [name, ptr] : instanceMap_) {
|
||||
if (auto* derived = dynamic_cast<T*>(ptr)) {
|
||||
return derived;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按名称)
|
||||
*/
|
||||
Module* getModule(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否存在
|
||||
*/
|
||||
bool hasModule(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有模块实例
|
||||
*/
|
||||
std::vector<Module*> getAllModules() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ModuleRegistry() = default;
|
||||
~ModuleRegistry() = default;
|
||||
ModuleRegistry(const ModuleRegistry&) = delete;
|
||||
ModuleRegistry& operator=(const ModuleRegistry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 按依赖关系拓扑排序
|
||||
*/
|
||||
std::vector<ModuleMetaBase*> sortByDependency();
|
||||
|
||||
/**
|
||||
* @brief 检测循环依赖
|
||||
*/
|
||||
bool hasCircularDependency() const;
|
||||
|
||||
std::vector<ModuleMetaBase*> metas_;
|
||||
std::vector<std::unique_ptr<Module>> instances_;
|
||||
std::unordered_map<std::string, Module*> instanceMap_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 属性值类型
|
||||
*
|
||||
* 支持多种基础类型的运行时存储和查询
|
||||
*/
|
||||
using PropertyValue = std::variant<
|
||||
std::monostate,
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
double,
|
||||
std::string,
|
||||
Vec2,
|
||||
Vec3,
|
||||
Color,
|
||||
Rect,
|
||||
Size
|
||||
>;
|
||||
|
||||
/**
|
||||
* @brief 属性元数据
|
||||
*
|
||||
* 包含属性的描述信息,用于编辑器和序列化
|
||||
*/
|
||||
struct PropertyMeta {
|
||||
const char* name = nullptr;
|
||||
const char* displayName = nullptr;
|
||||
const char* description = nullptr;
|
||||
PropertyValue defaultValue{};
|
||||
bool editable = true;
|
||||
bool serializable = true;
|
||||
|
||||
PropertyMeta() = default;
|
||||
|
||||
PropertyMeta(const char* n, const char* dn, const char* desc,
|
||||
PropertyValue def, bool edit = true, bool ser = true)
|
||||
: name(n), displayName(dn), description(desc),
|
||||
defaultValue(def), editable(edit), serializable(ser) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 属性访问器
|
||||
*
|
||||
* 用于运行时读写属性值
|
||||
*/
|
||||
struct PropertyAccessor {
|
||||
std::function<PropertyValue()> getter;
|
||||
std::function<bool(const PropertyValue&)> setter;
|
||||
PropertyMeta meta;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 属性绑定器基类
|
||||
*
|
||||
* 提供属性的绑定、查询和修改接口
|
||||
*/
|
||||
class PropertyBinder {
|
||||
public:
|
||||
virtual ~PropertyBinder() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取所有属性元数据
|
||||
* @return 属性元数据列表
|
||||
*/
|
||||
virtual std::vector<PropertyMeta> getProperties() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取属性值
|
||||
* @param name 属性名
|
||||
* @return 属性值
|
||||
*/
|
||||
virtual PropertyValue getProperty(const char* name) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置属性值
|
||||
* @param name 属性名
|
||||
* @param value 属性值
|
||||
* @return 设置成功返回 true
|
||||
*/
|
||||
virtual bool setProperty(const char* name, const PropertyValue& value) = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有指定属性
|
||||
* @param name 属性名
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
virtual bool hasProperty(const char* name) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取属性元数据
|
||||
* @param name 属性名
|
||||
* @return 属性元数据指针,不存在返回 nullptr
|
||||
*/
|
||||
virtual const PropertyMeta* getPropertyMeta(const char* name) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 属性绑定器实现
|
||||
*
|
||||
* 使用函数指针实现属性的运行时访问
|
||||
*/
|
||||
class PropertyBinderImpl : public PropertyBinder {
|
||||
public:
|
||||
/**
|
||||
* @brief 绑定属性
|
||||
* @tparam T 属性类型
|
||||
* @param name 属性名
|
||||
* @param value 属性引用
|
||||
* @param displayName 显示名
|
||||
* @param description 描述
|
||||
*/
|
||||
template<typename T>
|
||||
void bind(const char* name, T& value,
|
||||
const char* displayName = nullptr,
|
||||
const char* description = nullptr) {
|
||||
PropertyAccessor accessor;
|
||||
accessor.meta = PropertyMeta(name, displayName ? displayName : name,
|
||||
description ? description : "", T{});
|
||||
accessor.getter = [&value]() -> PropertyValue { return value; };
|
||||
accessor.setter = [&value](const PropertyValue& v) -> bool {
|
||||
if (auto* ptr = std::get_if<T>(&v)) {
|
||||
value = *ptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
accessors_[name] = std::move(accessor);
|
||||
}
|
||||
|
||||
std::vector<PropertyMeta> getProperties() const override {
|
||||
std::vector<PropertyMeta> result;
|
||||
result.reserve(accessors_.size());
|
||||
for (const auto& [name, accessor] : accessors_) {
|
||||
result.push_back(accessor.meta);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PropertyValue getProperty(const char* name) const override {
|
||||
auto it = accessors_.find(name);
|
||||
if (it != accessors_.end() && it->second.getter) {
|
||||
return it->second.getter();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool setProperty(const char* name, const PropertyValue& value) override {
|
||||
auto it = accessors_.find(name);
|
||||
if (it != accessors_.end() && it->second.setter) {
|
||||
return it->second.setter(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasProperty(const char* name) const override {
|
||||
return accessors_.find(name) != accessors_.end();
|
||||
}
|
||||
|
||||
const PropertyMeta* getPropertyMeta(const char* name) const override {
|
||||
auto it = accessors_.find(name);
|
||||
if (it != accessors_.end()) {
|
||||
return &it->second.meta;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, PropertyAccessor> accessors_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -6,23 +6,20 @@
|
|||
// Core
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_macros.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/platform_config.h>
|
||||
#include <extra2d/config/platform_detector.h>
|
||||
|
||||
// Modules
|
||||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/modules/logger_module.h>
|
||||
#include <extra2d/modules/platform_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
|
||||
// Platform
|
||||
#include <extra2d/platform/iinput.h>
|
||||
|
|
@ -61,10 +58,10 @@
|
|||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Services
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
|
||||
// Application
|
||||
#include <extra2d/app/application.h>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/platform_detector.h>
|
||||
#include <extra2d/graphics/shader_cache.h>
|
||||
#include <extra2d/graphics/shader_hot_reloader.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
|
|
|
|||
|
|
@ -67,9 +67,14 @@ public:
|
|||
* @brief 设置窗口
|
||||
* @param window 窗口接口指针
|
||||
*/
|
||||
void setWindow(IWindow* window) { window_ = window; }
|
||||
void setWindow(IWindow* window);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 使用窗口初始化输入
|
||||
*/
|
||||
void initializeWithWindow();
|
||||
|
||||
IWindow* window_ = nullptr;
|
||||
IInput* input_ = nullptr;
|
||||
InputConfigData config_;
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 日志模块
|
||||
* 管理日志系统的初始化和配置
|
||||
*/
|
||||
class LoggerModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
LoggerModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~LoggerModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Logger"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级(核心模块,最先初始化)
|
||||
*/
|
||||
int getPriority() const override { return -1; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
* @param level 日志级别
|
||||
*/
|
||||
void setLogLevel(LogLevel level) { logLevel_ = level; }
|
||||
|
||||
/**
|
||||
* @brief 设置控制台输出
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setConsoleOutput(bool enabled) { consoleOutput_ = enabled; }
|
||||
|
||||
/**
|
||||
* @brief 设置文件输出
|
||||
* @param filePath 日志文件路径
|
||||
*/
|
||||
void setFileOutput(const std::string& filePath) {
|
||||
fileOutput_ = true;
|
||||
logFilePath_ = filePath;
|
||||
}
|
||||
|
||||
private:
|
||||
LogLevel logLevel_ = LogLevel::Info;
|
||||
bool consoleOutput_ = true;
|
||||
bool fileOutput_ = false;
|
||||
std::string logFilePath_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/config/platform_config.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 平台模块
|
||||
* 管理平台相关的初始化和配置
|
||||
*/
|
||||
class PlatformModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
PlatformModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~PlatformModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Platform"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级
|
||||
*/
|
||||
int getPriority() const override { return 10; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 设置目标平台
|
||||
* @param platform 目标平台类型
|
||||
*/
|
||||
void setTargetPlatform(PlatformType platform) { targetPlatform_ = platform; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台
|
||||
* @return 当前平台类型
|
||||
*/
|
||||
PlatformType getPlatform() const { return resolvedPlatform_; }
|
||||
|
||||
/**
|
||||
* @brief 获取平台配置
|
||||
* @return 平台配置指针
|
||||
*/
|
||||
PlatformConfig* getPlatformConfig() const { return platformConfig_.get(); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 初始化 Switch 平台
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initSwitch();
|
||||
|
||||
/**
|
||||
* @brief 关闭 Switch 平台
|
||||
*/
|
||||
void shutdownSwitch();
|
||||
|
||||
PlatformType targetPlatform_ = PlatformType::Auto;
|
||||
PlatformType resolvedPlatform_ = PlatformType::Windows;
|
||||
UniquePtr<PlatformConfig> platformConfig_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -99,7 +99,7 @@ public:
|
|||
* @brief 设置窗口
|
||||
* @param window 窗口接口指针
|
||||
*/
|
||||
void setWindow(IWindow* window) { window_ = window; }
|
||||
void setWindow(IWindow* window);
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
|
|
@ -108,6 +108,11 @@ public:
|
|||
RenderBackend* getRenderer() const { return renderer_.get(); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 使用窗口初始化渲染器
|
||||
*/
|
||||
void initializeWithWindow();
|
||||
|
||||
IWindow* window_ = nullptr;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
RenderModuleConfig config_;
|
||||
|
|
|
|||
|
|
@ -10,16 +10,6 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 平台模块配置
|
||||
*/
|
||||
struct PlatformModuleConfig {
|
||||
std::string backend = "sdl2";
|
||||
bool gamepad = true;
|
||||
bool touch = true;
|
||||
float deadzone = 0.15f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台后端工厂
|
||||
* 用于注册和创建平台后端
|
||||
|
|
@ -95,4 +85,4 @@ private:
|
|||
} e2d_backend_reg_##name; \
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
|
|
@ -19,7 +20,7 @@ struct RenderCommand;
|
|||
// ============================================================================
|
||||
// 节点基类 - 场景图的基础
|
||||
// ============================================================================
|
||||
class Node : public std::enable_shared_from_this<Node> {
|
||||
class E2D_API Node : public std::enable_shared_from_this<Node> {
|
||||
public:
|
||||
Node();
|
||||
virtual ~Node();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <vector>
|
||||
|
|
@ -13,7 +14,7 @@ struct RenderCommand;
|
|||
// ============================================================================
|
||||
// 场景类 - 节点容器,管理整个场景图
|
||||
// ============================================================================
|
||||
class Scene : public Node {
|
||||
class E2D_API Scene : public Node {
|
||||
public:
|
||||
Scene();
|
||||
~Scene() override = default;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <vector>
|
||||
|
|
@ -15,7 +16,7 @@ enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
|||
// ============================================================================
|
||||
// 形状节点 - 用于绘制几何形状
|
||||
// ============================================================================
|
||||
class ShapeNode : public Node {
|
||||
class E2D_API ShapeNode : public Node {
|
||||
public:
|
||||
ShapeNode();
|
||||
~ShapeNode() override = default;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
|
@ -128,7 +129,7 @@ inline std::string e2d_format(const char *fmt, const Args &...args) {
|
|||
|
||||
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
||||
|
||||
class Logger {
|
||||
class E2D_API Logger {
|
||||
public:
|
||||
static void init();
|
||||
static void shutdown();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/modules/platform_module.h>
|
||||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
|
|
@ -11,9 +11,7 @@
|
|||
#include <extra2d/services/timer_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -36,23 +34,6 @@ Application &Application::get() {
|
|||
return instance;
|
||||
}
|
||||
|
||||
void Application::use(Module &m) {
|
||||
for (auto *existing : modules_) {
|
||||
if (existing == &m) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
modules_.push_back(&m);
|
||||
}
|
||||
|
||||
void Application::use(std::initializer_list<Module *> modules) {
|
||||
for (auto *m : modules) {
|
||||
if (m) {
|
||||
use(*m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::init() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
|
|
@ -63,49 +44,14 @@ bool Application::init(const AppConfig &config) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!initCoreModules()) {
|
||||
// 先注册核心服务,因为模块初始化时可能需要它们
|
||||
registerCoreServices();
|
||||
|
||||
if (!initModules(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigModule *configModule = nullptr;
|
||||
for (auto *m : modules_) {
|
||||
if (auto *cm = dynamic_cast<ConfigModule *>(m)) {
|
||||
configModule = cm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (configModule) {
|
||||
configModule->setAppConfig(config);
|
||||
}
|
||||
|
||||
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
|
||||
|
||||
WindowModule *windowModule = nullptr;
|
||||
InputModule *inputModule = nullptr;
|
||||
RenderModule *renderModule = nullptr;
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (auto *wm = dynamic_cast<WindowModule *>(m)) {
|
||||
windowModule = wm;
|
||||
}
|
||||
if (auto *im = dynamic_cast<InputModule *>(m)) {
|
||||
inputModule = im;
|
||||
}
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderModule = rm;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (m == inputModule || m == renderModule) {
|
||||
continue;
|
||||
}
|
||||
if (!m->isInitialized()) {
|
||||
m->setupModule();
|
||||
}
|
||||
}
|
||||
|
||||
auto windowModule = getModule<WindowModule>();
|
||||
if (!windowModule || !windowModule->isInitialized()) {
|
||||
E2D_LOG_ERROR("Window module not initialized");
|
||||
return false;
|
||||
|
|
@ -117,18 +63,19 @@ bool Application::init(const AppConfig &config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
registerCoreServices();
|
||||
|
||||
auto inputModule = getModule<InputModule>();
|
||||
if (inputModule) {
|
||||
inputModule->setWindow(window_);
|
||||
inputModule->setupModule();
|
||||
}
|
||||
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
renderModule->setupModule();
|
||||
}
|
||||
|
||||
// 注册 CameraService(需要 window)
|
||||
registerCameraService();
|
||||
|
||||
if (!ServiceLocator::instance().initializeAll()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -162,168 +109,29 @@ bool Application::init(const std::string &configPath) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!initCoreModules()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigModule *configModule = nullptr;
|
||||
for (auto *m : modules_) {
|
||||
if (auto *cm = dynamic_cast<ConfigModule *>(m)) {
|
||||
configModule = cm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto configModule = getModule<ConfigModule>();
|
||||
if (configModule) {
|
||||
configModule->setConfigPath(configPath);
|
||||
}
|
||||
|
||||
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
|
||||
|
||||
WindowModule *windowModule = nullptr;
|
||||
InputModule *inputModule = nullptr;
|
||||
RenderModule *renderModule = nullptr;
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (auto *wm = dynamic_cast<WindowModule *>(m)) {
|
||||
windowModule = wm;
|
||||
}
|
||||
if (auto *im = dynamic_cast<InputModule *>(m)) {
|
||||
inputModule = im;
|
||||
}
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderModule = rm;
|
||||
}
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (m == inputModule || m == renderModule) {
|
||||
continue;
|
||||
}
|
||||
if (!m->isInitialized()) {
|
||||
m->setupModule();
|
||||
}
|
||||
bool Application::initModules(const AppConfig &config) {
|
||||
auto configModule = getModule<ConfigModule>();
|
||||
if (configModule) {
|
||||
configModule->setAppConfig(config);
|
||||
}
|
||||
|
||||
if (!windowModule || !windowModule->isInitialized()) {
|
||||
E2D_LOG_ERROR("Window module not initialized");
|
||||
if (!ModuleRegistry::instance().createAndInitAll()) {
|
||||
E2D_LOG_ERROR("Failed to initialize modules");
|
||||
return false;
|
||||
}
|
||||
|
||||
window_ = windowModule->getWindow();
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Window not created");
|
||||
return false;
|
||||
}
|
||||
|
||||
registerCoreServices();
|
||||
|
||||
if (inputModule) {
|
||||
inputModule->setWindow(window_);
|
||||
inputModule->setupModule();
|
||||
}
|
||||
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
renderModule->setupModule();
|
||||
}
|
||||
|
||||
if (!ServiceLocator::instance().initializeAll()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||
if (cameraService && window_) {
|
||||
window_->onResize([cameraService](int width, int height) {
|
||||
cameraService->updateViewport(width, height);
|
||||
cameraService->applyViewportAdapter();
|
||||
|
||||
auto sceneService =
|
||||
ServiceLocator::instance().getService<ISceneService>();
|
||||
if (sceneService) {
|
||||
auto currentScene = sceneService->getCurrentScene();
|
||||
if (currentScene) {
|
||||
currentScene->setViewportSize(static_cast<float>(width),
|
||||
static_cast<float>(height));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::initCoreModules() {
|
||||
bool hasConfig = false;
|
||||
bool hasPlatform = false;
|
||||
bool hasWindow = false;
|
||||
bool hasInput = false;
|
||||
bool hasRender = false;
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (dynamic_cast<ConfigModule *>(m))
|
||||
hasConfig = true;
|
||||
if (dynamic_cast<PlatformModule *>(m))
|
||||
hasPlatform = true;
|
||||
if (dynamic_cast<WindowModule *>(m))
|
||||
hasWindow = true;
|
||||
if (dynamic_cast<InputModule *>(m))
|
||||
hasInput = true;
|
||||
if (dynamic_cast<RenderModule *>(m))
|
||||
hasRender = true;
|
||||
}
|
||||
|
||||
if (!hasConfig) {
|
||||
static ConfigModule defaultConfigModule;
|
||||
use(defaultConfigModule);
|
||||
}
|
||||
|
||||
if (!hasPlatform) {
|
||||
static PlatformModule defaultPlatformModule;
|
||||
use(defaultPlatformModule);
|
||||
}
|
||||
|
||||
if (!hasWindow) {
|
||||
static WindowModule defaultWindowModule;
|
||||
use(defaultWindowModule);
|
||||
}
|
||||
|
||||
if (!hasInput) {
|
||||
static InputModule defaultInputModule;
|
||||
use(defaultInputModule);
|
||||
}
|
||||
|
||||
if (!hasRender) {
|
||||
static RenderModule defaultRenderModule;
|
||||
use(defaultRenderModule);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::setupAllModules() {
|
||||
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
|
||||
|
||||
for (auto *m : modules_) {
|
||||
if (!m->isInitialized()) {
|
||||
m->setupModule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::destroyAllModules() {
|
||||
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
|
||||
Module *m = *it;
|
||||
if (m->isInitialized()) {
|
||||
m->destroyModule();
|
||||
}
|
||||
}
|
||||
modules_.clear();
|
||||
}
|
||||
|
||||
void Application::registerCoreServices() {
|
||||
auto &locator = ServiceLocator::instance();
|
||||
|
||||
|
|
@ -338,6 +146,10 @@ void Application::registerCoreServices() {
|
|||
if (!locator.hasService<ITimerService>()) {
|
||||
locator.registerService<ITimerService>(makeShared<TimerService>());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::registerCameraService() {
|
||||
auto &locator = ServiceLocator::instance();
|
||||
|
||||
if (!locator.hasService<ICameraService>()) {
|
||||
auto cameraService = makeShared<CameraService>();
|
||||
|
|
@ -359,16 +171,20 @@ void Application::shutdown() {
|
|||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Application shutting down...");
|
||||
|
||||
VRAMMgr::get().printStats();
|
||||
|
||||
// 先销毁模块(它们可能需要服务和窗口)
|
||||
ModuleRegistry::instance().destroyAll();
|
||||
|
||||
// 最后清除服务
|
||||
ServiceLocator::instance().clear();
|
||||
|
||||
window_ = nullptr;
|
||||
|
||||
destroyAllModules();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
||||
E2D_LOG_INFO("Application shutdown complete");
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
|
|
@ -387,6 +203,9 @@ void Application::run() {
|
|||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
|
||||
// 主循环结束后自动清理
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void Application::quit() {
|
||||
|
|
@ -437,14 +256,7 @@ void Application::mainLoop() {
|
|||
|
||||
render();
|
||||
|
||||
RenderModule *renderModule = nullptr;
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderModule = rm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
(void)renderModule;
|
||||
|
||||
ConfigManager::instance().update(deltaTime_);
|
||||
|
|
@ -453,19 +265,18 @@ void Application::mainLoop() {
|
|||
void Application::update() {
|
||||
ServiceLocator::instance().updateAll(deltaTime_);
|
||||
|
||||
auto ctx = UpdateContext(modules_, deltaTime_);
|
||||
if (!modules_.empty()) {
|
||||
auto modules = ModuleRegistry::instance().getAllModules();
|
||||
auto ctx = UpdateContext(modules, deltaTime_);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::render() {
|
||||
RenderBackend *renderer = nullptr;
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
renderer = rm->getRenderer();
|
||||
break;
|
||||
}
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule) {
|
||||
renderer = renderModule->getRenderer();
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
|
|
@ -484,16 +295,18 @@ void Application::render() {
|
|||
renderer->setViewport(0, 0, window_->width(), window_->height());
|
||||
}
|
||||
|
||||
auto modules = ModuleRegistry::instance().getAllModules();
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules_, RenderContext::Phase::Before);
|
||||
if (!modules_.empty()) {
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::Before);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules_, RenderContext::Phase::On);
|
||||
if (!modules_.empty()) {
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::On);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
|
|
@ -504,8 +317,8 @@ void Application::render() {
|
|||
}
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules_, RenderContext::Phase::After);
|
||||
if (!modules_.empty()) {
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::After);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
|
|
@ -514,12 +327,9 @@ void Application::render() {
|
|||
IInput &Application::input() { return *window_->input(); }
|
||||
|
||||
RenderBackend &Application::renderer() {
|
||||
for (auto *m : modules_) {
|
||||
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
|
||||
if (rm->getRenderer()) {
|
||||
return *rm->getRenderer();
|
||||
}
|
||||
}
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule && renderModule->getRenderer()) {
|
||||
return *renderModule->getRenderer();
|
||||
}
|
||||
static RenderBackend *dummy = nullptr;
|
||||
if (!dummy) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ AppConfig AppConfig::createDefault() {
|
|||
config.appVersion = "1.0.0";
|
||||
config.organization = "";
|
||||
config.configFile = "config.json";
|
||||
config.targetPlatform = PlatformType::Auto;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -50,9 +49,6 @@ void AppConfig::merge(const AppConfig& other) {
|
|||
if (other.configFile != "config.json") {
|
||||
configFile = other.configFile;
|
||||
}
|
||||
if (other.targetPlatform != PlatformType::Auto) {
|
||||
targetPlatform = other.targetPlatform;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Merged app config");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 去除字符串首尾空白字符
|
||||
* @param str 输入字符串
|
||||
* @return 去除空白后的字符串
|
||||
*/
|
||||
static std::string trim(const std::string &str) {
|
||||
size_t start = 0;
|
||||
while (start < str.length() &&
|
||||
|
|
@ -28,11 +23,6 @@ static std::string trim(const std::string &str) {
|
|||
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) {
|
||||
|
|
@ -41,17 +31,8 @@ static std::string toLower(const std::string &str) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
|
@ -97,14 +78,6 @@ static ConfigLoadResult parseIniContent(const std::string &content,
|
|||
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 §ion,
|
||||
const std::string &key,
|
||||
const std::string &defaultValue = "") {
|
||||
|
|
@ -119,13 +92,6 @@ static std::string getIniValue(const IniData &data, const std::string §ion,
|
|||
return keyIt->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查 INI 值是否存在
|
||||
* @param data INI 数据
|
||||
* @param section 节名
|
||||
* @param key 键名
|
||||
* @return 是否存在
|
||||
*/
|
||||
static bool hasIniValue(const IniData &data, const std::string §ion,
|
||||
const std::string &key) {
|
||||
auto sectionIt = data.find(section);
|
||||
|
|
@ -192,14 +158,6 @@ ConfigLoadResult IniConfigLoader::loadFromString(const std::string &content,
|
|||
if (hasIniValue(data, "app", "configFile")) {
|
||||
config.configFile = getIniValue(data, "app", "configFile");
|
||||
}
|
||||
if (hasIniValue(data, "app", "targetPlatform")) {
|
||||
int value;
|
||||
auto res = parseInt(getIniValue(data, "app", "targetPlatform"), value,
|
||||
"app.targetPlatform");
|
||||
if (res.isOk()) {
|
||||
config.targetPlatform = static_cast<PlatformType>(value);
|
||||
}
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("INI 应用配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
|
|
@ -213,7 +171,6 @@ std::string IniConfigLoader::saveToString(const AppConfig &config) {
|
|||
oss << "version=" << config.appVersion << "\n";
|
||||
oss << "organization=" << config.organization << "\n";
|
||||
oss << "configFile=" << config.configFile << "\n";
|
||||
oss << "targetPlatform=" << static_cast<int>(config.targetPlatform) << "\n";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
|
@ -329,4 +286,4 @@ ConfigLoadResult IniConfigLoader::parseBool(const std::string &value,
|
|||
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,10 +71,6 @@ ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, Ap
|
|||
config.configFile = root["configFile"].get<std::string>();
|
||||
}
|
||||
|
||||
if (root.contains("targetPlatform") && root["targetPlatform"].is_number_integer()) {
|
||||
config.targetPlatform = static_cast<PlatformType>(root["targetPlatform"].get<int>());
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("JSON 应用配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
|
@ -86,7 +82,6 @@ std::string JsonConfigLoader::saveToString(const AppConfig& config) {
|
|||
root["appVersion"] = config.appVersion;
|
||||
root["organization"] = config.organization;
|
||||
root["configFile"] = config.configFile;
|
||||
root["targetPlatform"] = static_cast<int>(config.targetPlatform);
|
||||
|
||||
return root.dump(4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#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>
|
||||
|
||||
|
|
@ -36,12 +34,6 @@ bool ConfigManager::initialize(const std::string& configPath) {
|
|||
|
||||
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");
|
||||
|
|
@ -49,13 +41,11 @@ bool ConfigManager::initialize(const std::string& configPath) {
|
|||
}
|
||||
|
||||
m_appConfig = AppConfig::createDefault();
|
||||
m_appConfig.targetPlatform = PlatformDetector::detect();
|
||||
|
||||
m_initialized = true;
|
||||
m_modified = false;
|
||||
|
||||
E2D_LOG_INFO("ConfigManager initialized for platform: {}",
|
||||
m_platformConfig->platformName());
|
||||
E2D_LOG_INFO("ConfigManager initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +63,6 @@ void ConfigManager::shutdown() {
|
|||
m_changeCallbacks.clear();
|
||||
m_rawValues.clear();
|
||||
m_loader.reset();
|
||||
m_platformConfig.reset();
|
||||
|
||||
m_initialized = false;
|
||||
m_modified = false;
|
||||
|
|
@ -217,14 +206,6 @@ void ConfigManager::setAppConfig(const AppConfig& config) {
|
|||
E2D_LOG_INFO("App config updated");
|
||||
}
|
||||
|
||||
PlatformConfig* ConfigManager::platformConfig() {
|
||||
return m_platformConfig.get();
|
||||
}
|
||||
|
||||
const PlatformConfig* ConfigManager::platformConfig() const {
|
||||
return m_platformConfig.get();
|
||||
}
|
||||
|
||||
int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/platform_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_; }
|
||||
|
||||
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_; }
|
||||
|
||||
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_; }
|
||||
|
||||
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 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_; }
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,678 +0,0 @@
|
|||
#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 获取平台特定的资源路径(Shader、纹理等)
|
||||
* Switch平台使用romfs,其他平台使用相对路径
|
||||
* @param appName 应用名称
|
||||
* @return 资源目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getResourcePath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
(void)appName;
|
||||
return "romfs:/";
|
||||
#else
|
||||
(void)appName;
|
||||
return "./resources/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader路径
|
||||
* @param appName 应用名称
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getShaderPath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
(void)appName;
|
||||
return "romfs:/shaders/";
|
||||
#else
|
||||
(void)appName;
|
||||
return "./shaders/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader缓存路径
|
||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
||||
* @param appName 应用名称
|
||||
* @return Shader缓存目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getShaderCachePath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
std::string name = appName.empty() ? "extra2d" : appName;
|
||||
return "sdmc:/cache/" + name + "/shaders/";
|
||||
#else
|
||||
return getCachePath(appName.empty() ? "extra2d" : appName) + "/shaders/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
||||
* @return 使用romfs返回true
|
||||
*/
|
||||
bool PlatformDetector::usesRomfs() {
|
||||
#ifdef __SWITCH__
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否支持热重载
|
||||
* Switch平台不支持热重载(romfs只读)
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
bool PlatformDetector::supportsHotReload() {
|
||||
#ifdef __SWITCH__
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#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();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getLinuxDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getMacOSDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getSwitchDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ModuleRegistry &ModuleRegistry::instance() {
|
||||
static ModuleRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ModuleRegistry::registerMeta(ModuleMetaBase *meta) {
|
||||
if (!meta)
|
||||
return;
|
||||
|
||||
for (const auto *existing : metas_) {
|
||||
if (existing && std::string(existing->getName()) == meta->getName()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
metas_.push_back(meta);
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> ModuleRegistry::getAllMetas() const {
|
||||
return metas_;
|
||||
}
|
||||
|
||||
ModuleMetaBase *ModuleRegistry::getMeta(const char *name) const {
|
||||
if (!name)
|
||||
return nullptr;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (meta && std::string(meta->getName()) == name) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Module *ModuleRegistry::getModule(const char *name) const {
|
||||
auto it = instanceMap_.find(name ? name : "");
|
||||
return it != instanceMap_.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::hasModule(const char *name) const {
|
||||
return instanceMap_.find(name ? name : "") != instanceMap_.end();
|
||||
}
|
||||
|
||||
std::vector<Module *> ModuleRegistry::getAllModules() const {
|
||||
std::vector<Module *> result;
|
||||
result.reserve(instances_.size());
|
||||
for (const auto &inst : instances_) {
|
||||
result.push_back(inst.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::hasCircularDependency() const {
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> graph;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
for (const auto *dep : meta->getDependencies()) {
|
||||
graph[name].insert(dep);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> visited;
|
||||
std::unordered_set<std::string> recStack;
|
||||
|
||||
std::function<bool(const std::string &)> hasCycle =
|
||||
[&](const std::string &node) -> bool {
|
||||
visited.insert(node);
|
||||
recStack.insert(node);
|
||||
|
||||
for (const auto &neighbor : graph[node]) {
|
||||
if (visited.find(neighbor) == visited.end()) {
|
||||
if (hasCycle(neighbor))
|
||||
return true;
|
||||
} else if (recStack.find(neighbor) != recStack.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
recStack.erase(node);
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
if (visited.find(name) == visited.end()) {
|
||||
if (hasCycle(name))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> ModuleRegistry::sortByDependency() {
|
||||
if (hasCircularDependency()) {
|
||||
E2D_LOG_ERROR("Circular dependency detected in modules!");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int> inDegree;
|
||||
std::unordered_map<std::string, std::vector<std::string>> graph;
|
||||
std::unordered_map<std::string, ModuleMetaBase *> nameToMeta;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
nameToMeta[name] = meta;
|
||||
if (inDegree.find(name) == inDegree.end()) {
|
||||
inDegree[name] = 0;
|
||||
}
|
||||
|
||||
for (const auto *dep : meta->getDependencies()) {
|
||||
std::string depName = dep;
|
||||
graph[depName].push_back(name);
|
||||
inDegree[name]++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> sorted;
|
||||
std::vector<std::string> currentLevel;
|
||||
|
||||
for (const auto &[name, degree] : inDegree) {
|
||||
if (degree == 0) {
|
||||
currentLevel.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
while (!currentLevel.empty()) {
|
||||
std::sort(currentLevel.begin(), currentLevel.end(),
|
||||
[&nameToMeta](const std::string &a, const std::string &b) {
|
||||
auto *metaA = nameToMeta[a];
|
||||
auto *metaB = nameToMeta[b];
|
||||
if (!metaA || !metaB)
|
||||
return a < b;
|
||||
return metaA->getPriority() < metaB->getPriority();
|
||||
});
|
||||
|
||||
std::vector<std::string> nextLevel;
|
||||
|
||||
for (const auto &name : currentLevel) {
|
||||
if (nameToMeta.find(name) != nameToMeta.end()) {
|
||||
sorted.push_back(nameToMeta[name]);
|
||||
}
|
||||
|
||||
for (const auto &neighbor : graph[name]) {
|
||||
inDegree[neighbor]--;
|
||||
if (inDegree[neighbor] == 0) {
|
||||
nextLevel.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentLevel = std::move(nextLevel);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::createAndInitAll() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 动态库中模块已通过 E2D_MODULE 宏自动注册
|
||||
// 无需手动调用 registerCoreModules()
|
||||
|
||||
E2D_LOG_INFO("ModuleRegistry: {} modules registered", metas_.size());
|
||||
for (auto *meta : metas_) {
|
||||
if (meta) {
|
||||
E2D_LOG_INFO(" - {} (priority: {})", meta->getName(),
|
||||
meta->getPriority());
|
||||
}
|
||||
}
|
||||
|
||||
auto sorted = sortByDependency();
|
||||
if (sorted.empty() && !metas_.empty()) {
|
||||
E2D_LOG_ERROR(
|
||||
"Failed to sort modules by dependency - possible circular dependency");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto *meta : sorted) {
|
||||
if (!meta)
|
||||
continue;
|
||||
|
||||
Module *instance = meta->create();
|
||||
if (!instance) {
|
||||
E2D_LOG_ERROR("Failed to create module: {}", meta->getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
PropertyBinderImpl binder;
|
||||
meta->bindProperties(instance, binder);
|
||||
|
||||
instance->setupModule();
|
||||
|
||||
instances_.emplace_back(instance);
|
||||
instanceMap_[meta->getName()] = instance;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModuleRegistry::destroyAll() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Destroying {} modules in reverse order", instances_.size());
|
||||
|
||||
// 先销毁所有模块(逆序)
|
||||
for (auto it = instances_.rbegin(); it != instances_.rend(); ++it) {
|
||||
if (*it) {
|
||||
E2D_LOG_INFO("Destroying module: {}", (*it)->getName());
|
||||
(*it)->destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
// 然后清理实例
|
||||
instances_.clear();
|
||||
instanceMap_.clear();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -633,7 +633,7 @@ void GLRenderer::resetStats() { stats_ = Stats{}; }
|
|||
*/
|
||||
void GLRenderer::initShapeRendering() {
|
||||
// 从ShaderManager获取形状着色器
|
||||
shapeShader_ = ShaderManager::getInstance().getBuiltin("builtin_shape");
|
||||
shapeShader_ = ShaderManager::getInstance().getBuiltin("shape");
|
||||
if (!shapeShader_) {
|
||||
E2D_LOG_WARN("Failed to get builtin shape shader, loading from manager");
|
||||
if (!ShaderManager::getInstance().isInitialized()) {
|
||||
|
|
|
|||
|
|
@ -3,42 +3,30 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return Shader管理器实例引用
|
||||
*/
|
||||
ShaderManager& ShaderManager::getInstance() {
|
||||
static ShaderManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用平台默认路径初始化Shader系统
|
||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @param appName 应用名称(用于缓存目录)
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
|
||||
std::string shaderDir = PlatformDetector::getShaderPath(appName);
|
||||
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
|
||||
std::string shaderDir;
|
||||
std::string cacheDir;
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
E2D_LOG_INFO("Platform: {} (HotReload: {})",
|
||||
PlatformDetector::platformName(),
|
||||
hotReloadSupported_ ? "supported" : "not supported");
|
||||
#ifdef __SWITCH__
|
||||
shaderDir = "romfs:/shaders/";
|
||||
cacheDir = "sdmc:/config/" + appName + "/shader_cache/";
|
||||
hotReloadSupported_ = false;
|
||||
E2D_LOG_INFO("Platform: Switch (HotReload: not supported)");
|
||||
#else
|
||||
shaderDir = "shaders/";
|
||||
cacheDir = "shader_cache/";
|
||||
hotReloadSupported_ = true;
|
||||
E2D_LOG_INFO("Platform: Desktop (HotReload: supported)");
|
||||
#endif
|
||||
|
||||
return init(shaderDir, cacheDir, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化Shader系统
|
||||
* @param shaderDir Shader文件目录
|
||||
* @param cacheDir 缓存目录
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(const std::string& shaderDir,
|
||||
const std::string& cacheDir,
|
||||
Ptr<IShaderFactory> factory) {
|
||||
|
|
@ -56,13 +44,13 @@ bool ShaderManager::init(const std::string& shaderDir,
|
|||
cacheDir_ = cacheDir;
|
||||
factory_ = factory;
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
hotReloadSupported_ = false;
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache on Switch");
|
||||
}
|
||||
#else
|
||||
hotReloadSupported_ = true;
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
|
||||
}
|
||||
|
|
@ -78,16 +66,10 @@ bool ShaderManager::init(const std::string& shaderDir,
|
|||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("ShaderManager initialized");
|
||||
E2D_LOG_INFO(" Shader directory: {}", shaderDir_);
|
||||
E2D_LOG_INFO(" Cache directory: {}", cacheDir_);
|
||||
E2D_LOG_INFO(" Hot reload: {}", hotReloadSupported_ ? "supported" : "not supported");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭Shader系统
|
||||
*/
|
||||
void ShaderManager::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
|
|
@ -105,175 +87,57 @@ void ShaderManager::shutdown() {
|
|||
E2D_LOG_INFO("ShaderManager shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
auto vertSource = loader_.readFile(vertPath);
|
||||
auto fragSource = loader_.readFile(fragPath);
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, vertPath, fragPath);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load shader files: {} - {}", vertPath, fragPath);
|
||||
if (vertSource.empty() || fragSource.empty()) {
|
||||
E2D_LOG_ERROR("Failed to load shader sources: {} / {}", vertPath, fragPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
E2D_LOG_DEBUG("No valid cache found, compiling shader from source: {}", name);
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
return loadFromSource(name, vertSource, fragSource);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
E2D_LOG_DEBUG("Got shader binary, size: {} bytes", binary.size());
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
} else {
|
||||
E2D_LOG_WARN("Failed to get shader binary for: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {vertPath, fragPath};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
|
||||
info.metadata.name = name;
|
||||
info.metadata.vertPath = vertPath;
|
||||
info.metadata.fragPath = fragPath;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCombinedFile(const std::string& path) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShaderMetadata metadata = loader_.getMetadata(path);
|
||||
std::string name = metadata.name.empty() ? path : metadata.name;
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(path);
|
||||
auto result = loader_.loadFromCombinedFile(path);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load combined shader file: {}", path);
|
||||
E2D_LOG_ERROR("Failed to load combined shader: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
E2D_LOG_DEBUG("No valid cache found, compiling shader from source: {}", name);
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
std::string name = path.substr(path.find_last_of("/\\") + 1);
|
||||
return loadFromSource(name, result.vertSource, result.fragSource);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
E2D_LOG_DEBUG("Got shader binary, size: {} bytes", binary.size());
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
} else {
|
||||
E2D_LOG_WARN("Failed to get shader binary for: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {path};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
info.metadata = metadata;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from combined file: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
if (has(name)) {
|
||||
E2D_LOG_WARN("Shader already exists: {}", name);
|
||||
return get(name);
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
auto shader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
E2D_LOG_ERROR("Failed to create shader: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -281,19 +145,13 @@ Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
|
|||
info.shader = shader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
info.metadata.name = name;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from source: {}", name);
|
||||
E2D_LOG_INFO("Shader loaded: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取已加载的Shader
|
||||
* @param name Shader名称
|
||||
* @return Shader实例,不存在返回nullptr
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::get(const std::string& name) const {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
|
|
@ -302,232 +160,118 @@ Ptr<IShader> ShaderManager::get(const std::string& name) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
* @param name Shader名称
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::has(const std::string& name) const {
|
||||
return shaders_.find(name) != shaders_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除Shader
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void ShaderManager::remove(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
ShaderHotReloader::getInstance().unwatch(name);
|
||||
shaders_.erase(it);
|
||||
E2D_LOG_DEBUG("Shader removed: {}", name);
|
||||
E2D_LOG_INFO("Shader removed: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有Shader
|
||||
*/
|
||||
void ShaderManager::clear() {
|
||||
if (hotReloadSupported_) {
|
||||
for (const auto& pair : shaders_) {
|
||||
ShaderHotReloader::getInstance().unwatch(pair.first);
|
||||
}
|
||||
}
|
||||
shaders_.clear();
|
||||
E2D_LOG_DEBUG("All shaders cleared");
|
||||
E2D_LOG_INFO("All shaders cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册重载回调
|
||||
* @param name Shader名称
|
||||
* @param callback 重载回调函数
|
||||
*/
|
||||
void ShaderManager::setReloadCallback(const std::string& name, ShaderReloadCallback callback) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
it->second.reloadCallback = callback;
|
||||
it->second.reloadCallback = std::move(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void ShaderManager::setHotReloadEnabled(bool enabled) {
|
||||
if (!hotReloadSupported_) {
|
||||
if (!hotReloadSupported_ && enabled) {
|
||||
E2D_LOG_WARN("Hot reload not supported on this platform");
|
||||
return;
|
||||
}
|
||||
hotReloadEnabled_ = enabled;
|
||||
ShaderHotReloader::getInstance().setEnabled(enabled);
|
||||
E2D_LOG_INFO("Hot reload {}", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::isHotReloadEnabled() const {
|
||||
return hotReloadEnabled_ && hotReloadSupported_;
|
||||
return hotReloadEnabled_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新热重载系统(主循环调用)
|
||||
*/
|
||||
void ShaderManager::update() {
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
if (!hotReloadEnabled_ || !hotReloadSupported_) {
|
||||
return;
|
||||
}
|
||||
ShaderHotReloader::getInstance().update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动重载Shader
|
||||
* @param name Shader名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::reload(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it == shaders_.end()) {
|
||||
E2D_LOG_WARN("Shader not found for reload: {}", name);
|
||||
E2D_LOG_ERROR("Shader not found: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderInfo& info = it->second;
|
||||
|
||||
std::string vertSource = info.vertSource;
|
||||
std::string fragSource = info.fragSource;
|
||||
|
||||
if (!info.metadata.vertPath.empty() && !info.metadata.fragPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, info.metadata.vertPath, info.metadata.fragPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
} else if (!info.metadata.combinedPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(info.metadata.combinedPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<IShader> newShader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!newShader) {
|
||||
auto shader = factory_->createFromSource(name, it->second.vertSource, it->second.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.shader = newShader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
it->second.shader = shader;
|
||||
|
||||
if (info.reloadCallback) {
|
||||
info.reloadCallback(newShader);
|
||||
if (it->second.reloadCallback) {
|
||||
it->second.reloadCallback(shader);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded: {}", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置Shader
|
||||
* @param name 内置Shader名称
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||
Ptr<IShader> shader = get(name);
|
||||
if (shader) {
|
||||
return shader;
|
||||
return get("builtin_" + name);
|
||||
}
|
||||
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
return loadFromCombinedFile(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载所有内置Shader
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::loadBuiltinShaders() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return false;
|
||||
}
|
||||
E2D_LOG_INFO("Loading builtin shaders...");
|
||||
|
||||
bool allSuccess = true;
|
||||
|
||||
const char* builtinNames[] = {
|
||||
const std::vector<std::string> builtinShaders = {
|
||||
"sprite",
|
||||
"particle",
|
||||
"shape",
|
||||
"postprocess",
|
||||
"font"
|
||||
"font",
|
||||
"particle",
|
||||
"postprocess"
|
||||
};
|
||||
|
||||
for (const char* name : builtinNames) {
|
||||
int loaded = 0;
|
||||
for (const auto& name : builtinShaders) {
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
std::string shaderName = std::string("builtin_") + name;
|
||||
|
||||
if (!loadFromCombinedFile(path)) {
|
||||
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
|
||||
allSuccess = false;
|
||||
} else {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
shaders_[shaderName] = it->second;
|
||||
shaders_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
E2D_LOG_INFO("All builtin shaders loaded");
|
||||
}
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存加载Shader
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCache(const std::string& name,
|
||||
const std::string& sourceHash,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!ShaderCache::getInstance().isInitialized()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ShaderCache::getInstance().hasValidCache(name, sourceHash)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<ShaderCacheEntry> entry = ShaderCache::getInstance().loadCache(name);
|
||||
if (!entry || entry->binary.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromBinary(name, entry->binary);
|
||||
if (ShaderLoader::fileExists(path)) {
|
||||
auto result = loader_.loadFromCombinedFile(path);
|
||||
if (result.success) {
|
||||
auto shader = factory_->createFromSource("builtin_" + name, result.vertSource, result.fragSource);
|
||||
if (shader) {
|
||||
E2D_LOG_DEBUG("Shader loaded from cache: {}", name);
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
shaders_["builtin_" + name] = std::move(info);
|
||||
loaded++;
|
||||
E2D_LOG_DEBUG("Loaded builtin shader: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shader;
|
||||
E2D_LOG_INFO("Builtin shaders loaded: {}/{}", loaded, builtinShaders.size());
|
||||
return loaded > 0;
|
||||
}
|
||||
|
||||
void ShaderManager::createBuiltinShaderSources() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化事件
|
||||
* @param shaderName Shader名称
|
||||
* @param event 文件变化事件
|
||||
*/
|
||||
void ShaderManager::handleFileChange(const std::string& shaderName, const FileChangeEvent& event) {
|
||||
E2D_LOG_DEBUG("Shader file changed: {} -> {}", shaderName, event.filepath);
|
||||
if (hotReloadEnabled_) {
|
||||
reload(shaderName);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -51,3 +52,5 @@ void ConfigModule::destroyModule() {
|
|||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(ConfigModule, 0)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
|
@ -24,8 +25,18 @@ void InputModule::setupModule() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 如果 window 还没设置,延迟初始化
|
||||
if (!window_) {
|
||||
E2D_LOG_INFO("Input module waiting for window");
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
initializeWithWindow();
|
||||
}
|
||||
|
||||
void InputModule::initializeWithWindow() {
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Window not set - cannot initialize input module");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +60,24 @@ void InputModule::setupModule() {
|
|||
}
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Input module initialized");
|
||||
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);
|
||||
E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity);
|
||||
E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
void InputModule::setWindow(IWindow* window) {
|
||||
if (window_ == window) {
|
||||
return;
|
||||
}
|
||||
window_ = window;
|
||||
|
||||
// 如果模块已初始化但还没初始化输入,现在初始化
|
||||
if (isInitialized() && window_ && !input_) {
|
||||
initializeWithWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void InputModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
|
|
@ -78,3 +100,5 @@ void InputModule::onUpdate(UpdateContext& ctx) {
|
|||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(InputModule, 30, "WindowModule")
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
#include <extra2d/modules/logger_module.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
LoggerModule::LoggerModule()
|
||||
: Module()
|
||||
, logLevel_(LogLevel::Info)
|
||||
, consoleOutput_(true)
|
||||
, fileOutput_(false) {
|
||||
}
|
||||
|
||||
LoggerModule::~LoggerModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void LoggerModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::init();
|
||||
|
||||
Logger::setLevel(logLevel_);
|
||||
Logger::setConsoleOutput(consoleOutput_);
|
||||
|
||||
if (fileOutput_ && !logFilePath_.empty()) {
|
||||
Logger::setFileOutput(logFilePath_);
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Logger module initialized");
|
||||
}
|
||||
|
||||
void LoggerModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Logger module shutting down");
|
||||
Logger::shutdown();
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
#include <extra2d/modules/platform_module.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
PlatformModule::PlatformModule()
|
||||
: Module()
|
||||
, targetPlatform_(PlatformType::Auto)
|
||||
, resolvedPlatform_(PlatformType::Windows) {
|
||||
}
|
||||
|
||||
PlatformModule::~PlatformModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolvedPlatform_ = targetPlatform_;
|
||||
if (resolvedPlatform_ == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
resolvedPlatform_ = PlatformType::Switch;
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
resolvedPlatform_ = PlatformType::Windows;
|
||||
#elif defined(__linux__)
|
||||
resolvedPlatform_ = PlatformType::Linux;
|
||||
#elif defined(__APPLE__)
|
||||
resolvedPlatform_ = PlatformType::macOS;
|
||||
#else
|
||||
resolvedPlatform_ = PlatformType::Windows;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
platformConfig_ = createPlatformConfig(resolvedPlatform_);
|
||||
if (!platformConfig_) {
|
||||
E2D_LOG_ERROR("Failed to create platform config");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedPlatform_ == PlatformType::Switch) {
|
||||
if (!initSwitch()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Platform module initialized ({})", getPlatformTypeName(resolvedPlatform_));
|
||||
}
|
||||
|
||||
void PlatformModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Platform module shutting down");
|
||||
|
||||
if (resolvedPlatform_ == PlatformType::Switch) {
|
||||
shutdownSwitch();
|
||||
}
|
||||
|
||||
platformConfig_.reset();
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
bool PlatformModule::initSwitch() {
|
||||
#ifdef __SWITCH__
|
||||
Result rc;
|
||||
rc = romfsInit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
E2D_LOG_INFO("RomFS initialized successfully");
|
||||
} else {
|
||||
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
|
||||
}
|
||||
|
||||
rc = socketInitializeDefault();
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformModule::shutdownSwitch() {
|
||||
#ifdef __SWITCH__
|
||||
romfsExit();
|
||||
socketExit();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
|
|
@ -29,8 +30,18 @@ void RenderModule::setupModule() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 如果 window 还没设置,延迟初始化
|
||||
if (!window_) {
|
||||
E2D_LOG_INFO("Render module waiting for window");
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
initializeWithWindow();
|
||||
}
|
||||
|
||||
void RenderModule::initializeWithWindow() {
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Render module requires window to be set");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -55,10 +66,21 @@ void RenderModule::setupModule() {
|
|||
return;
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Render module initialized");
|
||||
}
|
||||
|
||||
void RenderModule::setWindow(IWindow* window) {
|
||||
if (window_ == window) {
|
||||
return;
|
||||
}
|
||||
window_ = window;
|
||||
|
||||
// 如果模块已初始化但还没初始化渲染器,现在初始化
|
||||
if (isInitialized() && window_ && !renderer_) {
|
||||
initializeWithWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
|
|
@ -96,3 +118,5 @@ void RenderModule::afterRender(RenderContext& ctx) {
|
|||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(RenderModule, 40, "WindowModule")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <SDL.h>
|
||||
|
||||
|
|
@ -119,3 +120,5 @@ bool WindowModule::createWindow(const WindowConfigData& config) {
|
|||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(WindowModule, 20, "ConfigModule")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -12,18 +12,16 @@ void BackendFactory::reg(const std::string& name, WindowFn win, InputFn in) {
|
|||
}
|
||||
|
||||
UniquePtr<IWindow> BackendFactory::createWindow(const std::string& name) {
|
||||
auto& reg = registry();
|
||||
auto it = reg.find(name);
|
||||
if (it != reg.end() && it->second.windowFn) {
|
||||
auto it = registry().find(name);
|
||||
if (it != registry().end() && it->second.windowFn) {
|
||||
return it->second.windowFn();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniquePtr<IInput> BackendFactory::createInput(const std::string& name) {
|
||||
auto& reg = registry();
|
||||
auto it = reg.find(name);
|
||||
if (it != reg.end() && it->second.inputFn) {
|
||||
auto it = registry().find(name);
|
||||
if (it != registry().end() && it->second.inputFn) {
|
||||
return it->second.inputFn();
|
||||
}
|
||||
return nullptr;
|
||||
|
|
@ -41,4 +39,4 @@ bool BackendFactory::has(const std::string& name) {
|
|||
return registry().find(name) != registry().end();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include "sdl2_window.h"
|
||||
#include "sdl2_input.h"
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
SDL2Input::SDL2Input() {
|
||||
SDL2Input::SDL2Input()
|
||||
: initialized_(false) {
|
||||
keyCurrent_.fill(false);
|
||||
keyPrevious_.fill(false);
|
||||
mouseCurrent_.fill(false);
|
||||
|
|
@ -18,6 +19,8 @@ SDL2Input::~SDL2Input() {
|
|||
}
|
||||
|
||||
void SDL2Input::init() {
|
||||
if (initialized_) return;
|
||||
|
||||
E2D_LOG_INFO("SDL2Input initialized");
|
||||
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
|
|
@ -25,10 +28,14 @@ void SDL2Input::init() {
|
|||
}
|
||||
|
||||
openGamepad();
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void SDL2Input::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
closeGamepad();
|
||||
initialized_ = false;
|
||||
E2D_LOG_INFO("SDL2Input shutdown");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ private:
|
|||
float rightTrigger_ = 0.0f;
|
||||
float deadzone_ = 0.15f;
|
||||
|
||||
bool initialized_ = false;
|
||||
EventCallback eventCallback_;
|
||||
};
|
||||
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -134,6 +134,30 @@ flowchart TB
|
|||
| Input | 输入处理 | 30 |
|
||||
| Render | 渲染系统 | 40 |
|
||||
|
||||
### 模块自发现原理
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 编译时
|
||||
participant R as 运行时
|
||||
participant MR as ModuleRegistry
|
||||
participant App as Application
|
||||
|
||||
Note over C: E2D_MODULE 宏展开
|
||||
C->>C: 创建静态变量 ModuleAutoRegister<T>
|
||||
C->>C: 添加 __attribute__((used)) 防止优化
|
||||
|
||||
Note over R: 程序启动
|
||||
R->>MR: 静态初始化阶段
|
||||
MR->>MR: 构造函数自动调用 registerModule()
|
||||
MR->>MR: 模块信息存入注册表
|
||||
|
||||
Note over App: Application::init()
|
||||
App->>MR: getModules()
|
||||
MR->>App: 返回按优先级排序的模块列表
|
||||
App->>App: 按顺序调用 setupModule()
|
||||
```
|
||||
|
||||
### 服务系统
|
||||
|
||||
| 服务 | 职责 | 优先级 |
|
||||
|
|
|
|||
|
|
@ -4,33 +4,267 @@
|
|||
|
||||
Extra2D 采用模块化架构设计(参考 Kiwano),所有核心功能通过模块系统和服务系统管理。系统提供:
|
||||
|
||||
- **自动发现注册**:模块定义即注册,无需手动调用
|
||||
- **统一的生命周期管理**:初始化、更新、渲染、关闭
|
||||
- **优先级排序**:确保模块按正确顺序初始化
|
||||
- **显式注册**:通过 `Application::use()` 注册模块
|
||||
- **Context 模式**:使用上下文对象遍历模块链
|
||||
- **依赖注入**:通过服务定位器解耦模块间依赖
|
||||
|
||||
## 架构图
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Application["Application<br/>(协调模块和服务)"]
|
||||
|
||||
Application --> ModuleRegistry
|
||||
Application --> ServiceLocator
|
||||
|
||||
ModuleRegistry["ModuleRegistry<br/>(模块注册器)"]
|
||||
ServiceLocator["ServiceLocator<br/>(服务定位器)"]
|
||||
|
||||
ModuleRegistry --> ConfigModule["ConfigModule"]
|
||||
ModuleRegistry --> WindowModule["WindowModule"]
|
||||
|
||||
ServiceLocator --> SceneService["SceneService"]
|
||||
ServiceLocator --> TimerService["TimerService"]
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Application │
|
||||
│ (协调模块和服务,通过服务定位器获取依赖) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
||||
│ Modules │ │ ServiceLocator │
|
||||
│ (模块列表,按优先级排序) │ │ (服务定位器,管理运行时服务) │
|
||||
└─────────────────────────────┘ └─────────────────────────────┘
|
||||
│ │
|
||||
┌─────┴─────┐ ┌───────┴───────┐
|
||||
▼ ▼ ▼ ▼
|
||||
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ Config │ │ Window │ │ Scene │ │ Timer │
|
||||
│ Module │ │ Module │ │ Service │ │ Service │
|
||||
└───────────┘ └───────────┘ └───────────┘ └───────────┘
|
||||
|
||||
---
|
||||
|
||||
## 模块自动注册
|
||||
|
||||
### E2D_MODULE 宏
|
||||
|
||||
模块通过 `E2D_MODULE` 宏自动注册,无需手动调用任何注册函数:
|
||||
|
||||
```cpp
|
||||
// config_module.cpp
|
||||
#include "config_module.h"
|
||||
|
||||
namespace extra2d {
|
||||
// 模块实现...
|
||||
}
|
||||
|
||||
// 在文件末尾,namespace 外部
|
||||
E2D_MODULE(ConfigModule, 0) // 名称, 优先级
|
||||
```
|
||||
|
||||
### 带依赖的模块
|
||||
|
||||
```cpp
|
||||
// window_module.cpp
|
||||
E2D_MODULE(WindowModule, 20, "ConfigModule") // 依赖 ConfigModule
|
||||
|
||||
// render_module.cpp
|
||||
E2D_MODULE(RenderModule, 40, "WindowModule", "ConfigModule") // 多个依赖
|
||||
```
|
||||
|
||||
### 带属性的模块
|
||||
|
||||
```cpp
|
||||
// render_module.cpp
|
||||
E2D_MODULE_BEGIN(RenderModule)
|
||||
E2D_PRIORITY(40)
|
||||
E2D_DEPENDENCIES("WindowModule")
|
||||
E2D_PROPERTY(vsync, bool)
|
||||
E2D_PROPERTY(targetFPS, int)
|
||||
E2D_MODULE_END()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 链接方式详解
|
||||
|
||||
### 静态链接 vs 动态链接
|
||||
|
||||
| 特性 | 静态链接 | 动态链接 |
|
||||
|------|---------|---------|
|
||||
| **引擎库** | `.a` / `.lib` | `.dll` / `.so` |
|
||||
| **模块注册** | 需要 `--whole-archive` 或直接编译 | 自动注册 |
|
||||
| **自定义模块** | 直接编译到可执行文件 | 编译为独立 DLL |
|
||||
| **额外代码** | 无 | 需要 `E2D_FORCE_LINK` |
|
||||
| **分发** | 单一可执行文件 | 需要 DLL 文件 |
|
||||
| **热更新** | 不支持 | 支持(重新加载 DLL) |
|
||||
|
||||
### 静态链接方案(推荐)
|
||||
|
||||
#### 原理
|
||||
|
||||
静态链接时,链接器会优化掉未引用的代码。解决方案:
|
||||
1. **引擎库**:使用 `--whole-archive` 强制链接所有符号
|
||||
2. **自定义模块**:直接编译到可执行文件
|
||||
|
||||
#### xmake 配置
|
||||
|
||||
```lua
|
||||
-- 引擎库(静态库)
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
-- ... 其他配置
|
||||
|
||||
-- 可执行文件
|
||||
target("demo_basic")
|
||||
set_kind("binary")
|
||||
|
||||
-- 强制链接引擎静态库
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
add_files("main.cpp")
|
||||
```
|
||||
|
||||
#### 自定义模块配置
|
||||
|
||||
```lua
|
||||
target("demo_hello_module")
|
||||
set_kind("binary")
|
||||
|
||||
-- 强制链接引擎静态库
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
-- 直接编译模块源文件到可执行文件
|
||||
add_files("main.cpp", "hello_module.cpp")
|
||||
add_includedirs("hello_module")
|
||||
```
|
||||
|
||||
#### 模块定义
|
||||
|
||||
```cpp
|
||||
// hello_module.cpp
|
||||
#include "hello_module.h"
|
||||
|
||||
namespace extra2d {
|
||||
// 模块实现...
|
||||
}
|
||||
|
||||
// 在文件末尾,namespace 外部
|
||||
E2D_MODULE(HelloModule, 1000)
|
||||
```
|
||||
|
||||
#### main.cpp
|
||||
|
||||
```cpp
|
||||
#include "hello_module.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
int main() {
|
||||
extra2d::Application& app = extra2d::Application::get();
|
||||
|
||||
// 无需任何注册代码!
|
||||
// 模块在静态初始化时自动注册
|
||||
|
||||
extra2d::AppConfig config;
|
||||
config.appName = "My App";
|
||||
|
||||
if (!app.init(config)) return 1;
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 动态链接方案
|
||||
|
||||
#### xmake 配置
|
||||
|
||||
```lua
|
||||
-- 引擎库(动态库)
|
||||
target("extra2d")
|
||||
set_kind("shared")
|
||||
add_defines("E2D_BUILDING_DLL", {public = false})
|
||||
-- ... 其他配置
|
||||
|
||||
-- 自定义模块 DLL
|
||||
target("hello_module_lib")
|
||||
set_kind("shared")
|
||||
add_defines("E2D_BUILDING_DLL", {public = false})
|
||||
add_deps("extra2d")
|
||||
add_files("hello_module.cpp")
|
||||
add_includedirs("hello_module", {public = true})
|
||||
|
||||
-- 可执行文件
|
||||
target("demo_hello_module")
|
||||
set_kind("binary")
|
||||
add_deps("hello_module_lib")
|
||||
add_files("main.cpp")
|
||||
```
|
||||
|
||||
#### 模块定义
|
||||
|
||||
```cpp
|
||||
// hello_module.cpp
|
||||
#include "hello_module.h"
|
||||
|
||||
namespace extra2d {
|
||||
// 模块实现...
|
||||
}
|
||||
|
||||
// 使用 E2D_MODULE_EXPORT(自动生成导出函数)
|
||||
E2D_MODULE_EXPORT(HelloModule, 1000)
|
||||
```
|
||||
|
||||
#### main.cpp
|
||||
|
||||
```cpp
|
||||
#include "hello_module.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
// 声明外部模块的导出函数
|
||||
E2D_DECLARE_FORCE_LINK(HelloModule);
|
||||
|
||||
int main() {
|
||||
// 触发 DLL 加载
|
||||
E2D_CALL_FORCE_LINK(HelloModule);
|
||||
|
||||
extra2d::Application& app = extra2d::Application::get();
|
||||
if (!app.init(config)) return 1;
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 平台兼容性
|
||||
|
||||
### Linux
|
||||
|
||||
```lua
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 使用 `-force_load` 代替 `--whole-archive`:
|
||||
|
||||
```lua
|
||||
if is_plat("macosx") then
|
||||
add_ldflags("-force_load", {force = true})
|
||||
end
|
||||
add_deps("extra2d")
|
||||
```
|
||||
|
||||
### Windows (MSVC)
|
||||
|
||||
MSVC 使用 `/WHOLEARCHIVE`:
|
||||
|
||||
```lua
|
||||
if is_plat("windows") and is_toolchain("msvc") then
|
||||
add_ldflags("/WHOLEARCHIVE:extra2d", {force = true})
|
||||
end
|
||||
add_deps("extra2d")
|
||||
```
|
||||
|
||||
### Windows (MinGW)
|
||||
|
||||
```lua
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -139,7 +373,7 @@ class EventContext : public ModuleContext {
|
|||
|-----|--------------|---------------|
|
||||
| 用途 | 平台级初始化 | 运行时功能 |
|
||||
| 生命周期 | Application 管理 | ServiceLocator 管理 |
|
||||
| 注册方式 | `app.use(module)` | `locator.registerService()` |
|
||||
| 注册方式 | 自动发现 | `locator.registerService()` |
|
||||
| 可替换性 | 编译时确定 | 运行时可替换 |
|
||||
| 示例 | Window, Render, Input | Scene, Timer, Event, Camera |
|
||||
|
||||
|
|
@ -164,116 +398,78 @@ class EventContext : public ModuleContext {
|
|||
|
||||
## 创建新模块
|
||||
|
||||
### 简单示例
|
||||
### 完整示例(静态链接)
|
||||
|
||||
**hello_module.h:**
|
||||
```cpp
|
||||
#include <extra2d/core/module.h>
|
||||
|
||||
class MyModule : public extra2d::Module {
|
||||
public:
|
||||
const char* getName() const override { return "MyModule"; }
|
||||
|
||||
int getPriority() const override { return 1000; }
|
||||
|
||||
void setupModule() override {
|
||||
// 初始化资源
|
||||
extra2d::E2D_LOG_INFO("MyModule initialized");
|
||||
}
|
||||
|
||||
void destroyModule() override {
|
||||
// 清理资源
|
||||
extra2d::E2D_LOG_INFO("MyModule destroyed");
|
||||
}
|
||||
|
||||
void onUpdate(extra2d::UpdateContext& ctx) override {
|
||||
// 更新逻辑
|
||||
ctx.next(); // 继续下一个模块
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 注册模块
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
auto& app = extra2d::Application::get();
|
||||
|
||||
MyModule myModule;
|
||||
app.use(myModule); // 显式注册
|
||||
|
||||
app.init();
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 带配置的模块
|
||||
|
||||
```cpp
|
||||
// my_module.h
|
||||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <string>
|
||||
|
||||
struct MyModuleConfig {
|
||||
namespace extra2d {
|
||||
|
||||
struct HelloModuleConfig {
|
||||
std::string greeting = "Hello!";
|
||||
int repeatCount = 1;
|
||||
};
|
||||
|
||||
class MyModule : public extra2d::Module {
|
||||
class HelloModule : public Module {
|
||||
public:
|
||||
MyModule();
|
||||
~MyModule() override;
|
||||
HelloModule();
|
||||
~HelloModule() override;
|
||||
|
||||
const char* getName() const override { return "MyModule"; }
|
||||
const char* getName() const override { return "HelloModule"; }
|
||||
int getPriority() const override { return 1000; }
|
||||
|
||||
void setupModule() override;
|
||||
void destroyModule() override;
|
||||
void onUpdate(extra2d::UpdateContext& ctx) override;
|
||||
void onUpdate(UpdateContext& ctx) override;
|
||||
|
||||
void setConfig(const MyModuleConfig& config) { config_ = config; }
|
||||
void setConfig(const HelloModuleConfig& config) { config_ = config; }
|
||||
void sayHello() const;
|
||||
|
||||
private:
|
||||
MyModuleConfig config_;
|
||||
HelloModuleConfig config_;
|
||||
float time_ = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
```
|
||||
|
||||
**hello_module.cpp:**
|
||||
```cpp
|
||||
// my_module.cpp
|
||||
#include "my_module.h"
|
||||
#include "hello_module.h"
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
MyModule::MyModule() : Module() {}
|
||||
namespace extra2d {
|
||||
|
||||
MyModule::~MyModule() {
|
||||
HelloModule::HelloModule() = default;
|
||||
|
||||
HelloModule::~HelloModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void MyModule::setupModule() {
|
||||
void HelloModule::setupModule() {
|
||||
if (isInitialized()) return;
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("MyModule initialized");
|
||||
E2D_LOG_INFO("HelloModule initialized");
|
||||
E2D_LOG_INFO(" Greeting: {}", config_.greeting);
|
||||
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
|
||||
|
||||
sayHello();
|
||||
}
|
||||
|
||||
void MyModule::destroyModule() {
|
||||
void HelloModule::destroyModule() {
|
||||
if (!isInitialized()) return;
|
||||
|
||||
E2D_LOG_INFO("MyModule shutdown");
|
||||
E2D_LOG_INFO("HelloModule shutdown");
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
void MyModule::onUpdate(extra2d::UpdateContext& ctx) {
|
||||
void HelloModule::onUpdate(UpdateContext& ctx) {
|
||||
if (!isInitialized()) {
|
||||
ctx.next();
|
||||
return;
|
||||
|
|
@ -289,11 +485,49 @@ void MyModule::onUpdate(extra2d::UpdateContext& ctx) {
|
|||
ctx.next();
|
||||
}
|
||||
|
||||
void MyModule::sayHello() const {
|
||||
void HelloModule::sayHello() const {
|
||||
for (int i = 0; i < config_.repeatCount; ++i) {
|
||||
E2D_LOG_INFO("[MyModule] {}", config_.greeting);
|
||||
E2D_LOG_INFO("[HelloModule] {}", config_.greeting);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// 自动注册(在 namespace 外部)
|
||||
E2D_MODULE(HelloModule, 1000)
|
||||
```
|
||||
|
||||
**main.cpp:**
|
||||
```cpp
|
||||
#include "hello_module.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
int main() {
|
||||
extra2d::Application& app = extra2d::Application::get();
|
||||
|
||||
extra2d::AppConfig config;
|
||||
config.appName = "Hello Module Demo";
|
||||
|
||||
// 无需手动注册!模块已自动注册
|
||||
|
||||
if (!app.init(config)) return 1;
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**xmake.lua:**
|
||||
```lua
|
||||
target("demo_hello_module")
|
||||
set_kind("binary")
|
||||
|
||||
-- 强制链接引擎静态库
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
-- 直接编译模块源文件
|
||||
add_files("main.cpp", "hello_module.cpp")
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -314,35 +548,6 @@ config.appVersion = "1.0.0";
|
|||
|
||||
---
|
||||
|
||||
### Logger 模块
|
||||
|
||||
**职责**:日志系统初始化
|
||||
|
||||
**优先级**:-1(最先初始化)
|
||||
|
||||
```cpp
|
||||
extra2d::LoggerModule loggerModule;
|
||||
loggerModule.setLogLevel(extra2d::LogLevel::Debug);
|
||||
loggerModule.setFileOutput("app.log");
|
||||
app.use(loggerModule);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Platform 模块
|
||||
|
||||
**职责**:平台检测和平台特定初始化
|
||||
|
||||
**优先级**:10
|
||||
|
||||
**支持平台**:
|
||||
- Windows
|
||||
- Linux
|
||||
- macOS
|
||||
- Nintendo Switch
|
||||
|
||||
---
|
||||
|
||||
### Window 模块
|
||||
|
||||
**职责**:窗口创建和管理
|
||||
|
|
@ -351,17 +556,6 @@ app.use(loggerModule);
|
|||
|
||||
**后端**:统一使用 SDL2
|
||||
|
||||
```cpp
|
||||
extra2d::WindowModule windowModule;
|
||||
extra2d::WindowConfigData windowConfig;
|
||||
windowConfig.title = "My App";
|
||||
windowConfig.width = 1280;
|
||||
windowConfig.height = 720;
|
||||
windowConfig.vsync = true;
|
||||
windowModule.setWindowConfig(windowConfig);
|
||||
app.use(windowModule);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Input 模块
|
||||
|
|
@ -370,15 +564,6 @@ app.use(windowModule);
|
|||
|
||||
**优先级**:30
|
||||
|
||||
```cpp
|
||||
extra2d::InputModule inputModule;
|
||||
extra2d::InputConfigData inputConfig;
|
||||
inputConfig.deadzone = 0.15f;
|
||||
inputConfig.enableVibration = true;
|
||||
inputModule.setInputConfig(inputConfig);
|
||||
app.use(inputModule);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Render 模块
|
||||
|
|
@ -387,15 +572,6 @@ app.use(inputModule);
|
|||
|
||||
**优先级**:40
|
||||
|
||||
```cpp
|
||||
extra2d::RenderModule renderModule;
|
||||
extra2d::RenderModuleConfig renderConfig;
|
||||
renderConfig.vsync = true;
|
||||
renderConfig.multisamples = 4;
|
||||
renderModule.setRenderConfig(renderConfig);
|
||||
app.use(renderModule);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 服务系统
|
||||
|
|
@ -558,48 +734,37 @@ auto triangle = ShapeNode::createFilledTriangle(
|
|||
);
|
||||
```
|
||||
|
||||
### 变换继承
|
||||
|
||||
```cpp
|
||||
auto parent = makeShared<Node>();
|
||||
parent->setPos(100, 100);
|
||||
parent->setRotation(45);
|
||||
|
||||
auto child = makeShared<Node>();
|
||||
child->setPos(50, 0); // 相对于父节点
|
||||
parent->addChild(child);
|
||||
|
||||
// child 会随 parent 一起旋转
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 视口适配系统
|
||||
## 常见问题
|
||||
|
||||
### ViewportAdapter
|
||||
### Q: 模块没有被注册?
|
||||
|
||||
```cpp
|
||||
enum class ViewportMode {
|
||||
AspectRatio, // 保持宽高比,可能有黑边
|
||||
Stretch, // 拉伸填满整个窗口
|
||||
Center, // 居中显示,不缩放
|
||||
Custom // 自定义缩放和偏移
|
||||
};
|
||||
**静态链接:**
|
||||
- 确保使用了 `--whole-archive`
|
||||
- 自定义模块要直接编译到可执行文件
|
||||
|
||||
**动态链接:**
|
||||
- 确保使用了 `E2D_MODULE_EXPORT`
|
||||
- 确保调用了 `E2D_CALL_FORCE_LINK`
|
||||
|
||||
### Q: 链接错误 "undefined reference"?
|
||||
|
||||
检查链接顺序,`--whole-archive` 要在 `add_deps` 之前。
|
||||
|
||||
### Q: 模块初始化顺序错误?
|
||||
|
||||
使用 `getPriority()` 控制顺序,数字小的先初始化。
|
||||
|
||||
### Q: 如何调试模块注册?
|
||||
|
||||
查看日志输出:
|
||||
```
|
||||
|
||||
### 使用 CameraService 配置视口
|
||||
|
||||
```cpp
|
||||
auto cameraService = app.camera();
|
||||
if (cameraService) {
|
||||
extra2d::ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = 1280.0f;
|
||||
vpConfig.logicHeight = 720.0f;
|
||||
vpConfig.mode = extra2d::ViewportMode::AspectRatio;
|
||||
|
||||
cameraService->setViewportConfig(vpConfig);
|
||||
cameraService->updateViewport(windowWidth, windowHeight);
|
||||
}
|
||||
[INFO ] ModuleRegistry: 4 modules registered
|
||||
[INFO ] - ConfigModule (priority: 0)
|
||||
[INFO ] - WindowModule (priority: 20)
|
||||
[INFO ] - InputModule (priority: 30)
|
||||
[INFO ] - RenderModule (priority: 40)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -653,3 +818,7 @@ void onUpdate(UpdateContext& ctx) override {
|
|||
ctx.next();
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 静态链接优先
|
||||
|
||||
静态链接更简单,无需额外配置,推荐用于大多数场景。
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
* - 变换(位置、旋转、缩放)
|
||||
* - 形状节点渲染
|
||||
* - 输入事件处理
|
||||
*
|
||||
* 模块现在通过 E2D_MODULE 宏自动注册,无需手动调用 app.use()
|
||||
*/
|
||||
|
||||
#include <extra2d/extra2d.h>
|
||||
|
|
@ -111,6 +113,8 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
Application &app = Application::get();
|
||||
|
||||
// 模块已通过 E2D_MODULE 宏自动注册,无需调用 app.use()
|
||||
|
||||
if (!app.init(config)) {
|
||||
std::cerr << "Failed to initialize application!" << std::endl;
|
||||
return -1;
|
||||
|
|
@ -166,9 +170,6 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
app.run();
|
||||
|
||||
std::cout << "Shutting down..." << std::endl;
|
||||
app.shutdown();
|
||||
|
||||
std::cout << "Goodbye!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "hello_module.h"
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -77,3 +78,5 @@ void HelloModule::sayHello() const {
|
|||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(HelloModule, 1000)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ struct HelloModuleConfigData {
|
|||
* 这是一个简单的自定义模块示例,展示如何:
|
||||
* 1. 继承 Module 基类
|
||||
* 2. 实现生命周期方法
|
||||
* 3. 使用 Application::use() 注册模块
|
||||
* 3. 使用 E2D_MODULE 宏自动注册模块
|
||||
*/
|
||||
class HelloModule : public Module {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ public:
|
|||
Scene::onEnter();
|
||||
addListener(EventType::KeyPress, [](Event &e) {
|
||||
auto &keyEvent = std::get<KeyEvent>(e.data);
|
||||
|
||||
auto app = Application::get().getModule<HelloModule>();
|
||||
E2D_LOG_INFO("Module {}", app->getName());
|
||||
if (keyEvent.scancode == static_cast<int>(Key::Escape)) {
|
||||
e.handled = true;
|
||||
E2D_LOG_INFO("ESC !!!exit");
|
||||
E2D_LOG_INFO("ESC pressed, exiting...");
|
||||
Application::get().quit();
|
||||
}
|
||||
});
|
||||
|
|
@ -33,14 +34,15 @@ private:
|
|||
|
||||
/**
|
||||
* @brief 应用程序入口
|
||||
*
|
||||
* 静态链接时模块直接编译到可执行文件,自动注册,无需任何额外代码!
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
Application &app = Application::get();
|
||||
|
||||
HelloModule helloModule;
|
||||
app.use(helloModule);
|
||||
|
||||
AppConfig appConfig;
|
||||
appConfig.appName = "HelloModule Example";
|
||||
appConfig.appVersion = "1.0.0";
|
||||
|
|
@ -55,7 +57,5 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
app.run();
|
||||
|
||||
app.shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
35
xmake.lua
35
xmake.lua
|
|
@ -145,7 +145,11 @@ target("demo_basic")
|
|||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
-- 强制链接整个静态库(保留静态初始化的模块注册变量)
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
add_files("examples/basic/main.cpp")
|
||||
|
||||
-- 平台配置
|
||||
|
|
@ -165,13 +169,42 @@ target("demo_basic")
|
|||
after_build(install_shaders)
|
||||
target_end()
|
||||
|
||||
-- Hello Module 静态库 - 自定义模块示例
|
||||
target("hello_module_lib")
|
||||
set_kind("static")
|
||||
set_default(false)
|
||||
|
||||
add_deps("extra2d")
|
||||
add_files("examples/hello_module/hello_module.cpp")
|
||||
add_includedirs("examples/hello_module", {public = true})
|
||||
|
||||
-- 平台配置
|
||||
local plat = get_config("plat") or os.host()
|
||||
if plat == "mingw" or plat == "windows" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||
elseif plat == "linux" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_syslinks("GL", "dl", "pthread")
|
||||
elseif plat == "macosx" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo")
|
||||
end
|
||||
target_end()
|
||||
|
||||
-- Hello Module 示例 - 展示如何创建自定义模块
|
||||
-- 注意:静态链接时,自定义模块需要直接编译到可执行文件中
|
||||
target("demo_hello_module")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
-- 强制链接引擎静态库
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_files("examples/hello_module/*.cpp")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
-- 直接编译模块源文件(静态链接时的推荐方式)
|
||||
add_files("examples/hello_module/main.cpp", "examples/hello_module/hello_module.cpp")
|
||||
add_includedirs("examples/hello_module")
|
||||
|
||||
-- 平台配置
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ end
|
|||
-- 定义 Extra2D 引擎库目标
|
||||
function define_extra2d_engine()
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
set_kind("static") -- 改回静态库
|
||||
|
||||
-- 引擎核心源文件
|
||||
add_files("Extra2D/src/**.cpp")
|
||||
|
|
|
|||
Loading…
Reference in New Issue