From cd3d65b5f489493bcd7b8f6ad189075bbc25a8d9 Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 4 Feb 2026 20:29:08 +0100 Subject: [PATCH] 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. --- ...lizer.BinaryDeserializationContextClass.cs | 81 +++---- ...erializer.BinaryDeserializeTypeMetadata.cs | 8 - .../Binaries/AcBinaryDeserializer.Populate.cs | 210 ++++++------------ .../Binaries/AcBinaryDeserializer.cs | 156 +++++++++---- .../Serializers/TypeMetadataWrapper.cs | 16 ++ 5 files changed, 223 insertions(+), 248 deletions(-) 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);