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:
hyperion51 2020-02-26 17:48:40 +00:00
commit a0c8dbee72
493 changed files with 57790 additions and 3 deletions

View 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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c3adb89f0d51ea448be279645a9c04c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dca9e0a0d2088cd47b918c6f54739b95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d0b2ee1dc31faa43811b3a203a3fc16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4257d32e13eee874493c07bf4d4bfc8f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d05c89a473ec164b8dc9a918d5c57d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 251abd13d00009a44979847381ea168f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9446850abcb2fa44e9499655da1e0cbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1035ff4255061dd45be4535ae9d8931f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 536fcbe91a4c2dc4a81b84a43e4b3bab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea92a6bb508a8d744897743fe5741f4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: