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
100
Assets/Scripts/MagicOnion/Utils/ArrayPoolBufferWriter.cs
Normal file
100
Assets/Scripts/MagicOnion/Utils/ArrayPoolBufferWriter.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
internal sealed class ArrayPoolBufferWriter : IBufferWriter<byte>, IDisposable
|
||||
{
|
||||
[ThreadStatic]
|
||||
static ArrayPoolBufferWriter staticInstance;
|
||||
|
||||
public static ArrayPoolBufferWriter RentThreadStaticWriter()
|
||||
{
|
||||
if (staticInstance == null)
|
||||
{
|
||||
staticInstance = new ArrayPoolBufferWriter();
|
||||
}
|
||||
staticInstance.Prepare();
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
const int MinimumBufferSize = 32767; // use 32k buffer.
|
||||
|
||||
byte[] buffer;
|
||||
int index;
|
||||
|
||||
void Prepare()
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = ArrayPool<byte>.Shared.Rent(MinimumBufferSize);
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<byte> WrittenMemory => buffer.AsMemory(0, index);
|
||||
public ReadOnlySpan<byte> WrittenSpan => buffer.AsSpan(0, index);
|
||||
|
||||
public int WrittenCount => index;
|
||||
|
||||
public int Capacity => buffer.Length;
|
||||
|
||||
public int FreeCapacity => buffer.Length - index;
|
||||
|
||||
public void Advance(int count)
|
||||
{
|
||||
if (count < 0) throw new ArgumentException(nameof(count));
|
||||
index += count;
|
||||
}
|
||||
|
||||
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
CheckAndResizeBuffer(sizeHint);
|
||||
return buffer.AsMemory(index);
|
||||
}
|
||||
|
||||
public Span<byte> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
CheckAndResizeBuffer(sizeHint);
|
||||
return buffer.AsSpan(index);
|
||||
}
|
||||
|
||||
void CheckAndResizeBuffer(int sizeHint)
|
||||
{
|
||||
if (sizeHint < 0) throw new ArgumentException(nameof(sizeHint));
|
||||
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
sizeHint = MinimumBufferSize;
|
||||
}
|
||||
|
||||
int availableSpace = buffer.Length - index;
|
||||
|
||||
if (sizeHint > availableSpace)
|
||||
{
|
||||
int growBy = Math.Max(sizeHint, buffer.Length);
|
||||
|
||||
int newSize = checked(buffer.Length + growBy);
|
||||
|
||||
byte[] oldBuffer = buffer;
|
||||
|
||||
buffer = ArrayPool<byte>.Shared.Rent(newSize);
|
||||
|
||||
Span<byte> previousBuffer = oldBuffer.AsSpan(0, index);
|
||||
previousBuffer.CopyTo(buffer);
|
||||
ArrayPool<byte>.Shared.Return(oldBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c3adb89f0d51ea448be279645a9c04c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
109
Assets/Scripts/MagicOnion/Utils/AsyncLocalShim.cs
Normal file
109
Assets/Scripts/MagicOnion/Utils/AsyncLocalShim.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#if net45
|
||||
using System.Collections;
|
||||
using System.Runtime.Remoting;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace System.Threading
|
||||
{
|
||||
/// <summary>
|
||||
/// Polyfill for AsyncLocal functionality added in NETSTANDARD1.3 and .NET 4.6
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// The type of the value to encapsulate and follow.
|
||||
/// </typeparam>
|
||||
internal class AsyncLocal<T>
|
||||
{
|
||||
private readonly string _name = Guid.NewGuid().ToString("N");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the value to flow with <see cref="ExecutionContext"/>.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CallContext.LogicalGetData(_name) is ObjectHandle slot)
|
||||
return (T)slot.Unwrap();
|
||||
return default(T);
|
||||
}
|
||||
set
|
||||
{
|
||||
// Mimic the implementation of AsyncLocal<T>
|
||||
var executionContext = Thread.CurrentThread.GetMutableExecutionContext();
|
||||
var logicalCallContext = executionContext.GetLogicalCallContext();
|
||||
var datastore = logicalCallContext.GetDatastore();
|
||||
var datastoreCopy = datastore == null ? new Hashtable() : new Hashtable(datastore);
|
||||
var slot = new ObjectHandle(value);
|
||||
datastoreCopy[_name] = slot;
|
||||
logicalCallContext.SetDatastore(datastoreCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ThreadExtensions
|
||||
{
|
||||
private static readonly Func<Thread, ExecutionContext> _getMutableExecutionContextFunc;
|
||||
|
||||
static ThreadExtensions()
|
||||
{
|
||||
var getMutableExecutionContextMethodInfo = typeof(Thread).GetMethod("GetMutableExecutionContext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var instanceParameterExpression = Expression.Parameter(typeof(Thread));
|
||||
var functionCallExpression = Expression.Call(instanceParameterExpression, getMutableExecutionContextMethodInfo);
|
||||
var lambdaExpression = Expression.Lambda<Func<Thread, ExecutionContext>>(functionCallExpression, instanceParameterExpression);
|
||||
_getMutableExecutionContextFunc = lambdaExpression.Compile();
|
||||
}
|
||||
|
||||
private static MethodInfo GetMutableExecutionContextMethodInfo =
|
||||
typeof(Thread).GetMethod("GetMutableExecutionContext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
public static ExecutionContext GetMutableExecutionContext(this Thread thread)
|
||||
=> _getMutableExecutionContextFunc(thread);
|
||||
}
|
||||
|
||||
internal static class LogicalCallContextExtensions
|
||||
{
|
||||
private static readonly Func<LogicalCallContext, Hashtable> _getDatastoreFunc;
|
||||
private static readonly Func<LogicalCallContext, Hashtable, bool> _setDatastoreFunc;
|
||||
|
||||
static LogicalCallContextExtensions()
|
||||
{
|
||||
var datastoreFieldInfo = typeof(LogicalCallContext).GetField("m_Datastore", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var instanceParameterExpression = Expression.Parameter(typeof(LogicalCallContext));
|
||||
var memberAccessExpression = Expression.MakeMemberAccess(instanceParameterExpression, datastoreFieldInfo);
|
||||
var getLambdaExpression = Expression.Lambda<Func<LogicalCallContext, Hashtable>>(memberAccessExpression, instanceParameterExpression);
|
||||
_getDatastoreFunc = getLambdaExpression.Compile();
|
||||
var valueParameterExpression = Expression.Parameter(typeof(Hashtable));
|
||||
var assignmentExpression = Expression.Assign(memberAccessExpression, valueParameterExpression);
|
||||
var setFunctionBody = Expression.Block(assignmentExpression, Expression.Constant(true));
|
||||
var setLambdaExpression = Expression.Lambda<Func<LogicalCallContext, Hashtable, bool>>(setFunctionBody, instanceParameterExpression, valueParameterExpression);
|
||||
_setDatastoreFunc = setLambdaExpression.Compile();
|
||||
}
|
||||
|
||||
public static Hashtable GetDatastore(this LogicalCallContext context)
|
||||
=> _getDatastoreFunc(context);
|
||||
|
||||
public static void SetDatastore(this LogicalCallContext context, Hashtable datastore)
|
||||
=> _setDatastoreFunc(context, datastore);
|
||||
}
|
||||
|
||||
internal static class ExecutionContextExtensions
|
||||
{
|
||||
private static readonly Func<ExecutionContext, LogicalCallContext> _getLogicalCallContextFunc;
|
||||
|
||||
static ExecutionContextExtensions()
|
||||
{
|
||||
var logicalCallContextPropertyInfo = typeof(ExecutionContext).GetProperty("LogicalCallContext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var instanceParameterExpression = Expression.Parameter(typeof(ExecutionContext));
|
||||
var memberAccessExpression = Expression.MakeMemberAccess(instanceParameterExpression, logicalCallContextPropertyInfo);
|
||||
var lambdaExpression = Expression.Lambda<Func<ExecutionContext, LogicalCallContext>>(memberAccessExpression, instanceParameterExpression);
|
||||
_getLogicalCallContextFunc = lambdaExpression.Compile();
|
||||
}
|
||||
|
||||
public static LogicalCallContext GetLogicalCallContext(this ExecutionContext executionContext)
|
||||
=> _getLogicalCallContextFunc(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Scripts/MagicOnion/Utils/AsyncLocalShim.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/AsyncLocalShim.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dca9e0a0d2088cd47b918c6f54739b95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
Assets/Scripts/MagicOnion/Utils/DynamicAssembly.cs
Normal file
98
Assets/Scripts/MagicOnion/Utils/DynamicAssembly.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#if NON_UNITY || !NET_STANDARD_2_0
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class DynamicAssembly
|
||||
{
|
||||
readonly object gate = new object();
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
readonly string moduleName;
|
||||
#endif
|
||||
readonly AssemblyBuilder assemblyBuilder;
|
||||
readonly ModuleBuilder moduleBuilder;
|
||||
|
||||
// don't expose ModuleBuilder(should use lock)
|
||||
// public ModuleBuilder ModuleBuilder { get { return moduleBuilder; } }
|
||||
|
||||
public DynamicAssembly(string moduleName)
|
||||
{
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
this.moduleName = moduleName;
|
||||
this.assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(moduleName), AssemblyBuilderAccess.RunAndSave);
|
||||
this.moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName, moduleName + ".dll");
|
||||
#else
|
||||
this.assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(moduleName), AssemblyBuilderAccess.Run);
|
||||
this.moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName);
|
||||
#endif
|
||||
|
||||
// HACK: Allow access to `internal` classes from dynamically generated assembly.
|
||||
// https://www.strathweb.com/2018/10/no-internalvisibleto-no-problem-bypassing-c-visibility-rules-with-roslyn/
|
||||
var ignoreAccessChecksTo = new CustomAttributeBuilder(typeof(IgnoresAccessChecksToAttribute).GetConstructor(new[] {typeof(string)}), new []{ "MagicOnion" });
|
||||
this.assemblyBuilder.SetCustomAttribute(ignoreAccessChecksTo);
|
||||
}
|
||||
|
||||
// requires lock on mono environment(for example, UnityEditor). see: https://github.com/neuecc/MessagePack-CSharp/issues/161
|
||||
|
||||
public TypeBuilder DefineType(string name, TypeAttributes attr)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return moduleBuilder.DefineType(name, attr);
|
||||
}
|
||||
}
|
||||
|
||||
public TypeBuilder DefineType(string name, TypeAttributes attr, Type parent)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return moduleBuilder.DefineType(name, attr, parent);
|
||||
}
|
||||
}
|
||||
|
||||
public TypeBuilder DefineType(string name, TypeAttributes attr, Type parent, Type[] interfaces)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return moduleBuilder.DefineType(name, attr, parent, interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SAVE_ASSEMBLY
|
||||
|
||||
public AssemblyBuilder Save()
|
||||
{
|
||||
assemblyBuilder.Save(moduleName + ".dll");
|
||||
return assemblyBuilder;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
internal class IgnoresAccessChecksToAttribute : Attribute
|
||||
{
|
||||
public IgnoresAccessChecksToAttribute(string assemblyName)
|
||||
{
|
||||
AssemblyName = assemblyName;
|
||||
}
|
||||
|
||||
public string AssemblyName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Scripts/MagicOnion/Utils/DynamicAssembly.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/DynamicAssembly.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9d0b2ee1dc31faa43811b3a203a3fc16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/Scripts/MagicOnion/Utils/FNV1A32.cs
Normal file
27
Assets/Scripts/MagicOnion/Utils/FNV1A32.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.Text;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
public static class FNV1A32
|
||||
{
|
||||
public static int GetHashCode(string str)
|
||||
{
|
||||
return GetHashCode(Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
public static int GetHashCode(byte[] obj)
|
||||
{
|
||||
uint hash = 0;
|
||||
if (obj != null)
|
||||
{
|
||||
hash = 2166136261;
|
||||
for (int i = 0; i < obj.Length; i++)
|
||||
{
|
||||
hash = unchecked((obj[i] ^ hash) * 16777619);
|
||||
}
|
||||
}
|
||||
|
||||
return unchecked((int)hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Utils/FNV1A32.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/FNV1A32.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4257d32e13eee874493c07bf4d4bfc8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
170
Assets/Scripts/MagicOnion/Utils/ILGeneratorExtensions.cs
Normal file
170
Assets/Scripts/MagicOnion/Utils/ILGeneratorExtensions.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#if NON_UNITY || !NET_STANDARD_2_0
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
// full list of can create optimize helper -> https://github.com/kevin-montrose/Sigil#automated-opcode-choice
|
||||
|
||||
/// <summary>
|
||||
/// Provides optimized generation code and helpers.
|
||||
/// </summary>
|
||||
internal static class ILGeneratorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the local variable at a specific index onto the evaluation stack.
|
||||
/// </summary>
|
||||
public static void EmitLdloc(this ILGenerator il, int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
break;
|
||||
case 1:
|
||||
il.Emit(OpCodes.Ldloc_1);
|
||||
break;
|
||||
case 2:
|
||||
il.Emit(OpCodes.Ldloc_2);
|
||||
break;
|
||||
case 3:
|
||||
il.Emit(OpCodes.Ldloc_3);
|
||||
break;
|
||||
default:
|
||||
if (index <= 255)
|
||||
{
|
||||
il.Emit(OpCodes.Ldloc_S, (byte)index);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldloc, (short)index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops the current value from the top of the evaluation stack and stores it in a the local variable list at a specified index.
|
||||
/// </summary>
|
||||
public static void EmitStloc(this ILGenerator il, int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
break;
|
||||
case 1:
|
||||
il.Emit(OpCodes.Stloc_1);
|
||||
break;
|
||||
case 2:
|
||||
il.Emit(OpCodes.Stloc_2);
|
||||
break;
|
||||
case 3:
|
||||
il.Emit(OpCodes.Stloc_3);
|
||||
break;
|
||||
default:
|
||||
if (index <= 255)
|
||||
{
|
||||
il.Emit(OpCodes.Stloc_S, (byte)index);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Stloc, (short)index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the address of the local variable at a specific index onto the evaluation statck.
|
||||
/// </summary>
|
||||
public static void EmitLdloca(this ILGenerator il, int index)
|
||||
{
|
||||
if (index <= 255)
|
||||
{
|
||||
il.Emit(OpCodes.Ldloca_S, (byte)index);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldloca, (short)index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a supplied value of type int32 onto the evaluation stack as an int32.
|
||||
/// </summary>
|
||||
public static void EmitLdc_I4(this ILGenerator il, int value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case -1:
|
||||
il.Emit(OpCodes.Ldc_I4_M1);
|
||||
break;
|
||||
case 0:
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
break;
|
||||
case 1:
|
||||
il.Emit(OpCodes.Ldc_I4_1);
|
||||
break;
|
||||
case 2:
|
||||
il.Emit(OpCodes.Ldc_I4_2);
|
||||
break;
|
||||
case 3:
|
||||
il.Emit(OpCodes.Ldc_I4_3);
|
||||
break;
|
||||
case 4:
|
||||
il.Emit(OpCodes.Ldc_I4_4);
|
||||
break;
|
||||
case 5:
|
||||
il.Emit(OpCodes.Ldc_I4_5);
|
||||
break;
|
||||
case 6:
|
||||
il.Emit(OpCodes.Ldc_I4_6);
|
||||
break;
|
||||
case 7:
|
||||
il.Emit(OpCodes.Ldc_I4_7);
|
||||
break;
|
||||
case 8:
|
||||
il.Emit(OpCodes.Ldc_I4_8);
|
||||
break;
|
||||
default:
|
||||
if (value >= -128 && value <= 127)
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I4, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for Pop op.
|
||||
/// </summary>
|
||||
public static void EmitPop(this ILGenerator il, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
il.Emit(OpCodes.Pop);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitNullReturn(this ILGenerator il)
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
public static void EmitThrowNotimplemented(this ILGenerator il)
|
||||
{
|
||||
il.Emit(OpCodes.Newobj, typeof(System.NotImplementedException).GetConstructor(Type.EmptyTypes));
|
||||
il.Emit(OpCodes.Throw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8d05c89a473ec164b8dc9a918d5c57d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/Scripts/MagicOnion/Utils/ReflectionExtensions.cs
Normal file
20
Assets/Scripts/MagicOnion/Utils/ReflectionExtensions.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
// Internal, Global.
|
||||
internal static class ReflectionExtensions
|
||||
{
|
||||
public static bool IsNullable(this System.Reflection.TypeInfo type)
|
||||
{
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Nullable<>);
|
||||
}
|
||||
|
||||
public static bool IsPublic(this System.Reflection.TypeInfo type)
|
||||
{
|
||||
return type.IsPublic;
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Utils/ReflectionExtensions.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/ReflectionExtensions.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 251abd13d00009a44979847381ea168f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
185
Assets/Scripts/MagicOnion/Utils/ReservedWhenAllPromise.cs
Normal file
185
Assets/Scripts/MagicOnion/Utils/ReservedWhenAllPromise.cs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#if NON_UNITY
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
public class ReservedWhenAllPromise : IValueTaskSource
|
||||
{
|
||||
static class ContinuationSentinel
|
||||
{
|
||||
public static readonly Action<object> AvailableContinuation = _ => { };
|
||||
public static readonly Action<object> CompletedContinuation = _ => { };
|
||||
}
|
||||
|
||||
static readonly ContextCallback execContextCallback = ExecutionContextCallback;
|
||||
static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback;
|
||||
|
||||
int completedCount;
|
||||
readonly int resultCount;
|
||||
|
||||
ExceptionDispatchInfo exception;
|
||||
Action<object> continuation = ContinuationSentinel.AvailableContinuation;
|
||||
object state;
|
||||
SynchronizationContext syncContext;
|
||||
ExecutionContext execContext;
|
||||
|
||||
public ReservedWhenAllPromise(int reserveCount)
|
||||
{
|
||||
this.resultCount = reserveCount;
|
||||
}
|
||||
|
||||
public ValueTask AsValueTask() => new ValueTask(this, 0);
|
||||
|
||||
public void Add(ValueTask task)
|
||||
{
|
||||
var awaiter = task.GetAwaiter();
|
||||
if (awaiter.IsCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
awaiter.GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
return;
|
||||
}
|
||||
TryInvokeContinuationWithIncrement();
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterUnsafeOnCompleted(awaiter);
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterUnsafeOnCompleted(ValueTaskAwaiter awaiter)
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(() => ContinuationWithCapture(awaiter));
|
||||
}
|
||||
|
||||
void ContinuationWithCapture(ValueTaskAwaiter awaiter)
|
||||
{
|
||||
try
|
||||
{
|
||||
awaiter.GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
TryInvokeContinuation();
|
||||
return;
|
||||
}
|
||||
TryInvokeContinuationWithIncrement();
|
||||
}
|
||||
|
||||
void TryInvokeContinuationWithIncrement()
|
||||
{
|
||||
if (Interlocked.Increment(ref completedCount) == resultCount)
|
||||
{
|
||||
TryInvokeContinuation();
|
||||
}
|
||||
}
|
||||
|
||||
void TryInvokeContinuation()
|
||||
{
|
||||
var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation);
|
||||
if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation)
|
||||
{
|
||||
var spinWait = new SpinWait();
|
||||
while (state == null) // worst case, state is not set yet so wait.
|
||||
{
|
||||
spinWait.SpinOnce();
|
||||
}
|
||||
|
||||
if (execContext != null)
|
||||
{
|
||||
ExecutionContext.Run(execContext, execContextCallback, Tuple.Create(c, this));
|
||||
}
|
||||
else if (syncContext != null)
|
||||
{
|
||||
syncContext.Post(syncContextCallback, Tuple.Create(c, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
c(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GetResult(short token)
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
exception.Throw();
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTaskSourceStatus GetStatus(short token)
|
||||
{
|
||||
return (completedCount == resultCount) ? ValueTaskSourceStatus.Succeeded
|
||||
: (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted)
|
||||
: ValueTaskSourceStatus.Pending;
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation) != ContinuationSentinel.AvailableContinuation)
|
||||
{
|
||||
if (this.continuation == ContinuationSentinel.CompletedContinuation)
|
||||
{
|
||||
continuation(state);
|
||||
return;
|
||||
}
|
||||
throw new InvalidOperationException("does not allow multiple await.");
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext)
|
||||
{
|
||||
execContext = ExecutionContext.Capture();
|
||||
}
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext)
|
||||
{
|
||||
syncContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
if (GetStatus(token) != ValueTaskSourceStatus.Pending)
|
||||
{
|
||||
TryInvokeContinuation();
|
||||
}
|
||||
}
|
||||
|
||||
static void ExecutionContextCallback(object state)
|
||||
{
|
||||
var t = (Tuple<Action<object>, ReservedWhenAllPromise>)state;
|
||||
var self = t.Item2;
|
||||
if (self.syncContext != null)
|
||||
{
|
||||
SynchronizationContextCallback(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
var invokeState = self.state;
|
||||
self.state = null;
|
||||
t.Item1.Invoke(invokeState);
|
||||
}
|
||||
}
|
||||
|
||||
static void SynchronizationContextCallback(object state)
|
||||
{
|
||||
var t = (Tuple<Action<object>, ReservedWhenAllPromise>)state;
|
||||
var self = t.Item2;
|
||||
var invokeState = self.state;
|
||||
self.state = null;
|
||||
t.Item1.Invoke(invokeState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9446850abcb2fa44e9499655da1e0cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Scripts/MagicOnion/Utils/TaskCompletionSourceEx.cs
Normal file
24
Assets/Scripts/MagicOnion/Utils/TaskCompletionSourceEx.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
internal interface ITaskCompletion
|
||||
{
|
||||
bool TrySetException(Exception ex);
|
||||
bool TrySetCanceled();
|
||||
}
|
||||
|
||||
internal class TaskCompletionSourceEx<T> : TaskCompletionSource<T>, ITaskCompletion
|
||||
{
|
||||
bool ITaskCompletion.TrySetCanceled()
|
||||
{
|
||||
return this.TrySetCanceled();
|
||||
}
|
||||
|
||||
bool ITaskCompletion.TrySetException(Exception ex)
|
||||
{
|
||||
return this.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1035ff4255061dd45be4535ae9d8931f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
15
Assets/Scripts/MagicOnion/Utils/TaskEx.cs
Normal file
15
Assets/Scripts/MagicOnion/Utils/TaskEx.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace MagicOnion
|
||||
{
|
||||
internal static class TaskEx
|
||||
{
|
||||
public static readonly Task CompletedTask =
|
||||
#if net45
|
||||
Task.FromResult(0);
|
||||
#else
|
||||
Task.CompletedTask;
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/MagicOnion/Utils/TaskEx.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/TaskEx.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 536fcbe91a4c2dc4a81b84a43e4b3bab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
Assets/Scripts/MagicOnion/Utils/UniqueHashDictionary.cs
Normal file
158
Assets/Scripts/MagicOnion/Utils/UniqueHashDictionary.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#if NON_UNITY
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MagicOnion.Utils
|
||||
{
|
||||
public class DuplicateKeyException : Exception
|
||||
{
|
||||
public int HashCode { get; }
|
||||
|
||||
public DuplicateKeyException(int hashCode)
|
||||
: base("Duplicated. HashCode:" + hashCode)
|
||||
{
|
||||
HashCode = hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
< 10000 String.GetHashCode is almost unique.
|
||||
For example, Class/Methods lookup can avoid raw string lookup.
|
||||
*/
|
||||
public class UniqueHashDictionary<T>
|
||||
{
|
||||
(int hash, T value)[][] table;
|
||||
int indexFor;
|
||||
int count;
|
||||
int maxConflict; // debugging for perf.
|
||||
|
||||
public int Count => count;
|
||||
public int MaxConflict => maxConflict;
|
||||
|
||||
public UniqueHashDictionary(params (int hashCode, T value)[] values)
|
||||
{
|
||||
CreateTable(values);
|
||||
}
|
||||
|
||||
void CreateTable((int hashCode, T value)[] values)
|
||||
{
|
||||
var capacity = CalculateCapacity(values.Length, 0.72f);
|
||||
|
||||
table = new (int, T)[capacity][];
|
||||
indexFor = table.Length - 1;
|
||||
count = values.Length;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
ref var v = ref values[i];
|
||||
|
||||
var index = v.hashCode & indexFor;
|
||||
if (table[index] == null)
|
||||
{
|
||||
// first case.
|
||||
table[index] = new (int, T)[1] { v };
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var t = ref table[index];
|
||||
|
||||
// check duplicate
|
||||
foreach (var item in t)
|
||||
{
|
||||
if (item.hash == v.hashCode)
|
||||
{
|
||||
throw new DuplicateKeyException(v.hashCode);
|
||||
}
|
||||
}
|
||||
|
||||
// add last
|
||||
Array.Resize(ref t, t.Length + 1);
|
||||
t[t.Length - 1] = v;
|
||||
|
||||
maxConflict = Math.Max(maxConflict, t.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(int hashCode, out T value)
|
||||
{
|
||||
ref var array = ref table[hashCode & indexFor];
|
||||
if (array == null)
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var v = ref array[0];
|
||||
if (v.hash == hashCode)
|
||||
{
|
||||
value = v.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryGetValueSlow(ref array, hashCode, out value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
bool TryGetValueSlow(ref (int, T)[] array, int hashCode, out T value)
|
||||
{
|
||||
for (int i = 1; i < array.Length; i++) // 0 is already checked.
|
||||
{
|
||||
ref var v = ref array[i];
|
||||
if (v.Item1 == hashCode)
|
||||
{
|
||||
value = v.Item2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<T> AllValues()
|
||||
{
|
||||
foreach (var item in table)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
foreach (var item2 in item)
|
||||
{
|
||||
yield return item2.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CalculateCapacity(int collectionSize, float loadFactor)
|
||||
{
|
||||
var size = (int)(((float)collectionSize) / loadFactor);
|
||||
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
size += 1;
|
||||
|
||||
// specialize adjust
|
||||
if (size < 128)
|
||||
{
|
||||
size = 128;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Count:{Count} MaxConflict:{MaxConflict}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Scripts/MagicOnion/Utils/UniqueHashDictionary.cs.meta
Normal file
11
Assets/Scripts/MagicOnion/Utils/UniqueHashDictionary.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea92a6bb508a8d744897743fe5741f4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in a new issue