using MessagePack; using System.Buffers; using MessagePack.Formatters; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MagicOnion { public class UnsafeDirectBlitResolver : IFormatterResolver { public static readonly UnsafeDirectBlitResolver Instance = new UnsafeDirectBlitResolver(); bool isFreezed = false; Dictionary formatters = new Dictionary(); UnsafeDirectBlitResolver() { } public void Register() where T : unmanaged { if (isFreezed) { throw new InvalidOperationException("Register must call on startup(before use GetFormatter)."); } formatters.Add(typeof(T), new UnsafeDirectBlitFormatter()); formatters.Add(typeof(T[]), new UnsafeDirectBlitArrayFormatter()); } public IMessagePackFormatter GetFormatter() { return FormatterCache.formatter; } static class FormatterCache { public static readonly IMessagePackFormatter formatter; static FormatterCache() { Instance.isFreezed = true; var t = typeof(T); object formatterObject; if (Instance.formatters.TryGetValue(t, out formatterObject)) { formatter = (IMessagePackFormatter)formatterObject; } } } } public class UnsafeDirectBlitArrayFormatter : IMessagePackFormatter where T : unmanaged { const int TypeCode = 45; // use ext instead of ArrayFormatter for extremely boostup performance. // Layout: [extHeader, isLittlEendian(bool), bytes()] readonly int StructLength; public UnsafeDirectBlitArrayFormatter() { this.StructLength = Unsafe.SizeOf(); } public void Serialize(ref MessagePackWriter writer, T[] value, MessagePackSerializerOptions options) { if (value == null) { writer.WriteNil(); return; } var byteLen = value.Length * StructLength; writer.WriteExtensionFormatHeader(new ExtensionHeader(TypeCode, byteLen + 1)); writer.Write(BitConverter.IsLittleEndian); /* Span span = writer.GetSpan(); Unsafe.CopyBlockUnaligned(ref span[0], ref Unsafe.As(ref value[0]), (uint)byteLen); writer.Advance(byteLen); */ // currently can not write directly, wait msgpack update. var span = System.Buffers.ArrayPool.Shared.Rent(byteLen); try { Unsafe.CopyBlockUnaligned(ref span[0], ref Unsafe.As(ref value[0]), (uint)byteLen); writer.WriteRaw(span.AsSpan().Slice(0, byteLen)); } finally { System.Buffers.ArrayPool.Shared.Return(span); } } public T[] Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { if (reader.IsNil) { return null; } var ext = reader.ReadExtensionFormat(); if (ext.Header.TypeCode != TypeCode) throw new InvalidOperationException("Invalid typeCode."); var extReader = new MessagePackReader(ext.Data); var isLittleEndian = extReader.ReadBoolean(); // extReader.read byte[] rentMemory = default; ReadOnlySpan span = default; { var seqSlice = extReader.Sequence.Slice(extReader.Position); if (isLittleEndian != BitConverter.IsLittleEndian) { rentMemory = ArrayPool.Shared.Rent((int)seqSlice.Length); seqSlice.CopyTo(rentMemory); Array.Reverse(rentMemory); span = rentMemory; } else { if (seqSlice.IsSingleSegment) { span = seqSlice.First.Span; } else { rentMemory = ArrayPool.Shared.Rent((int)seqSlice.Length); seqSlice.CopyTo(rentMemory); span = rentMemory; } } } var result = new T[span.Length / StructLength]; Unsafe.CopyBlockUnaligned(ref Unsafe.As(ref result[0]), ref MemoryMarshal.GetReference(span), (uint)span.Length); return result; } } public class UnsafeDirectBlitFormatter : IMessagePackFormatter where T : unmanaged { readonly int size; public UnsafeDirectBlitFormatter() { // Note: check is T blittable? this.size = Unsafe.SizeOf(); } public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) { // fix to v2.0-stable byte[] rentMemory = ArrayPool.Shared.Rent(size); try { var span = rentMemory.AsSpan().Slice(size); Unsafe.WriteUnaligned(ref span[0], value); writer.Write(span); } finally { ArrayPool.Shared.Return(rentMemory); } } public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var segment = reader.ReadBytes(); if (segment == null) { throw new InvalidOperationException("data is null"); } ValidateRead(segment.Value.Length); if (segment.Value.IsSingleSegment) { var firstMemory = segment.Value.First; return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(firstMemory.Span)); } else { var memory = segment.Value.ToArray(); return Unsafe.ReadUnaligned(ref memory[0]); } } void ValidateRead(long length) { if (length < size) { throw new InvalidOperationException("Overflow"); } } } }