Files
Workspace/Assets/Modules/ShrinkNetwork.Integration.EventBus
..

ShrinkNetwork.Integration.EventBus

ShrinkNetworkShrinkEventBus 的桥接层。目标不是“把网络生命周期抛几个普通事件出来”,而是让某些 EventBase 类型本身就能作为网络消息声明,并在本地与远端之间保持一致的事件语义。

特性概览

特性 说明
🌉 事件即网络消息 EventBase 可直接实现 IShrinkNetworkMessage / IShrinkNetworkRequest 参与网络同步
🔁 自动双端分发 本地 EventBus.TriggerEvent(...) 后,可自动转发到远端并重新进入远端 EventBus
HasResult 请求语义 请求型事件支持 EventResult 与取消状态回传
📉 增量事件去重 IShrinkNetworkDeltaEventSessionId + EventType + DeltaKey 做版本过滤
🧩 按键接入 ShrinkNetworkEventBusComponent 注入网络服务,随依赖激活和撤回
🧰 更顺手的扩展 API session.PublishEventAsync(...)session.RequestEventAsync(...)service.UseEventBusBridge(...)

📦 依赖

  • ShrinkNetwork
  • ShrinkEventBus
  • Unity 2022.3+

⚙️ 安装前提

桥接层默认假设你已经有:

  • 一套正常工作的 ShrinkNetworkService
  • 一套正常工作的 ShrinkEventBus
  • 事件类型明确声明 [ShrinkNetworkEvent] + [ShrinkNetworkMessage]

🚀 快速上手

第一步:声明广播型网络事件

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;
}

第二步:正常启动网络服务

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(...);仅绑定传输不会再通过反射接桥。

第三步:像普通事件一样触发

EventBus.TriggerEvent(new PlayerReadyEvent
{
    PlayerId = "10001",
    RoomId = "alpha"
});

注册之后:

  • 本地事件先按正常 EventBus 流程执行
  • 桥接层自动把该事件转发到当前 ShrinkNetworkService 的在线 session
  • 远端收到后重新进入远端 EventBus.TriggerEventAsync(...)

📖 核心概念

事件声明规则

可桥接事件需要同时满足:

  1. 继承 EventBase
  2. 实现 IShrinkNetworkMessageIShrinkNetworkRequest
  3. 标记 [ShrinkNetworkEvent]
  4. 标记 [ShrinkNetworkMessage(opcode, route)]

如果缺少这些条件,桥接层会忽略该事件类型。

广播型事件

广播型事件只需要:

  • EventBase
  • IShrinkNetworkMessage
  • [ShrinkNetworkEvent]
  • [ShrinkNetworkMessage(...)]

适合:

  • 玩家就绪
  • 房间状态变更
  • UI 同步通知

手动定向发送时,推荐直接用扩展:

await session.PublishEventAsync(new PlayerReadyEvent
{
    PlayerId = "10001",
    RoomId = "alpha"
});

增量事件

如果事件本身就是 delta,而不是完整状态,可以实现 IShrinkNetworkDeltaEvent

[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

就可以走“远端裁决”语义:

[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;
}
var outcome = await session.RequestEventAsync(new CanUseSkillEvent
{
    PlayerId = "10001",
    SkillId = "fireball"
});

if (outcome.IsSuccess && outcome.Result == EventResult.ALLOW)
{
    // 允许释放技能
}

当前回传内容只有:

  • EventResult
  • IsCanceled
  • ErrorCode / ErrorMessage

不会自动回传整个事件对象上其他字段的最终改动。

🔧 API 参考

ContextLoader 接入

// ShrinkApp.Starter.Basic 组合根自动加入 ShrinkNetworkEventBusComponent。
// 组件注入 shrink.service.network 后注册,网络提供者撤回时注销。

显式接入

Standalone 或需要自定义 session 过滤时:

service.UseEventBusBridge(new ShrinkNetworkEventBusBridgeOptions
{
    SessionFilter = (session, evt) => session.SessionId > 0,
    DispatchScheduler = new ShrinkNetworkUnityMainThreadDispatchScheduler()
});

如果网络事件数量较大并且需要明确的每帧预算,使用 ShrinkNetworkDispatchQueue,在 Unity 主线程的 Update 中调用 PumpAsync(maxItems);队列满时默认拒绝,避免无界堆积。

发送扩展

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