Extra2D/docs/module_system.md

825 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. 静态链接优先
静态链接更简单,无需额外配置,推荐用于大多数场景。