Initial Client/Server Communication
#1 Followed the MagicOnion quickstart guide, borrowing code from the chat sample app to get something working
This commit is contained in:
parent
52b0d4d8ff
commit
a0c8dbee72
493 changed files with 57790 additions and 3 deletions
213
Assets/Scripts/MagicOnion/Client/ClientFilter.cs
Normal file
213
Assets/Scripts/MagicOnion/Client/ClientFilter.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
using Grpc.Core;
|
||||
using MessagePack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
public interface IClientFilter
|
||||
{
|
||||
ValueTask<ResponseContext> SendAsync(RequestContext context, Func<RequestContext, ValueTask<ResponseContext>> next);
|
||||
}
|
||||
|
||||
public abstract class RequestContext
|
||||
{
|
||||
static readonly Func<byte[], byte[]> DefaultMutator = xs => xs;
|
||||
|
||||
public string MethodPath { get; }
|
||||
public CallOptions CallOptions { get; }
|
||||
public Type ResponseType { get; }
|
||||
public abstract Type RequestType { get; }
|
||||
public Func<byte[], byte[]> RequestMutator { get; private set; }
|
||||
public Func<byte[], byte[]> ResponseMutator { get; private set; }
|
||||
|
||||
Dictionary<string, object> items;
|
||||
public IDictionary<string, object> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
items = new Dictionary<string, object>();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
// internal use to avoid lambda capture.
|
||||
internal MagicOnionClientBase Client { get; }
|
||||
internal IClientFilter[] Filters { get; }
|
||||
internal Func<RequestContext, ResponseContext> RequestMethod { get; }
|
||||
|
||||
internal RequestContext(MagicOnionClientBase client, string methodPath, CallOptions callOptions, Type responseType, IClientFilter[] filters, Func<RequestContext, ResponseContext> requestMethod)
|
||||
{
|
||||
this.Client = client;
|
||||
this.MethodPath = methodPath;
|
||||
this.CallOptions = callOptions;
|
||||
this.ResponseType = responseType;
|
||||
this.Filters = filters;
|
||||
this.RequestMethod = requestMethod;
|
||||
this.RequestMutator = DefaultMutator;
|
||||
this.ResponseMutator = DefaultMutator;
|
||||
}
|
||||
|
||||
public void SetRequestMutator(Func<byte[], byte[]> mutator)
|
||||
{
|
||||
this.RequestMutator = mutator;
|
||||
}
|
||||
|
||||
public void SetResponseMutator(Func<byte[], byte[]> mutator)
|
||||
{
|
||||
this.ResponseMutator = mutator;
|
||||
}
|
||||
}
|
||||
|
||||
public class RequestContext<T> : RequestContext
|
||||
{
|
||||
public T Request { get; }
|
||||
public override Type RequestType => typeof(T);
|
||||
|
||||
public RequestContext(T request, MagicOnionClientBase client, string methodPath, CallOptions callOptions, Type responseType, IClientFilter[] filters, Func<RequestContext, ResponseContext> requestMethod)
|
||||
: base(client, methodPath, callOptions, responseType, filters, requestMethod)
|
||||
{
|
||||
this.Request = request;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ResponseContext : IResponseContext
|
||||
{
|
||||
static readonly Func<byte[], byte[]> DefaultMutator = xs => xs;
|
||||
|
||||
public abstract Task<Metadata> ResponseHeadersAsync { get; }
|
||||
public abstract Status GetStatus();
|
||||
public abstract Metadata GetTrailers();
|
||||
public abstract void Dispose();
|
||||
public abstract Type ResponseType { get; }
|
||||
public Func<byte[], byte[]> ResponseMutator { get; private set; }
|
||||
|
||||
public abstract Task<ResponseContext> WaitResponseAsync();
|
||||
|
||||
public ResponseContext<T> As<T>()
|
||||
{
|
||||
return this as ResponseContext<T>;
|
||||
}
|
||||
|
||||
public Task<T> GetResponseAs<T>()
|
||||
{
|
||||
var t = this as ResponseContext<T>;
|
||||
if (t == null) return Task.FromResult(default(T));
|
||||
|
||||
return t.ResponseAsync;
|
||||
}
|
||||
|
||||
public ResponseContext()
|
||||
{
|
||||
this.ResponseMutator = DefaultMutator;
|
||||
}
|
||||
|
||||
public void SetResponseMutator(Func<byte[], byte[]> mutator)
|
||||
{
|
||||
this.ResponseMutator = mutator;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ResponseContext<T> : ResponseContext, IResponseContext<T>
|
||||
{
|
||||
readonly AsyncUnaryCall<byte[]> inner;
|
||||
readonly MessagePackSerializerOptions serializerOptions;
|
||||
readonly bool isMock;
|
||||
bool deserialized;
|
||||
|
||||
T responseObject; // cache value.
|
||||
|
||||
// mock
|
||||
readonly Metadata trailers;
|
||||
readonly Metadata responseHeaders;
|
||||
readonly Status status;
|
||||
|
||||
public ResponseContext(AsyncUnaryCall<byte[]> inner, MessagePackSerializerOptions serializerOptions)
|
||||
: base()
|
||||
{
|
||||
this.isMock = false;
|
||||
this.inner = inner;
|
||||
this.serializerOptions = serializerOptions;
|
||||
}
|
||||
|
||||
public ResponseContext(T responseObject)
|
||||
: this(responseObject, new Metadata(), new Metadata(), Status.DefaultSuccess)
|
||||
{
|
||||
}
|
||||
|
||||
public ResponseContext(T responseObject, Metadata trailers, Metadata responseHeaders, Status status)
|
||||
: base()
|
||||
{
|
||||
this.isMock = true;
|
||||
this.responseObject = responseObject;
|
||||
this.trailers = trailers;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
async Task<T> Deserialize()
|
||||
{
|
||||
if (deserialized)
|
||||
{
|
||||
return responseObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bytes = await inner.ResponseAsync.ConfigureAwait(false);
|
||||
responseObject = MessagePackSerializer.Deserialize<T>(this.ResponseMutator(bytes), serializerOptions);
|
||||
deserialized = true;
|
||||
return responseObject;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<T> ResponseAsync => !isMock ? Deserialize() : Task.FromResult(responseObject);
|
||||
|
||||
public override async Task<ResponseContext> WaitResponseAsync()
|
||||
{
|
||||
await ResponseAsync;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override Type ResponseType => typeof(T);
|
||||
|
||||
public override Task<Metadata> ResponseHeadersAsync => !isMock ? inner.ResponseHeadersAsync : Task.FromResult(responseHeaders);
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!isMock)
|
||||
{
|
||||
inner.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override Status GetStatus()
|
||||
{
|
||||
return !isMock ? inner.GetStatus() : status;
|
||||
}
|
||||
|
||||
public override Metadata GetTrailers()
|
||||
{
|
||||
return !isMock ? inner.GetTrailers() : trailers;
|
||||
}
|
||||
|
||||
public ResponseContext<T> WithNewResult(T result)
|
||||
{
|
||||
if (isMock)
|
||||
{
|
||||
return new ResponseContext<T>(result, trailers, responseHeaders, status);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newContext = new ResponseContext<T>(inner, serializerOptions);
|
||||
newContext.deserialized = true;
|
||||
newContext.responseObject = result;
|
||||
return newContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Client/ClientFilter.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Client/ClientFilter.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0190394f8c415b4b84aa1b95b993369
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
628
Assets/Scripts/MagicOnion/Client/DynamicClientBuilder.cs
Normal file
628
Assets/Scripts/MagicOnion/Client/DynamicClientBuilder.cs
Normal file
|
|
@ -0,0 +1,628 @@
|
|||
#if NON_UNITY || !NET_STANDARD_2_0
|
||||
|
||||
using Grpc.Core;
|
||||
using MagicOnion.Utils;
|
||||
using MessagePack;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class DynamicClientAssemblyHolder
|
||||
{
|
||||
public const string ModuleName = "MagicOnion.Client.DynamicClient";
|
||||
|
||||
readonly static DynamicAssembly assembly;
|
||||
public static DynamicAssembly Assembly { get { return assembly; } }
|
||||
|
||||
static DynamicClientAssemblyHolder()
|
||||
{
|
||||
assembly = new DynamicAssembly(ModuleName);
|
||||
}
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
|
||||
public static AssemblyBuilder Save()
|
||||
{
|
||||
return assembly.Save();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class DynamicClientBuilder<T>
|
||||
where T : IService<T>
|
||||
{
|
||||
public static readonly Type ClientType;
|
||||
static readonly Type bytesMethod = typeof(Method<,>).MakeGenericType(new[] { typeof(byte[]), typeof(byte[]) });
|
||||
static readonly FieldInfo throughMarshaller = typeof(MagicOnionMarshallers).GetField("ThroughMarshaller", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
static readonly FieldInfo nilBytes = typeof(MagicOnionMarshallers).GetField("UnsafeNilBytes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
static readonly MethodInfo callMessagePackSerialize = typeof(MessagePackSerializer).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
|
||||
.First(x => x.Name == "Serialize" && x.GetParameters().Length == 3 && x.ReturnType == typeof(byte[]));
|
||||
static readonly MethodInfo callCancellationTokenNone = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod();
|
||||
|
||||
static DynamicClientBuilder()
|
||||
{
|
||||
var t = typeof(T);
|
||||
var ti = t.GetTypeInfo();
|
||||
if (!ti.IsInterface) throw new Exception("Client Proxy only allows interface. Type:" + ti.Name);
|
||||
|
||||
var asm = DynamicClientAssemblyHolder.Assembly;
|
||||
var methodDefinitions = SearchDefinitions(t);
|
||||
|
||||
var parentType = typeof(MagicOnionClientBase<>).MakeGenericType(t);
|
||||
var typeBuilder = asm.DefineType($"{DynamicClientAssemblyHolder.ModuleName}.{ti.FullName}Client", TypeAttributes.Public, parentType, new Type[] { t });
|
||||
|
||||
// Set `IgnoreAttribute` to the generated client type. Hides generated-types from building MagicOnion service definitions.
|
||||
typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(IgnoreAttribute).GetConstructor(Array.Empty<Type>()), Array.Empty<object>()));
|
||||
|
||||
DefineStaticFields(typeBuilder, methodDefinitions);
|
||||
DefineStaticConstructor(typeBuilder, t, methodDefinitions);
|
||||
var emptyCtor = DefineConstructors(typeBuilder, methodDefinitions);
|
||||
DefineMethods(typeBuilder, t, methodDefinitions, emptyCtor);
|
||||
|
||||
ClientType = typeBuilder.CreateTypeInfo().AsType();
|
||||
}
|
||||
|
||||
static MethodDefinition[] SearchDefinitions(Type interfaceType)
|
||||
{
|
||||
return interfaceType
|
||||
.GetInterfaces()
|
||||
.Concat(new[] { interfaceType })
|
||||
.SelectMany(x => x.GetMethods())
|
||||
.Where(x =>
|
||||
{
|
||||
var methodInfo = x;
|
||||
if (methodInfo.IsSpecialName && (methodInfo.Name.StartsWith("set_") || methodInfo.Name.StartsWith("get_"))) return false;
|
||||
if (methodInfo.GetCustomAttribute<IgnoreAttribute>(false) != null) return false; // ignore
|
||||
|
||||
var methodName = methodInfo.Name;
|
||||
if (methodName == "Equals"
|
||||
|| methodName == "GetHashCode"
|
||||
|| methodName == "GetType"
|
||||
|| methodName == "ToString"
|
||||
|| methodName == "WithOptions"
|
||||
|| methodName == "WithHeaders"
|
||||
|| methodName == "WithDeadline"
|
||||
|| methodName == "WithCancellationToken"
|
||||
|| methodName == "WithHost"
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.Where(x => !x.IsSpecialName)
|
||||
.Select(x => new MethodDefinition
|
||||
{
|
||||
ServiceType = interfaceType,
|
||||
MethodInfo = x,
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
static void DefineStaticFields(TypeBuilder typeBuilder, MethodDefinition[] definitions)
|
||||
{
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
item.FieldMethod = typeBuilder.DefineField(item.MethodInfo.Name + "Method", bytesMethod, FieldAttributes.Private | FieldAttributes.Static);
|
||||
item.ResponseType = UnwrapResponseType(item, out item.MethodType, out item.ResponseIsTask, out item.RequestType);
|
||||
|
||||
if (item.RequestType == null)
|
||||
{
|
||||
item.RequestType = MagicOnionMarshallers.CreateRequestType(item.MethodInfo.GetParameters());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DefineStaticConstructor(TypeBuilder typeBuilder, Type interfaceType, MethodDefinition[] definitions)
|
||||
{
|
||||
var cctor = typeBuilder.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
|
||||
var il = cctor.GetILGenerator();
|
||||
|
||||
for (int i = 0; i < definitions.Length; i++)
|
||||
{
|
||||
var def = definitions[i];
|
||||
|
||||
il.EmitLdc_I4((int)def.MethodType);
|
||||
il.Emit(OpCodes.Ldstr, def.ServiceType.Name);
|
||||
il.Emit(OpCodes.Ldstr, def.MethodInfo.Name);
|
||||
il.Emit(OpCodes.Ldsfld, throughMarshaller);
|
||||
il.Emit(OpCodes.Ldsfld, throughMarshaller);
|
||||
il.Emit(OpCodes.Newobj, bytesMethod.GetConstructors()[0]);
|
||||
il.Emit(OpCodes.Stsfld, def.FieldMethod);
|
||||
|
||||
if (def.MethodType == MethodType.Unary)
|
||||
{
|
||||
DefineUnaryRequestDelegate(il, typeBuilder, interfaceType, def);
|
||||
}
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
static void DefineUnaryRequestDelegate(ILGenerator staticContructorGenerator, TypeBuilder typeBuilder, Type interfaceType, MethodDefinition definition)
|
||||
{
|
||||
// static ResponseContext _Method(RequestContext context);
|
||||
MethodBuilder method;
|
||||
{
|
||||
method = typeBuilder.DefineMethod("_" + definition.MethodInfo.Name, MethodAttributes.Private | MethodAttributes.Static,
|
||||
typeof(ResponseContext),
|
||||
new[] { typeof(RequestContext) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
// CreateResponseContext<TRequest, TResponse>(Context, Method);
|
||||
var createMethod = typeof(MagicOnionClientBase)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
|
||||
.Where(x => x.Name == "CreateResponseContext")
|
||||
.OrderByDescending(x => x.GetGenericArguments().Length)
|
||||
.First()
|
||||
.MakeGenericMethod(definition.RequestType, definition.ResponseType);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // context
|
||||
il.Emit(OpCodes.Ldsfld, definition.FieldMethod); // method
|
||||
il.Emit(OpCodes.Call, createMethod);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
// static readonly Func<RequestContext, ResposneContext> methodDelegate = _Method;
|
||||
{
|
||||
definition.UnaryRequestDelegate = typeBuilder.DefineField(definition.MethodInfo.Name + "Delegate", typeof(Func<RequestContext, ResponseContext>), FieldAttributes.Private | FieldAttributes.Static);
|
||||
|
||||
var il = staticContructorGenerator;
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Ldftn, method);
|
||||
il.Emit(OpCodes.Newobj, typeof(Func<RequestContext, ResponseContext>).GetConstructors()[0]);
|
||||
il.Emit(OpCodes.Stsfld, definition.UnaryRequestDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
static ConstructorInfo DefineConstructors(TypeBuilder typeBuilder, MethodDefinition[] definitions)
|
||||
{
|
||||
ConstructorInfo emptyCtor;
|
||||
// .ctor()
|
||||
{
|
||||
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Private, CallingConventions.Standard, Type.EmptyTypes);
|
||||
var il = ctor.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null));
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
emptyCtor = ctor;
|
||||
}
|
||||
|
||||
// .ctor(CallInvoker, MessagePackSerializerOptions, IClientFilter[]):base(callInvoker, resolver, clientFilters)
|
||||
{
|
||||
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(CallInvoker), typeof(MessagePackSerializerOptions), typeof(IClientFilter[]) });
|
||||
var il = ctor.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldarg_2);
|
||||
il.Emit(OpCodes.Ldarg_3);
|
||||
il.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(CallInvoker), typeof(MessagePackSerializerOptions), typeof(IClientFilter[]) }, null));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
return emptyCtor;
|
||||
}
|
||||
|
||||
static void DefineMethods(TypeBuilder typeBuilder, Type interfaceType, MethodDefinition[] definitions, ConstructorInfo emptyCtor)
|
||||
{
|
||||
var filedHolderType = typeof(MagicOnionClientBase);
|
||||
var baseType = typeof(MagicOnionClientBase<>).MakeGenericType(interfaceType);
|
||||
var hostField = filedHolderType.GetField("host", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var optionField = filedHolderType.GetField("option", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var invokerField = filedHolderType.GetField("callInvoker", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var serializerOptionsField = filedHolderType.GetField("serializerOptions", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var filtersField = filedHolderType.GetField("filters", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// Clone
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("Clone", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(MagicOnionClientBase<>).MakeGenericType(interfaceType),
|
||||
Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Newobj, emptyCtor);
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, hostField);
|
||||
il.Emit(OpCodes.Stfld, hostField);
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, optionField);
|
||||
il.Emit(OpCodes.Stfld, optionField);
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, invokerField);
|
||||
il.Emit(OpCodes.Stfld, invokerField);
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Stfld, serializerOptionsField);
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, filtersField);
|
||||
il.Emit(OpCodes.Stfld, filtersField);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// Overrides
|
||||
{
|
||||
// TSelf WithOption(CallOptions option)
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WithOptions", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType,
|
||||
new[] { typeof(CallOptions) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WithOptions", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// TSelf WithHeaders(Metadata headers);
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WithHeaders", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType,
|
||||
new[] { typeof(Metadata) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WithHeaders", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// TSelf WithDeadline(DateTime deadline);
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WithDeadline", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType,
|
||||
new[] { typeof(DateTime) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WithDeadline", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// TSelf WithCancellationToken(CancellationToken cancellationToken);
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WithCancellationToken", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType,
|
||||
new[] { typeof(CancellationToken) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WithCancellationToken", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// TSelf WithHost(string host);
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WithHost", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType,
|
||||
new[] { typeof(string) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WithHost", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
// Proxy Methods
|
||||
for (int i = 0; i < definitions.Length; i++)
|
||||
{
|
||||
var def = definitions[i];
|
||||
var parameters = def.MethodInfo.GetParameters().Select(x => x.ParameterType).ToArray();
|
||||
|
||||
var method = typeBuilder.DefineMethod(def.MethodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
def.MethodInfo.ReturnType,
|
||||
parameters);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
switch (def.MethodType)
|
||||
{
|
||||
case MethodType.Unary:
|
||||
{
|
||||
// base.InvokeAsync<TRequest, TResponse>/InvokeTaskAsync(string path, TRequest request, Func<RequestContext, ResponseContext> requestMethod)
|
||||
|
||||
// this.
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// path
|
||||
il.Emit(OpCodes.Ldstr, def.Path);
|
||||
|
||||
// create request
|
||||
for (int j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg, j + 1);
|
||||
}
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
// use empty byte[0]
|
||||
il.Emit(OpCodes.Ldsfld, nilBytes);
|
||||
}
|
||||
else if (parameters.Length == 1)
|
||||
{
|
||||
// already loaded parameter.
|
||||
}
|
||||
else
|
||||
{
|
||||
// call new DynamicArgumentTuple<T>
|
||||
il.Emit(OpCodes.Newobj, def.RequestType.GetConstructors()[0]);
|
||||
}
|
||||
|
||||
// requestMethod
|
||||
il.Emit(OpCodes.Ldsfld, def.UnaryRequestDelegate);
|
||||
|
||||
// InvokeAsync/InvokeTaskAsync
|
||||
var invokeMethod = def.ResponseIsTask
|
||||
? baseType.GetMethod("InvokeTaskAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance)
|
||||
: baseType.GetMethod("InvokeAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
|
||||
invokeMethod = invokeMethod.MakeGenericMethod(def.RequestType, def.ResponseType);
|
||||
il.Emit(OpCodes.Callvirt, invokeMethod);
|
||||
}
|
||||
|
||||
break;
|
||||
case MethodType.ServerStreaming:
|
||||
{
|
||||
il.DeclareLocal(typeof(byte[])); // request
|
||||
il.DeclareLocal(typeof(AsyncServerStreamingCall<byte[]>));
|
||||
|
||||
// create request
|
||||
for (int j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg, j + 1);
|
||||
}
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
// use empty byte[0]
|
||||
il.Emit(OpCodes.Ldsfld, nilBytes);
|
||||
}
|
||||
else if (parameters.Length == 1)
|
||||
{
|
||||
// already loaded parameter.
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Call, callCancellationTokenNone);
|
||||
il.Emit(OpCodes.Call, callMessagePackSerialize.MakeGenericMethod(def.RequestType));
|
||||
}
|
||||
else
|
||||
{
|
||||
// call new DynamicArgumentTuple<T>
|
||||
il.Emit(OpCodes.Newobj, def.RequestType.GetConstructors()[0]);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Call, callCancellationTokenNone);
|
||||
il.Emit(OpCodes.Call, callMessagePackSerialize.MakeGenericMethod(def.RequestType));
|
||||
}
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
|
||||
// create ***Result
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, invokerField);
|
||||
il.Emit(OpCodes.Ldsfld, def.FieldMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, hostField);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, optionField);
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Callvirt, typeof(CallInvoker).GetMethod("AsyncServerStreamingCall").MakeGenericMethod(typeof(byte[]), typeof(byte[])));
|
||||
il.Emit(OpCodes.Stloc_1);
|
||||
|
||||
// create return result
|
||||
// new ServerStreamingResult<TResponse>(
|
||||
|
||||
// inner,
|
||||
il.Emit(OpCodes.Ldloc_1);
|
||||
|
||||
// new MarshallingAsyncStreamReader<TResponse>(inner.ResponseStream, this.resolver),
|
||||
il.Emit(OpCodes.Ldloc_1);
|
||||
il.Emit(OpCodes.Call, typeof(AsyncServerStreamingCall<byte[]>).GetProperty("ResponseStream").GetMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Newobj, typeof(MarshallingAsyncStreamReader<>).MakeGenericType(def.ResponseType).GetConstructors().Single());
|
||||
|
||||
// this.resolver
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
|
||||
// );
|
||||
|
||||
var resultType = typeof(ServerStreamingResult<>).MakeGenericType(def.ResponseType);
|
||||
il.Emit(OpCodes.Newobj, resultType.GetConstructors()[0]);
|
||||
if (def.ResponseIsTask)
|
||||
{
|
||||
il.Emit(OpCodes.Call, typeof(Task).GetMethod("FromResult").MakeGenericMethod(resultType));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MethodType.ClientStreaming:
|
||||
case MethodType.DuplexStreaming:
|
||||
if (def.MethodType == MethodType.ClientStreaming)
|
||||
{
|
||||
il.DeclareLocal(typeof(AsyncClientStreamingCall<byte[], byte[]>)); // callResult
|
||||
}
|
||||
else
|
||||
{
|
||||
il.DeclareLocal(typeof(AsyncDuplexStreamingCall<byte[], byte[]>)); // callResult
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, invokerField);
|
||||
il.Emit(OpCodes.Ldsfld, def.FieldMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, hostField);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, optionField);
|
||||
if (def.MethodType == MethodType.ClientStreaming)
|
||||
{
|
||||
il.Emit(OpCodes.Callvirt, typeof(CallInvoker).GetMethod("AsyncClientStreamingCall").MakeGenericMethod(typeof(byte[]), typeof(byte[])));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Callvirt, typeof(CallInvoker).GetMethod("AsyncDuplexStreamingCall").MakeGenericMethod(typeof(byte[]), typeof(byte[])));
|
||||
}
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
|
||||
// create return result
|
||||
Type resultType2;
|
||||
if (def.MethodType == MethodType.ClientStreaming)
|
||||
{
|
||||
// new ClientStreamingResult<TRequest, TResponse>(
|
||||
// inner,
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
|
||||
// new MarshallingClientStreamWriter<TRequest>()(inner.RequestStream, this.resolver),
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Call, typeof(AsyncClientStreamingCall<byte[], byte[]>).GetProperty("RequestStream").GetMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Newobj, (typeof(MarshallingClientStreamWriter<>).MakeGenericType(def.RequestType).GetConstructors().Single()));
|
||||
|
||||
// this.resolver
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
|
||||
// );
|
||||
resultType2 = typeof(ClientStreamingResult<,>).MakeGenericType(def.RequestType, def.ResponseType);
|
||||
il.Emit(OpCodes.Newobj, resultType2.GetConstructors().OrderBy(x => x.GetParameters().Length).Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
// new DuplexStreamingResult<TRequest, TResponse>(
|
||||
// inner,
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
|
||||
// new MarshallingClientStreamWriter<TRequest>()(inner.RequestStream, this.resolver),
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Call, typeof(AsyncDuplexStreamingCall<byte[], byte[]>).GetProperty("RequestStream").GetMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Newobj, (typeof(MarshallingClientStreamWriter<>).MakeGenericType(def.RequestType).GetConstructors().Single()));
|
||||
|
||||
// new MarshallingAsyncStreamReader<TResponse>()(inner.ResponseStream, this.resolver),
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Call, typeof(AsyncDuplexStreamingCall<byte[], byte[]>).GetProperty("ResponseStream").GetMethod);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Newobj, (typeof(MarshallingAsyncStreamReader<>).MakeGenericType(def.ResponseType).GetConstructors().Single()));
|
||||
|
||||
// this.resolver
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
|
||||
// );
|
||||
resultType2 = typeof(DuplexStreamingResult<,>).MakeGenericType(def.RequestType, def.ResponseType);
|
||||
il.Emit(OpCodes.Newobj, resultType2.GetConstructors()[0]);
|
||||
}
|
||||
|
||||
if (def.ResponseIsTask)
|
||||
{
|
||||
il.Emit(OpCodes.Call, typeof(Task).GetMethod("FromResult").MakeGenericMethod(resultType2));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Not supported method type:" + def.MethodType);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
static Type UnwrapResponseType(MethodDefinition def, out MethodType methodType, out bool responseIsTask, out Type requestTypeIfExists)
|
||||
{
|
||||
//if (!t.IsGenericType) throw new Exception($"Invalid ResponseType, Path:{def.Path} Type:{t.Name}");
|
||||
|
||||
var t = def.MethodInfo.ReturnType;
|
||||
if (!t.GetTypeInfo().IsGenericType) throw new Exception($"Invalid return type, path:{def.Path} type:{t.Name}");
|
||||
|
||||
// Task<Unary<T>>
|
||||
if (t.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
responseIsTask = true;
|
||||
t = t.GetGenericArguments()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
responseIsTask = false;
|
||||
}
|
||||
|
||||
// Unary<T>
|
||||
var returnType = t.GetGenericTypeDefinition();
|
||||
if (returnType == typeof(UnaryResult<>))
|
||||
{
|
||||
methodType = MethodType.Unary;
|
||||
requestTypeIfExists = null;
|
||||
return t.GetGenericArguments()[0];
|
||||
}
|
||||
else if (returnType == typeof(ClientStreamingResult<,>))
|
||||
{
|
||||
methodType = MethodType.ClientStreaming;
|
||||
var genArgs = t.GetGenericArguments();
|
||||
requestTypeIfExists = genArgs[0];
|
||||
return genArgs[1];
|
||||
}
|
||||
else if (returnType == typeof(ServerStreamingResult<>))
|
||||
{
|
||||
methodType = MethodType.ServerStreaming;
|
||||
requestTypeIfExists = null;
|
||||
return t.GetGenericArguments()[0];
|
||||
}
|
||||
else if (returnType == typeof(DuplexStreamingResult<,>))
|
||||
{
|
||||
methodType = MethodType.DuplexStreaming;
|
||||
var genArgs = t.GetGenericArguments();
|
||||
requestTypeIfExists = genArgs[0];
|
||||
return genArgs[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid return type, path:{def.Path} type:{t.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
class MethodDefinition
|
||||
{
|
||||
public string Path => ServiceType.Name + "/" + MethodInfo.Name;
|
||||
|
||||
// set after search definitions
|
||||
public Type ServiceType;
|
||||
public MethodInfo MethodInfo;
|
||||
public MethodType MethodType;
|
||||
|
||||
// set after define static fields
|
||||
public bool ResponseIsTask;
|
||||
public FieldInfo FieldMethod;
|
||||
public Type RequestType;
|
||||
public Type ResponseType;
|
||||
|
||||
// unary only, set after define static fields
|
||||
public FieldInfo UnaryRequestDelegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4756b0967dbdbae4fb4cb2afb87f4101
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
Assets/Scripts/MagicOnion/Client/InterceptInvokeHelper.cs
Normal file
158
Assets/Scripts/MagicOnion/Client/InterceptInvokeHelper.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
internal static class InterceptInvokeHelper
|
||||
{
|
||||
public static ValueTask<ResponseContext> InvokeWithFilter(RequestContext context)
|
||||
{
|
||||
switch (context.Filters.Length)
|
||||
{
|
||||
case 0:
|
||||
return new ValueTask<ResponseContext>(context.RequestMethod(context));
|
||||
case 1:
|
||||
return InvokeWithFilter1(context);
|
||||
case 2:
|
||||
return InvokeWithFilter2(context);
|
||||
case 3:
|
||||
return InvokeWithFilter3(context);
|
||||
case 4:
|
||||
return InvokeWithFilter4(context);
|
||||
case 5:
|
||||
return InvokeWithFilter5(context);
|
||||
case 6:
|
||||
return InvokeWithFilter6(context);
|
||||
case 7:
|
||||
return InvokeWithFilter7(context);
|
||||
case 8:
|
||||
return InvokeWithFilter8(context);
|
||||
case 9:
|
||||
return InvokeWithFilter9(context);
|
||||
case 10:
|
||||
return InvokeWithFilter10(context);
|
||||
default:
|
||||
return InvokeRecursive(-1, context);
|
||||
}
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter1(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync()));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter2(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync())));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter3(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync()))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter4(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync())))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter5(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync()))))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter6(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
x5 => x5.Filters[5].SendAsync(x5,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync())))))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter7(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
x5 => x5.Filters[5].SendAsync(x5,
|
||||
x6 => x6.Filters[6].SendAsync(x6,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync()))))))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter8(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
x5 => x5.Filters[5].SendAsync(x5,
|
||||
x6 => x6.Filters[6].SendAsync(x6,
|
||||
x7 => x7.Filters[7].SendAsync(x7,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync())))))))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter9(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
x5 => x5.Filters[5].SendAsync(x5,
|
||||
x6 => x6.Filters[6].SendAsync(x6,
|
||||
x7 => x7.Filters[7].SendAsync(x7,
|
||||
x8 => x8.Filters[8].SendAsync(x8,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync()))))))))));
|
||||
}
|
||||
|
||||
static ValueTask<ResponseContext> InvokeWithFilter10(RequestContext context)
|
||||
{
|
||||
return context.Filters[0].SendAsync(context,
|
||||
x1 => x1.Filters[1].SendAsync(x1,
|
||||
x2 => x2.Filters[2].SendAsync(x2,
|
||||
x3 => x3.Filters[3].SendAsync(x3,
|
||||
x4 => x4.Filters[4].SendAsync(x4,
|
||||
x5 => x5.Filters[5].SendAsync(x5,
|
||||
x6 => x6.Filters[6].SendAsync(x6,
|
||||
x7 => x7.Filters[7].SendAsync(x7,
|
||||
x8 => x8.Filters[8].SendAsync(x8,
|
||||
x9 => x9.Filters[9].SendAsync(x9,
|
||||
ctx => new ValueTask<ResponseContext>(ctx.RequestMethod(ctx).WaitResponseAsync())))))))))));
|
||||
}
|
||||
|
||||
// for invoke N filters(slow path).
|
||||
|
||||
static ValueTask<ResponseContext> InvokeRecursive(int index, RequestContext context)
|
||||
{
|
||||
index += 1; // start from -1
|
||||
if (index != context.Filters.Length)
|
||||
{
|
||||
return context.Filters[index].SendAsync(context, ctx => InvokeRecursive(index, ctx));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ValueTask<ResponseContext>(context.RequestMethod(context).WaitResponseAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4a428b894e3ddac4a80496efd076633c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/Scripts/MagicOnion/Client/MagicOnionClient.cs
Normal file
79
Assets/Scripts/MagicOnion/Client/MagicOnionClient.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using Grpc.Core;
|
||||
using MessagePack;
|
||||
using System;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
public static class MagicOnionClient
|
||||
{
|
||||
static readonly IClientFilter[] emptyFilters = Array.Empty<IClientFilter>();
|
||||
|
||||
public static T Create<T>(Channel channel)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(new DefaultCallInvoker(channel), MessagePackSerializer.DefaultOptions, emptyFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(CallInvoker invoker)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(invoker, MessagePackSerializer.DefaultOptions, emptyFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(Channel channel, IClientFilter[] clientFilters)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(new DefaultCallInvoker(channel), MessagePackSerializer.DefaultOptions, clientFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(CallInvoker invoker, IClientFilter[] clientFilters)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(invoker, MessagePackSerializer.DefaultOptions, clientFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(Channel channel, MessagePackSerializerOptions serializerOptions)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(new DefaultCallInvoker(channel), serializerOptions, emptyFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(CallInvoker invoker, MessagePackSerializerOptions serializerOptions)
|
||||
where T : IService<T>
|
||||
{
|
||||
return Create<T>(invoker, serializerOptions, emptyFilters);
|
||||
}
|
||||
|
||||
public static T Create<T>(CallInvoker invoker, MessagePackSerializerOptions serializerOptions, IClientFilter[] clientFilters)
|
||||
where T : IService<T>
|
||||
{
|
||||
if (invoker == null) throw new ArgumentNullException(nameof(invoker));
|
||||
|
||||
var ctor = MagicOnionClientRegistry<T>.consturtor;
|
||||
if (ctor == null)
|
||||
{
|
||||
#if ((ENABLE_IL2CPP && !UNITY_EDITOR) || NET_STANDARD_2_0)
|
||||
throw new InvalidOperationException("Does not registered client factory, dynamic code generation is not supported on IL2CPP. Please use code generator(moc).");
|
||||
#else
|
||||
var t = DynamicClientBuilder<T>.ClientType;
|
||||
return (T)Activator.CreateInstance(t, invoker, serializerOptions, clientFilters);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
return ctor(invoker, serializerOptions, clientFilters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MagicOnionClientRegistry<T>
|
||||
where T : IService<T>
|
||||
{
|
||||
public static Func<CallInvoker, MessagePackSerializerOptions, IClientFilter[], T> consturtor;
|
||||
|
||||
public static void Register(Func<CallInvoker, MessagePackSerializerOptions, IClientFilter[], T> ctor)
|
||||
{
|
||||
consturtor = ctor;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Client/MagicOnionClient.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Client/MagicOnionClient.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cd0a981f86ef1843bf6b375b2d9b5dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
130
Assets/Scripts/MagicOnion/Client/MagicOnionClientBase.cs
Normal file
130
Assets/Scripts/MagicOnion/Client/MagicOnionClientBase.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using Grpc.Core;
|
||||
using MessagePack;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
public abstract class MagicOnionClientBase
|
||||
{
|
||||
protected string host;
|
||||
protected CallOptions option;
|
||||
protected CallInvoker callInvoker;
|
||||
protected MessagePackSerializerOptions serializerOptions;
|
||||
protected IClientFilter[] filters;
|
||||
|
||||
static protected ResponseContext CreateResponseContext<TResponse>(RequestContext context, Method<byte[], byte[]> method)
|
||||
{
|
||||
var self = context.Client;
|
||||
var callResult = self.callInvoker.AsyncUnaryCall(method, self.host, context.CallOptions, context.RequestMutator(MagicOnionMarshallers.UnsafeNilBytes));
|
||||
var response = new ResponseContext<TResponse>(callResult, self.serializerOptions);
|
||||
response.SetResponseMutator(context.ResponseMutator);
|
||||
return response;
|
||||
}
|
||||
|
||||
static protected ResponseContext CreateResponseContext<TRequest, TResponse>(RequestContext context, Method<byte[], byte[]> method)
|
||||
{
|
||||
var self = context.Client;
|
||||
var message = MessagePackSerializer.Serialize<TRequest>(((RequestContext<TRequest>)context).Request, self.serializerOptions);
|
||||
var callResult = self.callInvoker.AsyncUnaryCall(method, self.host, context.CallOptions, context.RequestMutator(message));
|
||||
var response = new ResponseContext<TResponse>(callResult, self.serializerOptions);
|
||||
response.SetResponseMutator(context.ResponseMutator);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class MagicOnionClientBase<T> : MagicOnionClientBase
|
||||
where T : IService<T>
|
||||
{
|
||||
protected MagicOnionClientBase()
|
||||
{
|
||||
}
|
||||
|
||||
protected MagicOnionClientBase(CallInvoker callInvoker, MessagePackSerializerOptions serializerOptions, IClientFilter[] filters)
|
||||
{
|
||||
this.callInvoker = callInvoker;
|
||||
this.serializerOptions = serializerOptions;
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
protected UnaryResult<TResponse> InvokeAsync<TRequest, TResponse>(string path, TRequest request, Func<RequestContext, ResponseContext> requestMethod)
|
||||
{
|
||||
var future = InvokeAsyncCore<TRequest, TResponse>(path, request, requestMethod);
|
||||
return new UnaryResult<TResponse>(future);
|
||||
}
|
||||
|
||||
async Task<IResponseContext<TResponse>> InvokeAsyncCore<TRequest, TResponse>(string path, TRequest request, Func<RequestContext, ResponseContext> requestMethod)
|
||||
{
|
||||
if (this.option.Headers == null && filters.Length != 0)
|
||||
{
|
||||
// always creating new Metadata is bad manner for performance
|
||||
this.option = this.option.WithHeaders(new Metadata());
|
||||
}
|
||||
|
||||
var requestContext = new RequestContext<TRequest>(request, this, path, option, typeof(TResponse), filters, requestMethod);
|
||||
var response = await InterceptInvokeHelper.InvokeWithFilter(requestContext);
|
||||
var result = response as IResponseContext<TResponse>;
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("ResponseContext is null.");
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<UnaryResult<TResponse>> InvokeTaskAsync<TRequest, TResponse>(string path, TRequest request, Func<RequestContext, ResponseContext> requestMethod)
|
||||
{
|
||||
if (this.option.Headers == null && filters.Length != 0)
|
||||
{
|
||||
// always creating new Metadata is bad manner for performance
|
||||
this.option = this.option.WithHeaders(new Metadata());
|
||||
}
|
||||
|
||||
var requestContext = new RequestContext<TRequest>(request, this, path, option, typeof(TResponse), filters, requestMethod);
|
||||
var response = await InterceptInvokeHelper.InvokeWithFilter(requestContext);
|
||||
var result = response as IResponseContext<TResponse>;
|
||||
if (result != null)
|
||||
{
|
||||
return new UnaryResult<TResponse>(Task.FromResult(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("ResponseContext is null.");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract MagicOnionClientBase<T> Clone();
|
||||
|
||||
public virtual T WithCancellationToken(CancellationToken cancellationToken)
|
||||
{
|
||||
return WithOptions(this.option.WithCancellationToken(cancellationToken));
|
||||
}
|
||||
|
||||
public virtual T WithDeadline(DateTime deadline)
|
||||
{
|
||||
return WithOptions(this.option.WithDeadline(deadline));
|
||||
}
|
||||
|
||||
public virtual T WithHeaders(Metadata headers)
|
||||
{
|
||||
return WithOptions(this.option.WithHeaders(headers));
|
||||
}
|
||||
|
||||
public virtual T WithHost(string host)
|
||||
{
|
||||
var newInstance = Clone();
|
||||
newInstance.host = host;
|
||||
return (T)(object)newInstance;
|
||||
}
|
||||
|
||||
public virtual T WithOptions(CallOptions option)
|
||||
{
|
||||
var newInstance = Clone();
|
||||
newInstance.option = option;
|
||||
return (T)(object)newInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d469b4f1a615e4f4f8bf51459e9e5d50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/Scripts/MagicOnion/Client/StreamingHubClient.cs
Normal file
50
Assets/Scripts/MagicOnion/Client/StreamingHubClient.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using Grpc.Core;
|
||||
using Grpc.Core.Logging;
|
||||
using MessagePack;
|
||||
using System;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
public static class StreamingHubClient
|
||||
{
|
||||
public static TStreamingHub Connect<TStreamingHub, TReceiver>(Channel channel, TReceiver receiver, string host = null, CallOptions option = default(CallOptions), MessagePackSerializerOptions serializerOptions = null, ILogger logger = null)
|
||||
where TStreamingHub : IStreamingHub<TStreamingHub, TReceiver>
|
||||
{
|
||||
return Connect<TStreamingHub, TReceiver>(new DefaultCallInvoker(channel), receiver, host, option, serializerOptions, logger);
|
||||
}
|
||||
|
||||
public static TStreamingHub Connect<TStreamingHub, TReceiver>(CallInvoker callInvoker, TReceiver receiver, string host = null, CallOptions option = default(CallOptions), MessagePackSerializerOptions serializerOptions = null, ILogger logger = null)
|
||||
where TStreamingHub : IStreamingHub<TStreamingHub, TReceiver>
|
||||
{
|
||||
var ctor = StreamingHubClientRegistry<TStreamingHub, TReceiver>.consturtor;
|
||||
StreamingHubClientBase<TStreamingHub, TReceiver> client = null;
|
||||
if (ctor == null)
|
||||
{
|
||||
#if ((ENABLE_IL2CPP && !UNITY_EDITOR) || NET_STANDARD_2_0)
|
||||
throw new InvalidOperationException("Does not registered client factory, dynamic code generation is not supported on IL2CPP. Please use code generator(moc).");
|
||||
#else
|
||||
var type = StreamingHubClientBuilder<TStreamingHub, TReceiver>.ClientType;
|
||||
client = (StreamingHubClientBase<TStreamingHub, TReceiver>)Activator.CreateInstance(type, new object[] { callInvoker, host, option, serializerOptions, logger });
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
client = (StreamingHubClientBase<TStreamingHub, TReceiver>)(object)ctor(callInvoker, receiver, host, option, serializerOptions, logger);
|
||||
}
|
||||
|
||||
client.__ConnectAndSubscribe(receiver);
|
||||
return (TStreamingHub)(object)client;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StreamingHubClientRegistry<TStreamingHub, TReceiver>
|
||||
where TStreamingHub : IStreamingHub<TStreamingHub, TReceiver>
|
||||
{
|
||||
public static Func<CallInvoker, TReceiver, string, CallOptions, MessagePackSerializerOptions, ILogger, TStreamingHub> consturtor;
|
||||
|
||||
public static void Register(Func<CallInvoker, TReceiver, string, CallOptions, MessagePackSerializerOptions, ILogger, TStreamingHub> ctor)
|
||||
{
|
||||
consturtor = ctor;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Client/StreamingHubClient.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Client/StreamingHubClient.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d74d1bafd22f5794fa60ad8963e9be64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
332
Assets/Scripts/MagicOnion/Client/StreamingHubClientBase.cs
Normal file
332
Assets/Scripts/MagicOnion/Client/StreamingHubClientBase.cs
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
using Grpc.Core;
|
||||
using Grpc.Core.Logging;
|
||||
using MessagePack;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MagicOnion.Utils;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MagicOnion.Server;
|
||||
using System.Buffers;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
public abstract class StreamingHubClientBase<TStreamingHub, TReceiver>
|
||||
where TStreamingHub : IStreamingHub<TStreamingHub, TReceiver>
|
||||
{
|
||||
readonly string host;
|
||||
readonly CallOptions option;
|
||||
readonly CallInvoker callInvoker;
|
||||
readonly ILogger logger;
|
||||
|
||||
protected readonly MessagePackSerializerOptions serializerOptions;
|
||||
readonly AsyncLock asyncLock = new AsyncLock();
|
||||
|
||||
DuplexStreamingResult<byte[], byte[]> connection;
|
||||
protected TReceiver receiver;
|
||||
Task subscription;
|
||||
TaskCompletionSource<object> waitForDisconnect = new TaskCompletionSource<object>();
|
||||
|
||||
// {messageId, TaskCompletionSource}
|
||||
ConcurrentDictionary<int, object> responseFutures = new ConcurrentDictionary<int, object>();
|
||||
protected CancellationTokenSource cts = new CancellationTokenSource();
|
||||
int messageId = 0;
|
||||
bool disposed;
|
||||
|
||||
protected StreamingHubClientBase(CallInvoker callInvoker, string host, CallOptions option, MessagePackSerializerOptions serializerOptions, ILogger logger)
|
||||
{
|
||||
this.callInvoker = callInvoker;
|
||||
this.host = host;
|
||||
this.option = option;
|
||||
this.serializerOptions = serializerOptions;
|
||||
this.logger = logger ?? GrpcEnvironment.Logger;
|
||||
}
|
||||
|
||||
protected abstract Method<byte[], byte[]> DuplexStreamingAsyncMethod { get; }
|
||||
|
||||
// call immediately after create.
|
||||
public void __ConnectAndSubscribe(TReceiver receiver)
|
||||
{
|
||||
var callResult = callInvoker.AsyncDuplexStreamingCall<byte[], byte[]>(DuplexStreamingAsyncMethod, host, option);
|
||||
var streamingResult = new DuplexStreamingResult<byte[], byte[]>(
|
||||
callResult,
|
||||
new MarshallingClientStreamWriter<byte[]>(callResult.RequestStream, serializerOptions),
|
||||
new MarshallingAsyncStreamReader<byte[]>(callResult.ResponseStream, serializerOptions),
|
||||
serializerOptions
|
||||
);
|
||||
|
||||
this.connection = streamingResult;
|
||||
this.receiver = receiver;
|
||||
this.subscription = StartSubscribe();
|
||||
}
|
||||
|
||||
protected abstract void OnResponseEvent(int methodId, object taskCompletionSource, ArraySegment<byte> data);
|
||||
protected abstract void OnBroadcastEvent(int methodId, ArraySegment<byte> data);
|
||||
|
||||
async Task StartSubscribe()
|
||||
{
|
||||
var syncContext = SynchronizationContext.Current; // capture SynchronizationContext.
|
||||
var reader = connection.RawStreamingCall.ResponseStream;
|
||||
try
|
||||
{
|
||||
while (await reader.MoveNext(cts.Token).ConfigureAwait(false)) // avoid Post to SyncContext(it losts one-frame per operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
// MessageFormat:
|
||||
// broadcast: [methodId, [argument]]
|
||||
// response: [messageId, methodId, response]
|
||||
// error-response: [messageId, statusCode, detail, StringMessage]
|
||||
void ConsumeData(byte[] data)
|
||||
{
|
||||
var messagePackReader = new MessagePackReader(data);
|
||||
var arrayLength = messagePackReader.ReadArrayHeader();
|
||||
if (arrayLength == 3)
|
||||
{
|
||||
var messageId = messagePackReader.ReadInt32();
|
||||
object future;
|
||||
if (responseFutures.TryRemove(messageId, out future))
|
||||
{
|
||||
var methodId = messagePackReader.ReadInt32();
|
||||
try
|
||||
{
|
||||
var offset = (int)messagePackReader.Consumed;
|
||||
var rest = new ArraySegment<byte>(data, offset, data.Length - offset);
|
||||
OnResponseEvent(methodId, future, rest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!(future as ITaskCompletion).TrySetException(ex))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (arrayLength == 4)
|
||||
{
|
||||
var messageId = messagePackReader.ReadInt32();
|
||||
object future;
|
||||
if (responseFutures.TryRemove(messageId, out future))
|
||||
{
|
||||
var statusCode = messagePackReader.ReadInt32();
|
||||
var detail = messagePackReader.ReadString();
|
||||
var offset = (int)messagePackReader.Consumed;
|
||||
var rest = new ArraySegment<byte>(data, offset, data.Length - offset);
|
||||
var error = MessagePackSerializer.Deserialize<string>(rest, serializerOptions);
|
||||
(future as ITaskCompletion).TrySetException(new RpcException(new Status((StatusCode)statusCode, detail), error));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var methodId = messagePackReader.ReadInt32();
|
||||
var offset = (int)messagePackReader.Consumed;
|
||||
if (syncContext != null)
|
||||
{
|
||||
var tuple = Tuple.Create(methodId, data, offset, data.Length - offset);
|
||||
syncContext.Post(state =>
|
||||
{
|
||||
var t = (Tuple<int, byte[], int, int>)state;
|
||||
OnBroadcastEvent(t.Item1, new ArraySegment<byte>(t.Item2, t.Item3, t.Item4));
|
||||
}, tuple);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBroadcastEvent(methodId, new ArraySegment<byte>(data, offset, data.Length - offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConsumeData(reader.Current);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
const string msg = "Error on consume received message, but keep subscribe.";
|
||||
// log post on main thread.
|
||||
if (syncContext != null)
|
||||
{
|
||||
syncContext.Post(state => logger.Error((Exception)state, msg), ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error(ex, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const string msg = "Error on subscribing message.";
|
||||
// log post on main thread.
|
||||
if (syncContext != null)
|
||||
{
|
||||
syncContext.Post(state => logger.Error((Exception)state, msg), ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error(ex, msg);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
// set syncContext before await
|
||||
if (syncContext != null && SynchronizationContext.Current == null)
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(syncContext);
|
||||
}
|
||||
await DisposeAsyncCore(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
waitForDisconnect.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected async Task WriteMessageAsync<T>(int methodId, T message)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
byte[] BuildMessage()
|
||||
{
|
||||
using (var buffer = ArrayPoolBufferWriter.RentThreadStaticWriter())
|
||||
{
|
||||
var writer = new MessagePackWriter(buffer);
|
||||
writer.WriteArrayHeader(2);
|
||||
writer.Write(methodId);
|
||||
MessagePackSerializer.Serialize(ref writer, message, serializerOptions);
|
||||
writer.Flush();
|
||||
return buffer.WrittenSpan.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
var v = BuildMessage();
|
||||
using (await asyncLock.LockAsync())
|
||||
{
|
||||
await connection.RawStreamingCall.RequestStream.WriteAsync(v).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<TResponse> WriteMessageAsyncFireAndForget<TRequest, TResponse>(int methodId, TRequest message)
|
||||
{
|
||||
await WriteMessageAsync(methodId, message).ConfigureAwait(false);
|
||||
return default(TResponse);
|
||||
}
|
||||
|
||||
protected async Task<TResponse> WriteMessageWithResponseAsync<TRequest, TResponse>(int methodId, TRequest message)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var mid = Interlocked.Increment(ref messageId);
|
||||
var tcs = new TaskCompletionSourceEx<TResponse>(); // use Ex
|
||||
responseFutures[mid] = (object)tcs;
|
||||
|
||||
byte[] BuildMessage()
|
||||
{
|
||||
using (var buffer = ArrayPoolBufferWriter.RentThreadStaticWriter())
|
||||
{
|
||||
var writer = new MessagePackWriter(buffer);
|
||||
writer.WriteArrayHeader(3);
|
||||
writer.Write(mid);
|
||||
writer.Write(methodId);
|
||||
MessagePackSerializer.Serialize(ref writer, message, serializerOptions);
|
||||
writer.Flush();
|
||||
return buffer.WrittenSpan.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
var v = BuildMessage();
|
||||
using (await asyncLock.LockAsync().ConfigureAwait(false))
|
||||
{
|
||||
await connection.RawStreamingCall.RequestStream.WriteAsync(v).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await tcs.Task; // wait until server return response(or error). if connection was closed, throws cancellation from DisposeAsyncCore.
|
||||
}
|
||||
|
||||
void ThrowIfDisposed()
|
||||
{
|
||||
if (disposed) throw new ObjectDisposedException("StreamingHubClient");
|
||||
}
|
||||
|
||||
public Task WaitForDisconnect()
|
||||
{
|
||||
return waitForDisconnect.Task;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return DisposeAsyncCore(true);
|
||||
}
|
||||
|
||||
async Task DisposeAsyncCore(bool waitSubscription)
|
||||
{
|
||||
if (disposed) return;
|
||||
if (connection.RawStreamingCall == null) return;
|
||||
|
||||
disposed = true;
|
||||
|
||||
try
|
||||
{
|
||||
await connection.RequestStream.CompleteAsync();
|
||||
}
|
||||
catch { } // ignore error?
|
||||
finally
|
||||
{
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
try
|
||||
{
|
||||
if (waitSubscription)
|
||||
{
|
||||
if (subscription != null)
|
||||
{
|
||||
await subscription;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup completion
|
||||
List<Exception> aggregateException = null;
|
||||
foreach (var item in responseFutures)
|
||||
{
|
||||
try
|
||||
{
|
||||
(item.Value as ITaskCompletion).TrySetCanceled();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!(ex is OperationCanceledException))
|
||||
{
|
||||
if (aggregateException != null)
|
||||
{
|
||||
aggregateException = new List<Exception>();
|
||||
aggregateException.Add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aggregateException != null)
|
||||
{
|
||||
throw new AggregateException(aggregateException);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!(ex is OperationCanceledException))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d3ca190a4e3a9184ea0ba8b8c5e88b2f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
584
Assets/Scripts/MagicOnion/Client/StreamingHubClientBuilder.cs
Normal file
584
Assets/Scripts/MagicOnion/Client/StreamingHubClientBuilder.cs
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
#if NON_UNITY || !NET_STANDARD_2_0
|
||||
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Logging;
|
||||
using MagicOnion.Server.Hubs;
|
||||
using MagicOnion.Utils;
|
||||
using MessagePack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Client
|
||||
{
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class StreamingHubClientAssemblyHolder
|
||||
{
|
||||
public const string ModuleName = "MagicOnion.Client.StreamingHubClient";
|
||||
|
||||
readonly static DynamicAssembly assembly;
|
||||
internal static DynamicAssembly Assembly { get { return assembly; } }
|
||||
|
||||
static StreamingHubClientAssemblyHolder()
|
||||
{
|
||||
assembly = new DynamicAssembly(ModuleName);
|
||||
}
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
|
||||
public static AssemblyBuilder Save()
|
||||
{
|
||||
return assembly.Save();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class StreamingHubClientBuilder<TStreamingHub, TReceiver>
|
||||
{
|
||||
public static readonly Type ClientType;
|
||||
// static readonly Type ClientFireAndForgetType;
|
||||
|
||||
static readonly Type bytesMethod = typeof(Method<,>).MakeGenericType(new[] { typeof(byte[]), typeof(byte[]) });
|
||||
static readonly FieldInfo throughMarshaller = typeof(MagicOnionMarshallers).GetField("ThroughMarshaller", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
static readonly ConstructorInfo notSupportedException = typeof(NotSupportedException).GetConstructor(Type.EmptyTypes);
|
||||
|
||||
static readonly MethodInfo callMessagePackDesrialize = typeof(MessagePackSerializer).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
|
||||
.First(x => x.Name == "Deserialize" && x.GetParameters().Length == 3 && x.GetParameters()[0].ParameterType == typeof(ReadOnlyMemory<byte>) && x.GetParameters()[1].ParameterType == typeof(MessagePackSerializerOptions));
|
||||
static readonly MethodInfo callCancellationTokenNone = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod();
|
||||
static readonly PropertyInfo completedTask = typeof(Task).GetProperty("CompletedTask", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
static StreamingHubClientBuilder()
|
||||
{
|
||||
var t = typeof(TStreamingHub);
|
||||
var ti = t.GetTypeInfo();
|
||||
if (!ti.IsInterface) throw new Exception("Client Proxy only allows interface. Type:" + ti.Name);
|
||||
var asm = StreamingHubClientAssemblyHolder.Assembly;
|
||||
var methodDefinitions = SearchDefinitions(t);
|
||||
|
||||
var parentType = typeof(StreamingHubClientBase<,>).MakeGenericType(typeof(TStreamingHub), typeof(TReceiver));
|
||||
var typeBuilder = asm.DefineType($"{DynamicClientAssemblyHolder.ModuleName}.{ti.FullName}StreamingHubClient_{Guid.NewGuid().ToString()}", TypeAttributes.Public, parentType, new Type[] { t });
|
||||
|
||||
VerifyMethodDefinitions(methodDefinitions);
|
||||
|
||||
{
|
||||
// Create FireAndForgetType first as nested type.
|
||||
var typeBuilderEx = typeBuilder.DefineNestedType($"FireAndForgetClient", TypeAttributes.NestedPrivate, typeof(object), new Type[] { t });
|
||||
var tuple = DefineFireAndForgetConstructor(typeBuilderEx, typeBuilder);
|
||||
var fireAndForgetClientCtor = tuple.Item1;
|
||||
var fireAndForgetField = tuple.Item2;
|
||||
|
||||
DefineMethodsFireAndForget(typeBuilderEx, t, fireAndForgetField, typeBuilder, methodDefinitions);
|
||||
typeBuilderEx.CreateTypeInfo(); // ok to create nested type.
|
||||
|
||||
var methodField = DefineStaticConstructor(typeBuilder, t);
|
||||
var clientField = DefineConstructor(typeBuilder, t, typeof(TReceiver), fireAndForgetClientCtor);
|
||||
DefineMethods(typeBuilder, t, typeof(TReceiver), methodField, clientField, methodDefinitions);
|
||||
}
|
||||
|
||||
ClientType = typeBuilder.CreateTypeInfo().AsType();
|
||||
}
|
||||
|
||||
static MethodDefinition[] SearchDefinitions(Type interfaceType)
|
||||
{
|
||||
return interfaceType
|
||||
.GetInterfaces()
|
||||
.Concat(new[] { interfaceType })
|
||||
.SelectMany(x => x.GetMethods())
|
||||
.Where(x =>
|
||||
{
|
||||
var methodInfo = x;
|
||||
if (methodInfo.IsSpecialName && (methodInfo.Name.StartsWith("set_") || methodInfo.Name.StartsWith("get_"))) return false;
|
||||
if (methodInfo.GetCustomAttribute<IgnoreAttribute>(false) != null) return false; // ignore
|
||||
|
||||
var methodName = methodInfo.Name;
|
||||
if (methodName == "Equals"
|
||||
|| methodName == "GetHashCode"
|
||||
|| methodName == "GetType"
|
||||
|| methodName == "ToString"
|
||||
|| methodName == "DisposeAsync"
|
||||
|| methodName == "WaitForDisconnect"
|
||||
|| methodName == "FireAndForget"
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.Where(x => !x.IsSpecialName)
|
||||
.Select(x => new MethodDefinition
|
||||
{
|
||||
ServiceType = interfaceType,
|
||||
MethodInfo = x,
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
static void VerifyMethodDefinitions(MethodDefinition[] definitions)
|
||||
{
|
||||
var map = new Dictionary<int, MethodDefinition>(definitions.Length);
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
var methodId = item.MethodInfo.GetCustomAttribute<MethodIdAttribute>()?.MethodId ?? FNV1A32.GetHashCode(item.MethodInfo.Name);
|
||||
if (map.ContainsKey(methodId))
|
||||
{
|
||||
throw new Exception($"TStreamingHub does not allows duplicate methodId(hash code). Please change name or use MethodIdAttribute. {map[methodId].MethodInfo.Name} and {item.MethodInfo.Name}");
|
||||
}
|
||||
map.Add(methodId, item);
|
||||
|
||||
if (!(item.MethodInfo.ReturnType.IsGenericType && item.MethodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
&& item.MethodInfo.ReturnType != typeof(Task))
|
||||
{
|
||||
throw new Exception($"Invalid definition, TStreamingHub's return type must only be `Task` or `Task<T>`. {item.MethodInfo.Name}.");
|
||||
}
|
||||
|
||||
item.MethodId = methodId;
|
||||
if (item.RequestType == null)
|
||||
{
|
||||
item.RequestType = MagicOnionMarshallers.CreateRequestType(item.MethodInfo.GetParameters());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FieldInfo DefineStaticConstructor(TypeBuilder typeBuilder, Type interfaceType)
|
||||
{
|
||||
// static readonly Method<byte[], byte[]> method = new Method<byte[], byte[]>(MethodType.DuplexStreaming, "IFoo", "Connect", MagicOnionMarshallers.ThroughMarshaller, MagicOnionMarshallers.ThroughMarshaller);
|
||||
var field = typeBuilder.DefineField("method", bytesMethod, FieldAttributes.Private | FieldAttributes.Static);
|
||||
|
||||
var cctor = typeBuilder.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
|
||||
var il = cctor.GetILGenerator();
|
||||
|
||||
il.EmitLdc_I4((int)MethodType.DuplexStreaming);
|
||||
il.Emit(OpCodes.Ldstr, interfaceType.Name);
|
||||
il.Emit(OpCodes.Ldstr, "Connect");
|
||||
il.Emit(OpCodes.Ldsfld, throughMarshaller);
|
||||
il.Emit(OpCodes.Ldsfld, throughMarshaller);
|
||||
il.Emit(OpCodes.Newobj, bytesMethod.GetConstructors()[0]);
|
||||
il.Emit(OpCodes.Stsfld, field);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
static FieldInfo DefineConstructor(TypeBuilder typeBuilder, Type interfaceType, Type receiverType, ConstructorInfo fireAndForgetClientCtor)
|
||||
{
|
||||
// .ctor(CallInvoker callInvoker, string host, CallOptions option, MessagePackSerializerOptions resolver, ILogger logger) :base(...)
|
||||
{
|
||||
var argTypes = new[] { typeof(CallInvoker), typeof(string), typeof(CallOptions), typeof(MessagePackSerializerOptions), typeof(ILogger) };
|
||||
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, argTypes);
|
||||
var il = ctor.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldarg_2);
|
||||
il.Emit(OpCodes.Ldarg_3);
|
||||
il.Emit(OpCodes.Ldarg_S, (byte)4);
|
||||
il.Emit(OpCodes.Ldarg_S, (byte)5);
|
||||
il.Emit(OpCodes.Call, typeBuilder.BaseType
|
||||
.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).First());
|
||||
|
||||
// { this.fireAndForgetClient = new FireAndForgetClient(this); }
|
||||
var clientField = typeBuilder.DefineField("fireAndForgetClient", fireAndForgetClientCtor.DeclaringType, FieldAttributes.Private);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Newobj, fireAndForgetClientCtor);
|
||||
il.Emit(OpCodes.Stfld, clientField);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return clientField;
|
||||
}
|
||||
}
|
||||
|
||||
static Tuple<ConstructorBuilder, FieldBuilder> DefineFireAndForgetConstructor(TypeBuilder typeBuilder, Type parentClientType)
|
||||
{
|
||||
// .ctor(Parent client) { this.client = client }
|
||||
{
|
||||
var argTypes = new[] { parentClientType };
|
||||
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, argTypes);
|
||||
var clientField = typeBuilder.DefineField("client", parentClientType, FieldAttributes.Private);
|
||||
var il = ctor.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeof(object).GetConstructors().First());
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Stfld, clientField);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return Tuple.Create(ctor, clientField);
|
||||
}
|
||||
}
|
||||
|
||||
static void DefineMethods(TypeBuilder typeBuilder, Type interfaceType, Type receiverType, FieldInfo methodField, FieldInfo clientField, MethodDefinition[] definitions)
|
||||
{
|
||||
var baseType = typeof(StreamingHubClientBase<,>).MakeGenericType(interfaceType, receiverType);
|
||||
var serializerOptionsField = baseType.GetField("serializerOptions", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var receiverField = baseType.GetField("receiver", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// protected abstract Method<byte[], byte[]> DuplexStreamingAsyncMethod { get; }
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("get_DuplexStreamingAsyncMethod", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
bytesMethod,
|
||||
Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, methodField);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
{
|
||||
// Task DisposeAsync();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("DisposeAsync", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(Task), Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("DisposeAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// Task WaitForDisconnect();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WaitForDisconnect", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(Task), Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, baseType.GetMethod("WaitForDisconnect", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
// TSelf FireAndForget();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("FireAndForget", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType, Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, clientField);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
// receiver types borrow from DynamicBroadcastBuilder
|
||||
{
|
||||
// protected abstract void OnResponseEvent(int methodId, object taskCompletionSource, ArraySegment<byte> data);
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("OnResponseEvent", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
null, new[] { typeof(int), typeof(object), typeof(ArraySegment<byte>) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
var labels = definitions
|
||||
.Select(x => new { def = x, label = il.DefineLabel() })
|
||||
.ToArray();
|
||||
|
||||
foreach (var item in labels)
|
||||
{
|
||||
// if( == ) goto ...
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.EmitLdc_I4(item.def.MethodId);
|
||||
il.Emit(OpCodes.Beq, item.label);
|
||||
}
|
||||
// else
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
foreach (var item in labels)
|
||||
{
|
||||
// var result = LZ4MessagePackSerializer.Deserialize<T>(data, resolver);
|
||||
// ((TaskCompletionSource<T>)taskCompletionSource).TrySetResult(result);
|
||||
|
||||
// => ((TaskCompletionSource<T>)taskCompletionSource).TrySetResult(LZ4MessagePackSerializer.Deserialize<T>(data, resolver));
|
||||
|
||||
Type responseType;
|
||||
Type tcsType;
|
||||
if (item.def.MethodInfo.ReturnType == typeof(Task))
|
||||
{
|
||||
// Task methods uses TaskCompletionSource<Nil>
|
||||
responseType = typeof(Nil);
|
||||
tcsType = typeof(TaskCompletionSource<Nil>);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseType = item.def.MethodInfo.ReturnType.GetGenericArguments()[0];
|
||||
tcsType = typeof(TaskCompletionSource<>).MakeGenericType(responseType);
|
||||
}
|
||||
|
||||
il.MarkLabel(item.label);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_2);
|
||||
il.Emit(OpCodes.Castclass, tcsType);
|
||||
il.Emit(OpCodes.Ldarg_3);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Call, callCancellationTokenNone);
|
||||
il.Emit(OpCodes.Call, callMessagePackDesrialize.MakeGenericMethod(responseType));
|
||||
|
||||
il.Emit(OpCodes.Callvirt, tcsType.GetMethod("TrySetResult"));
|
||||
il.Emit(OpCodes.Pop);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
// protected abstract void OnBroadcastEvent(int methodId, ArraySegment<byte> data);
|
||||
{
|
||||
var methodDefinitions = BroadcasterHelper.SearchDefinitions(receiverType);
|
||||
BroadcasterHelper.VerifyMethodDefinitions(methodDefinitions);
|
||||
|
||||
var method = typeBuilder.DefineMethod("OnBroadcastEvent", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(void), new[] { typeof(int), typeof(ArraySegment<byte>) });
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
var labels = methodDefinitions
|
||||
.Select(x => new { def = x, label = il.DefineLabel() })
|
||||
.ToArray();
|
||||
|
||||
foreach (var item in labels)
|
||||
{
|
||||
// if( == ) goto ...
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.EmitLdc_I4(item.def.MethodId);
|
||||
il.Emit(OpCodes.Beq, item.label);
|
||||
}
|
||||
// else
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
foreach (var item in labels)
|
||||
{
|
||||
il.MarkLabel(item.label);
|
||||
|
||||
// var result = LZ4MessagePackSerializer.Deserialize<DynamicArgumentTuple<int, string>>(data, resolver);
|
||||
// return receiver.OnReceiveMessage(result.Item1, result.Item2);
|
||||
|
||||
var parameters = item.def.MethodInfo.GetParameters();
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, receiverField);
|
||||
il.Emit(OpCodes.Callvirt, item.def.MethodInfo);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
else if (parameters.Length == 1)
|
||||
{
|
||||
// TODO:fix emit
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, receiverField);
|
||||
il.Emit(OpCodes.Ldarg_2);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Call, callCancellationTokenNone);
|
||||
il.Emit(OpCodes.Call, callMessagePackDesrialize.MakeGenericMethod(parameters[0].ParameterType));
|
||||
il.Emit(OpCodes.Callvirt, item.def.MethodInfo);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO:fix emit
|
||||
var deserializeType = BroadcasterHelper.dynamicArgumentTupleTypes[parameters.Length - 2]
|
||||
.MakeGenericType(parameters.Select(x => x.ParameterType).ToArray());
|
||||
var lc = il.DeclareLocal(deserializeType);
|
||||
il.Emit(OpCodes.Ldarg_2);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, serializerOptionsField);
|
||||
il.Emit(OpCodes.Call, callCancellationTokenNone);
|
||||
il.Emit(OpCodes.Call, callMessagePackDesrialize.MakeGenericMethod(deserializeType));
|
||||
il.Emit(OpCodes.Stloc, lc);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, receiverField);
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
il.Emit(OpCodes.Ldloc, lc);
|
||||
il.Emit(OpCodes.Ldfld, deserializeType.GetField("Item" + (i + 1)));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Callvirt, item.def.MethodInfo);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy Methods
|
||||
for (int i = 0; i < definitions.Length; i++)
|
||||
{
|
||||
var def = definitions[i];
|
||||
var parameters = def.MethodInfo.GetParameters().Select(x => x.ParameterType).ToArray();
|
||||
|
||||
var method = typeBuilder.DefineMethod(def.MethodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
def.MethodInfo.ReturnType,
|
||||
parameters);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
// return WriteMessageAsync<T>(methodId, message);
|
||||
// return WriteMessageWithResponseAsync<TReq, TRes>(methodId, message);
|
||||
|
||||
// this.***
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// arg1
|
||||
il.EmitLdc_I4(def.MethodId);
|
||||
|
||||
// create request for arg2
|
||||
for (int j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg, j + 1);
|
||||
}
|
||||
|
||||
Type callType = null;
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
// use Nil.
|
||||
callType = typeof(Nil);
|
||||
il.Emit(OpCodes.Ldsfld, typeof(Nil).GetField("Default"));
|
||||
}
|
||||
else if (parameters.Length == 1)
|
||||
{
|
||||
// already loaded parameter.
|
||||
callType = parameters[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// call new DynamicArgumentTuple<T>
|
||||
callType = def.RequestType;
|
||||
il.Emit(OpCodes.Newobj, callType.GetConstructors().First());
|
||||
}
|
||||
|
||||
if (def.MethodInfo.ReturnType == typeof(Task))
|
||||
{
|
||||
var mInfo = baseType.GetMethod("WriteMessageWithResponseAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
il.Emit(OpCodes.Callvirt, mInfo.MakeGenericMethod(callType, typeof(Nil)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var mInfo = baseType.GetMethod("WriteMessageWithResponseAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
il.Emit(OpCodes.Callvirt, mInfo.MakeGenericMethod(callType, def.MethodInfo.ReturnType.GetGenericArguments()[0]));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void DefineMethodsFireAndForget(TypeBuilder typeBuilder, Type interfaceType, FieldInfo clientField, Type parentNestedType, MethodDefinition[] definitions)
|
||||
{
|
||||
{
|
||||
// Task DisposeAsync();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("DisposeAsync", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(Task), Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Newobj, notSupportedException);
|
||||
il.Emit(OpCodes.Throw);
|
||||
}
|
||||
// Task WaitForDisconnect();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("WaitForDisconnect", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
typeof(Task), Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Newobj, notSupportedException);
|
||||
il.Emit(OpCodes.Throw);
|
||||
}
|
||||
// TSelf FireAndForget();
|
||||
{
|
||||
var method = typeBuilder.DefineMethod("FireAndForget", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
interfaceType, Type.EmptyTypes);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Newobj, notSupportedException);
|
||||
il.Emit(OpCodes.Throw);
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy Methods
|
||||
for (int i = 0; i < definitions.Length; i++)
|
||||
{
|
||||
var def = definitions[i];
|
||||
var parameters = def.MethodInfo.GetParameters().Select(x => x.ParameterType).ToArray();
|
||||
|
||||
var method = typeBuilder.DefineMethod(def.MethodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
|
||||
def.MethodInfo.ReturnType,
|
||||
parameters);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
// return client.WriteMessage***<T>(methodId, message);
|
||||
|
||||
// this.client.***
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, clientField);
|
||||
|
||||
// arg1
|
||||
il.EmitLdc_I4(def.MethodId);
|
||||
|
||||
// create request for arg2
|
||||
for (int j = 0; j < parameters.Length; j++)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg, j + 1);
|
||||
}
|
||||
|
||||
Type callType = null;
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
// use Nil.
|
||||
callType = typeof(Nil);
|
||||
il.Emit(OpCodes.Ldsfld, typeof(Nil).GetField("Default"));
|
||||
}
|
||||
else if (parameters.Length == 1)
|
||||
{
|
||||
// already loaded parameter.
|
||||
callType = parameters[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// call new DynamicArgumentTuple<T>
|
||||
callType = def.RequestType;
|
||||
il.Emit(OpCodes.Newobj, callType.GetConstructors().First());
|
||||
}
|
||||
|
||||
if (def.MethodInfo.ReturnType == typeof(Task))
|
||||
{
|
||||
var mInfo = parentNestedType.BaseType.GetMethod("WriteMessageAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
il.Emit(OpCodes.Callvirt, mInfo.MakeGenericMethod(callType));
|
||||
}
|
||||
else
|
||||
{
|
||||
var mInfo = parentNestedType.BaseType.GetMethod("WriteMessageAsyncFireAndForget", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
il.Emit(OpCodes.Callvirt, mInfo.MakeGenericMethod(callType, def.MethodInfo.ReturnType.GetGenericArguments()[0]));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MethodDefinition
|
||||
{
|
||||
public string Path => ServiceType.Name + "/" + MethodInfo.Name;
|
||||
|
||||
public Type ServiceType;
|
||||
public MethodInfo MethodInfo;
|
||||
public int MethodId;
|
||||
|
||||
public Type RequestType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88e3748798a988c4482dafe8624b1400
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in a new issue