825 lines
16 KiB
Markdown
825 lines
16 KiB
Markdown
# Extra2D 模块系统
|
||
|
||
## 概述
|
||
|
||
Extra2D 采用模块化架构设计(参考 Kiwano),所有核心功能通过模块系统和服务系统管理。系统提供:
|
||
|
||
- **自动发现注册**:模块定义即注册,无需手动调用
|
||
- **统一的生命周期管理**:初始化、更新、渲染、关闭
|
||
- **优先级排序**:确保模块按正确顺序初始化
|
||
- **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"]
|
||
```
|
||
|
||
---
|
||
|
||
## 模块自动注册
|
||
|
||
### E2D_MODULE 宏
|
||
|
||
模块通过 `E2D_MODULE` 宏自动注册,无需手动调用任何注册函数:
|
||
|
||
```cpp
|
||
// config_module.cpp
|
||
#include "config_module.h"
|
||
|
||
namespace extra2d {
|
||
// 模块实现...
|
||
}
|
||
|
||
// 在文件末尾,namespace 外部
|
||
E2D_MODULE(ConfigModule, 0) // 名称, 优先级
|
||
```
|
||
|
||
### 带依赖的模块
|
||
|
||
```cpp
|
||
// window_module.cpp
|
||
E2D_MODULE(WindowModule, 20, "ConfigModule") // 依赖 ConfigModule
|
||
|
||
// render_module.cpp
|
||
E2D_MODULE(RenderModule, 40, "WindowModule", "ConfigModule") // 多个依赖
|
||
```
|
||
|
||
### 带属性的模块
|
||
|
||
```cpp
|
||
// render_module.cpp
|
||
E2D_MODULE_BEGIN(RenderModule)
|
||
E2D_PRIORITY(40)
|
||
E2D_DEPENDENCIES("WindowModule")
|
||
E2D_PROPERTY(vsync, bool)
|
||
E2D_PROPERTY(targetFPS, int)
|
||
E2D_MODULE_END()
|
||
```
|
||
|
||
---
|
||
|
||
## 链接方式详解
|
||
|
||
### 静态链接 vs 动态链接
|
||
|
||
| 特性 | 静态链接 | 动态链接 |
|
||
|------|---------|---------|
|
||
| **引擎库** | `.a` / `.lib` | `.dll` / `.so` |
|
||
| **模块注册** | 需要 `--whole-archive` 或直接编译 | 自动注册 |
|
||
| **自定义模块** | 直接编译到可执行文件 | 编译为独立 DLL |
|
||
| **额外代码** | 无 | 需要 `E2D_FORCE_LINK` |
|
||
| **分发** | 单一可执行文件 | 需要 DLL 文件 |
|
||
| **热更新** | 不支持 | 支持(重新加载 DLL) |
|
||
|
||
### 静态链接方案(推荐)
|
||
|
||
#### 原理
|
||
|
||
静态链接时,链接器会优化掉未引用的代码。解决方案:
|
||
1. **引擎库**:使用 `--whole-archive` 强制链接所有符号
|
||
2. **自定义模块**:直接编译到可执行文件
|
||
|
||
#### xmake 配置
|
||
|
||
```lua
|
||
-- 引擎库(静态库)
|
||
target("extra2d")
|
||
set_kind("static")
|
||
-- ... 其他配置
|
||
|
||
-- 可执行文件
|
||
target("demo_basic")
|
||
set_kind("binary")
|
||
|
||
-- 强制链接引擎静态库
|
||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||
add_deps("extra2d")
|
||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||
|
||
add_files("main.cpp")
|
||
```
|
||
|
||
#### 自定义模块配置
|
||
|
||
```lua
|
||
target("demo_hello_module")
|
||
set_kind("binary")
|
||
|
||
-- 强制链接引擎静态库
|
||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||
add_deps("extra2d")
|
||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||
|
||
-- 直接编译模块源文件到可执行文件
|
||
add_files("main.cpp", "hello_module.cpp")
|
||
add_includedirs("hello_module")
|
||
```
|
||
|
||
#### 模块定义
|
||
|
||
```cpp
|
||
// hello_module.cpp
|
||
#include "hello_module.h"
|
||
|
||
namespace extra2d {
|
||
// 模块实现...
|
||
}
|
||
|
||
// 在文件末尾,namespace 外部
|
||
E2D_MODULE(HelloModule, 1000)
|
||
```
|
||
|
||
#### main.cpp
|
||
|
||
```cpp
|
||
#include "hello_module.h"
|
||
#include <extra2d/extra2d.h>
|
||
|
||
int main() {
|
||
extra2d::Application& app = extra2d::Application::get();
|
||
|
||
// 无需任何注册代码!
|
||
// 模块在静态初始化时自动注册
|
||
|
||
extra2d::AppConfig config;
|
||
config.appName = "My App";
|
||
|
||
if (!app.init(config)) return 1;
|
||
app.run();
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 动态链接方案
|
||
|
||
#### xmake 配置
|
||
|
||
```lua
|
||
-- 引擎库(动态库)
|
||
target("extra2d")
|
||
set_kind("shared")
|
||
add_defines("E2D_BUILDING_DLL", {public = false})
|
||
-- ... 其他配置
|
||
|
||
-- 自定义模块 DLL
|
||
target("hello_module_lib")
|
||
set_kind("shared")
|
||
add_defines("E2D_BUILDING_DLL", {public = false})
|
||
add_deps("extra2d")
|
||
add_files("hello_module.cpp")
|
||
add_includedirs("hello_module", {public = true})
|
||
|
||
-- 可执行文件
|
||
target("demo_hello_module")
|
||
set_kind("binary")
|
||
add_deps("hello_module_lib")
|
||
add_files("main.cpp")
|
||
```
|
||
|
||
#### 模块定义
|
||
|
||
```cpp
|
||
// hello_module.cpp
|
||
#include "hello_module.h"
|
||
|
||
namespace extra2d {
|
||
// 模块实现...
|
||
}
|
||
|
||
// 使用 E2D_MODULE_EXPORT(自动生成导出函数)
|
||
E2D_MODULE_EXPORT(HelloModule, 1000)
|
||
```
|
||
|
||
#### main.cpp
|
||
|
||
```cpp
|
||
#include "hello_module.h"
|
||
#include <extra2d/extra2d.h>
|
||
|
||
// 声明外部模块的导出函数
|
||
E2D_DECLARE_FORCE_LINK(HelloModule);
|
||
|
||
int main() {
|
||
// 触发 DLL 加载
|
||
E2D_CALL_FORCE_LINK(HelloModule);
|
||
|
||
extra2d::Application& app = extra2d::Application::get();
|
||
if (!app.init(config)) return 1;
|
||
app.run();
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 平台兼容性
|
||
|
||
### Linux
|
||
|
||
```lua
|
||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||
add_deps("extra2d")
|
||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||
```
|
||
|
||
### macOS
|
||
|
||
macOS 使用 `-force_load` 代替 `--whole-archive`:
|
||
|
||
```lua
|
||
if is_plat("macosx") then
|
||
add_ldflags("-force_load", {force = true})
|
||
end
|
||
add_deps("extra2d")
|
||
```
|
||
|
||
### Windows (MSVC)
|
||
|
||
MSVC 使用 `/WHOLEARCHIVE`:
|
||
|
||
```lua
|
||
if is_plat("windows") and is_toolchain("msvc") then
|
||
add_ldflags("/WHOLEARCHIVE:extra2d", {force = true})
|
||
end
|
||
add_deps("extra2d")
|
||
```
|
||
|
||
### Windows (MinGW)
|
||
|
||
```lua
|
||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||
add_deps("extra2d")
|
||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||
```
|
||
|
||
---
|
||
|
||
## 模块基类
|
||
|
||
### Module
|
||
|
||
所有模块只需继承 `Module` 基类,实现需要的生命周期方法:
|
||
|
||
```cpp
|
||
class Module {
|
||
public:
|
||
virtual ~Module() = default;
|
||
|
||
/**
|
||
* @brief 设置模块(初始化)
|
||
*/
|
||
virtual void setupModule() {}
|
||
|
||
/**
|
||
* @brief 销毁模块
|
||
*/
|
||
virtual void destroyModule() {}
|
||
|
||
/**
|
||
* @brief 更新时
|
||
*/
|
||
virtual void onUpdate(UpdateContext& ctx) { ctx.next(); }
|
||
|
||
/**
|
||
* @brief 渲染前
|
||
*/
|
||
virtual void beforeRender(RenderContext& ctx) { ctx.next(); }
|
||
|
||
/**
|
||
* @brief 渲染时
|
||
*/
|
||
virtual void onRender(RenderContext& ctx) { ctx.next(); }
|
||
|
||
/**
|
||
* @brief 渲染后
|
||
*/
|
||
virtual void afterRender(RenderContext& ctx) { ctx.next(); }
|
||
|
||
/**
|
||
* @brief 事件处理
|
||
*/
|
||
virtual void handleEvent(EventContext& ctx) { ctx.next(); }
|
||
|
||
/**
|
||
* @brief 获取模块名称
|
||
*/
|
||
virtual const char* getName() const = 0;
|
||
|
||
/**
|
||
* @brief 获取模块优先级
|
||
*/
|
||
virtual int getPriority() const { return 0; }
|
||
};
|
||
```
|
||
|
||
### 模块上下文
|
||
|
||
用于遍历模块链,支持链式调用:
|
||
|
||
```cpp
|
||
/**
|
||
* @brief 模块上下文基类
|
||
*/
|
||
class ModuleContext {
|
||
public:
|
||
void next(); // 遍历下一个模块
|
||
bool isDone() const; // 检查是否完成
|
||
};
|
||
|
||
/**
|
||
* @brief 更新上下文
|
||
*/
|
||
class UpdateContext : public ModuleContext {
|
||
public:
|
||
float getDeltaTime() const; // 获取帧间隔时间
|
||
};
|
||
|
||
/**
|
||
* @brief 渲染上下文
|
||
*/
|
||
class RenderContext : public ModuleContext {
|
||
public:
|
||
enum class Phase { Before, On, After };
|
||
Phase getPhase() const;
|
||
};
|
||
|
||
/**
|
||
* @brief 事件上下文
|
||
*/
|
||
class EventContext : public ModuleContext {
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 模块 vs 服务
|
||
|
||
| 特性 | 模块 (Module) | 服务 (Service) |
|
||
|-----|--------------|---------------|
|
||
| 用途 | 平台级初始化 | 运行时功能 |
|
||
| 生命周期 | Application 管理 | ServiceLocator 管理 |
|
||
| 注册方式 | 自动发现 | `locator.registerService()` |
|
||
| 可替换性 | 编译时确定 | 运行时可替换 |
|
||
| 示例 | Window, Render, Input | Scene, Timer, Event, Camera |
|
||
|
||
---
|
||
|
||
## 模块优先级
|
||
|
||
模块按优先级从小到大初始化,关闭时逆序执行:
|
||
|
||
| 模块 | 优先级 | 说明 |
|
||
|------|--------|------|
|
||
| LoggerModule | -1 | 最先初始化 |
|
||
| ConfigModule | 0 | 配置加载 |
|
||
| PlatformModule | 10 | 平台初始化 |
|
||
| WindowModule | 20 | 窗口创建 |
|
||
| InputModule | 30 | 输入系统 |
|
||
| RenderModule | 40 | 渲染系统 |
|
||
| ScriptModule | 500 | 脚本系统 |
|
||
| 用户模块 | 1000+ | 用户自定义 |
|
||
|
||
---
|
||
|
||
## 创建新模块
|
||
|
||
### 完整示例(静态链接)
|
||
|
||
**hello_module.h:**
|
||
```cpp
|
||
#pragma once
|
||
#include <extra2d/core/module.h>
|
||
#include <string>
|
||
|
||
namespace extra2d {
|
||
|
||
struct HelloModuleConfig {
|
||
std::string greeting = "Hello!";
|
||
int repeatCount = 1;
|
||
};
|
||
|
||
class HelloModule : public Module {
|
||
public:
|
||
HelloModule();
|
||
~HelloModule() override;
|
||
|
||
const char* getName() const override { return "HelloModule"; }
|
||
int getPriority() const override { return 1000; }
|
||
|
||
void setupModule() override;
|
||
void destroyModule() override;
|
||
void onUpdate(UpdateContext& ctx) override;
|
||
|
||
void setConfig(const HelloModuleConfig& config) { config_ = config; }
|
||
void sayHello() const;
|
||
|
||
private:
|
||
HelloModuleConfig config_;
|
||
float time_ = 0.0f;
|
||
};
|
||
|
||
} // namespace extra2d
|
||
```
|
||
|
||
**hello_module.cpp:**
|
||
```cpp
|
||
#include "hello_module.h"
|
||
#include <extra2d/utils/logger.h>
|
||
|
||
namespace extra2d {
|
||
|
||
HelloModule::HelloModule() = default;
|
||
|
||
HelloModule::~HelloModule() {
|
||
if (isInitialized()) {
|
||
destroyModule();
|
||
}
|
||
}
|
||
|
||
void HelloModule::setupModule() {
|
||
if (isInitialized()) return;
|
||
|
||
setInitialized(true);
|
||
E2D_LOG_INFO("HelloModule initialized");
|
||
E2D_LOG_INFO(" Greeting: {}", config_.greeting);
|
||
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
|
||
|
||
sayHello();
|
||
}
|
||
|
||
void HelloModule::destroyModule() {
|
||
if (!isInitialized()) return;
|
||
|
||
E2D_LOG_INFO("HelloModule shutdown");
|
||
setInitialized(false);
|
||
}
|
||
|
||
void HelloModule::onUpdate(UpdateContext& ctx) {
|
||
if (!isInitialized()) {
|
||
ctx.next();
|
||
return;
|
||
}
|
||
|
||
time_ += ctx.getDeltaTime();
|
||
|
||
if (time_ >= 5.0f) {
|
||
sayHello();
|
||
time_ = 0.0f;
|
||
}
|
||
|
||
ctx.next();
|
||
}
|
||
|
||
void HelloModule::sayHello() const {
|
||
for (int i = 0; i < config_.repeatCount; ++i) {
|
||
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")
|
||
```
|
||
|
||
---
|
||
|
||
## 内置模块
|
||
|
||
### Config 模块
|
||
|
||
**职责**:管理 ConfigManager 和应用配置
|
||
|
||
**优先级**:0
|
||
|
||
```cpp
|
||
extra2d::AppConfig config;
|
||
config.appName = "My Application";
|
||
config.appVersion = "1.0.0";
|
||
```
|
||
|
||
---
|
||
|
||
### Window 模块
|
||
|
||
**职责**:窗口创建和管理
|
||
|
||
**优先级**:20
|
||
|
||
**后端**:统一使用 SDL2
|
||
|
||
---
|
||
|
||
### Input 模块
|
||
|
||
**职责**:输入设备管理(键盘、鼠标、手柄)
|
||
|
||
**优先级**:30
|
||
|
||
---
|
||
|
||
### Render 模块
|
||
|
||
**职责**:渲染器初始化和管理
|
||
|
||
**优先级**:40
|
||
|
||
---
|
||
|
||
## 服务系统
|
||
|
||
### IService
|
||
|
||
服务接口基类:
|
||
|
||
```cpp
|
||
class IService {
|
||
public:
|
||
virtual ~IService() = default;
|
||
|
||
virtual ServiceInfo getServiceInfo() const = 0;
|
||
virtual bool initialize() = 0;
|
||
virtual void shutdown() = 0;
|
||
virtual void update(float deltaTime);
|
||
};
|
||
```
|
||
|
||
### 内置服务
|
||
|
||
| 服务 | 用途 | 优先级 |
|
||
|-----|------|-------|
|
||
| EventService | 事件分发 | 100 |
|
||
| TimerService | 计时器 | 200 |
|
||
| SceneService | 场景管理 | 300 |
|
||
| CameraService | 相机系统 | 400 |
|
||
|
||
### 使用服务
|
||
|
||
```cpp
|
||
auto& app = extra2d::Application::get();
|
||
|
||
// 获取服务
|
||
auto sceneService = app.scenes();
|
||
auto timerService = app.timers();
|
||
auto eventService = app.events();
|
||
auto cameraService = app.camera();
|
||
|
||
// 使用场景服务
|
||
sceneService->pushScene(myScene);
|
||
|
||
// 使用计时器服务
|
||
timerService->addTimer(1.0f, []() {
|
||
E2D_LOG_INFO("Timer fired!");
|
||
});
|
||
|
||
// 使用事件服务
|
||
eventService->addListener(extra2d::EventType::KeyPress, [](extra2d::Event& e) {
|
||
auto& keyEvent = std::get<extra2d::KeyEvent>(e.data);
|
||
E2D_LOG_INFO("Key pressed: {}", keyEvent.keyCode);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 输入事件系统
|
||
|
||
### 事件类型
|
||
|
||
```cpp
|
||
enum class EventType {
|
||
// 键盘
|
||
KeyPress,
|
||
KeyRelease,
|
||
KeyRepeat,
|
||
|
||
// 鼠标
|
||
MousePress,
|
||
MouseRelease,
|
||
MouseMove,
|
||
MouseScroll,
|
||
|
||
// 手柄
|
||
GamepadConnect,
|
||
GamepadDisconnect,
|
||
GamepadPress,
|
||
GamepadRelease,
|
||
|
||
// 触摸
|
||
TouchBegin,
|
||
TouchMove,
|
||
TouchEnd,
|
||
|
||
// 窗口
|
||
WindowResize,
|
||
WindowClose,
|
||
};
|
||
```
|
||
|
||
### 事件监听
|
||
|
||
```cpp
|
||
auto eventService = app.events();
|
||
|
||
// 监听键盘事件
|
||
eventService->addListener(EventType::KeyPress, [](Event& e) {
|
||
auto& key = std::get<KeyEvent>(e.data);
|
||
if (key.scancode == static_cast<int>(Key::Escape)) {
|
||
Application::get().quit();
|
||
}
|
||
});
|
||
|
||
// 监听鼠标事件
|
||
eventService->addListener(EventType::MousePress, [](Event& e) {
|
||
auto& mouse = std::get<MouseEvent>(e.data);
|
||
E2D_LOG_INFO("Click at ({}, {})", mouse.position.x, mouse.position.y);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 场景图系统
|
||
|
||
### Node 基类
|
||
|
||
```cpp
|
||
class Node : public std::enable_shared_from_this<Node> {
|
||
public:
|
||
// 层级管理
|
||
void addChild(Ptr<Node> child);
|
||
void removeChild(Ptr<Node> child);
|
||
Ptr<Node> getParent() const;
|
||
|
||
// 变换属性
|
||
void setPos(const Vec2& pos);
|
||
void setRotation(float degrees);
|
||
void setScale(const Vec2& scale);
|
||
|
||
// 世界变换
|
||
Vec2 toWorld(const Vec2& localPos) const;
|
||
glm::mat4 getWorldTransform() const;
|
||
|
||
// 生命周期回调
|
||
virtual void onEnter();
|
||
virtual void onExit();
|
||
virtual void onUpdate(float dt);
|
||
virtual void onRender(RenderBackend& renderer);
|
||
};
|
||
```
|
||
|
||
### ShapeNode 形状节点
|
||
|
||
```cpp
|
||
// 创建形状节点
|
||
auto rect = ShapeNode::createFilledRect(
|
||
Rect(0, 0, 100, 100),
|
||
Color(1.0f, 0.4f, 0.4f, 1.0f)
|
||
);
|
||
|
||
auto circle = ShapeNode::createFilledCircle(
|
||
Vec2(0, 0), 50,
|
||
Color(0.4f, 0.4f, 1.0f, 1.0f)
|
||
);
|
||
|
||
auto triangle = ShapeNode::createFilledTriangle(
|
||
Vec2(0, -40), Vec2(-35, 30), Vec2(35, 30),
|
||
Color(0.4f, 1.0f, 0.4f, 1.0f)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 常见问题
|
||
|
||
### Q: 模块没有被注册?
|
||
|
||
**静态链接:**
|
||
- 确保使用了 `--whole-archive`
|
||
- 自定义模块要直接编译到可执行文件
|
||
|
||
**动态链接:**
|
||
- 确保使用了 `E2D_MODULE_EXPORT`
|
||
- 确保调用了 `E2D_CALL_FORCE_LINK`
|
||
|
||
### Q: 链接错误 "undefined reference"?
|
||
|
||
检查链接顺序,`--whole-archive` 要在 `add_deps` 之前。
|
||
|
||
### Q: 模块初始化顺序错误?
|
||
|
||
使用 `getPriority()` 控制顺序,数字小的先初始化。
|
||
|
||
### Q: 如何调试模块注册?
|
||
|
||
查看日志输出:
|
||
```
|
||
[INFO ] ModuleRegistry: 4 modules registered
|
||
[INFO ] - ConfigModule (priority: 0)
|
||
[INFO ] - WindowModule (priority: 20)
|
||
[INFO ] - InputModule (priority: 30)
|
||
[INFO ] - RenderModule (priority: 40)
|
||
```
|
||
|
||
---
|
||
|
||
## 示例
|
||
|
||
完整示例请参考:
|
||
- [examples/hello_module/](../../examples/hello_module/) - 自定义模块示例
|
||
- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 模块优先级
|
||
|
||
```cpp
|
||
// 核心模块使用低优先级
|
||
class LoggerModule : public Module {
|
||
int getPriority() const override { return -1; }
|
||
};
|
||
|
||
// 用户模块使用高优先级
|
||
class MyModule : public Module {
|
||
int getPriority() const override { return 1000; }
|
||
};
|
||
```
|
||
|
||
### 2. 链式调用
|
||
|
||
```cpp
|
||
void onUpdate(UpdateContext& ctx) override {
|
||
// 执行更新逻辑
|
||
doSomething();
|
||
|
||
// 继续下一个模块
|
||
ctx.next();
|
||
}
|
||
```
|
||
|
||
### 3. 检查初始化状态
|
||
|
||
```cpp
|
||
void onUpdate(UpdateContext& ctx) override {
|
||
if (!isInitialized()) {
|
||
ctx.next();
|
||
return;
|
||
}
|
||
|
||
// 执行更新逻辑
|
||
ctx.next();
|
||
}
|
||
```
|
||
|
||
### 4. 静态链接优先
|
||
|
||
静态链接更简单,无需额外配置,推荐用于大多数场景。
|