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; private const int SmallArrayThreshold = 256;
/// <summary> /// <summary>
/// Footer metadata bejegyzések flat array-ben. /// Inline metadata bejegyzések flat array-ben.
/// Index = footer entry index (0 = root, 1+ = nested). /// A propNameHash alapján lineárisan keresünk (kis számú típus per stream).
/// Minden entry tartalmazza a source propNameHash-t és a source property hash-eket. /// Minden entry: source típus propNameHash + source property hash-ek.
/// </summary> /// </summary>
private MetadataFooterEntry[]? _footerEntries; private MetadataEntry[]? _metadataEntries;
private int _footerEntryCount; private int _metadataEntryCount;
/// <summary> /// <summary>
/// Egy footer bejegyzés a deserializer számára. /// Egy metadata bejegyzés a deserializer számára.
/// </summary> /// </summary>
internal struct MetadataFooterEntry internal struct MetadataEntry
{ {
public int PropNameHash; // source típus propNameHash (FNV-1a) public int PropNameHash; // source típus propNameHash (FNV-1a)
public int[] PropertyHashes; // source property hash-ek sorrendben public int[] PropertyHashes; // source property hash-ek sorrendben
@ -104,77 +104,48 @@ public static partial class AcBinaryDeserializer
} }
/// <summary> /// <summary>
/// Footer bejegyzés regisztrálása a metadata footer olvasásakor. /// Inline metadata regisztrálása az első előforduláskor.
/// Az entryIndex a footer-ben lévő sorrend (0 = root). /// A stream-ből beolvasott source property hash-ek tárolása propNameHash alapján.
/// </summary> /// </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 (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length)
if (_footerEntries == null || _footerEntries.Length <= entryIndex)
{ {
var newSize = Math.Max(entryIndex + 1, 8); var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8);
var newArray = new MetadataFooterEntry[newSize]; var newArray = new MetadataEntry[newSize];
if (_footerEntries != null) if (_metadataEntries != null)
Array.Copy(_footerEntries, newArray, _footerEntryCount); Array.Copy(_metadataEntries, newArray, _metadataEntryCount);
_footerEntries = newArray; _metadataEntries = newArray;
} }
_footerEntries[entryIndex] = new MetadataFooterEntry _metadataEntries[_metadataEntryCount++] = new MetadataEntry
{ {
PropNameHash = propNameHash, PropNameHash = propNameHash,
PropertyHashes = propertyHashes PropertyHashes = propertyHashes
}; };
if (entryIndex >= _footerEntryCount)
_footerEntryCount = entryIndex + 1;
} }
/// <summary> /// <summary>
/// Footer entry lekérése index alapján. /// Source property hash-ek keresése propNameHash alapján.
/// A root mindig a 0. elem. /// 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> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref MetadataFooterEntry GetFooterEntry(int entryIndex) public int[]? FindSourceHashes(int propNameHash)
{ {
return ref _footerEntries![entryIndex]; for (var i = 0; i < _metadataEntryCount; i++)
}
/// <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++)
{ {
var sourceHash = sourceHashes[i]; if (_metadataEntries![i].PropNameHash == propNameHash)
for (var j = 0; j < targetProperties.Length; j++) return _metadataEntries[i].PropertyHashes;
{
if (targetProperties[j].PropertyNameHash == sourceHash)
{
mapping[i] = targetProperties[j];
break;
}
}
// ha nincs match → mapping[i] marad null → skip
} }
return mapping; return null;
} }
public override void Clear() public override void Clear()
{ {
base.Clear(); base.Clear();
_footerEntryCount = 0; _metadataEntryCount = 0;
// Intern cache: clear GC roots, return large arrays to pool // Intern cache: clear GC roots, return large arrays to pool
if (_pooledInternCache != null) if (_pooledInternCache != null)

View File

@ -16,14 +16,6 @@ public static partial class AcBinaryDeserializer
/// </summary> /// </summary>
public BinaryPropertySetterInfo[] PropertiesArray { get; } 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> /// <summary>
/// True if this type has a Source Generator generated deserializer available. /// 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. /// 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); var wrapper = context.ContextClass.GetWrapper(targetType);
// UseMetadata: Populate esetén a root = footer entry 0 // UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate
if (context.HasMetadata && wrapper.Metadata.CacheMap == null && context.ContextClass.FooterEntryCount > 0)
{
wrapper.Metadata.CacheMap = context.ContextClass.BuildCacheMap(0, wrapper.Metadata);
}
PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false); PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false);
} }
@ -57,50 +52,78 @@ public static partial class AcBinaryDeserializer
int depth, int depth,
bool skipDefaultWrite) 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.
/// </summary>
private static void PopulateObjectProperties(
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> /// <summary>
/// UseMetadata olvasás cachemap-pel. Marker-vezérelt, pontosan úgy olvas mint az index-alapú, /// Unified property populate for both UseMetadata and non-UseMetadata modes.
/// de a property index-ből a cachemap adja vissza a setter-t. Ha null → skip. /// 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> /// </summary>
private static void PopulateWithCacheMap( private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context, ref BinaryDeserializationContext context,
object target, object target,
BinaryPropertySetterInfo?[] cacheMap, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
BinaryDeserializeTypeMetadata metadata,
int depth, int depth,
bool skipDefaultWrite) bool skipDefaultWrite)
{ {
var metadata = wrapper.Metadata;
var properties = metadata.PropertiesArray;
var cacheMap = wrapper.CacheMap;
var nextDepth = depth + 1; var nextDepth = depth + 1;
var isMergeMode = context.IsMergeMode; 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(); var peekCode = context.PeekByte();
// Nincs megfelelő target property → skip // Nincs megfelelő target property → skip
@ -110,102 +133,6 @@ public static partial class AcBinaryDeserializer
continue; 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 // Skip marker - property has default/null value
if (peekCode == BinaryTypeCode.PropertySkip) if (peekCode == BinaryTypeCode.PropertySkip)
{ {
@ -297,16 +224,19 @@ public static partial class AcBinaryDeserializer
ex); ex);
} }
} }
// CacheMap kész — sourceHashes null-ra, hogy a következő objektumnál ne keressen újra
if (sourceHashes != null)
wrapper.SourceHashes = null;
} }
/// <summary> /// <summary>
/// Called from ReadObject for new instances. /// Called from ReadObject/ReadObjectWithMetadata for new instances.
/// Note: For Non-IId types, hashcode is already read and registered in ReadObject before this call.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 #endregion
@ -343,7 +273,7 @@ public static partial class AcBinaryDeserializer
/// <summary> /// <summary>
/// Optimized list populate that reuses existing items when possible. /// Optimized list populate that reuses existing items when possible.
/// </summary> /// </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 elementType = propInfo.ElementType ?? typeof(object);
var count = (int)context.ReadVarUInt(); var count = (int)context.ReadVarUInt();
@ -415,7 +345,7 @@ public static partial class AcBinaryDeserializer
/// IId collection merge using cached property info. /// IId collection merge using cached property info.
/// Matches items by Id, updates existing, adds new, optionally removes orphans. /// Matches items by Id, updates existing, adds new, optionally removes orphans.
/// </summary> /// </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 elementType = propInfo.ElementType!;
var idGetter = propInfo.ElementIdGetter!; 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. /// Sets a property to its default value using typed setters to avoid boxing.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetPropertyToDefault(object target, BinaryPropertySetterInfo propInfo) private static void SetPropertyToDefault(object target, BinaryPropertySetterBase propInfo)
=> propInfo.SetToDefault(target); => propInfo.SetToDefault(target);
/// <summary> /// <summary>

View File

@ -268,6 +268,12 @@ public static partial class AcBinaryDeserializer
context.ReadByte(); // consume Object marker context.ReadByte(); // consume Object marker
PopulateObject(ref context, target, targetType, 0); 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) else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{ {
context.ReadByte(); // consume Array marker context.ReadByte(); // consume Array marker
@ -329,7 +335,12 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object) if (typeCode == BinaryTypeCode.Object)
{ {
context.ReadByte(); 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); PopulateObject(ref context, target, targetType, 0);
} }
else if (typeCode == BinaryTypeCode.Array && target is IList targetList) else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
@ -479,6 +490,12 @@ public static partial class AcBinaryDeserializer
context.ReadByte(); context.ReadByte();
PopulateObject(ref context, target, targetType, 0); 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) else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{ {
context.ReadByte(); context.ReadByte();
@ -525,7 +542,7 @@ public static partial class AcBinaryDeserializer
/// Returns true if handled, false if should fall back to generic path. /// Returns true if handled, false if should fall back to generic path.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 // Only handle if we have a typed setter
if (propInfo.AccessorType == PropertyAccessorType.Object) if (propInfo.AccessorType == PropertyAccessorType.Object)
@ -975,14 +992,8 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValue(instance, streamPosition); context.RegisterInternedValue(instance, streamPosition);
} }
// UseMetadata: root = footer entry 0, cachemap felépítése ha még nincs // UseMetadata: root object is now ObjectWithMetadata marker — no footer entry 0 handling needed here.
if (context.HasMetadata && context.ContextClass.FooterEntryCount > 0) PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
{
if (metadata.CacheMap == null)
metadata.CacheMap = context.ContextClass.BuildCacheMap(0, metadata);
}
PopulateObject(ref context, instance, metadata, depth, skipDefaultWrite: true);
// ChainMode kezelés // ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1003,16 +1014,32 @@ public static partial class AcBinaryDeserializer
} }
/// <summary> /// <summary>
/// Nested object olvasása UseMetadata módban. /// Object olvasása UseMetadata módban (inline metadata).
/// Wire format: [ObjectWithMetadata][footerIndex (VarUInt)][props...] /// Wire format:
/// A footer index-ből megkapjuk a source property hash-eket → cachemap felépítése. /// 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> /// </summary>
private static object? ReadObjectWithMetadata(ref BinaryDeserializationContext context, Type targetType, int depth) private static object? ReadObjectWithMetadata(ref BinaryDeserializationContext context, Type targetType, int depth)
{ {
var streamPosition = context.Position - 1; var streamPosition = context.Position - 1;
// Footer index olvasása // Inline metadata: propNameHash mindig jön
var footerIndex = (int)context.ReadVarUInt(); 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 // Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType)) if (IsDictionaryType(targetType, out var keyType, out var valueType))
@ -1031,11 +1058,14 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValue(instance, streamPosition); context.RegisterInternedValue(instance, streamPosition);
} }
// Cachemap felépítése ha még nincs (lazy, 1x per wrapper) // CacheMap + SourceHashes a wrapper-en (per-context, futásonként változhat)
if (metadata.CacheMap == null) if (wrapper.CacheMap == null)
metadata.CacheMap = context.ContextClass.BuildCacheMap(footerIndex, metadata); {
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 // ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1055,6 +1085,35 @@ public static partial class AcBinaryDeserializer
return instance; 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 #endregion
#region Array Reading #region Array Reading
@ -1357,11 +1416,10 @@ public static partial class AcBinaryDeserializer
if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt(); if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt();
return; return;
case BinaryTypeCode.Object: case BinaryTypeCode.Object:
SkipObject(ref context, metaData, footerIndex: -1); SkipObject(ref context, metaData);
return; return;
case BinaryTypeCode.ObjectWithMetadata: case BinaryTypeCode.ObjectWithMetadata:
var skipFooterIndex = (int)context.ReadVarUInt(); SkipObjectWithMetadata(ref context, metaData);
SkipObject(ref context, metaData, skipFooterIndex);
return; return;
case BinaryTypeCode.ObjectRef: case BinaryTypeCode.ObjectRef:
context.ReadVarInt(); context.ReadVarInt();
@ -1421,36 +1479,44 @@ public static partial class AcBinaryDeserializer
//} //}
/// <summary> /// <summary>
/// Object kihagyása. UseMetadata módban a footer entry-ből tudjuk a property számot. /// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
/// footerIndex: -1 = root (footer entry 0), >=0 = nested (a body-ból olvasott index).
/// </summary> /// </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( throw new NotSupportedException(
"SkipObject nem támogatott metadata nélkül. " + "SkipObject nem támogatott metadata nélkül. " +
"A property szám nem határozható meg típus 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) private static void SkipArray(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{ {
var count = (int)context.ReadVarUInt(); var count = (int)context.ReadVarUInt();

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using AyCode.Core.Helpers; using AyCode.Core.Helpers;
using AyCode.Core.Serializers.Binaries;
namespace AyCode.Core.Serializers; namespace AyCode.Core.Serializers;
@ -42,6 +43,19 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
/// </summary> /// </summary>
internal int MetadataFooterIndex = -1; 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! #region Typed IdentityMaps - No generic type checks in hot path!
/// <summary> /// <summary>
@ -118,6 +132,8 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
public void ResetTracking(bool preRentBuckets = false) public void ResetTracking(bool preRentBuckets = false)
{ {
MetadataFooterIndex = -1; MetadataFooterIndex = -1;
CacheMap = null;
SourceHashes = null;
if (SmallIdBitmap != null) if (SmallIdBitmap != null)
Array.Clear(SmallIdBitmap); Array.Clear(SmallIdBitmap);