diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs
index b08bc76..366187c 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs
@@ -32,17 +32,17 @@ public static partial class AcBinaryDeserializer
private const int SmallArrayThreshold = 256;
///
- /// 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.
///
- private MetadataFooterEntry[]? _footerEntries;
- private int _footerEntryCount;
+ private MetadataEntry[]? _metadataEntries;
+ private int _metadataEntryCount;
///
- /// Egy footer bejegyzés a deserializer számára.
+ /// Egy metadata bejegyzés a deserializer számára.
///
- 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
}
///
- /// 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.
///
- 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;
}
///
- /// 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 < 10).
+ /// Null ha nincs találat (első előfordulás, hash-eket olvasni kell a stream-ből).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ref MetadataFooterEntry GetFooterEntry(int entryIndex)
+ public int[]? FindSourceHashes(int propNameHash)
{
- return ref _footerEntries![entryIndex];
- }
-
- ///
- /// Footer entry-k száma.
- ///
- public int FooterEntryCount => _footerEntryCount;
-
- ///
- /// Cachemap felépítése: source property hash-ek → target PropertySetter?[] mapping.
- /// Null entry = source property-nek nincs megfelelője a target-ben → skip.
- ///
- 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)
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
index 923428d..746c28c 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
@@ -16,14 +16,6 @@ public static partial class AcBinaryDeserializer
///
public BinaryPropertySetterInfo[] PropertiesArray { get; }
- ///
- /// 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.
- ///
- internal BinaryPropertySetterInfo?[]? CacheMap;
-
///
/// 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.
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
index 44c71f2..e47db4e 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
@@ -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);
- }
-
- ///
- /// 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.
- ///
- 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);
- }
+ PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
}
///
- /// 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.
+ /// 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).
///
- private static void PopulateWithCacheMap(
+ private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context,
object target,
- BinaryPropertySetterInfo?[] cacheMap,
- BinaryDeserializeTypeMetadata metadata,
+ TypeMetadataWrapper 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);
- }
- }
- }
-
- ///
- /// Index-based property populate (original logic, used when UseMetadata is off).
- /// Properties are read in order matching the target type's property array.
- ///
- 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;
}
///
- /// 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.
///
[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 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
///
/// Optimized list populate that reuses existing items when possible.
///
- 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.
///
- 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.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SetPropertyToDefault(object target, BinaryPropertySetterInfo propInfo)
+ private static void SetPropertyToDefault(object target, BinaryPropertySetterBase propInfo)
=> propInfo.SetToDefault(target);
///
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
index 433c53c..1a2a9fa 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
@@ -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.
///
[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
}
///
- /// 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.
///
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;
}
+ ///
+ /// 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.
+ ///
+ 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
//}
///
- /// 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.
///
- 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.");
}
+ ///
+ /// 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.
+ ///
+ 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();
diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
index c62657a..b8b75b6 100644
--- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs
+++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
@@ -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 where TMetadata : TypeMetadat
///
internal int MetadataFooterIndex = -1;
+ ///
+ /// 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.
+ ///
+ internal BinaryPropertySetterBase?[]? CacheMap;
+
+ ///
+ /// 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.
+ ///
+ internal int[]? SourceHashes;
+
#region Typed IdentityMaps - No generic type checks in hot path!
///
@@ -118,6 +132,8 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat
public void ResetTracking(bool preRentBuckets = false)
{
MetadataFooterIndex = -1;
+ CacheMap = null;
+ SourceHashes = null;
if (SmallIdBitmap != null)
Array.Clear(SmallIdBitmap);