refactor(engine): 重构模块系统与平台后端

- 移除PlatformModule和LoggerModule,改为使用E2D_MODULE宏自动注册模块
- 新增ModuleRegistry和ModuleMeta系统实现模块自发现
- 将BackendFactory从PlatformModule移至独立文件
- 添加export.h统一管理导出宏
- 更新README.md添加模块自发现流程图
- 修复SDL2Input初始化状态管理问题
- 清理不再使用的平台配置相关代码
- 示例项目改为静态链接确保模块自动注册
- 添加属性绑定系统支持运行时反射
This commit is contained in:
ChestnutYueyue 2026-02-16 09:29:11 +08:00
parent a78e6f7a05
commit 8fc3b794d2
50 changed files with 1661 additions and 2699 deletions

View File

@ -1,14 +1,14 @@
#pragma once #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/app_config.h>
#include <extra2d/config/config_manager.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 <extra2d/platform/iwindow.h>
#include <string> #include <string>
#include <vector>
#include <initializer_list>
namespace extra2d { namespace extra2d {
@ -17,240 +17,237 @@ class RenderBackend;
/** /**
* @brief * @brief
* 使Mock *
* E2D_MODULE use()
* Application
*/ */
class Application { class E2D_API Application {
public: public:
/** /**
* @brief * @brief
* @return * @return
*/ */
static Application& get(); static Application &get();
Application(const Application&) = delete; Application(const Application &) = delete;
Application& operator=(const Application&) = delete; Application &operator=(const Application &) = delete;
/** /**
* @brief * @brief 使
* @param m * @return true
*/ */
void use(Module& m); bool init();
/** /**
* @brief * @brief 使
* @param modules * @param config
*/ * @return true
void use(std::initializer_list<Module*> modules); */
bool init(const AppConfig &config);
/** /**
* @brief 使 * @brief 使
* @return true * @param configPath
*/ * @return true
bool init(); */
bool init(const std::string &configPath);
/** /**
* @brief 使 * @brief
* @param config */
* @return true void shutdown();
*/
bool init(const AppConfig& config);
/** /**
* @brief 使 * @brief
* @param configPath */
* @return true void run();
*/
bool init(const std::string& configPath);
/** /**
* @brief * @brief 退
*/ */
void shutdown(); void quit();
/** /**
* @brief * @brief
*/ */
void run(); void pause();
/** /**
* @brief 退 * @brief
*/ */
void quit(); void resume();
/** /**
* @brief * @brief
*/ * @return true
void pause(); */
bool isPaused() const { return paused_; }
/** /**
* @brief * @brief
*/ * @return true
void resume(); */
bool isRunning() const { return running_; }
/** /**
* @brief * @brief
* @return true * @return
*/ */
bool isPaused() const { return paused_; } IWindow &window() { return *window_; }
/** /**
* @brief * @brief
* @return true * @return
*/ */
bool isRunning() const { return running_; } RenderBackend &renderer();
/** /**
* @brief * @brief
* @return * @return
*/ */
IWindow& window() { return *window_; } IInput &input();
/** /**
* @brief * @brief
* @return * @return
*/ */
RenderBackend& renderer(); SharedPtr<class ISceneService> scenes();
/** /**
* @brief * @brief
* @return * @return
*/ */
IInput& input(); SharedPtr<class ITimerService> timers();
/** /**
* @brief * @brief
* @return * @return
*/ */
SharedPtr<class ISceneService> scenes(); SharedPtr<class IEventService> events();
/** /**
* @brief * @brief
* @return * @return
*/ */
SharedPtr<class ITimerService> timers(); SharedPtr<class ICameraService> camera();
/** /**
* @brief * @brief
* @return * @param scene
*/ */
SharedPtr<class IEventService> events(); void enterScene(Ptr<class Scene> scene);
/** /**
* @brief * @brief
* @return * @return
*/ */
SharedPtr<class ICameraService> camera(); float deltaTime() const { return deltaTime_; }
/** /**
* @brief * @brief
* @param scene * @return
*/ */
void enterScene(Ptr<class Scene> scene); float totalTime() const { return totalTime_; }
/** /**
* @brief * @brief
* @return * @return
*/ */
float deltaTime() const { return deltaTime_; } int fps() const { return currentFps_; }
/** /**
* @brief * @brief
* @return * @return
*/ */
float totalTime() const { return totalTime_; } ConfigManager &config();
/** /**
* @brief * @brief
* @return * @return
*/ */
int fps() const { return currentFps_; } const AppConfig &getConfig() const;
/** /**
* @brief * @brief
* @return * @tparam T
*/ * @return nullptr
ConfigManager& config(); */
template <typename T> T *getModule() {
return ModuleRegistry::instance().getModule<T>();
}
/** /**
* @brief * @brief
* @return * @param name
*/ * @return nullptr
const AppConfig& getConfig() const; */
Module *getModule(const char *name) {
return ModuleRegistry::instance().getModule(name);
}
/** /**
* @brief * @brief
* @tparam T * @tparam T
* @param service * @param service
*/ */
template<typename T> template <typename T> void registerService(SharedPtr<T> service) {
void registerService(SharedPtr<T> service) { ServiceLocator::instance().registerService(service);
ServiceLocator::instance().registerService(service); }
}
/** /**
* @brief * @brief
* @tparam T * @tparam T
* @return * @return
*/ */
template<typename T> template <typename T> SharedPtr<T> getService() {
SharedPtr<T> getService() { return ServiceLocator::instance().getService<T>();
return ServiceLocator::instance().getService<T>(); }
}
private: private:
Application() = default; Application() = default;
~Application(); ~Application();
/** /**
* @brief * @brief
* @return true * @param config
*/ * @return true
bool initCoreModules(); */
bool initModules(const AppConfig &config);
/** /**
* @brief * @brief
*/ */
void setupAllModules(); void registerCoreServices();
void registerCameraService();
/** /**
* @brief * @brief
*/ */
void destroyAllModules(); void mainLoop();
/** /**
* @brief * @brief
*/ */
void registerCoreServices(); void update();
/** /**
* @brief * @brief
*/ */
void mainLoop(); void render();
/** IWindow *window_ = nullptr;
* @brief
*/
void update();
/** bool initialized_ = false;
* @brief bool running_ = false;
*/ bool paused_ = false;
void render(); bool shouldQuit_ = false;
std::vector<Module*> modules_; float deltaTime_ = 0.0f;
IWindow* window_ = nullptr; float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
bool initialized_ = false; int frameCount_ = 0;
bool running_ = false; float fpsTimer_ = 0.0f;
bool paused_ = false; int currentFps_ = 0;
bool shouldQuit_ = false;
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
}; };
} } // namespace extra2d

View File

@ -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; }
};
}

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <extra2d/config/platform_config.h> #include <extra2d/core/export.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <string> #include <string>
@ -21,12 +21,11 @@ namespace extra2d {
* @brief * @brief
* *
*/ */
struct AppConfig { struct E2D_API AppConfig {
std::string appName = "Extra2D App"; std::string appName = "Extra2D App";
std::string appVersion = "1.0.0"; std::string appVersion = "1.0.0";
std::string organization = ""; std::string organization = "";
std::string configFile = "config.json"; std::string configFile = "config.json";
PlatformType targetPlatform = PlatformType::Auto;
/** /**
* @brief * @brief

View File

@ -2,7 +2,6 @@
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -114,18 +113,6 @@ public:
*/ */
void setAppConfig(const AppConfig &config); void setAppConfig(const AppConfig &config);
/**
* @brief
* @return
*/
PlatformConfig *platformConfig();
/**
* @brief
* @return
*/
const PlatformConfig *platformConfig() const;
/** /**
* @brief * @brief
* @param callback * @param callback
@ -273,7 +260,6 @@ private:
void notifyChangeCallbacks(const ConfigChangeEvent &event); void notifyChangeCallbacks(const ConfigChangeEvent &event);
AppConfig m_appConfig; AppConfig m_appConfig;
UniquePtr<PlatformConfig> m_platformConfig;
UniquePtr<ConfigLoader> m_loader; UniquePtr<ConfigLoader> m_loader;
std::string m_configPath; std::string m_configPath;
bool m_initialized = false; bool m_initialized = false;

View File

@ -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);
}

View File

@ -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();
};
}

View File

@ -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

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/property.h>
#include <vector> #include <vector>
namespace extra2d { namespace extra2d {
@ -14,7 +16,7 @@ class EventContext;
* @brief * @brief
* *
*/ */
class ModuleContext { class E2D_API ModuleContext {
public: public:
/** /**
* @brief * @brief

View File

@ -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); \
}

View File

@ -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

View File

@ -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

View File

@ -6,23 +6,20 @@
// Core // Core
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/core/module_macros.h>
#include <extra2d/core/types.h>
// Config // Config
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/config_manager.h> #include <extra2d/config/config_manager.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
// Modules // Modules
#include <extra2d/modules/config_module.h> #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/input_module.h>
#include <extra2d/modules/render_module.h> #include <extra2d/modules/render_module.h>
#include <extra2d/modules/window_module.h>
// Platform // Platform
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
@ -61,10 +58,10 @@
#include <extra2d/utils/timer.h> #include <extra2d/utils/timer.h>
// Services // Services
#include <extra2d/services/camera_service.h>
#include <extra2d/services/event_service.h> #include <extra2d/services/event_service.h>
#include <extra2d/services/scene_service.h> #include <extra2d/services/scene_service.h>
#include <extra2d/services/timer_service.h> #include <extra2d/services/timer_service.h>
#include <extra2d/services/camera_service.h>
// Application // Application
#include <extra2d/app/application.h> #include <extra2d/app/application.h>

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <extra2d/config/platform_detector.h>
#include <extra2d/graphics/shader_cache.h> #include <extra2d/graphics/shader_cache.h>
#include <extra2d/graphics/shader_hot_reloader.h> #include <extra2d/graphics/shader_hot_reloader.h>
#include <extra2d/graphics/shader_interface.h> #include <extra2d/graphics/shader_interface.h>

View File

@ -67,9 +67,14 @@ public:
* @brief * @brief
* @param window * @param window
*/ */
void setWindow(IWindow* window) { window_ = window; } void setWindow(IWindow* window);
private: private:
/**
* @brief 使
*/
void initializeWithWindow();
IWindow* window_ = nullptr; IWindow* window_ = nullptr;
IInput* input_ = nullptr; IInput* input_ = nullptr;
InputConfigData config_; InputConfigData config_;

View File

@ -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

View File

@ -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

View File

@ -99,7 +99,7 @@ public:
* @brief * @brief
* @param window * @param window
*/ */
void setWindow(IWindow* window) { window_ = window; } void setWindow(IWindow* window);
/** /**
* @brief * @brief
@ -108,6 +108,11 @@ public:
RenderBackend* getRenderer() const { return renderer_.get(); } RenderBackend* getRenderer() const { return renderer_.get(); }
private: private:
/**
* @brief 使
*/
void initializeWithWindow();
IWindow* window_ = nullptr; IWindow* window_ = nullptr;
UniquePtr<RenderBackend> renderer_; UniquePtr<RenderBackend> renderer_;
RenderModuleConfig config_; RenderModuleConfig config_;

View File

@ -10,16 +10,6 @@
namespace extra2d { namespace extra2d {
/**
* @brief
*/
struct PlatformModuleConfig {
std::string backend = "sdl2";
bool gamepad = true;
bool touch = true;
float deadzone = 0.15f;
};
/** /**
* @brief * @brief
* *
@ -95,4 +85,4 @@ private:
} e2d_backend_reg_##name; \ } e2d_backend_reg_##name; \
} }
} // namespace extra2d }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/event/event_dispatcher.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: public:
Node(); Node();
virtual ~Node(); virtual ~Node();

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/graphics/camera.h> #include <extra2d/graphics/camera.h>
#include <extra2d/scene/node.h> #include <extra2d/scene/node.h>
#include <vector> #include <vector>
@ -13,7 +14,7 @@ struct RenderCommand;
// ============================================================================ // ============================================================================
// 场景类 - 节点容器,管理整个场景图 // 场景类 - 节点容器,管理整个场景图
// ============================================================================ // ============================================================================
class Scene : public Node { class E2D_API Scene : public Node {
public: public:
Scene(); Scene();
~Scene() override = default; ~Scene() override = default;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/scene/node.h> #include <extra2d/scene/node.h>
#include <vector> #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: public:
ShapeNode(); ShapeNode();
~ShapeNode() override = default; ~ShapeNode() override = default;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdio> #include <cstdio>
#include <extra2d/core/export.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <sstream> #include <sstream>
#include <string> #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); } inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
class Logger { class E2D_API Logger {
public: public:
static void init(); static void init();
static void shutdown(); static void shutdown();

View File

@ -1,8 +1,8 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/core/module_meta.h>
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/modules/config_module.h> #include <extra2d/modules/config_module.h>
#include <extra2d/modules/input_module.h> #include <extra2d/modules/input_module.h>
#include <extra2d/modules/platform_module.h>
#include <extra2d/modules/render_module.h> #include <extra2d/modules/render_module.h>
#include <extra2d/modules/window_module.h> #include <extra2d/modules/window_module.h>
#include <extra2d/services/camera_service.h> #include <extra2d/services/camera_service.h>
@ -11,9 +11,7 @@
#include <extra2d/services/timer_service.h> #include <extra2d/services/timer_service.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <algorithm>
#include <chrono> #include <chrono>
#include <thread>
namespace extra2d { namespace extra2d {
@ -36,23 +34,6 @@ Application &Application::get() {
return instance; 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() { bool Application::init() {
AppConfig cfg; AppConfig cfg;
return init(cfg); return init(cfg);
@ -63,49 +44,14 @@ bool Application::init(const AppConfig &config) {
return true; return true;
} }
if (!initCoreModules()) { // 先注册核心服务,因为模块初始化时可能需要它们
registerCoreServices();
if (!initModules(config)) {
return false; return false;
} }
ConfigModule *configModule = nullptr; auto windowModule = getModule<WindowModule>();
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();
}
}
if (!windowModule || !windowModule->isInitialized()) { if (!windowModule || !windowModule->isInitialized()) {
E2D_LOG_ERROR("Window module not initialized"); E2D_LOG_ERROR("Window module not initialized");
return false; return false;
@ -117,18 +63,19 @@ bool Application::init(const AppConfig &config) {
return false; return false;
} }
registerCoreServices(); auto inputModule = getModule<InputModule>();
if (inputModule) { if (inputModule) {
inputModule->setWindow(window_); inputModule->setWindow(window_);
inputModule->setupModule();
} }
auto renderModule = getModule<RenderModule>();
if (renderModule) { if (renderModule) {
renderModule->setWindow(window_); renderModule->setWindow(window_);
renderModule->setupModule();
} }
// 注册 CameraService需要 window
registerCameraService();
if (!ServiceLocator::instance().initializeAll()) { if (!ServiceLocator::instance().initializeAll()) {
return false; return false;
} }
@ -162,168 +109,29 @@ bool Application::init(const std::string &configPath) {
return true; return true;
} }
if (!initCoreModules()) { auto configModule = getModule<ConfigModule>();
return false;
}
ConfigModule *configModule = nullptr;
for (auto *m : modules_) {
if (auto *cm = dynamic_cast<ConfigModule *>(m)) {
configModule = cm;
break;
}
}
if (configModule) { if (configModule) {
configModule->setConfigPath(configPath); configModule->setConfigPath(configPath);
} }
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare); AppConfig cfg;
return init(cfg);
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();
}
}
if (!windowModule || !windowModule->isInitialized()) {
E2D_LOG_ERROR("Window module not initialized");
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 Application::initModules(const AppConfig &config) {
bool hasConfig = false; auto configModule = getModule<ConfigModule>();
bool hasPlatform = false; if (configModule) {
bool hasWindow = false; configModule->setAppConfig(config);
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) { if (!ModuleRegistry::instance().createAndInitAll()) {
static ConfigModule defaultConfigModule; E2D_LOG_ERROR("Failed to initialize modules");
use(defaultConfigModule); return false;
}
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; 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() { void Application::registerCoreServices() {
auto &locator = ServiceLocator::instance(); auto &locator = ServiceLocator::instance();
@ -338,6 +146,10 @@ void Application::registerCoreServices() {
if (!locator.hasService<ITimerService>()) { if (!locator.hasService<ITimerService>()) {
locator.registerService<ITimerService>(makeShared<TimerService>()); locator.registerService<ITimerService>(makeShared<TimerService>());
} }
}
void Application::registerCameraService() {
auto &locator = ServiceLocator::instance();
if (!locator.hasService<ICameraService>()) { if (!locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>(); auto cameraService = makeShared<CameraService>();
@ -359,16 +171,20 @@ void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
E2D_LOG_INFO("Application shutting down...");
VRAMMgr::get().printStats(); VRAMMgr::get().printStats();
// 先销毁模块(它们可能需要服务和窗口)
ModuleRegistry::instance().destroyAll();
// 最后清除服务
ServiceLocator::instance().clear(); ServiceLocator::instance().clear();
window_ = nullptr;
destroyAllModules();
initialized_ = false; initialized_ = false;
running_ = false; running_ = false;
E2D_LOG_INFO("Application shutdown complete");
} }
Application::~Application() { Application::~Application() {
@ -387,6 +203,9 @@ void Application::run() {
while (running_ && !window_->shouldClose()) { while (running_ && !window_->shouldClose()) {
mainLoop(); mainLoop();
} }
// 主循环结束后自动清理
shutdown();
} }
void Application::quit() { void Application::quit() {
@ -437,14 +256,7 @@ void Application::mainLoop() {
render(); render();
RenderModule *renderModule = nullptr; auto renderModule = getModule<RenderModule>();
for (auto *m : modules_) {
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
renderModule = rm;
break;
}
}
(void)renderModule; (void)renderModule;
ConfigManager::instance().update(deltaTime_); ConfigManager::instance().update(deltaTime_);
@ -453,19 +265,18 @@ void Application::mainLoop() {
void Application::update() { void Application::update() {
ServiceLocator::instance().updateAll(deltaTime_); ServiceLocator::instance().updateAll(deltaTime_);
auto ctx = UpdateContext(modules_, deltaTime_); auto modules = ModuleRegistry::instance().getAllModules();
if (!modules_.empty()) { auto ctx = UpdateContext(modules, deltaTime_);
if (!modules.empty()) {
ctx.next(); ctx.next();
} }
} }
void Application::render() { void Application::render() {
RenderBackend *renderer = nullptr; RenderBackend *renderer = nullptr;
for (auto *m : modules_) { auto renderModule = getModule<RenderModule>();
if (auto *rm = dynamic_cast<RenderModule *>(m)) { if (renderModule) {
renderer = rm->getRenderer(); renderer = renderModule->getRenderer();
break;
}
} }
if (!renderer) { if (!renderer) {
@ -484,16 +295,18 @@ void Application::render() {
renderer->setViewport(0, 0, window_->width(), window_->height()); renderer->setViewport(0, 0, window_->width(), window_->height());
} }
auto modules = ModuleRegistry::instance().getAllModules();
{ {
auto ctx = RenderContext(modules_, RenderContext::Phase::Before); auto ctx = RenderContext(modules, RenderContext::Phase::Before);
if (!modules_.empty()) { if (!modules.empty()) {
ctx.next(); ctx.next();
} }
} }
{ {
auto ctx = RenderContext(modules_, RenderContext::Phase::On); auto ctx = RenderContext(modules, RenderContext::Phase::On);
if (!modules_.empty()) { if (!modules.empty()) {
ctx.next(); ctx.next();
} }
} }
@ -504,8 +317,8 @@ void Application::render() {
} }
{ {
auto ctx = RenderContext(modules_, RenderContext::Phase::After); auto ctx = RenderContext(modules, RenderContext::Phase::After);
if (!modules_.empty()) { if (!modules.empty()) {
ctx.next(); ctx.next();
} }
} }
@ -514,12 +327,9 @@ void Application::render() {
IInput &Application::input() { return *window_->input(); } IInput &Application::input() { return *window_->input(); }
RenderBackend &Application::renderer() { RenderBackend &Application::renderer() {
for (auto *m : modules_) { auto renderModule = getModule<RenderModule>();
if (auto *rm = dynamic_cast<RenderModule *>(m)) { if (renderModule && renderModule->getRenderer()) {
if (rm->getRenderer()) { return *renderModule->getRenderer();
return *rm->getRenderer();
}
}
} }
static RenderBackend *dummy = nullptr; static RenderBackend *dummy = nullptr;
if (!dummy) { if (!dummy) {

View File

@ -10,7 +10,6 @@ AppConfig AppConfig::createDefault() {
config.appVersion = "1.0.0"; config.appVersion = "1.0.0";
config.organization = ""; config.organization = "";
config.configFile = "config.json"; config.configFile = "config.json";
config.targetPlatform = PlatformType::Auto;
return config; return config;
} }
@ -50,9 +49,6 @@ void AppConfig::merge(const AppConfig& other) {
if (other.configFile != "config.json") { if (other.configFile != "config.json") {
configFile = other.configFile; configFile = other.configFile;
} }
if (other.targetPlatform != PlatformType::Auto) {
targetPlatform = other.targetPlatform;
}
E2D_LOG_INFO("Merged app config"); E2D_LOG_INFO("Merged app config");
} }

View File

@ -9,11 +9,6 @@
namespace extra2d { namespace extra2d {
/**
* @brief
* @param str
* @return
*/
static std::string trim(const std::string &str) { static std::string trim(const std::string &str) {
size_t start = 0; size_t start = 0;
while (start < str.length() && while (start < str.length() &&
@ -28,11 +23,6 @@ static std::string trim(const std::string &str) {
return str.substr(start, end - start); return str.substr(start, end - start);
} }
/**
* @brief
* @param str
* @return
*/
static std::string toLower(const std::string &str) { static std::string toLower(const std::string &str) {
std::string result = str; std::string result = str;
for (char &c : result) { for (char &c : result) {
@ -41,17 +31,8 @@ static std::string toLower(const std::string &str) {
return result; return result;
} }
/**
* @brief INI
*/
using IniData = std::map<std::string, std::map<std::string, std::string>>; 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, static ConfigLoadResult parseIniContent(const std::string &content,
IniData &data) { IniData &data) {
std::istringstream stream(content); std::istringstream stream(content);
@ -97,14 +78,6 @@ static ConfigLoadResult parseIniContent(const std::string &content,
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
} }
/**
* @brief INI
* @param data INI
* @param section
* @param key
* @param defaultValue
* @return
*/
static std::string getIniValue(const IniData &data, const std::string &section, static std::string getIniValue(const IniData &data, const std::string &section,
const std::string &key, const std::string &key,
const std::string &defaultValue = "") { const std::string &defaultValue = "") {
@ -119,13 +92,6 @@ static std::string getIniValue(const IniData &data, const std::string &section,
return keyIt->second; return keyIt->second;
} }
/**
* @brief INI
* @param data INI
* @param section
* @param key
* @return
*/
static bool hasIniValue(const IniData &data, const std::string &section, static bool hasIniValue(const IniData &data, const std::string &section,
const std::string &key) { const std::string &key) {
auto sectionIt = data.find(section); auto sectionIt = data.find(section);
@ -192,14 +158,6 @@ ConfigLoadResult IniConfigLoader::loadFromString(const std::string &content,
if (hasIniValue(data, "app", "configFile")) { if (hasIniValue(data, "app", "configFile")) {
config.configFile = getIniValue(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 应用配置加载成功"); E2D_LOG_INFO("INI 应用配置加载成功");
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
@ -213,7 +171,6 @@ std::string IniConfigLoader::saveToString(const AppConfig &config) {
oss << "version=" << config.appVersion << "\n"; oss << "version=" << config.appVersion << "\n";
oss << "organization=" << config.organization << "\n"; oss << "organization=" << config.organization << "\n";
oss << "configFile=" << config.configFile << "\n"; oss << "configFile=" << config.configFile << "\n";
oss << "targetPlatform=" << static_cast<int>(config.targetPlatform) << "\n";
return oss.str(); return oss.str();
} }
@ -329,4 +286,4 @@ ConfigLoadResult IniConfigLoader::parseBool(const std::string &value,
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName); return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
} }
} // namespace extra2d }

View File

@ -71,10 +71,6 @@ ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, Ap
config.configFile = root["configFile"].get<std::string>(); 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 应用配置加载成功"); E2D_LOG_INFO("JSON 应用配置加载成功");
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
} }
@ -86,7 +82,6 @@ std::string JsonConfigLoader::saveToString(const AppConfig& config) {
root["appVersion"] = config.appVersion; root["appVersion"] = config.appVersion;
root["organization"] = config.organization; root["organization"] = config.organization;
root["configFile"] = config.configFile; root["configFile"] = config.configFile;
root["targetPlatform"] = static_cast<int>(config.targetPlatform);
return root.dump(4); return root.dump(4);
} }

View File

@ -1,6 +1,4 @@
#include <extra2d/config/config_manager.h> #include <extra2d/config/config_manager.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <sstream> #include <sstream>
@ -36,12 +34,6 @@ bool ConfigManager::initialize(const std::string& configPath) {
m_configPath = 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>(); m_loader = makeUnique<JsonConfigLoader>();
if (!m_loader) { if (!m_loader) {
E2D_LOG_ERROR("Failed to create config 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 = AppConfig::createDefault();
m_appConfig.targetPlatform = PlatformDetector::detect();
m_initialized = true; m_initialized = true;
m_modified = false; m_modified = false;
E2D_LOG_INFO("ConfigManager initialized for platform: {}", E2D_LOG_INFO("ConfigManager initialized");
m_platformConfig->platformName());
return true; return true;
} }
@ -73,7 +63,6 @@ void ConfigManager::shutdown() {
m_changeCallbacks.clear(); m_changeCallbacks.clear();
m_rawValues.clear(); m_rawValues.clear();
m_loader.reset(); m_loader.reset();
m_platformConfig.reset();
m_initialized = false; m_initialized = false;
m_modified = false; m_modified = false;
@ -217,14 +206,6 @@ void ConfigManager::setAppConfig(const AppConfig& config) {
E2D_LOG_INFO("App config updated"); 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) { int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);

View File

@ -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";
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -633,7 +633,7 @@ void GLRenderer::resetStats() { stats_ = Stats{}; }
*/ */
void GLRenderer::initShapeRendering() { void GLRenderer::initShapeRendering() {
// 从ShaderManager获取形状着色器 // 从ShaderManager获取形状着色器
shapeShader_ = ShaderManager::getInstance().getBuiltin("builtin_shape"); shapeShader_ = ShaderManager::getInstance().getBuiltin("shape");
if (!shapeShader_) { if (!shapeShader_) {
E2D_LOG_WARN("Failed to get builtin shape shader, loading from manager"); E2D_LOG_WARN("Failed to get builtin shape shader, loading from manager");
if (!ShaderManager::getInstance().isInitialized()) { if (!ShaderManager::getInstance().isInitialized()) {

View File

@ -3,42 +3,30 @@
namespace extra2d { namespace extra2d {
/**
* @brief
* @return Shader管理器实例引用
*/
ShaderManager& ShaderManager::getInstance() { ShaderManager& ShaderManager::getInstance() {
static ShaderManager instance; static ShaderManager instance;
return instance; return instance;
} }
/**
* @brief 使Shader系统
* 使romfs/sdmc/
* @param factory Shader工厂
* @param appName
* @return truefalse
*/
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) { bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
std::string shaderDir = PlatformDetector::getShaderPath(appName); std::string shaderDir;
std::string cacheDir = PlatformDetector::getShaderCachePath(appName); std::string cacheDir;
hotReloadSupported_ = PlatformDetector::supportsHotReload(); #ifdef __SWITCH__
shaderDir = "romfs:/shaders/";
E2D_LOG_INFO("Platform: {} (HotReload: {})", cacheDir = "sdmc:/config/" + appName + "/shader_cache/";
PlatformDetector::platformName(), hotReloadSupported_ = false;
hotReloadSupported_ ? "supported" : "not supported"); 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); return init(shaderDir, cacheDir, factory);
} }
/**
* @brief Shader系统
* @param shaderDir Shader文件目录
* @param cacheDir
* @param factory Shader工厂
* @return truefalse
*/
bool ShaderManager::init(const std::string& shaderDir, bool ShaderManager::init(const std::string& shaderDir,
const std::string& cacheDir, const std::string& cacheDir,
Ptr<IShaderFactory> factory) { Ptr<IShaderFactory> factory) {
@ -56,13 +44,13 @@ bool ShaderManager::init(const std::string& shaderDir,
cacheDir_ = cacheDir; cacheDir_ = cacheDir;
factory_ = factory; factory_ = factory;
hotReloadSupported_ = PlatformDetector::supportsHotReload();
#ifdef __SWITCH__ #ifdef __SWITCH__
hotReloadSupported_ = false;
if (!ShaderCache::getInstance().init(cacheDir_)) { if (!ShaderCache::getInstance().init(cacheDir_)) {
E2D_LOG_WARN("Failed to initialize shader cache on Switch"); E2D_LOG_WARN("Failed to initialize shader cache on Switch");
} }
#else #else
hotReloadSupported_ = true;
if (!ShaderCache::getInstance().init(cacheDir_)) { if (!ShaderCache::getInstance().init(cacheDir_)) {
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled"); E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
} }
@ -78,16 +66,10 @@ bool ShaderManager::init(const std::string& shaderDir,
initialized_ = true; initialized_ = true;
E2D_LOG_INFO("ShaderManager initialized"); 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; return true;
} }
/**
* @brief Shader系统
*/
void ShaderManager::shutdown() { void ShaderManager::shutdown() {
if (!initialized_) { if (!initialized_) {
return; return;
@ -105,175 +87,57 @@ void ShaderManager::shutdown() {
E2D_LOG_INFO("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, Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
const std::string& vertPath, const std::string& vertPath,
const std::string& fragPath) { const std::string& fragPath) {
if (!initialized_) { if (!factory_) {
E2D_LOG_ERROR("ShaderManager not initialized"); E2D_LOG_ERROR("Shader factory not initialized");
return nullptr; return nullptr;
} }
auto it = shaders_.find(name); auto vertSource = loader_.readFile(vertPath);
if (it != shaders_.end()) { auto fragSource = loader_.readFile(fragPath);
return it->second.shader;
}
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, vertPath, fragPath); if (vertSource.empty() || fragSource.empty()) {
if (!result.success) { E2D_LOG_ERROR("Failed to load shader sources: {} / {}", vertPath, fragPath);
E2D_LOG_ERROR("Failed to load shader files: {} - {}", vertPath, fragPath);
return nullptr; return nullptr;
} }
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource); return loadFromSource(name, vertSource, 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::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) { Ptr<IShader> ShaderManager::loadFromCombinedFile(const std::string& path) {
if (!initialized_) { if (!factory_) {
E2D_LOG_ERROR("ShaderManager not initialized"); E2D_LOG_ERROR("Shader factory not initialized");
return nullptr; return nullptr;
} }
ShaderMetadata metadata = loader_.getMetadata(path); auto result = loader_.loadFromCombinedFile(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);
if (!result.success) { if (!result.success) {
E2D_LOG_ERROR("Failed to load combined shader file: {}", path); E2D_LOG_ERROR("Failed to load combined shader: {}", path);
return nullptr; return nullptr;
} }
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource); std::string name = path.substr(path.find_last_of("/\\") + 1);
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource); return loadFromSource(name, 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::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, Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
const std::string& vertSource, const std::string& vertSource,
const std::string& fragSource) { const std::string& fragSource) {
if (!initialized_) { if (!factory_) {
E2D_LOG_ERROR("ShaderManager not initialized"); E2D_LOG_ERROR("Shader factory not initialized");
return nullptr; return nullptr;
} }
auto it = shaders_.find(name); if (has(name)) {
if (it != shaders_.end()) { E2D_LOG_WARN("Shader already exists: {}", name);
return it->second.shader; return get(name);
} }
Ptr<IShader> shader = factory_->createFromSource(name, vertSource, fragSource); auto shader = factory_->createFromSource(name, vertSource, fragSource);
if (!shader) { if (!shader) {
E2D_LOG_ERROR("Failed to create shader from source: {}", name); E2D_LOG_ERROR("Failed to create shader: {}", name);
return nullptr; return nullptr;
} }
@ -281,19 +145,13 @@ Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
info.shader = shader; info.shader = shader;
info.vertSource = vertSource; info.vertSource = vertSource;
info.fragSource = fragSource; info.fragSource = fragSource;
info.metadata.name = name;
shaders_[name] = std::move(info); shaders_[name] = std::move(info);
E2D_LOG_DEBUG("Shader loaded from source: {}", name); E2D_LOG_INFO("Shader loaded: {}", name);
return shader; return shader;
} }
/**
* @brief Shader
* @param name Shader名称
* @return Shader实例nullptr
*/
Ptr<IShader> ShaderManager::get(const std::string& name) const { Ptr<IShader> ShaderManager::get(const std::string& name) const {
auto it = shaders_.find(name); auto it = shaders_.find(name);
if (it != shaders_.end()) { if (it != shaders_.end()) {
@ -302,232 +160,118 @@ Ptr<IShader> ShaderManager::get(const std::string& name) const {
return nullptr; return nullptr;
} }
/**
* @brief Shader是否存在
* @param name Shader名称
* @return truefalse
*/
bool ShaderManager::has(const std::string& name) const { bool ShaderManager::has(const std::string& name) const {
return shaders_.find(name) != shaders_.end(); return shaders_.find(name) != shaders_.end();
} }
/**
* @brief Shader
* @param name Shader名称
*/
void ShaderManager::remove(const std::string& name) { void ShaderManager::remove(const std::string& name) {
auto it = shaders_.find(name); auto it = shaders_.find(name);
if (it != shaders_.end()) { if (it != shaders_.end()) {
ShaderHotReloader::getInstance().unwatch(name);
shaders_.erase(it); shaders_.erase(it);
E2D_LOG_DEBUG("Shader removed: {}", name); E2D_LOG_INFO("Shader removed: {}", name);
} }
} }
/**
* @brief Shader
*/
void ShaderManager::clear() { void ShaderManager::clear() {
if (hotReloadSupported_) {
for (const auto& pair : shaders_) {
ShaderHotReloader::getInstance().unwatch(pair.first);
}
}
shaders_.clear(); 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) { void ShaderManager::setReloadCallback(const std::string& name, ShaderReloadCallback callback) {
auto it = shaders_.find(name); auto it = shaders_.find(name);
if (it != shaders_.end()) { if (it != shaders_.end()) {
it->second.reloadCallback = callback; it->second.reloadCallback = std::move(callback);
} }
} }
/**
* @brief /
* @param enabled
*/
void ShaderManager::setHotReloadEnabled(bool enabled) { void ShaderManager::setHotReloadEnabled(bool enabled) {
if (!hotReloadSupported_) { if (!hotReloadSupported_ && enabled) {
E2D_LOG_WARN("Hot reload not supported on this platform"); E2D_LOG_WARN("Hot reload not supported on this platform");
return; return;
} }
hotReloadEnabled_ = enabled; hotReloadEnabled_ = enabled;
ShaderHotReloader::getInstance().setEnabled(enabled);
E2D_LOG_INFO("Hot reload {}", enabled ? "enabled" : "disabled");
} }
/**
* @brief
* @return truefalse
*/
bool ShaderManager::isHotReloadEnabled() const { bool ShaderManager::isHotReloadEnabled() const {
return hotReloadEnabled_ && hotReloadSupported_; return hotReloadEnabled_;
} }
/**
* @brief
*/
void ShaderManager::update() { void ShaderManager::update() {
if (hotReloadEnabled_ && hotReloadSupported_) { if (!hotReloadEnabled_ || !hotReloadSupported_) {
ShaderHotReloader::getInstance().update(); return;
} }
ShaderHotReloader::getInstance().update();
} }
/**
* @brief Shader
* @param name Shader名称
* @return truefalse
*/
bool ShaderManager::reload(const std::string& name) { bool ShaderManager::reload(const std::string& name) {
auto it = shaders_.find(name); auto it = shaders_.find(name);
if (it == shaders_.end()) { if (it == shaders_.end()) {
E2D_LOG_WARN("Shader not found for reload: {}", name); E2D_LOG_ERROR("Shader not found: {}", name);
return false; return false;
} }
ShaderInfo& info = it->second; auto shader = factory_->createFromSource(name, it->second.vertSource, it->second.fragSource);
if (!shader) {
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) {
E2D_LOG_ERROR("Failed to reload shader: {}", name); E2D_LOG_ERROR("Failed to reload shader: {}", name);
return false; return false;
} }
info.shader = newShader; it->second.shader = shader;
info.vertSource = vertSource;
info.fragSource = fragSource;
if (info.reloadCallback) { if (it->second.reloadCallback) {
info.reloadCallback(newShader); it->second.reloadCallback(shader);
} }
E2D_LOG_INFO("Shader reloaded: {}", name); E2D_LOG_INFO("Shader reloaded: {}", name);
return true; return true;
} }
/**
* @brief Shader
* @param name Shader名称
* @return Shader实例
*/
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) { Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
Ptr<IShader> shader = get(name); return get("builtin_" + name);
if (shader) {
return shader;
}
std::string path = shaderDir_ + "builtin/" + name + ".shader";
return loadFromCombinedFile(path);
} }
/**
* @brief Shader
* @return truefalse
*/
bool ShaderManager::loadBuiltinShaders() { bool ShaderManager::loadBuiltinShaders() {
if (!initialized_) { E2D_LOG_INFO("Loading builtin shaders...");
E2D_LOG_ERROR("ShaderManager not initialized");
return false;
}
bool allSuccess = true; const std::vector<std::string> builtinShaders = {
const char* builtinNames[] = {
"sprite", "sprite",
"particle",
"shape", "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 path = shaderDir_ + "builtin/" + name + ".shader";
std::string shaderName = std::string("builtin_") + name; if (ShaderLoader::fileExists(path)) {
auto result = loader_.loadFromCombinedFile(path);
if (!loadFromCombinedFile(path)) { if (result.success) {
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path); auto shader = factory_->createFromSource("builtin_" + name, result.vertSource, result.fragSource);
allSuccess = false; if (shader) {
} else { ShaderInfo info;
auto it = shaders_.find(name); info.shader = shader;
if (it != shaders_.end()) { info.vertSource = result.vertSource;
shaders_[shaderName] = it->second; info.fragSource = result.fragSource;
shaders_.erase(it); shaders_["builtin_" + name] = std::move(info);
loaded++;
E2D_LOG_DEBUG("Loaded builtin shader: {}", name);
}
} }
} }
} }
if (allSuccess) { E2D_LOG_INFO("Builtin shaders loaded: {}/{}", loaded, builtinShaders.size());
E2D_LOG_INFO("All builtin shaders loaded"); return loaded > 0;
}
return allSuccess;
} }
/** void ShaderManager::createBuiltinShaderSources() {
* @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 (shader) {
E2D_LOG_DEBUG("Shader loaded from cache: {}", name);
}
return shader;
} }
/**
* @brief
* @param shaderName Shader名称
* @param event
*/
void ShaderManager::handleFileChange(const std::string& shaderName, const FileChangeEvent& event) { void ShaderManager::handleFileChange(const std::string& shaderName, const FileChangeEvent& event) {
E2D_LOG_DEBUG("Shader file changed: {} -> {}", shaderName, event.filepath); if (hotReloadEnabled_) {
reload(shaderName); reload(shaderName);
}
} }
} // namespace extra2d }

View File

@ -1,4 +1,5 @@
#include <extra2d/modules/config_module.h> #include <extra2d/modules/config_module.h>
#include <extra2d/core/module_macros.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {
@ -51,3 +52,5 @@ void ConfigModule::destroyModule() {
} }
} // namespace extra2d } // namespace extra2d
E2D_MODULE(ConfigModule, 0)

View File

@ -1,4 +1,5 @@
#include <extra2d/modules/input_module.h> #include <extra2d/modules/input_module.h>
#include <extra2d/core/module_macros.h>
#include <extra2d/core/service_locator.h> #include <extra2d/core/service_locator.h>
#include <extra2d/services/event_service.h> #include <extra2d/services/event_service.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
@ -24,8 +25,18 @@ void InputModule::setupModule() {
return; return;
} }
// 如果 window 还没设置,延迟初始化
if (!window_) {
E2D_LOG_INFO("Input module waiting for window");
setInitialized(true);
return;
}
initializeWithWindow();
}
void InputModule::initializeWithWindow() {
if (!window_) { if (!window_) {
E2D_LOG_ERROR("Window not set - cannot initialize input module");
return; return;
} }
@ -49,13 +60,24 @@ void InputModule::setupModule() {
} }
} }
setInitialized(true);
E2D_LOG_INFO("Input module initialized"); E2D_LOG_INFO("Input module initialized");
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone); E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);
E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity); E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity);
E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled"); 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() { void InputModule::destroyModule() {
if (!isInitialized()) { if (!isInitialized()) {
return; return;
@ -78,3 +100,5 @@ void InputModule::onUpdate(UpdateContext& ctx) {
} }
} // namespace extra2d } // namespace extra2d
E2D_MODULE(InputModule, 30, "WindowModule")

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,5 @@
#include <extra2d/modules/render_module.h> #include <extra2d/modules/render_module.h>
#include <extra2d/core/module_macros.h>
#include <extra2d/graphics/opengl/gl_shader.h> #include <extra2d/graphics/opengl/gl_shader.h>
#include <extra2d/graphics/shader_manager.h> #include <extra2d/graphics/shader_manager.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
@ -29,8 +30,18 @@ void RenderModule::setupModule() {
return; return;
} }
// 如果 window 还没设置,延迟初始化
if (!window_) {
E2D_LOG_INFO("Render module waiting for window");
setInitialized(true);
return;
}
initializeWithWindow();
}
void RenderModule::initializeWithWindow() {
if (!window_) { if (!window_) {
E2D_LOG_ERROR("Render module requires window to be set");
return; return;
} }
@ -55,10 +66,21 @@ void RenderModule::setupModule() {
return; return;
} }
setInitialized(true);
E2D_LOG_INFO("Render module initialized"); 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() { void RenderModule::destroyModule() {
if (!isInitialized()) { if (!isInitialized()) {
return; return;
@ -96,3 +118,5 @@ void RenderModule::afterRender(RenderContext& ctx) {
} }
} // namespace extra2d } // namespace extra2d
E2D_MODULE(RenderModule, 40, "WindowModule")

View File

@ -1,5 +1,6 @@
#include <extra2d/modules/window_module.h> #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 <extra2d/utils/logger.h>
#include <SDL.h> #include <SDL.h>
@ -119,3 +120,5 @@ bool WindowModule::createWindow(const WindowConfigData& config) {
} }
} // namespace extra2d } // namespace extra2d
E2D_MODULE(WindowModule, 20, "ConfigModule")

View File

@ -1,4 +1,4 @@
#include <extra2d/platform/platform_module.h> #include <extra2d/platform/backend_factory.h>
namespace extra2d { 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) { UniquePtr<IWindow> BackendFactory::createWindow(const std::string& name) {
auto& reg = registry(); auto it = registry().find(name);
auto it = reg.find(name); if (it != registry().end() && it->second.windowFn) {
if (it != reg.end() && it->second.windowFn) {
return it->second.windowFn(); return it->second.windowFn();
} }
return nullptr; return nullptr;
} }
UniquePtr<IInput> BackendFactory::createInput(const std::string& name) { UniquePtr<IInput> BackendFactory::createInput(const std::string& name) {
auto& reg = registry(); auto it = registry().find(name);
auto it = reg.find(name); if (it != registry().end() && it->second.inputFn) {
if (it != reg.end() && it->second.inputFn) {
return it->second.inputFn(); return it->second.inputFn();
} }
return nullptr; return nullptr;
@ -41,4 +39,4 @@ bool BackendFactory::has(const std::string& name) {
return registry().find(name) != registry().end(); return registry().find(name) != registry().end();
} }
} // namespace extra2d }

View File

@ -1,6 +1,6 @@
#include "sdl2_window.h" #include "sdl2_window.h"
#include "sdl2_input.h" #include "sdl2_input.h"
#include <extra2d/platform/platform_module.h> #include <extra2d/platform/backend_factory.h>
namespace extra2d { namespace extra2d {

View File

@ -4,7 +4,8 @@
namespace extra2d { namespace extra2d {
SDL2Input::SDL2Input() { SDL2Input::SDL2Input()
: initialized_(false) {
keyCurrent_.fill(false); keyCurrent_.fill(false);
keyPrevious_.fill(false); keyPrevious_.fill(false);
mouseCurrent_.fill(false); mouseCurrent_.fill(false);
@ -18,6 +19,8 @@ SDL2Input::~SDL2Input() {
} }
void SDL2Input::init() { void SDL2Input::init() {
if (initialized_) return;
E2D_LOG_INFO("SDL2Input initialized"); E2D_LOG_INFO("SDL2Input initialized");
if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) { if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) {
@ -25,10 +28,14 @@ void SDL2Input::init() {
} }
openGamepad(); openGamepad();
initialized_ = true;
} }
void SDL2Input::shutdown() { void SDL2Input::shutdown() {
if (!initialized_) return;
closeGamepad(); closeGamepad();
initialized_ = false;
E2D_LOG_INFO("SDL2Input shutdown"); E2D_LOG_INFO("SDL2Input shutdown");
} }

View File

@ -98,6 +98,7 @@ private:
float rightTrigger_ = 0.0f; float rightTrigger_ = 0.0f;
float deadzone_ = 0.15f; float deadzone_ = 0.15f;
bool initialized_ = false;
EventCallback eventCallback_; EventCallback eventCallback_;
}; };

View File

@ -134,6 +134,30 @@ flowchart TB
| Input | 输入处理 | 30 | | Input | 输入处理 | 30 |
| Render | 渲染系统 | 40 | | 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()
```
### 服务系统 ### 服务系统
| 服务 | 职责 | 优先级 | | 服务 | 职责 | 优先级 |

View File

@ -4,33 +4,267 @@
Extra2D 采用模块化架构设计(参考 Kiwano所有核心功能通过模块系统和服务系统管理。系统提供 Extra2D 采用模块化架构设计(参考 Kiwano所有核心功能通过模块系统和服务系统管理。系统提供
- **自动发现注册**:模块定义即注册,无需手动调用
- **统一的生命周期管理**:初始化、更新、渲染、关闭 - **统一的生命周期管理**:初始化、更新、渲染、关闭
- **优先级排序**:确保模块按正确顺序初始化 - **优先级排序**:确保模块按正确顺序初始化
- **显式注册**:通过 `Application::use()` 注册模块
- **Context 模式**:使用上下文对象遍历模块链 - **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 │ ---
│ (协调模块和服务,通过服务定位器获取依赖) │
└─────────────────────────────────────────────────────────────┘ ## 模块自动注册
┌───────────────┴───────────────┐ ### E2D_MODULE 宏
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐ 模块通过 `E2D_MODULE` 宏自动注册,无需手动调用任何注册函数:
│ Modules │ │ ServiceLocator │
│ (模块列表,按优先级排序) │ │ (服务定位器,管理运行时服务) │ ```cpp
└─────────────────────────────┘ └─────────────────────────────┘ // config_module.cpp
│ │ #include "config_module.h"
┌─────┴─────┐ ┌───────┴───────┐
▼ ▼ ▼ ▼ namespace extra2d {
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ // 模块实现...
│ Config │ │ Window │ │ Scene │ │ Timer │ }
│ Module │ │ Module │ │ Service │ │ Service │
└───────────┘ └───────────┘ └───────────┘ └───────────┘ // 在文件末尾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 管理 | | 生命周期 | Application 管理 | ServiceLocator 管理 |
| 注册方式 | `app.use(module)` | `locator.registerService()` | | 注册方式 | 自动发现 | `locator.registerService()` |
| 可替换性 | 编译时确定 | 运行时可替换 | | 可替换性 | 编译时确定 | 运行时可替换 |
| 示例 | Window, Render, Input | Scene, Timer, Event, Camera | | 示例 | Window, Render, Input | Scene, Timer, Event, Camera |
@ -164,116 +398,78 @@ class EventContext : public ModuleContext {
## 创建新模块 ## 创建新模块
### 简单示例 ### 完整示例(静态链接)
**hello_module.h:**
```cpp ```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 #pragma once
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <string> #include <string>
struct MyModuleConfig { namespace extra2d {
struct HelloModuleConfig {
std::string greeting = "Hello!"; std::string greeting = "Hello!";
int repeatCount = 1; int repeatCount = 1;
}; };
class MyModule : public extra2d::Module { class HelloModule : public Module {
public: public:
MyModule(); HelloModule();
~MyModule() override; ~HelloModule() override;
const char* getName() const override { return "MyModule"; } const char* getName() const override { return "HelloModule"; }
int getPriority() const override { return 1000; } int getPriority() const override { return 1000; }
void setupModule() override; void setupModule() override;
void destroyModule() 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; void sayHello() const;
private: private:
MyModuleConfig config_; HelloModuleConfig config_;
float time_ = 0.0f; float time_ = 0.0f;
}; };
} // namespace extra2d
``` ```
**hello_module.cpp:**
```cpp ```cpp
// my_module.cpp #include "hello_module.h"
#include "my_module.h"
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
MyModule::MyModule() : Module() {} namespace extra2d {
MyModule::~MyModule() { HelloModule::HelloModule() = default;
HelloModule::~HelloModule() {
if (isInitialized()) { if (isInitialized()) {
destroyModule(); destroyModule();
} }
} }
void MyModule::setupModule() { void HelloModule::setupModule() {
if (isInitialized()) return; if (isInitialized()) return;
setInitialized(true); setInitialized(true);
E2D_LOG_INFO("MyModule initialized"); E2D_LOG_INFO("HelloModule initialized");
E2D_LOG_INFO(" Greeting: {}", config_.greeting); E2D_LOG_INFO(" Greeting: {}", config_.greeting);
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount); E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
sayHello(); sayHello();
} }
void MyModule::destroyModule() { void HelloModule::destroyModule() {
if (!isInitialized()) return; if (!isInitialized()) return;
E2D_LOG_INFO("MyModule shutdown"); E2D_LOG_INFO("HelloModule shutdown");
setInitialized(false); setInitialized(false);
} }
void MyModule::onUpdate(extra2d::UpdateContext& ctx) { void HelloModule::onUpdate(UpdateContext& ctx) {
if (!isInitialized()) { if (!isInitialized()) {
ctx.next(); ctx.next();
return; return;
@ -289,11 +485,49 @@ void MyModule::onUpdate(extra2d::UpdateContext& ctx) {
ctx.next(); ctx.next();
} }
void MyModule::sayHello() const { void HelloModule::sayHello() const {
for (int i = 0; i < config_.repeatCount; ++i) { 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 模块 ### Window 模块
**职责**:窗口创建和管理 **职责**:窗口创建和管理
@ -351,17 +556,6 @@ app.use(loggerModule);
**后端**:统一使用 SDL2 **后端**:统一使用 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 模块 ### Input 模块
@ -370,15 +564,6 @@ app.use(windowModule);
**优先级**30 **优先级**30
```cpp
extra2d::InputModule inputModule;
extra2d::InputConfigData inputConfig;
inputConfig.deadzone = 0.15f;
inputConfig.enableVibration = true;
inputModule.setInputConfig(inputConfig);
app.use(inputModule);
```
--- ---
### Render 模块 ### Render 模块
@ -387,15 +572,6 @@ app.use(inputModule);
**优先级**40 **优先级**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 { - 确保使用了 `--whole-archive`
AspectRatio, // 保持宽高比,可能有黑边 - 自定义模块要直接编译到可执行文件
Stretch, // 拉伸填满整个窗口
Center, // 居中显示,不缩放 **动态链接:**
Custom // 自定义缩放和偏移 - 确保使用了 `E2D_MODULE_EXPORT`
}; - 确保调用了 `E2D_CALL_FORCE_LINK`
### Q: 链接错误 "undefined reference"
检查链接顺序,`--whole-archive` 要在 `add_deps` 之前。
### Q: 模块初始化顺序错误?
使用 `getPriority()` 控制顺序,数字小的先初始化。
### Q: 如何调试模块注册?
查看日志输出:
``` ```
[INFO ] ModuleRegistry: 4 modules registered
### 使用 CameraService 配置视口 [INFO ] - ConfigModule (priority: 0)
[INFO ] - WindowModule (priority: 20)
```cpp [INFO ] - InputModule (priority: 30)
auto cameraService = app.camera(); [INFO ] - RenderModule (priority: 40)
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);
}
``` ```
--- ---
@ -653,3 +818,7 @@ void onUpdate(UpdateContext& ctx) override {
ctx.next(); ctx.next();
} }
``` ```
### 4. 静态链接优先
静态链接更简单,无需额外配置,推荐用于大多数场景。

View File

@ -7,6 +7,8 @@
* - * -
* - * -
* - * -
*
* E2D_MODULE app.use()
*/ */
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
@ -111,6 +113,8 @@ int main(int argc, char *argv[]) {
Application &app = Application::get(); Application &app = Application::get();
// 模块已通过 E2D_MODULE 宏自动注册,无需调用 app.use()
if (!app.init(config)) { if (!app.init(config)) {
std::cerr << "Failed to initialize application!" << std::endl; std::cerr << "Failed to initialize application!" << std::endl;
return -1; return -1;
@ -166,9 +170,6 @@ int main(int argc, char *argv[]) {
app.run(); app.run();
std::cout << "Shutting down..." << std::endl;
app.shutdown();
std::cout << "Goodbye!" << std::endl; std::cout << "Goodbye!" << std::endl;
return 0; return 0;
} }

View File

@ -1,4 +1,5 @@
#include "hello_module.h" #include "hello_module.h"
#include <extra2d/core/module_macros.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {
@ -77,3 +78,5 @@ void HelloModule::sayHello() const {
} }
} // namespace extra2d } // namespace extra2d
E2D_MODULE(HelloModule, 1000)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <string> #include <string>
@ -9,9 +10,9 @@ namespace extra2d {
* @brief Hello模块配置数据结构 * @brief Hello模块配置数据结构
*/ */
struct HelloModuleConfigData { struct HelloModuleConfigData {
std::string greeting = "Hello, Extra2D!"; std::string greeting = "Hello, Extra2D!";
int repeatCount = 1; int repeatCount = 1;
bool enableLogging = true; bool enableLogging = true;
}; };
/** /**
@ -20,58 +21,58 @@ struct HelloModuleConfigData {
* *
* 1. Module * 1. Module
* 2. * 2.
* 3. 使 Application::use() * 3. 使 E2D_MODULE
*/ */
class HelloModule : public Module { class HelloModule : public Module {
public: public:
/** /**
* @brief * @brief
*/ */
HelloModule(); HelloModule();
/** /**
* @brief * @brief
*/ */
~HelloModule() override; ~HelloModule() override;
/** /**
* @brief * @brief
*/ */
const char* getName() const override { return "HelloModule"; } const char *getName() const override { return "HelloModule"; }
/** /**
* @brief * @brief
*/ */
int getPriority() const override { return 1000; } int getPriority() const override { return 1000; }
/** /**
* @brief * @brief
*/ */
void setupModule() override; void setupModule() override;
/** /**
* @brief * @brief
*/ */
void destroyModule() override; void destroyModule() override;
/** /**
* @brief * @brief
*/ */
void onUpdate(UpdateContext& ctx) override; void onUpdate(UpdateContext &ctx) override;
/** /**
* @brief * @brief
*/ */
void setConfig(const HelloModuleConfigData& config) { config_ = config; } void setConfig(const HelloModuleConfigData &config) { config_ = config; }
/** /**
* @brief * @brief
*/ */
void sayHello() const; void sayHello() const;
private: private:
HelloModuleConfigData config_; HelloModuleConfigData config_;
float time_ = 0.0f; float time_ = 0.0f;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -16,10 +16,11 @@ public:
Scene::onEnter(); Scene::onEnter();
addListener(EventType::KeyPress, [](Event &e) { addListener(EventType::KeyPress, [](Event &e) {
auto &keyEvent = std::get<KeyEvent>(e.data); 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)) { if (keyEvent.scancode == static_cast<int>(Key::Escape)) {
e.handled = true; e.handled = true;
E2D_LOG_INFO("ESC !!!exit"); E2D_LOG_INFO("ESC pressed, exiting...");
Application::get().quit(); Application::get().quit();
} }
}); });
@ -33,14 +34,15 @@ private:
/** /**
* @brief * @brief
*
*
*/ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
Application &app = Application::get(); Application &app = Application::get();
HelloModule helloModule;
app.use(helloModule);
AppConfig appConfig; AppConfig appConfig;
appConfig.appName = "HelloModule Example"; appConfig.appName = "HelloModule Example";
appConfig.appVersion = "1.0.0"; appConfig.appVersion = "1.0.0";
@ -55,7 +57,5 @@ int main(int argc, char *argv[]) {
app.run(); app.run();
app.shutdown();
return 0; return 0;
} }

View File

@ -145,7 +145,11 @@ target("demo_basic")
set_kind("binary") set_kind("binary")
set_default(false) set_default(false)
-- 强制链接整个静态库(保留静态初始化的模块注册变量)
add_ldflags("-Wl,--whole-archive", {force = true})
add_deps("extra2d") add_deps("extra2d")
add_ldflags("-Wl,--no-whole-archive", {force = true})
add_files("examples/basic/main.cpp") add_files("examples/basic/main.cpp")
-- 平台配置 -- 平台配置
@ -165,13 +169,42 @@ target("demo_basic")
after_build(install_shaders) after_build(install_shaders)
target_end() 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 示例 - 展示如何创建自定义模块 -- Hello Module 示例 - 展示如何创建自定义模块
-- 注意:静态链接时,自定义模块需要直接编译到可执行文件中
target("demo_hello_module") target("demo_hello_module")
set_kind("binary") set_kind("binary")
set_default(false) set_default(false)
-- 强制链接引擎静态库
add_ldflags("-Wl,--whole-archive", {force = true})
add_deps("extra2d") 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") add_includedirs("examples/hello_module")
-- 平台配置 -- 平台配置

View File

@ -17,7 +17,7 @@ end
-- 定义 Extra2D 引擎库目标 -- 定义 Extra2D 引擎库目标
function define_extra2d_engine() function define_extra2d_engine()
target("extra2d") target("extra2d")
set_kind("static") set_kind("static") -- 改回静态库
-- 引擎核心源文件 -- 引擎核心源文件
add_files("Extra2D/src/**.cpp") add_files("Extra2D/src/**.cpp")