From b55d27961192a533bf0004ebfb8668abb2d98918 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sun, 15 Feb 2026 17:00:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=A4=BA=E4=BE=8B):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1=E5=9D=97=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E5=92=8C=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增HelloModule示例展示如何创建自定义模块,包含: 1. 模块配置类实现 2. 模块初始化器实现 3. 自动注册机制 4. JSON配置支持 5. 场景中使用模块的示例 同时更新模块系统文档,详细说明自定义模块开发流程 --- .../extra2d/graphics/opengl/gl_sprite_batch.h | 17 +- Extra2D/src/app/application.cpp | 576 +++++++++--------- docs/module_system.md | 213 +++++++ examples/hello_module/config.json | 18 + examples/hello_module/hello_module.cpp | 183 ++++++ examples/hello_module/hello_module.h | 148 +++++ examples/hello_module/main.cpp | 101 +++ xmake.lua | 26 + 8 files changed, 990 insertions(+), 292 deletions(-) create mode 100644 examples/hello_module/config.json create mode 100644 examples/hello_module/hello_module.cpp create mode 100644 examples/hello_module/hello_module.h create mode 100644 examples/hello_module/main.cpp diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h index 5372881..3c43cba 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,7 +8,6 @@ #include #include #include -#include #include @@ -52,10 +52,11 @@ public: void end(); // 批量绘制接口 - 用于自动批处理 - void drawBatch(const Texture& texture, const std::vector& sprites); - + void drawBatch(const Texture &texture, + const std::vector &sprites); + // 立即绘制(不缓存) - void drawImmediate(const Texture& texture, const SpriteData& data); + void drawImmediate(const Texture &texture, const SpriteData &data); // 统计 uint32_t getDrawCallCount() const { return drawCallCount_; } @@ -63,7 +64,7 @@ public: uint32_t getBatchCount() const { return batchCount_; } // 检查是否需要刷新 - bool needsFlush(const Texture& texture, bool isSDF) const; + bool needsFlush(const Texture &texture, bool isSDF) const; private: GLuint vao_; @@ -78,7 +79,7 @@ private: const Texture *currentTexture_; bool currentIsSDF_; glm::mat4 viewProjection_; - + // 缓存上一帧的 viewProjection,避免重复设置 glm::mat4 cachedViewProjection_; bool viewProjectionDirty_ = true; @@ -89,9 +90,9 @@ private: void flush(); void setupShader(); - + // 添加顶点到缓冲区 - void addVertices(const SpriteData& data); + void addVertices(const SpriteData &data); }; } // namespace extra2d diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index 285d591..4df5b1e 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -1,17 +1,18 @@ #include #include #include -#include #include +#include #include #include #include #include #include +#include +#include #include #include -#include -#include + #include #include @@ -20,384 +21,391 @@ namespace extra2d { static double getTimeSeconds() { #ifdef __SWITCH__ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) + - static_cast(ts.tv_nsec) / 1000000000.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec) / 1000000000.0; #else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); + using namespace std::chrono; + auto now = steady_clock::now(); + auto duration = now.time_since_epoch(); + return duration_cast>(duration).count(); #endif } -Application& Application::get() { - static Application instance; - return instance; -} - -Application::~Application() { - shutdown(); +Application &Application::get() { + static Application instance; + return instance; } bool Application::init() { - AppConfig cfg; - return init(cfg); + AppConfig cfg; + return init(cfg); } -bool Application::init(const AppConfig& config) { - if (initialized_) { - return true; +bool Application::init(const AppConfig &config) { + if (initialized_) { + return true; + } + + register_config_module(); + register_platform_module(); + register_window_module(); + register_input_module(); + register_render_module(); + + auto *configInit = + ModuleRegistry::instance().getInitializer(get_config_module_id()); + if (configInit) { + auto *cfgInit = dynamic_cast(configInit); + if (cfgInit) { + cfgInit->setAppConfig(config); } + } - register_config_module(); - register_platform_module(); - register_window_module(); - register_input_module(); - register_render_module(); - - auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id()); - if (configInit) { - auto* cfgInit = dynamic_cast(configInit); - if (cfgInit) { - cfgInit->setAppConfig(config); - } - } - - return initModules(); + return initModules(); } -bool Application::init(const std::string& configPath) { - if (initialized_) { - return true; +bool Application::init(const std::string &configPath) { + if (initialized_) { + return true; + } + + register_config_module(); + register_platform_module(); + register_window_module(); + register_input_module(); + register_render_module(); + + auto *configInit = + ModuleRegistry::instance().getInitializer(get_config_module_id()); + if (configInit) { + auto *cfgInit = dynamic_cast(configInit); + if (cfgInit) { + cfgInit->setConfigPath(configPath); } + } - register_config_module(); - register_platform_module(); - register_window_module(); - register_input_module(); - register_render_module(); - - auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id()); - if (configInit) { - auto* cfgInit = dynamic_cast(configInit); - if (cfgInit) { - cfgInit->setConfigPath(configPath); - } - } - - return initModules(); + return initModules(); } void Application::registerCoreServices() { - auto& locator = ServiceLocator::instance(); + auto &locator = ServiceLocator::instance(); - if (!locator.hasService()) { - locator.registerService(makeShared()); - } + if (!locator.hasService()) { + locator.registerService(makeShared()); + } - if (!locator.hasService()) { - locator.registerService(makeShared()); - } + if (!locator.hasService()) { + locator.registerService(makeShared()); + } - if (!locator.hasService()) { - auto cameraService = makeShared(); - if (window_) { - cameraService->setViewport(0, static_cast(window_->width()), - static_cast(window_->height()), 0); - ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(window_->width()); - vpConfig.logicHeight = static_cast(window_->height()); - vpConfig.mode = ViewportMode::AspectRatio; - cameraService->setViewportConfig(vpConfig); - cameraService->updateViewport(window_->width(), window_->height()); - } - locator.registerService(cameraService); + if (!locator.hasService()) { + auto cameraService = makeShared(); + if (window_) { + cameraService->setViewport(0, static_cast(window_->width()), + static_cast(window_->height()), 0); + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(window_->width()); + vpConfig.logicHeight = static_cast(window_->height()); + vpConfig.mode = ViewportMode::AspectRatio; + cameraService->setViewportConfig(vpConfig); + cameraService->updateViewport(window_->width(), window_->height()); } + locator.registerService(cameraService); + } } bool Application::initModules() { - auto& locator = ServiceLocator::instance(); - - if (!locator.hasService()) { - locator.registerService(makeShared()); - } - - auto initOrder = ModuleRegistry::instance().getInitializationOrder(); - - for (ModuleId moduleId : initOrder) { - auto* initializer = ModuleRegistry::instance().getInitializer(moduleId); - if (!initializer) { - continue; - } + auto &locator = ServiceLocator::instance(); - auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId); - if (!moduleConfig) { - continue; - } + if (!locator.hasService()) { + locator.registerService(makeShared()); + } - auto info = moduleConfig->getModuleInfo(); - if (!info.enabled) { - continue; - } + auto initOrder = ModuleRegistry::instance().getInitializationOrder(); - if (info.name == "Render") { - continue; - } - - if (!initializer->initialize(moduleConfig)) { - return false; - } + for (ModuleId moduleId : initOrder) { + auto *initializer = ModuleRegistry::instance().getInitializer(moduleId); + if (!initializer) { + continue; } - auto* windowInit = ModuleRegistry::instance().getInitializer(get_window_module_id()); - if (!windowInit || !windowInit->isInitialized()) { + auto *moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId); + if (!moduleConfig) { + continue; + } + + auto info = moduleConfig->getModuleInfo(); + if (!info.enabled) { + continue; + } + + if (info.name == "Render") { + continue; + } + + if (!initializer->initialize(moduleConfig)) { + return false; + } + } + + auto *windowInit = + ModuleRegistry::instance().getInitializer(get_window_module_id()); + if (!windowInit || !windowInit->isInitialized()) { + return false; + } + + auto *windowModule = dynamic_cast(windowInit); + if (!windowModule) { + return false; + } + + window_ = windowModule->getWindow(); + if (!window_) { + return false; + } + + auto *renderInit = + ModuleRegistry::instance().getInitializer(get_render_module_id()); + if (renderInit) { + auto *renderModule = dynamic_cast(renderInit); + if (renderModule) { + renderModule->setWindow(window_); + + auto *renderConfig = + ModuleRegistry::instance().getModuleConfig(get_render_module_id()); + if (renderConfig && !renderInit->initialize(renderConfig)) { return false; + } } + } - auto* windowModule = dynamic_cast(windowInit); - if (!windowModule) { - return false; - } + registerCoreServices(); - window_ = windowModule->getWindow(); - if (!window_) { - return false; - } + if (!ServiceLocator::instance().initializeAll()) { + return false; + } - auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); - if (renderInit) { - auto* renderModule = dynamic_cast(renderInit); - if (renderModule) { - renderModule->setWindow(window_); - - auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id()); - if (renderConfig && !renderInit->initialize(renderConfig)) { - return false; - } + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService && window_) { + window_->onResize([cameraService](int width, int height) { + cameraService->updateViewport(width, height); + cameraService->applyViewportAdapter(); + + auto sceneService = + ServiceLocator::instance().getService(); + if (sceneService) { + auto currentScene = sceneService->getCurrentScene(); + if (currentScene) { + currentScene->setViewportSize(static_cast(width), + static_cast(height)); } - } + } + }); + } - registerCoreServices(); + initialized_ = true; + running_ = true; - if (!ServiceLocator::instance().initializeAll()) { - return false; - } - - auto cameraService = ServiceLocator::instance().getService(); - if (cameraService && window_) { - window_->onResize([this, cameraService](int width, int height) { - cameraService->updateViewport(width, height); - cameraService->applyViewportAdapter(); - - auto sceneService = ServiceLocator::instance().getService(); - if (sceneService) { - auto currentScene = sceneService->getCurrentScene(); - if (currentScene) { - currentScene->setViewportSize(static_cast(width), - static_cast(height)); - } - } - }); - } - - initialized_ = true; - running_ = true; - - return true; + return true; } void Application::shutdown() { - if (!initialized_) - return; + if (!initialized_) + return; - VRAMMgr::get().printStats(); + VRAMMgr::get().printStats(); - ServiceLocator::instance().clear(); + ServiceLocator::instance().clear(); - window_ = nullptr; + window_ = nullptr; - auto initOrder = ModuleRegistry::instance().getInitializationOrder(); - - for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) { - ModuleId moduleId = *it; - auto* initializer = ModuleRegistry::instance().getInitializer(moduleId); - if (initializer && initializer->isInitialized()) { - initializer->shutdown(); - } + auto initOrder = ModuleRegistry::instance().getInitializationOrder(); + + for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) { + ModuleId moduleId = *it; + auto *initializer = ModuleRegistry::instance().getInitializer(moduleId); + if (initializer && initializer->isInitialized()) { + initializer->shutdown(); } + } - initialized_ = false; - running_ = false; + initialized_ = false; + running_ = false; +} + +Application::~Application() { + if (initialized_) { + shutdown(); + } } void Application::run() { - if (!initialized_) { - return; - } + if (!initialized_) { + return; + } - lastFrameTime_ = getTimeSeconds(); + lastFrameTime_ = getTimeSeconds(); - while (running_ && !window_->shouldClose()) { - mainLoop(); - } + while (running_ && !window_->shouldClose()) { + mainLoop(); + } } void Application::quit() { - shouldQuit_ = true; - running_ = false; + shouldQuit_ = true; + running_ = false; } void Application::pause() { - if (!paused_) { - paused_ = true; - ServiceLocator::instance().pauseAll(); - } + if (!paused_) { + paused_ = true; + ServiceLocator::instance().pauseAll(); + } } void Application::resume() { - if (paused_) { - paused_ = false; - ServiceLocator::instance().resumeAll(); - lastFrameTime_ = getTimeSeconds(); - } + if (paused_) { + paused_ = false; + ServiceLocator::instance().resumeAll(); + lastFrameTime_ = getTimeSeconds(); + } } void Application::mainLoop() { - double currentTime = getTimeSeconds(); - deltaTime_ = static_cast(currentTime - lastFrameTime_); - lastFrameTime_ = currentTime; + double currentTime = getTimeSeconds(); + deltaTime_ = static_cast(currentTime - lastFrameTime_); + lastFrameTime_ = currentTime; - totalTime_ += deltaTime_; + totalTime_ += deltaTime_; - frameCount_++; - fpsTimer_ += deltaTime_; - if (fpsTimer_ >= 1.0f) { - currentFps_ = frameCount_; - frameCount_ = 0; - fpsTimer_ -= 1.0f; + frameCount_++; + fpsTimer_ += deltaTime_; + if (fpsTimer_ >= 1.0f) { + currentFps_ = frameCount_; + frameCount_ = 0; + fpsTimer_ -= 1.0f; + } + + window_->poll(); + + auto eventService = ServiceLocator::instance().getService(); + if (eventService) { + eventService->processQueue(); + } + + if (!paused_) { + update(); + } + + render(); + + const auto &appConfig = ConfigManager::instance().appConfig(); + + auto *renderConfig = + ModuleRegistry::instance().getModuleConfig(get_render_module_id()); + auto *renderModuleConfig = + dynamic_cast(renderConfig); + + if (renderModuleConfig && !renderModuleConfig->vsync && + renderModuleConfig->targetFPS > 0) { + double frameEndTime = getTimeSeconds(); + double frameTime = frameEndTime - currentTime; + double target = 1.0 / static_cast(renderModuleConfig->targetFPS); + if (frameTime < target) { + auto sleepSeconds = target - frameTime; + std::this_thread::sleep_for(std::chrono::duration(sleepSeconds)); } + } - window_->poll(); - - auto eventService = ServiceLocator::instance().getService(); - if (eventService) { - eventService->processQueue(); - } - - if (!paused_) { - update(); - } - - render(); - - const auto& appConfig = ConfigManager::instance().appConfig(); - - auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id()); - auto* renderModuleConfig = dynamic_cast(renderConfig); - - if (renderModuleConfig && !renderModuleConfig->vsync && renderModuleConfig->targetFPS > 0) { - double frameEndTime = getTimeSeconds(); - double frameTime = frameEndTime - currentTime; - double target = 1.0 / static_cast(renderModuleConfig->targetFPS); - if (frameTime < target) { - auto sleepSeconds = target - frameTime; - std::this_thread::sleep_for(std::chrono::duration(sleepSeconds)); - } - } - - ConfigManager::instance().update(deltaTime_); + ConfigManager::instance().update(deltaTime_); } -void Application::update() { - ServiceLocator::instance().updateAll(deltaTime_); -} +void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); } void Application::render() { - auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); - RenderBackend* renderer = nullptr; - if (renderInit) { - auto* renderModule = dynamic_cast(renderInit); - if (renderModule) { - renderer = renderModule->getRenderer(); - } + auto *renderInit = + ModuleRegistry::instance().getInitializer(get_render_module_id()); + RenderBackend *renderer = nullptr; + if (renderInit) { + auto *renderModule = dynamic_cast(renderInit); + if (renderModule) { + renderer = renderModule->getRenderer(); } + } - if (!renderer) { - return; - } + if (!renderer) { + return; + } - auto cameraService = ServiceLocator::instance().getService(); - if (cameraService) { - const auto& vp = cameraService->getViewportResult().viewport; - renderer->setViewport( - static_cast(vp.origin.x), static_cast(vp.origin.y), - static_cast(vp.size.width), static_cast(vp.size.height)); - - renderer->setViewProjection(cameraService->getViewProjectionMatrix()); - } else { - renderer->setViewport(0, 0, window_->width(), window_->height()); - } + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService) { + const auto &vp = cameraService->getViewportResult().viewport; + renderer->setViewport( + static_cast(vp.origin.x), static_cast(vp.origin.y), + static_cast(vp.size.width), static_cast(vp.size.height)); - auto sceneService = ServiceLocator::instance().getService(); - if (sceneService) { - sceneService->render(*renderer); - } + renderer->setViewProjection(cameraService->getViewProjectionMatrix()); + } else { + renderer->setViewport(0, 0, window_->width(), window_->height()); + } - window_->swap(); + auto sceneService = ServiceLocator::instance().getService(); + if (sceneService) { + sceneService->render(*renderer); + } + + window_->swap(); } -IInput& Application::input() { - return *window_->input(); -} +IInput &Application::input() { return *window_->input(); } -RenderBackend& Application::renderer() { - auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); - if (renderInit) { - auto* renderModule = dynamic_cast(renderInit); - if (renderModule && renderModule->getRenderer()) { - return *renderModule->getRenderer(); - } +RenderBackend &Application::renderer() { + auto *renderInit = + ModuleRegistry::instance().getInitializer(get_render_module_id()); + if (renderInit) { + auto *renderModule = dynamic_cast(renderInit); + if (renderModule && renderModule->getRenderer()) { + return *renderModule->getRenderer(); } - static RenderBackend* dummy = nullptr; - if (!dummy) { - dummy = RenderBackend::create(BackendType::OpenGL).release(); - } - return *dummy; + } + static RenderBackend *dummy = nullptr; + if (!dummy) { + dummy = RenderBackend::create(BackendType::OpenGL).release(); + } + return *dummy; } SharedPtr Application::scenes() { - return ServiceLocator::instance().getService(); + return ServiceLocator::instance().getService(); } SharedPtr Application::timers() { - return ServiceLocator::instance().getService(); + return ServiceLocator::instance().getService(); } SharedPtr Application::events() { - return ServiceLocator::instance().getService(); + return ServiceLocator::instance().getService(); } SharedPtr Application::camera() { - return ServiceLocator::instance().getService(); + return ServiceLocator::instance().getService(); } void Application::enterScene(Ptr scene) { - auto sceneService = ServiceLocator::instance().getService(); - if (sceneService && scene) { - scene->setViewportSize(static_cast(window_->width()), - static_cast(window_->height())); - sceneService->enterScene(scene); - } + auto sceneService = ServiceLocator::instance().getService(); + if (sceneService && scene) { + scene->setViewportSize(static_cast(window_->width()), + static_cast(window_->height())); + sceneService->enterScene(scene); + } } -ConfigManager& Application::config() { - return ConfigManager::instance(); +ConfigManager &Application::config() { return ConfigManager::instance(); } + +const AppConfig &Application::getConfig() const { + return ConfigManager::instance().appConfig(); } -const AppConfig& Application::getConfig() const { - return ConfigManager::instance().appConfig(); -} - -} +} // namespace extra2d diff --git a/docs/module_system.md b/docs/module_system.md index a1f7bd3..ef074dc 100644 --- a/docs/module_system.md +++ b/docs/module_system.md @@ -966,9 +966,222 @@ window_->onResize([this, cameraService](int width, int height) { ## 示例 完整示例请参考: +- [examples/hello_module/](../../examples/hello_module/) - **Hello World 自定义模块示例** - [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例(场景图、输入事件、视口适配) - [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现 - [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现 - [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现 - [Extra2D/src/scene/node.cpp](../../Extra2D/src/scene/node.cpp) - Node 实现 - [Extra2D/src/scene/shape_node.cpp](../../Extra2D/src/scene/shape_node.cpp) - ShapeNode 实现 + +--- + +## Hello World 自定义模块示例 + +### 示例概述 + +`examples/hello_module/` 目录包含一个完整的自定义模块示例,展示如何: + +1. 定义模块配置数据结构 +2. 实现 `IModuleConfig` 接口 +3. 实现 `IModuleInitializer` 接口 +4. 使用自动注册机制 +5. 支持 JSON 配置 + +### 文件结构 + +``` +examples/hello_module/ +├── hello_module.h # 模块头文件(配置类 + 初始化器类) +├── hello_module.cpp # 模块实现 +├── main.cpp # 示例入口 +└── config.json # 配置文件示例 +``` + +### 核心代码解析 + +#### 1. 配置数据结构 + +```cpp +struct HelloModuleConfigData { + std::string greeting = "Hello, Extra2D!"; + int repeatCount = 1; + bool enableLogging = true; +}; +``` + +#### 2. 配置类实现 + +```cpp +class HelloModuleConfig : public IModuleConfig { +public: + HelloModuleConfigData config; + + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.name = "HelloModule"; + info.version = "1.0.0"; + info.priority = ModulePriority::User; // 用户自定义模块 + return info; + } + + std::string getConfigSectionName() const override { + return "hello"; // 对应 config.json 中的 "hello" 节 + } + + bool validate() const override { + return !config.greeting.empty() && config.repeatCount > 0; + } +}; +``` + +#### 3. 初始化器实现 + +```cpp +class HelloModuleInitializer : public IModuleInitializer { +public: + bool initialize(const IModuleConfig* config) override { + const HelloModuleConfig* cfg = dynamic_cast(config); + if (!cfg || !cfg->validate()) { + return false; + } + + config_ = cfg->config; + initialized_ = true; + + // 执行模块初始化逻辑 + E2D_LOG_INFO("HelloModule initialized: {}", config_.greeting); + return true; + } + + void shutdown() override { + E2D_LOG_INFO("HelloModule shutdown"); + initialized_ = false; + } +}; +``` + +#### 4. 自动注册机制 + +```cpp +namespace { + struct HelloModuleAutoRegister { + HelloModuleAutoRegister() { + register_hello_module(); + } + }; + + static HelloModuleAutoRegister s_autoRegister; // 程序启动时自动执行 +} +``` + +### 配置文件示例 + +```json +{ + "hello": { + "greeting": "Hello from custom module!", + "repeatCount": 3, + "enableLogging": true + } +} +``` + +### 运行示例 + +```bash +xmake run demo_hello_module +``` + +### 预期输出 + +``` +[INFO] HelloModule initialized +[INFO] Greeting: Hello, Extra2D! +[INFO] Repeat Count: 1 +[INFO] Logging Enabled: true +[INFO] [HelloModule] Hello, Extra2D! +[INFO] HelloScene entered +[INFO] [HelloModule] Hello, Extra2D! # 场景 onEnter() 调用 +[INFO] Scene calling HelloModule from onUpdate... +[INFO] [HelloModule] Hello, Extra2D! # 场景每5秒调用 +[INFO] HelloModule shutdown - Goodbye! +``` + +### 在场景中使用模块 + +模块初始化后,可以在场景中通过 `ModuleRegistry` 获取模块实例并调用其功能: + +```cpp +class HelloScene : public Scene { +public: + void onEnter() override { + Scene::onEnter(); + + // 获取模块初始化器 + ModuleId helloId = get_hello_module_id(); + auto* initializer = ModuleRegistry::instance().getInitializer(helloId); + + if (initializer) { + // 转换为具体类型 + auto* helloInit = dynamic_cast(initializer); + if (helloInit) { + // 调用模块功能 + helloInit->sayHello(); + } + } + } + + void onUpdate(float dt) override { + Scene::onUpdate(dt); + + time_ += dt; + + // 每5秒调用一次模块功能 + if (time_ >= 5.0f) { + ModuleId helloId = get_hello_module_id(); + auto* initializer = ModuleRegistry::instance().getInitializer(helloId); + if (initializer) { + auto* helloInit = dynamic_cast(initializer); + if (helloInit) { + helloInit->sayHello(); + } + } + time_ = 0.0f; + } + } + +private: + float time_ = 0.0f; +}; +``` + +### 模块使用流程总结 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 1. 程序启动 │ +│ └── 静态变量 HelloModuleAutoRegister 自动注册模块 │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 2. Application::init() │ +│ └── 遍历 ModuleRegistry 中所有已注册模块 │ +│ └── 按优先级顺序调用 initialize() │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 3. 场景/其他代码使用模块 │ +│ └── ModuleRegistry::getInitializer(moduleId) │ +│ └── dynamic_cast 转换为具体类型 │ +│ └── 调用模块方法 │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 4. Application::shutdown() │ +│ └── 按逆序调用所有模块的 shutdown() │ +└─────────────────────────────────────────────────────────────┘ +``` diff --git a/examples/hello_module/config.json b/examples/hello_module/config.json new file mode 100644 index 0000000..f6661c2 --- /dev/null +++ b/examples/hello_module/config.json @@ -0,0 +1,18 @@ +{ + "app": { + "name": "HelloModule Example", + "version": "1.0.0" + }, + "window": { + "title": "Hello Module Example", + "width": 800, + "height": 600, + "mode": "windowed", + "vsync": true + }, + "hello": { + "greeting": "Hello from custom module!", + "repeatCount": 3, + "enableLogging": true + } +} diff --git a/examples/hello_module/hello_module.cpp b/examples/hello_module/hello_module.cpp new file mode 100644 index 0000000..50456da --- /dev/null +++ b/examples/hello_module/hello_module.cpp @@ -0,0 +1,183 @@ +#include "hello_module.h" +#include +#include +#include + +using json = nlohmann::json; + +namespace extra2d { + +static ModuleId s_helloModuleId = INVALID_MODULE_ID; + +/** + * @brief 获取Hello模块标识符 + */ +ModuleId get_hello_module_id() { + return s_helloModuleId; +} + +/** + * @brief 从JSON加载配置 + */ +bool HelloModuleConfig::loadFromJson(const void* jsonData) { + if (!jsonData) return false; + + try { + const json& j = *static_cast(jsonData); + + if (j.contains("greeting")) { + config.greeting = j["greeting"].get(); + } + if (j.contains("repeatCount")) { + config.repeatCount = j["repeatCount"].get(); + } + if (j.contains("enableLogging")) { + config.enableLogging = j["enableLogging"].get(); + } + + return true; + } catch (...) { + return false; + } +} + +/** + * @brief 保存配置到JSON + */ +bool HelloModuleConfig::saveToJson(void* jsonData) const { + if (!jsonData) return false; + + try { + json& j = *static_cast(jsonData); + j["greeting"] = config.greeting; + j["repeatCount"] = config.repeatCount; + j["enableLogging"] = config.enableLogging; + return true; + } catch (...) { + return false; + } +} + +/** + * @brief 构造函数 + */ +HelloModuleInitializer::HelloModuleInitializer() + : moduleId_(INVALID_MODULE_ID) + , initialized_(false) { +} + +/** + * @brief 析构函数 + */ +HelloModuleInitializer::~HelloModuleInitializer() { + if (initialized_) { + shutdown(); + } +} + +/** + * @brief 获取模块依赖列表 + */ +std::vector HelloModuleInitializer::getDependencies() const { + return {}; +} + +/** + * @brief 初始化模块 + */ +bool HelloModuleInitializer::initialize(const IModuleConfig* config) { + if (initialized_) { + E2D_LOG_WARN("HelloModule already initialized"); + return true; + } + + if (!config) { + E2D_LOG_ERROR("HelloModule config is null"); + return false; + } + + const HelloModuleConfig* helloConfig = dynamic_cast(config); + if (!helloConfig) { + E2D_LOG_ERROR("Invalid HelloModule config type"); + return false; + } + + if (!helloConfig->validate()) { + E2D_LOG_ERROR("HelloModule config validation failed"); + return false; + } + + config_ = helloConfig->config; + + initialized_ = true; + + E2D_LOG_INFO("HelloModule initialized"); + E2D_LOG_INFO(" Greeting: {}", config_.greeting); + E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount); + E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging); + + sayHello(); + + return true; +} + +/** + * @brief 关闭模块 + */ +void HelloModuleInitializer::shutdown() { + if (!initialized_) return; + + if (config_.enableLogging) { + E2D_LOG_INFO("HelloModule shutdown - Goodbye!"); + } + + initialized_ = false; +} + +/** + * @brief 执行问候操作 + */ +void HelloModuleInitializer::sayHello() const { + if (!config_.enableLogging) return; + + for (int i = 0; i < config_.repeatCount; ++i) { + E2D_LOG_INFO("[HelloModule] {}", config_.greeting); + } +} + +/** + * @brief 注册Hello模块 + */ +void register_hello_module() { + if (s_helloModuleId != INVALID_MODULE_ID) { + E2D_LOG_WARN("HelloModule already registered"); + return; + } + + s_helloModuleId = ModuleRegistry::instance().registerModule( + makeUnique(), + []() -> UniquePtr { + auto initializer = makeUnique(); + initializer->setModuleId(s_helloModuleId); + return initializer; + } + ); + + E2D_LOG_DEBUG("HelloModule registered with id: {}", s_helloModuleId); +} + +namespace { + /** + * @brief 自动注册器 + * 在程序启动时自动注册模块 + */ + struct HelloModuleAutoRegister { + HelloModuleAutoRegister() { + register_hello_module(); + } + }; + + static HelloModuleAutoRegister s_autoRegister; +} + +} // namespace extra2d diff --git a/examples/hello_module/hello_module.h b/examples/hello_module/hello_module.h new file mode 100644 index 0000000..9a75f15 --- /dev/null +++ b/examples/hello_module/hello_module.h @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief Hello模块配置数据结构 + */ +struct HelloModuleConfigData { + std::string greeting = "Hello, Extra2D!"; + int repeatCount = 1; + bool enableLogging = true; +}; + +/** + * @brief Hello模块配置类 + * + * 这是一个简单的自定义模块示例,展示如何: + * 1. 定义模块配置数据结构 + * 2. 实现IModuleConfig接口 + * 3. 支持JSON配置加载/保存 + */ +class HelloModuleConfig : public IModuleConfig { +public: + HelloModuleConfigData config; + + /** + * @brief 获取模块信息 + */ + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.id = 0; + info.name = "HelloModule"; + info.version = "1.0.0"; + info.priority = ModulePriority::User; + info.enabled = true; + return info; + } + + /** + * @brief 获取配置节名称 + */ + std::string getConfigSectionName() const override { + return "hello"; + } + + /** + * @brief 验证配置有效性 + */ + bool validate() const override { + return !config.greeting.empty() && config.repeatCount > 0; + } + + /** + * @brief 重置为默认配置 + */ + void resetToDefaults() override { + config = HelloModuleConfigData{}; + } + + /** + * @brief 应用平台约束 + */ + void applyPlatformConstraints(PlatformType platform) override { + (void)platform; + } + + /** + * @brief 从JSON加载配置 + */ + bool loadFromJson(const void* jsonData) override; + + /** + * @brief 保存配置到JSON + */ + bool saveToJson(void* jsonData) const override; +}; + +/** + * @brief Hello模块初始化器 + * + * 负责模块的生命周期管理 + */ +class HelloModuleInitializer : public IModuleInitializer { +public: + HelloModuleInitializer(); + ~HelloModuleInitializer() override; + + /** + * @brief 获取模块标识符 + */ + ModuleId getModuleId() const override { return moduleId_; } + + /** + * @brief 获取模块优先级 + */ + ModulePriority getPriority() const override { return ModulePriority::User; } + + /** + * @brief 获取模块依赖列表 + */ + std::vector getDependencies() const override; + + /** + * @brief 初始化模块 + */ + bool initialize(const IModuleConfig* config) override; + + /** + * @brief 关闭模块 + */ + void shutdown() override; + + /** + * @brief 检查是否已初始化 + */ + bool isInitialized() const override { return initialized_; } + + /** + * @brief 设置模块标识符 + */ + void setModuleId(ModuleId id) { moduleId_ = id; } + + /** + * @brief 执行问候操作 + */ + void sayHello() const; + +private: + ModuleId moduleId_ = INVALID_MODULE_ID; + bool initialized_ = false; + HelloModuleConfigData config_; +}; + +/** + * @brief 获取Hello模块标识符 + */ +ModuleId get_hello_module_id(); + +/** + * @brief 注册Hello模块 + */ +void register_hello_module(); + +} // namespace extra2d diff --git a/examples/hello_module/main.cpp b/examples/hello_module/main.cpp new file mode 100644 index 0000000..738e20d --- /dev/null +++ b/examples/hello_module/main.cpp @@ -0,0 +1,101 @@ +#include "hello_module.h" +#include +#include +#include +#include +#include + +using namespace extra2d; + +/** + * @brief 自定义场景类 + * + * 展示如何在场景中使用自定义模块 + */ +class HelloScene : public Scene { +public: + static Ptr create() { return makeShared(); } + + void onEnter() override { + Scene::onEnter(); + E2D_LOG_INFO("HelloScene entered"); + + setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f)); + + ModuleId helloId = get_hello_module_id(); + auto *initializer = ModuleRegistry::instance().getInitializer(helloId); + if (initializer) { + auto *helloInit = dynamic_cast(initializer); + if (helloInit) { + E2D_LOG_INFO("Scene calling HelloModule from onEnter..."); + helloInit->sayHello(); + } + } + } + + void onUpdate(float dt) override { + Scene::onUpdate(dt); + + time_ += dt; + + if (time_ >= 5.0f) { + ModuleId helloId = get_hello_module_id(); + auto *initializer = ModuleRegistry::instance().getInitializer(helloId); + if (initializer) { + auto *helloInit = dynamic_cast(initializer); + if (helloInit) { + E2D_LOG_INFO("Scene calling HelloModule from onUpdate..."); + helloInit->sayHello(); + } + } + time_ = 0.0f; + } + } + +private: + float time_ = 0.0f; +}; + +/** + * @brief 应用程序入口 + */ +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + E2D_LOG_INFO("=== Hello Module Example ==="); + E2D_LOG_INFO("This example demonstrates how to create a custom module"); + E2D_LOG_INFO(""); + + Application &app = Application::get(); + + AppConfig appConfig; + appConfig.appName = "HelloModule Example"; + appConfig.appVersion = "1.0.0"; + + if (!app.init(appConfig)) { + E2D_LOG_ERROR("Failed to initialize application"); + return 1; + } + + E2D_LOG_INFO(""); + E2D_LOG_INFO("Application initialized successfully"); + E2D_LOG_INFO("HelloModule should have been auto-registered and initialized"); + E2D_LOG_INFO(""); + + auto scene = HelloScene::create(); + app.enterScene(scene); + + E2D_LOG_INFO("Starting main loop..."); + E2D_LOG_INFO("Press ESC or close window to exit"); + E2D_LOG_INFO(""); + + app.run(); + + E2D_LOG_INFO("Application shutting down..."); + + app.shutdown(); + + E2D_LOG_INFO("Application shutdown complete"); + return 0; +} diff --git a/xmake.lua b/xmake.lua index 5d3a8ce..f629793 100644 --- a/xmake.lua +++ b/xmake.lua @@ -164,3 +164,29 @@ target("demo_basic") -- 构建后安装Shader文件 after_build(install_shaders) target_end() + +-- Hello Module 示例 - 展示如何创建自定义模块 +target("demo_hello_module") + set_kind("binary") + set_default(false) + + add_deps("extra2d") + add_files("examples/hello_module/*.cpp") + add_includedirs("examples/hello_module") + + -- 平台配置 + local plat = get_config("plat") or os.host() + if plat == "mingw" or plat == "windows" then + add_packages("glm", "nlohmann_json", "libsdl2") + add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") + elseif plat == "linux" then + add_packages("glm", "nlohmann_json", "libsdl2") + add_syslinks("GL", "dl", "pthread") + elseif plat == "macosx" then + add_packages("glm", "nlohmann_json", "libsdl2") + add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") + end + + -- 构建后安装Shader文件 + after_build(install_shaders) +target_end()