Refactor serialization reference tracking and contexts
- Rework SerializationReferenceTracker to use a unified Bloom filter + HashSet for both IId and reference-based tracking, improving efficiency and reducing allocations. - Introduce AcSerializeBase as a common base class for serialization contexts; update Binary, JSON, and Toon contexts to inherit from it. - Move AcBinaryDeserializationException, AcJsonDeserializationException, and TypeConversionInfo to separate files for better organization. - Remove obsolete code and update documentation to reflect new reference tracking logic.
This commit is contained in:
parent
858d43b881
commit
2ab640b375
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base class for serialization operations.
|
||||||
|
/// Contains serialize-specific logic that may need override capability.
|
||||||
|
///
|
||||||
|
/// Responsibilities:
|
||||||
|
/// - Reference scanning (ScanReferences)
|
||||||
|
/// - Reference tracking during write (TrackForScanning, ShouldWriteId, MarkAsWritten)
|
||||||
|
/// - IId-aware serialization logic
|
||||||
|
///
|
||||||
|
/// Derived classes:
|
||||||
|
/// - BinarySerializationContext (or similar) for Binary serialization
|
||||||
|
/// - JsonSerializationContext (or similar) for JSON serialization
|
||||||
|
/// - ToonSerializationContext for Toon serialization
|
||||||
|
///
|
||||||
|
/// Note: Currently SerializationReferenceTracker remains in AcSerializerCommon.
|
||||||
|
/// As patterns emerge, serialize-specific methods can be moved here.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AcSerializeBase
|
||||||
|
{
|
||||||
|
// Future: Move serialize-specific logic here
|
||||||
|
// - SerializationReferenceTracker (or make it a protected property)
|
||||||
|
// - Virtual ComputeHash method (for IId vs Reference distinction)
|
||||||
|
// - Virtual TrackForScanning method
|
||||||
|
// - Virtual ShouldWriteRef method
|
||||||
|
// - etc.
|
||||||
|
}
|
||||||
|
|
@ -878,112 +878,144 @@ public static class AcSerializerCommon
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Common reference tracking for serialization.
|
/// Common reference tracking for serialization.
|
||||||
/// Used by both JSON and Binary serializers to track multi-referenced objects.
|
/// Uses unified Bloom filter + HashSet for both IId and Reference tracking.
|
||||||
/// Supports both ReferenceEquals-based tracking and IId-based tracking.
|
/// - IId objects: hash = Id (positive, DB guarantees uniqueness per entity type)
|
||||||
/// Uses int IDs for efficiency (no string allocation).
|
/// - Non-IId objects: hash = RuntimeHelpers.GetHashCode | 0x80000000 (negative)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SerializationReferenceTracker
|
public sealed class SerializationReferenceTracker
|
||||||
{
|
{
|
||||||
private const int InitialReferenceCapacity = 64;
|
private const int InitialCapacity = 128;
|
||||||
private const int InitialMultiRefCapacity = 32;
|
|
||||||
|
// Unified Bloom filter (256 bits = 4 x 64-bit)
|
||||||
|
private ulong _bloom0, _bloom1, _bloom2, _bloom3;
|
||||||
|
|
||||||
|
// Unified HashSet for seen hashes (both IId and Reference)
|
||||||
|
private HashSet<int>? _seenHashes;
|
||||||
|
|
||||||
|
// Multi-referenced hashes
|
||||||
|
private HashSet<int>? _multiRefHashes;
|
||||||
|
|
||||||
|
// Written refs: hash → refId
|
||||||
|
private Dictionary<int, int>? _writtenRefs;
|
||||||
|
|
||||||
private Dictionary<object, int>? _scanOccurrences;
|
|
||||||
private Dictionary<object, int>? _writtenRefs;
|
|
||||||
private Dictionary<(Type, object), int>? _iidWrittenRefs; // IId-based written refs
|
|
||||||
private HashSet<object>? _multiReferenced;
|
|
||||||
private int _nextRefId = 1;
|
private int _nextRefId = 1;
|
||||||
|
|
||||||
// IId tracker for same-IId-different-instance deduplication
|
|
||||||
private IIdReferenceTracker? _iidTracker;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the tracker for reuse.
|
/// Resets the tracker for reuse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_nextRefId = 1;
|
_nextRefId = 1;
|
||||||
_scanOccurrences?.Clear();
|
_bloom0 = _bloom1 = _bloom2 = _bloom3 = 0;
|
||||||
|
_seenHashes?.Clear();
|
||||||
|
_multiRefHashes?.Clear();
|
||||||
_writtenRefs?.Clear();
|
_writtenRefs?.Clear();
|
||||||
_iidWrittenRefs?.Clear();
|
|
||||||
_multiReferenced?.Clear();
|
|
||||||
_iidTracker?.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures internal collections are initialized.
|
/// Ensures internal collections are initialized.
|
||||||
/// Call once before scanning when reference handling is enabled.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void EnsureInitialized()
|
public void EnsureInitialized()
|
||||||
{
|
{
|
||||||
_scanOccurrences ??= new Dictionary<object, int>(InitialReferenceCapacity, ReferenceEqualityComparer.Instance);
|
_seenHashes ??= new HashSet<int>(InitialCapacity);
|
||||||
_writtenRefs ??= new Dictionary<object, int>(InitialReferenceCapacity, ReferenceEqualityComparer.Instance);
|
_multiRefHashes ??= new HashSet<int>();
|
||||||
_iidWrittenRefs ??= new Dictionary<(Type, object), int>(InitialMultiRefCapacity);
|
_writtenRefs ??= new Dictionary<int, int>(InitialCapacity);
|
||||||
_multiReferenced ??= new HashSet<object>(InitialMultiRefCapacity, ReferenceEqualityComparer.Instance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks an object during reference scanning phase.
|
/// Computes the tracking hash for an object.
|
||||||
/// Returns true if this is the first occurrence (continue scanning).
|
/// IId objects: positive hash from Id
|
||||||
/// Returns false if already seen (object is multi-referenced, stop scanning this branch).
|
/// Non-IId objects: negative hash from RuntimeHelpers.GetHashCode
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int ComputeHash<TMetadata>(object obj, TMetadata metadata) where TMetadata : TypeMetadataBase
|
||||||
|
{
|
||||||
|
if (metadata.IsIId)
|
||||||
|
{
|
||||||
|
return metadata.IdAccessorType switch
|
||||||
|
{
|
||||||
|
IdAccessorType.Int32 => metadata.GetIdInt32(obj),
|
||||||
|
IdAccessorType.Int64 => (int)(metadata.GetIdInt64(obj) ^ (metadata.GetIdInt64(obj) >> 32)),
|
||||||
|
IdAccessorType.Guid => metadata.GetIdGuid(obj).GetHashCode() & 0x7FFFFFFF,
|
||||||
|
_ => metadata.IdGetter?.Invoke(obj)?.GetHashCode() ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-IId: use RuntimeHelpers identity hash with high bit set (negative)
|
||||||
|
return RuntimeHelpers.GetHashCode(obj) | unchecked((int)0x80000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks an object during reference scanning phase (non-IId version).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TrackForScanning(object obj)
|
public bool TrackForScanning(object obj)
|
||||||
{
|
{
|
||||||
if (_scanOccurrences == null) return true;
|
var hash = RuntimeHelpers.GetHashCode(obj) | unchecked((int)0x80000000);
|
||||||
|
return TrackHash(hash);
|
||||||
|
|
||||||
ref var count = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(_scanOccurrences, obj, out var exists);
|
|
||||||
if (exists)
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
_multiReferenced!.Add(obj);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
count = 1;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extended tracking with IId support.
|
/// Tracks an object during reference scanning phase with IId support.
|
||||||
/// First checks IId match (different instance, same Id), then falls back to ReferenceEquals.
|
/// Returns true if first occurrence (continue scanning).
|
||||||
/// Returns true if this is the first occurrence (continue scanning children).
|
/// Returns false if already seen (multi-referenced, stop scanning).
|
||||||
/// Returns false if already seen (stop scanning this branch).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">Object to track</param>
|
|
||||||
/// <param name="metadata">Type metadata with IId info</param>
|
|
||||||
/// <param name="existingRefId">If returning false, contains the refId of the existing object (unused, kept for API compatibility)</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TrackForScanningWithIId<TMetadata>(object obj, TMetadata metadata, out int existingRefId)
|
public bool TrackForScanningWithIId<TMetadata>(object obj, TMetadata metadata, out int existingRefId)
|
||||||
where TMetadata : TypeMetadataBase
|
where TMetadata : TypeMetadataBase
|
||||||
{
|
{
|
||||||
existingRefId = 0;
|
existingRefId = 0;
|
||||||
if (_scanOccurrences == null) return true;
|
var hash = ComputeHash(obj, metadata);
|
||||||
|
|
||||||
// 1. IId check first (different instance, same Id)
|
// Skip default IId values (Id = 0)
|
||||||
if (metadata.IsIId && _iidTracker != null && _iidTracker.TryGetOriginalObject(obj, metadata, out var originalObject))
|
if (metadata.IsIId && hash == 0) return true;
|
||||||
{
|
|
||||||
// Same IId already seen → mark BOTH original and current as multi-referenced
|
return TrackHash(hash);
|
||||||
_multiReferenced!.Add(originalObject!); // Original object
|
|
||||||
_multiReferenced.Add(obj); // Current object (duplicate)
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. ReferenceEquals check (same instance)
|
/// <summary>
|
||||||
ref var count = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(_scanOccurrences, obj, out var exists);
|
/// Core tracking logic using Bloom filter + HashSet.
|
||||||
if (exists)
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool TrackHash(int hash)
|
||||||
{
|
{
|
||||||
count++;
|
// Bloom filter check - fast "definitely new" detection
|
||||||
_multiReferenced!.Add(obj);
|
var segment = (hash >> 6) & 3;
|
||||||
return false;
|
var bit = hash & 63;
|
||||||
}
|
var mask = 1UL << bit;
|
||||||
count = 1;
|
|
||||||
|
|
||||||
// 3. Register IId for future lookups
|
var bloomHit = segment switch
|
||||||
if (metadata.IsIId)
|
|
||||||
{
|
{
|
||||||
_iidTracker ??= new IIdReferenceTracker();
|
0 => (_bloom0 & mask) != 0,
|
||||||
_iidTracker.Register(obj, metadata);
|
1 => (_bloom1 & mask) != 0,
|
||||||
|
2 => (_bloom2 & mask) != 0,
|
||||||
|
_ => (_bloom3 & mask) != 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!bloomHit)
|
||||||
|
{
|
||||||
|
// Definitely new - add to bloom and set
|
||||||
|
switch (segment)
|
||||||
|
{
|
||||||
|
case 0: _bloom0 |= mask; break;
|
||||||
|
case 1: _bloom1 |= mask; break;
|
||||||
|
case 2: _bloom2 |= mask; break;
|
||||||
|
default: _bloom3 |= mask; break;
|
||||||
|
}
|
||||||
|
_seenHashes ??= new HashSet<int>(InitialCapacity);
|
||||||
|
_seenHashes.Add(hash);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possible duplicate - check HashSet
|
||||||
|
_seenHashes ??= new HashSet<int>(InitialCapacity);
|
||||||
|
if (!_seenHashes.Add(hash))
|
||||||
|
{
|
||||||
|
// Already seen - multi-referenced!
|
||||||
|
_multiRefHashes ??= new HashSet<int>();
|
||||||
|
_multiRefHashes.Add(hash);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -991,16 +1023,37 @@ public static class AcSerializerCommon
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if object needs a reference ID during serialization.
|
/// Checks if object needs a reference ID during serialization.
|
||||||
/// Returns true if object is multi-referenced and hasn't been written yet.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool ShouldWriteId(object obj, out int refId)
|
public bool ShouldWriteId(object obj, out int refId)
|
||||||
{
|
{
|
||||||
if (_multiReferenced != null && _multiReferenced.Contains(obj) && !_writtenRefs!.ContainsKey(obj))
|
var hash = RuntimeHelpers.GetHashCode(obj) | unchecked((int)0x80000000);
|
||||||
|
return ShouldWriteIdForHash(hash, out refId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if object needs a reference ID (IId-aware version).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool ShouldWriteIdForIId<TMetadata>(object obj, TMetadata metadata, out int refId)
|
||||||
|
where TMetadata : TypeMetadataBase
|
||||||
|
{
|
||||||
|
var hash = ComputeHash(obj, metadata);
|
||||||
|
return ShouldWriteIdForHash(hash, out refId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool ShouldWriteIdForHash(int hash, out int refId)
|
||||||
|
{
|
||||||
|
if (_multiRefHashes != null && _multiRefHashes.Contains(hash))
|
||||||
|
{
|
||||||
|
_writtenRefs ??= new Dictionary<int, int>(InitialCapacity);
|
||||||
|
if (!_writtenRefs.ContainsKey(hash))
|
||||||
{
|
{
|
||||||
refId = _nextRefId++;
|
refId = _nextRefId++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
refId = 0;
|
refId = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1011,22 +1064,20 @@ public static class AcSerializerCommon
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void MarkAsWritten(object obj, int refId)
|
public void MarkAsWritten(object obj, int refId)
|
||||||
{
|
{
|
||||||
var type = obj.GetType();
|
var hash = RuntimeHelpers.GetHashCode(obj) | unchecked((int)0x80000000);
|
||||||
var idInfo = JsonUtilities.GetIdInfo(type);
|
_writtenRefs![hash] = refId;
|
||||||
|
|
||||||
// IId típus → IId alapján tároljuk
|
|
||||||
if (idInfo.IsId && idInfo.IdType != null)
|
|
||||||
{
|
|
||||||
var key = GetIIdKey(obj, type, idInfo.IdType);
|
|
||||||
if (key.HasValue)
|
|
||||||
{
|
|
||||||
_iidWrittenRefs![key.Value] = refId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nem IId → ReferenceEquals alapján
|
/// <summary>
|
||||||
_writtenRefs![obj] = refId;
|
/// Marks object as written (IId-aware version).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void MarkAsWrittenForIId<TMetadata>(object obj, TMetadata metadata, int refId)
|
||||||
|
where TMetadata : TypeMetadataBase
|
||||||
|
{
|
||||||
|
var hash = ComputeHash(obj, metadata);
|
||||||
|
_writtenRefs ??= new Dictionary<int, int>(InitialCapacity);
|
||||||
|
_writtenRefs[hash] = refId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1035,42 +1086,30 @@ public static class AcSerializerCommon
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TryGetExistingRef(object obj, out int refId)
|
public bool TryGetExistingRef(object obj, out int refId)
|
||||||
{
|
{
|
||||||
var type = obj.GetType();
|
var hash = RuntimeHelpers.GetHashCode(obj) | unchecked((int)0x80000000);
|
||||||
var idInfo = JsonUtilities.GetIdInfo(type);
|
return TryGetExistingRefForHash(hash, out refId);
|
||||||
|
|
||||||
// IId típus → IId alapján keresünk
|
|
||||||
if (idInfo.IsId && idInfo.IdType != null && _iidWrittenRefs != null)
|
|
||||||
{
|
|
||||||
var key = GetIIdKey(obj, type, idInfo.IdType);
|
|
||||||
if (key.HasValue && _iidWrittenRefs.TryGetValue(key.Value, out refId))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nem IId → ReferenceEquals alapján
|
|
||||||
if (_writtenRefs != null && _writtenRefs.TryGetValue(obj, out refId))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
refId = 0;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the (Type, Id) key for IId-based lookup.
|
/// Tries to get existing reference ID (IId-aware version).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static (Type, object)? GetIIdKey(object obj, Type type, Type idType)
|
public bool TryGetExistingRefForIId<TMetadata>(object obj, TMetadata metadata, out int refId)
|
||||||
|
where TMetadata : TypeMetadataBase
|
||||||
{
|
{
|
||||||
var idProp = type.GetProperty("Id");
|
var hash = ComputeHash(obj, metadata);
|
||||||
if (idProp == null) return null;
|
return TryGetExistingRefForHash(hash, out refId);
|
||||||
|
}
|
||||||
|
|
||||||
var id = idProp.GetValue(obj);
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
if (id == null || JsonUtilities.IsDefaultValue(id, idType)) return null;
|
private bool TryGetExistingRefForHash(int hash, out int refId)
|
||||||
|
{
|
||||||
return (type, id);
|
if (_writtenRefs != null && _writtenRefs.TryGetValue(hash, out refId))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
refId = 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown when binary deserialization fails.
|
||||||
|
/// </summary>
|
||||||
|
public class AcBinaryDeserializationException : Exception
|
||||||
|
{
|
||||||
|
public int Position { get; }
|
||||||
|
public Type? TargetType { get; }
|
||||||
|
|
||||||
|
public AcBinaryDeserializationException(string message, int position = 0, Type? targetType = null, Exception? innerException = null)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
TargetType = targetType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,22 +12,6 @@ using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Binaries;
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exception thrown when binary deserialization fails.
|
|
||||||
/// </summary>
|
|
||||||
public class AcBinaryDeserializationException : Exception
|
|
||||||
{
|
|
||||||
public int Position { get; }
|
|
||||||
public Type? TargetType { get; }
|
|
||||||
|
|
||||||
public AcBinaryDeserializationException(string message, int position = 0, Type? targetType = null, Exception? innerException = null)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
Position = position;
|
|
||||||
TargetType = targetType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// High-performance binary deserializer matching AcBinarySerializer.
|
/// High-performance binary deserializer matching AcBinarySerializer.
|
||||||
/// Features:
|
/// Features:
|
||||||
|
|
@ -1412,21 +1396,4 @@ public static partial class AcBinaryDeserializer
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached type conversion info. Using readonly struct to avoid heap allocation.
|
|
||||||
/// </summary>
|
|
||||||
readonly struct TypeConversionInfo
|
|
||||||
{
|
|
||||||
public readonly Type UnderlyingType;
|
|
||||||
public readonly TypeCode TypeCode;
|
|
||||||
public readonly bool IsEnum;
|
|
||||||
|
|
||||||
public TypeConversionInfo(Type underlyingType, TypeCode typeCode, bool isEnum)
|
|
||||||
{
|
|
||||||
UnderlyingType = underlyingType;
|
|
||||||
TypeCode = typeCode;
|
|
||||||
IsEnum = isEnum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation moved to AcBinaryDeserializer.TypeConversionInfo.cs
|
// Implementation moved to AcBinaryDeserializer.TypeConversionInfo.cs
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public static partial class AcBinarySerializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binary serialization context. Public for generated serializers.
|
/// Binary serialization context. Public for generated serializers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class BinarySerializationContext : IDisposable
|
internal sealed class BinarySerializationContext : AcSerializeBase, IDisposable
|
||||||
{
|
{
|
||||||
private const int MinBufferSize = 256;
|
private const int MinBufferSize = 256;
|
||||||
private const int PropertyIndexBufferMaxCache = 512;
|
private const int PropertyIndexBufferMaxCache = 512;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached type conversion info. Using readonly struct to avoid heap allocation.
|
||||||
|
/// </summary>
|
||||||
|
readonly struct TypeConversionInfo
|
||||||
|
{
|
||||||
|
public readonly Type UnderlyingType;
|
||||||
|
public readonly TypeCode TypeCode;
|
||||||
|
public readonly bool IsEnum;
|
||||||
|
|
||||||
|
public TypeConversionInfo(Type underlyingType, TypeCode typeCode, bool isEnum)
|
||||||
|
{
|
||||||
|
UnderlyingType = underlyingType;
|
||||||
|
TypeCode = typeCode;
|
||||||
|
IsEnum = isEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace AyCode.Core.Serializers.Jsons;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown when JSON deserialization fails.
|
||||||
|
/// </summary>
|
||||||
|
public class AcJsonDeserializationException : Exception
|
||||||
|
{
|
||||||
|
public string? Json { get; }
|
||||||
|
public Type? TargetType { get; }
|
||||||
|
|
||||||
|
public AcJsonDeserializationException(string message, string? json = null, Type? targetType = null, Exception? innerException = null)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
Json = json?.Length > 500 ? json[..500] + "..." : json;
|
||||||
|
TargetType = targetType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,22 +9,6 @@ using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Jsons;
|
namespace AyCode.Core.Serializers.Jsons;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exception thrown when JSON deserialization fails.
|
|
||||||
/// </summary>
|
|
||||||
public class AcJsonDeserializationException : Exception
|
|
||||||
{
|
|
||||||
public string? Json { get; }
|
|
||||||
public Type? TargetType { get; }
|
|
||||||
|
|
||||||
public AcJsonDeserializationException(string message, string? json = null, Type? targetType = null, Exception? innerException = null)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
Json = json?.Length > 500 ? json[..500] + "..." : json;
|
|
||||||
TargetType = targetType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// High-performance custom JSON deserializer optimized for IId<T> reference handling.
|
/// High-performance custom JSON deserializer optimized for IId<T> reference handling.
|
||||||
/// Uses Utf8JsonReader for streaming deserialization when possible (STJ approach).
|
/// Uses Utf8JsonReader for streaming deserialization when possible (STJ approach).
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public static partial class AcJsonSerializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class JsonSerializationContext : IDisposable
|
private sealed class JsonSerializationContext : AcSerializeBase, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ArrayBufferWriter<byte> _buffer;
|
private readonly ArrayBufferWriter<byte> _buffer;
|
||||||
public Utf8JsonWriter Writer { get; private set; }
|
public Utf8JsonWriter Writer { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public static partial class AcToonSerializer
|
||||||
/// Pooled context for Toon serialization.
|
/// Pooled context for Toon serialization.
|
||||||
/// Handles output building, indentation, and reference tracking.
|
/// Handles output building, indentation, and reference tracking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private sealed class ToonSerializationContext
|
private sealed class ToonSerializationContext : AcSerializeBase
|
||||||
{
|
{
|
||||||
private readonly StringBuilder _builder;
|
private readonly StringBuilder _builder;
|
||||||
private Dictionary<object, int>? _scanOccurrences;
|
private Dictionary<object, int>? _scanOccurrences;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue