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);