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(ref _buffer[_position]); _position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ReadUInt16Unsafe() { EnsureAvailable(2); var value = Unsafe.ReadUnaligned(ref _buffer[_position]); _position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public char ReadCharUnsafe() { EnsureAvailable(2); var value = (char)Unsafe.ReadUnaligned(ref _buffer[_position]); _position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public float ReadSingleUnsafe() { EnsureAvailable(4); var bits = Unsafe.ReadUnaligned(ref _buffer[_position]); _position += 4; return BitConverter.Int32BitsToSingle(bits); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double ReadDoubleUnsafe() { EnsureAvailable(8); var bits = Unsafe.ReadUnaligned(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(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(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(ref _buffer[_position]); var offsetMinutes = Unsafe.ReadUnaligned(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(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(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(); } EnsureAvailable(length); var result = GC.AllocateUninitializedArray(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 utf8Bytes) { return Ascii.Equals(utf8Bytes, cached); } /// /// Full-content hash for string caching. /// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names. /// See BinaryDeserializationContext for full history. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ComputeStringHashFull(ReadOnlySpan 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); } } } }