Optimize cache index assignment during scan pass
Refactored AcBinarySerializer to assign cache indices immediately upon detecting duplicates during the scan pass, eliminating the need for a separate post-processing step. Updated TryTrack methods to take a ref nextCacheIndex for inline assignment. Removed AssignCacheIndicesInOrder and related code, simplified string interning, and made RegisterMetadataType static. This reduces allocations and improves performance by making cache index assignment a single-pass operation.
This commit is contained in:
parent
e5d4b1091f
commit
9b4fa1159a
|
|
@ -96,9 +96,18 @@ public static partial class AcBinarySerializer
|
|||
//private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new();
|
||||
|
||||
private IdentityMap<string, InternEntry>? _stringInternMap;
|
||||
private int _nextCacheIndex; // Next dense cache index to assign
|
||||
private int _nextCacheIndex; // Next dense cache index to assign (starts at 0, uses ++_nextCacheIndex)
|
||||
private int _nextFirstIndex; // Next first occurrence index to assign (scan pass)
|
||||
|
||||
/// <summary>
|
||||
/// Next cache index reference for scan pass. Direct ref access for TryTrack methods.
|
||||
/// </summary>
|
||||
public ref int NextCacheIndexRef
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _nextCacheIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Next first occurrence index for scan pass. Direct access for performance.
|
||||
/// </summary>
|
||||
|
|
@ -259,7 +268,7 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan pass: tracks a string for interning. Marks as cached on 2nd occurrence.
|
||||
/// Scan pass: tracks a string for interning. Assigns CacheIndex immediately on 2nd occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ScanInternString(string value)
|
||||
|
|
@ -268,14 +277,17 @@ public static partial class AcBinarySerializer
|
|||
|
||||
if (!_stringInternMap.TryAdd(value, out var slotIndex))
|
||||
{
|
||||
// 2+ occurrence: mark as cached
|
||||
// 2+ occurrence: assign CacheIndex immediately
|
||||
ref var entry = ref _stringInternMap.GetValueRef(slotIndex);
|
||||
if (entry.CacheIndex == -1)
|
||||
entry.CacheIndex = -2; // -2 = cached, pending CacheIndex assignment
|
||||
{
|
||||
entry.CacheIndex = ++_nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1st occurrence: store FirstIndex
|
||||
// 1st occurrence: store FirstIndex for validation, CacheIndex = -1 (not cached yet)
|
||||
ref var newEntry = ref _stringInternMap.GetValueRef(slotIndex);
|
||||
newEntry.FirstIndex = _nextFirstIndex++;
|
||||
newEntry.CacheIndex = -1;
|
||||
|
|
@ -291,220 +303,6 @@ public static partial class AcBinarySerializer
|
|||
/// </summary>
|
||||
public int GetCacheCount() => _nextCacheIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Assigns CacheIndex values in FirstIndex order after scan pass.
|
||||
/// Collects all cached entries (CacheIndex == -2), sorts by FirstIndex, assigns 0, 1, 2...
|
||||
/// Optimized: single pass collection, no allocations for wrapper iteration.
|
||||
/// </summary>
|
||||
public void AssignCacheIndicesInOrder()
|
||||
{
|
||||
// Fast path: no caching at all
|
||||
if (_stringInternMap == null && !HasAnyIdentityMap())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Count cached entries in single pass
|
||||
var cachedCount = CountAllCachedEntries();
|
||||
if (cachedCount == 0)
|
||||
return;
|
||||
|
||||
// Collect entries for sorting
|
||||
Span<(int SlotIndex, int FirstIndex, int MapType)> entries = cachedCount <= 64
|
||||
? stackalloc (int, int, int)[cachedCount]
|
||||
: new (int, int, int)[cachedCount];
|
||||
|
||||
var idx = 0;
|
||||
|
||||
// Collect from string intern map (mapType = 0)
|
||||
if (_stringInternMap != null)
|
||||
{
|
||||
var count = _stringInternMap.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
ref var entry = ref _stringInternMap.GetValueRefAt(i);
|
||||
if (entry.CacheIndex == -2)
|
||||
entries[idx++] = (i, entry.FirstIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect from wrapper identity maps - use foreach, no allocation
|
||||
var wrapperIdx = 1;
|
||||
foreach (var wrapper in GetWrappers())
|
||||
{
|
||||
var baseMapType = wrapperIdx * 3;
|
||||
CollectCachedEntries(wrapper.IdentityMapInt32, baseMapType + 0, ref entries, ref idx);
|
||||
CollectCachedEntries(wrapper.IdentityMapInt64, baseMapType + 1, ref entries, ref idx);
|
||||
CollectCachedEntries(wrapper.IdentityMapGuid, baseMapType + 2, ref entries, ref idx);
|
||||
wrapperIdx++;
|
||||
}
|
||||
|
||||
// Sort by FirstIndex
|
||||
var usedEntries = entries.Slice(0, idx);
|
||||
usedEntries.Sort((a, b) => a.FirstIndex.CompareTo(b.FirstIndex));
|
||||
|
||||
// Assign CacheIndex in sorted order
|
||||
for (var i = 0; i < idx; i++)
|
||||
{
|
||||
var (slotIndex, _, mapType) = usedEntries[i];
|
||||
if (mapType == 0)
|
||||
{
|
||||
ref var entry = ref _stringInternMap!.GetValueRefAt(slotIndex);
|
||||
entry.CacheIndex = _nextCacheIndex++;
|
||||
entry.IsFirstWrite = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find wrapper by index
|
||||
var wrapperIndex = mapType / 3 - 1;
|
||||
var mapIndex = mapType % 3;
|
||||
var wIdx = 0;
|
||||
foreach (var wrapper in GetWrappers())
|
||||
{
|
||||
if (wIdx == wrapperIndex)
|
||||
{
|
||||
switch (mapIndex)
|
||||
{
|
||||
case 0:
|
||||
ref var entry32 = ref wrapper.IdentityMapInt32!.GetValueRefAt(slotIndex);
|
||||
entry32.CacheIndex = _nextCacheIndex++;
|
||||
entry32.IsFirstWrite = true;
|
||||
break;
|
||||
case 1:
|
||||
ref var entry64 = ref wrapper.IdentityMapInt64!.GetValueRefAt(slotIndex);
|
||||
entry64.CacheIndex = _nextCacheIndex++;
|
||||
entry64.IsFirstWrite = true;
|
||||
break;
|
||||
case 2:
|
||||
ref var entryGuid = ref wrapper.IdentityMapGuid!.GetValueRefAt(slotIndex);
|
||||
entryGuid.CacheIndex = _nextCacheIndex++;
|
||||
entryGuid.IsFirstWrite = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
wIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// DEBUG: Print string intern map contents
|
||||
if (_stringInternMap != null)
|
||||
{
|
||||
Console.WriteLine($"\n=== AssignCacheIndicesInOrder completed ===");
|
||||
Console.WriteLine($"Total strings in map: {_stringInternMap.Count}");
|
||||
Console.WriteLine($"Total cached (CacheIndex >= 0): {cachedCount}");
|
||||
Console.WriteLine($"NextCacheIndex: {_nextCacheIndex}");
|
||||
Console.WriteLine("String entries:");
|
||||
for (var i = 0; i < _stringInternMap.Count; i++)
|
||||
{
|
||||
ref var entry = ref _stringInternMap.GetValueRefAt(i);
|
||||
var key = _stringInternMap.GetKeyAt(i);
|
||||
Console.WriteLine($" [{i}] Key=\"{key}\" FirstIndex={entry.FirstIndex} CacheIndex={entry.CacheIndex} IsFirstWrite={entry.IsFirstWrite}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool HasAnyIdentityMap()
|
||||
{
|
||||
foreach (var wrapper in GetWrappers())
|
||||
{
|
||||
if (wrapper.IdentityMapInt32 != null || wrapper.IdentityMapInt64 != null || wrapper.IdentityMapGuid != null)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int CountAllCachedEntries()
|
||||
{
|
||||
var cachedCount = 0;
|
||||
if (_stringInternMap != null)
|
||||
{
|
||||
var count = _stringInternMap.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
ref var entry = ref _stringInternMap.GetValueRefAt(i);
|
||||
if (entry.CacheIndex == -2)
|
||||
cachedCount++;
|
||||
}
|
||||
}
|
||||
foreach (var wrapper in GetWrappers())
|
||||
{
|
||||
cachedCount += CountCachedEntries(wrapper.IdentityMapInt32);
|
||||
cachedCount += CountCachedEntries(wrapper.IdentityMapInt64);
|
||||
cachedCount += CountCachedEntries(wrapper.IdentityMapGuid);
|
||||
}
|
||||
return cachedCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CountCachedEntries<TKey>(IdentityMap<TKey, InternEntry>? map) where TKey : notnull
|
||||
{
|
||||
if (map == null) return 0;
|
||||
var count = 0;
|
||||
var mapCount = map.Count;
|
||||
for (var i = 0; i < mapCount; i++)
|
||||
{
|
||||
ref var entry = ref map.GetValueRefAt(i);
|
||||
if (entry.CacheIndex == -2)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CollectCachedEntries<TKey>(
|
||||
IdentityMap<TKey, InternEntry>? map,
|
||||
int mapType,
|
||||
ref Span<(int SlotIndex, int FirstIndex, int MapType)> entries,
|
||||
ref int idx) where TKey : notnull
|
||||
{
|
||||
if (map == null) return;
|
||||
var count = map.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
ref var entry = ref map.GetValueRefAt(i);
|
||||
if (entry.CacheIndex == -2)
|
||||
entries[idx++] = (i, entry.FirstIndex, mapType);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Reference Tracking (IId + Non-IId)
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an IId object (Int32 Id).
|
||||
/// Returns true if first occurrence, false if already seen.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackObject(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, object obj, out int cacheIndex)
|
||||
{
|
||||
return TryTrack(wrapper, obj, _nextFirstIndex++, out cacheIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an IId object (Int64 Id).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackObjectLong(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, object obj, out int cacheIndex)
|
||||
{
|
||||
return TryTrackLong(wrapper, obj, _nextFirstIndex++, out cacheIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an IId object (Guid Id).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackObjectGuid(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, object obj, out int cacheIndex)
|
||||
{
|
||||
return TryTrackGuid(wrapper, obj, _nextFirstIndex++, out cacheIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -516,7 +314,7 @@ public static partial class AcBinarySerializer
|
|||
/// false-t ha ismételt (csak propNameHash kell).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RegisterMetadataType(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
|
||||
public static bool RegisterMetadataType(TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
|
||||
{
|
||||
if (wrapper.MetadataFooterIndex >= 0)
|
||||
return false; // ismételt
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public static partial class AcBinarySerializer
|
|||
/// First pass: scans object graph to identify duplicates (strings + objects).
|
||||
/// Only traverses reference properties (complex types + strings).
|
||||
/// Stops traversing an object after its 2nd occurrence.
|
||||
/// After scan: assigns CacheIndex in FirstIndex order.
|
||||
/// CacheIndex is assigned immediately on 2nd occurrence (no post-processing needed).
|
||||
/// </summary>
|
||||
private static void ScanForDuplicates(object value, Type type, BinarySerializationContext context)
|
||||
{
|
||||
|
|
@ -17,7 +17,7 @@ public static partial class AcBinarySerializer
|
|||
return;
|
||||
|
||||
ScanValue(value, type, context, 0);
|
||||
context.AssignCacheIndicesInOrder();
|
||||
// No AssignCacheIndicesInOrder() needed - CacheIndex assigned inline on 2nd occurrence
|
||||
}
|
||||
|
||||
private static void ScanValue(object? value, Type type, BinarySerializationContext context, int depth)
|
||||
|
|
@ -70,15 +70,15 @@ public static partial class AcBinarySerializer
|
|||
{
|
||||
case IdAccessorType.Int32:
|
||||
var id32 = wrapper.RefIdGetterInt32!(value);
|
||||
isFirst = wrapper.TryTrackInt32(id32, context.NextFirstIndex++, out _);
|
||||
isFirst = wrapper.TryTrackInt32(id32, context.NextFirstIndex++, ref context.NextCacheIndexRef, out _);
|
||||
break;
|
||||
case IdAccessorType.Int64:
|
||||
var id64 = wrapper.RefIdGetterInt64!(value);
|
||||
isFirst = wrapper.TryTrackInt64(id64, context.NextFirstIndex++, out _);
|
||||
isFirst = wrapper.TryTrackInt64(id64, context.NextFirstIndex++, ref context.NextCacheIndexRef, out _);
|
||||
break;
|
||||
case IdAccessorType.Guid:
|
||||
var idGuid = wrapper.RefIdGetterGuid!(value);
|
||||
isFirst = wrapper.TryTrackGuid(idGuid, context.NextFirstIndex++, out _);
|
||||
isFirst = wrapper.TryTrackGuid(idGuid, context.NextFirstIndex++, ref context.NextCacheIndexRef, out _);
|
||||
break;
|
||||
default:
|
||||
isFirst = true;
|
||||
|
|
@ -90,13 +90,26 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
|
||||
// Recursive scan on reference properties only
|
||||
// Use typed getter for strings (much faster than reflection GetValue)
|
||||
var refProperties = metadata.ReferenceProperties;
|
||||
var nextDepth2 = depth + 1;
|
||||
for (var i = 0; i < refProperties.Length; i++)
|
||||
{
|
||||
var propValue = refProperties[i].GetValue(value);
|
||||
if (propValue != null)
|
||||
ScanValue(propValue, refProperties[i].PropertyType, context, nextDepth2);
|
||||
var prop = refProperties[i];
|
||||
if (prop.AccessorType == PropertyAccessorType.String)
|
||||
{
|
||||
// Fast path: typed getter for string
|
||||
var str2 = prop.GetString(value);
|
||||
if (str2 != null && context.IsValidForInterningString(str2.Length))
|
||||
context.ScanInternString(str2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Object property: use generic getter
|
||||
var propValue = prop.GetValue(value);
|
||||
if (propValue != null)
|
||||
ScanValue(propValue, prop.PropertyType, context, nextDepth2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@ public static partial class AcBinarySerializer
|
|||
var isFirstMetadataOccurrence = false;
|
||||
if (context.UseMetadata)
|
||||
{
|
||||
isFirstMetadataOccurrence = context.RegisterMetadataType(wrapper);
|
||||
isFirstMetadataOccurrence = BinarySerializationContext.RegisterMetadataType(wrapper);
|
||||
}
|
||||
|
||||
// Reference handling: lookup entry from scan pass, check IsFirstWrite
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AyCode.Core.Serializers;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for all serialization contexts (Binary, JSON, Toon).
|
||||
/// Provides common serialization operations: Id extraction, tracking first occurrence.
|
||||
/// Provides common serialization operations.
|
||||
/// Derived classes are sealed for JIT devirtualization (direct call speed).
|
||||
/// </summary>
|
||||
/// <typeparam name="TMetadata">The concrete metadata type for serialization.</typeparam>
|
||||
|
|
@ -15,45 +11,6 @@ public abstract class SerializationContextBase<TMetadata, TOptions> : AcSerializ
|
|||
where TMetadata : TypeMetadataBase
|
||||
where TOptions : AcSerializerOptions
|
||||
{
|
||||
|
||||
#region Tracking - InternEntry based (serializer side)
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with int RefId.
|
||||
/// Returns true if first occurrence, false if already seen.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrack(TypeMetadataWrapper<TMetadata> wrapper, object obj, int firstIndex, out int cacheIndex)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == IdAccessorType.Int32);
|
||||
var id = wrapper.RefIdGetterInt32!(obj);
|
||||
return wrapper.TryTrackInt32(id, firstIndex, out cacheIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with long RefId.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackLong(TypeMetadataWrapper<TMetadata> wrapper, object obj, int firstIndex, out int cacheIndex)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == IdAccessorType.Int64);
|
||||
var id = wrapper.RefIdGetterInt64!(obj);
|
||||
return wrapper.TryTrackInt64(id, firstIndex, out cacheIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with Guid RefId.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackGuid(TypeMetadataWrapper<TMetadata> wrapper, object obj, int firstIndex, out int cacheIndex)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == IdAccessorType.Guid);
|
||||
var id = wrapper.RefIdGetterGuid!(obj);
|
||||
return wrapper.TryTrackGuid(id, firstIndex, out cacheIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -140,26 +140,28 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
|
||||
/// <summary>
|
||||
/// Tries to track Int32 Id. Returns true if first occurrence.
|
||||
/// On 2+ occurrence: marks as cached (-2), returns existing CacheIndex.
|
||||
/// CacheIndex is assigned later by AssignCacheIndicesInOrder().
|
||||
/// On 2+ occurrence: assigns CacheIndex immediately using ++nextCacheIndex.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackInt32(int id, int firstIndex, out int cacheIndex)
|
||||
public bool TryTrackInt32(int id, int firstIndex, ref int nextCacheIndex, out int cacheIndex)
|
||||
{
|
||||
if (id == 0) { cacheIndex = -1; return true; } // Default Id - no tracking
|
||||
|
||||
var map = IdentityMapInt32 ??= new IdentityMap<int, InternEntry>();
|
||||
if (!map.TryAdd(id, out var slotIndex))
|
||||
{
|
||||
// 2+ occurrence: mark as cached
|
||||
// 2+ occurrence: assign CacheIndex immediately
|
||||
ref var entry = ref map.GetValueRef(slotIndex);
|
||||
if (entry.CacheIndex == -1)
|
||||
entry.CacheIndex = -2; // -2 = cached, pending assignment
|
||||
{
|
||||
entry.CacheIndex = ++nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
}
|
||||
cacheIndex = entry.CacheIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1st occurrence: store FirstIndex
|
||||
// 1st occurrence: store FirstIndex for validation
|
||||
ref var newEntry = ref map.GetValueRef(slotIndex);
|
||||
newEntry.FirstIndex = firstIndex;
|
||||
newEntry.CacheIndex = -1;
|
||||
|
|
@ -171,7 +173,7 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
/// Tries to track Int64 Id. Returns true if first occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackInt64(long id, int firstIndex, out int cacheIndex)
|
||||
public bool TryTrackInt64(long id, int firstIndex, ref int nextCacheIndex, out int cacheIndex)
|
||||
{
|
||||
if (id == 0) { cacheIndex = -1; return true; }
|
||||
|
||||
|
|
@ -180,7 +182,10 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
{
|
||||
ref var entry = ref map.GetValueRef(slotIndex);
|
||||
if (entry.CacheIndex == -1)
|
||||
entry.CacheIndex = -2;
|
||||
{
|
||||
entry.CacheIndex = ++nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
}
|
||||
cacheIndex = entry.CacheIndex;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -196,7 +201,7 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
/// Tries to track Guid Id. Returns true if first occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrackGuid(Guid id, int firstIndex, out int cacheIndex)
|
||||
public bool TryTrackGuid(Guid id, int firstIndex, ref int nextCacheIndex, out int cacheIndex)
|
||||
{
|
||||
if (id == Guid.Empty) { cacheIndex = -1; return true; }
|
||||
|
||||
|
|
@ -205,7 +210,10 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
{
|
||||
ref var entry = ref map.GetValueRef(slotIndex);
|
||||
if (entry.CacheIndex == -1)
|
||||
entry.CacheIndex = -2;
|
||||
{
|
||||
entry.CacheIndex = ++nextCacheIndex;
|
||||
entry.IsFirstWrite = true;
|
||||
}
|
||||
cacheIndex = entry.CacheIndex;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue