feat(cordis): 接入上下文组合与模组事务热替换
This commit is contained in:
@@ -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:
|
||||
Reference in New Issue
Block a user