Unify SGen wrapper slot tracking for metadata and refs
Refactored binary serializer to use a single wrapper slot per SGen type for both metadata registration and reference tracking. Removed slot-based IdentityMap arrays and scan pass tracking methods, replacing them with wrapper-based TryTrack logic. Updated generated code to use wrapper slots for all slot-based operations. Changed UseMetadata registration to use a MetadataSeen flag on the wrapper. Added fast slot-indexed wrapper access in context base. Default UseMetadata option is now false. Simplifies and optimizes SGen tracking, reducing dictionary lookups and unifying tracking logic.
This commit is contained in:
parent
fe35e60649
commit
48c737024f
|
|
@ -311,9 +311,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedWriter : IGeneratedBinaryWriter");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
|
||||
sb.AppendLine($" internal static readonly int s_metadataSlot = AcBinarySerializer.AllocateMetadataSlot();");
|
||||
if (ci.IsIId || ci.EnableRefHandling)
|
||||
sb.AppendLine($" internal static readonly int s_trackingSlot = AcBinarySerializer.AllocateTrackingSlot();");
|
||||
sb.AppendLine($" internal static readonly int s_wrapperSlot = AcBinarySerializer.AllocateWrapperSlot();");
|
||||
sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};");
|
||||
sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ ");
|
||||
sb.Append(string.Join(", ", ci.PropertyNameHashes));
|
||||
|
|
@ -384,33 +382,46 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine();
|
||||
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
|
||||
|
||||
// Self ref tracking — matches runtime ScanValue UseTypeReferenceHandling block
|
||||
// Self ref tracking — inline TryTrack via wrapper (no bridge method overhead)
|
||||
// Only emitted when the corresponding feature flag is enabled.
|
||||
if (ci.IsIId)
|
||||
{
|
||||
// IId type: track when ReferenceHandling != None
|
||||
var trackMethod = ci.IdTypeName switch
|
||||
var tryTrackMethod = ci.IdTypeName switch
|
||||
{
|
||||
"int" => "ScanTrackObjectInt32",
|
||||
"long" => "ScanTrackObjectInt64",
|
||||
"System.Guid" => "ScanTrackObjectGuid",
|
||||
_ => "ScanTrackObjectInt32"
|
||||
"int" => "TryTrackInt32",
|
||||
"long" => "TryTrackInt64",
|
||||
"System.Guid" => "TryTrackGuid",
|
||||
_ => "TryTrackInt32"
|
||||
};
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.None)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" if (!context.{trackMethod}(s_trackingSlot, obj.Id))");
|
||||
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
||||
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
||||
sb.AppendLine($" if (!wrapper.{tryTrackMethod}(obj.Id, visitIndex, ref context.NextCacheIndexRef, out var cacheIndex, out var firstVisitIndex))");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (firstVisitIndex >= 0)");
|
||||
sb.AppendLine(" context.AddWriteDuplicateEntry(firstVisitIndex, cacheIndex, isFirst: true, value: null);");
|
||||
sb.AppendLine(" context.AddWriteDuplicateEntry(visitIndex, cacheIndex, isFirst: false, value: null);");
|
||||
sb.AppendLine(" return;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
}
|
||||
else if (ci.EnableRefHandling)
|
||||
{
|
||||
// Non-IId type: track when ReferenceHandling == All
|
||||
// Non-IId type: track via wrapper.TryTrackInt32 with RuntimeHelpers.GetHashCode
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" if (context.ReferenceHandling == ReferenceHandlingMode.All)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (!context.ScanTrackObjectInt32(s_trackingSlot, System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value)))");
|
||||
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
||||
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
||||
sb.AppendLine(" if (!wrapper.TryTrackInt32(System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value), visitIndex, ref context.NextCacheIndexRef, out var cacheIndex, out var firstVisitIndex))");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (firstVisitIndex >= 0)");
|
||||
sb.AppendLine(" context.AddWriteDuplicateEntry(firstVisitIndex, cacheIndex, isFirst: true, value: null);");
|
||||
sb.AppendLine(" context.AddWriteDuplicateEntry(visitIndex, cacheIndex, isFirst: false, value: null);");
|
||||
sb.AppendLine(" return;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
}
|
||||
|
||||
|
|
@ -891,7 +902,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
else
|
||||
{
|
||||
// UseMetadata: register type for first/repeated tracking via slot (no GetWrapper needed)
|
||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({p.TypeNameForTypeof})));");
|
||||
|
||||
// Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior.
|
||||
var refGuard = p.IsIId
|
||||
|
|
@ -1041,7 +1052,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
else
|
||||
{
|
||||
// UseMetadata: register element type for first/repeated tracking
|
||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({p.ElementFullTypeName})));");
|
||||
|
||||
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ public abstract class AcSerializerContextBase<TMetadata, TOptions>
|
|||
/// </summary>
|
||||
private readonly Dictionary<Type, TypeMetadataWrapper<TMetadata>> _wrappers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Slot-indexed wrapper cache for SGen types. Indexed by AllocateWrapperSlot() value.
|
||||
/// Avoids dictionary lookup — direct array access for types with compile-time known slot index.
|
||||
/// Not cleared on pool return: wrapper references are stable across serialization calls.
|
||||
/// </summary>
|
||||
private TypeMetadataWrapper<TMetadata>?[]? _wrapperSlots;
|
||||
|
||||
/// <summary>
|
||||
/// Factory function to create metadata. Implemented by derived class.
|
||||
/// </summary>
|
||||
|
|
@ -81,6 +88,44 @@ public abstract class AcSerializerContextBase<TMetadata, TOptions>
|
|||
return wrapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a wrapper for the specified type using a pre-allocated slot index.
|
||||
/// SGen types call this with their compile-time known slot — avoids dictionary lookup.
|
||||
/// First call per slot per context: falls back to GetWrapper + stores in slot array.
|
||||
/// Subsequent calls: direct array index (~1-2ns vs ~15-25ns dictionary lookup).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TypeMetadataWrapper<TMetadata> GetWrapperBySlot(int slot, Type type)
|
||||
{
|
||||
var slots = _wrapperSlots;
|
||||
if (slots != null && (uint)slot < (uint)slots.Length)
|
||||
{
|
||||
var wrapper = slots[slot];
|
||||
if (wrapper != null)
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
return GetWrapperBySlotSlow(slot, type);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private TypeMetadataWrapper<TMetadata> GetWrapperBySlotSlow(int slot, Type type)
|
||||
{
|
||||
var wrapper = GetWrapper(type);
|
||||
_wrapperSlots![slot] = wrapper;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-allocates the wrapper slot array with the known total slot count.
|
||||
/// Called once from derived context constructor after all AllocateWrapperSlot() calls have completed.
|
||||
/// </summary>
|
||||
protected void InitializeWrapperSlots(int count)
|
||||
{
|
||||
if (count > 0)
|
||||
_wrapperSlots = new TypeMetadataWrapper<TMetadata>?[count];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wrapper Iteration (for footer writing)
|
||||
|
|
|
|||
|
|
@ -89,26 +89,16 @@ public static partial class AcBinarySerializer
|
|||
#endregion
|
||||
|
||||
private IdentityMap<string, InternEntry>? _stringInternMap;
|
||||
private readonly ulong[] _metadataSeenBits = new ulong[4]; // 256 SGen slot (bit per type, first/repeated tracking)
|
||||
private HashSet<int>? _metadataSeenOverflow; // fallback for slot >= 256
|
||||
private int _nextCacheIndex; // Next dense cache index to assign (starts at 0, uses ++_nextCacheIndex)
|
||||
private int _nextCacheIndex;
|
||||
public int NextFirstIndex; // Next first occurrence index for scan pass. Direct access for performance.
|
||||
|
||||
#region Slot-based IdentityMaps for SGen scan pass (replaces wrapper-based tracking)
|
||||
|
||||
private IdentityMap<int, InternEntry>?[]? _slottedIdMapsInt32;
|
||||
private IdentityMap<long, InternEntry>?[]? _slottedIdMapsInt64;
|
||||
private IdentityMap<Guid, InternEntry>?[]? _slottedIdMapsGuid;
|
||||
|
||||
#endregion
|
||||
|
||||
#region WriteDuplicateEntry — scan pass output for write pass cursor
|
||||
|
||||
private WriteDuplicateEntry[]? _writePlan;
|
||||
private int _writePlanCount;
|
||||
|
||||
/// <summary>Unified scan visit counter. Increments on every IId object and internable string visit.</summary>
|
||||
internal int ScanVisitIndex;
|
||||
public int ScanVisitIndex;
|
||||
|
||||
/// <summary>Write plan entry count for write pass cursor.</summary>
|
||||
internal int WritePlanCount => _writePlanCount;
|
||||
|
|
@ -119,7 +109,7 @@ public static partial class AcBinarySerializer
|
|||
/// <summary>
|
||||
/// Adds a pre-computed write instruction for a duplicate string or IId object reference.
|
||||
/// </summary>
|
||||
internal void AddWriteDuplicateEntry(int visitIndex, int cacheMapIndex, bool isFirst, string? value)
|
||||
public void AddWriteDuplicateEntry(int visitIndex, int cacheMapIndex, bool isFirst, string? value)
|
||||
{
|
||||
if (_writePlan == null)
|
||||
{
|
||||
|
|
@ -261,7 +251,7 @@ public static partial class AcBinarySerializer
|
|||
public BinarySerializationContext(AcBinarySerializerOptions options)
|
||||
{
|
||||
Reset(options);
|
||||
//FastWire = options.WireMode == WireMode.Fast;
|
||||
InitializeWrapperSlots(Volatile.Read(ref s_nextWrapperSlot));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -287,12 +277,6 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
|
||||
_stringInternMap?.Reset();
|
||||
_metadataSeenBits.AsSpan().Clear();
|
||||
_metadataSeenOverflow?.Clear();
|
||||
|
||||
ResetSlottedMaps(_slottedIdMapsInt32);
|
||||
ResetSlottedMaps(_slottedIdMapsInt64);
|
||||
ResetSlottedMaps(_slottedIdMapsGuid);
|
||||
|
||||
_nextCacheIndex = 0;
|
||||
NextFirstIndex = 0;
|
||||
|
|
@ -782,175 +766,22 @@ public static partial class AcBinarySerializer
|
|||
|
||||
#endregion
|
||||
|
||||
#region Slot-based Scan Pass Ref Tracking (SGen)
|
||||
|
||||
/// <summary>
|
||||
/// SGen scan pass: tracks an object by Int32 Id (IId or RuntimeHelpers.GetHashCode for non-IId).
|
||||
/// Increments ScanVisitIndex, manages IdentityMap via slot, and builds WriteDuplicateEntry on duplicates.
|
||||
/// Returns true if first occurrence (caller should scan children), false if duplicate (skip children).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ScanTrackObjectInt32(int slot, int id)
|
||||
{
|
||||
var visitIndex = ScanVisitIndex++;
|
||||
|
||||
if (id == 0) return true; // default Id — no tracking
|
||||
|
||||
ref var maps = ref _slottedIdMapsInt32;
|
||||
if (maps == null || maps.Length <= slot)
|
||||
GrowSlottedMaps(ref maps, slot);
|
||||
|
||||
var map = maps[slot] ??= new IdentityMap<int, InternEntry>();
|
||||
|
||||
if (!map.TryAdd(id, out var si))
|
||||
{
|
||||
ref var entry = ref map.GetValueRef(si);
|
||||
if (entry.CacheIndex == -1)
|
||||
{
|
||||
entry.CacheIndex = ++_nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
AddWriteDuplicateEntry(entry.FirstIndex, entry.CacheIndex, isFirst: true, value: null);
|
||||
}
|
||||
AddWriteDuplicateEntry(visitIndex, entry.CacheIndex, isFirst: false, value: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var ne = ref map.GetValueRef(si);
|
||||
ne.FirstIndex = visitIndex;
|
||||
ne.CacheIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SGen scan pass: tracks an object by Int64 Id.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ScanTrackObjectInt64(int slot, long id)
|
||||
{
|
||||
var visitIndex = ScanVisitIndex++;
|
||||
|
||||
if (id == 0) return true;
|
||||
|
||||
ref var maps = ref _slottedIdMapsInt64;
|
||||
if (maps == null || maps.Length <= slot)
|
||||
GrowSlottedMaps(ref maps, slot);
|
||||
|
||||
var map = maps[slot] ??= new IdentityMap<long, InternEntry>();
|
||||
|
||||
if (!map.TryAdd(id, out var si))
|
||||
{
|
||||
ref var entry = ref map.GetValueRef(si);
|
||||
if (entry.CacheIndex == -1)
|
||||
{
|
||||
entry.CacheIndex = ++_nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
AddWriteDuplicateEntry(entry.FirstIndex, entry.CacheIndex, isFirst: true, value: null);
|
||||
}
|
||||
AddWriteDuplicateEntry(visitIndex, entry.CacheIndex, isFirst: false, value: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var ne = ref map.GetValueRef(si);
|
||||
ne.FirstIndex = visitIndex;
|
||||
ne.CacheIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SGen scan pass: tracks an object by Guid Id.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ScanTrackObjectGuid(int slot, Guid id)
|
||||
{
|
||||
var visitIndex = ScanVisitIndex++;
|
||||
|
||||
if (id == Guid.Empty) return true;
|
||||
|
||||
ref var maps = ref _slottedIdMapsGuid;
|
||||
if (maps == null || maps.Length <= slot)
|
||||
GrowSlottedMaps(ref maps, slot);
|
||||
|
||||
var map = maps[slot] ??= new IdentityMap<Guid, InternEntry>();
|
||||
|
||||
if (!map.TryAdd(id, out var si))
|
||||
{
|
||||
ref var entry = ref map.GetValueRef(si);
|
||||
if (entry.CacheIndex == -1)
|
||||
{
|
||||
entry.CacheIndex = ++_nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
AddWriteDuplicateEntry(entry.FirstIndex, entry.CacheIndex, isFirst: true, value: null);
|
||||
}
|
||||
AddWriteDuplicateEntry(visitIndex, entry.CacheIndex, isFirst: false, value: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var ne = ref map.GetValueRef(si);
|
||||
ne.FirstIndex = visitIndex;
|
||||
ne.CacheIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void GrowSlottedMaps<TMap>(ref TMap?[]? maps, int slot) where TMap : class
|
||||
{
|
||||
var newSize = Math.Max(slot + 1, 16);
|
||||
if (maps == null)
|
||||
{
|
||||
maps = new TMap?[newSize];
|
||||
return;
|
||||
}
|
||||
var newMaps = new TMap?[newSize];
|
||||
maps.AsSpan().CopyTo(newMaps);
|
||||
maps = newMaps;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResetSlottedMaps<TKey, TValue>(IdentityMap<TKey, TValue>?[]? maps)
|
||||
where TKey : notnull where TValue : struct
|
||||
{
|
||||
if (maps == null) return;
|
||||
for (var i = 0; i < maps.Length; i++)
|
||||
maps[i]?.Reset();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UseMetadata Type Tracking
|
||||
|
||||
/// <summary>
|
||||
/// Regisztrálja a típust UseMetadata módban.
|
||||
/// Visszaadja true-t ha ez az első előfordulás (inline hash-eket kell írni),
|
||||
/// false-t ha ismételt (csak propNameHash kell).
|
||||
/// Registers a type for UseMetadata first/repeated tracking.
|
||||
/// Returns true on first occurrence (caller should write inline property hashes),
|
||||
/// false on repeated (caller writes only typeNameHash).
|
||||
/// Used by both runtime and SGen paths via wrapper.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool RegisterMetadataType(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
|
||||
{
|
||||
if (wrapper.MetadataFooterIndex >= 0)
|
||||
return false; // ismételt
|
||||
if (wrapper.MetadataSeen)
|
||||
return false;
|
||||
|
||||
wrapper.MetadataFooterIndex = 0; // jelöljük hogy már regisztrálva
|
||||
return true; // első előfordulás
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SGen inline metadata regisztráció — slot-alapú, nem kell TypeMetadataWrapper.
|
||||
/// Minden SGen típus kap egy compile-time slot indexet (AllocateMetadataSlot).
|
||||
/// Első 256 slot: bit művelet (ulong[4]), felette: HashSet fallback.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RegisterMetadataTypeBySlot(int slot)
|
||||
{
|
||||
if (slot < 256)
|
||||
{
|
||||
ref var bits = ref _metadataSeenBits[slot >> 6];
|
||||
var mask = 1UL << (slot & 63);
|
||||
if ((bits & mask) != 0) return false;
|
||||
bits |= mask;
|
||||
return true;
|
||||
}
|
||||
_metadataSeenOverflow ??= new HashSet<int>();
|
||||
return _metadataSeenOverflow.Add(slot);
|
||||
wrapper.MetadataSeen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -251,29 +251,16 @@ public static partial class AcBinarySerializer
|
|||
GeneratedWriterRegistry.Register(type, writer);
|
||||
}
|
||||
|
||||
#region UseMetadata Slot Allocation
|
||||
#region SGen Slot Allocation
|
||||
|
||||
private static int s_nextMetadataSlot;
|
||||
private static int s_nextWrapperSlot;
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a unique slot index for SGen UseMetadata first/repeated tracking.
|
||||
/// Called once per SGen type at startup (ModuleInitializer). Thread-safe.
|
||||
/// Slot is used by BinarySerializationContext.RegisterMetadataTypeBySlot.
|
||||
/// Allocates a unique slot index for SGen wrapper cache.
|
||||
/// Indexes _wrapperSlots array on AcSerializerContextBase.
|
||||
/// Used for: IdentityMap ref tracking (scan pass), MetadataSeen (write pass).
|
||||
/// </summary>
|
||||
internal static int AllocateMetadataSlot() => Interlocked.Increment(ref s_nextMetadataSlot) - 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scan Pass Tracking Slot Allocation
|
||||
|
||||
private static int s_nextTrackingSlot;
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a unique slot index for SGen scan pass ref tracking IdentityMaps.
|
||||
/// Called once per SGen type at startup (ModuleInitializer). Thread-safe.
|
||||
/// Slot indexes the per-context IdentityMap arrays in BinarySerializationContext.
|
||||
/// </summary>
|
||||
internal static int AllocateTrackingSlot() => Interlocked.Increment(ref s_nextTrackingSlot) - 1;
|
||||
internal static int AllocateWrapperSlot() => Interlocked.Increment(ref s_nextWrapperSlot) - 1;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -553,6 +540,25 @@ public static partial class AcBinarySerializer
|
|||
WriteObject(value, wrapper, context, depth, isNested: depth > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bridge for generated writers: writes a non-null complex OBJECT using slot-based wrapper lookup.
|
||||
/// SGen types pass their compile-time known slot index — avoids GetWrapper dictionary lookup.
|
||||
/// First call per slot per context: populates slot from GetWrapper. Subsequent calls: direct array index.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void WriteObjectGenerated<TOutput>(object value, Type type, int wrapperSlot, BinarySerializationContext<TOutput> context, int depth)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
if (depth > context.MaxDepth)
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.Null);
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapper = context.GetWrapperBySlot(wrapperSlot, type);
|
||||
WriteObject(value, wrapper, context, depth, isNested: depth > 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value Writing
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
|||
/// allowing the deserializer to match properties by name between different types.
|
||||
/// Default: false (no overhead)
|
||||
/// </summary>
|
||||
public bool UseMetadata { get; set; } = true;
|
||||
public bool UseMetadata { get; set; } = false;
|
||||
|
||||
public bool UseGeneratedCode { get; set; } = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
internal readonly Func<object, Guid>? RefIdGetterGuid;
|
||||
|
||||
/// <summary>
|
||||
/// UseMetadata: footer entry index for this type in the current serialization session.
|
||||
/// -1 = not yet registered. Set by RegisterMetadataType, reset by ResetTracking.
|
||||
/// Eliminates the need for Dictionary<Type, int> lookup in the serializer hot path.
|
||||
/// UseMetadata: has this type been seen in the current serialization session?
|
||||
/// false = not yet registered. Set by RegisterMetadataType, reset by ResetTracking.
|
||||
/// Used by both runtime and SGen paths (single wrapper per type per context).
|
||||
/// </summary>
|
||||
internal int MetadataFooterIndex = -1;
|
||||
internal bool MetadataSeen;
|
||||
|
||||
/// <summary>
|
||||
/// UseMetadata cachemap: source property index → target PropertySetter.
|
||||
|
|
@ -190,7 +190,7 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ResetTracking(bool preRentBuckets = false)
|
||||
{
|
||||
MetadataFooterIndex = -1;
|
||||
MetadataSeen = false;
|
||||
CacheMap = null;
|
||||
|
||||
// Options may change between sessions (pool reuse) → rebuild on next scan
|
||||
|
|
|
|||
Loading…
Reference in New Issue