# ShrinkEventBus 2.0 ShrinkEventBus 2.0 是面向 Unity、普通 C# 宿主和 Entities/Burst 生产端的统一事件总线。运行时只有一种事件模型、一个订阅入口和一组发布 API;不同 Bus 只表达生命周期、所有权与调度策略。 ## 核心契约 ```csharp public interface IShrinkEvent { } public interface IShrinkCancelableEvent : IShrinkEvent { bool IsCanceled { get; } void SetCanceled(bool value); } public interface IShrinkResultEvent : IShrinkEvent { TResult Result { get; } void SetResult(TResult result); } ``` 事件可以是 class、struct 或 unmanaged struct,不需要继承 SDK 基类。 ## 多 Bus ```csharp var gameBus = EventBus.GetOrCreateBus( ShrinkBusKey.Game, ShrinkBusOptions.MainThread()); var modBus = EventBus.CreateBus( ShrinkBusKey.Mod("com.example.mod"), ShrinkBusOptions.DedicatedThread()); var workerBus = EventBus.CreateBus( new ShrinkBusKey("worker", "pathfinding"), ShrinkBusOptions.TaskPool(maxConcurrency: 4)); ``` 标准 Key 包括 `Game`、`Server`、`Scene(id)`、`Mod(id)` 和 `World(id)`。调度器在 Bus 创建时确定,之后不可修改: | Scheduler | 语义 | |---|---| | `Inline` | 在发布线程立即执行 | | `MainThread` | Unity PlayerLoop 主线程;已在主线程时直调 | | `DedicatedThread` | 有界队列、专属线程、Ordered 串行 | | `TaskPool` | 有界队列、显式最大并发;Parallel 忽略优先级顺序 | `Post` 表示投递:同线程同步完成,跨线程入队后返回。`PostAsync` 等待全部 handler 完成,并传播取消与异常。需要结果或最终取消状态时使用 `PostAsync`。 ## 唯一订阅入口 ```csharp [ShrinkEventSubscriber( OwnerId = "com.example.mod", DefaultBus = "mod:com.example.mod")] public sealed class PlayerHandlers { [ShrinkSubscribe(Priority = ShrinkEventPriority.High)] private void OnJoined(PlayerJoinedEvent value) { } [ShrinkSubscribe] private async UniTask OnLoaded( PlayerLoadedEvent value, CancellationToken cancellationToken) { await UniTask.Yield(cancellationToken); } } ``` Bus 解析顺序:方法 `Bus`、类型 `DefaultBus`、`Attach` 传入默认 Bus、否则 `game`。 普通对象由宿主管理生命周期: ```csharp using var binding = EventBus.Attach(new PlayerHandlers()); ``` MonoBehaviour 不需要继承 SDK 基类。可自行在 `OnEnable/OnDisable` 中 Attach/Dispose,也可以在 GameObject 上添加 `ShrinkMonoEventScope`,由它统一绑定同对象或子层级中的生成 subscriber。 静态类型同样只使用特性: ```csharp [ShrinkEventSubscriber(DefaultBus = "game")] public static class GlobalHandlers { [ShrinkSubscribe] private static void OnStartup(GameStartupEvent value) { } } ``` Unity ILPostProcessor 会生成直接调用桥和静态模块初始化注册;纯 .NET incremental generator 要求 subscriber 是 `partial`,并生成 ValueTask handler 绑定。生产路径不枚举程序集和方法,不使用 `MethodInfo.Invoke`、`DynamicInvoke` 或 `object[]` 参数。 ## Unity 与纯 .NET Unity 包依赖 UniTask: ```csharp UniTask PostAsync(...) ``` 纯 .NET 实现在 `Tools~/DotNet/ShrinkEventBus.Core`: ```csharp ValueTask PostAsync(...) ``` 两边共享事件与特性契约,awaitable 和调度实现按宿主选择,不把 UniTask/ValueTask 写进事件数据。 ## ECS/Burst `com.cneicy.shrink-eventbus-entities` 提供: ```csharp ShrinkEcsEventWriter ShrinkEcsEventQueue.Playback(IShrinkEventBus bus) ``` Burst Job 只写 unmanaged 事实事件到 NativeQueue;Playback 在托管/ECS 系统阶段进入相同 Bus。取消、结果或等待异步完成的事件不应从 Burst Job 直接发布。 ## 队列与关闭 `ShrinkBusOptions` 配置队列容量、溢出策略、最大并发、关闭排空和超时。关键业务默认使用 `Reject`;`DropNewest/DropOldest` 只适用于明确允许丢失的数据;`Wait` 只由 `PostAsync` 使用。 ## UI Toolkit 调试器 菜单:`ShrinkSDK/事件总线/事件查看器`。 窗口使用密集的 Bus 导航、事件流和事件详情三段布局,提供 Bus/Scheduler/DispatchMode/队列/溢出策略/handler 数量、实时速率、平均分发耗时、同步或异步执行方式、实际线程、`ShrinkPostResult`、问题筛选、跟随、搜索、CSV 导出与最多 5000 条环形保留。详细采样只在窗口开启且 `Capture` 为启用状态时生效,暂停或关闭窗口后不计时、不保留事件对象。 它只观察静态 `EventBus` 宿主管理的 Bus,不捕获独立 `ShrinkEventBusHost`;性能基准仍使用独立 Host,避免 UI 采样进入被测热路径。 ## Benchmark 实现位于 `Benchmark/ShrinkEventBusBenchmark.cs`,入口: ```csharp ShrinkEventBusBenchmark.Run( iterations: 1_000_000, massIterations: 10_000, registrationIterations: 5_000); ``` 2026-08-24 在 Unity `2022.3.62f3`、Windows Editor Play Mode、独立 Inline Host 下三轮中位数(全局调试采样不观察该 Host): | 场景 | 2.0 ops/s | 1.3.0 基线 | |---|---:|---:| | 0 handler | 129,282,649 | 8,971,378 | | 1 handler | 67,249,586 | 6,132,242 | | 8 handlers, struct | 35,024,798 | 无同口径数据 | | 8 handlers, class | 25,321,199 | MessagePipe 图示 25,639,260 | | 30 handlers | 10,823,612 | 258,805 | | canceled skips 10 | 9,029,517 | 1,894,776 | | 1 async handler | 5,909,446 | 1,168,634 | | generated Attach + Dispose | 674,291 | instance scan 76,353 | 同步 struct `Post` 热路径实测为 `0 B / 10,000,000 ops`;全局门面在没有 Network bridge 对象观察者时也不会为 struct 事件装箱。MessagePipe 图来自不同机器和 .NET benchmark,class 结果只能说明已达到同一吞吐量级,不能作为跨设备胜负结论。当前 Windows x64 Development IL2CPP Player 已构建成功;仍应在目标平台 Player/设备上复测吞吐。 ## 设计边界 - handler 不单独选择线程;需要不同线程亲和性时发布到另一个 Bus。 - Parallel 分发不承诺优先级顺序。 - 同步 `Post` 遇到异步 handler 时是 fire-and-forget;需要等待使用 `PostAsync`。 - 外部 Mod DLL 必须携带生成合同;没有合同的生产 DLL 不自动反射注册。 - Editor 诊断是可选观察层,不参与正式无诊断热路径。 架构审计与来源说明见 `Docs/JustAnyProjectArchitectureAudit.md` 和根目录 `DESIGN.md`。