From 7d133a4b2434f883f13e30986788d8bcb0257946 Mon Sep 17 00:00:00 2001 From: Loretta Date: Tue, 20 Jan 2026 08:58:07 +0100 Subject: [PATCH] Add IId-aware reference tracking to serializer/deserializer Enhances both binary deserialization and JSON serialization to support IId-based reference tracking and handling. Refactors object reference reading in AcBinaryDeserializer to use a unified TryGetValue approach. Adds IId-aware tracking, ID writing, and reference lookup methods to AcJsonSerializer and updates ScanReferences and WriteObject to use them. Improves support for custom identity types and robust reference management. --- .../Binaries/AcBinaryDeserializer.cs | 32 ++++++++----------- ...JsonSerializer.JsonSerializationContext.cs | 32 +++++++++++++++++++ .../Serializers/Jsons/AcJsonSerializer.cs | 21 +++++++----- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 95b56d5..7d3d3fd 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -913,18 +913,20 @@ public static partial class AcBinaryDeserializer private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth) { var wrapper = context.ContextClass.GetWrapper(targetType); + return ReadObjectRef(ref context, ref wrapper, depth); + } + private static object? ReadObjectRef(ref BinaryDeserializationContext context, ref TypeMetadataWrapper wrapper, int depth) + { var metadata = wrapper.Metadata; if (metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None) { - var map = wrapper.IdentityMap; - // Read ID based on type and lookup in IdentityMap return metadata.IdAccessorType switch { - AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, map), - AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, map), - AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, map), + AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, ref wrapper), + AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, ref wrapper), + AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, ref wrapper), _ => throw new Exception("metadata.IdAccessorType not valid") }; } @@ -933,30 +935,24 @@ public static partial class AcBinaryDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectRefInt32(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map) + private static object? ReadObjectRefInt32(ref BinaryDeserializationContext context, ref TypeMetadataWrapper wrapper) { var id = context.ReadVarInt(); - if (map is AcSerializerCommon.IdentityMap intMap && intMap.TryGetValue(id, out var obj)) - return obj; - return null; + return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefInt32"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectRefInt64(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map) + private static object? ReadObjectRefInt64(ref BinaryDeserializationContext context, ref TypeMetadataWrapper wrapper) { - var id = context.ReadVarLong(); // VarLong, not VarInt! - if (map is AcSerializerCommon.IdentityMap longMap && longMap.TryGetValue(id, out var obj)) - return obj; - return null; // Long IIds don't use flat reference tracking + var id = context.ReadVarLong(); + return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefInt64");; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectRefGuid(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map) + private static object? ReadObjectRefGuid(ref BinaryDeserializationContext context, ref TypeMetadataWrapper wrapper) { var id = context.ReadGuidUnsafe(); // 16 bytes raw! - if (map is AcSerializerCommon.IdentityMap guidMap && guidMap.TryGetValue(id, out var obj)) - return obj; - return null; // Guid IIds don't use flat reference tracking + return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefGuid");; } private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth) diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs index 1e839ef..bac9048 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs @@ -86,15 +86,47 @@ public static partial class AcJsonSerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj); + + /// + /// IId-aware tracking for reference scanning. + /// Uses IId.Id for IId types, RuntimeHelpers.GetHashCode for others. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrackForScanningWithIId(object obj, JsonSerializeTypeMetadata metadata) + { + int existingRefId; + return _refTracker.TrackForScanningWithIId(obj, metadata, out existingRefId); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ShouldWriteId(object obj, out int id) => _refTracker.ShouldWriteId(obj, out id); + + /// + /// IId-aware check for reference ID writing. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ShouldWriteIdForIId(object obj, JsonSerializeTypeMetadata metadata, out int id) + => _refTracker.ShouldWriteIdForIId(obj, metadata, out id); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkAsWritten(object obj, int id) => _refTracker.MarkAsWritten(obj, id); + /// + /// IId-aware mark as written. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MarkAsWrittenForIId(object obj, JsonSerializeTypeMetadata metadata, int id) + => _refTracker.MarkAsWrittenForIId(obj, metadata, id); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId); + + /// + /// IId-aware existing ref lookup. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetExistingRefForIId(object obj, JsonSerializeTypeMetadata metadata, out int refId) + => _refTracker.TryGetExistingRefForIId(obj, metadata, out refId); public string GetResult() { diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs index ed48862..02e1b4b 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs @@ -130,7 +130,13 @@ public static partial class AcJsonSerializer var type = value.GetType(); if (IsPrimitiveOrStringFast(type)) return; - if (!context.TrackForScanning(value)) return; + + // Get wrapper early to use IId-aware tracking + var wrapper = context.GetWrapper(type); + var metadata = wrapper.Metadata; + + // Use IId-aware scanning if available + if (!context.TrackForScanningWithIId(value, metadata)) return; if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { @@ -139,8 +145,6 @@ public static partial class AcJsonSerializer return; } - var wrapper = context.GetWrapper(type); - var metadata = wrapper.Metadata; var props = metadata.Properties; var propCount = props.Length; for (var i = 0; i < propCount; i++) @@ -174,8 +178,11 @@ public static partial class AcJsonSerializer private static void WriteObject(object value, in Type type, JsonSerializationContext context, int depth) { var writer = context.Writer; + var wrapper = context.GetWrapper(type); + var metadata = wrapper.Metadata; - if (context.UseReferenceHandling && context.TryGetExistingRef(value, out var refId)) + // Use IId-aware reference handling + if (context.UseReferenceHandling && context.TryGetExistingRefForIId(value, metadata, out var refId)) { writer.WriteStartObject(); writer.WriteString(RefPropertyEncoded, refId.ToString(CultureInfo.InvariantCulture)); @@ -185,14 +192,12 @@ public static partial class AcJsonSerializer writer.WriteStartObject(); - if (context.UseReferenceHandling && context.ShouldWriteId(value, out var id)) + if (context.UseReferenceHandling && context.ShouldWriteIdForIId(value, metadata, out var id)) { writer.WriteString(IdPropertyEncoded, id.ToString(CultureInfo.InvariantCulture)); - context.MarkAsWritten(value, id); + context.MarkAsWrittenForIId(value, metadata, id); } - var wrapper = context.GetWrapper(type); - var metadata = wrapper.Metadata; var props = metadata.Properties; var propCount = props.Length; var nextDepth = depth + 1;