Refactor: unify deserialization context into single class
Eliminate BinaryDeserializationContextClass and merge all buffer, pooling, and cache logic into a single sealed BinaryDeserializationContext. All pooling and state management is now handled directly by the context, reducing allocations and indirection. All deserialization entry points and internal logic are updated to use the new context class and pool. String interning, metadata, and array pooling are now managed within the unified context, improving performance and maintainability.
This commit is contained in:
parent
97e4315d12
commit
4c6342aa2b
|
|
@ -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<short>(ref _buffer[_position]);
|
||||||
|
_position += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ushort ReadUInt16Unsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(2);
|
||||||
|
var value = Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
|
||||||
|
_position += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public char ReadCharUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(2);
|
||||||
|
var value = (char)Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
|
||||||
|
_position += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public float ReadSingleUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(4);
|
||||||
|
var bits = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
|
||||||
|
_position += 4;
|
||||||
|
return BitConverter.Int32BitsToSingle(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public double ReadDoubleUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(8);
|
||||||
|
var bits = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
|
||||||
|
_position += 8;
|
||||||
|
return BitConverter.Int64BitsToDouble(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public decimal ReadDecimalUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(16);
|
||||||
|
var span = _buffer.AsSpan(_position, 16);
|
||||||
|
var ints = MemoryMarshal.Cast<byte, int>(span);
|
||||||
|
var lo = ints[0];
|
||||||
|
var mid = ints[1];
|
||||||
|
var hi = ints[2];
|
||||||
|
var flags = ints[3];
|
||||||
|
var isNegative = (flags & unchecked((int)0x80000000)) != 0;
|
||||||
|
var scale = (byte)((flags >> 16) & 0x7F);
|
||||||
|
_position += 16;
|
||||||
|
return new decimal(lo, mid, hi, isNegative, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public DateTime ReadDateTimeUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(9);
|
||||||
|
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
|
||||||
|
var kind = (DateTimeKind)_buffer[_position + 8];
|
||||||
|
_position += 9;
|
||||||
|
return new DateTime(ticks, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public DateTimeOffset ReadDateTimeOffsetUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(10);
|
||||||
|
var utcTicks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
|
||||||
|
var offsetMinutes = Unsafe.ReadUnaligned<short>(ref _buffer[_position + 8]);
|
||||||
|
_position += 10;
|
||||||
|
var utcValue = new DateTime(utcTicks, DateTimeKind.Utc);
|
||||||
|
return new DateTimeOffset(utcValue).ToOffset(TimeSpan.FromMinutes(offsetMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public TimeSpan ReadTimeSpanUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(8);
|
||||||
|
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
|
||||||
|
_position += 8;
|
||||||
|
return new TimeSpan(ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Guid ReadGuidUnsafe()
|
||||||
|
{
|
||||||
|
EnsureAvailable(16);
|
||||||
|
var value = new Guid(_buffer.AsSpan(_position, 16));
|
||||||
|
_position += 16;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int ReadInt32Raw()
|
||||||
|
{
|
||||||
|
EnsureAvailable(4);
|
||||||
|
var value = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
|
||||||
|
_position += 4;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region VarInt Reading
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int ReadVarInt()
|
||||||
|
{
|
||||||
|
var raw = ReadVarUInt();
|
||||||
|
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public uint ReadVarUInt()
|
||||||
|
{
|
||||||
|
// Fast path: single byte (0-127) - ~70% of cases
|
||||||
|
var b0 = _buffer[_position];
|
||||||
|
if ((b0 & 0x80) == 0)
|
||||||
|
{
|
||||||
|
_position++;
|
||||||
|
return b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path: two bytes (128-16383) - ~25% of cases
|
||||||
|
if (_position + 1 < _bufferLength)
|
||||||
|
{
|
||||||
|
var b1 = _buffer[_position + 1];
|
||||||
|
if ((b1 & 0x80) == 0)
|
||||||
|
{
|
||||||
|
_position += 2;
|
||||||
|
return (uint)(b0 & 0x7F) | ((uint)b1 << 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: 3+ bytes - ~5% of cases
|
||||||
|
return ReadVarUIntSlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint ReadVarUIntSlow()
|
||||||
|
{
|
||||||
|
uint value = 0;
|
||||||
|
var shift = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var b = ReadByte();
|
||||||
|
value |= (uint)(b & 0x7F) << shift;
|
||||||
|
if ((b & 0x80) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
shift += 7;
|
||||||
|
if (shift > 35)
|
||||||
|
{
|
||||||
|
throw new AcBinaryDeserializationException("Invalid VarUInt encoding.", _position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public long ReadVarLong()
|
||||||
|
{
|
||||||
|
var raw = ReadVarULong();
|
||||||
|
var value = (long)(raw >> 1) ^ -((long)raw & 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ulong ReadVarULong()
|
||||||
|
{
|
||||||
|
ulong value = 0;
|
||||||
|
var shift = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var b = ReadByte();
|
||||||
|
value |= (ulong)(b & 0x7F) << shift;
|
||||||
|
if ((b & 0x80) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
shift += 7;
|
||||||
|
if (shift > 70)
|
||||||
|
{
|
||||||
|
throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Bytes & String Reading
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public byte[] ReadBytes(int length)
|
||||||
|
{
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureAvailable(length);
|
||||||
|
var result = GC.AllocateUninitializedArray<byte>(length);
|
||||||
|
_buffer.AsSpan(_position, length).CopyTo(result);
|
||||||
|
_position += length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public string ReadStringUtf8(int length)
|
||||||
|
{
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureAvailable(length);
|
||||||
|
|
||||||
|
// WASM optimization: cache short strings to reduce allocations
|
||||||
|
if (_useStringCaching && length <= _maxCachedStringLength)
|
||||||
|
{
|
||||||
|
return ReadStringUtf8Cached(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = Utf8NoBom.GetString(_buffer, _position, length);
|
||||||
|
_position += length;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadStringUtf8Cached(int length)
|
||||||
|
{
|
||||||
|
var slice = _buffer.AsSpan(_position, length);
|
||||||
|
var hash = ComputeStringHashFull(slice);
|
||||||
|
|
||||||
|
if (_stringCache!.TryGetValue(hash, out var cached))
|
||||||
|
{
|
||||||
|
if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice))
|
||||||
|
{
|
||||||
|
_position += length;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = Utf8NoBom.GetString(slice);
|
||||||
|
_stringCache[hash] = value;
|
||||||
|
_position += length;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
|
||||||
|
{
|
||||||
|
return Ascii.Equals(utf8Bytes, cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full-content hash for string caching.
|
||||||
|
/// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names.
|
||||||
|
/// See BinaryDeserializationContext for full history.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int ComputeStringHashFull(ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length <= 32)
|
||||||
|
{
|
||||||
|
var hash = new HashCode();
|
||||||
|
hash.AddBytes(data);
|
||||||
|
return hash.ToHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = new HashCode();
|
||||||
|
h.Add(data.Length);
|
||||||
|
h.AddBytes(data.Slice(0, 8));
|
||||||
|
h.AddBytes(data.Slice(data.Length - 8, 8));
|
||||||
|
h.AddBytes(data.Slice(data.Length / 2 - 4, 8));
|
||||||
|
return h.ToHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureAvailable(int length)
|
||||||
|
{
|
||||||
|
if (_position > _bufferLength - length)
|
||||||
|
{
|
||||||
|
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,87 +1,161 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Binaries;
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
public static partial class AcBinaryDeserializer
|
public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binary deserialization context. Public for generated serializers.
|
/// Pool for BinaryDeserializationContext instances.
|
||||||
/// Uses composition with pooled BinaryDeserializationContextClass for zero-alloc deserialization.
|
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
|
||||||
/// String cache and intern cache index are delegated to ContextClass for pool reuse.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ref struct BinaryDeserializationContext
|
private static class DeserializationContextPool
|
||||||
{
|
{
|
||||||
private readonly ReadOnlySpan<byte> _buffer;
|
private static readonly ConcurrentQueue<BinaryDeserializationContext> Pool = new();
|
||||||
private int _position;
|
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed partial class BinaryDeserializationContext
|
||||||
|
: AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
|
||||||
|
{
|
||||||
// Marker-based interning: sequential cache (no footer needed)
|
// Marker-based interning: sequential cache (no footer needed)
|
||||||
// StringInternFirst/ObjectRefFirst markers register values in order
|
private object?[]? _internCache;
|
||||||
private object?[]? _internCache; // Shared cache for interned strings AND IId objects
|
|
||||||
|
|
||||||
/// <summary>
|
public bool HasMetadata;
|
||||||
/// Heap-allocated context class for IId-based reference tracking.
|
public bool IsMergeMode;
|
||||||
/// Pooled via DeserializationContextClassPool — holds caches, options, metadata.
|
public bool RemoveOrphanedItems;
|
||||||
/// </summary>
|
|
||||||
public readonly BinaryDeserializationContextClass ContextClass;
|
|
||||||
|
|
||||||
public bool HasMetadata { get; private set; }
|
// Options-derived properties
|
||||||
/// <summary>
|
public byte MinStringInternLength => Options.MinStringInternLength;
|
||||||
/// Convenience property - true if any reference handling is enabled.
|
|
||||||
/// </summary>
|
|
||||||
//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;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chain reference tracker for maintaining object identity across chain operations.
|
/// Chain reference tracker for maintaining object identity across chain operations.
|
||||||
/// Only set when in chain mode (CreateDeserializeChain).
|
/// Only set when in chain mode (CreateDeserializeChain).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AcSerializerCommon.ChainReferenceTracker? ChainTracker { readonly get; set; }
|
public AcSerializerCommon.ChainReferenceTracker? ChainTracker;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if in chain mode (ChainTracker is set).
|
/// Returns true if in chain mode (ChainTracker is set).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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<int, string>? _stringCache;
|
||||||
|
|
||||||
|
// Intern cache index counter
|
||||||
|
private int _nextCacheIndex;
|
||||||
|
|
||||||
|
// Linearized buffer for ReadOnlySequence<byte> input
|
||||||
|
private byte[]? _linearizedBuffer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a deserialization context with a pooled ContextClass.
|
/// Inline metadata entries flat array.
|
||||||
/// ContextClass must already be Reset() with options before passing in.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BinaryDeserializationContext(ReadOnlySpan<byte> data, BinaryDeserializationContextClass contextClass)
|
private MetadataEntry[]? _metadataEntries;
|
||||||
|
private int _metadataEntryCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A metadata entry for the deserializer.
|
||||||
|
/// </summary>
|
||||||
|
internal struct MetadataEntry
|
||||||
|
{
|
||||||
|
public int PropNameHash;
|
||||||
|
public int[] PropertyHashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for creating BinaryDeserializeTypeMetadata instances.
|
||||||
|
/// </summary>
|
||||||
|
protected override Func<Type, BinaryDeserializeTypeMetadata> MetadataFactory
|
||||||
|
=> static t => new BinaryDeserializeTypeMetadata(t);
|
||||||
|
|
||||||
|
public BinaryDeserializationContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the buffer for a new deserialization operation from a byte array.
|
||||||
|
/// Zero-copy: context references the byte[] directly.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void InitBuffer(byte[] data, int length)
|
||||||
{
|
{
|
||||||
_buffer = data;
|
_buffer = data;
|
||||||
|
_bufferLength = length;
|
||||||
_position = 0;
|
_position = 0;
|
||||||
|
_useStringCaching = Options.UseStringCaching;
|
||||||
// Marker-based interning fields
|
_maxCachedStringLength = Options.MaxCachedStringLength;
|
||||||
|
if (_useStringCaching) GetOrCreateStringCache();
|
||||||
_internCache = null;
|
_internCache = null;
|
||||||
|
|
||||||
HasMetadata = false;
|
HasMetadata = false;
|
||||||
IsMergeMode = false;
|
IsMergeMode = false;
|
||||||
RemoveOrphanedItems = false;
|
RemoveOrphanedItems = false;
|
||||||
ChainTracker = null;
|
ChainTracker = null;
|
||||||
ContextClass = contextClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the buffer from a ReadOnlySpan by copying to a linearized buffer.
|
||||||
|
/// Used when the source is not a byte[] (e.g. stackalloc, pinned).
|
||||||
|
/// </summary>
|
||||||
|
public void InitBufferFromSpan(ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
var buffer = RentLinearizedBuffer(data.Length);
|
||||||
|
data.CopyTo(buffer);
|
||||||
|
InitBuffer(buffer, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Header
|
||||||
|
|
||||||
public void ReadHeader()
|
public void ReadHeader()
|
||||||
{
|
{
|
||||||
if (_buffer.Length < 2)
|
if (_position == 0 && IsAtEnd)
|
||||||
{
|
{
|
||||||
throw new AcBinaryDeserializationException("Binary payload is too short to contain a header.");
|
throw new AcBinaryDeserializationException("Binary payload is too short to contain a header.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = ReadByteInternal();
|
var version = ReadByte();
|
||||||
if (version != AcBinarySerializerOptions.FormatVersion)
|
if (version != AcBinarySerializerOptions.FormatVersion)
|
||||||
{
|
{
|
||||||
throw new AcBinaryDeserializationException(
|
throw new AcBinaryDeserializationException(
|
||||||
|
|
@ -89,38 +163,36 @@ public static partial class AcBinaryDeserializer
|
||||||
_position - 1);
|
_position - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var marker = ReadByteInternal();
|
var marker = ReadByte();
|
||||||
var hasPropertyTable = false;
|
var hasPropertyTable = false;
|
||||||
|
|
||||||
if (marker == BinaryTypeCode.MetadataHeader)
|
if (marker == BinaryTypeCode.MetadataHeader)
|
||||||
{
|
{
|
||||||
hasPropertyTable = true;
|
hasPropertyTable = true;
|
||||||
ContextClass.Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
|
Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
|
||||||
}
|
}
|
||||||
else if (marker == BinaryTypeCode.NoMetadataHeader)
|
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)
|
else if ((marker & 0xF0) == BinaryTypeCode.HeaderFlagsBase)
|
||||||
{
|
{
|
||||||
var flags = (byte)(marker & 0x0F);
|
var flags = (byte)(marker & 0x0F);
|
||||||
hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
|
hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
|
||||||
// Decode ReferenceHandlingMode from separate bits
|
|
||||||
var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
|
var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
|
||||||
var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
|
var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
|
||||||
ContextClass.Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
|
Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
|
||||||
: hasOnlyId ? ReferenceHandlingMode.OnlyId
|
: hasOnlyId ? ReferenceHandlingMode.OnlyId
|
||||||
: ReferenceHandlingMode.None;
|
: ReferenceHandlingMode.None;
|
||||||
|
|
||||||
// Read cache count if flag is set (marker-based format)
|
|
||||||
var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
|
var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
|
||||||
if (hasCacheCount)
|
if (hasCacheCount)
|
||||||
{
|
{
|
||||||
var cacheCount = (int)ReadVarUInt();
|
var cacheCount = (int)ReadVarUInt();
|
||||||
if (cacheCount > 0)
|
if (cacheCount > 0)
|
||||||
{
|
{
|
||||||
_internCache = ContextClass.RentInternCache(cacheCount);
|
_internCache = RentInternCache(cacheCount);
|
||||||
ContextClass.SetInternCacheUsed(cacheCount);
|
SetInternCacheUsed(cacheCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,407 +206,31 @@ public static partial class AcBinaryDeserializer
|
||||||
HasMetadata = hasPropertyTable;
|
HasMetadata = hasPropertyTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
#endregion
|
||||||
public byte ReadByte() => ReadByteInternal();
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
#region String Interning
|
||||||
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<byte, int>(_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimized VarInt reader with fast path for 1-2 byte values (most common case).
|
/// Next intern cache index to assign when registering interned values.
|
||||||
/// Uses ZigZag decoding to handle signed integers.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
internal ref int NextCacheIndexRef
|
||||||
public int ReadVarInt()
|
|
||||||
{
|
{
|
||||||
var raw = ReadVarUInt();
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
// ZigZag decode: handle full uint range before casting to int
|
get => ref _nextCacheIndex;
|
||||||
// This correctly handles values like int.MaxValue which encode to uint > int.MaxValue
|
|
||||||
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optimized VarUInt reader with fast path for 1-2 byte values.
|
|
||||||
/// Most VarInts in real data are small (property indices, array lengths, etc.)
|
|
||||||
/// </summary>
|
|
||||||
[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<byte>();
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureAvailable(length);
|
|
||||||
var result = GC.AllocateUninitializedArray<byte>(length);
|
|
||||||
_buffer.Slice(_position, length).CopyTo(result);
|
|
||||||
_position += length;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read UTF8 string with optional caching for WASM optimization.
|
|
||||||
/// </summary>
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read string with caching - reduces allocations in WASM.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < cached.Length; i++)
|
|
||||||
{
|
|
||||||
if ((byte)cached[i] != utf8Bytes[i])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static int ComputeStringHashFull(ReadOnlySpan<byte> 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads a raw 4-byte int32 (little-endian) without type code.
|
|
||||||
/// Used for reading type name hashes in UseMetadata mode.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public int ReadInt32Raw()
|
|
||||||
{
|
|
||||||
EnsureAvailable(4);
|
|
||||||
var value = Unsafe.ReadUnaligned<int>(ref Unsafe.AsRef(in _buffer[_position]));
|
|
||||||
_position += 4;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void Skip(int count)
|
|
||||||
{
|
|
||||||
EnsureAvailable(count);
|
|
||||||
_position += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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, ...).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void RegisterNextInternedValue(object value)
|
public void RegisterNextInternedValue(object value)
|
||||||
{
|
{
|
||||||
_internCache![ContextClass.NextCacheIndexRef++] = value;
|
_internCache![_nextCacheIndex++] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers an interned value at a specific cache index.
|
|
||||||
/// Used when the serializer writes explicit cache indices (non-sequential).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void RegisterInternedValueAt(int cacheIndex, object value)
|
public void RegisterInternedValueAt(int cacheIndex, object value)
|
||||||
{
|
{
|
||||||
_internCache![cacheIndex] = value;
|
_internCache![cacheIndex] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an interned string by cache index (StringInterned type code).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public string GetInternedString(int cacheIndex)
|
public string GetInternedString(int cacheIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -549,9 +245,6 @@ public static partial class AcBinaryDeserializer
|
||||||
return (string)result;
|
return (string)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an interned object by cache index (ObjectRef type code).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public object GetInternedObject(int cacheIndex)
|
public object GetInternedObject(int cacheIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -566,13 +259,155 @@ public static partial class AcBinaryDeserializer
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureAvailable(int length)
|
#endregion
|
||||||
|
|
||||||
|
#region Pooled Arrays & Caches (merged from ContextClass)
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal Dictionary<int, string> GetOrCreateStringCache()
|
||||||
{
|
{
|
||||||
if (_position > _buffer.Length - length)
|
return _stringCache ??= new Dictionary<int, string>(128);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents a linearized buffer for ReadOnlySequence multi-segment input.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal byte[] RentLinearizedBuffer(int minSize)
|
||||||
|
{
|
||||||
|
if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize)
|
||||||
|
return _linearizedBuffer;
|
||||||
|
|
||||||
|
if (_linearizedBuffer != null)
|
||||||
|
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
|
||||||
|
|
||||||
|
_linearizedBuffer = ArrayPool<byte>.Shared.Rent(minSize);
|
||||||
|
return _linearizedBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] RentDupData(int minLength)
|
||||||
|
{
|
||||||
|
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
|
||||||
|
return _pooledDupData;
|
||||||
|
|
||||||
|
if (_pooledDupData != null)
|
||||||
|
ArrayPool<int>.Shared.Return(_pooledDupData);
|
||||||
|
|
||||||
|
_pooledDupData = ArrayPool<int>.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<object?>.Shared.Return(_pooledInternCache, clearArray: true);
|
||||||
|
|
||||||
|
_pooledInternCache = ArrayPool<object?>.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<object?>.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<int>.Shared.Return(_pooledDupData);
|
||||||
|
_pooledDupData = null;
|
||||||
|
_pooledDupDataLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linearized buffer: no GC roots (byte[]), keep small, return large
|
||||||
|
if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
|
||||||
|
_linearizedBuffer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pool for BinaryDeserializationContextClass instances.
|
|
||||||
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
|
|
||||||
/// </summary>
|
|
||||||
private static class DeserializationContextClassPool
|
|
||||||
{
|
|
||||||
private static readonly ConcurrentQueue<BinaryDeserializationContextClass> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Factory for creating BinaryDeserializeTypeMetadata instances.
|
|
||||||
/// </summary>
|
|
||||||
protected override Func<Type, BinaryDeserializeTypeMetadata> 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<int, string>? _stringCache;
|
|
||||||
|
|
||||||
// Intern cache index counter - moved from ref struct for pool reuse
|
|
||||||
private int _nextCacheIndex;
|
|
||||||
|
|
||||||
// Linearized buffer for ReadOnlySequence<byte> input
|
|
||||||
private byte[]? _linearizedBuffer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String cache for WASM optimization. Reused across deserializations.
|
|
||||||
/// </summary>
|
|
||||||
internal Dictionary<int, string>? StringCache => _stringCache;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or lazily creates the string cache with initial capacity.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal Dictionary<int, string> GetOrCreateStringCache()
|
|
||||||
{
|
|
||||||
return _stringCache ??= new Dictionary<int, string>(128);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Next intern cache index to assign when registering interned values.
|
|
||||||
/// </summary>
|
|
||||||
internal ref int NextCacheIndexRef
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => ref _nextCacheIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a linearized buffer for ReadOnlySequence multi-segment input.
|
|
||||||
/// Buffer is pooled and reused across deserializations.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal byte[] RentLinearizedBuffer(int minSize)
|
|
||||||
{
|
|
||||||
if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize)
|
|
||||||
return _linearizedBuffer;
|
|
||||||
|
|
||||||
if (_linearizedBuffer != null)
|
|
||||||
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
|
|
||||||
|
|
||||||
_linearizedBuffer = ArrayPool<byte>.Shared.Rent(minSize);
|
|
||||||
return _linearizedBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
private MetadataEntry[]? _metadataEntries;
|
|
||||||
private int _metadataEntryCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Egy metadata bejegyzés a deserializer számára.
|
|
||||||
/// </summary>
|
|
||||||
internal struct MetadataEntry
|
|
||||||
{
|
|
||||||
public int PropNameHash; // source típus propNameHash (FNV-1a)
|
|
||||||
public int[] PropertyHashes; // source property hash-ek sorrendben
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryDeserializationContextClass()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or allocates a pooled int[] for dup data (position/cacheIndex pairs).
|
|
||||||
/// </summary>
|
|
||||||
public int[] RentDupData(int minLength)
|
|
||||||
{
|
|
||||||
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
|
|
||||||
return _pooledDupData;
|
|
||||||
|
|
||||||
// Too small - return old and rent bigger
|
|
||||||
if (_pooledDupData != null)
|
|
||||||
ArrayPool<int>.Shared.Return(_pooledDupData);
|
|
||||||
|
|
||||||
_pooledDupData = ArrayPool<int>.Shared.Rent(minLength);
|
|
||||||
_pooledDupDataLength = _pooledDupData.Length;
|
|
||||||
return _pooledDupData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or allocates a pooled object?[] for intern cache.
|
|
||||||
/// </summary>
|
|
||||||
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<object?>.Shared.Return(_pooledInternCache, clearArray: true);
|
|
||||||
|
|
||||||
_pooledInternCache = ArrayPool<object?>.Shared.Rent(minLength);
|
|
||||||
_pooledInternCacheLength = _pooledInternCache.Length;
|
|
||||||
_lastInternCacheUsed = 0;
|
|
||||||
return _pooledInternCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks how many intern cache slots were used (for targeted Clear).
|
|
||||||
/// </summary>
|
|
||||||
public void SetInternCacheUsed(int count)
|
|
||||||
{
|
|
||||||
_lastInternCacheUsed = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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).
|
|
||||||
/// </summary>
|
|
||||||
[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<object?>.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<int>.Shared.Return(_pooledDupData);
|
|
||||||
_pooledDupData = null;
|
|
||||||
_pooledDupDataLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linearized buffer: no GC roots (byte[]), keep small, return large
|
|
||||||
if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold)
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
|
|
||||||
_linearizedBuffer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Reset(AcBinarySerializerOptions options)
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
base.Reset(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -55,13 +55,13 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
// Cross-type path: use index mapping
|
// Cross-type path: use index mapping
|
||||||
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var result = ReadValueWithMapping(ref context, destType, indexMapping, 0);
|
var result = ReadValueWithMapping(context, destType, indexMapping, 0);
|
||||||
return (TDest?)result;
|
return (TDest?)result;
|
||||||
}
|
}
|
||||||
catch (AcBinaryDeserializationException)
|
catch (AcBinaryDeserializationException)
|
||||||
|
|
@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,8 +141,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
// Cross-type path: use index mapping
|
// Cross-type path: use index mapping
|
||||||
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -158,7 +158,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
if (typeCode == BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume Object marker
|
context.ReadByte(); // consume Object marker
|
||||||
PopulateObjectWithMapping(ref context, target, destType, indexMapping, 0);
|
PopulateObjectWithMapping(context, target, destType, indexMapping, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,16 +207,16 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Reads a value with index mapping applied.
|
/// Reads a value with index mapping applied.
|
||||||
/// Maps source PropertyIndex to destination PropertyIndex using the provided mapping.
|
/// Maps source PropertyIndex to destination PropertyIndex using the provided mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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();
|
var typeCode = context.ReadByte();
|
||||||
|
|
||||||
return typeCode switch
|
return typeCode switch
|
||||||
{
|
{
|
||||||
BinaryTypeCode.Null => null,
|
BinaryTypeCode.Null => null,
|
||||||
BinaryTypeCode.Object => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: false),
|
BinaryTypeCode.Object => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: false),
|
||||||
BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: true),
|
BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: true),
|
||||||
_ => ReadValue(ref context, destType, depth) // Primitives, arrays, etc. use normal path
|
_ => 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.
|
/// Reads an object using index mapping for property resolution.
|
||||||
/// Note: Object marker already consumed by caller.
|
/// Note: Object marker already consumed by caller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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 metadata = wrapper.Metadata;
|
||||||
|
|
||||||
var obj = CreateInstance(destType, metadata);
|
var obj = CreateInstance(destType, metadata);
|
||||||
|
|
@ -237,7 +237,7 @@ public static partial class AcBinaryDeserializer
|
||||||
context.RegisterNextInternedValue(obj);
|
context.RegisterNextInternedValue(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
PopulateObjectWithMapping(ref context, obj, destType, indexMapping, depth);
|
PopulateObjectWithMapping(context, obj, destType, indexMapping, depth);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
@ -247,13 +247,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Source property indices are remapped to destination indices.
|
/// Source property indices are remapped to destination indices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectWithMapping(
|
private static void PopulateObjectWithMapping(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
object target,
|
object target,
|
||||||
Type destType,
|
Type destType,
|
||||||
int[] indexMapping,
|
int[] indexMapping,
|
||||||
int depth)
|
int depth)
|
||||||
{
|
{
|
||||||
var wrapper = context.ContextClass.GetWrapper(destType);
|
var wrapper = context.GetWrapper(destType);
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
var propertyCount = (int)context.ReadVarUInt();
|
var propertyCount = (int)context.ReadVarUInt();
|
||||||
var nextDepth = depth + 1;
|
var nextDepth = depth + 1;
|
||||||
|
|
@ -268,7 +268,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (destPropIndex == -1)
|
if (destPropIndex == -1)
|
||||||
{
|
{
|
||||||
// No mapping - skip this property
|
// No mapping - skip this property
|
||||||
SkipValue(ref context, metadata);
|
SkipValue(context, metadata);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,12 +277,12 @@ public static partial class AcBinaryDeserializer
|
||||||
if (propInfo == null)
|
if (propInfo == null)
|
||||||
{
|
{
|
||||||
// Destination property not found - skip
|
// Destination property not found - skip
|
||||||
SkipValue(ref context, metadata);
|
SkipValue(context, metadata);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse common populate logic
|
// 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.
|
/// Shared between normal populate and cross-type populate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulatePropertyValue(
|
private static void PopulatePropertyValue(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
object target,
|
object target,
|
||||||
BinaryPropertySetterInfo propInfo,
|
BinaryPropertySetterInfo propInfo,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
|
|
@ -339,7 +339,7 @@ public static partial class AcBinaryDeserializer
|
||||||
context.RegisterNextInternedValue(existingObj);
|
context.RegisterNextInternedValue(existingObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
PopulateObjectCore(ref context, existingObj, wrapper, nextDepth, skipDefaultWrite: false);
|
PopulateObjectCore(context, existingObj, wrapper, nextDepth, skipDefaultWrite: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,7 +351,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (existingCollection is IList existingList)
|
if (existingCollection is IList existingList)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume Array marker
|
context.ReadByte(); // consume Array marker
|
||||||
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
|
PopulateListOptimized(context, existingList, propInfo, nextDepth);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,10 +360,10 @@ public static partial class AcBinaryDeserializer
|
||||||
var positionBeforeRead = context.Position;
|
var positionBeforeRead = context.Position;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
if (TryReadAndSetTypedValue(context, target, propInfo, peekCode))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
|
||||||
propInfo.SetValue(target, value);
|
propInfo.SetValue(target, value);
|
||||||
}
|
}
|
||||||
catch (InvalidCastException ex)
|
catch (InvalidCastException ex)
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,12 @@ public static partial class AcBinaryDeserializer
|
||||||
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
|
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
|
||||||
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
|
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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
|
// UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate
|
||||||
PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false);
|
PopulateObjectCore(context, target, wrapper, depth, skipDefaultWrite: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -46,13 +46,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// No hashcode prefix - position-based footer handles reference tracking.
|
/// No hashcode prefix - position-based footer handles reference tracking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectCore(
|
private static void PopulateObjectCore(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
object target,
|
object target,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
int depth,
|
int depth,
|
||||||
bool skipDefaultWrite)
|
bool skipDefaultWrite)
|
||||||
{
|
{
|
||||||
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
|
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -61,7 +61,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// UseMetadata=false: properties[i] gives the setter directly.
|
/// UseMetadata=false: properties[i] gives the setter directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectPropertiesIndexed(
|
private static void PopulateObjectPropertiesIndexed(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
object target,
|
object target,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
int depth,
|
int depth,
|
||||||
|
|
@ -87,12 +87,12 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
if (propInfo.ExpectedTypeCode.HasValue)
|
if (propInfo.ExpectedTypeCode.HasValue)
|
||||||
{
|
{
|
||||||
ReadAndSetMarkerlessValue(ref context, target, propInfo);
|
ReadAndSetMarkerlessValue(context, target, propInfo);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-markerless properties: standard marker-based read
|
// 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
|
else
|
||||||
|
|
@ -102,7 +102,7 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
|
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.
|
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulatePropertyWithMarker(
|
private static void PopulatePropertyWithMarker(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
object target,
|
object target,
|
||||||
BinaryPropertySetterBase? propInfo,
|
BinaryPropertySetterBase? propInfo,
|
||||||
BinaryDeserializeTypeMetadata metadata,
|
BinaryDeserializeTypeMetadata metadata,
|
||||||
|
|
@ -126,7 +126,7 @@ public static partial class AcBinaryDeserializer
|
||||||
// Nincs megfelelő target property → skip
|
// Nincs megfelelő target property → skip
|
||||||
if (propInfo == null)
|
if (propInfo == null)
|
||||||
{
|
{
|
||||||
SkipValue(ref context, metadata);
|
SkipValue(context, metadata);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,12 +163,12 @@ public static partial class AcBinaryDeserializer
|
||||||
// Merge mode with IId collection: use merge logic
|
// Merge mode with IId collection: use merge logic
|
||||||
if (isMergeMode && propInfo.IsIIdCollection)
|
if (isMergeMode && propInfo.IsIIdCollection)
|
||||||
{
|
{
|
||||||
MergeIIdCollection(ref context, existingList, propInfo, nextDepth);
|
MergeIIdCollection(context, existingList, propInfo, nextDepth);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Normal populate: replace collection contents
|
// Normal populate: replace collection contents
|
||||||
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
|
PopulateListOptimized(context, existingList, propInfo, nextDepth);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +181,10 @@ public static partial class AcBinaryDeserializer
|
||||||
if (existingObj != null)
|
if (existingObj != null)
|
||||||
{
|
{
|
||||||
// ReadValue kezeli mindkét markert
|
// ReadValue kezeli mindkét markert
|
||||||
var nestedValue = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth);
|
||||||
if (nestedValue != null)
|
if (nestedValue != null)
|
||||||
{
|
{
|
||||||
var nestedMeta = context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata;
|
var nestedMeta = context.GetWrapper(propInfo.PropertyType).Metadata;
|
||||||
CopyProperties(nestedValue, existingObj, nestedMeta);
|
CopyProperties(nestedValue, existingObj, nestedMeta);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -197,10 +197,10 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
// Use typed setters for primitives and strings to avoid ReadValue dispatch
|
// Use typed setters for primitives and strings to avoid ReadValue dispatch
|
||||||
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
||||||
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
TryReadAndSetTypedValue(context, target, propInfo, peekCode))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
|
||||||
propInfo.SetValue(target, value);
|
propInfo.SetValue(target, value);
|
||||||
}
|
}
|
||||||
catch (InvalidCastException ex)
|
catch (InvalidCastException ex)
|
||||||
|
|
@ -227,7 +227,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
switch (propInfo.AccessorType)
|
||||||
{
|
{
|
||||||
|
|
@ -274,16 +274,16 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
|
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void PopulateObject(ref BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
|
private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
|
||||||
{
|
{
|
||||||
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
|
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Populate List Methods
|
#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);
|
var elementType = GetCollectionElementType(listType) ?? typeof(object);
|
||||||
|
|
||||||
|
|
@ -300,7 +300,7 @@ public static partial class AcBinaryDeserializer
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
// ReadValue handles ChainMode internally (ReadObject returns cached instance)
|
// ReadValue handles ChainMode internally (ReadObject returns cached instance)
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
targetList.Add(value);
|
targetList.Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +313,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimized list populate that reuses existing items when possible.
|
/// Optimized list populate that reuses existing items when possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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 elementType = propInfo.ElementType ?? typeof(object);
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
|
|
@ -333,7 +333,7 @@ public static partial class AcBinaryDeserializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wrapper = context.ContextClass.GetWrapper(elementType);
|
var wrapper = context.GetWrapper(elementType);
|
||||||
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
|
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
|
|
@ -347,13 +347,13 @@ public static partial class AcBinaryDeserializer
|
||||||
if (existingItem != null)
|
if (existingItem != null)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume Object marker
|
context.ReadByte(); // consume Object marker
|
||||||
PopulateObjectCore(ref context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
|
PopulateObjectCore(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read new value
|
// Read new value
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
|
|
||||||
if (i < existingCount)
|
if (i < existingCount)
|
||||||
{
|
{
|
||||||
|
|
@ -385,7 +385,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// IId collection merge using cached property info.
|
/// IId collection merge using cached property info.
|
||||||
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
|
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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 elementType = propInfo.ElementType!;
|
||||||
var idGetter = propInfo.ElementIdGetter!;
|
var idGetter = propInfo.ElementIdGetter!;
|
||||||
|
|
@ -416,7 +416,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
var arrayCount = (int)context.ReadVarUInt();
|
var arrayCount = (int)context.ReadVarUInt();
|
||||||
var nextDepth = depth + 1;
|
var nextDepth = depth + 1;
|
||||||
var wrapper = context.ContextClass.GetWrapper(elementType);
|
var wrapper = context.GetWrapper(elementType);
|
||||||
var elementMetadata = wrapper.Metadata;
|
var elementMetadata = wrapper.Metadata;
|
||||||
|
|
||||||
// Track which IDs we see in source (for orphan removal)
|
// Track which IDs we see in source (for orphan removal)
|
||||||
|
|
@ -429,7 +429,7 @@ public static partial class AcBinaryDeserializer
|
||||||
var itemCode = context.PeekByte();
|
var itemCode = context.PeekByte();
|
||||||
if (itemCode != BinaryTypeCode.Object)
|
if (itemCode != BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
existingList.Add(value);
|
existingList.Add(value);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -440,7 +440,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (newItem == null) continue;
|
if (newItem == null) continue;
|
||||||
|
|
||||||
// PopulateObjectCore handles hashcode reading for Non-IId types
|
// 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);
|
var itemId = idGetter(newItem);
|
||||||
if (itemId != null && !IsDefaultValue(itemId, idType))
|
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).
|
/// IId collection merge using type metadata (for top-level list merge).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void MergeIIdCollectionWithMetadata(
|
private static void MergeIIdCollectionWithMetadata(
|
||||||
ref BinaryDeserializationContext context,
|
BinaryDeserializationContext context,
|
||||||
IList existingList,
|
IList existingList,
|
||||||
Type elementType,
|
Type elementType,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
|
|
@ -533,7 +533,7 @@ public static partial class AcBinaryDeserializer
|
||||||
var itemCode = context.PeekByte();
|
var itemCode = context.PeekByte();
|
||||||
if (itemCode != BinaryTypeCode.Object)
|
if (itemCode != BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
existingList.Add(value);
|
existingList.Add(value);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -544,7 +544,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (newItem == null) continue;
|
if (newItem == null) continue;
|
||||||
|
|
||||||
// PopulateObjectCore handles hashcode reading for Non-IId types
|
// 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);
|
var itemId = idGetter(newItem);
|
||||||
if (itemId != null && !IsDefaultValue(itemId, idType))
|
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.
|
/// Reads Id value without type marker. The serializer didn't write a marker for IId types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
switch (idType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,39 +36,39 @@ public static partial class AcBinaryDeserializer
|
||||||
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
|
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
|
||||||
|
|
||||||
// Type dispatch table for fast ReadValue
|
// 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 TypeReader?[] TypeReaders = new TypeReader[byte.MaxValue + 1];
|
||||||
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||||
|
|
||||||
static AcBinaryDeserializer()
|
static AcBinaryDeserializer()
|
||||||
{
|
{
|
||||||
RegisterReader(BinaryTypeCode.Null, static (ref BinaryDeserializationContext _, Type _, int _) => null);
|
RegisterReader(BinaryTypeCode.Null, static (BinaryDeserializationContext _, Type _, int _) => null);
|
||||||
RegisterReader(BinaryTypeCode.True, static (ref BinaryDeserializationContext _, Type _, int _) => true);
|
RegisterReader(BinaryTypeCode.True, static (BinaryDeserializationContext _, Type _, int _) => true);
|
||||||
RegisterReader(BinaryTypeCode.False, static (ref BinaryDeserializationContext _, Type _, int _) => false);
|
RegisterReader(BinaryTypeCode.False, static (BinaryDeserializationContext _, Type _, int _) => false);
|
||||||
RegisterReader(BinaryTypeCode.Int8, static (ref BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte());
|
RegisterReader(BinaryTypeCode.Int8, static (BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte());
|
||||||
RegisterReader(BinaryTypeCode.UInt8, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte());
|
RegisterReader(BinaryTypeCode.UInt8, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte());
|
||||||
RegisterReader(BinaryTypeCode.Int16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe());
|
RegisterReader(BinaryTypeCode.Int16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe());
|
||||||
RegisterReader(BinaryTypeCode.UInt16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe());
|
RegisterReader(BinaryTypeCode.UInt16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe());
|
||||||
RegisterReader(BinaryTypeCode.Int32, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ref ctx, type));
|
RegisterReader(BinaryTypeCode.Int32, static (BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ctx, type));
|
||||||
RegisterReader(BinaryTypeCode.UInt32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt());
|
RegisterReader(BinaryTypeCode.UInt32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt());
|
||||||
RegisterReader(BinaryTypeCode.Int64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong());
|
RegisterReader(BinaryTypeCode.Int64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong());
|
||||||
RegisterReader(BinaryTypeCode.UInt64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong());
|
RegisterReader(BinaryTypeCode.UInt64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong());
|
||||||
RegisterReader(BinaryTypeCode.Float32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe());
|
RegisterReader(BinaryTypeCode.Float32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.Float64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe());
|
RegisterReader(BinaryTypeCode.Float64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.Decimal, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe());
|
RegisterReader(BinaryTypeCode.Decimal, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.Char, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe());
|
RegisterReader(BinaryTypeCode.Char, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.String, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ref ctx));
|
RegisterReader(BinaryTypeCode.String, static (BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ctx));
|
||||||
RegisterReader(BinaryTypeCode.StringInterned, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()));
|
RegisterReader(BinaryTypeCode.StringInterned, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()));
|
||||||
RegisterReader(BinaryTypeCode.StringEmpty, static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty);
|
RegisterReader(BinaryTypeCode.StringEmpty, static (BinaryDeserializationContext _, Type _, int _) => string.Empty);
|
||||||
// StringInternFirst: first occurrence of interned string - read cacheIndex + content + register in cache
|
// StringInternFirst: first occurrence of interned string - read cacheIndex + content + register in cache
|
||||||
RegisterReader(BinaryTypeCode.StringInternFirst, static (ref BinaryDeserializationContext ctx, Type _, int _) =>
|
RegisterReader(BinaryTypeCode.StringInternFirst, static (BinaryDeserializationContext ctx, Type _, int _) =>
|
||||||
ReadAndRegisterInternedString(ref ctx));
|
ReadAndRegisterInternedString(ctx));
|
||||||
RegisterReader(BinaryTypeCode.DateTime, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe());
|
RegisterReader(BinaryTypeCode.DateTime, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.DateTimeOffset, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe());
|
RegisterReader(BinaryTypeCode.DateTimeOffset, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.TimeSpan, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe());
|
RegisterReader(BinaryTypeCode.TimeSpan, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.Guid, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe());
|
RegisterReader(BinaryTypeCode.Guid, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe());
|
||||||
RegisterReader(BinaryTypeCode.Enum, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ref ctx, type));
|
RegisterReader(BinaryTypeCode.Enum, static (BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ctx, type));
|
||||||
RegisterReader(BinaryTypeCode.Object, ReadObject);
|
RegisterReader(BinaryTypeCode.Object, ReadObject);
|
||||||
RegisterReader(BinaryTypeCode.ObjectRefFirst, ReadObjectRefFirst);
|
RegisterReader(BinaryTypeCode.ObjectRefFirst, ReadObjectRefFirst);
|
||||||
RegisterReader(BinaryTypeCode.ObjectWithMetadata, ReadObjectWithMetadata);
|
RegisterReader(BinaryTypeCode.ObjectWithMetadata, ReadObjectWithMetadata);
|
||||||
|
|
@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer
|
||||||
RegisterReader(BinaryTypeCode.ObjectRef, ReadObjectRef);
|
RegisterReader(BinaryTypeCode.ObjectRef, ReadObjectRef);
|
||||||
RegisterReader(BinaryTypeCode.Array, ReadArray);
|
RegisterReader(BinaryTypeCode.Array, ReadArray);
|
||||||
RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary);
|
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)
|
// Register FixStr readers (34-65)
|
||||||
for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++)
|
for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++)
|
||||||
|
|
@ -93,9 +93,9 @@ public static partial class AcBinaryDeserializer
|
||||||
private static TypeReader CreateFixStrReader(int length)
|
private static TypeReader CreateFixStrReader(int length)
|
||||||
{
|
{
|
||||||
if (length == 0)
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -136,13 +136,13 @@ public static partial class AcBinaryDeserializer
|
||||||
return (T?)(object?)DeserializeExpression(data, targetType, options);
|
return (T?)(object?)DeserializeExpression(data, targetType, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var result = ReadValue(ref context, targetType, 0);
|
var result = ReadValue(context, targetType, 0);
|
||||||
// Position-based string interning - no validation needed
|
// Position-based string interning - no validation needed
|
||||||
return (T?)result;
|
return (T?)result;
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +158,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,13 +182,13 @@ public static partial class AcBinaryDeserializer
|
||||||
return DeserializeExpression(data, targetType, options);
|
return DeserializeExpression(data, targetType, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var result = ReadValue(ref context, targetType, 0);
|
var result = ReadValue(context, targetType, 0);
|
||||||
// Position-based string interning - no validation needed
|
// Position-based string interning - no validation needed
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,16 +225,16 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.IsSingleSegment)
|
if (data.IsSingleSegment)
|
||||||
return Deserialize<T>(data.FirstSpan, options);
|
return Deserialize<T>(data.FirstSpan, options);
|
||||||
|
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var buffer = ctxClass.RentLinearizedBuffer((int)data.Length);
|
var buffer = context.RentLinearizedBuffer((int)data.Length);
|
||||||
data.CopyTo(buffer);
|
data.CopyTo(buffer);
|
||||||
return Deserialize<T>(buffer.AsSpan(0, (int)data.Length), ctxClass);
|
return Deserialize<T>(buffer.AsSpan(0, (int)data.Length), context);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,35 +254,35 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.IsSingleSegment)
|
if (data.IsSingleSegment)
|
||||||
return Deserialize(data.FirstSpan, targetType, options);
|
return Deserialize(data.FirstSpan, targetType, options);
|
||||||
|
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var buffer = ctxClass.RentLinearizedBuffer((int)data.Length);
|
var buffer = context.RentLinearizedBuffer((int)data.Length);
|
||||||
data.CopyTo(buffer);
|
data.CopyTo(buffer);
|
||||||
return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, ctxClass);
|
return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, context);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path).
|
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static T? Deserialize<T>(ReadOnlySpan<byte> data, BinaryDeserializationContextClass ctxClass)
|
private static T? Deserialize<T>(ReadOnlySpan<byte> data, BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
||||||
|
|
||||||
var targetType = typeof(T);
|
var targetType = typeof(T);
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
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
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
return (T?)ReadValue(ref context, targetType, 0);
|
return (T?)ReadValue(context, targetType, 0);
|
||||||
}
|
}
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
catch (AcBinaryDeserializationException) { throw; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -294,20 +294,20 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path).
|
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? Deserialize(ReadOnlySpan<byte> data, Type targetType, BinaryDeserializationContextClass ctxClass)
|
private static object? Deserialize(ReadOnlySpan<byte> data, Type targetType, BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
|
||||||
|
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
return DeserializeExpression(data, targetType, ctxClass);
|
return DeserializeExpression(data, targetType, context);
|
||||||
|
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
return ReadValue(ref context, targetType, 0);
|
return ReadValue(context, targetType, 0);
|
||||||
}
|
}
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
catch (AcBinaryDeserializationException) { throw; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -319,15 +319,15 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: DeserializeExpression with pre-pooled ContextClass.
|
/// Internal: DeserializeExpression with pre-pooled context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, BinaryDeserializationContextClass ctxClass)
|
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var node = (AcExpressionNode?)ReadValue(ref context, typeof(AcExpressionNode), 0);
|
var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0);
|
||||||
if (node == null) return null;
|
if (node == null) return null;
|
||||||
|
|
||||||
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
|
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
|
||||||
|
|
@ -348,13 +348,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, AcBinarySerializerOptions options)
|
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, AcBinarySerializerOptions options)
|
||||||
{
|
{
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
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
|
// Position-based string interning - no validation needed
|
||||||
if (node == null) return null;
|
if (node == null) return null;
|
||||||
|
|
||||||
|
|
@ -373,7 +373,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
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;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
|
||||||
|
|
||||||
var targetType = target.GetType();
|
var targetType = target.GetType();
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass);
|
context.InitBufferFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -416,18 +416,18 @@ public static partial class AcBinaryDeserializer
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
if (typeCode == BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume Object marker
|
context.ReadByte(); // consume Object marker
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume ObjectWithMetadata marker
|
context.ReadByte(); // consume ObjectWithMetadata marker
|
||||||
ReadInlineMetadataForPopulate(ref context, targetType);
|
ReadInlineMetadataForPopulate(context, targetType);
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
||||||
{
|
{
|
||||||
context.ReadByte(); // consume Array marker
|
context.ReadByte(); // consume Array marker
|
||||||
PopulateList(ref context, targetList, targetType, 0);
|
PopulateList(context, targetList, targetType, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -450,7 +450,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -475,12 +475,10 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
var opts = options ?? AcBinarySerializerOptions.Default;
|
var opts = options ?? AcBinarySerializerOptions.Default;
|
||||||
var targetType = target.GetType();
|
var targetType = target.GetType();
|
||||||
var ctxClass = DeserializationContextClassPool.Get(opts);
|
var context = DeserializationContextPool.Get(opts);
|
||||||
var context = new BinaryDeserializationContext(data, ctxClass)
|
context.InitBufferFromSpan(data);
|
||||||
{
|
context.IsMergeMode = true;
|
||||||
IsMergeMode = true,
|
context.RemoveOrphanedItems = opts.RemoveOrphanedItems;
|
||||||
RemoveOrphanedItems = opts.RemoveOrphanedItems
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -490,13 +488,13 @@ public static partial class AcBinaryDeserializer
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
if (typeCode == BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
ReadInlineMetadataForPopulate(ref context, targetType);
|
ReadInlineMetadataForPopulate(context, targetType);
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
||||||
{
|
{
|
||||||
|
|
@ -505,19 +503,19 @@ public static partial class AcBinaryDeserializer
|
||||||
var elementType = GetCollectionElementType(targetType);
|
var elementType = GetCollectionElementType(targetType);
|
||||||
if (elementType != null)
|
if (elementType != null)
|
||||||
{
|
{
|
||||||
var wrapper = context.ContextClass.GetWrapper(elementType);
|
var wrapper = context.GetWrapper(elementType);
|
||||||
var elementMetadata = wrapper.Metadata;
|
var elementMetadata = wrapper.Metadata;
|
||||||
|
|
||||||
if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null)
|
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
|
// Position-based string interning - no validation needed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-IId collection, just populate
|
// Non-IId collection, just populate
|
||||||
PopulateList(ref context, targetList, targetType, 0);
|
PopulateList(context, targetList, targetType, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -540,7 +538,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,19 +566,20 @@ public static partial class AcBinaryDeserializer
|
||||||
// Copy data to array for chain storage
|
// Copy data to array for chain storage
|
||||||
var dataArray = data.ToArray();
|
var dataArray = data.ToArray();
|
||||||
var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
|
var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
|
||||||
var ctxClass = DeserializationContextClassPool.Get(options);
|
var context = DeserializationContextPool.Get(options);
|
||||||
var context = new BinaryDeserializationContext(dataArray, ctxClass) { ChainTracker = chainTracker };
|
context.InitBuffer(dataArray, dataArray.Length);
|
||||||
|
context.ChainTracker = chainTracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var result = ReadValue(ref context, targetType, 0);
|
var result = ReadValue(context, targetType, 0);
|
||||||
// Position-based string interning - no validation needed
|
// Position-based string interning - no validation needed
|
||||||
return new BinaryDeserializeChain<T>(dataArray, options, chainTracker, (T?)result);
|
return new BinaryDeserializeChain<T>(dataArray, options, chainTracker, (T?)result);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -614,13 +613,14 @@ public static partial class AcBinaryDeserializer
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
var targetType = typeof(TResult);
|
var targetType = typeof(TResult);
|
||||||
var ctxClass = DeserializationContextClassPool.Get(_options);
|
var context = DeserializationContextPool.Get(_options);
|
||||||
var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker };
|
context.InitBuffer(_data, _data.Length);
|
||||||
|
context.ChainTracker = _chainTracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
var result = ReadValue(ref context, targetType, 0);
|
var result = ReadValue(context, targetType, 0);
|
||||||
// Position-based string interning - no validation needed
|
// Position-based string interning - no validation needed
|
||||||
return (TResult?)result;
|
return (TResult?)result;
|
||||||
}
|
}
|
||||||
|
|
@ -633,7 +633,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextClassPool.Return(ctxClass);
|
DeserializationContextPool.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -643,8 +643,9 @@ public static partial class AcBinaryDeserializer
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
var targetType = target.GetType();
|
var targetType = target.GetType();
|
||||||
var ctxClass = DeserializationContextClassPool.Get(_options);
|
var context = DeserializationContextPool.Get(_options);
|
||||||
var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker };
|
context.InitBuffer(_data, _data.Length);
|
||||||
|
context.ChainTracker = _chainTracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -654,18 +655,18 @@ public static partial class AcBinaryDeserializer
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
if (typeCode == BinaryTypeCode.Object)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
ReadInlineMetadataForPopulate(ref context, targetType);
|
ReadInlineMetadataForPopulate(context, targetType);
|
||||||
PopulateObject(ref context, target, targetType, 0);
|
PopulateObject(context, target, targetType, 0);
|
||||||
}
|
}
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
PopulateList(ref context, targetList, targetType, 0);
|
PopulateList(context, targetList, targetType, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -686,7 +687,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
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.
|
/// Returns true if handled, false if should fall back to generic path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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
|
// Only handle if we have a typed setter
|
||||||
if (propInfo.AccessorType == PropertyAccessorType.Object)
|
if (propInfo.AccessorType == PropertyAccessorType.Object)
|
||||||
|
|
@ -927,7 +928,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (peekCode == BinaryTypeCode.String)
|
if (peekCode == BinaryTypeCode.String)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
propInfo.SetValue(target, ReadPlainString(ref context));
|
propInfo.SetValue(target, ReadPlainString(context));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (peekCode == BinaryTypeCode.StringEmpty)
|
if (peekCode == BinaryTypeCode.StringEmpty)
|
||||||
|
|
@ -945,7 +946,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (peekCode == BinaryTypeCode.StringInternFirst)
|
if (peekCode == BinaryTypeCode.StringInternFirst)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
propInfo.SetValue(target, ReadAndRegisterInternedString(ref context));
|
propInfo.SetValue(target, ReadAndRegisterInternedString(context));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -957,7 +958,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimized value reader using FrozenDictionary dispatch table.
|
/// Optimized value reader using FrozenDictionary dispatch table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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;
|
if (context.IsAtEnd) return null;
|
||||||
|
|
||||||
|
|
@ -983,7 +984,7 @@ public static partial class AcBinaryDeserializer
|
||||||
var reader = TypeReaders[typeCode];
|
var reader = TypeReaders[typeCode];
|
||||||
if (reader != null)
|
if (reader != null)
|
||||||
{
|
{
|
||||||
return reader(ref context, targetType, depth);
|
return reader(context, targetType, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new AcBinaryDeserializationException(
|
throw new AcBinaryDeserializationException(
|
||||||
|
|
@ -995,7 +996,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Sima string olvas<61>sa - NEM regisztr<74>l az intern t<>bl<62>ba.
|
/// Sima string olvas<61>sa - NEM regisztr<74>l az intern t<>bl<62>ba.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static string ReadPlainString(ref BinaryDeserializationContext context)
|
private static string ReadPlainString(BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var length = (int)context.ReadVarUInt();
|
var length = (int)context.ReadVarUInt();
|
||||||
if (length == 0) return string.Empty;
|
if (length == 0) return string.Empty;
|
||||||
|
|
@ -1007,7 +1008,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static string ReadAndRegisterInternedString(ref BinaryDeserializationContext context)
|
private static string ReadAndRegisterInternedString(BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
var length = (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.
|
///// Read a string and register it in the intern table for future references.
|
||||||
///// </summary>
|
///// </summary>
|
||||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
//[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();
|
// var length = (int)context.ReadVarUInt();
|
||||||
// if (length == 0) return string.Empty;
|
// if (length == 0) return string.Empty;
|
||||||
|
|
@ -1036,7 +1037,7 @@ public static partial class AcBinaryDeserializer
|
||||||
//}
|
//}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object ReadInt32Value(ref BinaryDeserializationContext context, Type targetType)
|
private static object ReadInt32Value(BinaryDeserializationContext context, Type targetType)
|
||||||
{
|
{
|
||||||
var value = context.ReadVarInt();
|
var value = context.ReadVarInt();
|
||||||
return ConvertToTargetType(value, targetType);
|
return ConvertToTargetType(value, targetType);
|
||||||
|
|
@ -1106,7 +1107,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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 info = GetConversionInfo(targetType);
|
||||||
var nextByte = context.ReadByte();
|
var nextByte = context.ReadByte();
|
||||||
|
|
@ -1131,7 +1132,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static byte[] ReadByteArray(ref BinaryDeserializationContext context)
|
private static byte[] ReadByteArray(BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var length = (int)context.ReadVarUInt();
|
var length = (int)context.ReadVarUInt();
|
||||||
if (length == 0) return [];
|
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.
|
/// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return context.GetInternedObject(cacheIndex);
|
return context.GetInternedObject(cacheIndex);
|
||||||
|
|
@ -1174,9 +1175,9 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
|
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
|
||||||
/// Wire format: [Object][props...]
|
/// Wire format: [Object][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1184,25 +1185,25 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
||||||
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return ReadObjectCore(ref context, targetType, depth, cacheIndex: cacheIndex);
|
return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object olvasás core implementáció.
|
/// Object olvasás core implementáció.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
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
|
// Handle dictionary types
|
||||||
if (IsDictionaryType(targetType, out var keyType, out var valueType))
|
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 metadata = wrapper.Metadata;
|
||||||
|
|
||||||
var instance = CreateInstance(targetType, metadata);
|
var instance = CreateInstance(targetType, metadata);
|
||||||
|
|
@ -1213,7 +1214,7 @@ public static partial class AcBinaryDeserializer
|
||||||
context.RegisterInternedValueAt(cacheIndex, instance);
|
context.RegisterInternedValueAt(cacheIndex, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
|
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
|
||||||
|
|
||||||
// ChainMode kezelés
|
// ChainMode kezelés
|
||||||
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
|
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...]
|
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
|
||||||
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
|
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1249,23 +1250,23 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
|
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
|
||||||
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return ReadObjectWithMetadataCore(ref context, targetType, depth, cacheIndex: cacheIndex);
|
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ObjectWithMetadata olvasás core implementáció.
|
/// ObjectWithMetadata olvasás core implementáció.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
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
|
// Inline metadata: propNameHash mindig jön
|
||||||
var propNameHash = context.ReadInt32Raw();
|
var propNameHash = context.ReadInt32Raw();
|
||||||
|
|
||||||
// Source hash-ek keresése — ha nincs, első előfordulás → olvasás a stream-ből
|
// 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)
|
if (sourceHashes == null)
|
||||||
{
|
{
|
||||||
var propCount = (int)context.ReadVarUInt();
|
var propCount = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1274,16 +1275,16 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
sourceHashes[i] = context.ReadInt32Raw();
|
sourceHashes[i] = context.ReadInt32Raw();
|
||||||
}
|
}
|
||||||
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
|
context.RegisterInlineMetadata(propNameHash, sourceHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle dictionary types
|
// Handle dictionary types
|
||||||
if (IsDictionaryType(targetType, out var keyType, out var valueType))
|
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 metadata = wrapper.Metadata;
|
||||||
|
|
||||||
var instance = CreateInstance(targetType, metadata);
|
var instance = CreateInstance(targetType, metadata);
|
||||||
|
|
@ -1298,7 +1299,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (wrapper.CacheMap == null)
|
if (wrapper.CacheMap == null)
|
||||||
BuildCacheMap(wrapper, sourceHashes);
|
BuildCacheMap(wrapper, sourceHashes);
|
||||||
|
|
||||||
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
|
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
|
||||||
|
|
||||||
// ChainMode kezelés
|
// ChainMode kezelés
|
||||||
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
|
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.
|
/// 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.
|
/// Beolvassa a propNameHash-t + hash-eket (első előfordulásnál), és felépíti a cacheMap-et.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ReadInlineMetadataForPopulate(ref BinaryDeserializationContext context, Type targetType)
|
private static void ReadInlineMetadataForPopulate(BinaryDeserializationContext context, Type targetType)
|
||||||
{
|
{
|
||||||
var propNameHash = context.ReadInt32Raw();
|
var propNameHash = context.ReadInt32Raw();
|
||||||
|
|
||||||
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
|
var sourceHashes = context.FindSourceHashes(propNameHash);
|
||||||
if (sourceHashes == null)
|
if (sourceHashes == null)
|
||||||
{
|
{
|
||||||
var propCount = (int)context.ReadVarUInt();
|
var propCount = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1336,10 +1337,10 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
sourceHashes[i] = context.ReadInt32Raw();
|
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)
|
if (wrapper.CacheMap == null)
|
||||||
BuildCacheMap(wrapper, sourceHashes);
|
BuildCacheMap(wrapper, sourceHashes);
|
||||||
}
|
}
|
||||||
|
|
@ -1382,7 +1383,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Array Reading
|
#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);
|
var elementType = GetCollectionElementType(targetType);
|
||||||
if (elementType == null) elementType = typeof(object);
|
if (elementType == null) elementType = typeof(object);
|
||||||
|
|
@ -1393,7 +1394,7 @@ public static partial class AcBinaryDeserializer
|
||||||
// Optimized path for primitive arrays
|
// Optimized path for primitive arrays
|
||||||
if (targetType.IsArray && count > 0)
|
if (targetType.IsArray && count > 0)
|
||||||
{
|
{
|
||||||
var result = TryReadPrimitiveArray(ref context, elementType, count);
|
var result = TryReadPrimitiveArray(context, elementType, count);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1402,7 +1403,7 @@ public static partial class AcBinaryDeserializer
|
||||||
var array = Array.CreateInstance(elementType, count);
|
var array = Array.CreateInstance(elementType, count);
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
array.SetValue(value, i);
|
array.SetValue(value, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1429,7 +1430,7 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var value = ReadValue(ref context, elementType, nextDepth);
|
var value = ReadValue(context, elementType, nextDepth);
|
||||||
list.Add(value);
|
list.Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1445,7 +1446,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Optimized primitive array reader using bulk operations.
|
/// Optimized primitive array reader using bulk operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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
|
// Int32 array
|
||||||
if (ReferenceEquals(elementType, IntType))
|
if (ReferenceEquals(elementType, IntType))
|
||||||
|
|
@ -1563,7 +1564,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Dictionary Reading
|
#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))
|
if (!IsDictionaryType(targetType, out var keyType, out var valueType))
|
||||||
{
|
{
|
||||||
|
|
@ -1571,10 +1572,10 @@ public static partial class AcBinaryDeserializer
|
||||||
valueType = typeof(object);
|
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 dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1583,8 +1584,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var key = ReadValue(ref context, keyType, nextDepth);
|
var key = ReadValue(context, keyType, nextDepth);
|
||||||
var value = ReadValue(ref context, valueType, nextDepth);
|
var value = ReadValue(context, valueType, nextDepth);
|
||||||
if (key != null)
|
if (key != null)
|
||||||
dict.Add(key, value);
|
dict.Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
@ -1596,7 +1597,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Skip Value
|
#region Skip Value
|
||||||
|
|
||||||
private static void SkipValue(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipValue(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
||||||
{
|
{
|
||||||
var typeCode = context.ReadByte();
|
var typeCode = context.ReadByte();
|
||||||
|
|
||||||
|
|
@ -1658,14 +1659,14 @@ public static partial class AcBinaryDeserializer
|
||||||
context.Skip(16);
|
context.Skip(16);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.String:
|
case BinaryTypeCode.String:
|
||||||
SkipPlainString(ref context);
|
SkipPlainString(context);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.StringInterned:
|
case BinaryTypeCode.StringInterned:
|
||||||
context.ReadVarUInt();
|
context.ReadVarUInt();
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.StringInternFirst:
|
case BinaryTypeCode.StringInternFirst:
|
||||||
// First occurrence - must register even when skipping
|
// First occurrence - must register even when skipping
|
||||||
SkipAndRegisterInternedString(ref context);
|
SkipAndRegisterInternedString(context);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.ByteArray:
|
case BinaryTypeCode.ByteArray:
|
||||||
var byteLen = (int)context.ReadVarUInt();
|
var byteLen = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1677,28 +1678,28 @@ public static partial class AcBinaryDeserializer
|
||||||
if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt();
|
if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt();
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.Object:
|
case BinaryTypeCode.Object:
|
||||||
SkipObject(ref context, metaData);
|
SkipObject(context, metaData);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.ObjectRefFirst:
|
case BinaryTypeCode.ObjectRefFirst:
|
||||||
SkipObjectRefFirst(ref context, metaData);
|
SkipObjectRefFirst(context, metaData);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.ObjectWithMetadata:
|
case BinaryTypeCode.ObjectWithMetadata:
|
||||||
SkipObjectWithMetadata(ref context, metaData, cacheIndex: -1);
|
SkipObjectWithMetadata(context, metaData, cacheIndex: -1);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.ObjectWithMetadataRefFirst:
|
case BinaryTypeCode.ObjectWithMetadataRefFirst:
|
||||||
{
|
{
|
||||||
var cacheIdx = (int)context.ReadVarUInt();
|
var cacheIdx = (int)context.ReadVarUInt();
|
||||||
SkipObjectWithMetadata(ref context, metaData, cacheIndex: cacheIdx);
|
SkipObjectWithMetadata(context, metaData, cacheIndex: cacheIdx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case BinaryTypeCode.ObjectRef:
|
case BinaryTypeCode.ObjectRef:
|
||||||
context.ReadVarUInt();
|
context.ReadVarUInt();
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.Array:
|
case BinaryTypeCode.Array:
|
||||||
SkipArray(ref context, metaData);
|
SkipArray(context, metaData);
|
||||||
return;
|
return;
|
||||||
case BinaryTypeCode.Dictionary:
|
case BinaryTypeCode.Dictionary:
|
||||||
SkipDictionary(ref context, metaData);
|
SkipDictionary(context, metaData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1707,7 +1708,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Sima string kihagy<67>sa - NEM regisztr<74>l.
|
/// Sima string kihagy<67>sa - NEM regisztr<74>l.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void SkipPlainString(ref BinaryDeserializationContext context)
|
private static void SkipPlainString(BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var byteLen = (int)context.ReadVarUInt();
|
var byteLen = (int)context.ReadVarUInt();
|
||||||
if (byteLen > 0)
|
if (byteLen > 0)
|
||||||
|
|
@ -1721,7 +1722,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void SkipAndRegisterInternedString(ref BinaryDeserializationContext context)
|
private static void SkipAndRegisterInternedString(BinaryDeserializationContext context)
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
var byteLen = (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.
|
/// Skip ObjectRefFirst - must read cacheIndex and register placeholder in cache.
|
||||||
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void SkipObjectRefFirst(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipObjectRefFirst(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
// Register placeholder (stream position as boxed int for potential lazy load)
|
// Register placeholder (stream position as boxed int for potential lazy load)
|
||||||
context.RegisterInternedValueAt(cacheIndex, context.Position);
|
context.RegisterInternedValueAt(cacheIndex, context.Position);
|
||||||
SkipObject(ref context, metaData);
|
SkipObject(context, metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
///// <summary>
|
///// <summary>
|
||||||
|
|
@ -1748,7 +1749,7 @@ public static partial class AcBinaryDeserializer
|
||||||
///// <param name="context">Deserialization context</param>
|
///// <param name="context">Deserialization context</param>
|
||||||
///// <param name="streamPosition">Position before the type code was read</param>
|
///// <param name="streamPosition">Position before the type code was read</param>
|
||||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
//[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();
|
// var byteLen = (int)context.ReadVarUInt();
|
||||||
// if (byteLen == 0) return;
|
// if (byteLen == 0) return;
|
||||||
|
|
@ -1763,7 +1764,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
|
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipObject(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
"SkipObject nem támogatott metadata nélkül. " +
|
"SkipObject nem támogatott metadata nélkül. " +
|
||||||
|
|
@ -1774,7 +1775,7 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst.
|
/// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
private static void SkipObjectWithMetadata(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
|
private static void SkipObjectWithMetadata(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
|
||||||
{
|
{
|
||||||
if (cacheIndex >= 0)
|
if (cacheIndex >= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1784,7 +1785,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
var propNameHash = context.ReadInt32Raw();
|
var propNameHash = context.ReadInt32Raw();
|
||||||
|
|
||||||
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
|
var sourceHashes = context.FindSourceHashes(propNameHash);
|
||||||
if (sourceHashes == null)
|
if (sourceHashes == null)
|
||||||
{
|
{
|
||||||
var propCount = (int)context.ReadVarUInt();
|
var propCount = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1793,32 +1794,32 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
sourceHashes[i] = context.ReadInt32Raw();
|
sourceHashes[i] = context.ReadInt32Raw();
|
||||||
}
|
}
|
||||||
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
|
context.RegisterInlineMetadata(propNameHash, sourceHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip all properties
|
// Skip all properties
|
||||||
for (var i = 0; i < sourceHashes.Length; i++)
|
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();
|
var count = (int)context.ReadVarUInt();
|
||||||
for (int i = 0; i < count; i++)
|
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();
|
var count = (int)context.ReadVarUInt();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
SkipValue(ref context, metaData); // key
|
SkipValue(context, metaData); // key
|
||||||
SkipValue(ref context, metaData); // value
|
SkipValue(context, metaData); // value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue