Refactor UseMetadata to support inline metadata entries

Refactored deserialization logic to use inline metadata for UseMetadata mode, replacing footer-based property hash lookup. Context now tracks inline metadata entries and builds cache maps incrementally per source type. Unified property population for both metadata modes. Updated TypeMetadataWrapper to manage cache map and source hashes per context. Improved robustness for runtime source type changes and streamlined object skipping logic. Updated method signatures to use BinaryPropertySetterBase abstraction.
This commit is contained in:
Loretta 2026-02-04 20:29:08 +01:00
parent 1410ee71f0
commit cd3d65b5f4
5 changed files with 223 additions and 248 deletions

View File

@ -32,17 +32,17 @@ public static partial class AcBinaryDeserializer
private const int SmallArrayThreshold = 256;
/// <summary>
/// Footer metadata bejegyzések flat array-ben.
/// Index = footer entry index (0 = root, 1+ = nested).
/// Minden entry tartalmazza a source propNameHash-t és a source property hash-eket.
/// 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 MetadataFooterEntry[]? _footerEntries;
private int _footerEntryCount;
private MetadataEntry[]? _metadataEntries;
private int _metadataEntryCount;
/// <summary>
/// Egy footer bejegyzés a deserializer számára.
/// Egy metadata bejegyzés a deserializer számára.
/// </summary>
internal struct MetadataFooterEntry
internal struct MetadataEntry
{
public int PropNameHash; // source típus propNameHash (FNV-1a)
public int[] PropertyHashes; // source property hash-ek sorrendben
@ -104,77 +104,48 @@ public static partial class AcBinaryDeserializer
}
/// <summary>
/// Footer bejegyzés regisztrálása a metadata footer olvasásakor.
/// Az entryIndex a footer-ben lévő sorrend (0 = root).
/// 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 RegisterFooterEntry(int entryIndex, int propNameHash, int[] propertyHashes)
public void RegisterInlineMetadata(int propNameHash, int[] propertyHashes)
{
// Szükség esetén növeljük a tömböt
if (_footerEntries == null || _footerEntries.Length <= entryIndex)
if (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length)
{
var newSize = Math.Max(entryIndex + 1, 8);
var newArray = new MetadataFooterEntry[newSize];
if (_footerEntries != null)
Array.Copy(_footerEntries, newArray, _footerEntryCount);
_footerEntries = newArray;
var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8);
var newArray = new MetadataEntry[newSize];
if (_metadataEntries != null)
Array.Copy(_metadataEntries, newArray, _metadataEntryCount);
_metadataEntries = newArray;
}
_footerEntries[entryIndex] = new MetadataFooterEntry
_metadataEntries[_metadataEntryCount++] = new MetadataEntry
{
PropNameHash = propNameHash,
PropertyHashes = propertyHashes
};
if (entryIndex >= _footerEntryCount)
_footerEntryCount = entryIndex + 1;
}
/// <summary>
/// Footer entry lekérése index alapján.
/// A root mindig a 0. elem.
/// Source property hash-ek keresése propNameHash alapján.
/// Lineáris keresés — kis számú típus per stream (tipikusan &lt; 10).
/// Null ha nincs találat (első előfordulás, hash-eket olvasni kell a stream-ből).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref MetadataFooterEntry GetFooterEntry(int entryIndex)
public int[]? FindSourceHashes(int propNameHash)
{
return ref _footerEntries![entryIndex];
}
/// <summary>
/// Footer entry-k száma.
/// </summary>
public int FooterEntryCount => _footerEntryCount;
/// <summary>
/// Cachemap felépítése: source property hash-ek → target PropertySetter?[] mapping.
/// Null entry = source property-nek nincs megfelelője a target-ben → skip.
/// </summary>
public BinaryPropertySetterInfo?[] BuildCacheMap(int footerEntryIndex, BinaryDeserializeTypeMetadata targetMetadata)
{
ref var entry = ref _footerEntries![footerEntryIndex];
var sourceHashes = entry.PropertyHashes;
var targetProperties = targetMetadata.PropertiesArray;
var mapping = new BinaryPropertySetterInfo?[sourceHashes.Length];
for (var i = 0; i < sourceHashes.Length; i++)
for (var i = 0; i < _metadataEntryCount; i++)
{
var sourceHash = sourceHashes[i];
for (var j = 0; j < targetProperties.Length; j++)
{
if (targetProperties[j].PropertyNameHash == sourceHash)
{
mapping[i] = targetProperties[j];
break;
}
}
// ha nincs match → mapping[i] marad null → skip
if (_metadataEntries![i].PropNameHash == propNameHash)
return _metadataEntries[i].PropertyHashes;
}
return mapping;
return null;
}
public override void Clear()
{
base.Clear();
_footerEntryCount = 0;
_metadataEntryCount = 0;
// Intern cache: clear GC roots, return large arrays to pool
if (_pooledInternCache != null)

View File

@ -16,14 +16,6 @@ public static partial class AcBinaryDeserializer
/// </summary>
public BinaryPropertySetterInfo[] PropertiesArray { get; }
/// <summary>
/// UseMetadata cachemap: source property index → target PropertySetter.
/// Null entry = source property-nek nincs megfelelője a target-ben → skip.
/// Lazy módon épül az első találkozáskor, utána újrahasználódik.
/// A wrapper mindig új vagy pool-ból jön (clear-elve), tehát nem kell invalidálni.
/// </summary>
internal BinaryPropertySetterInfo?[]? CacheMap;
/// <summary>
/// True if this type has a Source Generator generated deserializer available.
/// Note: Due to ref struct limitations, the generated code cannot be called via delegates.

View File

@ -36,12 +36,7 @@ public static partial class AcBinaryDeserializer
{
var wrapper = context.ContextClass.GetWrapper(targetType);
// UseMetadata: Populate esetén a root = footer entry 0
if (context.HasMetadata && wrapper.Metadata.CacheMap == null && context.ContextClass.FooterEntryCount > 0)
{
wrapper.Metadata.CacheMap = context.ContextClass.BuildCacheMap(0, wrapper.Metadata);
}
// UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate
PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false);
}
@ -57,50 +52,78 @@ public static partial class AcBinaryDeserializer
int depth,
bool skipDefaultWrite)
{
PopulateObjectProperties(ref context, target, wrapper.Metadata, depth, skipDefaultWrite);
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
}
/// <summary>
/// Property-k olvasása. UseMetadata módban cachemap-alapú (marker-vezérelt),
/// egyébként index-alapú (mint eddig).
/// A cachemap a metadata.CacheMap-ben van, a ReadObject/ReadObjectWithMetadata építi fel.
/// Unified property populate for both UseMetadata and non-UseMetadata modes.
/// UseMetadata=false: properties[i] gives the setter directly (same-type, index-based).
/// UseMetadata=true: cacheMap[i] gives the setter. If null (first object of this type),
/// fast path checks props[index+1], fallback searches from props[index+2].
/// index tracks last matched target property index (starts at -1).
/// </summary>
private static void PopulateObjectProperties(
private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context,
object target,
BinaryDeserializeTypeMetadata metadata,
int depth,
bool skipDefaultWrite)
{
if (context.HasMetadata && metadata.CacheMap != null)
{
// UseMetadata: marker-vezérelt olvasás, cachemap-ből vesszük a setter-t
PopulateWithCacheMap(ref context, target, metadata.CacheMap, metadata, depth, skipDefaultWrite);
}
else
{
PopulateObjectPropertiesIndexed(ref context, target, metadata, depth, skipDefaultWrite);
}
}
/// <summary>
/// UseMetadata olvasás cachemap-pel. Marker-vezérelt, pontosan úgy olvas mint az index-alapú,
/// de a property index-ből a cachemap adja vissza a setter-t. Ha null → skip.
/// </summary>
private static void PopulateWithCacheMap(
ref BinaryDeserializationContext context,
object target,
BinaryPropertySetterInfo?[] cacheMap,
BinaryDeserializeTypeMetadata metadata,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth,
bool skipDefaultWrite)
{
var metadata = wrapper.Metadata;
var properties = metadata.PropertiesArray;
var cacheMap = wrapper.CacheMap;
var nextDepth = depth + 1;
var isMergeMode = context.IsMergeMode;
for (int i = 0; i < cacheMap.Length; i++)
// UseMetadata: cacheMap.Length a source property-k száma
// Non-UseMetadata: properties.Length a target property-k száma (source == target)
var propCount = cacheMap?.Length ?? properties.Length;
var sourceHashes = wrapper.SourceHashes; // only non-null when cacheMap != null
var index = -1; // last matched target property index (for incremental CacheMap building)
for (int i = 0; i < propCount; i++)
{
var propInfo = cacheMap[i];
BinaryPropertySetterBase? propInfo;
if (cacheMap != null)
{
// UseMetadata mode
propInfo = cacheMap[i];
if (propInfo == null && sourceHashes != null)
{
// First object of this type — inkrementális CacheMap felépítés
var hash = sourceHashes[i];
// Fast path: props[index+1] (same-type → next property in order)
var nextIdx = index + 1;
if (nextIdx < properties.Length && properties[nextIdx].PropertyNameHash == hash)
{
propInfo = properties[nextIdx];
index = nextIdx;
}
else
{
// Fallback: search from index+2 (cross-type, alphabetical order → forward only)
for (var j = nextIdx + 1; j < properties.Length; j++)
{
if (properties[j].PropertyNameHash == hash)
{
propInfo = properties[j];
index = j;
break;
}
}
}
cacheMap[i] = propInfo; // null marad ha nincs match → skip next time too
}
}
else
{
// Non-UseMetadata: direct index-based
propInfo = properties[i];
}
var peekCode = context.PeekByte();
// Nincs megfelelő target property → skip
@ -110,102 +133,6 @@ public static partial class AcBinaryDeserializer
continue;
}
// PropertySkip marker - default/null érték
if (peekCode == BinaryTypeCode.PropertySkip)
{
context.ReadByte();
if (!skipDefaultWrite)
SetPropertyToDefault(target, propInfo);
continue;
}
// Null érték
if (peekCode == BinaryTypeCode.Null)
{
context.ReadByte();
propInfo.SetValue(target, null);
continue;
}
// Kollekció kezelés
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
{
var existingCollection = propInfo.GetValue(target);
if (existingCollection is IList existingList)
{
context.ReadByte();
if (isMergeMode && propInfo.IsIIdCollection)
MergeIIdCollection(ref context, existingList, propInfo, nextDepth);
else
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
continue;
}
}
// Nested object kezelés - Object és ObjectWithMetadata marker
if ((peekCode == BinaryTypeCode.Object || peekCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
{
var existingObj = propInfo.GetValue(target);
if (existingObj != null)
{
// A ReadValue kezeli mindkét markert (Object és ObjectWithMetadata)
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
if (value != null)
{
CopyProperties(value, existingObj, context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata);
}
continue;
}
}
// Default: érték olvasás és beállítás
var positionBeforeRead = context.Position;
try
{
if (propInfo.AccessorType != PropertyAccessorType.Object &&
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
continue;
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
propInfo.SetValue(target, value);
}
catch (InvalidCastException ex)
{
var targetType = target.GetType();
throw new AcBinaryDeserializationException(
$"Típushibás property '{propInfo.Name}' (cachemap index {i}), target: '{targetType.Name}'. " +
$"Várt típus: '{propInfo.PropertyType.FullName}'. " +
$"PeekCode: {peekCode} (0x{peekCode:X2}). " +
$"Pozíció: {positionBeforeRead} → {context.Position}. Depth: {depth}. " +
$"Hiba: {ex.Message}",
positionBeforeRead,
propInfo.PropertyType,
ex);
}
}
}
/// <summary>
/// Index-based property populate (original logic, used when UseMetadata is off).
/// Properties are read in order matching the target type's property array.
/// </summary>
private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context,
object target,
BinaryDeserializeTypeMetadata metadata,
int depth,
bool skipDefaultWrite)
{
var properties = metadata.PropertiesArray;
var nextDepth = depth + 1;
var isMergeMode = context.IsMergeMode;
// All properties start from index 0 - Id is included with normal type markers
for (int i = 0; i < properties.Length; i++)
{
var propInfo = properties[i];
var peekCode = context.PeekByte();
// Skip marker - property has default/null value
if (peekCode == BinaryTypeCode.PropertySkip)
{
@ -297,16 +224,19 @@ public static partial class AcBinaryDeserializer
ex);
}
}
// CacheMap kész — sourceHashes null-ra, hogy a következő objektumnál ne keressen újra
if (sourceHashes != null)
wrapper.SourceHashes = null;
}
/// <summary>
/// Called from ReadObject for new instances.
/// Note: For Non-IId types, hashcode is already read and registered in ReadObject before this call.
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PopulateObject(ref BinaryDeserializationContext context, object target, BinaryDeserializeTypeMetadata metadata, int depth, bool skipDefaultWrite)
private static void PopulateObject(ref BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
{
PopulateObjectProperties(ref context, target, metadata, depth, skipDefaultWrite);
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
}
#endregion
@ -343,7 +273,7 @@ public static partial class AcBinaryDeserializer
/// <summary>
/// Optimized list populate that reuses existing items when possible.
/// </summary>
private static void PopulateListOptimized(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterInfo propInfo, int depth)
private static void PopulateListOptimized(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
{
var elementType = propInfo.ElementType ?? typeof(object);
var count = (int)context.ReadVarUInt();
@ -415,7 +345,7 @@ public static partial class AcBinaryDeserializer
/// IId collection merge using cached property info.
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
/// </summary>
private static void MergeIIdCollection(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterInfo propInfo, int depth)
private static void MergeIIdCollection(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
{
var elementType = propInfo.ElementType!;
var idGetter = propInfo.ElementIdGetter!;
@ -653,7 +583,7 @@ public static partial class AcBinaryDeserializer
/// Sets a property to its default value using typed setters to avoid boxing.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetPropertyToDefault(object target, BinaryPropertySetterInfo propInfo)
private static void SetPropertyToDefault(object target, BinaryPropertySetterBase propInfo)
=> propInfo.SetToDefault(target);
/// <summary>

View File

@ -268,6 +268,12 @@ public static partial class AcBinaryDeserializer
context.ReadByte(); // consume Object marker
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte(); // consume ObjectWithMetadata marker
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{
context.ReadByte(); // consume Array marker
@ -329,7 +335,12 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object)
{
context.ReadByte();
// Uses unified PopulateObject which checks IsMergeMode internally
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte();
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
@ -479,6 +490,12 @@ public static partial class AcBinaryDeserializer
context.ReadByte();
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte();
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{
context.ReadByte();
@ -525,7 +542,7 @@ public static partial class AcBinaryDeserializer
/// Returns true if handled, false if should fall back to generic path.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryReadAndSetTypedValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, byte peekCode)
private static bool TryReadAndSetTypedValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
{
// Only handle if we have a typed setter
if (propInfo.AccessorType == PropertyAccessorType.Object)
@ -975,14 +992,8 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValue(instance, streamPosition);
}
// UseMetadata: root = footer entry 0, cachemap felépítése ha még nincs
if (context.HasMetadata && context.ContextClass.FooterEntryCount > 0)
{
if (metadata.CacheMap == null)
metadata.CacheMap = context.ContextClass.BuildCacheMap(0, metadata);
}
PopulateObject(ref context, instance, metadata, depth, skipDefaultWrite: true);
// UseMetadata: root object is now ObjectWithMetadata marker — no footer entry 0 handling needed here.
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1003,16 +1014,32 @@ public static partial class AcBinaryDeserializer
}
/// <summary>
/// Nested object olvasása UseMetadata módban.
/// Wire format: [ObjectWithMetadata][footerIndex (VarUInt)][props...]
/// A footer index-ből megkapjuk a source property hash-eket → cachemap felépítése.
/// Object olvasása UseMetadata módban (inline metadata).
/// Wire format:
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0 (4b)][hash1]...[props...]
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
/// A propNameHash-ből a ContextClass megkeresi a source hash-eket (lineáris, kis tömb).
/// Ha nincs találat → első előfordulás, beolvassuk a hash-eket inline-ból.
/// </summary>
private static object? ReadObjectWithMetadata(ref BinaryDeserializationContext context, Type targetType, int depth)
{
var streamPosition = context.Position - 1;
// Footer index olvasása
var footerIndex = (int)context.ReadVarUInt();
// Inline metadata: propNameHash mindig jön
var propNameHash = context.ReadInt32Raw();
// Source hash-ek keresése — ha nincs, első előfordulás → olvasás a stream-ből
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
var propCount = (int)context.ReadVarUInt();
sourceHashes = new int[propCount];
for (var i = 0; i < propCount; i++)
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
}
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
@ -1031,11 +1058,14 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValue(instance, streamPosition);
}
// Cachemap felépítése ha még nincs (lazy, 1x per wrapper)
if (metadata.CacheMap == null)
metadata.CacheMap = context.ContextClass.BuildCacheMap(footerIndex, metadata);
// CacheMap + SourceHashes a wrapper-en (per-context, futásonként változhat)
if (wrapper.CacheMap == null)
{
wrapper.CacheMap = new BinaryPropertySetterBase?[sourceHashes.Length];
wrapper.SourceHashes = sourceHashes;
}
PopulateObject(ref context, instance, metadata, depth, skipDefaultWrite: true);
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1055,6 +1085,35 @@ public static partial class AcBinaryDeserializer
return instance;
}
/// <summary>
/// Inline metadata olvasása a Populate path-hoz.
/// 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.
/// </summary>
private static void ReadInlineMetadataForPopulate(ref BinaryDeserializationContext context, Type targetType)
{
var propNameHash = context.ReadInt32Raw();
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
var propCount = (int)context.ReadVarUInt();
sourceHashes = new int[propCount];
for (var i = 0; i < propCount; i++)
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
}
var wrapper = context.ContextClass.GetWrapper(targetType);
if (wrapper.CacheMap == null)
{
wrapper.CacheMap = new BinaryPropertySetterBase?[sourceHashes.Length];
wrapper.SourceHashes = sourceHashes;
}
}
#endregion
#region Array Reading
@ -1357,11 +1416,10 @@ public static partial class AcBinaryDeserializer
if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt();
return;
case BinaryTypeCode.Object:
SkipObject(ref context, metaData, footerIndex: -1);
SkipObject(ref context, metaData);
return;
case BinaryTypeCode.ObjectWithMetadata:
var skipFooterIndex = (int)context.ReadVarUInt();
SkipObject(ref context, metaData, skipFooterIndex);
SkipObjectWithMetadata(ref context, metaData);
return;
case BinaryTypeCode.ObjectRef:
context.ReadVarInt();
@ -1421,36 +1479,44 @@ public static partial class AcBinaryDeserializer
//}
/// <summary>
/// Object kihagyása. UseMetadata módban a footer entry-ből tudjuk a property számot.
/// footerIndex: -1 = root (footer entry 0), >=0 = nested (a body-ból olvasott index).
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
/// </summary>
private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int footerIndex)
private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
if (context.HasMetadata)
{
// Root object: footer entry 0, nested: a megadott footerIndex
var entryIndex = footerIndex < 0 ? 0 : footerIndex;
if (entryIndex >= context.ContextClass.FooterEntryCount)
{
throw new AcBinaryDeserializationException(
$"SkipObject: Érvénytelen footer index {entryIndex}.",
context.Position);
}
ref var entry = ref context.ContextClass.GetFooterEntry(entryIndex);
var propCount = entry.PropertyHashes.Length;
for (var i = 0; i < propCount; i++)
{
SkipValue(ref context, metaData);
}
return;
}
// Nincs metadata → nem tudjuk kihagyni az object-et
throw new NotSupportedException(
"SkipObject nem támogatott metadata nélkül. " +
"A property szám nem határozható meg típus metadata nélkül.");
}
/// <summary>
/// Skip ObjectWithMetadata: inline metadata-ból olvassuk a propCount-ot.
/// Ha az adott propNameHash-hez már van source hash → propCount onnan.
/// Ha első előfordulás → propCount + hash-ek a stream-ből.
/// </summary>
private static void SkipObjectWithMetadata(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var propNameHash = context.ReadInt32Raw();
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
// Első előfordulás: propCount + hash-ek jönnek a stream-ben
var propCount = (int)context.ReadVarUInt();
sourceHashes = new int[propCount];
for (var i = 0; i < propCount; i++)
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
}
// Skip all properties
for (var i = 0; i < sourceHashes.Length; i++)
{
SkipValue(ref context, metaData);
}
}
private static void SkipArray(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var count = (int)context.ReadVarUInt();

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using AyCode.Core.Helpers;
using AyCode.Core.Serializers.Binaries;
namespace AyCode.Core.Serializers;
@ -42,6 +43,19 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
/// </summary>
internal int MetadataFooterIndex = -1;
/// <summary>
/// UseMetadata cachemap: source property index → target PropertySetter.
/// Per-context (wrapper-szintű), mert futásonként eltérő source type-pal találkozhat.
/// Inkrementálisan épül a PopulateObjectPropertiesIndexed-ben.
/// </summary>
internal BinaryPropertySetterBase?[]? CacheMap;
/// <summary>
/// Source property hash-ek a CacheMap-hez — az inline metadata-ból olvasva.
/// Per-context, mert eltérő source type-pal más hash-ek jönnek.
/// </summary>
internal int[]? SourceHashes;
#region Typed IdentityMaps - No generic type checks in hot path!
/// <summary>
@ -118,6 +132,8 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
public void ResetTracking(bool preRentBuckets = false)
{
MetadataFooterIndex = -1;
CacheMap = null;
SourceHashes = null;
if (SmallIdBitmap != null)
Array.Clear(SmallIdBitmap);