AyCode.Core/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Binary...

378 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
internal sealed partial class BinaryDeserializationContext
{
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
#region Buffer State owned by context for zero virtual dispatch
internal byte[] _buffer = null!;
internal int _bufferLength;
internal int _position;
#endregion
// String caching state — needed for WASM optimization
// The cache dictionary is owned by context (pooled), passed in at init time.
private bool _useStringCaching;
private int _maxCachedStringLength;
public bool IsAtEnd
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _position >= _bufferLength;
}
public int Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _position;
}
#region Core Read Methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
if (_position >= _bufferLength)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position++];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte PeekByte()
{
if (_position >= _bufferLength)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
EnsureAvailable(count);
_position += count;
}
#endregion
#region Fixed-Width Reads
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadInt16Unsafe()
{
EnsureAvailable(2);
var value = Unsafe.ReadUnaligned<short>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUInt16Unsafe()
{
EnsureAvailable(2);
var value = Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char ReadCharUnsafe()
{
EnsureAvailable(2);
var value = (char)Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadSingleUnsafe()
{
EnsureAvailable(4);
var bits = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
_position += 4;
return BitConverter.Int32BitsToSingle(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDoubleUnsafe()
{
EnsureAvailable(8);
var bits = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
_position += 8;
return BitConverter.Int64BitsToDouble(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public decimal ReadDecimalUnsafe()
{
EnsureAvailable(16);
var span = _buffer.AsSpan(_position, 16);
var ints = MemoryMarshal.Cast<byte, int>(span);
var lo = ints[0];
var mid = ints[1];
var hi = ints[2];
var flags = ints[3];
var isNegative = (flags & unchecked((int)0x80000000)) != 0;
var scale = (byte)((flags >> 16) & 0x7F);
_position += 16;
return new decimal(lo, mid, hi, isNegative, scale);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTime ReadDateTimeUnsafe()
{
EnsureAvailable(9);
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
var kind = (DateTimeKind)_buffer[_position + 8];
_position += 9;
return new DateTime(ticks, kind);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTimeOffset ReadDateTimeOffsetUnsafe()
{
EnsureAvailable(10);
var utcTicks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
var offsetMinutes = Unsafe.ReadUnaligned<short>(ref _buffer[_position + 8]);
_position += 10;
var utcValue = new DateTime(utcTicks, DateTimeKind.Utc);
return new DateTimeOffset(utcValue).ToOffset(TimeSpan.FromMinutes(offsetMinutes));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TimeSpan ReadTimeSpanUnsafe()
{
EnsureAvailable(8);
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
_position += 8;
return new TimeSpan(ticks);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Guid ReadGuidUnsafe()
{
EnsureAvailable(16);
var value = new Guid(_buffer.AsSpan(_position, 16));
_position += 16;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32Raw()
{
EnsureAvailable(4);
var value = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
_position += 4;
return value;
}
#endregion
#region VarInt Reading
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadVarInt()
{
var raw = ReadVarUInt();
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadVarUInt()
{
// Fast path: single byte (0-127) - ~70% of cases
var b0 = _buffer[_position];
if ((b0 & 0x80) == 0)
{
_position++;
return b0;
}
// Fast path: two bytes (128-16383) - ~25% of cases
if (_position + 1 < _bufferLength)
{
var b1 = _buffer[_position + 1];
if ((b1 & 0x80) == 0)
{
_position += 2;
return (uint)(b0 & 0x7F) | ((uint)b1 << 7);
}
}
// Slow path: 3+ bytes - ~5% of cases
return ReadVarUIntSlow();
}
private uint ReadVarUIntSlow()
{
uint value = 0;
var shift = 0;
while (true)
{
var b = ReadByte();
value |= (uint)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 35)
{
throw new AcBinaryDeserializationException("Invalid VarUInt encoding.", _position);
}
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadVarLong()
{
var raw = ReadVarULong();
var value = (long)(raw >> 1) ^ -((long)raw & 1);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadVarULong()
{
ulong value = 0;
var shift = 0;
while (true)
{
var b = ReadByte();
value |= (ulong)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 70)
{
throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position);
}
}
return value;
}
#endregion
#region Bytes & String Reading
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte[] ReadBytes(int length)
{
if (length == 0)
{
return Array.Empty<byte>();
}
EnsureAvailable(length);
var result = GC.AllocateUninitializedArray<byte>(length);
_buffer.AsSpan(_position, length).CopyTo(result);
_position += length;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadStringUtf8(int length)
{
if (length == 0)
{
return string.Empty;
}
EnsureAvailable(length);
// WASM optimization: cache short strings to reduce allocations
if (_useStringCaching && length <= _maxCachedStringLength)
{
return ReadStringUtf8Cached(length);
}
var value = Utf8NoBom.GetString(_buffer, _position, length);
_position += length;
return value;
}
private string ReadStringUtf8Cached(int length)
{
var slice = _buffer.AsSpan(_position, length);
var hash = ComputeStringHashFull(slice);
if (_stringCache!.TryGetValue(hash, out var cached))
{
if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice))
{
_position += length;
return cached;
}
}
var value = Utf8NoBom.GetString(slice);
_stringCache[hash] = value;
_position += length;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
{
return Ascii.Equals(utf8Bytes, cached);
}
/// <summary>
/// Full-content hash for string caching.
/// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names.
/// See BinaryDeserializationContext for full history.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ComputeStringHashFull(ReadOnlySpan<byte> data)
{
if (data.Length <= 32)
{
var hash = new HashCode();
hash.AddBytes(data);
return hash.ToHashCode();
}
var h = new HashCode();
h.Add(data.Length);
h.AddBytes(data.Slice(0, 8));
h.AddBytes(data.Slice(data.Length - 8, 8));
h.AddBytes(data.Slice(data.Length / 2 - 4, 8));
return h.ToHashCode();
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureAvailable(int length)
{
if (_position > _bufferLength - length)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
}
}
}