feat(sdk): migrate to EventBus 2.0
Replace the legacy EventBase runtime with generated multi-bus bindings and explicit scheduling. Migrate app, data, network, demo, and mod consumers; add generated network-event registration and owner-scoped mod content overrides.
This commit is contained in:
@@ -1,252 +1,70 @@
|
||||
# ShrinkNetwork.Integration.EventBus
|
||||
|
||||
`ShrinkNetwork` 与 `ShrinkEventBus` 的桥接层。目标不是“把网络生命周期抛几个普通事件出来”,而是让某些 `EventBase` 类型本身就能作为网络消息声明,并在本地与远端之间保持一致的事件语义。
|
||||
把实现 `IShrinkEvent` 的网络事件与 ShrinkEventBus 2.0 连接。广播事件可从本地 Bus 转发到远端;请求事件可在远端 `PostAsync` 后回传 `EventResult` 与取消状态;delta 事件按版本去重。
|
||||
|
||||
## ✨ 特性概览
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| 🌉 **事件即网络消息** | `EventBase` 可直接实现 `IShrinkNetworkMessage / IShrinkNetworkRequest` 参与网络同步 |
|
||||
| 🔁 **自动双端分发** | 本地 `EventBus.TriggerEvent(...)` 后,可自动转发到远端并重新进入远端 `EventBus` |
|
||||
| ✅ **HasResult 请求语义** | 请求型事件支持 `EventResult` 与取消状态回传 |
|
||||
| 📉 **增量事件去重** | `IShrinkNetworkDeltaEvent` 按 `SessionId + EventType + DeltaKey` 做版本过滤 |
|
||||
| 🧩 **按键接入** | `ShrinkNetworkEventBusComponent` 注入网络服务,随依赖激活和撤回 |
|
||||
| 🧰 **更顺手的扩展 API** | `session.PublishEventAsync(...)`、`session.RequestEventAsync(...)`、`service.UseEventBusBridge(...)` |
|
||||
|
||||
## 📦 依赖
|
||||
|
||||
- `ShrinkNetwork`
|
||||
- `ShrinkEventBus`
|
||||
- Unity 2022.3+
|
||||
|
||||
## ⚙️ 安装前提
|
||||
|
||||
桥接层默认假设你已经有:
|
||||
|
||||
- 一套正常工作的 `ShrinkNetworkService`
|
||||
- 一套正常工作的 `ShrinkEventBus`
|
||||
- 事件类型明确声明 `[ShrinkNetworkEvent] + [ShrinkNetworkMessage]`
|
||||
|
||||
## 🚀 快速上手
|
||||
|
||||
### 第一步:声明广播型网络事件
|
||||
## 广播事件
|
||||
|
||||
```csharp
|
||||
using ShrinkEventBus;
|
||||
using ShrinkNetwork;
|
||||
using ShrinkNetwork.Integration;
|
||||
|
||||
[ShrinkNetworkEvent]
|
||||
[ShrinkNetworkMessage(3001, "room/player_ready")]
|
||||
public sealed class PlayerReadyEvent : EventBase, IShrinkNetworkMessage
|
||||
public sealed class PlayerReadyEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
public string PlayerId { get; set; } = string.Empty;
|
||||
public string RoomId { get; set; } = string.Empty;
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步:正常启动网络服务
|
||||
|
||||
```csharp
|
||||
var service = new ShrinkNetworkService(
|
||||
new ShrinkMessagePackNetworkSerializer(),
|
||||
new ShrinkNetworkMessageRegistry(),
|
||||
new ShrinkNetworkRouter());
|
||||
|
||||
service.AutoRegisterAll();
|
||||
service.BindTransport(new ShrinkTcpClientTransport("127.0.0.1", 17001));
|
||||
EventBus.Post(new PlayerReadyEvent { PlayerId = "10001" });
|
||||
await session.PublishEventAsync(new PlayerReadyEvent { PlayerId = "10001" });
|
||||
```
|
||||
|
||||
ContextLoader 项目由 Starter 组合根装配 `ShrinkNetworkEventBusComponent`。Standalone 项目需要显式调用 `service.UseEventBusBridge(...)`;仅绑定传输不会再通过反射接桥。
|
||||
事件必须同时实现 `IShrinkEvent`、`IShrinkNetworkMessage`,并声明 `[ShrinkNetworkEvent]` 与 `[ShrinkNetworkMessage]`。本包的 ILPostProcessor 为每个事件生成强类型入站 dispatcher 和模块注册,不在运行时扫描程序集、构造泛型方法或反射调用 handler。
|
||||
|
||||
### 第三步:像普通事件一样触发
|
||||
## 远端裁决
|
||||
|
||||
```csharp
|
||||
EventBus.TriggerEvent(new PlayerReadyEvent
|
||||
{
|
||||
PlayerId = "10001",
|
||||
RoomId = "alpha"
|
||||
});
|
||||
```
|
||||
|
||||
注册之后:
|
||||
|
||||
- 本地事件先按正常 `EventBus` 流程执行
|
||||
- 桥接层自动把该事件转发到当前 `ShrinkNetworkService` 的在线 session
|
||||
- 远端收到后重新进入远端 `EventBus.TriggerEventAsync(...)`
|
||||
|
||||
## 📖 核心概念
|
||||
|
||||
### 事件声明规则
|
||||
|
||||
可桥接事件需要同时满足:
|
||||
|
||||
1. 继承 `EventBase`
|
||||
2. 实现 `IShrinkNetworkMessage` 或 `IShrinkNetworkRequest`
|
||||
3. 标记 `[ShrinkNetworkEvent]`
|
||||
4. 标记 `[ShrinkNetworkMessage(opcode, route)]`
|
||||
|
||||
如果缺少这些条件,桥接层会忽略该事件类型。
|
||||
|
||||
### 广播型事件
|
||||
|
||||
广播型事件只需要:
|
||||
|
||||
- `EventBase`
|
||||
- `IShrinkNetworkMessage`
|
||||
- `[ShrinkNetworkEvent]`
|
||||
- `[ShrinkNetworkMessage(...)]`
|
||||
|
||||
适合:
|
||||
|
||||
- 玩家就绪
|
||||
- 房间状态变更
|
||||
- UI 同步通知
|
||||
|
||||
手动定向发送时,推荐直接用扩展:
|
||||
|
||||
```csharp
|
||||
await session.PublishEventAsync(new PlayerReadyEvent
|
||||
{
|
||||
PlayerId = "10001",
|
||||
RoomId = "alpha"
|
||||
});
|
||||
```
|
||||
|
||||
### 增量事件
|
||||
|
||||
如果事件本身就是 delta,而不是完整状态,可以实现 `IShrinkNetworkDeltaEvent`:
|
||||
|
||||
```csharp
|
||||
[ShrinkNetworkEvent]
|
||||
[ShrinkNetworkMessage(3002, "room/player_state_delta")]
|
||||
public sealed class PlayerStateDeltaEvent : EventBase, IShrinkNetworkMessage, IShrinkNetworkDeltaEvent
|
||||
{
|
||||
public string PlayerId { get; set; } = string.Empty;
|
||||
public int Hp { get; set; }
|
||||
|
||||
public string DeltaKey => PlayerId;
|
||||
public long DeltaVersion { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
桥接层会按:
|
||||
|
||||
`SessionId + EventType + DeltaKey`
|
||||
|
||||
记录已应用版本:
|
||||
|
||||
- 新版本进入远端 `EventBus`
|
||||
- 旧版本或重复版本直接丢弃
|
||||
|
||||
这套语义是“事件自己声明 delta 载荷”,不是自动做字段 diff。
|
||||
|
||||
### HasResult 请求型事件
|
||||
|
||||
如果事件同时满足:
|
||||
|
||||
1. `[ShrinkNetworkEvent]`
|
||||
2. `[ShrinkNetworkMessage(...)]`
|
||||
3. `[HasResult]`
|
||||
4. `IShrinkNetworkRequest`
|
||||
|
||||
就可以走“远端裁决”语义:
|
||||
|
||||
```csharp
|
||||
[Cancelable]
|
||||
[HasResult]
|
||||
[ShrinkNetworkEvent]
|
||||
[ShrinkNetworkMessage(3010, "room/can_use_skill")]
|
||||
public sealed class CanUseSkillEvent : EventBase, IShrinkNetworkRequest
|
||||
public sealed class CanUseSkillEvent :
|
||||
IShrinkResultEvent<EventResult>, IShrinkCancelableEvent, IShrinkNetworkRequest
|
||||
{
|
||||
public string PlayerId { get; set; } = string.Empty;
|
||||
public string SkillId { get; set; } = string.Empty;
|
||||
private EventResult _result;
|
||||
private bool _canceled;
|
||||
|
||||
public EventResult Result => _result;
|
||||
public bool IsCanceled => _canceled;
|
||||
public void SetResult(EventResult value) => _result = value;
|
||||
public void SetCanceled(bool value) => _canceled = value;
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
var outcome = await session.RequestEventAsync(new CanUseSkillEvent
|
||||
{
|
||||
PlayerId = "10001",
|
||||
SkillId = "fireball"
|
||||
});
|
||||
|
||||
if (outcome.IsSuccess && outcome.Result == EventResult.ALLOW)
|
||||
{
|
||||
// 允许释放技能
|
||||
}
|
||||
var outcome = await session.RequestEventAsync(new CanUseSkillEvent());
|
||||
```
|
||||
|
||||
当前回传内容只有:
|
||||
请求事件必须实现 `IShrinkResultEvent<EventResult>` 与 `IShrinkNetworkRequest`。如果需要取消回传,再实现 `IShrinkCancelableEvent`。当前响应只包含 `EventResult`、取消状态和 RPC 错误,不自动回传事件上的其它可变字段。
|
||||
|
||||
- `EventResult`
|
||||
- `IsCanceled`
|
||||
- `ErrorCode / ErrorMessage`
|
||||
## Delta
|
||||
|
||||
不会自动回传整个事件对象上其他字段的最终改动。
|
||||
实现 `IShrinkNetworkDeltaEvent` 后,入站按 `SessionId + EventType + DeltaKey` 记录最高 `DeltaVersion`;旧版本和重复版本不会进入 Bus。
|
||||
|
||||
## 🔧 API 参考
|
||||
## 接入
|
||||
|
||||
### ContextLoader 接入
|
||||
|
||||
```csharp
|
||||
// ShrinkApp.Starter.Basic 组合根自动加入 ShrinkNetworkEventBusComponent。
|
||||
// 组件注入 shrink.service.network 后注册,网络提供者撤回时注销。
|
||||
```
|
||||
|
||||
### 显式接入
|
||||
|
||||
Standalone 或需要自定义 session 过滤时:
|
||||
ContextLoader 默认通过 `ShrinkNetworkEventBusComponent` 接桥。Standalone:
|
||||
|
||||
```csharp
|
||||
service.UseEventBusBridge(new ShrinkNetworkEventBusBridgeOptions
|
||||
{
|
||||
SessionFilter = (session, evt) => session.SessionId > 0,
|
||||
DispatchScheduler = new ShrinkNetworkUnityMainThreadDispatchScheduler()
|
||||
SessionFilter = (session, value) => session.SessionId > 0
|
||||
});
|
||||
```
|
||||
|
||||
如果网络事件数量较大并且需要明确的每帧预算,使用 `ShrinkNetworkDispatchQueue`,在 Unity 主线程的 `Update` 中调用 `PumpAsync(maxItems)`;队列满时默认拒绝,避免无界堆积。
|
||||
|
||||
### 发送扩展
|
||||
可用发送扩展:
|
||||
|
||||
```csharp
|
||||
session.PublishEventAsync<TEvent>(eventArgs, route = null)
|
||||
session.RequestEventAsync<TEvent>(eventArgs, options = null)
|
||||
service.BroadcastEventAsync<TEvent>(eventArgs, sessionFilter = null, route = null)
|
||||
session.PublishEventAsync(eventData);
|
||||
session.RequestEventAsync(eventData);
|
||||
service.BroadcastEventAsync(eventData);
|
||||
```
|
||||
|
||||
## 🏗️ 工作流
|
||||
|
||||
```
|
||||
本地 EventBus.TriggerEvent(evt)
|
||||
├─ 本地订阅链正常执行
|
||||
├─ Bridge 监听 OnEventTriggered
|
||||
├─ 识别为 [ShrinkNetworkEvent]
|
||||
├─ 自动转发到在线 session
|
||||
└─ 远端收到后重新进入 EventBus.TriggerEventAsync(evt)
|
||||
```
|
||||
|
||||
请求型事件则额外带回:
|
||||
|
||||
```
|
||||
远端 EventResult / IsCanceled / ErrorCode
|
||||
```
|
||||
|
||||
## ✅ 最佳实践
|
||||
|
||||
- 广播型事件和请求型事件分开设计,不要一个类型同时混两种用途
|
||||
- 高实时状态优先实现 `IShrinkNetworkDeltaEvent`
|
||||
- 需要“远端裁决”的事件显式标记 `[HasResult]`
|
||||
- 优先走 `session.PublishEventAsync(...)` / `session.RequestEventAsync(...)`,不要在业务层到处直接调用底层 bridge
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- 核心网络程序集不反射发现桥接包;桥接生命周期只来自组合组件或显式 API
|
||||
- 当前只回传 `EventResult` 与取消状态,不自动同步事件对象其它字段改动
|
||||
- 桥接层会抑制“远端收到后再次回传”的回环转发
|
||||
- 如果你需要按目标 session 精细控制广播范围,使用 `UseEventBusBridge(...)` 或 `BroadcastEventAsync(...)`
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT](LICENSE)
|
||||
远端事件重入 Bus 时会抑制回环转发。网络桥只观察 `EventBus` 全局宿主管理的 Bus;独立 `ShrinkEventBusHost` 不会被自动网络转发。
|
||||
|
||||
Reference in New Issue
Block a user