diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index 1ea9f08..f07a866 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -275,6 +275,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator string? dictValueTypeName = null; bool dictValueHasGenWriter = false; string? dictValueWriterClassName = null; + bool dictValueIsIId = false; + bool dictValueEnableMetadata = true; + bool dictValueNeedsIdScan = true; + bool dictValueNeedsAllRefScan = true; + bool dictValueNeedsInternScan = true; + int dictValueTypeNameHash = 0; + int[]? dictValuePropertyHashes = null; if (kind == PropertyTypeKind.Dictionary) { var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type); @@ -300,6 +307,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator dictValueWriterClassName = string.IsNullOrEmpty(vns) ? $"{vfn}_GeneratedWriter" : $"{vns}.{vfn}_GeneratedWriter"; + + dictValueEnableMetadata = ReadEnableMetadata(resolvedValue); + var dvScanFlags = ComputeNeedsScan(resolvedValue); + dictValueNeedsIdScan = dvScanFlags.needsIdScan; + dictValueNeedsAllRefScan = dvScanFlags.needsAllRefScan; + dictValueNeedsInternScan = dvScanFlags.needsInternScan; + var dvIidIface = resolvedValue.AllInterfaces.FirstOrDefault(ifc => + ifc.IsGenericType && + ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId"); + dictValueIsIId = dvIidIface != null; + dictValueTypeNameHash = ComputeFnvHash(resolvedValue.Name); + dictValuePropertyHashes = ComputeChildPropertyHashes(resolvedValue); } } } @@ -316,6 +335,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName, collAddMethod, collHasCapacityCtor, dictKeyKind, dictValueKind, dictKeyTypeName, dictValueTypeName, dictValueHasGenWriter, dictValueWriterClassName, + dictValueIsIId, dictValueEnableMetadata, dictValueTypeNameHash, dictValuePropertyHashes, + dictValueNeedsIdScan, dictValueNeedsAllRefScan, dictValueNeedsInternScan, childTypeNameHash, childPropertyHashes, elementTypeNameHash, elementPropertyHashes, propEnableMetadata, elemEnableMetadata, @@ -610,7 +631,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Hoist UseStringInterning + IsValidForInterningString checks if any string scanning needed var hasStringScan = scanProps.Any(p => (p.TypeKind == PropertyTypeKind.String && p.InterningFlags != 0) || - (p.TypeKind == PropertyTypeKind.Collection && p.ElementKind == PropertyTypeKind.String && p.InterningFlags != 0)); + (p.TypeKind == PropertyTypeKind.Collection && p.ElementKind == PropertyTypeKind.String && p.InterningFlags != 0) || + (p.TypeKind == PropertyTypeKind.Dictionary && (p.DictKeyKind == PropertyTypeKind.String || p.DictValueKind == PropertyTypeKind.String) && p.InterningFlags != 0)); if (hasStringScan) { @@ -745,14 +767,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); break; case PropertyTypeKind.Dictionary: - // Dictionary: always runtime fallback via WriteValueGenerated (which calls WriteDictionary) - if (p.IsNullable) - { - sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); - } - else - sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + EmitDirectDictionaryWrite(sb, p, a, i); break; default: EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i); @@ -849,14 +864,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator break; case PropertyTypeKind.Dictionary: - // Dictionary scan: runtime fallback via ScanValueGenerated - if (p.IsNullable) - { - sb.AppendLine($"{i}if ({a} != null)"); - sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); - } - else - sb.AppendLine($"{i}AcBinarySerializer.ScanValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + EmitScanDictionary(sb, p, a, i); break; } @@ -1073,6 +1081,126 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Primitive element collection — no scanning needed } + /// + /// Emits inline dictionary scan. Iterates entries and: + /// - String keys: ScanInternString if interning flags match + /// - String values: ScanInternString if interning flags match + /// - Complex+SGen values: ScanObject on each value (handles ref tracking internally) + /// Eliminates GetWrapper dictionary lookup for all inlineable dictionary types. + /// + private static void EmitScanDictionary(StringBuilder sb, PropInfo p, string a, string i) + { + var s = p.Name; + var hasStringKeys = p.DictKeyKind == PropertyTypeKind.String && p.InterningFlags != 0; + var hasStringValues = p.DictValueKind == PropertyTypeKind.String && p.InterningFlags != 0; + var hasComplexValues = p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter; + + // No scanning needed for primitive-only dictionaries without internable strings or complex values + if (!hasStringKeys && !hasStringValues && !hasComplexValues) return; + + // Complex+SGen values: compile-time proven scan is no-op → skip entirely + if (hasComplexValues && !p.DictValueNeedsScan && !hasStringKeys && !hasStringValues) return; + + // Build guard expression for Complex+SGen values (3-axis: IId/AllRef/Intern) + string? complexGuard = null; + if (hasComplexValues && p.DictValueNeedsScan && !p.DictValueNeedsIdScan) + { + if (p.DictValueNeedsAllRefScan && p.DictValueNeedsInternScan) + complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning"; + else if (p.DictValueNeedsAllRefScan) + complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All"; + else if (p.DictValueNeedsInternScan) + complexGuard = "context.UseStringInterning"; + } + + // For string-only scan (no complex values), use simple interning loop + if (!hasComplexValues) + { + sb.AppendLine($"{i}var sd_{s} = {a};"); + sb.AppendLine($"{i}if (sd_{s} != null && ({p.InterningFlags} & internBit) != 0)"); + sb.AppendLine($"{i}{{"); + sb.AppendLine($"{i} foreach (var sde_{s} in sd_{s})"); + sb.AppendLine($"{i} {{"); + if (hasStringKeys) + { + sb.AppendLine($"{i} if (sde_{s}.Key != null)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} var sklen_{s} = sde_{s}.Key.Length;"); + sb.AppendLine($"{i} if (sklen_{s} >= minIntern && (maxIntern == 0 || sklen_{s} <= maxIntern))"); + sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Key);"); + sb.AppendLine($"{i} }}"); + } + if (hasStringValues) + { + sb.AppendLine($"{i} if (sde_{s}.Value != null)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} var svlen_{s} = sde_{s}.Value.Length;"); + sb.AppendLine($"{i} if (svlen_{s} >= minIntern && (maxIntern == 0 || svlen_{s} <= maxIntern))"); + sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Value);"); + sb.AppendLine($"{i} }}"); + } + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i}}}"); + return; + } + + // Complex+SGen values (with optional string key/value interning) + var writer = p.DictValueWriterClassName!; + + // Guard entire scan block when no IId in value subtree + if (complexGuard != null && !hasStringKeys && !hasStringValues) + sb.AppendLine($"{i}if ({complexGuard})"); + + sb.AppendLine($"{i}{{"); + sb.AppendLine($"{i} var sd_{s} = {a};"); + sb.AppendLine($"{i} if (sd_{s} != null)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} var snd_{s} = depth + 2;"); + sb.AppendLine($"{i} foreach (var sde_{s} in sd_{s})"); + sb.AppendLine($"{i} {{"); + + // String key interning + if (hasStringKeys) + { + sb.AppendLine($"{i} if (({p.InterningFlags} & internBit) != 0 && sde_{s}.Key != null)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} var sklen_{s} = sde_{s}.Key.Length;"); + sb.AppendLine($"{i} if (sklen_{s} >= minIntern && (maxIntern == 0 || sklen_{s} <= maxIntern))"); + sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Key);"); + sb.AppendLine($"{i} }}"); + } + + // String value interning + if (hasStringValues) + { + sb.AppendLine($"{i} if (({p.InterningFlags} & internBit) != 0 && sde_{s}.Value != null)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} var svlen_{s} = sde_{s}.Value.Length;"); + sb.AppendLine($"{i} if (svlen_{s} >= minIntern && (maxIntern == 0 || svlen_{s} <= maxIntern))"); + sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Value);"); + sb.AppendLine($"{i} }}"); + } + + // Complex value ScanObject + if (hasComplexValues) + { + sb.AppendLine($"{i} if (sde_{s}.Value != null)"); + if (complexGuard != null) + { + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} if ({complexGuard})"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});"); + sb.AppendLine($"{i} }}"); + } + else + sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});"); + } + + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i}}}"); + } + #endregion /// @@ -1378,6 +1506,269 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}}}"); } + /// + /// Emits inline write of a primitive/string/enum value in non-property context (no PropertySkip). + /// Matches runtime TryWritePrimitive wire format: TinyInt for small int, type code + value otherwise. + /// Used for dictionary key/value writes. + /// + private static void EmitWritePrimitiveValue(StringBuilder sb, PropertyTypeKind kind, string a, string suffix, string i) + { + switch (kind) + { + case PropertyTypeKind.Int32: + sb.AppendLine($"{i}if (BinaryTypeCode.TryEncodeTinyInt({a}, out var tk_{suffix})) context.WriteByte(tk_{suffix});"); + sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}"); + break; + case PropertyTypeKind.Int64: + sb.AppendLine($"{i}if ({a} >= int.MinValue && {a} <= int.MaxValue)"); + sb.AppendLine($"{i}{{"); + sb.AppendLine($"{i} var iv_{suffix} = (int){a};"); + sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(iv_{suffix}, out var tk_{suffix})) context.WriteByte(tk_{suffix});"); + sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(iv_{suffix}); }}"); + sb.AppendLine($"{i}}}"); + sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}"); + break; + case PropertyTypeKind.Boolean: + sb.AppendLine($"{i}context.WriteByte({a} ? BinaryTypeCode.True : BinaryTypeCode.False);"); + break; + case PropertyTypeKind.Double: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a});"); + break; + case PropertyTypeKind.Single: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a});"); + break; + case PropertyTypeKind.Decimal: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a});"); + break; + case PropertyTypeKind.DateTime: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});"); + break; + case PropertyTypeKind.Guid: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a});"); + break; + case PropertyTypeKind.TimeSpan: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.TimeSpan); context.WriteRaw({a}.Ticks);"); + break; + case PropertyTypeKind.DateTimeOffset: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});"); + break; + case PropertyTypeKind.Byte: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt8); context.WriteByte({a});"); + break; + case PropertyTypeKind.Int16: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int16); context.WriteRaw({a});"); + break; + case PropertyTypeKind.UInt16: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt16); context.WriteRaw({a});"); + break; + case PropertyTypeKind.UInt32: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt32); context.WriteVarUInt({a});"); + break; + case PropertyTypeKind.UInt64: + sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt64); context.WriteVarULong({a});"); + break; + case PropertyTypeKind.Enum: + sb.AppendLine($"{i}{{ var ev_{suffix} = (int){a}; context.WriteByte(BinaryTypeCode.Enum);"); + sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(ev_{suffix}, out var te_{suffix})) context.WriteByte(te_{suffix});"); + sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(ev_{suffix}); }} }}"); + break; + } + } + + /// + /// Emits inline dictionary write. Wire format: [Dictionary][count][key₁ value₁ key₂ value₂ ...]. + /// Keys/values are written with type codes matching runtime TryWritePrimitive/WriteValue. + /// Eliminates GetWrapper dictionary lookup for all inlineable key/value types. + /// + private static void EmitDirectDictionaryWrite(StringBuilder sb, PropInfo p, string a, string i) + { + var s = p.Name; + var keyType = p.DictKeyTypeName ?? "object"; + var valType = p.DictValueTypeName ?? "object"; + + if (p.IsNullable) + { + sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); + sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); + sb.AppendLine($"{i}else"); + sb.AppendLine($"{i}{{"); + } + else + { + sb.AppendLine($"{i}if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); + sb.AppendLine($"{i}else"); + sb.AppendLine($"{i}{{"); + } + + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Dictionary);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint){a}.Count);"); + sb.AppendLine($"{i} var nd_{s} = depth + 2;"); + sb.AppendLine($"{i} foreach (var kvp_{s} in {a})"); + sb.AppendLine($"{i} {{"); + + var k = $"kvp_{s}.Key"; + var v = $"kvp_{s}.Value"; + var ii = i + " "; + + // Write key + if (p.DictKeyKind == PropertyTypeKind.String) + { + if (p.InterningFlags == 0) + sb.AppendLine($"{ii}context.StringInternEligible = false;"); + else + sb.AppendLine($"{ii}context.StringInternEligible = ({p.InterningFlags} & (1 << (int)context.Options.UseStringInterning)) != 0;"); + sb.AppendLine($"{ii}AcBinarySerializer.WriteStringGenerated({k}, context);"); + } + else if (IsMarkerless(p.DictKeyKind) || p.DictKeyKind == PropertyTypeKind.Enum) + { + EmitWritePrimitiveValue(sb, p.DictKeyKind, k, $"dk_{s}", ii); + } + else + { + sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context, nd_{s});"); + } + + // Write value + if (p.DictValueKind == PropertyTypeKind.String) + { + // String value: null → Null, non-null → WriteStringGenerated + sb.AppendLine($"{ii}if ({v} == null) context.WriteByte(BinaryTypeCode.Null);"); + sb.AppendLine($"{ii}else"); + sb.AppendLine($"{ii}{{"); + if (p.InterningFlags == 0) + sb.AppendLine($"{ii} context.StringInternEligible = false;"); + else + sb.AppendLine($"{ii} context.StringInternEligible = ({p.InterningFlags} & (1 << (int)context.Options.UseStringInterning)) != 0;"); + sb.AppendLine($"{ii} AcBinarySerializer.WriteStringGenerated({v}, context);"); + sb.AppendLine($"{ii}}}"); + } + else if (IsMarkerless(p.DictValueKind) || p.DictValueKind == PropertyTypeKind.Enum) + { + EmitWritePrimitiveValue(sb, p.DictValueKind, v, $"dv_{s}", ii); + } + else if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter) + { + EmitDictValueComplexWrite(sb, p, v, s, ii); + } + else + { + // Fallback for non-inlineable value types + sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context, nd_{s});"); + } + + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i}}}"); + } + + /// + /// Emits inline write for a Complex+SGen dictionary value with ref tracking and metadata support. + /// Mirrors EmitDirectCollectionWrite per-element write pattern. + /// + private static void EmitDictValueComplexWrite(StringBuilder sb, PropInfo p, string v, string s, string i) + { + var writer = p.DictValueWriterClassName!; + var valType = p.DictValueTypeName!; + + sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}"); + sb.AppendLine($"{i}else if (nd_{s} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); }}"); + sb.AppendLine($"{i}else"); + sb.AppendLine($"{i}{{"); + + if (!p.DictValueNeedsRefScan) + { + if (!p.DictValueEnableMetadata) + { + // No ref, no metadata → always Object + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + } + else + { + // No ref, metadata possible + sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({valType})));"); + sb.AppendLine($"{i} if (context.UseMetadata)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);"); + EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " "); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + } + } + else + { + var dvRefGuard = p.DictValueIsIId + ? "context.ReferenceHandling != ReferenceHandlingMode.None" + : "context.ReferenceHandling == ReferenceHandlingMode.All"; + + if (!p.DictValueEnableMetadata) + { + // Ref tracking, no metadata + sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i} }}"); + } + else + { + // Full path: ref tracking + metadata + sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({valType})));"); + sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} if (context.UseMetadata)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);"); + EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " "); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);"); + sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} if (context.UseMetadata)"); + sb.AppendLine($"{i} {{"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);"); + EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " "); + sb.AppendLine($"{i} }}"); + sb.AppendLine($"{i} else"); + sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i} }}"); + } + } + + sb.AppendLine($"{i}}}"); + } + private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i) { switch (k) @@ -2623,6 +3014,24 @@ internal sealed class PropInfo public bool DictValueHasGeneratedWriter { get; } /// Generated writer class name for dictionary value type. public string? DictValueWriterClassName { get; } + /// True if dictionary value type implements IId<T>. + public bool DictValueIsIId { get; } + /// When false, dict value type skips inline metadata. + public bool DictValueEnableMetadata { get; } + /// FNV-1a hash of dict value type name. + public int DictValueTypeNameHash { get; } + /// FNV-1a hashes of dict value type's properties. + public int[]? DictValuePropertyHashes { get; } + /// When true, dict value subtree has IId types needing scan. + public bool DictValueNeedsIdScan { get; } + /// When true, dict value subtree has non-IId ref tracking. + public bool DictValueNeedsAllRefScan { get; } + /// When true, dict value subtree needs string interning scan. + public bool DictValueNeedsInternScan { get; } + /// Derived: DictValueNeedsIdScan || DictValueNeedsAllRefScan. + public bool DictValueNeedsRefScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan; + /// Derived: any dict value scan axis active. + public bool DictValueNeedsScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan || DictValueNeedsInternScan; // UseMetadata inline hash-ek (Complex/Collection child típushoz) /// FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter. @@ -2667,6 +3076,9 @@ internal sealed class PropInfo PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown, PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown, string? dictKeyTypeName = null, string? dictValueTypeName = null, bool dictValueHasGeneratedWriter = false, string? dictValueWriterClassName = null, + bool dictValueIsIId = false, bool dictValueEnableMetadata = true, + int dictValueTypeNameHash = 0, int[]? dictValuePropertyHashes = null, + bool dictValueNeedsIdScan = true, bool dictValueNeedsAllRefScan = true, bool dictValueNeedsInternScan = true, int childTypeNameHash = 0, int[]? childPropertyHashes = null, int elementTypeNameHash = 0, int[]? elementPropertyHashes = null, bool childEnableMetadata = true, bool elementEnableMetadata = true, @@ -2698,6 +3110,13 @@ internal sealed class PropInfo DictValueTypeName = dictValueTypeName; DictValueHasGeneratedWriter = dictValueHasGeneratedWriter; DictValueWriterClassName = dictValueWriterClassName; + DictValueIsIId = dictValueIsIId; + DictValueEnableMetadata = dictValueEnableMetadata; + DictValueTypeNameHash = dictValueTypeNameHash; + DictValuePropertyHashes = dictValuePropertyHashes; + DictValueNeedsIdScan = dictValueNeedsIdScan; + DictValueNeedsAllRefScan = dictValueNeedsAllRefScan; + DictValueNeedsInternScan = dictValueNeedsInternScan; ChildTypeNameHash = childTypeNameHash; ChildPropertyHashes = childPropertyHashes; ElementTypeNameHash = elementTypeNameHash;