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:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c674d22418294a93b1da9df8149c49e9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a63a45e5ab8a4de682ca810f8a09cbca
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Pdb;
|
||||
using ShrinkEventBus.CodeGen;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace ShrinkNetwork.Integration.CodeGen
|
||||
{
|
||||
public sealed class NetworkEventILPostProcessor : ILPostProcessor
|
||||
{
|
||||
private const string IntegrationAssembly = "ShrinkNetwork.Integration.EventBus";
|
||||
|
||||
public override ILPostProcessor GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.References.Any(reference =>
|
||||
string.Equals(Path.GetFileNameWithoutExtension(reference), IntegrationAssembly,
|
||||
StringComparison.Ordinal));
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
var diagnostics = new List<DiagnosticMessage>();
|
||||
if (!WillProcess(compiledAssembly))
|
||||
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, diagnostics);
|
||||
|
||||
var assembly = ReadAssembly(compiledAssembly);
|
||||
try
|
||||
{
|
||||
InjectRegistrations(assembly.MainModule);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Error,
|
||||
MessageData = $"[ShrinkNetwork.EventBus.CodeGen] {exception.Message}"
|
||||
});
|
||||
}
|
||||
return WriteAssembly(assembly, diagnostics);
|
||||
}
|
||||
|
||||
private static void InjectRegistrations(ModuleDefinition module)
|
||||
{
|
||||
var networkEventAttribute = ResolveType(module,
|
||||
"ShrinkNetwork.Integration.ShrinkNetworkEventAttribute", IntegrationAssembly);
|
||||
var messageAttribute = ResolveType(module,
|
||||
"ShrinkNetwork.ShrinkNetworkMessageAttribute", "ShrinkNetwork.Runtime");
|
||||
var registryType = ResolveType(module,
|
||||
"ShrinkNetwork.Integration.ShrinkNetworkEventRegistry", IntegrationAssembly);
|
||||
if (networkEventAttribute == null || messageAttribute == null || registryType == null)
|
||||
return;
|
||||
|
||||
var registrations = GetAllTypes(module.Types)
|
||||
.Where(type => !type.IsAbstract && HasAttribute(type, networkEventAttribute))
|
||||
.Select(type => new
|
||||
{
|
||||
Type = type,
|
||||
Message = type.CustomAttributes.FirstOrDefault(attribute =>
|
||||
attribute.AttributeType.FullName == messageAttribute.FullName)
|
||||
})
|
||||
.Where(item => item.Message != null)
|
||||
.ToArray();
|
||||
if (registrations.Length == 0)
|
||||
return;
|
||||
|
||||
var registerDefinition = registryType.Resolve()?.Methods.Single(method =>
|
||||
method.Name == "Register" && method.HasGenericParameters)
|
||||
?? throw new InvalidOperationException("ShrinkNetworkEventRegistry.Register<T> was not found.");
|
||||
var registerOpen = module.ImportReference(registerDefinition);
|
||||
|
||||
var bootstrap = new TypeDefinition("ShrinkNetwork.Integration.Generated",
|
||||
"ShrinkGeneratedNetworkEventBindings",
|
||||
TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.NotPublic,
|
||||
module.TypeSystem.Object);
|
||||
module.Types.Add(bootstrap);
|
||||
var initialize = new MethodDefinition("Register",
|
||||
MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
module.TypeSystem.Void);
|
||||
bootstrap.Methods.Add(initialize);
|
||||
var il = initialize.Body.GetILProcessor();
|
||||
|
||||
foreach (var item in registrations)
|
||||
{
|
||||
var attribute = item.Message!;
|
||||
if (attribute.ConstructorArguments.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
$"[ShrinkNetworkMessage] on {item.Type.FullName} has no opcode.");
|
||||
|
||||
var opcode = Convert.ToInt32(attribute.ConstructorArguments[0].Value);
|
||||
var route = attribute.ConstructorArguments.Count > 1
|
||||
? attribute.ConstructorArguments[1].Value as string
|
||||
: null;
|
||||
var registerClosed = new GenericInstanceMethod(registerOpen);
|
||||
registerClosed.GenericArguments.Add(module.ImportReference(item.Type));
|
||||
il.Emit(OpCodes.Ldc_I4, opcode);
|
||||
if (route == null)
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
else
|
||||
il.Emit(OpCodes.Ldstr, route);
|
||||
il.Emit(OpCodes.Call, registerClosed);
|
||||
}
|
||||
il.Emit(OpCodes.Ret);
|
||||
InjectModuleInitializer(module, initialize);
|
||||
}
|
||||
|
||||
private static void InjectModuleInitializer(ModuleDefinition module, MethodReference register)
|
||||
{
|
||||
var moduleType = module.Types.First(type => type.Name == "<Module>");
|
||||
var initializer = moduleType.Methods.FirstOrDefault(method => method.Name == ".cctor");
|
||||
if (initializer == null)
|
||||
{
|
||||
initializer = new MethodDefinition(".cctor",
|
||||
MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
|
||||
module.TypeSystem.Void);
|
||||
initializer.Body.GetILProcessor().Emit(OpCodes.Ret);
|
||||
moduleType.Methods.Add(initializer);
|
||||
}
|
||||
var il = initializer.Body.GetILProcessor();
|
||||
il.InsertBefore(initializer.Body.Instructions[0], il.Create(OpCodes.Call, register));
|
||||
}
|
||||
|
||||
private static bool HasAttribute(ICustomAttributeProvider provider, TypeReference attributeType) =>
|
||||
provider.CustomAttributes.Any(attribute =>
|
||||
attribute.AttributeType.FullName == attributeType.FullName);
|
||||
|
||||
private static IEnumerable<TypeDefinition> GetAllTypes(IEnumerable<TypeDefinition> roots)
|
||||
{
|
||||
foreach (var type in roots)
|
||||
{
|
||||
yield return type;
|
||||
foreach (var nested in GetAllTypes(type.NestedTypes))
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
|
||||
private static TypeReference? ResolveType(ModuleDefinition module, string fullName,
|
||||
string assemblyName)
|
||||
{
|
||||
var reference = module.AssemblyReferences.FirstOrDefault(item => item.Name == assemblyName);
|
||||
var definition = reference == null ? null : module.AssemblyResolver.Resolve(reference);
|
||||
var type = definition?.MainModule.GetType(fullName);
|
||||
return type == null ? null : module.ImportReference(type);
|
||||
}
|
||||
|
||||
private static AssemblyDefinition ReadAssembly(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
var resolver = new PostProcessorAssemblyResolver(compiledAssembly);
|
||||
var parameters = new ReaderParameters
|
||||
{
|
||||
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()),
|
||||
SymbolReaderProvider = new PdbReaderProvider(),
|
||||
AssemblyResolver = resolver,
|
||||
ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
|
||||
ReadingMode = ReadingMode.Immediate
|
||||
};
|
||||
var assembly = AssemblyDefinition.ReadAssembly(
|
||||
new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()), parameters);
|
||||
resolver.AddAssemblyDefinitionBeingOperatedOn(assembly);
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private static ILPostProcessResult WriteAssembly(AssemblyDefinition assembly,
|
||||
List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
assembly.Write(pe, new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
});
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64b8b451fc094da2a1547ade9f5ba4dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Unity.ShrinkNetwork.EventBus.CodeGen",
|
||||
"rootNamespace": "ShrinkNetwork.Integration.CodeGen",
|
||||
"references": [
|
||||
"ShrinkNetwork.Integration.EventBus",
|
||||
"Unity.ShrinkEventBus.CodeGen"
|
||||
],
|
||||
"includePlatforms": ["Editor"],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Mono.Cecil.dll",
|
||||
"Mono.Cecil.Mdb.dll",
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0970354612f4fb6bdc88828308160c5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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` 不会被自动网络转发。
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using ShrinkEventBus;
|
||||
@@ -16,38 +14,27 @@ namespace ShrinkNetwork.Integration
|
||||
private sealed class ServiceRegistration
|
||||
{
|
||||
public ShrinkNetworkService Service = null!;
|
||||
public Func<ShrinkNetworkSession, EventBase, bool>? SessionFilter;
|
||||
public Func<ShrinkNetworkSession, IShrinkEvent, bool>? SessionFilter;
|
||||
public Dictionary<string, long> AppliedDeltaVersions { get; } = new();
|
||||
}
|
||||
|
||||
private static readonly object SyncRoot = new();
|
||||
private static readonly Dictionary<ShrinkNetworkService, ServiceRegistration> RegisteredServices = new();
|
||||
private static readonly AsyncLocal<int> SuppressForwardDepth = new();
|
||||
private static readonly ConcurrentDictionary<Type, Func<EventBase, UniTask>> IncomingDispatchers = new();
|
||||
private static readonly ConcurrentDictionary<Type, Func<ShrinkNetworkContext, EventBase, UniTask<object?>>> IncomingRequestDispatchers = new();
|
||||
private static readonly MethodInfo DispatchIncomingGenericMethod =
|
||||
typeof(ShrinkNetworkEventBusBridge).GetMethod(nameof(DispatchIncomingGenericAsync),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
private static readonly MethodInfo DispatchIncomingRequestGenericMethod =
|
||||
typeof(ShrinkNetworkEventBusBridge).GetMethod(nameof(DispatchIncomingRequestGenericAsync),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
|
||||
private static Dictionary<Type, ShrinkNetworkMessageAttribute> _networkEventTypes = new();
|
||||
private static Dictionary<Type, ShrinkNetworkEventRegistration> _networkEventTypes = new();
|
||||
private static bool _initialized;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStaticState()
|
||||
{
|
||||
EventBus.OnEventTriggered -= HandleLocalEventTriggered;
|
||||
EventBus.Posted -= HandleLocalEventPosted;
|
||||
lock (SyncRoot)
|
||||
{
|
||||
RegisteredServices.Clear();
|
||||
FallbackDeltaVersions.Clear();
|
||||
}
|
||||
|
||||
IncomingDispatchers.Clear();
|
||||
IncomingRequestDispatchers.Clear();
|
||||
_networkEventTypes = new Dictionary<Type, ShrinkNetworkMessageAttribute>();
|
||||
_networkEventTypes = new Dictionary<Type, ShrinkNetworkEventRegistration>();
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
@@ -58,11 +45,11 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
_initialized = true;
|
||||
RefreshNetworkEventTypes();
|
||||
EventBus.OnEventTriggered += HandleLocalEventTriggered;
|
||||
EventBus.Posted += HandleLocalEventPosted;
|
||||
}
|
||||
|
||||
public static void RegisterService(ShrinkNetworkService service,
|
||||
Func<ShrinkNetworkSession, EventBase, bool>? sessionFilter = null)
|
||||
Func<ShrinkNetworkSession, IShrinkEvent, bool>? sessionFilter = null)
|
||||
{
|
||||
if (service == null)
|
||||
throw new ArgumentNullException(nameof(service));
|
||||
@@ -116,7 +103,7 @@ namespace ShrinkNetwork.Integration
|
||||
}
|
||||
|
||||
public static UniTask PublishAsync<TEvent>(ShrinkNetworkSession session, TEvent eventArgs, string? route = null)
|
||||
where TEvent : EventBase, IShrinkNetworkMessage
|
||||
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
if (session == null)
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
@@ -130,7 +117,7 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
public static async UniTask PublishAsync<TEvent>(IEnumerable<ShrinkNetworkSession> sessions, TEvent eventArgs,
|
||||
string? route = null)
|
||||
where TEvent : EventBase, IShrinkNetworkMessage
|
||||
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
if (sessions == null)
|
||||
throw new ArgumentNullException(nameof(sessions));
|
||||
@@ -159,7 +146,7 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
public static async UniTask<ShrinkNetworkEventResultResponse> RequestResultAsync<TEvent>(ShrinkNetworkSession session,
|
||||
TEvent eventArgs, ShrinkRpcCallOptions? options = null)
|
||||
where TEvent : EventBase, IShrinkNetworkRequest
|
||||
where TEvent : IShrinkResultEvent<EventResult>, IShrinkNetworkRequest
|
||||
{
|
||||
if (session == null)
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
@@ -178,14 +165,14 @@ namespace ShrinkNetwork.Integration
|
||||
if (response.IsSuccess)
|
||||
{
|
||||
eventArgs.SetResult(response.Result);
|
||||
if (eventArgs.IsCancelable)
|
||||
eventArgs.SetCanceled(response.IsCanceled);
|
||||
if (eventArgs is IShrinkCancelableEvent cancelable)
|
||||
cancelable.SetCanceled(response.IsCanceled);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static void HandleLocalEventTriggered(EventBase eventArgs, Type eventType)
|
||||
private static void HandleLocalEventPosted(IShrinkEvent eventArgs, Type eventType, ShrinkBusKey busKey)
|
||||
{
|
||||
if (eventArgs == null || eventType == null)
|
||||
return;
|
||||
@@ -201,7 +188,7 @@ namespace ShrinkNetwork.Integration
|
||||
ForwardEventAsync(eventArgs, eventType, networkMessage).Forget();
|
||||
}
|
||||
|
||||
private static async UniTaskVoid ForwardEventAsync(EventBase eventArgs, Type eventType,
|
||||
private static async UniTaskVoid ForwardEventAsync(IShrinkEvent eventArgs, Type eventType,
|
||||
IShrinkNetworkMessage networkMessage)
|
||||
{
|
||||
List<ServiceRegistration> registrations;
|
||||
@@ -234,9 +221,8 @@ namespace ShrinkNetwork.Integration
|
||||
continue;
|
||||
}
|
||||
|
||||
// Complete every serializer pass before the first await. EventBase
|
||||
// instances may be returned to EventPool immediately after the
|
||||
// synchronous trigger returns.
|
||||
// Complete every serializer pass before the first await so mutable
|
||||
// event payloads cannot change between target sessions.
|
||||
batches.Add((registration, sessions, payload));
|
||||
}
|
||||
|
||||
@@ -269,13 +255,8 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
if (!service.MessageRegistry.TryGetMeta(typeof(ShrinkNetworkEventResultResponse), out _))
|
||||
{
|
||||
var responseAttribute =
|
||||
typeof(ShrinkNetworkEventResultResponse).GetCustomAttribute<ShrinkNetworkMessageAttribute>(false);
|
||||
if (responseAttribute != null)
|
||||
{
|
||||
service.RegisterMessage(typeof(ShrinkNetworkEventResultResponse), responseAttribute.Opcode,
|
||||
responseAttribute.Route);
|
||||
}
|
||||
service.RegisterMessage(typeof(ShrinkNetworkEventResultResponse), -300001,
|
||||
"__integration/event_result_response");
|
||||
}
|
||||
|
||||
var isRequestEvent = typeof(IShrinkNetworkRequest).IsAssignableFrom(pair.Key);
|
||||
@@ -301,43 +282,37 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
private static UniTask DispatchIncomingAsync(ShrinkNetworkContext context, object message)
|
||||
{
|
||||
if (message is not EventBase eventArgs)
|
||||
if (message is not IShrinkEvent eventArgs)
|
||||
return UniTask.CompletedTask;
|
||||
|
||||
var dispatcher = IncomingDispatchers.GetOrAdd(eventArgs.GetType(), CreateIncomingDispatcher);
|
||||
return dispatcher(eventArgs);
|
||||
}
|
||||
|
||||
private static Func<EventBase, UniTask> CreateIncomingDispatcher(Type eventType)
|
||||
{
|
||||
var closedMethod = DispatchIncomingGenericMethod.MakeGenericMethod(eventType);
|
||||
return (Func<EventBase, UniTask>)closedMethod.CreateDelegate(typeof(Func<EventBase, UniTask>));
|
||||
return _networkEventTypes.TryGetValue(eventArgs.GetType(), out var registration)
|
||||
? registration.Dispatch(eventArgs)
|
||||
: UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
private static UniTask<object?> DispatchIncomingRequestAsync(ShrinkNetworkContext context, object message)
|
||||
{
|
||||
if (message is not EventBase eventArgs)
|
||||
if (message is not IShrinkEvent eventArgs)
|
||||
{
|
||||
return UniTask.FromResult<object?>(new ShrinkNetworkEventResultResponse
|
||||
{
|
||||
ErrorCode = ShrinkRpcErrorCode.InvalidResponse,
|
||||
ErrorMessage = "Incoming network event request is not an EventBase."
|
||||
ErrorMessage = "Incoming network event request does not implement IShrinkEvent."
|
||||
});
|
||||
}
|
||||
|
||||
var dispatcher = IncomingRequestDispatchers.GetOrAdd(eventArgs.GetType(), CreateIncomingRequestDispatcher);
|
||||
return dispatcher(context, eventArgs);
|
||||
return _networkEventTypes.TryGetValue(eventArgs.GetType(), out var registration) &&
|
||||
registration.RequestDispatch != null
|
||||
? registration.RequestDispatch(context, eventArgs)
|
||||
: UniTask.FromResult<object?>(new ShrinkNetworkEventResultResponse
|
||||
{
|
||||
ErrorCode = ShrinkRpcErrorCode.InvalidResponse,
|
||||
ErrorMessage = $"No generated network event request binding for {eventArgs.GetType().FullName}."
|
||||
});
|
||||
}
|
||||
|
||||
private static Func<ShrinkNetworkContext, EventBase, UniTask<object?>> CreateIncomingRequestDispatcher(Type eventType)
|
||||
{
|
||||
var closedMethod = DispatchIncomingRequestGenericMethod.MakeGenericMethod(eventType);
|
||||
return (Func<ShrinkNetworkContext, EventBase, UniTask<object?>>)closedMethod.CreateDelegate(
|
||||
typeof(Func<ShrinkNetworkContext, EventBase, UniTask<object?>>));
|
||||
}
|
||||
|
||||
private static async UniTask DispatchIncomingGenericAsync<TEvent>(EventBase eventArgs)
|
||||
where TEvent : EventBase
|
||||
internal static async UniTask DispatchGeneratedAsync<TEvent>(TEvent eventArgs)
|
||||
where TEvent : IShrinkEvent
|
||||
{
|
||||
SuppressForwardDepth.Value++;
|
||||
try
|
||||
@@ -348,7 +323,7 @@ namespace ShrinkNetwork.Integration
|
||||
return;
|
||||
}
|
||||
|
||||
await EventBus.TriggerEventAsync((TEvent)eventArgs);
|
||||
await EventBus.PostAsync(eventArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -356,23 +331,21 @@ namespace ShrinkNetwork.Integration
|
||||
}
|
||||
}
|
||||
|
||||
private static async UniTask<object?> DispatchIncomingRequestGenericAsync<TEvent>(ShrinkNetworkContext context,
|
||||
EventBase eventArgs)
|
||||
where TEvent : EventBase
|
||||
internal static async UniTask<object?> DispatchGeneratedRequestAsync<TEvent>(
|
||||
ShrinkNetworkContext context, TEvent eventArgs)
|
||||
where TEvent : IShrinkEvent
|
||||
{
|
||||
var typedEvent = (TEvent)eventArgs;
|
||||
|
||||
SuppressForwardDepth.Value++;
|
||||
try
|
||||
{
|
||||
if (TryGetDeltaEvent(typedEvent, typeof(TEvent), out var deltaEvent) &&
|
||||
if (TryGetDeltaEvent(eventArgs, typeof(TEvent), out var deltaEvent) &&
|
||||
!TryMarkIncomingDelta(typeof(TEvent), deltaEvent, context))
|
||||
{
|
||||
return BuildResultResponse(typedEvent);
|
||||
return BuildResultResponse(eventArgs);
|
||||
}
|
||||
|
||||
await EventBus.TriggerEventAsync(typedEvent);
|
||||
return BuildResultResponse(typedEvent);
|
||||
await EventBus.PostAsync(eventArgs);
|
||||
return BuildResultResponse(eventArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -381,8 +354,10 @@ namespace ShrinkNetwork.Integration
|
||||
{
|
||||
ErrorCode = ShrinkRpcErrorCode.HandlerException,
|
||||
ErrorMessage = ex.Message,
|
||||
Result = typedEvent.HasResult ? typedEvent.Result : EventResult.DEFAULT,
|
||||
IsCanceled = typedEvent.IsCancelable && typedEvent.IsCanceled
|
||||
Result = eventArgs is IShrinkResultEvent<EventResult> resultEvent
|
||||
? resultEvent.Result
|
||||
: EventResult.DEFAULT,
|
||||
IsCanceled = eventArgs is IShrinkCancelableEvent cancelable && cancelable.IsCanceled
|
||||
};
|
||||
}
|
||||
finally
|
||||
@@ -393,62 +368,30 @@ namespace ShrinkNetwork.Integration
|
||||
|
||||
private static void RefreshNetworkEventTypes()
|
||||
{
|
||||
var eventTypes = new Dictionary<Type, ShrinkNetworkMessageAttribute>();
|
||||
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type[] types;
|
||||
try
|
||||
{
|
||||
types = assembly.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type == null || type.IsAbstract)
|
||||
continue;
|
||||
if (!typeof(EventBase).IsAssignableFrom(type))
|
||||
continue;
|
||||
if (!typeof(IShrinkNetworkMessage).IsAssignableFrom(type))
|
||||
continue;
|
||||
if (type.GetCustomAttribute<ShrinkNetworkEventAttribute>(false) == null)
|
||||
continue;
|
||||
|
||||
var messageAttr = type.GetCustomAttribute<ShrinkNetworkMessageAttribute>(false);
|
||||
if (messageAttr == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[ShrinkNetwork.Integration] {type.FullName} 标记了 [ShrinkNetworkEvent],但缺少 [ShrinkNetworkMessage],已忽略。");
|
||||
continue;
|
||||
}
|
||||
|
||||
eventTypes[type] = messageAttr;
|
||||
}
|
||||
}
|
||||
var eventTypes = new Dictionary<Type, ShrinkNetworkEventRegistration>();
|
||||
foreach (var registration in ShrinkNetworkEventRegistry.Snapshot())
|
||||
eventTypes[registration.EventType] = registration;
|
||||
|
||||
_networkEventTypes = eventTypes;
|
||||
}
|
||||
|
||||
private static bool TryGetNetworkEventMeta(Type eventType, out ShrinkNetworkMessageAttribute? messageAttribute)
|
||||
private static bool TryGetNetworkEventMeta(Type eventType,
|
||||
out ShrinkNetworkEventRegistration registration)
|
||||
{
|
||||
if (eventType == null)
|
||||
{
|
||||
messageAttribute = null;
|
||||
registration = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return _networkEventTypes.TryGetValue(eventType, out messageAttribute);
|
||||
return _networkEventTypes.TryGetValue(eventType, out registration);
|
||||
}
|
||||
|
||||
private static void EnsureNetworkEventType(Type eventType)
|
||||
{
|
||||
if (!TryGetNetworkEventMeta(eventType, out _))
|
||||
throw new InvalidOperationException(
|
||||
$"Event type {eventType.FullName} must implement EventBase + IShrinkNetworkMessage and declare [ShrinkNetworkEvent] + [ShrinkNetworkMessage].");
|
||||
$"Event type {eventType.FullName} must implement IShrinkEvent + IShrinkNetworkMessage and declare [ShrinkNetworkEvent] + [ShrinkNetworkMessage].");
|
||||
}
|
||||
|
||||
private static void EnsurePublishableEventType(Type eventType)
|
||||
@@ -465,21 +408,23 @@ namespace ShrinkNetwork.Integration
|
||||
if (!typeof(IShrinkNetworkRequest).IsAssignableFrom(eventType))
|
||||
throw new InvalidOperationException(
|
||||
$"Event type {eventType.FullName} must implement IShrinkNetworkRequest to use RequestResultAsync.");
|
||||
if (eventType.GetCustomAttribute<HasResultAttribute>(false) == null)
|
||||
if (!typeof(IShrinkResultEvent<EventResult>).IsAssignableFrom(eventType))
|
||||
throw new InvalidOperationException(
|
||||
$"Event type {eventType.FullName} must declare [HasResult] to use RequestResultAsync.");
|
||||
$"Event type {eventType.FullName} must implement IShrinkResultEvent<EventResult> to use RequestResultAsync.");
|
||||
}
|
||||
|
||||
private static ShrinkNetworkEventResultResponse BuildResultResponse(EventBase eventArgs)
|
||||
private static ShrinkNetworkEventResultResponse BuildResultResponse(IShrinkEvent eventArgs)
|
||||
{
|
||||
return new ShrinkNetworkEventResultResponse
|
||||
{
|
||||
Result = eventArgs.HasResult ? eventArgs.Result : EventResult.DEFAULT,
|
||||
IsCanceled = eventArgs.IsCancelable && eventArgs.IsCanceled
|
||||
Result = eventArgs is IShrinkResultEvent<EventResult> resultEvent
|
||||
? resultEvent.Result
|
||||
: EventResult.DEFAULT,
|
||||
IsCanceled = eventArgs is IShrinkCancelableEvent cancelable && cancelable.IsCanceled
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryGetDeltaEvent(EventBase eventArgs, Type eventType, out IShrinkNetworkDeltaEvent deltaEvent)
|
||||
private static bool TryGetDeltaEvent(IShrinkEvent eventArgs, Type eventType, out IShrinkNetworkDeltaEvent deltaEvent)
|
||||
{
|
||||
if (eventArgs is IShrinkNetworkDeltaEvent eventDelta)
|
||||
{
|
||||
|
||||
+3
-3
@@ -27,14 +27,14 @@ namespace ShrinkNetwork.Integration
|
||||
}
|
||||
|
||||
public static UniTask PublishEventAsync<TEvent>(this ShrinkNetworkSession session, TEvent eventArgs, string? route = null)
|
||||
where TEvent : EventBase, IShrinkNetworkMessage
|
||||
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
return ShrinkNetworkEventBusBridge.PublishAsync(session, eventArgs, route);
|
||||
}
|
||||
|
||||
public static UniTask BroadcastEventAsync<TEvent>(this ShrinkNetworkService service, TEvent eventArgs,
|
||||
Func<ShrinkNetworkSession, bool>? sessionFilter = null, string? route = null)
|
||||
where TEvent : EventBase, IShrinkNetworkMessage
|
||||
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
if (service == null)
|
||||
throw new ArgumentNullException(nameof(service));
|
||||
@@ -59,7 +59,7 @@ namespace ShrinkNetwork.Integration
|
||||
this ShrinkNetworkSession session,
|
||||
TEvent eventArgs,
|
||||
ShrinkRpcCallOptions? options = null)
|
||||
where TEvent : EventBase, IShrinkNetworkRequest
|
||||
where TEvent : IShrinkResultEvent<EventResult>, IShrinkNetworkRequest
|
||||
{
|
||||
var response = await ShrinkNetworkEventBusBridge.RequestResultAsync(session, eventArgs, options);
|
||||
return ShrinkNetworkEventRequestOutcome.FromResponse(response);
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ namespace ShrinkNetwork.Integration
|
||||
{
|
||||
public bool AutoRegisterAttributedMessages { get; set; } = true;
|
||||
public bool AutoRegisterStaticHandlers { get; set; }
|
||||
public Func<ShrinkNetworkSession, EventBase, bool>? SessionFilter { get; set; }
|
||||
public Func<ShrinkNetworkSession, IShrinkEvent, bool>? SessionFilter { get; set; }
|
||||
public IShrinkNetworkDispatchScheduler? DispatchScheduler { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using ShrinkEventBus;
|
||||
|
||||
namespace ShrinkNetwork.Integration
|
||||
{
|
||||
public readonly struct ShrinkNetworkEventRegistration
|
||||
{
|
||||
public ShrinkNetworkEventRegistration(Type eventType, int opcode, string? route,
|
||||
Func<IShrinkEvent, UniTask> dispatch,
|
||||
Func<ShrinkNetworkContext, IShrinkEvent, UniTask<object?>>? requestDispatch)
|
||||
{
|
||||
EventType = eventType ?? throw new ArgumentNullException(nameof(eventType));
|
||||
Opcode = opcode;
|
||||
Route = route;
|
||||
Dispatch = dispatch ?? throw new ArgumentNullException(nameof(dispatch));
|
||||
RequestDispatch = requestDispatch;
|
||||
}
|
||||
|
||||
public Type EventType { get; }
|
||||
public int Opcode { get; }
|
||||
public string? Route { get; }
|
||||
public Func<IShrinkEvent, UniTask> Dispatch { get; }
|
||||
public Func<ShrinkNetworkContext, IShrinkEvent, UniTask<object?>>? RequestDispatch { get; }
|
||||
}
|
||||
|
||||
public static class ShrinkNetworkEventRegistry
|
||||
{
|
||||
private static readonly object Gate = new();
|
||||
private static readonly Dictionary<Type, ShrinkNetworkEventRegistration> Registrations = new();
|
||||
|
||||
public static void Register<TEvent>(int opcode, string? route)
|
||||
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
var registration = new ShrinkNetworkEventRegistration(
|
||||
typeof(TEvent), opcode, route,
|
||||
static eventData => ShrinkNetworkEventBusBridge.DispatchGeneratedAsync((TEvent)eventData),
|
||||
typeof(IShrinkNetworkRequest).IsAssignableFrom(typeof(TEvent))
|
||||
? static (context, eventData) =>
|
||||
ShrinkNetworkEventBusBridge.DispatchGeneratedRequestAsync(context, (TEvent)eventData)
|
||||
: null);
|
||||
lock (Gate)
|
||||
Registrations[typeof(TEvent)] = registration;
|
||||
}
|
||||
|
||||
internal static IReadOnlyList<ShrinkNetworkEventRegistration> Snapshot()
|
||||
{
|
||||
lock (Gate)
|
||||
return Registrations.Values.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8863e85b074d7486cd06cbe46b8dd3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba18b28ec93a44c2b78d95b2378f82d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "ShrinkNetwork.Integration.EventBus.Tests",
|
||||
"rootNamespace": "ShrinkNetwork.Integration.EventBus.Tests",
|
||||
"references": [
|
||||
"ShrinkNetwork.Integration.EventBus",
|
||||
"ShrinkNetwork.Runtime",
|
||||
"ShrinkEventBus.Runtime",
|
||||
"UniTask",
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
],
|
||||
"includePlatforms": ["Editor"],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": ["UNITY_INCLUDE_TESTS"]
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 132ab80308b64e7abf3a68bb161b7d2f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#nullable enable
|
||||
|
||||
using NUnit.Framework;
|
||||
using ShrinkEventBus;
|
||||
|
||||
namespace ShrinkNetwork.Integration.EventBus.Tests
|
||||
{
|
||||
[ShrinkNetworkEvent]
|
||||
[ShrinkNetworkMessage(49001, "tests/generated_event")]
|
||||
internal sealed class GeneratedNetworkEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class ShrinkNetworkEventCodeGenTests
|
||||
{
|
||||
[Test]
|
||||
public void GeneratedRegistrationAddsNetworkEventToService()
|
||||
{
|
||||
var service = new ShrinkNetworkService(
|
||||
new ShrinkJsonNetworkSerializer(),
|
||||
new ShrinkNetworkMessageRegistry(),
|
||||
new ShrinkNetworkRouter());
|
||||
ShrinkNetworkEventBusBridge.RegisterService(service);
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(service.MessageRegistry.TryGetMeta(typeof(GeneratedNetworkEvent), out var meta));
|
||||
Assert.AreEqual(49001, meta!.Opcode);
|
||||
Assert.AreEqual("tests/generated_event", meta.Route);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ShrinkNetworkEventBusBridge.UnregisterService(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d790812eb9845b0820da061de4d7fe5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,12 +2,12 @@
|
||||
"name": "com.cneicy.shrink-network-integration-eventbus",
|
||||
"version": "0.1.1",
|
||||
"displayName": "ShrinkNetwork - EventBus Integration",
|
||||
"description": "ShrinkNetwork 与 ShrinkEventBus 的桥接层,让特定 EventBase 事件类型可直接走网络同步并在远端重新分发到 EventBus。",
|
||||
"description": "ShrinkNetwork 与 ShrinkEventBus 2.0 的生成式桥接层,让 IShrinkEvent 网络事件可广播、裁决并在远端重新进入命名 Bus。",
|
||||
"unity": "2022.3",
|
||||
"dependencies": {
|
||||
"com.cneicy.shrink-network": "0.2.0",
|
||||
"com.cneicy.shrink-context-core": "0.1.0",
|
||||
"com.cneicy.shrink-eventbus": "1.3.0"
|
||||
"com.cneicy.shrink-eventbus": "2.0.0"
|
||||
},
|
||||
"keywords": ["network", "eventbus", "integration", "bridge", "sync"],
|
||||
"author": {
|
||||
|
||||
Reference in New Issue
Block a user