feat(cordis): 接入上下文组合与模组事务热替换

This commit is contained in:
2026-08-16 23:20:40 +08:00
commit ad256f109b
676 changed files with 52168 additions and 0 deletions
@@ -0,0 +1,81 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ShrinkNetwork
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ShrinkNetworkMessageRegistryAttribute : Attribute
{
public ShrinkNetworkMessageRegistryAttribute(params Type[] messageTypes)
{
MessageTypes = messageTypes ?? Array.Empty<Type>();
}
public Type[] MessageTypes { get; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ShrinkNetworkStaticSubscriberRegistryAttribute : Attribute
{
public ShrinkNetworkStaticSubscriberRegistryAttribute(params Type[] subscriberTypes)
{
SubscriberTypes = subscriberTypes ?? Array.Empty<Type>();
}
public Type[] SubscriberTypes { get; }
}
internal static class ShrinkNetworkGeneratedRegistry
{
public static IReadOnlyList<Type> GetAttributedMessageTypes()
=> GetAssemblyRegisteredTypes<ShrinkNetworkMessageRegistryAttribute>(attribute => attribute.MessageTypes);
public static IReadOnlyList<Type> GetStaticSubscriberTypes()
=> GetAssemblyRegisteredTypes<ShrinkNetworkStaticSubscriberRegistryAttribute>(attribute => attribute.SubscriberTypes);
public static void RegisterAll(ShrinkNetworkService service)
{
if (service == null)
throw new ArgumentNullException(nameof(service));
ShrinkNetworkRegHelper.RegisterAttributedMessages(service, GetAttributedMessageTypes());
ShrinkNetworkRegHelper.RegisterStaticHandlers(service, GetStaticSubscriberTypes());
}
private static IReadOnlyList<Type> GetAssemblyRegisteredTypes<TAttribute>(Func<TAttribute, Type[]> selector)
where TAttribute : Attribute
{
var types = new List<Type>();
var seen = new HashSet<Type>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
object[] attributes;
try
{
attributes = assembly.GetCustomAttributes(typeof(TAttribute), false);
}
catch
{
continue;
}
foreach (var attribute in attributes.OfType<TAttribute>())
{
foreach (var registeredType in selector(attribute) ?? Array.Empty<Type>())
{
if (registeredType == null || !seen.Add(registeredType))
continue;
types.Add(registeredType);
}
}
}
return types;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8557e972f759fd4cac12069d5ac43e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,60 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace ShrinkNetwork
{
public sealed class ShrinkNetworkMessageRegistry
{
private readonly Dictionary<int, ShrinkNetworkMessageMeta> _opcodeToMeta = new();
private readonly Dictionary<Type, ShrinkNetworkMessageMeta> _typeToMeta = new();
public void Register<TMessage>(int opcode, string? route = null) where TMessage : IShrinkNetworkMessage
=> Register(typeof(TMessage), opcode, route);
public void Register(Type messageType, int opcode, string? route = null)
{
if (messageType == null)
throw new ArgumentNullException(nameof(messageType));
if (!typeof(IShrinkNetworkMessage).IsAssignableFrom(messageType))
throw new ArgumentException($"Type {messageType.FullName} is not a network message.", nameof(messageType));
if (_opcodeToMeta.ContainsKey(opcode))
throw new InvalidOperationException($"Opcode {opcode} is already registered.");
if (_typeToMeta.ContainsKey(messageType))
throw new InvalidOperationException($"Message type {messageType.FullName} is already registered.");
var meta = new ShrinkNetworkMessageMeta(opcode, messageType, route);
_opcodeToMeta.Add(opcode, meta);
_typeToMeta.Add(messageType, meta);
}
public bool TryGetMeta(int opcode, out ShrinkNetworkMessageMeta? meta) => _opcodeToMeta.TryGetValue(opcode, out meta);
public bool TryGetMeta(Type type, out ShrinkNetworkMessageMeta? meta) => _typeToMeta.TryGetValue(type, out meta);
public ShrinkNetworkMessageMeta GetMeta<TMessage>() where TMessage : IShrinkNetworkMessage => GetMeta(typeof(TMessage));
public ShrinkNetworkMessageMeta GetMeta(Type type)
{
if (_typeToMeta.TryGetValue(type, out var meta))
return meta;
throw new KeyNotFoundException($"Message type {type.FullName} is not registered.");
}
}
public sealed class ShrinkNetworkMessageMeta
{
public int Opcode { get; }
public Type MessageType { get; }
public string? Route { get; }
public ShrinkNetworkMessageMeta(int opcode, Type messageType, string? route)
{
Opcode = opcode;
MessageType = messageType;
Route = route;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ba2cec4b827a0a4b86f42a16fd10393
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,210 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using Cysharp.Threading.Tasks;
namespace ShrinkNetwork
{
public static class ShrinkNetworkRegHelper
{
private static readonly MethodInfo AwaitUniTaskResponseMethod =
typeof(ShrinkNetworkRegHelper).GetMethod(nameof(AwaitUniTaskResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
public static void RegisterAttributedMessages(ShrinkNetworkService service)
{
RegisterAttributedMessages(service, ShrinkNetworkGeneratedRegistry.GetAttributedMessageTypes());
}
public static void RegisterAttributedMessages(ShrinkNetworkService service, IEnumerable<Type> messageTypes)
{
if (service == null)
throw new ArgumentNullException(nameof(service));
if (messageTypes == null)
throw new ArgumentNullException(nameof(messageTypes));
foreach (var type in messageTypes)
{
if (type == null || !typeof(IShrinkNetworkMessage).IsAssignableFrom(type))
continue;
var attr = type.GetCustomAttribute<ShrinkNetworkMessageAttribute>(false);
if (attr == null || service.MessageRegistry.TryGetMeta(type, out _))
continue;
service.RegisterMessage(type, attr.Opcode, attr.Route);
}
}
public static void RegisterStaticHandlers(ShrinkNetworkService service)
=> RegisterStaticHandlers(service, ShrinkNetworkGeneratedRegistry.GetStaticSubscriberTypes());
public static void RegisterStaticHandlers(ShrinkNetworkService service, IEnumerable<Type> subscriberTypes)
{
if (service == null)
throw new ArgumentNullException(nameof(service));
if (subscriberTypes == null)
throw new ArgumentNullException(nameof(subscriberTypes));
foreach (var type in subscriberTypes)
{
if (type == null || type.GetCustomAttribute<ShrinkNetworkSubscriberAttribute>(false) == null)
continue;
ScanMethodsAndRegister(service, null, type,
type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
}
}
public static void RegisterHandlers(ShrinkNetworkService service, object target)
=> RegisterHandlersInternal(service, target);
private static void RegisterHandlersInternal(ShrinkNetworkService service, object? target)
{
if (target == null)
return;
var ownerType = target.GetType();
if (ownerType.GetCustomAttribute<ShrinkNetworkSubscriberAttribute>(false) == null)
return;
ScanMethodsAndRegister(service, target, ownerType,
ownerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
}
private static void ScanMethodsAndRegister(ShrinkNetworkService service, object? target, Type ownerType, MethodInfo[] methods)
{
foreach (var method in methods)
{
if (target != null && method.IsStatic)
continue;
var attributes = method.GetCustomAttributes(typeof(ShrinkNetworkSubscribeAttribute), false);
if (attributes.Length == 0)
continue;
var subscribeAttr = (ShrinkNetworkSubscribeAttribute)attributes[0];
var requirement = new ShrinkNetworkPermissionRequirement(subscribeAttr.Authority, subscribeAttr.Permission);
if (!TryParseHandlerSignature(method, out var hasContext, out var messageType, out var responseType))
{
ShrinkNetworkLogger.Warn($"[ShrinkNetwork] Invalid handler signature: {ownerType.FullName}.{method.Name}");
continue;
}
var resolvedMessageType = messageType!;
EnsureMessageRegistered(service, resolvedMessageType);
if (responseType != null)
EnsureMessageRegistered(service, responseType);
if (responseType == null)
{
service.RegisterHandler(resolvedMessageType, (context, message) =>
InvokeMessageHandler(target, method, hasContext, context, message), requirement);
continue;
}
var resolvedResponseType = responseType!;
service.RegisterRequestHandler(resolvedMessageType, resolvedResponseType, (context, request) =>
InvokeRequestHandler(target, method, hasContext, resolvedResponseType, context, request), requirement);
}
}
private static bool TryParseHandlerSignature(MethodInfo method, out bool hasContext, out Type? messageType, out Type? responseType)
{
hasContext = false;
messageType = null;
responseType = null;
var parameters = method.GetParameters();
if (parameters.Length == 1)
{
messageType = parameters[0].ParameterType;
}
else if (parameters.Length == 2 && parameters[0].ParameterType == typeof(ShrinkNetworkContext))
{
hasContext = true;
messageType = parameters[1].ParameterType;
}
else
{
return false;
}
if (!typeof(IShrinkNetworkMessage).IsAssignableFrom(messageType))
return false;
var returnType = method.ReturnType;
if (returnType == typeof(void) || returnType == typeof(UniTask))
return true;
if (typeof(IShrinkNetworkResponse).IsAssignableFrom(returnType))
{
responseType = returnType;
return true;
}
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(UniTask<>))
{
var resultType = returnType.GetGenericArguments()[0];
if (typeof(IShrinkNetworkResponse).IsAssignableFrom(resultType))
{
responseType = resultType;
return true;
}
}
return false;
}
private static void EnsureMessageRegistered(ShrinkNetworkService service, Type messageType)
{
if (service.MessageRegistry.TryGetMeta(messageType, out _))
return;
var attr = messageType.GetCustomAttribute<ShrinkNetworkMessageAttribute>(false);
if (attr == null)
throw new InvalidOperationException($"Message type {messageType.FullName} must declare [ShrinkNetworkMessage].");
service.RegisterMessage(messageType, attr.Opcode, attr.Route);
}
private static async UniTask InvokeMessageHandler(object? target, MethodInfo method, bool hasContext,
ShrinkNetworkContext context, object message)
{
var args = hasContext ? new object?[] { context, message } : new object?[] { message };
var result = method.Invoke(target, args);
if (method.ReturnType == typeof(UniTask))
await (UniTask)(result ?? throw new InvalidOperationException($"Handler returned null UniTask: {method.DeclaringType?.FullName}.{method.Name}"));
}
private static async UniTask<object?> InvokeRequestHandler(object? target, MethodInfo method, bool hasContext,
Type responseType, ShrinkNetworkContext context, object request)
{
var args = hasContext ? new object?[] { context, request } : new object?[] { request };
var result = method.Invoke(target, args);
if (result == null)
return null;
if (responseType.IsInstanceOfType(result))
return result;
if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(UniTask<>))
return await AwaitUniTaskResponseObject(result, responseType);
return result;
}
private static UniTask<object?> AwaitUniTaskResponseObject(object taskObject, Type responseType)
{
return (UniTask<object?>)AwaitUniTaskResponseMethod
.MakeGenericMethod(responseType)
.Invoke(null, new[] { taskObject })!;
}
private static async UniTask<object?> AwaitUniTaskResponse<TResponse>(UniTask<TResponse> task)
where TResponse : class, IShrinkNetworkResponse
{
return await task;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fc0b4f8cf9205df4caedc2cb1dc8b6bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,152 @@
#nullable enable
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace ShrinkNetwork
{
public sealed class ShrinkNetworkRouter
{
private sealed class MessageHandlerRegistration
{
public ShrinkNetworkPermissionRequirement Requirement;
public Func<ShrinkNetworkContext, object, UniTask> Handler = null!;
}
private sealed class RequestHandlerRegistration
{
public Type ResponseType = null!;
public ShrinkNetworkPermissionRequirement Requirement;
public Func<ShrinkNetworkContext, object, UniTask<object?>> Handler = null!;
}
private readonly Dictionary<Type, MessageHandlerRegistration> _messageHandlers = new();
private readonly Dictionary<Type, RequestHandlerRegistration> _requestHandlers = new();
public void RegisterHandler<TMessage>(Func<ShrinkNetworkContext, TMessage, UniTask> handler,
ShrinkNetworkPermissionRequirement requirement = default)
where TMessage : IShrinkNetworkMessage
{
RegisterHandler(typeof(TMessage), (context, message) => handler(context, (TMessage)message), requirement);
}
public void RegisterHandler(Type messageType, Func<ShrinkNetworkContext, object, UniTask> handler,
ShrinkNetworkPermissionRequirement requirement = default)
{
if (messageType == null)
throw new ArgumentNullException(nameof(messageType));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
if (_messageHandlers.ContainsKey(messageType) || _requestHandlers.ContainsKey(messageType))
throw new InvalidOperationException($"Handler already exists for {messageType.FullName}.");
_messageHandlers.Add(messageType, new MessageHandlerRegistration
{
Requirement = requirement,
Handler = handler
});
}
public void RegisterRequestHandler<TRequest, TResponse>(Func<ShrinkNetworkContext, TRequest, UniTask<TResponse>> handler,
ShrinkNetworkPermissionRequirement requirement = default)
where TRequest : IShrinkNetworkRequest
where TResponse : class, IShrinkNetworkResponse
{
RegisterRequestHandler(typeof(TRequest), typeof(TResponse),
async (context, message) => await handler(context, (TRequest)message), requirement);
}
public void RegisterRequestHandler(Type requestType, Type responseType,
Func<ShrinkNetworkContext, object, UniTask<object?>> handler,
ShrinkNetworkPermissionRequirement requirement = default)
{
if (requestType == null)
throw new ArgumentNullException(nameof(requestType));
if (responseType == null)
throw new ArgumentNullException(nameof(responseType));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
if (_messageHandlers.ContainsKey(requestType) || _requestHandlers.ContainsKey(requestType))
throw new InvalidOperationException($"Handler already exists for {requestType.FullName}.");
_requestHandlers.Add(requestType, new RequestHandlerRegistration
{
ResponseType = responseType,
Requirement = requirement,
Handler = handler
});
}
public async UniTask<bool> DispatchAsync(ShrinkNetworkContext context, object message, Type messageType)
{
if (context.Packet.Kind == ShrinkNetworkPacketKind.Request &&
_requestHandlers.TryGetValue(messageType, out var requestHandler))
{
if (!ShrinkNetworkPermissionValidator.IsAllowed(context.Session, requestHandler.Requirement))
{
context.Service.ReportPermissionDenied(messageType);
var denied = CreatePermissionDeniedResponse(requestHandler.ResponseType, requestHandler.Requirement);
await context.Service.SendResponseAsync(context.Session, denied, requestHandler.ResponseType,
context.Packet.RequestToken, context.Packet.Route);
return true;
}
try
{
var response = await requestHandler.Handler(context, message);
if (response is IShrinkNetworkResponse networkResponse)
await context.Service.SendResponseAsync(context.Session, networkResponse, requestHandler.ResponseType,
context.Packet.RequestToken, context.Packet.Route);
}
catch (Exception ex)
{
context.Service.ReportHandlerException(messageType, ex);
var errorResponse = CreateErrorResponse(requestHandler.ResponseType, ex);
await context.Service.SendResponseAsync(context.Session, errorResponse, requestHandler.ResponseType,
context.Packet.RequestToken, context.Packet.Route);
}
return true;
}
if (_messageHandlers.TryGetValue(messageType, out var messageHandler))
{
if (!ShrinkNetworkPermissionValidator.IsAllowed(context.Session, messageHandler.Requirement))
{
context.Service.ReportPermissionDenied(messageType);
ShrinkNetworkLogger.Warn($"[ShrinkNetwork] Permission denied for message {messageType.FullName} on session {context.Session.SessionId}.");
return true;
}
await messageHandler.Handler(context, message);
return true;
}
return false;
}
private static IShrinkNetworkResponse CreateErrorResponse(Type responseType, Exception ex)
{
if (Activator.CreateInstance(responseType) is not IShrinkNetworkResponse response)
throw new InvalidOperationException($"Response type {responseType.FullName} cannot be instantiated.", ex);
response.ErrorCode = ShrinkRpcErrorCode.HandlerException;
response.ErrorMessage = ex.Message;
return response;
}
private static IShrinkNetworkResponse CreatePermissionDeniedResponse(Type responseType,
ShrinkNetworkPermissionRequirement requirement)
{
if (Activator.CreateInstance(responseType) is not IShrinkNetworkResponse response)
throw new InvalidOperationException($"Response type {responseType.FullName} cannot be instantiated.");
response.ErrorCode = ShrinkRpcErrorCode.PermissionDenied;
response.ErrorMessage = string.IsNullOrEmpty(requirement.Permission)
? $"Permission denied. Authority={requirement.Authority}"
: $"Permission denied. Authority={requirement.Authority}, Permission={requirement.Permission}";
return response;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89e99852277437b439ff7891cd6aa8d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: