feat(示例): 添加自定义模块示例和文档

新增HelloModule示例展示如何创建自定义模块,包含:
1. 模块配置类实现
2. 模块初始化器实现
3. 自动注册机制
4. JSON配置支持
5. 场景中使用模块的示例

同时更新模块系统文档,详细说明自定义模块开发流程
This commit is contained in:
ChestnutYueyue 2026-02-15 17:00:39 +08:00
parent 867013f6eb
commit b55d279611
8 changed files with 990 additions and 292 deletions

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array>
#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/types.h>
@ -7,7 +8,6 @@
#include <extra2d/graphics/texture.h> #include <extra2d/graphics/texture.h>
#include <glm/mat4x4.hpp> #include <glm/mat4x4.hpp>
#include <vector> #include <vector>
#include <array>
#include <glad/glad.h> #include <glad/glad.h>
@ -52,10 +52,11 @@ public:
void end(); void end();
// 批量绘制接口 - 用于自动批处理 // 批量绘制接口 - 用于自动批处理
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites); void drawBatch(const Texture &texture,
const std::vector<SpriteData> &sprites);
// 立即绘制(不缓存) // 立即绘制(不缓存)
void drawImmediate(const Texture& texture, const SpriteData& data); void drawImmediate(const Texture &texture, const SpriteData &data);
// 统计 // 统计
uint32_t getDrawCallCount() const { return drawCallCount_; } uint32_t getDrawCallCount() const { return drawCallCount_; }
@ -63,7 +64,7 @@ public:
uint32_t getBatchCount() const { return batchCount_; } uint32_t getBatchCount() const { return batchCount_; }
// 检查是否需要刷新 // 检查是否需要刷新
bool needsFlush(const Texture& texture, bool isSDF) const; bool needsFlush(const Texture &texture, bool isSDF) const;
private: private:
GLuint vao_; GLuint vao_;
@ -78,7 +79,7 @@ private:
const Texture *currentTexture_; const Texture *currentTexture_;
bool currentIsSDF_; bool currentIsSDF_;
glm::mat4 viewProjection_; glm::mat4 viewProjection_;
// 缓存上一帧的 viewProjection避免重复设置 // 缓存上一帧的 viewProjection避免重复设置
glm::mat4 cachedViewProjection_; glm::mat4 cachedViewProjection_;
bool viewProjectionDirty_ = true; bool viewProjectionDirty_ = true;
@ -89,9 +90,9 @@ private:
void flush(); void flush();
void setupShader(); void setupShader();
// 添加顶点到缓冲区 // 添加顶点到缓冲区
void addVertices(const SpriteData& data); void addVertices(const SpriteData &data);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,17 +1,18 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/config/config_module.h> #include <extra2d/config/config_module.h>
#include <extra2d/config/module_registry.h> #include <extra2d/config/module_registry.h>
#include <extra2d/graphics/render_module.h>
#include <extra2d/graphics/render_config.h> #include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/render_module.h>
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/platform/input_module.h> #include <extra2d/platform/input_module.h>
#include <extra2d/platform/platform_init_module.h> #include <extra2d/platform/platform_init_module.h>
#include <extra2d/platform/window_module.h> #include <extra2d/platform/window_module.h>
#include <extra2d/services/camera_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/event_service.h>
#include <extra2d/services/camera_service.h>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
@ -20,384 +21,391 @@ namespace extra2d {
static double getTimeSeconds() { static double getTimeSeconds() {
#ifdef __SWITCH__ #ifdef __SWITCH__
struct timespec ts; struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts); clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) + return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0; static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else #else
using namespace std::chrono; using namespace std::chrono;
auto now = steady_clock::now(); auto now = steady_clock::now();
auto duration = now.time_since_epoch(); auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count(); return duration_cast<std::chrono::duration<double>>(duration).count();
#endif #endif
} }
Application& Application::get() { Application &Application::get() {
static Application instance; static Application instance;
return instance; return instance;
}
Application::~Application() {
shutdown();
} }
bool Application::init() { bool Application::init() {
AppConfig cfg; AppConfig cfg;
return init(cfg); return init(cfg);
} }
bool Application::init(const AppConfig& config) { bool Application::init(const AppConfig &config) {
if (initialized_) { if (initialized_) {
return true; 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<ConfigModuleInitializer *>(configInit);
if (cfgInit) {
cfgInit->setAppConfig(config);
} }
}
register_config_module(); return initModules();
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<ConfigModuleInitializer*>(configInit);
if (cfgInit) {
cfgInit->setAppConfig(config);
}
}
return initModules();
} }
bool Application::init(const std::string& configPath) { bool Application::init(const std::string &configPath) {
if (initialized_) { if (initialized_) {
return true; 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<ConfigModuleInitializer *>(configInit);
if (cfgInit) {
cfgInit->setConfigPath(configPath);
} }
}
register_config_module(); return initModules();
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<ConfigModuleInitializer*>(configInit);
if (cfgInit) {
cfgInit->setConfigPath(configPath);
}
}
return initModules();
} }
void Application::registerCoreServices() { void Application::registerCoreServices() {
auto& locator = ServiceLocator::instance(); auto &locator = ServiceLocator::instance();
if (!locator.hasService<ISceneService>()) { if (!locator.hasService<ISceneService>()) {
locator.registerService<ISceneService>(makeShared<SceneService>()); locator.registerService<ISceneService>(makeShared<SceneService>());
} }
if (!locator.hasService<ITimerService>()) { if (!locator.hasService<ITimerService>()) {
locator.registerService<ITimerService>(makeShared<TimerService>()); locator.registerService<ITimerService>(makeShared<TimerService>());
} }
if (!locator.hasService<ICameraService>()) { if (!locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>(); auto cameraService = makeShared<CameraService>();
if (window_) { if (window_) {
cameraService->setViewport(0, static_cast<float>(window_->width()), cameraService->setViewport(0, static_cast<float>(window_->width()),
static_cast<float>(window_->height()), 0); static_cast<float>(window_->height()), 0);
ViewportConfig vpConfig; ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(window_->width()); vpConfig.logicWidth = static_cast<float>(window_->width());
vpConfig.logicHeight = static_cast<float>(window_->height()); vpConfig.logicHeight = static_cast<float>(window_->height());
vpConfig.mode = ViewportMode::AspectRatio; vpConfig.mode = ViewportMode::AspectRatio;
cameraService->setViewportConfig(vpConfig); cameraService->setViewportConfig(vpConfig);
cameraService->updateViewport(window_->width(), window_->height()); cameraService->updateViewport(window_->width(), window_->height());
}
locator.registerService<ICameraService>(cameraService);
} }
locator.registerService<ICameraService>(cameraService);
}
} }
bool Application::initModules() { bool Application::initModules() {
auto& locator = ServiceLocator::instance(); auto &locator = ServiceLocator::instance();
if (!locator.hasService<IEventService>()) {
locator.registerService<IEventService>(makeShared<EventService>());
}
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
for (ModuleId moduleId : initOrder) {
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
if (!initializer) {
continue;
}
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId); if (!locator.hasService<IEventService>()) {
if (!moduleConfig) { locator.registerService<IEventService>(makeShared<EventService>());
continue; }
}
auto info = moduleConfig->getModuleInfo(); auto initOrder = ModuleRegistry::instance().getInitializationOrder();
if (!info.enabled) {
continue;
}
if (info.name == "Render") { for (ModuleId moduleId : initOrder) {
continue; auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
} if (!initializer) {
continue;
if (!initializer->initialize(moduleConfig)) {
return false;
}
} }
auto* windowInit = ModuleRegistry::instance().getInitializer(get_window_module_id()); auto *moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
if (!windowInit || !windowInit->isInitialized()) { 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<WindowModuleInitializer *>(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<RenderModuleInitializer *>(renderInit);
if (renderModule) {
renderModule->setWindow(window_);
auto *renderConfig =
ModuleRegistry::instance().getModuleConfig(get_render_module_id());
if (renderConfig && !renderInit->initialize(renderConfig)) {
return false; return false;
}
} }
}
auto* windowModule = dynamic_cast<WindowModuleInitializer*>(windowInit); registerCoreServices();
if (!windowModule) {
return false;
}
window_ = windowModule->getWindow(); if (!ServiceLocator::instance().initializeAll()) {
if (!window_) { return false;
return false; }
}
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (renderInit) { if (cameraService && window_) {
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit); window_->onResize([cameraService](int width, int height) {
if (renderModule) { cameraService->updateViewport(width, height);
renderModule->setWindow(window_); cameraService->applyViewportAdapter();
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id()); auto sceneService =
if (renderConfig && !renderInit->initialize(renderConfig)) { ServiceLocator::instance().getService<ISceneService>();
return false; if (sceneService) {
} auto currentScene = sceneService->getCurrentScene();
if (currentScene) {
currentScene->setViewportSize(static_cast<float>(width),
static_cast<float>(height));
} }
} }
});
}
registerCoreServices(); initialized_ = true;
running_ = true;
if (!ServiceLocator::instance().initializeAll()) { return true;
return false;
}
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (cameraService && window_) {
window_->onResize([this, 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;
} }
void Application::shutdown() { void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
VRAMMgr::get().printStats(); VRAMMgr::get().printStats();
ServiceLocator::instance().clear(); ServiceLocator::instance().clear();
window_ = nullptr; window_ = nullptr;
auto initOrder = ModuleRegistry::instance().getInitializationOrder(); auto initOrder = ModuleRegistry::instance().getInitializationOrder();
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) { for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
ModuleId moduleId = *it; ModuleId moduleId = *it;
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId); auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
if (initializer && initializer->isInitialized()) { if (initializer && initializer->isInitialized()) {
initializer->shutdown(); initializer->shutdown();
}
} }
}
initialized_ = false; initialized_ = false;
running_ = false; running_ = false;
}
Application::~Application() {
if (initialized_) {
shutdown();
}
} }
void Application::run() { void Application::run() {
if (!initialized_) { if (!initialized_) {
return; return;
} }
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
while (running_ && !window_->shouldClose()) { while (running_ && !window_->shouldClose()) {
mainLoop(); mainLoop();
} }
} }
void Application::quit() { void Application::quit() {
shouldQuit_ = true; shouldQuit_ = true;
running_ = false; running_ = false;
} }
void Application::pause() { void Application::pause() {
if (!paused_) { if (!paused_) {
paused_ = true; paused_ = true;
ServiceLocator::instance().pauseAll(); ServiceLocator::instance().pauseAll();
} }
} }
void Application::resume() { void Application::resume() {
if (paused_) { if (paused_) {
paused_ = false; paused_ = false;
ServiceLocator::instance().resumeAll(); ServiceLocator::instance().resumeAll();
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
} }
} }
void Application::mainLoop() { void Application::mainLoop() {
double currentTime = getTimeSeconds(); double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_); deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime; lastFrameTime_ = currentTime;
totalTime_ += deltaTime_; totalTime_ += deltaTime_;
frameCount_++; frameCount_++;
fpsTimer_ += deltaTime_; fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) { if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_; currentFps_ = frameCount_;
frameCount_ = 0; frameCount_ = 0;
fpsTimer_ -= 1.0f; fpsTimer_ -= 1.0f;
}
window_->poll();
auto eventService = ServiceLocator::instance().getService<IEventService>();
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<const RenderModuleConfig *>(renderConfig);
if (renderModuleConfig && !renderModuleConfig->vsync &&
renderModuleConfig->targetFPS > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
} }
}
window_->poll(); ConfigManager::instance().update(deltaTime_);
auto eventService = ServiceLocator::instance().getService<IEventService>();
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<const RenderModuleConfig*>(renderConfig);
if (renderModuleConfig && !renderModuleConfig->vsync && renderModuleConfig->targetFPS > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
}
}
ConfigManager::instance().update(deltaTime_);
} }
void Application::update() { void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); }
ServiceLocator::instance().updateAll(deltaTime_);
}
void Application::render() { void Application::render() {
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); auto *renderInit =
RenderBackend* renderer = nullptr; ModuleRegistry::instance().getInitializer(get_render_module_id());
if (renderInit) { RenderBackend *renderer = nullptr;
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit); if (renderInit) {
if (renderModule) { auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
renderer = renderModule->getRenderer(); if (renderModule) {
} renderer = renderModule->getRenderer();
} }
}
if (!renderer) { if (!renderer) {
return; return;
} }
auto cameraService = ServiceLocator::instance().getService<ICameraService>(); auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (cameraService) { if (cameraService) {
const auto& vp = cameraService->getViewportResult().viewport; const auto &vp = cameraService->getViewportResult().viewport;
renderer->setViewport( renderer->setViewport(
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y), static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height)); static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
} else {
renderer->setViewport(0, 0, window_->width(), window_->height());
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>(); renderer->setViewProjection(cameraService->getViewProjectionMatrix());
if (sceneService) { } else {
sceneService->render(*renderer); renderer->setViewport(0, 0, window_->width(), window_->height());
} }
window_->swap(); auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService) {
sceneService->render(*renderer);
}
window_->swap();
} }
IInput& Application::input() { IInput &Application::input() { return *window_->input(); }
return *window_->input();
}
RenderBackend& Application::renderer() { RenderBackend &Application::renderer() {
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id()); auto *renderInit =
if (renderInit) { ModuleRegistry::instance().getInitializer(get_render_module_id());
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit); if (renderInit) {
if (renderModule && renderModule->getRenderer()) { auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
return *renderModule->getRenderer(); if (renderModule && renderModule->getRenderer()) {
} return *renderModule->getRenderer();
} }
static RenderBackend* dummy = nullptr; }
if (!dummy) { static RenderBackend *dummy = nullptr;
dummy = RenderBackend::create(BackendType::OpenGL).release(); if (!dummy) {
} dummy = RenderBackend::create(BackendType::OpenGL).release();
return *dummy; }
return *dummy;
} }
SharedPtr<ISceneService> Application::scenes() { SharedPtr<ISceneService> Application::scenes() {
return ServiceLocator::instance().getService<ISceneService>(); return ServiceLocator::instance().getService<ISceneService>();
} }
SharedPtr<ITimerService> Application::timers() { SharedPtr<ITimerService> Application::timers() {
return ServiceLocator::instance().getService<ITimerService>(); return ServiceLocator::instance().getService<ITimerService>();
} }
SharedPtr<IEventService> Application::events() { SharedPtr<IEventService> Application::events() {
return ServiceLocator::instance().getService<IEventService>(); return ServiceLocator::instance().getService<IEventService>();
} }
SharedPtr<ICameraService> Application::camera() { SharedPtr<ICameraService> Application::camera() {
return ServiceLocator::instance().getService<ICameraService>(); return ServiceLocator::instance().getService<ICameraService>();
} }
void Application::enterScene(Ptr<Scene> scene) { void Application::enterScene(Ptr<Scene> scene) {
auto sceneService = ServiceLocator::instance().getService<ISceneService>(); auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService && scene) { if (sceneService && scene) {
scene->setViewportSize(static_cast<float>(window_->width()), scene->setViewportSize(static_cast<float>(window_->width()),
static_cast<float>(window_->height())); static_cast<float>(window_->height()));
sceneService->enterScene(scene); sceneService->enterScene(scene);
} }
} }
ConfigManager& Application::config() { ConfigManager &Application::config() { return ConfigManager::instance(); }
return ConfigManager::instance();
const AppConfig &Application::getConfig() const {
return ConfigManager::instance().appConfig();
} }
const AppConfig& Application::getConfig() const { } // namespace extra2d
return ConfigManager::instance().appConfig();
}
}

View File

@ -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) - 基础示例(场景图、输入事件、视口适配) - [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例(场景图、输入事件、视口适配)
- [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现 - [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现
- [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现 - [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/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/node.cpp](../../Extra2D/src/scene/node.cpp) - Node 实现
- [Extra2D/src/scene/shape_node.cpp](../../Extra2D/src/scene/shape_node.cpp) - ShapeNode 实现 - [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<const HelloModuleConfig*>(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<HelloModuleInitializer*>(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<HelloModuleInitializer*>(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() │
└─────────────────────────────────────────────────────────────┘
```

View File

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

View File

@ -0,0 +1,183 @@
#include "hello_module.h"
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
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<const json*>(jsonData);
if (j.contains("greeting")) {
config.greeting = j["greeting"].get<std::string>();
}
if (j.contains("repeatCount")) {
config.repeatCount = j["repeatCount"].get<int>();
}
if (j.contains("enableLogging")) {
config.enableLogging = j["enableLogging"].get<bool>();
}
return true;
} catch (...) {
return false;
}
}
/**
* @brief JSON
*/
bool HelloModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) return false;
try {
json& j = *static_cast<json*>(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<ModuleId> 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<const HelloModuleConfig*>(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<HelloModuleConfig>(),
[]() -> UniquePtr<IModuleInitializer> {
auto initializer = makeUnique<HelloModuleInitializer>();
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

View File

@ -0,0 +1,148 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <string>
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<ModuleId> 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

View File

@ -0,0 +1,101 @@
#include "hello_module.h"
#include <extra2d/app/application.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/scene/scene.h>
#include <extra2d/services/scene_service.h>
#include <extra2d/utils/logger.h>
using namespace extra2d;
/**
* @brief
*
* 使
*/
class HelloScene : public Scene {
public:
static Ptr<HelloScene> create() { return makeShared<HelloScene>(); }
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<HelloModuleInitializer *>(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<HelloModuleInitializer *>(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;
}

View File

@ -164,3 +164,29 @@ target("demo_basic")
-- 构建后安装Shader文件 -- 构建后安装Shader文件
after_build(install_shaders) after_build(install_shaders)
target_end() 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()