ZK_Framework/Assets/Plugins/MessagePack/Internal/AutomataDictionary.cs

502 lines
17 KiB
C#

// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
#pragma warning disable SA1509 // Opening braces should not be preceded by blank line
namespace MessagePack.Internal
{
// Key = long, Value = int for UTF8String Dictionary
/// <remarks>
/// This code is used by dynamically generated code as well as AOT generated code,
/// and thus must be public for the "C# generated and compiled into saved assembly" scenario.
/// </remarks>
public class AutomataDictionary : IEnumerable<KeyValuePair<string, int>>
{
private readonly AutomataNode root;
public AutomataDictionary()
{
this.root = new AutomataNode(0);
}
public void Add(string str, int value)
{
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(str);
AutomataNode node = this.root;
while (bytes.Length > 0)
{
var key = AutomataKeyGen.GetKey(ref bytes);
if (bytes.Length == 0)
{
node = node.Add(key, value, str);
}
else
{
node = node.Add(key);
}
}
}
public bool TryGetValue(in ReadOnlySequence<byte> bytes, out int value) => this.TryGetValue(bytes.ToArray(), out value);
public bool TryGetValue(ReadOnlySpan<byte> bytes, out int value)
{
AutomataNode node = this.root;
while (bytes.Length > 0 && node != null)
{
node = node.SearchNext(ref bytes);
}
if (node == null)
{
value = -1;
return false;
}
else
{
value = node.Value;
return true;
}
}
// for debugging
public override string ToString()
{
var sb = new StringBuilder();
ToStringCore(this.root.YieldChildren(), sb, 0);
return sb.ToString();
}
private static void ToStringCore(IEnumerable<AutomataNode> nexts, StringBuilder sb, int depth)
{
foreach (AutomataNode item in nexts)
{
if (depth != 0)
{
sb.Append(' ', depth * 2);
}
sb.Append("[" + item.Key + "]");
if (item.Value != -1)
{
sb.Append("(" + item.OriginalKey + ")");
sb.Append(" = ");
sb.Append(item.Value);
}
sb.AppendLine();
ToStringCore(item.YieldChildren(), sb, depth + 1);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
{
return YieldCore(this.root.YieldChildren()).GetEnumerator();
}
private static IEnumerable<KeyValuePair<string, int>> YieldCore(IEnumerable<AutomataNode> nexts)
{
foreach (AutomataNode item in nexts)
{
if (item.Value != -1)
{
yield return new KeyValuePair<string, int>(item.OriginalKey, item.Value);
}
foreach (KeyValuePair<string, int> x in YieldCore(item.YieldChildren()))
{
yield return x;
}
}
}
/* IL Emit */
#if !NET_STANDARD_2_0
public void EmitMatch(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action<KeyValuePair<string, int>> onFound, Action onNotFound)
{
this.root.EmitSearchNext(il, bytesSpan, key, onFound, onNotFound);
}
#endif
private class AutomataNode : IComparable<AutomataNode>
{
#pragma warning disable SA1401 // Fields should be private
internal ulong Key;
internal int Value;
internal string OriginalKey;
#pragma warning restore SA1401 // Fields should be private
private AutomataNode[] nexts;
private ulong[] nextKeys;
private int count;
public bool HasChildren
{
get { return this.count != 0; }
}
public AutomataNode(ulong key)
{
this.Key = key;
this.Value = -1;
this.nexts = Array.Empty<AutomataNode>();
this.nextKeys = Array.Empty<ulong>();
this.count = 0;
this.OriginalKey = null;
}
public AutomataNode Add(ulong key)
{
var index = Array.BinarySearch(this.nextKeys, 0, this.count, key);
if (index < 0)
{
if (this.nexts.Length == this.count)
{
Array.Resize<AutomataNode>(ref this.nexts, (this.count == 0) ? 4 : (this.count * 2));
Array.Resize<ulong>(ref this.nextKeys, (this.count == 0) ? 4 : (this.count * 2));
}
this.count++;
var nextNode = new AutomataNode(key);
this.nexts[this.count - 1] = nextNode;
this.nextKeys[this.count - 1] = key;
Array.Sort(this.nexts, 0, this.count);
Array.Sort(this.nextKeys, 0, this.count);
return nextNode;
}
else
{
return this.nexts[index];
}
}
public AutomataNode Add(ulong key, int value, string originalKey)
{
AutomataNode v = this.Add(key);
v.Value = value;
v.OriginalKey = originalKey;
return v;
}
public AutomataNode SearchNext(ref ReadOnlySpan<byte> value)
{
var key = AutomataKeyGen.GetKey(ref value);
if (this.count < 4)
{
// linear search
for (int i = 0; i < this.count; i++)
{
if (this.nextKeys[i] == key)
{
return this.nexts[i];
}
}
}
else
{
// binary search
var index = BinarySearch(this.nextKeys, 0, this.count, key);
if (index >= 0)
{
return this.nexts[index];
}
}
return null;
}
internal static int BinarySearch(ulong[] array, int index, int length, ulong value)
{
int lo = index;
int hi = index + length - 1;
while (lo <= hi)
{
int i = lo + ((hi - lo) >> 1);
var arrayValue = array[i];
int order;
if (arrayValue < value)
{
order = -1;
}
else if (arrayValue > value)
{
order = 1;
}
else
{
order = 0;
}
if (order == 0)
{
return i;
}
if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}
return ~lo;
}
public int CompareTo(AutomataNode other)
{
return this.Key.CompareTo(other.Key);
}
public IEnumerable<AutomataNode> YieldChildren()
{
for (int i = 0; i < this.count; i++)
{
yield return this.nexts[i];
}
}
#if !NET_STANDARD_2_0
// SearchNext(ref ReadOnlySpan<byte> bytes)
public void EmitSearchNext(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action<KeyValuePair<string, int>> onFound, Action onNotFound)
{
// key = AutomataKeyGen.GetKey(ref bytesSpan);
il.EmitLdloca(bytesSpan);
il.EmitCall(AutomataKeyGen.GetKeyMethod);
il.EmitStloc(key);
// match children.
EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, this.nexts, this.count);
}
private static void EmitSearchNextCore(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action<KeyValuePair<string, int>> onFound, Action onNotFound, AutomataNode[] nexts, int count)
{
if (count < 4)
{
// linear-search
AutomataNode[] valueExists = nexts.Take(count).Where(x => x.Value != -1).ToArray();
AutomataNode[] childrenExists = nexts.Take(count).Where(x => x.HasChildren).ToArray();
Label gotoSearchNext = il.DefineLabel();
Label gotoNotFound = il.DefineLabel();
{
// bytesSpan.Length
il.EmitLdloca(bytesSpan);
il.EmitCall(typeof(ReadOnlySpan<byte>).GetRuntimeProperty(nameof(ReadOnlySpan<byte>.Length)).GetMethod);
if (childrenExists.Length != 0 && valueExists.Length == 0)
{
il.Emit(OpCodes.Brfalse, gotoNotFound); // if(bytesSpan.Length == 0)
}
else
{
il.Emit(OpCodes.Brtrue, gotoSearchNext); // if(bytesSpan.Length != 0)
}
}
{
Label[] ifValueNexts = Enumerable.Range(0, Math.Max(valueExists.Length - 1, 0)).Select(_ => il.DefineLabel()).ToArray();
for (int i = 0; i < valueExists.Length; i++)
{
Label notFoundLabel = il.DefineLabel();
if (i != 0)
{
il.MarkLabel(ifValueNexts[i - 1]);
}
il.EmitLdloc(key);
il.EmitULong(valueExists[i].Key);
il.Emit(OpCodes.Bne_Un, notFoundLabel);
// found
onFound(new KeyValuePair<string, int>(valueExists[i].OriginalKey, valueExists[i].Value));
// notfound
il.MarkLabel(notFoundLabel);
if (i != valueExists.Length - 1)
{
il.Emit(OpCodes.Br, ifValueNexts[i]);
}
else
{
onNotFound();
}
}
}
il.MarkLabel(gotoSearchNext);
Label[] ifRecNext = Enumerable.Range(0, Math.Max(childrenExists.Length - 1, 0)).Select(_ => il.DefineLabel()).ToArray();
for (int i = 0; i < childrenExists.Length; i++)
{
Label notFoundLabel = il.DefineLabel();
if (i != 0)
{
il.MarkLabel(ifRecNext[i - 1]);
}
il.EmitLdloc(key);
il.EmitULong(childrenExists[i].Key);
il.Emit(OpCodes.Bne_Un, notFoundLabel);
// found
childrenExists[i].EmitSearchNext(il, bytesSpan, key, onFound, onNotFound);
// notfound
il.MarkLabel(notFoundLabel);
if (i != childrenExists.Length - 1)
{
il.Emit(OpCodes.Br, ifRecNext[i]);
}
else
{
onNotFound();
}
}
il.MarkLabel(gotoNotFound);
onNotFound();
}
else
{
// binary-search
var midline = count / 2;
var mid = nexts[midline].Key;
AutomataNode[] l = nexts.Take(count).Take(midline).ToArray();
AutomataNode[] r = nexts.Take(count).Skip(midline).ToArray();
Label gotoRight = il.DefineLabel();
// if(key < mid)
il.EmitLdloc(key);
il.EmitULong(mid);
il.Emit(OpCodes.Bge_Un, gotoRight);
EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, l, l.Length);
// else
il.MarkLabel(gotoRight);
EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, r, r.Length);
}
}
#endif
}
}
/// <remarks>
/// This is used by dynamically generated code. It can be made internal after we enable our dynamic assemblies to access internals.
/// But that trick may require net46, so maybe we should leave this as public.
/// </remarks>
public static class AutomataKeyGen
{
public static readonly MethodInfo GetKeyMethod = typeof(AutomataKeyGen).GetRuntimeMethod(nameof(GetKey), new[] { typeof(ReadOnlySpan<byte>).MakeByRefType() });
public static ulong GetKey(ref ReadOnlySpan<byte> span)
{
ulong key;
unchecked
{
if (span.Length >= 8)
{
key = SafeBitConverter.ToUInt64(span);
span = span.Slice(8);
}
else
{
switch (span.Length)
{
case 1:
{
key = span[0];
span = span.Slice(1);
break;
}
case 2:
{
key = SafeBitConverter.ToUInt16(span);
span = span.Slice(2);
break;
}
case 3:
{
var a = span[0];
var b = SafeBitConverter.ToUInt16(span.Slice(1));
key = a | (ulong)b << 8;
span = span.Slice(3);
break;
}
case 4:
{
key = SafeBitConverter.ToUInt32(span);
span = span.Slice(4);
break;
}
case 5:
{
var a = span[0];
var b = SafeBitConverter.ToUInt32(span.Slice(1));
key = a | (ulong)b << 8;
span = span.Slice(5);
break;
}
case 6:
{
ulong a = SafeBitConverter.ToUInt16(span);
ulong b = SafeBitConverter.ToUInt32(span.Slice(2));
key = a | (b << 16);
span = span.Slice(6);
break;
}
case 7:
{
var a = span[0];
var b = SafeBitConverter.ToUInt16(span.Slice(1));
var c = SafeBitConverter.ToUInt32(span.Slice(3));
key = a | (ulong)b << 8 | (ulong)c << 24;
span = span.Slice(7);
break;
}
default:
throw new MessagePackSerializationException("Not Supported Length");
}
}
return key;
}
}
}
}