diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs new file mode 100644 index 0000000..802dd2b --- /dev/null +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs @@ -0,0 +1,377 @@ +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); + } + } + } +} diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs index a2c3636..023e7d2 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs @@ -1,87 +1,161 @@ using System; -using System.Buffers.Binary; +using System.Buffers; +using System.Collections.Concurrent; 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 { /// - /// Binary deserialization context. Public for generated serializers. - /// Uses composition with pooled BinaryDeserializationContextClass for zero-alloc deserialization. - /// String cache and intern cache index are delegated to ContextClass for pool reuse. + /// Pool for BinaryDeserializationContext instances. + /// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern. /// - internal ref struct BinaryDeserializationContext + private static class DeserializationContextPool { - private readonly ReadOnlySpan _buffer; - private int _position; + private static readonly ConcurrentQueue Pool = new(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BinaryDeserializationContext Get(AcBinarySerializerOptions options) + { + if (Pool.TryDequeue(out var ctx)) + { + ctx.Reset(options); + return ctx; + } + + var newCtx = new BinaryDeserializationContext(); + newCtx.Reset(options); + return newCtx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Return(BinaryDeserializationContext ctx) + { + if (Pool.Count < ctx.Options.MaxContextPoolSize) + { + ctx.Clear(); + Pool.Enqueue(ctx); + } + } + } + + /// + /// Binary deserialization context. Sealed class for pool reuse. + /// Holds all state: buffer, position, caches, options, metadata, interning. + /// Buffer state and read methods are directly in the context (via partial Read.cs) + /// for zero-indirection hot-path access — mirrors the serializer pattern. + /// + internal sealed partial class BinaryDeserializationContext + : AcSerializerContextBase + { // Marker-based interning: sequential cache (no footer needed) - // StringInternFirst/ObjectRefFirst markers register values in order - private object?[]? _internCache; // Shared cache for interned strings AND IId objects - - /// - /// Heap-allocated context class for IId-based reference tracking. - /// Pooled via DeserializationContextClassPool — holds caches, options, metadata. - /// - public readonly BinaryDeserializationContextClass ContextClass; + private object?[]? _internCache; + + public bool HasMetadata; + public bool IsMergeMode; + public bool RemoveOrphanedItems; + + // Options-derived properties + public byte MinStringInternLength => Options.MinStringInternLength; - public bool HasMetadata { get; private set; } - /// - /// Convenience property - true if any reference handling is enabled. - /// - //public readonly bool HasReferenceHandling => ContextClass.ReferenceHandling != ReferenceHandlingMode.None; - public bool IsMergeMode { readonly get; set; } - public bool RemoveOrphanedItems { readonly get; set; } - public readonly bool IsAtEnd => _position >= _buffer.Length; - public readonly int Position => _position; - - // Options-derived properties - delegate to ContextClass.Options - public readonly byte MinStringInternLength => ContextClass.Options.MinStringInternLength; - public readonly bool UseStringCaching => ContextClass.Options.UseStringCaching; - public readonly int MaxCachedStringLength => ContextClass.Options.MaxCachedStringLength; - /// /// Chain reference tracker for maintaining object identity across chain operations. /// Only set when in chain mode (CreateDeserializeChain). /// - public AcSerializerCommon.ChainReferenceTracker? ChainTracker { readonly get; set; } - + public AcSerializerCommon.ChainReferenceTracker? ChainTracker; + /// /// Returns true if in chain mode (ChainTracker is set). /// - public readonly bool IsChainMode => ChainTracker != null; + public bool IsChainMode => ChainTracker != null; + + // Pooled arrays - reused across deserializations, zero steady-state allocation + private int[]? _pooledDupData; + private object?[]? _pooledInternCache; + private int _pooledDupDataLength; + private int _pooledInternCacheLength; + private int _lastInternCacheUsed; // how many slots were used (for targeted Clear) + + // Small arrays: keep across calls. Large arrays: return to pool in Clear(). + private const int SmallArrayThreshold = 256; + + // String cache - for WASM optimization + private Dictionary? _stringCache; + + // Intern cache index counter + private int _nextCacheIndex; + + // Linearized buffer for ReadOnlySequence input + private byte[]? _linearizedBuffer; /// - /// Creates a deserialization context with a pooled ContextClass. - /// ContextClass must already be Reset() with options before passing in. + /// Inline metadata entries flat array. /// - public BinaryDeserializationContext(ReadOnlySpan data, BinaryDeserializationContextClass contextClass) + private MetadataEntry[]? _metadataEntries; + private int _metadataEntryCount; + + /// + /// A metadata entry for the deserializer. + /// + internal struct MetadataEntry + { + public int PropNameHash; + public int[] PropertyHashes; + } + + /// + /// Factory for creating BinaryDeserializeTypeMetadata instances. + /// + protected override Func MetadataFactory + => static t => new BinaryDeserializeTypeMetadata(t); + + public BinaryDeserializationContext() + { + } + + /// + /// Sets the buffer for a new deserialization operation from a byte array. + /// Zero-copy: context references the byte[] directly. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void InitBuffer(byte[] data, int length) { _buffer = data; + _bufferLength = length; _position = 0; - - // Marker-based interning fields + _useStringCaching = Options.UseStringCaching; + _maxCachedStringLength = Options.MaxCachedStringLength; + if (_useStringCaching) GetOrCreateStringCache(); _internCache = null; - HasMetadata = false; IsMergeMode = false; RemoveOrphanedItems = false; ChainTracker = null; - ContextClass = contextClass; } + /// + /// Sets the buffer from a ReadOnlySpan by copying to a linearized buffer. + /// Used when the source is not a byte[] (e.g. stackalloc, pinned). + /// + public void InitBufferFromSpan(ReadOnlySpan data) + { + var buffer = RentLinearizedBuffer(data.Length); + data.CopyTo(buffer); + InitBuffer(buffer, data.Length); + } + + #region Header + public void ReadHeader() { - if (_buffer.Length < 2) + if (_position == 0 && IsAtEnd) { throw new AcBinaryDeserializationException("Binary payload is too short to contain a header."); } - var version = ReadByteInternal(); + var version = ReadByte(); if (version != AcBinarySerializerOptions.FormatVersion) { throw new AcBinaryDeserializationException( @@ -89,38 +163,36 @@ public static partial class AcBinaryDeserializer _position - 1); } - var marker = ReadByteInternal(); + var marker = ReadByte(); var hasPropertyTable = false; if (marker == BinaryTypeCode.MetadataHeader) { hasPropertyTable = true; - ContextClass.Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId + Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId } else if (marker == BinaryTypeCode.NoMetadataHeader) { - ContextClass.Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId + Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId } else if ((marker & 0xF0) == BinaryTypeCode.HeaderFlagsBase) { var flags = (byte)(marker & 0x0F); hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0; - // Decode ReferenceHandlingMode from separate bits var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0; var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0; - ContextClass.Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All + Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All : hasOnlyId ? ReferenceHandlingMode.OnlyId : ReferenceHandlingMode.None; - // Read cache count if flag is set (marker-based format) var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0; if (hasCacheCount) { var cacheCount = (int)ReadVarUInt(); if (cacheCount > 0) { - _internCache = ContextClass.RentInternCache(cacheCount); - ContextClass.SetInternCacheUsed(cacheCount); + _internCache = RentInternCache(cacheCount); + SetInternCacheUsed(cacheCount); } } } @@ -134,407 +206,31 @@ public static partial class AcBinaryDeserializer HasMetadata = hasPropertyTable; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte() => ReadByteInternal(); + #endregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte ReadByteInternal() - { - if (_position >= _buffer.Length) - { - throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position); - } - - return _buffer[_position++]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte PeekByte() - { - if (_position >= _buffer.Length) - { - throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position); - } - - return _buffer[_position]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16Unsafe() - { - EnsureAvailable(2); - var value = BinaryPrimitives.ReadInt16LittleEndian(_buffer.Slice(_position, 2)); - _position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16Unsafe() - { - EnsureAvailable(2); - var value = BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(_position, 2)); - _position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public char ReadCharUnsafe() - { - EnsureAvailable(2); - var value = (char)BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(_position, 2)); - _position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadSingleUnsafe() - { - EnsureAvailable(4); - var bits = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4)); - _position += 4; - return BitConverter.Int32BitsToSingle(bits); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDoubleUnsafe() - { - EnsureAvailable(8); - var bits = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8)); - _position += 8; - return BitConverter.Int64BitsToDouble(bits); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public decimal ReadDecimalUnsafe() - { - EnsureAvailable(16); - var ints = MemoryMarshal.Cast(_buffer.Slice(_position, 16)); - 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 = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8)); - var kind = (DateTimeKind)_buffer[_position + 8]; - _position += 9; - return new DateTime(ticks, kind); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DateTimeOffset ReadDateTimeOffsetUnsafe() - { - EnsureAvailable(10); - var utcTicks = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8)); - var offsetMinutes = BinaryPrimitives.ReadInt16LittleEndian(_buffer.Slice(_position + 8, 2)); - _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 = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8)); - _position += 8; - return new TimeSpan(ticks); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Guid ReadGuidUnsafe() - { - EnsureAvailable(16); - var value = new Guid(_buffer.Slice(_position, 16)); - _position += 16; - return value; - } + #region String Interning /// - /// Optimized VarInt reader with fast path for 1-2 byte values (most common case). - /// Uses ZigZag decoding to handle signed integers. + /// Next intern cache index to assign when registering interned values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadVarInt() + internal ref int NextCacheIndexRef { - var raw = ReadVarUInt(); - // ZigZag decode: handle full uint range before casting to int - // This correctly handles values like int.MaxValue which encode to uint > int.MaxValue - var value = (int)(raw >> 1) ^ -(int)(raw & 1); - return value; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _nextCacheIndex; } - /// - /// Optimized VarUInt reader with fast path for 1-2 byte values. - /// Most VarInts in real data are small (property indices, array lengths, etc.) - /// - [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 < _buffer.Length) - { - 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 = ReadByteInternal(); - 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 = ReadByteInternal(); - value |= (ulong)(b & 0x7F) << shift; - if ((b & 0x80) == 0) - { - break; - } - - shift += 7; - if (shift > 70) - { - throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position); - } - } - - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte[] ReadBytes(int length) - { - if (length == 0) - { - return Array.Empty(); - } - - EnsureAvailable(length); - var result = GC.AllocateUninitializedArray(length); - _buffer.Slice(_position, length).CopyTo(result); - _position += length; - return result; - } - - /// - /// Read UTF8 string with optional caching for WASM optimization. - /// - [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.Slice(_position, length)); - _position += length; - return value; - } - - /// - /// Read string with caching - reduces allocations in WASM. - /// - private string ReadStringUtf8Cached(int length) - { - // CRITICAL FIX (2025-01-24): Use full-content hash to avoid collisions. - // BUG: Property names like "Creator" and "Created" have same length (7) and same - // first 4 bytes ("Crea"), causing hash collision with the old hash function. - // This caused WASM deserialization failures where "Creator" (int) value was - // incorrectly assigned to "Created" (DateTime) property. - // DO NOT REMOVE OR SIMPLIFY THIS HASH FUNCTION! - // See: ComputeStringHashFull() for the fix. - var slice = _buffer.Slice(_position, length); - var hash = ComputeStringHashFull(slice); - - var stringCache = ContextClass.GetOrCreateStringCache(); - - if (stringCache.TryGetValue(hash, out var cached)) - { - // CRITICAL: Verify actual content matches to prevent hash collision data corruption. - // cached.Length is char count, length is UTF-8 byte count — equal only for ASCII. - // For ASCII strings (common case): byte-by-byte comparison against UTF-8 buffer. - if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice)) - { - _position += length; - return cached; - } - // Hash collision or non-ASCII length mismatch - fall through to decode - } - - var value = Utf8NoBom.GetString(slice); - stringCache[hash] = value; - _position += length; - return value; - } - - /// - /// Verifies that a cached ASCII string matches the UTF-8 bytes exactly. - /// For ASCII, each char's low byte equals the UTF-8 byte. - /// Caller guarantees cached.Length == utf8Bytes.Length (ASCII invariant). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan utf8Bytes) - { - for (var i = 0; i < cached.Length; i++) - { - if ((byte)cached[i] != utf8Bytes[i]) - return false; - } - return true; - } - - /// - /// Compute hash that includes ALL bytes for short strings to avoid collisions. - /// - /// CRITICAL FIX (2025-01-24): DO NOT MODIFY THIS FUNCTION! - /// - /// PROBLEM: The original hash function only used first 4 bytes + length. - /// This caused hash collisions in WASM for similar property names like: - /// - "Creator" vs "Created" (both 7 bytes, both start with "Crea") - /// - "Modifier" vs "Modified" (both 8 bytes, both start with "Modi") - /// - /// SYMPTOM: In WASM, when "Created" was cached first, reading "Creator" returned - /// "Created" from cache, causing type mismatch (int value ? DateTime property). - /// Error: "Cannot set property 'Created' - PropertyType: DateTime, ValueType: Int32" - /// - /// FIX: Hash ALL bytes for strings ?32 bytes (covers all typical property names). - /// This eliminates collisions between similar property names completely. - /// - /// PERFORMANCE: ~5% slower hash computation, but zero-allocation cache hits. - /// This is acceptable for the reliability improvement. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeStringHashFull(ReadOnlySpan data) - { - // For strings up to 32 bytes (covers most property names), hash ALL bytes - // This completely eliminates collisions like Creator/Created - if (data.Length <= 32) - { - var hash = new HashCode(); - hash.AddBytes(data); - return hash.ToHashCode(); - } - - // For longer strings (rare for property names), use sampling strategy: - // first 8 bytes + last 8 bytes + middle 8 bytes + length - // This provides good collision resistance with O(1) performance - 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(); - } - - /// - /// Reads a raw 4-byte int32 (little-endian) without type code. - /// Used for reading type name hashes in UseMetadata mode. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32Raw() - { - EnsureAvailable(4); - var value = Unsafe.ReadUnaligned(ref Unsafe.AsRef(in _buffer[_position])); - _position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Skip(int count) - { - EnsureAvailable(count); - _position += count; - } - - /// - /// Registers an interned value (string or object) in the cache. - /// Called when StringInternFirst/ObjectRefFirst marker is encountered. - /// Sequential: values are registered in order (0, 1, 2, ...). - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RegisterNextInternedValue(object value) { - _internCache![ContextClass.NextCacheIndexRef++] = value; + _internCache![_nextCacheIndex++] = value; } - /// - /// Registers an interned value at a specific cache index. - /// Used when the serializer writes explicit cache indices (non-sequential). - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RegisterInternedValueAt(int cacheIndex, object value) { _internCache![cacheIndex] = value; } - /// - /// Gets an interned string by cache index (StringInterned type code). - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetInternedString(int cacheIndex) { @@ -549,9 +245,6 @@ public static partial class AcBinaryDeserializer return (string)result; } - /// - /// Gets an interned object by cache index (ObjectRef type code). - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public object GetInternedObject(int cacheIndex) { @@ -566,13 +259,155 @@ public static partial class AcBinaryDeserializer return result; } - private void EnsureAvailable(int length) + #endregion + + #region Pooled Arrays & Caches (merged from ContextClass) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Dictionary GetOrCreateStringCache() { - if (_position > _buffer.Length - length) + return _stringCache ??= new Dictionary(128); + } + + /// + /// Rents a linearized buffer for ReadOnlySequence multi-segment input. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal byte[] RentLinearizedBuffer(int minSize) + { + if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize) + return _linearizedBuffer; + + if (_linearizedBuffer != null) + ArrayPool.Shared.Return(_linearizedBuffer); + + _linearizedBuffer = ArrayPool.Shared.Rent(minSize); + return _linearizedBuffer; + } + + public int[] RentDupData(int minLength) + { + if (_pooledDupData != null && _pooledDupDataLength >= minLength) + return _pooledDupData; + + if (_pooledDupData != null) + ArrayPool.Shared.Return(_pooledDupData); + + _pooledDupData = ArrayPool.Shared.Rent(minLength); + _pooledDupDataLength = _pooledDupData.Length; + return _pooledDupData; + } + + public object?[] RentInternCache(int minLength) + { + if (_pooledInternCache != null && _pooledInternCacheLength >= minLength) { - throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position); + if (_lastInternCacheUsed > 0) + { + Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed); + _lastInternCacheUsed = 0; + } + return _pooledInternCache; + } + + if (_pooledInternCache != null) + ArrayPool.Shared.Return(_pooledInternCache, clearArray: true); + + _pooledInternCache = ArrayPool.Shared.Rent(minLength); + _pooledInternCacheLength = _pooledInternCache.Length; + _lastInternCacheUsed = 0; + return _pooledInternCache; + } + + public void SetInternCacheUsed(int count) + { + _lastInternCacheUsed = count; + } + + #endregion + + #region Inline Metadata + + public void RegisterInlineMetadata(int propNameHash, int[] propertyHashes) + { + if (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length) + { + var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8); + var newArray = new MetadataEntry[newSize]; + if (_metadataEntries != null) + Array.Copy(_metadataEntries, newArray, _metadataEntryCount); + _metadataEntries = newArray; + } + + _metadataEntries[_metadataEntryCount++] = new MetadataEntry + { + PropNameHash = propNameHash, + PropertyHashes = propertyHashes + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[]? FindSourceHashes(int propNameHash) + { + for (var i = 0; i < _metadataEntryCount; i++) + { + if (_metadataEntries![i].PropNameHash == propNameHash) + return _metadataEntries[i].PropertyHashes; + } + return null; + } + + #endregion + + #region Reset & Clear + + public override void Reset(AcBinarySerializerOptions options) + { + base.Reset(options); + } + + public override void Clear() + { + base.Clear(); + + _metadataEntryCount = 0; + _nextCacheIndex = 0; + + // String cache: clear content but keep dictionary allocated for reuse + _stringCache?.Clear(); + + // Intern cache: clear GC roots, return large arrays to pool + if (_pooledInternCache != null) + { + if (_pooledInternCacheLength > SmallArrayThreshold) + { + ArrayPool.Shared.Return(_pooledInternCache, clearArray: true); + _pooledInternCache = null; + _pooledInternCacheLength = 0; + } + else if (_lastInternCacheUsed > 0) + { + Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed); + } + _lastInternCacheUsed = 0; + } + + // Dup data: no GC roots (int[]), return large arrays to pool + if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold) + { + ArrayPool.Shared.Return(_pooledDupData); + _pooledDupData = null; + _pooledDupDataLength = 0; + } + + // Linearized buffer: no GC roots (byte[]), keep small, return large + if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold) + { + ArrayPool.Shared.Return(_linearizedBuffer); + _linearizedBuffer = null; } } + #endregion } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs deleted file mode 100644 index e5d056a..0000000 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace AyCode.Core.Serializers.Binaries; - -public static partial class AcBinaryDeserializer -{ - /// - /// Pool for BinaryDeserializationContextClass instances. - /// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern. - /// - private static class DeserializationContextClassPool - { - private static readonly ConcurrentQueue Pool = new(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BinaryDeserializationContextClass Get(AcBinarySerializerOptions options) - { - if (Pool.TryDequeue(out var ctx)) - { - ctx.Reset(options); - return ctx; - } - - var newCtx = new BinaryDeserializationContextClass(); - newCtx.Reset(options); - return newCtx; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(BinaryDeserializationContextClass ctx) - { - if (Pool.Count < ctx.Options.MaxContextPoolSize) - { - ctx.Clear(); - Pool.Enqueue(ctx); - } - } - } - - /// - /// Heap-allocated context class for binary deserialization. - /// Inherits from AcSerializerContextBase for unified metadata caching and IId-based reference tracking. - /// Used in composition with the ref struct BinaryDeserializationContext. - /// Holds pooled arrays and caches for reuse across deserializations. - /// - internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase - { - /// - /// Factory for creating BinaryDeserializeTypeMetadata instances. - /// - protected override Func MetadataFactory - => static t => new BinaryDeserializeTypeMetadata(t); - - // Pooled arrays - reused across deserializations, zero steady-state allocation - private int[]? _pooledDupData; - private object?[]? _pooledInternCache; - private int _pooledDupDataLength; - private int _pooledInternCacheLength; - private int _lastInternCacheUsed; // how many slots were used (for targeted Clear) - - // Small arrays: keep across calls. Large arrays: return to pool in Clear(). - private const int SmallArrayThreshold = 256; - - // String cache - moved from ref struct for pool reuse (WASM optimization) - private Dictionary? _stringCache; - - // Intern cache index counter - moved from ref struct for pool reuse - private int _nextCacheIndex; - - // Linearized buffer for ReadOnlySequence input - private byte[]? _linearizedBuffer; - - /// - /// String cache for WASM optimization. Reused across deserializations. - /// - internal Dictionary? StringCache => _stringCache; - - /// - /// Gets or lazily creates the string cache with initial capacity. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Dictionary GetOrCreateStringCache() - { - return _stringCache ??= new Dictionary(128); - } - - /// - /// Next intern cache index to assign when registering interned values. - /// - internal ref int NextCacheIndexRef - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _nextCacheIndex; - } - - /// - /// Rents a linearized buffer for ReadOnlySequence multi-segment input. - /// Buffer is pooled and reused across deserializations. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal byte[] RentLinearizedBuffer(int minSize) - { - if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize) - return _linearizedBuffer; - - if (_linearizedBuffer != null) - ArrayPool.Shared.Return(_linearizedBuffer); - - _linearizedBuffer = ArrayPool.Shared.Rent(minSize); - return _linearizedBuffer; - } - - /// - /// Inline metadata bejegyzĂ©sek flat array-ben. - /// A propNameHash alapján lineárisan keresĂĽnk (kis számĂş tĂ­pus per stream). - /// Minden entry: source tĂ­pus propNameHash + source property hash-ek. - /// - private MetadataEntry[]? _metadataEntries; - private int _metadataEntryCount; - - /// - /// Egy metadata bejegyzĂ©s a deserializer számára. - /// - internal struct MetadataEntry - { - public int PropNameHash; // source tĂ­pus propNameHash (FNV-1a) - public int[] PropertyHashes; // source property hash-ek sorrendben - } - - public BinaryDeserializationContextClass() - { - } - - /// - /// Gets or allocates a pooled int[] for dup data (position/cacheIndex pairs). - /// - public int[] RentDupData(int minLength) - { - if (_pooledDupData != null && _pooledDupDataLength >= minLength) - return _pooledDupData; - - // Too small - return old and rent bigger - if (_pooledDupData != null) - ArrayPool.Shared.Return(_pooledDupData); - - _pooledDupData = ArrayPool.Shared.Rent(minLength); - _pooledDupDataLength = _pooledDupData.Length; - return _pooledDupData; - } - - /// - /// Gets or allocates a pooled object?[] for intern cache. - /// - public object?[] RentInternCache(int minLength) - { - if (_pooledInternCache != null && _pooledInternCacheLength >= minLength) - { - // Reuse - clear previous content (release GC roots) - if (_lastInternCacheUsed > 0) - { - Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed); - _lastInternCacheUsed = 0; - } - return _pooledInternCache; - } - - // Too small - return old and rent bigger - if (_pooledInternCache != null) - ArrayPool.Shared.Return(_pooledInternCache, clearArray: true); - - _pooledInternCache = ArrayPool.Shared.Rent(minLength); - _pooledInternCacheLength = _pooledInternCache.Length; - _lastInternCacheUsed = 0; - return _pooledInternCache; - } - - /// - /// Tracks how many intern cache slots were used (for targeted Clear). - /// - public void SetInternCacheUsed(int count) - { - _lastInternCacheUsed = count; - } - - /// - /// Inline metadata regisztrálása az elsĹ‘ elĹ‘forduláskor. - /// A stream-bĹ‘l beolvasott source property hash-ek tárolása propNameHash alapján. - /// - public void RegisterInlineMetadata(int propNameHash, int[] propertyHashes) - { - if (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length) - { - var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8); - var newArray = new MetadataEntry[newSize]; - if (_metadataEntries != null) - Array.Copy(_metadataEntries, newArray, _metadataEntryCount); - _metadataEntries = newArray; - } - - _metadataEntries[_metadataEntryCount++] = new MetadataEntry - { - PropNameHash = propNameHash, - PropertyHashes = propertyHashes - }; - } - - /// - /// Source property hash-ek keresĂ©se propNameHash alapján. - /// Lineáris keresĂ©s — kis számĂş tĂ­pus per stream (tipikusan < 10). - /// Null ha nincs találat (elsĹ‘ elĹ‘fordulás, hash-eket olvasni kell a stream-bĹ‘l). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int[]? FindSourceHashes(int propNameHash) - { - for (var i = 0; i < _metadataEntryCount; i++) - { - if (_metadataEntries![i].PropNameHash == propNameHash) - return _metadataEntries[i].PropertyHashes; - } - return null; - } - - public override void Clear() - { - base.Clear(); - - _metadataEntryCount = 0; - _nextCacheIndex = 0; - - // String cache: clear content but keep dictionary allocated for reuse - _stringCache?.Clear(); - - // Intern cache: clear GC roots, return large arrays to pool - if (_pooledInternCache != null) - { - if (_pooledInternCacheLength > SmallArrayThreshold) - { - // Large: return to pool - no pre-rent needed, ReadFooterIndices will rent on demand - ArrayPool.Shared.Return(_pooledInternCache, clearArray: true); - _pooledInternCache = null; - _pooledInternCacheLength = 0; - } - else if (_lastInternCacheUsed > 0) - { - // Small: keep array, just clear content (release GC roots) - Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed); - } - _lastInternCacheUsed = 0; - } - - // Dup data: no GC roots (int[]), return large arrays to pool - if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold) - { - ArrayPool.Shared.Return(_pooledDupData); - _pooledDupData = null; - _pooledDupDataLength = 0; - } - - // Linearized buffer: no GC roots (byte[]), keep small, return large - if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold) - { - ArrayPool.Shared.Return(_linearizedBuffer); - _linearizedBuffer = null; - } - } - - public override void Reset(AcBinarySerializerOptions options) - { - Clear(); - base.Reset(options); - } - } -} diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs index a496ae6..fb7d09b 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs @@ -55,13 +55,13 @@ public static partial class AcBinaryDeserializer // Cross-type path: use index mapping var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper); - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - var result = ReadValueWithMapping(ref context, destType, indexMapping, 0); + var result = ReadValueWithMapping(context, destType, indexMapping, 0); return (TDest?)result; } catch (AcBinaryDeserializationException) @@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -141,8 +141,8 @@ public static partial class AcBinaryDeserializer // Cross-type path: use index mapping var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper); - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { @@ -158,7 +158,7 @@ public static partial class AcBinaryDeserializer if (typeCode == BinaryTypeCode.Object) { context.ReadByte(); // consume Object marker - PopulateObjectWithMapping(ref context, target, destType, indexMapping, 0); + PopulateObjectWithMapping(context, target, destType, indexMapping, 0); } else { @@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -207,16 +207,16 @@ public static partial class AcBinaryDeserializer /// Reads a value with index mapping applied. /// Maps source PropertyIndex to destination PropertyIndex using the provided mapping. /// - private static object? ReadValueWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth) + private static object? ReadValueWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth) { var typeCode = context.ReadByte(); return typeCode switch { BinaryTypeCode.Null => null, - BinaryTypeCode.Object => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: false), - BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: true), - _ => ReadValue(ref context, destType, depth) // Primitives, arrays, etc. use normal path + BinaryTypeCode.Object => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: false), + BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: true), + _ => ReadValue(context, destType, depth) // Primitives, arrays, etc. use normal path }; } @@ -224,9 +224,9 @@ public static partial class AcBinaryDeserializer /// Reads an object using index mapping for property resolution. /// Note: Object marker already consumed by caller. /// - private static object? ReadObjectWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth, bool registerInCache) + private static object? ReadObjectWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth, bool registerInCache) { - var wrapper = context.ContextClass.GetWrapper(destType); + var wrapper = context.GetWrapper(destType); var metadata = wrapper.Metadata; var obj = CreateInstance(destType, metadata); @@ -237,7 +237,7 @@ public static partial class AcBinaryDeserializer context.RegisterNextInternedValue(obj); } - PopulateObjectWithMapping(ref context, obj, destType, indexMapping, depth); + PopulateObjectWithMapping(context, obj, destType, indexMapping, depth); } return obj; } @@ -247,13 +247,13 @@ public static partial class AcBinaryDeserializer /// Source property indices are remapped to destination indices. /// private static void PopulateObjectWithMapping( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, object target, Type destType, int[] indexMapping, int depth) { - var wrapper = context.ContextClass.GetWrapper(destType); + var wrapper = context.GetWrapper(destType); var metadata = wrapper.Metadata; var propertyCount = (int)context.ReadVarUInt(); var nextDepth = depth + 1; @@ -268,7 +268,7 @@ public static partial class AcBinaryDeserializer if (destPropIndex == -1) { // No mapping - skip this property - SkipValue(ref context, metadata); + SkipValue(context, metadata); continue; } @@ -277,12 +277,12 @@ public static partial class AcBinaryDeserializer if (propInfo == null) { // Destination property not found - skip - SkipValue(ref context, metadata); + SkipValue(context, metadata); continue; } // Reuse common populate logic - PopulatePropertyValue(ref context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth); + PopulatePropertyValue(context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth); } } @@ -304,7 +304,7 @@ public static partial class AcBinaryDeserializer /// Shared between normal populate and cross-type populate. /// private static void PopulatePropertyValue( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, TypeMetadataWrapper wrapper, @@ -339,7 +339,7 @@ public static partial class AcBinaryDeserializer context.RegisterNextInternedValue(existingObj); } - PopulateObjectCore(ref context, existingObj, wrapper, nextDepth, skipDefaultWrite: false); + PopulateObjectCore(context, existingObj, wrapper, nextDepth, skipDefaultWrite: false); return; } } @@ -351,7 +351,7 @@ public static partial class AcBinaryDeserializer if (existingCollection is IList existingList) { context.ReadByte(); // consume Array marker - PopulateListOptimized(ref context, existingList, propInfo, nextDepth); + PopulateListOptimized(context, existingList, propInfo, nextDepth); return; } } @@ -360,10 +360,10 @@ public static partial class AcBinaryDeserializer var positionBeforeRead = context.Position; try { - if (TryReadAndSetTypedValue(ref context, target, propInfo, peekCode)) + if (TryReadAndSetTypedValue(context, target, propInfo, peekCode)) return; - var value = ReadValue(ref context, propInfo.PropertyType, nextDepth); + var value = ReadValue(context, propInfo.PropertyType, nextDepth); propInfo.SetValue(target, value); } catch (InvalidCastException ex) diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs index 5984d4f..7df78e7 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs @@ -32,12 +32,12 @@ public static partial class AcBinaryDeserializer /// - Non-IId + All: [Object][hashcode][props 0-tďż˝l...] - hashcode prefix /// - Ref=Off: [Object][props 0-tďż˝l...] - no prefix /// - private static void PopulateObject(ref BinaryDeserializationContext context, object target, Type targetType, int depth) + private static void PopulateObject(BinaryDeserializationContext context, object target, Type targetType, int depth) { - var wrapper = context.ContextClass.GetWrapper(targetType); + var wrapper = context.GetWrapper(targetType); // UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate - PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false); + PopulateObjectCore(context, target, wrapper, depth, skipDefaultWrite: false); } /// @@ -46,13 +46,13 @@ public static partial class AcBinaryDeserializer /// No hashcode prefix - position-based footer handles reference tracking. /// private static void PopulateObjectCore( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, bool skipDefaultWrite) { - PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite); + PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite); } /// @@ -61,7 +61,7 @@ public static partial class AcBinaryDeserializer /// UseMetadata=false: properties[i] gives the setter directly. /// private static void PopulateObjectPropertiesIndexed( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, @@ -87,12 +87,12 @@ public static partial class AcBinaryDeserializer if (propInfo.ExpectedTypeCode.HasValue) { - ReadAndSetMarkerlessValue(ref context, target, propInfo); + ReadAndSetMarkerlessValue(context, target, propInfo); continue; } // Non-markerless properties: standard marker-based read - PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth); + PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth); } } else @@ -102,7 +102,7 @@ public static partial class AcBinaryDeserializer { var propInfo = cacheMap != null ? cacheMap[i] : properties[i]; - PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth); + PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth); } } } @@ -111,7 +111,7 @@ public static partial class AcBinaryDeserializer /// Standard marker-based property read. Extracted to avoid duplicating logic in both loops. /// private static void PopulatePropertyWithMarker( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, object target, BinaryPropertySetterBase? propInfo, BinaryDeserializeTypeMetadata metadata, @@ -126,7 +126,7 @@ public static partial class AcBinaryDeserializer // Nincs megfelelĹ‘ target property → skip if (propInfo == null) { - SkipValue(ref context, metadata); + SkipValue(context, metadata); return; } @@ -163,12 +163,12 @@ public static partial class AcBinaryDeserializer // Merge mode with IId collection: use merge logic if (isMergeMode && propInfo.IsIIdCollection) { - MergeIIdCollection(ref context, existingList, propInfo, nextDepth); + MergeIIdCollection(context, existingList, propInfo, nextDepth); } else { // Normal populate: replace collection contents - PopulateListOptimized(ref context, existingList, propInfo, nextDepth); + PopulateListOptimized(context, existingList, propInfo, nextDepth); } return; } @@ -181,10 +181,10 @@ public static partial class AcBinaryDeserializer if (existingObj != null) { // ReadValue kezeli mindkĂ©t markert - var nestedValue = ReadValue(ref context, propInfo.PropertyType, nextDepth); + var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth); if (nestedValue != null) { - var nestedMeta = context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata; + var nestedMeta = context.GetWrapper(propInfo.PropertyType).Metadata; CopyProperties(nestedValue, existingObj, nestedMeta); } return; @@ -197,10 +197,10 @@ public static partial class AcBinaryDeserializer { // Use typed setters for primitives and strings to avoid ReadValue dispatch if (propInfo.AccessorType != PropertyAccessorType.Object && - TryReadAndSetTypedValue(ref context, target, propInfo, peekCode)) + TryReadAndSetTypedValue(context, target, propInfo, peekCode)) return; - var value = ReadValue(ref context, propInfo.PropertyType, nextDepth); + var value = ReadValue(context, propInfo.PropertyType, nextDepth); propInfo.SetValue(target, value); } catch (InvalidCastException ex) @@ -227,7 +227,7 @@ public static partial class AcBinaryDeserializer /// Only called for non-nullable value types with ExpectedTypeCode set. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadAndSetMarkerlessValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo) + private static void ReadAndSetMarkerlessValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo) { switch (propInfo.AccessorType) { @@ -274,16 +274,16 @@ public static partial class AcBinaryDeserializer /// Called from ReadObject/ReadObjectWithMetadata for new instances. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void PopulateObject(ref BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, bool skipDefaultWrite) + private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, bool skipDefaultWrite) { - PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite); + PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite); } #endregion #region Populate List Methods - private static void PopulateList(ref BinaryDeserializationContext context, IList targetList, Type listType, int depth) + private static void PopulateList(BinaryDeserializationContext context, IList targetList, Type listType, int depth) { var elementType = GetCollectionElementType(listType) ?? typeof(object); @@ -300,7 +300,7 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < count; i++) { // ReadValue handles ChainMode internally (ReadObject returns cached instance) - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); targetList.Add(value); } } @@ -313,7 +313,7 @@ public static partial class AcBinaryDeserializer /// /// Optimized list populate that reuses existing items when possible. /// - private static void PopulateListOptimized(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth) + private static void PopulateListOptimized(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth) { var elementType = propInfo.ElementType ?? typeof(object); var count = (int)context.ReadVarUInt(); @@ -333,7 +333,7 @@ public static partial class AcBinaryDeserializer return; } - var wrapper = context.ContextClass.GetWrapper(elementType); + var wrapper = context.GetWrapper(elementType); var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null; for (int i = 0; i < count; i++) @@ -347,13 +347,13 @@ public static partial class AcBinaryDeserializer if (existingItem != null) { context.ReadByte(); // consume Object marker - PopulateObjectCore(ref context, existingItem, wrapper, nextDepth, skipDefaultWrite: false); + PopulateObjectCore(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false); continue; } } // Read new value - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); if (i < existingCount) { @@ -385,7 +385,7 @@ public static partial class AcBinaryDeserializer /// IId collection merge using cached property info. /// Matches items by Id, updates existing, adds new, optionally removes orphans. /// - private static void MergeIIdCollection(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth) + private static void MergeIIdCollection(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth) { var elementType = propInfo.ElementType!; var idGetter = propInfo.ElementIdGetter!; @@ -416,7 +416,7 @@ public static partial class AcBinaryDeserializer var arrayCount = (int)context.ReadVarUInt(); var nextDepth = depth + 1; - var wrapper = context.ContextClass.GetWrapper(elementType); + var wrapper = context.GetWrapper(elementType); var elementMetadata = wrapper.Metadata; // Track which IDs we see in source (for orphan removal) @@ -429,7 +429,7 @@ public static partial class AcBinaryDeserializer var itemCode = context.PeekByte(); if (itemCode != BinaryTypeCode.Object) { - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); if (value != null) existingList.Add(value); continue; @@ -440,7 +440,7 @@ public static partial class AcBinaryDeserializer if (newItem == null) continue; // PopulateObjectCore handles hashcode reading for Non-IId types - PopulateObjectCore(ref context, newItem, wrapper, nextDepth, skipDefaultWrite: true); + PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); var itemId = idGetter(newItem); if (itemId != null && !IsDefaultValue(itemId, idType)) @@ -487,7 +487,7 @@ public static partial class AcBinaryDeserializer /// IId collection merge using type metadata (for top-level list merge). /// private static void MergeIIdCollectionWithMetadata( - ref BinaryDeserializationContext context, + BinaryDeserializationContext context, IList existingList, Type elementType, TypeMetadataWrapper wrapper, @@ -533,7 +533,7 @@ public static partial class AcBinaryDeserializer var itemCode = context.PeekByte(); if (itemCode != BinaryTypeCode.Object) { - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); if (value != null) existingList.Add(value); continue; @@ -544,7 +544,7 @@ public static partial class AcBinaryDeserializer if (newItem == null) continue; // PopulateObjectCore handles hashcode reading for Non-IId types - PopulateObjectCore(ref context, newItem, wrapper, nextDepth, skipDefaultWrite: true); + PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); var itemId = idGetter(newItem); if (itemId != null && !IsDefaultValue(itemId, idType)) @@ -603,7 +603,7 @@ public static partial class AcBinaryDeserializer /// Reads Id value without type marker. The serializer didn't write a marker for IId types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadIdValueWithoutMarker(ref BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType) + private static void ReadIdValueWithoutMarker(BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType) { switch (idType) { diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index adb8a03..7d6efbd 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -36,39 +36,39 @@ public static partial class AcBinaryDeserializer private static Dictionary? t_typeConversionLocalCache; // Type dispatch table for fast ReadValue - private delegate object? TypeReader(ref BinaryDeserializationContext context, Type targetType, int depth); + private delegate object? TypeReader(BinaryDeserializationContext context, Type targetType, int depth); private static readonly TypeReader?[] TypeReaders = new TypeReader[byte.MaxValue + 1]; private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); static AcBinaryDeserializer() { - RegisterReader(BinaryTypeCode.Null, static (ref BinaryDeserializationContext _, Type _, int _) => null); - RegisterReader(BinaryTypeCode.True, static (ref BinaryDeserializationContext _, Type _, int _) => true); - RegisterReader(BinaryTypeCode.False, static (ref BinaryDeserializationContext _, Type _, int _) => false); - RegisterReader(BinaryTypeCode.Int8, static (ref BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte()); - RegisterReader(BinaryTypeCode.UInt8, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte()); - RegisterReader(BinaryTypeCode.Int16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe()); - RegisterReader(BinaryTypeCode.UInt16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe()); - RegisterReader(BinaryTypeCode.Int32, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ref ctx, type)); - RegisterReader(BinaryTypeCode.UInt32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt()); - RegisterReader(BinaryTypeCode.Int64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong()); - RegisterReader(BinaryTypeCode.UInt64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong()); - RegisterReader(BinaryTypeCode.Float32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe()); - RegisterReader(BinaryTypeCode.Float64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe()); - RegisterReader(BinaryTypeCode.Decimal, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe()); - RegisterReader(BinaryTypeCode.Char, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe()); - RegisterReader(BinaryTypeCode.String, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ref ctx)); - RegisterReader(BinaryTypeCode.StringInterned, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt())); - RegisterReader(BinaryTypeCode.StringEmpty, static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty); + RegisterReader(BinaryTypeCode.Null, static (BinaryDeserializationContext _, Type _, int _) => null); + RegisterReader(BinaryTypeCode.True, static (BinaryDeserializationContext _, Type _, int _) => true); + RegisterReader(BinaryTypeCode.False, static (BinaryDeserializationContext _, Type _, int _) => false); + RegisterReader(BinaryTypeCode.Int8, static (BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte()); + RegisterReader(BinaryTypeCode.UInt8, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte()); + RegisterReader(BinaryTypeCode.Int16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe()); + RegisterReader(BinaryTypeCode.UInt16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe()); + RegisterReader(BinaryTypeCode.Int32, static (BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ctx, type)); + RegisterReader(BinaryTypeCode.UInt32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt()); + RegisterReader(BinaryTypeCode.Int64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong()); + RegisterReader(BinaryTypeCode.UInt64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong()); + RegisterReader(BinaryTypeCode.Float32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe()); + RegisterReader(BinaryTypeCode.Float64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe()); + RegisterReader(BinaryTypeCode.Decimal, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe()); + RegisterReader(BinaryTypeCode.Char, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe()); + RegisterReader(BinaryTypeCode.String, static (BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ctx)); + RegisterReader(BinaryTypeCode.StringInterned, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt())); + RegisterReader(BinaryTypeCode.StringEmpty, static (BinaryDeserializationContext _, Type _, int _) => string.Empty); // StringInternFirst: first occurrence of interned string - read cacheIndex + content + register in cache - RegisterReader(BinaryTypeCode.StringInternFirst, static (ref BinaryDeserializationContext ctx, Type _, int _) => - ReadAndRegisterInternedString(ref ctx)); - RegisterReader(BinaryTypeCode.DateTime, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe()); - RegisterReader(BinaryTypeCode.DateTimeOffset, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe()); - RegisterReader(BinaryTypeCode.TimeSpan, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe()); - RegisterReader(BinaryTypeCode.Guid, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe()); - RegisterReader(BinaryTypeCode.Enum, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ref ctx, type)); + RegisterReader(BinaryTypeCode.StringInternFirst, static (BinaryDeserializationContext ctx, Type _, int _) => + ReadAndRegisterInternedString(ctx)); + RegisterReader(BinaryTypeCode.DateTime, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe()); + RegisterReader(BinaryTypeCode.DateTimeOffset, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe()); + RegisterReader(BinaryTypeCode.TimeSpan, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe()); + RegisterReader(BinaryTypeCode.Guid, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe()); + RegisterReader(BinaryTypeCode.Enum, static (BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ctx, type)); RegisterReader(BinaryTypeCode.Object, ReadObject); RegisterReader(BinaryTypeCode.ObjectRefFirst, ReadObjectRefFirst); RegisterReader(BinaryTypeCode.ObjectWithMetadata, ReadObjectWithMetadata); @@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer RegisterReader(BinaryTypeCode.ObjectRef, ReadObjectRef); RegisterReader(BinaryTypeCode.Array, ReadArray); RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary); - RegisterReader(BinaryTypeCode.ByteArray, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ref ctx)); + RegisterReader(BinaryTypeCode.ByteArray, static (BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ctx)); // Register FixStr readers (34-65) for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++) @@ -93,9 +93,9 @@ public static partial class AcBinaryDeserializer private static TypeReader CreateFixStrReader(int length) { if (length == 0) - return static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty; + return static (BinaryDeserializationContext _, Type _, int _) => string.Empty; - return (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length); + return (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -136,13 +136,13 @@ public static partial class AcBinaryDeserializer return (T?)(object?)DeserializeExpression(data, targetType, options); } - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - var result = ReadValue(ref context, targetType, 0); + var result = ReadValue(context, targetType, 0); // Position-based string interning - no validation needed return (T?)result; } @@ -158,14 +158,14 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } /// /// Deserialize binary data to specified type. /// - public static object? Deserialize(ReadOnlySpan data, Type targetType) + public static object? Deserialize(ReadOnlySpan data, Type targetType) => Deserialize(data, targetType, AcBinarySerializerOptions.Default); /// @@ -182,13 +182,13 @@ public static partial class AcBinaryDeserializer return DeserializeExpression(data, targetType, options); } - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - var result = ReadValue(ref context, targetType, 0); + var result = ReadValue(context, targetType, 0); // Position-based string interning - no validation needed return result; } @@ -204,7 +204,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -225,16 +225,16 @@ public static partial class AcBinaryDeserializer if (data.IsSingleSegment) return Deserialize(data.FirstSpan, options); - var ctxClass = DeserializationContextClassPool.Get(options); + var context = DeserializationContextPool.Get(options); try { - var buffer = ctxClass.RentLinearizedBuffer((int)data.Length); + var buffer = context.RentLinearizedBuffer((int)data.Length); data.CopyTo(buffer); - return Deserialize(buffer.AsSpan(0, (int)data.Length), ctxClass); + return Deserialize(buffer.AsSpan(0, (int)data.Length), context); } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -254,35 +254,35 @@ public static partial class AcBinaryDeserializer if (data.IsSingleSegment) return Deserialize(data.FirstSpan, targetType, options); - var ctxClass = DeserializationContextClassPool.Get(options); + var context = DeserializationContextPool.Get(options); try { - var buffer = ctxClass.RentLinearizedBuffer((int)data.Length); + var buffer = context.RentLinearizedBuffer((int)data.Length); data.CopyTo(buffer); - return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, ctxClass); + return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, context); } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } /// - /// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path). + /// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path). /// - private static T? Deserialize(ReadOnlySpan data, BinaryDeserializationContextClass ctxClass) + private static T? Deserialize(ReadOnlySpan data, BinaryDeserializationContext context) { if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default; var targetType = typeof(T); if (AcSerializerCommon.IsExpressionType(targetType)) - return (T?)(object?)DeserializeExpression(data, targetType, ctxClass); + return (T?)(object?)DeserializeExpression(data, targetType, context); - var context = new BinaryDeserializationContext(data, ctxClass); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - return (T?)ReadValue(ref context, targetType, 0); + return (T?)ReadValue(context, targetType, 0); } catch (AcBinaryDeserializationException) { throw; } catch (Exception ex) @@ -294,20 +294,20 @@ public static partial class AcBinaryDeserializer } /// - /// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path). + /// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path). /// - private static object? Deserialize(ReadOnlySpan data, Type targetType, BinaryDeserializationContextClass ctxClass) + private static object? Deserialize(ReadOnlySpan data, Type targetType, BinaryDeserializationContext context) { if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null; if (AcSerializerCommon.IsExpressionType(targetType)) - return DeserializeExpression(data, targetType, ctxClass); + return DeserializeExpression(data, targetType, context); - var context = new BinaryDeserializationContext(data, ctxClass); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - return ReadValue(ref context, targetType, 0); + return ReadValue(context, targetType, 0); } catch (AcBinaryDeserializationException) { throw; } catch (Exception ex) @@ -319,15 +319,15 @@ public static partial class AcBinaryDeserializer } /// - /// Internal: DeserializeExpression with pre-pooled ContextClass. + /// Internal: DeserializeExpression with pre-pooled context. /// - private static Expression? DeserializeExpression(ReadOnlySpan data, Type targetExpressionType, BinaryDeserializationContextClass ctxClass) + private static Expression? DeserializeExpression(ReadOnlySpan data, Type targetExpressionType, BinaryDeserializationContext context) { - var context = new BinaryDeserializationContext(data, ctxClass); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - var node = (AcExpressionNode?)ReadValue(ref context, typeof(AcExpressionNode), 0); + var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0); if (node == null) return null; var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType); @@ -348,13 +348,13 @@ public static partial class AcBinaryDeserializer /// private static Expression? DeserializeExpression(ReadOnlySpan data, Type targetExpressionType, AcBinarySerializerOptions options) { - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { context.ReadHeader(); - var node = (AcExpressionNode?)ReadValue(ref context, typeof(AcExpressionNode), 0); + var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0); // Position-based string interning - no validation needed if (node == null) return null; @@ -373,7 +373,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -405,8 +405,8 @@ public static partial class AcBinaryDeserializer if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return; var targetType = target.GetType(); - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(data, ctxClass); + var context = DeserializationContextPool.Get(options); + context.InitBufferFromSpan(data); try { @@ -416,18 +416,18 @@ public static partial class AcBinaryDeserializer if (typeCode == BinaryTypeCode.Object) { context.ReadByte(); // consume Object marker - PopulateObject(ref context, target, targetType, 0); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.ObjectWithMetadata) { context.ReadByte(); // consume ObjectWithMetadata marker - ReadInlineMetadataForPopulate(ref context, targetType); - PopulateObject(ref context, target, targetType, 0); + ReadInlineMetadataForPopulate(context, targetType); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.Array && target is IList targetList) { context.ReadByte(); // consume Array marker - PopulateList(ref context, targetList, targetType, 0); + PopulateList(context, targetList, targetType, 0); } else { @@ -450,7 +450,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -475,12 +475,10 @@ public static partial class AcBinaryDeserializer var opts = options ?? AcBinarySerializerOptions.Default; var targetType = target.GetType(); - var ctxClass = DeserializationContextClassPool.Get(opts); - var context = new BinaryDeserializationContext(data, ctxClass) - { - IsMergeMode = true, - RemoveOrphanedItems = opts.RemoveOrphanedItems - }; + var context = DeserializationContextPool.Get(opts); + context.InitBufferFromSpan(data); + context.IsMergeMode = true; + context.RemoveOrphanedItems = opts.RemoveOrphanedItems; try { @@ -490,13 +488,13 @@ public static partial class AcBinaryDeserializer if (typeCode == BinaryTypeCode.Object) { context.ReadByte(); - PopulateObject(ref context, target, targetType, 0); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.ObjectWithMetadata) { context.ReadByte(); - ReadInlineMetadataForPopulate(ref context, targetType); - PopulateObject(ref context, target, targetType, 0); + ReadInlineMetadataForPopulate(context, targetType); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.Array && target is IList targetList) { @@ -505,19 +503,19 @@ public static partial class AcBinaryDeserializer var elementType = GetCollectionElementType(targetType); if (elementType != null) { - var wrapper = context.ContextClass.GetWrapper(elementType); + var wrapper = context.GetWrapper(elementType); var elementMetadata = wrapper.Metadata; if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null) { - MergeIIdCollectionWithMetadata(ref context, targetList, elementType, wrapper, 0); + MergeIIdCollectionWithMetadata(context, targetList, elementType, wrapper, 0); // Position-based string interning - no validation needed return; } } // Non-IId collection, just populate - PopulateList(ref context, targetList, targetType, 0); + PopulateList(context, targetList, targetType, 0); } else { @@ -540,7 +538,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -568,19 +566,20 @@ public static partial class AcBinaryDeserializer // Copy data to array for chain storage var dataArray = data.ToArray(); var chainTracker = new AcSerializerCommon.ChainReferenceTracker(); - var ctxClass = DeserializationContextClassPool.Get(options); - var context = new BinaryDeserializationContext(dataArray, ctxClass) { ChainTracker = chainTracker }; + var context = DeserializationContextPool.Get(options); + context.InitBuffer(dataArray, dataArray.Length); + context.ChainTracker = chainTracker; try { context.ReadHeader(); - var result = ReadValue(ref context, targetType, 0); + var result = ReadValue(context, targetType, 0); // Position-based string interning - no validation needed return new BinaryDeserializeChain(dataArray, options, chainTracker, (T?)result); } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -614,13 +613,14 @@ public static partial class AcBinaryDeserializer ThrowIfDisposed(); var targetType = typeof(TResult); - var ctxClass = DeserializationContextClassPool.Get(_options); - var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker }; + var context = DeserializationContextPool.Get(_options); + context.InitBuffer(_data, _data.Length); + context.ChainTracker = _chainTracker; try { context.ReadHeader(); - var result = ReadValue(ref context, targetType, 0); + var result = ReadValue(context, targetType, 0); // Position-based string interning - no validation needed return (TResult?)result; } @@ -633,7 +633,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -643,8 +643,9 @@ public static partial class AcBinaryDeserializer ThrowIfDisposed(); var targetType = target.GetType(); - var ctxClass = DeserializationContextClassPool.Get(_options); - var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker }; + var context = DeserializationContextPool.Get(_options); + context.InitBuffer(_data, _data.Length); + context.ChainTracker = _chainTracker; try { @@ -654,18 +655,18 @@ public static partial class AcBinaryDeserializer if (typeCode == BinaryTypeCode.Object) { context.ReadByte(); - PopulateObject(ref context, target, targetType, 0); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.ObjectWithMetadata) { context.ReadByte(); - ReadInlineMetadataForPopulate(ref context, targetType); - PopulateObject(ref context, target, targetType, 0); + ReadInlineMetadataForPopulate(context, targetType); + PopulateObject(context, target, targetType, 0); } else if (typeCode == BinaryTypeCode.Array && target is IList targetList) { context.ReadByte(); - PopulateList(ref context, targetList, targetType, 0); + PopulateList(context, targetList, targetType, 0); } else { @@ -686,7 +687,7 @@ public static partial class AcBinaryDeserializer } finally { - DeserializationContextClassPool.Return(ctxClass); + DeserializationContextPool.Return(context); } } @@ -712,7 +713,7 @@ public static partial class AcBinaryDeserializer /// Returns true if handled, false if should fall back to generic path. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryReadAndSetTypedValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode) + private static bool TryReadAndSetTypedValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode) { // Only handle if we have a typed setter if (propInfo.AccessorType == PropertyAccessorType.Object) @@ -927,7 +928,7 @@ public static partial class AcBinaryDeserializer if (peekCode == BinaryTypeCode.String) { context.ReadByte(); - propInfo.SetValue(target, ReadPlainString(ref context)); + propInfo.SetValue(target, ReadPlainString(context)); return true; } if (peekCode == BinaryTypeCode.StringEmpty) @@ -945,7 +946,7 @@ public static partial class AcBinaryDeserializer if (peekCode == BinaryTypeCode.StringInternFirst) { context.ReadByte(); - propInfo.SetValue(target, ReadAndRegisterInternedString(ref context)); + propInfo.SetValue(target, ReadAndRegisterInternedString(context)); return true; } break; @@ -957,7 +958,7 @@ public static partial class AcBinaryDeserializer /// /// Optimized value reader using FrozenDictionary dispatch table. /// - private static object? ReadValue(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadValue(BinaryDeserializationContext context, Type targetType, int depth) { if (context.IsAtEnd) return null; @@ -983,7 +984,7 @@ public static partial class AcBinaryDeserializer var reader = TypeReaders[typeCode]; if (reader != null) { - return reader(ref context, targetType, depth); + return reader(context, targetType, depth); } throw new AcBinaryDeserializationException( @@ -995,7 +996,7 @@ public static partial class AcBinaryDeserializer /// Sima string olvasďż˝sa - NEM regisztrďż˝l az intern tďż˝blďż˝ba. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string ReadPlainString(ref BinaryDeserializationContext context) + private static string ReadPlainString(BinaryDeserializationContext context) { var length = (int)context.ReadVarUInt(); if (length == 0) return string.Empty; @@ -1007,7 +1008,7 @@ public static partial class AcBinaryDeserializer /// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes] /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string ReadAndRegisterInternedString(ref BinaryDeserializationContext context) + private static string ReadAndRegisterInternedString(BinaryDeserializationContext context) { var cacheIndex = (int)context.ReadVarUInt(); var length = (int)context.ReadVarUInt(); @@ -1021,7 +1022,7 @@ public static partial class AcBinaryDeserializer ///// Read a string and register it in the intern table for future references. ///// //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //private static string ReadAndInternString(ref BinaryDeserializationContext context, int streamPosition) + //private static string ReadAndInternString(BinaryDeserializationContext context, int streamPosition) //{ // var length = (int)context.ReadVarUInt(); // if (length == 0) return string.Empty; @@ -1036,7 +1037,7 @@ public static partial class AcBinaryDeserializer //} [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object ReadInt32Value(ref BinaryDeserializationContext context, Type targetType) + private static object ReadInt32Value(BinaryDeserializationContext context, Type targetType) { var value = context.ReadVarInt(); return ConvertToTargetType(value, targetType); @@ -1106,7 +1107,7 @@ public static partial class AcBinaryDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object ReadEnumValue(ref BinaryDeserializationContext context, Type targetType) + private static object ReadEnumValue(BinaryDeserializationContext context, Type targetType) { var info = GetConversionInfo(targetType); var nextByte = context.ReadByte(); @@ -1131,7 +1132,7 @@ public static partial class AcBinaryDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte[] ReadByteArray(ref BinaryDeserializationContext context) + private static byte[] ReadByteArray(BinaryDeserializationContext context) { var length = (int)context.ReadVarUInt(); if (length == 0) return []; @@ -1164,7 +1165,7 @@ public static partial class AcBinaryDeserializer /// ami ritka eset (2+ referencia), tehát nem lassĂ­tja a hot path-ot. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectRef(BinaryDeserializationContext context, Type targetType, int depth) { var cacheIndex = (int)context.ReadVarUInt(); return context.GetInternedObject(cacheIndex); @@ -1174,9 +1175,9 @@ public static partial class AcBinaryDeserializer /// Object olvasása (nem tracked, vagy UseMetadata nĂ©lkĂĽl). /// Wire format: [Object][props...] /// - private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObject(BinaryDeserializationContext context, Type targetType, int depth) { - return ReadObjectCore(ref context, targetType, depth, cacheIndex: -1); + return ReadObjectCore(context, targetType, depth, cacheIndex: -1); } /// @@ -1184,25 +1185,25 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...] /// Az objektumot regisztráljuk a cache-be a megadott index-re. /// - private static object? ReadObjectRefFirst(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectRefFirst(BinaryDeserializationContext context, Type targetType, int depth) { var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectCore(ref context, targetType, depth, cacheIndex: cacheIndex); + return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex); } /// /// Object olvasás core implementáciĂł. /// /// -1 = not cached, 0+ = register at this cache index - private static object? ReadObjectCore(ref BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) + private static object? ReadObjectCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) { // Handle dictionary types if (IsDictionaryType(targetType, out var keyType, out var valueType)) { - return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!, depth); } - var wrapper = context.ContextClass.GetWrapper(targetType); + var wrapper = context.GetWrapper(targetType); var metadata = wrapper.Metadata; var instance = CreateInstance(targetType, metadata); @@ -1213,7 +1214,7 @@ public static partial class AcBinaryDeserializer context.RegisterInternedValueAt(cacheIndex, instance); } - PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true); + PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true); // ChainMode kezelĂ©s if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) @@ -1239,9 +1240,9 @@ public static partial class AcBinaryDeserializer /// ElsĹ‘ elĹ‘fordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...] /// IsmĂ©telt: [ObjectWithMetadata][propNameHash (4b)][props...] /// - private static object? ReadObjectWithMetadata(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithMetadata(BinaryDeserializationContext context, Type targetType, int depth) { - return ReadObjectWithMetadataCore(ref context, targetType, depth, cacheIndex: -1); + return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1); } /// @@ -1249,23 +1250,23 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...] /// Az objektumot regisztráljuk a cache-be a megadott index-re. /// - private static object? ReadObjectWithMetadataRefFirst(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithMetadataRefFirst(BinaryDeserializationContext context, Type targetType, int depth) { var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectWithMetadataCore(ref context, targetType, depth, cacheIndex: cacheIndex); + return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex); } /// /// ObjectWithMetadata olvasás core implementáciĂł. /// /// -1 = not cached, 0+ = register at this cache index - private static object? ReadObjectWithMetadataCore(ref BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) + private static object? ReadObjectWithMetadataCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) { // Inline metadata: propNameHash mindig jön var propNameHash = context.ReadInt32Raw(); // Source hash-ek keresĂ©se — ha nincs, elsĹ‘ elĹ‘fordulás → olvasás a stream-bĹ‘l - var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash); + var sourceHashes = context.FindSourceHashes(propNameHash); if (sourceHashes == null) { var propCount = (int)context.ReadVarUInt(); @@ -1274,16 +1275,16 @@ public static partial class AcBinaryDeserializer { sourceHashes[i] = context.ReadInt32Raw(); } - context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes); + context.RegisterInlineMetadata(propNameHash, sourceHashes); } // Handle dictionary types if (IsDictionaryType(targetType, out var keyType, out var valueType)) { - return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!, depth); } - var wrapper = context.ContextClass.GetWrapper(targetType); + var wrapper = context.GetWrapper(targetType); var metadata = wrapper.Metadata; var instance = CreateInstance(targetType, metadata); @@ -1298,7 +1299,7 @@ public static partial class AcBinaryDeserializer if (wrapper.CacheMap == null) BuildCacheMap(wrapper, sourceHashes); - PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true); + PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true); // ChainMode kezelĂ©s if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) @@ -1323,11 +1324,11 @@ public static partial class AcBinaryDeserializer /// Az ObjectWithMetadata marker már consume-álva van. /// Beolvassa a propNameHash-t + hash-eket (elsĹ‘ elĹ‘fordulásnál), Ă©s felĂ©pĂ­ti a cacheMap-et. /// - private static void ReadInlineMetadataForPopulate(ref BinaryDeserializationContext context, Type targetType) + private static void ReadInlineMetadataForPopulate(BinaryDeserializationContext context, Type targetType) { var propNameHash = context.ReadInt32Raw(); - var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash); + var sourceHashes = context.FindSourceHashes(propNameHash); if (sourceHashes == null) { var propCount = (int)context.ReadVarUInt(); @@ -1336,10 +1337,10 @@ public static partial class AcBinaryDeserializer { sourceHashes[i] = context.ReadInt32Raw(); } - context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes); + context.RegisterInlineMetadata(propNameHash, sourceHashes); } - var wrapper = context.ContextClass.GetWrapper(targetType); + var wrapper = context.GetWrapper(targetType); if (wrapper.CacheMap == null) BuildCacheMap(wrapper, sourceHashes); } @@ -1382,7 +1383,7 @@ public static partial class AcBinaryDeserializer #region Array Reading - private static object? ReadArray(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadArray(BinaryDeserializationContext context, Type targetType, int depth) { var elementType = GetCollectionElementType(targetType); if (elementType == null) elementType = typeof(object); @@ -1393,7 +1394,7 @@ public static partial class AcBinaryDeserializer // Optimized path for primitive arrays if (targetType.IsArray && count > 0) { - var result = TryReadPrimitiveArray(ref context, elementType, count); + var result = TryReadPrimitiveArray(context, elementType, count); if (result != null) return result; } @@ -1402,7 +1403,7 @@ public static partial class AcBinaryDeserializer var array = Array.CreateInstance(elementType, count); for (int i = 0; i < count; i++) { - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); array.SetValue(value, i); } @@ -1429,7 +1430,7 @@ public static partial class AcBinaryDeserializer { for (int i = 0; i < count; i++) { - var value = ReadValue(ref context, elementType, nextDepth); + var value = ReadValue(context, elementType, nextDepth); list.Add(value); } } @@ -1445,7 +1446,7 @@ public static partial class AcBinaryDeserializer /// Optimized primitive array reader using bulk operations. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Array? TryReadPrimitiveArray(ref BinaryDeserializationContext context, Type elementType, int count) + private static Array? TryReadPrimitiveArray(BinaryDeserializationContext context, Type elementType, int count) { // Int32 array if (ReferenceEquals(elementType, IntType)) @@ -1563,7 +1564,7 @@ public static partial class AcBinaryDeserializer #region Dictionary Reading - private static object? ReadDictionary(ref BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadDictionary(BinaryDeserializationContext context, Type targetType, int depth) { if (!IsDictionaryType(targetType, out var keyType, out var valueType)) { @@ -1571,10 +1572,10 @@ public static partial class AcBinaryDeserializer valueType = typeof(object); } - return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!, depth); } - private static object ReadDictionaryAsObject(ref BinaryDeserializationContext context, Type keyType, Type valueType, int depth) + private static object ReadDictionaryAsObject(BinaryDeserializationContext context, Type keyType, Type valueType, int depth) { var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType); var count = (int)context.ReadVarUInt(); @@ -1583,8 +1584,8 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < count; i++) { - var key = ReadValue(ref context, keyType, nextDepth); - var value = ReadValue(ref context, valueType, nextDepth); + var key = ReadValue(context, keyType, nextDepth); + var value = ReadValue(context, valueType, nextDepth); if (key != null) dict.Add(key, value); } @@ -1596,7 +1597,7 @@ public static partial class AcBinaryDeserializer #region Skip Value - private static void SkipValue(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) + private static void SkipValue(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) { var typeCode = context.ReadByte(); @@ -1658,14 +1659,14 @@ public static partial class AcBinaryDeserializer context.Skip(16); return; case BinaryTypeCode.String: - SkipPlainString(ref context); + SkipPlainString(context); return; case BinaryTypeCode.StringInterned: context.ReadVarUInt(); return; case BinaryTypeCode.StringInternFirst: // First occurrence - must register even when skipping - SkipAndRegisterInternedString(ref context); + SkipAndRegisterInternedString(context); return; case BinaryTypeCode.ByteArray: var byteLen = (int)context.ReadVarUInt(); @@ -1677,28 +1678,28 @@ public static partial class AcBinaryDeserializer if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt(); return; case BinaryTypeCode.Object: - SkipObject(ref context, metaData); + SkipObject(context, metaData); return; case BinaryTypeCode.ObjectRefFirst: - SkipObjectRefFirst(ref context, metaData); + SkipObjectRefFirst(context, metaData); return; case BinaryTypeCode.ObjectWithMetadata: - SkipObjectWithMetadata(ref context, metaData, cacheIndex: -1); + SkipObjectWithMetadata(context, metaData, cacheIndex: -1); return; case BinaryTypeCode.ObjectWithMetadataRefFirst: { var cacheIdx = (int)context.ReadVarUInt(); - SkipObjectWithMetadata(ref context, metaData, cacheIndex: cacheIdx); + SkipObjectWithMetadata(context, metaData, cacheIndex: cacheIdx); return; } case BinaryTypeCode.ObjectRef: context.ReadVarUInt(); return; case BinaryTypeCode.Array: - SkipArray(ref context, metaData); + SkipArray(context, metaData); return; case BinaryTypeCode.Dictionary: - SkipDictionary(ref context, metaData); + SkipDictionary(context, metaData); return; } } @@ -1707,7 +1708,7 @@ public static partial class AcBinaryDeserializer /// Sima string kihagyďż˝sa - NEM regisztrďż˝l. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SkipPlainString(ref BinaryDeserializationContext context) + private static void SkipPlainString(BinaryDeserializationContext context) { var byteLen = (int)context.ReadVarUInt(); if (byteLen > 0) @@ -1721,7 +1722,7 @@ public static partial class AcBinaryDeserializer /// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes] /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SkipAndRegisterInternedString(ref BinaryDeserializationContext context) + private static void SkipAndRegisterInternedString(BinaryDeserializationContext context) { var cacheIndex = (int)context.ReadVarUInt(); var byteLen = (int)context.ReadVarUInt(); @@ -1734,12 +1735,12 @@ public static partial class AcBinaryDeserializer /// Skip ObjectRefFirst - must read cacheIndex and register placeholder in cache. /// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...] /// - private static void SkipObjectRefFirst(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) + private static void SkipObjectRefFirst(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) { var cacheIndex = (int)context.ReadVarUInt(); // Register placeholder (stream position as boxed int for potential lazy load) context.RegisterInternedValueAt(cacheIndex, context.Position); - SkipObject(ref context, metaData); + SkipObject(context, metaData); } ///// @@ -1748,7 +1749,7 @@ public static partial class AcBinaryDeserializer ///// Deserialization context ///// Position before the type code was read //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //private static void SkipAndInternString(ref BinaryDeserializationContext context, int streamPosition) + //private static void SkipAndInternString(BinaryDeserializationContext context, int streamPosition) //{ // var byteLen = (int)context.ReadVarUInt(); // if (byteLen == 0) return; @@ -1763,7 +1764,7 @@ public static partial class AcBinaryDeserializer /// /// Object kihagyása metadata nĂ©lkĂĽl — nem támogatott, mert nem tudjuk a property számot. /// - private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) + private static void SkipObject(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) { throw new NotSupportedException( "SkipObject nem támogatott metadata nĂ©lkĂĽl. " + @@ -1774,7 +1775,7 @@ public static partial class AcBinaryDeserializer /// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst. /// /// -1 = not cached, 0+ = register at this cache index - private static void SkipObjectWithMetadata(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex) + private static void SkipObjectWithMetadata(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex) { if (cacheIndex >= 0) { @@ -1784,7 +1785,7 @@ public static partial class AcBinaryDeserializer var propNameHash = context.ReadInt32Raw(); - var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash); + var sourceHashes = context.FindSourceHashes(propNameHash); if (sourceHashes == null) { var propCount = (int)context.ReadVarUInt(); @@ -1793,32 +1794,32 @@ public static partial class AcBinaryDeserializer { sourceHashes[i] = context.ReadInt32Raw(); } - context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes); + context.RegisterInlineMetadata(propNameHash, sourceHashes); } // Skip all properties for (var i = 0; i < sourceHashes.Length; i++) { - SkipValue(ref context, metaData); + SkipValue(context, metaData); } } - private static void SkipArray(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) + private static void SkipArray(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) { var count = (int)context.ReadVarUInt(); for (int i = 0; i < count; i++) { - SkipValue(ref context, metaData); + SkipValue(context, metaData); } } - private static void SkipDictionary(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) + private static void SkipDictionary(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData) { var count = (int)context.ReadVarUInt(); for (int i = 0; i < count; i++) { - SkipValue(ref context, metaData); // key - SkipValue(ref context, metaData); // value + SkipValue(context, metaData); // key + SkipValue(context, metaData); // value } }