This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
name: Publish UPM package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Fetch tagged revision
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
ref="${{ gitea.sha }}"
|
||||||
|
test -n "$ref"
|
||||||
|
git init .
|
||||||
|
git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkNetwork.Integration.EventBus.git"
|
||||||
|
git fetch --depth=1 origin "$ref"
|
||||||
|
git checkout --detach FETCH_HEAD
|
||||||
|
|
||||||
|
- name: Validate immutable release version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
tag="$(git describe --exact-match --tags HEAD)"
|
||||||
|
version="$(node -p "require('./package.json').version")"
|
||||||
|
test "$tag" = "v$version"
|
||||||
|
npm pack --dry-run
|
||||||
|
|
||||||
|
- name: Publish to ShrinkSDK registry
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
: "${NODE_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}"
|
||||||
|
npmrc="$HOME/.npmrc"
|
||||||
|
cleanup() { rm -f "$npmrc"; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
printf '%s\n' \
|
||||||
|
'registry=https://git.crash.work/api/packages/ShrinkSDK/npm/' \
|
||||||
|
'//git.crash.work/api/packages/ShrinkSDK/npm/:_authToken=${NODE_AUTH_TOKEN}' > "$npmrc"
|
||||||
|
npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Verify standalone Unity package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
editmode:
|
||||||
|
runs-on: unity-2022.3.62f3
|
||||||
|
container:
|
||||||
|
image: docker.1panel.live/unityci/editor:ubuntu-2022.3.62f3-windows-mono-3
|
||||||
|
volumes:
|
||||||
|
- /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-license:/root/.local/share/unity3d/Unity
|
||||||
|
- /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-entitlements:/root/.config/unity3d/Unity/licenses
|
||||||
|
steps:
|
||||||
|
- name: Fetch selected revision
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
ref="${{ gitea.sha }}"
|
||||||
|
git init .
|
||||||
|
git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkNetwork.Integration.EventBus.git"
|
||||||
|
git fetch --depth=1 origin "$ref"
|
||||||
|
git checkout --detach FETCH_HEAD
|
||||||
|
|
||||||
|
- name: Run package EditMode tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
unity_bin="$(command -v unity-editor || command -v unity || command -v Unity || true)"
|
||||||
|
test -n "$unity_bin"
|
||||||
|
"$unity_bin" \
|
||||||
|
-batchmode \
|
||||||
|
-nographics \
|
||||||
|
-quit \
|
||||||
|
-projectPath "$PWD/Development~/UnityProject" \
|
||||||
|
-runTests \
|
||||||
|
-testPlatform EditMode \
|
||||||
|
-testResults "$PWD/TestResults/editmode.xml" \
|
||||||
|
-logFile "$PWD/TestResults/unity.log"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/Development~/UnityProject/[Ll]ibrary/
|
||||||
|
/Development~/UnityProject/[Tt]emp/
|
||||||
|
/Development~/UnityProject/[Oo]bj/
|
||||||
|
/Development~/UnityProject/[Ll]ogs/
|
||||||
|
/Development~/UnityProject/[Uu]ser[Ss]ettings/
|
||||||
|
/Development~/UnityProject/TestResults/
|
||||||
|
/Tools~/**/[Bb]in/
|
||||||
|
/Tools~/**/[Oo]bj/
|
||||||
|
*.user
|
||||||
|
*.DotSettings.user
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.git/
|
||||||
|
.gitea/
|
||||||
|
Development~/
|
||||||
|
Tools~/
|
||||||
|
*.csproj
|
||||||
|
*.sln
|
||||||
|
*.user
|
||||||
|
*.DotSettings.user
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
本文件记录 `ShrinkNetwork.Integration.EventBus` 在当前工作区中的包内变更。
|
||||||
|
|
||||||
|
## [0.1.1] - 2026-04-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 提供更顺手的桥接扩展:`session.PublishEventAsync(...)`、`session.RequestEventAsync(...)`、`service.BroadcastEventAsync(...)`、`service.UseEventBusBridge(...)`。
|
||||||
|
- 把 EventBus 事件桥接分为广播事件、请求事件、增量事件三类语义。
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 桥接层转发过程中配合 `ShrinkEventBus` 语义修正,异步路径改为以克隆/快照方式分发,降低复用事件对象带来的污染风险。
|
||||||
|
- README 重写,补齐桥接范式、事件语义和接入说明。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d177dfebca8947e438bde6305bb342a7
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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:
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 64b8b451fc094da2a1547ade9f5ba4dd
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a0970354612f4fb6bdc88828308160c5
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Ll]ibrary/
|
||||||
|
[Tt]emp/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]ogs/
|
||||||
|
[Uu]ser[Ss]ettings/
|
||||||
|
TestResults/
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"scopedRegistries": [
|
||||||
|
{
|
||||||
|
"name": "ShrinkSDK",
|
||||||
|
"url": "https://git.crash.work/api/packages/ShrinkSDK/npm/",
|
||||||
|
"scopes": [
|
||||||
|
"com.cneicy"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.test-framework": "1.1.33",
|
||||||
|
"com.cneicy.shrink-network-integration-eventbus": "file:../../.."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
m_EditorVersion: 2022.3.62f3
|
||||||
|
m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# ShrinkNetwork.Integration.EventBus
|
||||||
|
|
||||||
|
把实现 `IShrinkEvent` 的网络事件与 ShrinkEventBus 2.0 连接。广播事件可从本地 Bus 转发到远端;请求事件可在远端 `PostAsync` 后回传 `EventResult` 与取消状态;delta 事件按版本去重。
|
||||||
|
|
||||||
|
## 广播事件
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[ShrinkNetworkEvent]
|
||||||
|
[ShrinkNetworkMessage(3001, "room/player_ready")]
|
||||||
|
public sealed class PlayerReadyEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||||
|
{
|
||||||
|
public string PlayerId { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
EventBus.Post(new PlayerReadyEvent { PlayerId = "10001" });
|
||||||
|
await session.PublishEventAsync(new PlayerReadyEvent { PlayerId = "10001" });
|
||||||
|
```
|
||||||
|
|
||||||
|
事件必须同时实现 `IShrinkEvent`、`IShrinkNetworkMessage`,并声明 `[ShrinkNetworkEvent]` 与 `[ShrinkNetworkMessage]`。本包的 ILPostProcessor 为每个事件生成强类型入站 dispatcher 和模块注册,不在运行时扫描程序集、构造泛型方法或反射调用 handler。
|
||||||
|
|
||||||
|
## 远端裁决
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[ShrinkNetworkEvent]
|
||||||
|
[ShrinkNetworkMessage(3010, "room/can_use_skill")]
|
||||||
|
public sealed class CanUseSkillEvent :
|
||||||
|
IShrinkResultEvent<EventResult>, IShrinkCancelableEvent, IShrinkNetworkRequest
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
```
|
||||||
|
|
||||||
|
请求事件必须实现 `IShrinkResultEvent<EventResult>` 与 `IShrinkNetworkRequest`。如果需要取消回传,再实现 `IShrinkCancelableEvent`。当前响应只包含 `EventResult`、取消状态和 RPC 错误,不自动回传事件上的其它可变字段。
|
||||||
|
|
||||||
|
## Delta
|
||||||
|
|
||||||
|
实现 `IShrinkNetworkDeltaEvent` 后,入站按 `SessionId + EventType + DeltaKey` 记录最高 `DeltaVersion`;旧版本和重复版本不会进入 Bus。
|
||||||
|
|
||||||
|
## 接入
|
||||||
|
|
||||||
|
ContextLoader 默认通过 `ShrinkNetworkEventBusComponent` 接桥。Standalone:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
service.UseEventBusBridge(new ShrinkNetworkEventBusBridgeOptions
|
||||||
|
{
|
||||||
|
SessionFilter = (session, value) => session.SessionId > 0
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
可用发送扩展:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
session.PublishEventAsync(eventData);
|
||||||
|
session.RequestEventAsync(eventData);
|
||||||
|
service.BroadcastEventAsync(eventData);
|
||||||
|
```
|
||||||
|
|
||||||
|
远端事件重入 Bus 时会抑制回环转发。网络桥只观察 `EventBus` 全局宿主管理的 Bus;独立 `ShrinkEventBusHost` 不会被自动网络转发。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c7592055b3962e549a3a54179b99be3d
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "ShrinkNetwork.Integration.EventBus",
|
||||||
|
"rootNamespace": "ShrinkNetwork.Integration",
|
||||||
|
"references": [
|
||||||
|
"ShrinkNetwork.Runtime",
|
||||||
|
"ShrinkContext.Core.Runtime",
|
||||||
|
"ShrinkEventBus.Runtime",
|
||||||
|
"UniTask"
|
||||||
|
],
|
||||||
|
"optionalUnityReferences": [],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5642850b24bc7d248b4d2ff56c5275e5
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using ShrinkEventBus;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
public interface IShrinkNetworkDeltaEvent
|
||||||
|
{
|
||||||
|
string DeltaKey { get; }
|
||||||
|
long DeltaVersion { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ShrinkNetworkMessage(-300001, "__integration/event_result_response")]
|
||||||
|
public sealed class ShrinkNetworkEventResultResponse : ShrinkRpcResponseBase
|
||||||
|
{
|
||||||
|
public EventResult Result { get; set; } = EventResult.DEFAULT;
|
||||||
|
public bool IsCanceled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bd46e5dac04669543aee6fd72fd9adea
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class ShrinkNetworkEventAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3006890f24213734a9a8d7ccfb440c5f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using ShrinkEventBus;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
public static class ShrinkNetworkEventBusBridge
|
||||||
|
{
|
||||||
|
private sealed class ServiceRegistration
|
||||||
|
{
|
||||||
|
public ShrinkNetworkService Service = null!;
|
||||||
|
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 Dictionary<Type, ShrinkNetworkEventRegistration> _networkEventTypes = new();
|
||||||
|
private static bool _initialized;
|
||||||
|
|
||||||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||||
|
private static void ResetStaticState()
|
||||||
|
{
|
||||||
|
EventBus.Posted -= HandleLocalEventPosted;
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
RegisteredServices.Clear();
|
||||||
|
FallbackDeltaVersions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_networkEventTypes = new Dictionary<Type, ShrinkNetworkEventRegistration>();
|
||||||
|
_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureBridgeInitialized()
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
RefreshNetworkEventTypes();
|
||||||
|
EventBus.Posted += HandleLocalEventPosted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterService(ShrinkNetworkService service,
|
||||||
|
Func<ShrinkNetworkSession, IShrinkEvent, bool>? sessionFilter = null)
|
||||||
|
{
|
||||||
|
if (service == null)
|
||||||
|
throw new ArgumentNullException(nameof(service));
|
||||||
|
|
||||||
|
EnsureBridgeInitialized();
|
||||||
|
RefreshNetworkEventTypes();
|
||||||
|
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (RegisteredServices.TryGetValue(service, out var existing))
|
||||||
|
{
|
||||||
|
existing.SessionFilter = sessionFilter;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisteredServices.Add(service, new ServiceRegistration
|
||||||
|
{
|
||||||
|
Service = service,
|
||||||
|
SessionFilter = sessionFilter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterInboundBridgeHandlers(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnregisterService(ShrinkNetworkService service)
|
||||||
|
{
|
||||||
|
if (service == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
RegisteredServices.Remove(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RefreshBindings()
|
||||||
|
{
|
||||||
|
RefreshNetworkEventTypes();
|
||||||
|
|
||||||
|
List<ShrinkNetworkService> services;
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
services = RegisteredServices.Keys.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var service in services)
|
||||||
|
{
|
||||||
|
RegisterInboundBridgeHandlers(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UniTask PublishAsync<TEvent>(ShrinkNetworkSession session, TEvent eventArgs, string? route = null)
|
||||||
|
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||||
|
{
|
||||||
|
if (session == null)
|
||||||
|
throw new ArgumentNullException(nameof(session));
|
||||||
|
if (eventArgs == null)
|
||||||
|
throw new ArgumentNullException(nameof(eventArgs));
|
||||||
|
|
||||||
|
EnsureNetworkEventType(typeof(TEvent));
|
||||||
|
EnsurePublishableEventType(typeof(TEvent));
|
||||||
|
return session.SendAsync(eventArgs, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async UniTask PublishAsync<TEvent>(IEnumerable<ShrinkNetworkSession> sessions, TEvent eventArgs,
|
||||||
|
string? route = null)
|
||||||
|
where TEvent : IShrinkEvent, IShrinkNetworkMessage
|
||||||
|
{
|
||||||
|
if (sessions == null)
|
||||||
|
throw new ArgumentNullException(nameof(sessions));
|
||||||
|
if (eventArgs == null)
|
||||||
|
throw new ArgumentNullException(nameof(eventArgs));
|
||||||
|
|
||||||
|
EnsureNetworkEventType(typeof(TEvent));
|
||||||
|
EnsurePublishableEventType(typeof(TEvent));
|
||||||
|
|
||||||
|
var groupedSessions = sessions
|
||||||
|
.Where(session => session != null)
|
||||||
|
.GroupBy(session => session.Service);
|
||||||
|
|
||||||
|
foreach (var serviceGroup in groupedSessions)
|
||||||
|
{
|
||||||
|
var sessionList = serviceGroup.ToList();
|
||||||
|
if (sessionList.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var payload = serviceGroup.Key.Serializer.Serialize(eventArgs);
|
||||||
|
|
||||||
|
foreach (var session in sessionList)
|
||||||
|
await serviceGroup.Key.SendSerializedAsync(session, typeof(TEvent), payload, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async UniTask<ShrinkNetworkEventResultResponse> RequestResultAsync<TEvent>(ShrinkNetworkSession session,
|
||||||
|
TEvent eventArgs, ShrinkRpcCallOptions? options = null)
|
||||||
|
where TEvent : IShrinkResultEvent<EventResult>, IShrinkNetworkRequest
|
||||||
|
{
|
||||||
|
if (session == null)
|
||||||
|
throw new ArgumentNullException(nameof(session));
|
||||||
|
if (eventArgs == null)
|
||||||
|
throw new ArgumentNullException(nameof(eventArgs));
|
||||||
|
|
||||||
|
EnsureResultRequestEventType(typeof(TEvent));
|
||||||
|
|
||||||
|
options ??= new ShrinkRpcCallOptions();
|
||||||
|
if (string.IsNullOrWhiteSpace(options.DebugLabel))
|
||||||
|
options.DebugLabel = typeof(TEvent).Name;
|
||||||
|
|
||||||
|
var response = await session.RpcAsync<TEvent, ShrinkNetworkEventResultResponse>(eventArgs,
|
||||||
|
options);
|
||||||
|
|
||||||
|
if (response.IsSuccess)
|
||||||
|
{
|
||||||
|
eventArgs.SetResult(response.Result);
|
||||||
|
if (eventArgs is IShrinkCancelableEvent cancelable)
|
||||||
|
cancelable.SetCanceled(response.IsCanceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleLocalEventPosted(IShrinkEvent eventArgs, Type eventType, ShrinkBusKey busKey)
|
||||||
|
{
|
||||||
|
if (eventArgs == null || eventType == null)
|
||||||
|
return;
|
||||||
|
if (SuppressForwardDepth.Value > 0)
|
||||||
|
return;
|
||||||
|
if (!TryGetNetworkEventMeta(eventType, out _))
|
||||||
|
return;
|
||||||
|
if (eventArgs is IShrinkNetworkRequest)
|
||||||
|
return;
|
||||||
|
if (eventArgs is not IShrinkNetworkMessage networkMessage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ForwardEventAsync(eventArgs, eventType, networkMessage).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async UniTaskVoid ForwardEventAsync(IShrinkEvent eventArgs, Type eventType,
|
||||||
|
IShrinkNetworkMessage networkMessage)
|
||||||
|
{
|
||||||
|
List<ServiceRegistration> registrations;
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
registrations = RegisteredServices.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var batches = new List<(ServiceRegistration Registration, List<ShrinkNetworkSession> Sessions, byte[] Payload)>();
|
||||||
|
foreach (var registration in registrations)
|
||||||
|
{
|
||||||
|
var sessions = registration.Service.Sessions.Values
|
||||||
|
.Where(session => registration.SessionFilter == null || registration.SessionFilter(session, eventArgs))
|
||||||
|
.ToList();
|
||||||
|
if (sessions.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] payload;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Event payload is immutable after serialization and can be
|
||||||
|
// reused for every session owned by this service.
|
||||||
|
payload = registration.Service.Serializer.Serialize(networkMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogException(ex);
|
||||||
|
Debug.LogError(
|
||||||
|
$"[ShrinkNetwork.Integration] 序列化网络事件失败: {eventArgs.GetType().FullName}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete every serializer pass before the first await so mutable
|
||||||
|
// event payloads cannot change between target sessions.
|
||||||
|
batches.Add((registration, sessions, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var batch in batches)
|
||||||
|
{
|
||||||
|
foreach (var session in batch.Sessions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await batch.Registration.Service.SendSerializedAsync(session, eventType, batch.Payload);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogException(ex);
|
||||||
|
Debug.LogError(
|
||||||
|
$"[ShrinkNetwork.Integration] 转发网络事件失败: {eventArgs.GetType().FullName}, Session={session.SessionId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterInboundBridgeHandlers(ShrinkNetworkService service)
|
||||||
|
{
|
||||||
|
foreach (var pair in _networkEventTypes)
|
||||||
|
{
|
||||||
|
if (!service.MessageRegistry.TryGetMeta(pair.Key, out _))
|
||||||
|
{
|
||||||
|
service.RegisterMessage(pair.Key, pair.Value.Opcode, pair.Value.Route);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!service.MessageRegistry.TryGetMeta(typeof(ShrinkNetworkEventResultResponse), out _))
|
||||||
|
{
|
||||||
|
service.RegisterMessage(typeof(ShrinkNetworkEventResultResponse), -300001,
|
||||||
|
"__integration/event_result_response");
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRequestEvent = typeof(IShrinkNetworkRequest).IsAssignableFrom(pair.Key);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isRequestEvent)
|
||||||
|
{
|
||||||
|
service.RegisterRequestHandler(pair.Key, typeof(ShrinkNetworkEventResultResponse),
|
||||||
|
DispatchIncomingRequestAsync);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
service.RegisterHandler(pair.Key, DispatchIncomingAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// 同一个 service 上该事件类型只能绑定一个处理器。已存在时认为调用方自行接管。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UniTask DispatchIncomingAsync(ShrinkNetworkContext context, object message)
|
||||||
|
{
|
||||||
|
if (message is not IShrinkEvent eventArgs)
|
||||||
|
return UniTask.CompletedTask;
|
||||||
|
|
||||||
|
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 IShrinkEvent eventArgs)
|
||||||
|
{
|
||||||
|
return UniTask.FromResult<object?>(new ShrinkNetworkEventResultResponse
|
||||||
|
{
|
||||||
|
ErrorCode = ShrinkRpcErrorCode.InvalidResponse,
|
||||||
|
ErrorMessage = "Incoming network event request does not implement IShrinkEvent."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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}."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async UniTask DispatchGeneratedAsync<TEvent>(TEvent eventArgs)
|
||||||
|
where TEvent : IShrinkEvent
|
||||||
|
{
|
||||||
|
SuppressForwardDepth.Value++;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TryGetDeltaEvent(eventArgs, typeof(TEvent), out var deltaEvent) &&
|
||||||
|
!TryMarkIncomingDelta(typeof(TEvent), deltaEvent, null))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await EventBus.PostAsync(eventArgs);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SuppressForwardDepth.Value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async UniTask<object?> DispatchGeneratedRequestAsync<TEvent>(
|
||||||
|
ShrinkNetworkContext context, TEvent eventArgs)
|
||||||
|
where TEvent : IShrinkEvent
|
||||||
|
{
|
||||||
|
SuppressForwardDepth.Value++;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TryGetDeltaEvent(eventArgs, typeof(TEvent), out var deltaEvent) &&
|
||||||
|
!TryMarkIncomingDelta(typeof(TEvent), deltaEvent, context))
|
||||||
|
{
|
||||||
|
return BuildResultResponse(eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
await EventBus.PostAsync(eventArgs);
|
||||||
|
return BuildResultResponse(eventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogException(ex);
|
||||||
|
return new ShrinkNetworkEventResultResponse
|
||||||
|
{
|
||||||
|
ErrorCode = ShrinkRpcErrorCode.HandlerException,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
Result = eventArgs is IShrinkResultEvent<EventResult> resultEvent
|
||||||
|
? resultEvent.Result
|
||||||
|
: EventResult.DEFAULT,
|
||||||
|
IsCanceled = eventArgs is IShrinkCancelableEvent cancelable && cancelable.IsCanceled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SuppressForwardDepth.Value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshNetworkEventTypes()
|
||||||
|
{
|
||||||
|
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 ShrinkNetworkEventRegistration registration)
|
||||||
|
{
|
||||||
|
if (eventType == null)
|
||||||
|
{
|
||||||
|
registration = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 IShrinkEvent + IShrinkNetworkMessage and declare [ShrinkNetworkEvent] + [ShrinkNetworkMessage].");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsurePublishableEventType(Type eventType)
|
||||||
|
{
|
||||||
|
if (typeof(IShrinkNetworkRequest).IsAssignableFrom(eventType))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Event type {eventType.FullName} is a request event. Use RequestResultAsync instead of PublishAsync.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureResultRequestEventType(Type eventType)
|
||||||
|
{
|
||||||
|
EnsureNetworkEventType(eventType);
|
||||||
|
|
||||||
|
if (!typeof(IShrinkNetworkRequest).IsAssignableFrom(eventType))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Event type {eventType.FullName} must implement IShrinkNetworkRequest to use RequestResultAsync.");
|
||||||
|
if (!typeof(IShrinkResultEvent<EventResult>).IsAssignableFrom(eventType))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Event type {eventType.FullName} must implement IShrinkResultEvent<EventResult> to use RequestResultAsync.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShrinkNetworkEventResultResponse BuildResultResponse(IShrinkEvent eventArgs)
|
||||||
|
{
|
||||||
|
return new ShrinkNetworkEventResultResponse
|
||||||
|
{
|
||||||
|
Result = eventArgs is IShrinkResultEvent<EventResult> resultEvent
|
||||||
|
? resultEvent.Result
|
||||||
|
: EventResult.DEFAULT,
|
||||||
|
IsCanceled = eventArgs is IShrinkCancelableEvent cancelable && cancelable.IsCanceled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetDeltaEvent(IShrinkEvent eventArgs, Type eventType, out IShrinkNetworkDeltaEvent deltaEvent)
|
||||||
|
{
|
||||||
|
if (eventArgs is IShrinkNetworkDeltaEvent eventDelta)
|
||||||
|
{
|
||||||
|
deltaEvent = eventDelta;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaEvent = null!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryMarkIncomingDelta(Type eventType, IShrinkNetworkDeltaEvent deltaEvent,
|
||||||
|
ShrinkNetworkContext? context)
|
||||||
|
{
|
||||||
|
if (deltaEvent == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var scopeKey = BuildDeltaScopeKey(eventType, deltaEvent, context);
|
||||||
|
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (context != null && RegisteredServices.TryGetValue(context.Service, out var serviceRegistration))
|
||||||
|
{
|
||||||
|
return TryMarkIncomingDeltaCore(serviceRegistration.AppliedDeltaVersions, scopeKey,
|
||||||
|
deltaEvent.DeltaVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryMarkIncomingDeltaCore(FallbackDeltaVersions, scopeKey, deltaEvent.DeltaVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryMarkIncomingDeltaCore(Dictionary<string, long> versionMap, string scopeKey, long deltaVersion)
|
||||||
|
{
|
||||||
|
if (versionMap.TryGetValue(scopeKey, out var existingVersion) && deltaVersion <= existingVersion)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
versionMap[scopeKey] = deltaVersion;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildDeltaScopeKey(Type eventType, IShrinkNetworkDeltaEvent deltaEvent,
|
||||||
|
ShrinkNetworkContext? context)
|
||||||
|
{
|
||||||
|
var sessionId = context?.Session?.SessionId ?? 0;
|
||||||
|
var key = string.IsNullOrWhiteSpace(deltaEvent.DeltaKey) ? "__default" : deltaEvent.DeltaKey.Trim();
|
||||||
|
return $"{sessionId}:{eventType.FullName}:{key}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, long> FallbackDeltaVersions = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Switches network callbacks to Unity's main thread before entering EventBus
|
||||||
|
/// handlers. Use ShrinkNetworkDispatchQueue when a hard per-frame budget is
|
||||||
|
/// required instead of an unbounded PlayerLoop backlog.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ShrinkNetworkUnityMainThreadDispatchScheduler : IShrinkNetworkDispatchScheduler
|
||||||
|
{
|
||||||
|
public async UniTask<bool> ScheduleAsync(Func<UniTask> callback)
|
||||||
|
{
|
||||||
|
if (callback == null)
|
||||||
|
throw new ArgumentNullException(nameof(callback));
|
||||||
|
|
||||||
|
await UniTask.SwitchToMainThread();
|
||||||
|
await callback();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aaece45108fa6d64db2ef4a496c4a373
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using ShrinkEventBus;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
public static class ShrinkNetworkEventBusBridgeExtensions
|
||||||
|
{
|
||||||
|
public static ShrinkNetworkService UseEventBusBridge(this ShrinkNetworkService service,
|
||||||
|
ShrinkNetworkEventBusBridgeOptions? options = null)
|
||||||
|
{
|
||||||
|
if (service == null)
|
||||||
|
throw new ArgumentNullException(nameof(service));
|
||||||
|
|
||||||
|
options ??= new ShrinkNetworkEventBusBridgeOptions();
|
||||||
|
if (options.AutoRegisterAttributedMessages)
|
||||||
|
service.AutoRegisterAttributedMessages();
|
||||||
|
if (options.AutoRegisterStaticHandlers)
|
||||||
|
service.AutoRegisterStaticHandlers();
|
||||||
|
if (options.DispatchScheduler != null)
|
||||||
|
service.DispatchScheduler = options.DispatchScheduler;
|
||||||
|
|
||||||
|
ShrinkNetworkEventBusBridge.RegisterService(service, options.SessionFilter);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UniTask PublishEventAsync<TEvent>(this ShrinkNetworkSession session, TEvent eventArgs, string? route = null)
|
||||||
|
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 : IShrinkEvent, IShrinkNetworkMessage
|
||||||
|
{
|
||||||
|
if (service == null)
|
||||||
|
throw new ArgumentNullException(nameof(service));
|
||||||
|
|
||||||
|
IEnumerable<ShrinkNetworkSession> sessions = service.Sessions.Values;
|
||||||
|
if (sessionFilter != null)
|
||||||
|
{
|
||||||
|
var filtered = new List<ShrinkNetworkSession>();
|
||||||
|
foreach (var session in service.Sessions.Values)
|
||||||
|
{
|
||||||
|
if (sessionFilter(session))
|
||||||
|
filtered.Add(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShrinkNetworkEventBusBridge.PublishAsync(sessions, eventArgs, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async UniTask<ShrinkNetworkEventRequestOutcome> RequestEventAsync<TEvent>(
|
||||||
|
this ShrinkNetworkSession session,
|
||||||
|
TEvent eventArgs,
|
||||||
|
ShrinkRpcCallOptions? options = null)
|
||||||
|
where TEvent : IShrinkResultEvent<EventResult>, IShrinkNetworkRequest
|
||||||
|
{
|
||||||
|
var response = await ShrinkNetworkEventBusBridge.RequestResultAsync(session, eventArgs, options);
|
||||||
|
return ShrinkNetworkEventRequestOutcome.FromResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 942526ca2f2df5441b6c742f2476651a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using ShrinkEventBus;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
public sealed class ShrinkNetworkEventBusBridgeOptions
|
||||||
|
{
|
||||||
|
public bool AutoRegisterAttributedMessages { get; set; } = true;
|
||||||
|
public bool AutoRegisterStaticHandlers { get; set; }
|
||||||
|
public Func<ShrinkNetworkSession, IShrinkEvent, bool>? SessionFilter { get; set; }
|
||||||
|
public IShrinkNetworkDispatchScheduler? DispatchScheduler { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b24445c5c1c712a40bc2958fbf7bf313
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using ShrinkContext;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
/// <summary>通过网络服务键管理 Network ↔ EventBus 桥的可逆生命周期。</summary>
|
||||||
|
public sealed class ShrinkNetworkEventBusComponent : IShrinkComponent
|
||||||
|
{
|
||||||
|
public const string NetworkServiceKey = "shrink.service.network";
|
||||||
|
public const string ProvideKey = "shrink.integration.network-eventbus";
|
||||||
|
|
||||||
|
private static readonly string[] InjectKeys = { NetworkServiceKey };
|
||||||
|
private static readonly string[] ProvideKeys = { ProvideKey };
|
||||||
|
|
||||||
|
public string Name => "shrink.integration.network-eventbus";
|
||||||
|
public IReadOnlyList<string> Inject => InjectKeys;
|
||||||
|
public IReadOnlyList<string> Provide => ProvideKeys;
|
||||||
|
|
||||||
|
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
|
||||||
|
{
|
||||||
|
var service = ctx.Get<ShrinkNetworkService>(NetworkServiceKey);
|
||||||
|
service.UseEventBusBridge(config as ShrinkNetworkEventBusBridgeOptions);
|
||||||
|
ctx.EffectInverse(() =>
|
||||||
|
{
|
||||||
|
ShrinkNetworkEventBusBridge.UnregisterService(service);
|
||||||
|
return UniTask.CompletedTask;
|
||||||
|
});
|
||||||
|
ctx.Set(ProvideKey, Name);
|
||||||
|
return UniTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: baeaef51e2232ff449b18fb337209ab1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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,31 @@
|
|||||||
|
#nullable enable
|
||||||
|
using ShrinkEventBus;
|
||||||
|
|
||||||
|
namespace ShrinkNetwork.Integration
|
||||||
|
{
|
||||||
|
public readonly struct ShrinkNetworkEventRequestOutcome
|
||||||
|
{
|
||||||
|
public ShrinkNetworkEventRequestOutcome(int errorCode, string errorMessage, EventResult result, bool isCanceled)
|
||||||
|
{
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
ErrorMessage = errorMessage ?? string.Empty;
|
||||||
|
Result = result;
|
||||||
|
IsCanceled = isCanceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ErrorCode { get; }
|
||||||
|
public string ErrorMessage { get; }
|
||||||
|
public EventResult Result { get; }
|
||||||
|
public bool IsCanceled { get; }
|
||||||
|
public bool IsSuccess => ErrorCode == 0;
|
||||||
|
|
||||||
|
internal static ShrinkNetworkEventRequestOutcome FromResponse(ShrinkNetworkEventResultResponse response)
|
||||||
|
{
|
||||||
|
return new ShrinkNetworkEventRequestOutcome(
|
||||||
|
response?.ErrorCode ?? ShrinkRpcErrorCode.InvalidResponse,
|
||||||
|
response?.ErrorMessage ?? "Network event request returned an invalid response.",
|
||||||
|
response?.Result ?? EventResult.DEFAULT,
|
||||||
|
response?.IsCanceled ?? false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea5612b9af38aa049b71da7196dda594
|
||||||
|
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:
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 132ab80308b64e7abf3a68bb161b7d2f
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7d790812eb9845b0820da061de4d7fe5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "com.cneicy.shrink-network-integration-eventbus",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"displayName": "ShrinkNetwork - EventBus Integration",
|
||||||
|
"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": "2.0.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"network",
|
||||||
|
"eventbus",
|
||||||
|
"integration",
|
||||||
|
"bridge",
|
||||||
|
"sync"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "cneicy",
|
||||||
|
"url": "https://git.crash.work/ShrinkSDK"
|
||||||
|
},
|
||||||
|
"documentationUrl": "https://git.crash.work/ShrinkSDK/ShrinkNetwork.Integration.EventBus",
|
||||||
|
"changelogUrl": "https://git.crash.work/ShrinkSDK/ShrinkNetwork.Integration.EventBus/src/branch/main/CHANGELOG.md"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 157418d926bfc4d44946fc8f26531f74
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user