253 lines
7.0 KiB
Markdown
253 lines
7.0 KiB
Markdown
# ShrinkNetwork.Integration.EventBus
|
||
|
||
`ShrinkNetwork` 与 `ShrinkEventBus` 的桥接层。目标不是“把网络生命周期抛几个普通事件出来”,而是让某些 `EventBase` 类型本身就能作为网络消息声明,并在本地与远端之间保持一致的事件语义。
|
||
|
||
## ✨ 特性概览
|
||
|
||
| 特性 | 说明 |
|
||
|------|------|
|
||
| 🌉 **事件即网络消息** | `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 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));
|
||
```
|
||
|
||
ContextLoader 项目由 Starter 组合根装配 `ShrinkNetworkEventBusComponent`。Standalone 项目需要显式调用 `service.UseEventBusBridge(...)`;仅绑定传输不会再通过反射接桥。
|
||
|
||
### 第三步:像普通事件一样触发
|
||
|
||
```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 string PlayerId { get; set; } = string.Empty;
|
||
public string SkillId { get; set; } = string.Empty;
|
||
}
|
||
```
|
||
|
||
```csharp
|
||
var outcome = await session.RequestEventAsync(new CanUseSkillEvent
|
||
{
|
||
PlayerId = "10001",
|
||
SkillId = "fireball"
|
||
});
|
||
|
||
if (outcome.IsSuccess && outcome.Result == EventResult.ALLOW)
|
||
{
|
||
// 允许释放技能
|
||
}
|
||
```
|
||
|
||
当前回传内容只有:
|
||
|
||
- `EventResult`
|
||
- `IsCanceled`
|
||
- `ErrorCode / ErrorMessage`
|
||
|
||
不会自动回传整个事件对象上其他字段的最终改动。
|
||
|
||
## 🔧 API 参考
|
||
|
||
### ContextLoader 接入
|
||
|
||
```csharp
|
||
// ShrinkApp.Starter.Basic 组合根自动加入 ShrinkNetworkEventBusComponent。
|
||
// 组件注入 shrink.service.network 后注册,网络提供者撤回时注销。
|
||
```
|
||
|
||
### 显式接入
|
||
|
||
Standalone 或需要自定义 session 过滤时:
|
||
|
||
```csharp
|
||
service.UseEventBusBridge(new ShrinkNetworkEventBusBridgeOptions
|
||
{
|
||
SessionFilter = (session, evt) => session.SessionId > 0,
|
||
DispatchScheduler = new ShrinkNetworkUnityMainThreadDispatchScheduler()
|
||
});
|
||
```
|
||
|
||
如果网络事件数量较大并且需要明确的每帧预算,使用 `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)
|
||
```
|
||
|
||
## 🏗️ 工作流
|
||
|
||
```
|
||
本地 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)
|