# 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(eventArgs, route = null) session.RequestEventAsync(eventArgs, options = null) service.BroadcastEventAsync(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)