Add inline dictionary read support to AcBinary generator
Enable optimized codegen for Dictionary<TKey, TValue> and IDictionary<TKey, TValue> properties. The generator now analyzes key/value types and emits direct binary read loops for primitive, string, enum, and complex types with generated readers. PropInfo is extended with dictionary metadata. Improves performance and type safety by eliminating reflection and runtime type dispatch for supported dictionary types. Also refactors collection reading logic and updates dependency graph and scanning for dictionary value types.
This commit is contained in:
parent
8f665c5c4d
commit
2aa2eecccd
|
|
@ -181,6 +181,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
string? elemWriterClassName = null;
|
||||
string? elemIdTypeName = null;
|
||||
string? collKind = null;
|
||||
string? collAddMethod = null;
|
||||
bool collHasCapacityCtor = false;
|
||||
string? elemFullTypeName = null;
|
||||
int elementTypeNameHash = 0;
|
||||
int[]? elementPropertyHashes = null;
|
||||
|
|
@ -211,6 +213,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
"System.Collections.Generic.LinkedList<T>" => "Counted",
|
||||
_ => null
|
||||
};
|
||||
|
||||
// Determine add method + capacity ctor for Counted concrete types
|
||||
if (collKind == "Counted")
|
||||
{
|
||||
collAddMethod = origDef switch
|
||||
{
|
||||
"System.Collections.Generic.HashSet<T>" => "Add",
|
||||
"System.Collections.Generic.SortedSet<T>" => "Add",
|
||||
"System.Collections.Generic.Queue<T>" => "Enqueue",
|
||||
"System.Collections.Generic.LinkedList<T>" => "AddLast",
|
||||
_ => null // ICollection<T>, IReadOnlyCollection<T> → backed by List<T>
|
||||
};
|
||||
collHasCapacityCtor = origDef is
|
||||
"System.Collections.Generic.HashSet<T>" or
|
||||
"System.Collections.Generic.Queue<T>";
|
||||
}
|
||||
}
|
||||
|
||||
// For Complex element types, check for generated writer
|
||||
|
|
@ -250,6 +268,43 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
|
||||
// Dictionary key/value type analysis for inline dictionary read
|
||||
PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown;
|
||||
PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown;
|
||||
string? dictKeyTypeName = null;
|
||||
string? dictValueTypeName = null;
|
||||
bool dictValueHasGenWriter = false;
|
||||
string? dictValueWriterClassName = null;
|
||||
if (kind == PropertyTypeKind.Dictionary)
|
||||
{
|
||||
var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type);
|
||||
if (keyType != null)
|
||||
{
|
||||
dictKeyKind = GetKind(keyType);
|
||||
dictKeyTypeName = keyType.ToDisplayString();
|
||||
}
|
||||
if (valueType != null)
|
||||
{
|
||||
dictValueKind = GetKind(valueType);
|
||||
dictValueTypeName = valueType.ToDisplayString();
|
||||
if (dictValueKind == PropertyTypeKind.Complex)
|
||||
{
|
||||
var resolvedValue = valueType is INamedTypeSymbol nvt ? nvt.OriginalDefinition : valueType;
|
||||
dictValueHasGenWriter = resolvedValue.GetAttributes().Any(a =>
|
||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||
if (dictValueHasGenWriter)
|
||||
{
|
||||
var vfn = BuildFlatName((INamedTypeSymbol)resolvedValue);
|
||||
var vns = resolvedValue.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty : resolvedValue.ContainingNamespace.ToDisplayString();
|
||||
dictValueWriterClassName = string.IsNullOrEmpty(vns)
|
||||
? $"{vfn}_GeneratedWriter"
|
||||
: $"{vns}.{vfn}_GeneratedWriter";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
properties.Add(new PropInfo(
|
||||
p.Name,
|
||||
typeDisplayName,
|
||||
|
|
@ -259,6 +314,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
p.Type.SpecialType == SpecialType.System_Object,
|
||||
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
|
||||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
||||
collAddMethod, collHasCapacityCtor,
|
||||
dictKeyKind, dictValueKind, dictKeyTypeName, dictValueTypeName, dictValueHasGenWriter, dictValueWriterClassName,
|
||||
childTypeNameHash, childPropertyHashes,
|
||||
elementTypeNameHash, elementPropertyHashes,
|
||||
propEnableMetadata, elemEnableMetadata,
|
||||
|
|
@ -342,6 +399,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
if (writerToFull.TryGetValue(p.ElementWriterClassName, out var target))
|
||||
edges.Add(target);
|
||||
}
|
||||
if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter && p.DictValueWriterClassName != null)
|
||||
{
|
||||
if (writerToFull.TryGetValue(p.DictValueWriterClassName, out var target))
|
||||
edges.Add(target);
|
||||
}
|
||||
}
|
||||
adjacency[ci.FullTypeName] = edges;
|
||||
}
|
||||
|
|
@ -542,7 +604,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
var scanProps = ci.Properties.Where(p =>
|
||||
p.TypeKind == PropertyTypeKind.String ||
|
||||
p.TypeKind == PropertyTypeKind.Complex ||
|
||||
p.TypeKind == PropertyTypeKind.Collection).ToList();
|
||||
p.TypeKind == PropertyTypeKind.Collection ||
|
||||
p.TypeKind == PropertyTypeKind.Dictionary).ToList();
|
||||
|
||||
// Hoist UseStringInterning + IsValidForInterningString checks if any string scanning needed
|
||||
var hasStringScan = scanProps.Any(p =>
|
||||
|
|
@ -681,6 +744,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
else
|
||||
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);");
|
||||
break;
|
||||
default:
|
||||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
||||
break;
|
||||
|
|
@ -774,6 +847,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
case PropertyTypeKind.Collection:
|
||||
EmitScanCollection(sb, p, a, i);
|
||||
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);");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IsMarkerless(p.TypeKind))
|
||||
|
|
@ -1500,6 +1584,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
EmitReadCollection(sb, p, a, tc, i + " ");
|
||||
break;
|
||||
|
||||
case PropertyTypeKind.Dictionary:
|
||||
EmitReadDictionary(sb, p, a, tc, i + " ");
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
|
||||
sb.AppendLine($"{i} context._position--;");
|
||||
|
|
@ -1631,15 +1719,27 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when collection element reading can be inlined (no runtime ReadValue dispatch needed).
|
||||
/// </summary>
|
||||
private static bool CanInlineCollectionRead(PropInfo p)
|
||||
{
|
||||
if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter) return true;
|
||||
if (p.ElementKind == PropertyTypeKind.String) return true;
|
||||
if (p.ElementKind == PropertyTypeKind.Enum) return true;
|
||||
if (IsMarkerless(p.ElementKind)) return true; // all primitives
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline read for a Collection property.
|
||||
/// Complex element with SGen + known collection kind → inline Array loop with direct element reader calls.
|
||||
/// Known collection kind + inlineable element → inline Array loop with direct element reads.
|
||||
/// Else → runtime fallback via ReadValueGenerated.
|
||||
/// </summary>
|
||||
private static void EmitReadCollection(StringBuilder sb, PropInfo p, string a, string tc, string i)
|
||||
{
|
||||
// Check if we can inline: need SGen element reader + known collection shape + Array marker
|
||||
if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter && p.CollectionKind != null)
|
||||
// Check if we can inline: known collection shape + inlineable element type
|
||||
if (p.CollectionKind != null && CanInlineCollectionRead(p))
|
||||
{
|
||||
EmitReadCollectionInline(sb, p, a, tc, i);
|
||||
return;
|
||||
|
|
@ -1662,16 +1762,186 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline read for a Dictionary property.
|
||||
/// Wire format: [Dictionary][VarUInt count][key₁ value₁ key₂ value₂ ...].
|
||||
/// Keys and values are read inline when their types are known (primitive/string/Complex+SGen).
|
||||
/// </summary>
|
||||
private static void EmitReadDictionary(StringBuilder sb, PropInfo p, string a, string tc, string i)
|
||||
{
|
||||
var s = p.Name;
|
||||
var keyType = p.DictKeyTypeName ?? "object";
|
||||
var valType = p.DictValueTypeName ?? "object";
|
||||
|
||||
// Can we inline key/value reads?
|
||||
var canInlineKey = p.DictKeyKind == PropertyTypeKind.String || IsMarkerless(p.DictKeyKind) || p.DictKeyKind == PropertyTypeKind.Enum;
|
||||
var canInlineValue = p.DictValueKind == PropertyTypeKind.String || IsMarkerless(p.DictValueKind) || p.DictValueKind == PropertyTypeKind.Enum
|
||||
|| (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter);
|
||||
var canInline = canInlineKey || canInlineValue; // partial inline is still beneficial
|
||||
|
||||
if (p.IsNullable)
|
||||
{
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Dictionary)");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Dictionary)");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
|
||||
sb.AppendLine($"{i} var dict_{s} = new System.Collections.Generic.Dictionary<{keyType}, {valType}>(cnt_{s});");
|
||||
sb.AppendLine($"{i} var nd_{s} = depth + 1;");
|
||||
sb.AppendLine($"{i} for (var di_{s} = 0; di_{s} < cnt_{s}; di_{s}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
||||
// Read key
|
||||
if (canInlineKey)
|
||||
EmitReadDictElement(sb, p.DictKeyKind, keyType, $"dk_{s}", s, i + " ", null, false);
|
||||
else
|
||||
sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}), nd_{s})!;");
|
||||
|
||||
// Read value
|
||||
if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter)
|
||||
{
|
||||
var valReader = p.DictValueWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
|
||||
var vtc = $"vtc_{s}";
|
||||
sb.AppendLine($"{i} var {vtc} = context.ReadByte();");
|
||||
sb.AppendLine($"{i} {valType}? dv_{s} = null;");
|
||||
sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)");
|
||||
sb.AppendLine($"{i} dv_{s} = ({valType}){valReader}.Instance.ReadObject(context, nd_{s}, -1)!;");
|
||||
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRefFirst)");
|
||||
sb.AppendLine($"{i} dv_{s} = ({valType}){valReader}.Instance.ReadObject(context, nd_{s}, (int)context.ReadVarUInt())!;");
|
||||
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)");
|
||||
sb.AppendLine($"{i} dv_{s} = ({valType})context.GetInternedObject((int)context.ReadVarUInt())!;");
|
||||
sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context._position--;");
|
||||
sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else if (canInlineValue)
|
||||
EmitReadDictElement(sb, p.DictValueKind, valType, $"dv_{s}", s, i + " ", null, true);
|
||||
else
|
||||
sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});");
|
||||
|
||||
// Add to dictionary
|
||||
sb.AppendLine($"{i} if (dk_{s} != null) dict_{s}[dk_{s}] = dv_{s}!;");
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {a} = dict_{s};");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline read for a single dictionary key or value element.
|
||||
/// Reads type code byte, then dispatches based on element kind.
|
||||
/// </summary>
|
||||
private static void EmitReadDictElement(StringBuilder sb, PropertyTypeKind kind, string typeName, string varName, string propSuffix, string i, PropInfo? p, bool isRefType)
|
||||
{
|
||||
var etc = $"{varName}_tc";
|
||||
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
|
||||
|
||||
if (kind == PropertyTypeKind.String)
|
||||
{
|
||||
sb.AppendLine($"{i}{typeName}? {varName} = null;");
|
||||
EmitReadString(sb, varName, etc, i);
|
||||
}
|
||||
else if (kind == PropertyTypeKind.Enum)
|
||||
{
|
||||
sb.AppendLine($"{i}{typeName} {varName} = default;");
|
||||
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var eb = context.ReadByte();");
|
||||
sb.AppendLine($"{i} int eiv;");
|
||||
sb.AppendLine($"{i} if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
|
||||
sb.AppendLine($"{i} else eiv = context.ReadVarInt();");
|
||||
sb.AppendLine($"{i} {varName} = ({typeName})(object)eiv;");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {varName} = ({typeName})(object)BinaryTypeCode.DecodeTinyInt({etc});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primitive value type — never nullable
|
||||
sb.AppendLine($"{i}{typeName} {varName} = default;");
|
||||
EmitReadMarkeredValueForKind(sb, kind, varName, etc, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits markered value read by kind only (no PropInfo needed). For dict key/value inline reads.
|
||||
/// </summary>
|
||||
private static void EmitReadMarkeredValueForKind(StringBuilder sb, PropertyTypeKind k, string a, string tc, string i)
|
||||
{
|
||||
switch (k)
|
||||
{
|
||||
case PropertyTypeKind.Int32:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
|
||||
break;
|
||||
case PropertyTypeKind.Int64:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int64) {a} = context.ReadVarLong();");
|
||||
break;
|
||||
case PropertyTypeKind.Boolean:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.True) {a} = true;");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.False) {a} = false;");
|
||||
break;
|
||||
case PropertyTypeKind.Double:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float64) {a} = context.ReadDoubleUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.Single:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float32) {a} = context.ReadSingleUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.Decimal:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Decimal) {a} = context.ReadDecimalUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.DateTime:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTime) {a} = context.ReadDateTimeUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.Guid:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Guid) {a} = context.ReadGuidUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.Byte:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (byte)BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt8) {a} = context.ReadByte();");
|
||||
break;
|
||||
case PropertyTypeKind.Int16:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (short)BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int16) {a} = context.ReadInt16Unsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.UInt16:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ushort)BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt16) {a} = context.ReadUInt16Unsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.UInt32:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (uint)BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt32) {a} = context.ReadVarUInt();");
|
||||
break;
|
||||
case PropertyTypeKind.UInt64:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ulong)BinaryTypeCode.DecodeTinyInt({tc});");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt64) {a} = context.ReadVarULong();");
|
||||
break;
|
||||
case PropertyTypeKind.TimeSpan:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.TimeSpan) {a} = context.ReadTimeSpanUnsafe();");
|
||||
break;
|
||||
case PropertyTypeKind.DateTimeOffset:
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTimeOffset) {a} = context.ReadDateTimeOffsetUnsafe();");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline collection read: Array marker already consumed as tc.
|
||||
/// Reads count + loops with direct element reader calls.
|
||||
/// Reads count + loops with direct element reads (Complex with SGen, or primitive/string/enum).
|
||||
/// Eliminates per-element: ReadValue dispatch, ReadObjectCore dict lookup, Activator.CreateInstance.
|
||||
/// </summary>
|
||||
private static void EmitReadCollectionInline(StringBuilder sb, PropInfo p, string a, string tc, string i)
|
||||
{
|
||||
var reader = p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
|
||||
var isComplexElement = p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter;
|
||||
var elemType = p.ElementFullTypeName!;
|
||||
var elemCast = $"({elemType})";
|
||||
var s = p.Name;
|
||||
|
||||
// Null check
|
||||
|
|
@ -1687,23 +1957,45 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
|
||||
if (isComplexElement)
|
||||
sb.AppendLine($"{i} var nd_{s} = depth + 1;");
|
||||
|
||||
// Create collection based on kind
|
||||
// Create collection + loop based on kind
|
||||
if (p.CollectionKind == "Array")
|
||||
{
|
||||
sb.AppendLine($"{i} var col_{s} = new {elemType}[cnt_{s}];");
|
||||
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
EmitReadCollectionElement(sb, reader, elemCast, $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan);
|
||||
if (isComplexElement)
|
||||
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan);
|
||||
else
|
||||
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: true, null);
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else // List, IndexedCollection, Counted — all use List<T> with Add
|
||||
else if (p.CollectionKind == "Counted" && p.CollectionAddMethod != null)
|
||||
{
|
||||
// Concrete custom collection — use actual type + correct add method
|
||||
if (p.CollectionHasCapacityCtor)
|
||||
sb.AppendLine($"{i} var col_{s} = new {p.TypeNameForTypeof}(cnt_{s});");
|
||||
else
|
||||
sb.AppendLine($"{i} var col_{s} = new {p.TypeNameForTypeof}();");
|
||||
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
if (isComplexElement)
|
||||
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan, p.CollectionAddMethod);
|
||||
else
|
||||
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, p.CollectionAddMethod);
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else // List, IndexedCollection, Counted-interface → List<T> with Add
|
||||
{
|
||||
sb.AppendLine($"{i} var col_{s} = new System.Collections.Generic.List<{elemType}>(cnt_{s});");
|
||||
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
EmitReadCollectionElement(sb, reader, elemCast, $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan);
|
||||
if (isComplexElement)
|
||||
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan);
|
||||
else
|
||||
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, null);
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
|
||||
|
|
@ -1716,13 +2008,14 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
/// SGen reader = non-metadata mode → no ObjectWithMetadata fallback.
|
||||
/// !needsRefScan → only Object/Null possible → 1 branch per element.
|
||||
/// </summary>
|
||||
private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan)
|
||||
private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan, string? addMethod = null)
|
||||
{
|
||||
var etc = $"etc_{propSuffix}";
|
||||
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
|
||||
|
||||
var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.Add(null!);";
|
||||
var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = " : $"col_{propSuffix}.Add(";
|
||||
var addCall = addMethod ?? "Add";
|
||||
var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.{addCall}(null!);";
|
||||
var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = " : $"col_{propSuffix}.{addCall}(";
|
||||
var assignEnd = isArray ? ";" : ");";
|
||||
|
||||
if (!needsRefScan)
|
||||
|
|
@ -1744,6 +2037,63 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits per-element read for non-Complex collection elements (String, primitive, Enum).
|
||||
/// Reads type code byte, then dispatches based on ElementKind.
|
||||
/// </summary>
|
||||
private static void EmitReadNonComplexCollectionElement(StringBuilder sb, PropInfo p, string indexVar, string propSuffix, string i, bool isArray, string? addMethod)
|
||||
{
|
||||
var etc = $"etc_{propSuffix}";
|
||||
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
|
||||
|
||||
var addCall = addMethod ?? "Add";
|
||||
var elemType = p.ElementFullTypeName!;
|
||||
var colRef = $"col_{propSuffix}";
|
||||
|
||||
if (p.ElementKind == PropertyTypeKind.String)
|
||||
{
|
||||
// String element: FixStr / String / StringInternFirst / StringInterned / Null / StringEmpty
|
||||
var tempVar = $"sv_{propSuffix}";
|
||||
sb.AppendLine($"{i}string? {tempVar} = null;");
|
||||
EmitReadString(sb, tempVar, etc, i);
|
||||
if (isArray)
|
||||
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar}!;");
|
||||
else
|
||||
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar}!);");
|
||||
}
|
||||
else if (p.ElementKind == PropertyTypeKind.Enum)
|
||||
{
|
||||
// Enum element: Enum marker or TinyInt
|
||||
var tempVar = $"ev_{propSuffix}";
|
||||
sb.AppendLine($"{i}{elemType} {tempVar} = default;");
|
||||
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var eb = context.ReadByte();");
|
||||
sb.AppendLine($"{i} int eiv;");
|
||||
sb.AppendLine($"{i} if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
|
||||
sb.AppendLine($"{i} else eiv = context.ReadVarInt();");
|
||||
sb.AppendLine($"{i} {tempVar} = ({elemType})(object)eiv;");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {tempVar} = ({elemType})(object)BinaryTypeCode.DecodeTinyInt({etc});");
|
||||
if (isArray)
|
||||
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
|
||||
else
|
||||
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primitive element: read markered value
|
||||
var tempVar = $"pv_{propSuffix}";
|
||||
sb.AppendLine($"{i}{elemType} {tempVar} = default;");
|
||||
// Create a minimal PropInfo-like context for EmitReadMarkeredValue
|
||||
EmitReadMarkeredValue(sb, p.ElementKind, tempVar, etc, i, p, nullable: false);
|
||||
if (isArray)
|
||||
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
|
||||
else
|
||||
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits markered value read for primitive types (with type code already read).
|
||||
/// Handles TinyInt encoding for integer types.
|
||||
|
|
@ -1994,6 +2344,28 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dictionary → check key and value types
|
||||
if (kind == PropertyTypeKind.Dictionary)
|
||||
{
|
||||
var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type);
|
||||
if (keyType != null && enableInternString && GetKind(keyType) == PropertyTypeKind.String)
|
||||
needsInternScan = true;
|
||||
if (valueType != null)
|
||||
{
|
||||
var valKind = GetKind(valueType);
|
||||
if (enableInternString && valKind == PropertyTypeKind.String)
|
||||
needsInternScan = true;
|
||||
if (valKind == PropertyTypeKind.Complex)
|
||||
{
|
||||
var resolvedVal = valueType is INamedTypeSymbol nv ? nv.OriginalDefinition : valueType;
|
||||
var valFlags = ComputeNeedsScanCore(resolvedVal, visiting);
|
||||
needsIdScan |= valFlags.needsIdScan;
|
||||
needsAllRefScan |= valFlags.needsAllRefScan;
|
||||
needsInternScan |= valFlags.needsInternScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (needsIdScan, needsAllRefScan, needsInternScan);
|
||||
|
|
@ -2081,6 +2453,15 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
if (fn == "System.DateTimeOffset") return nullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset;
|
||||
if (type.TypeKind == TypeKind.Enum) return nullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum;
|
||||
if (type is IArrayTypeSymbol) return PropertyTypeKind.Collection;
|
||||
// Dictionary detection: must come before IEnumerable<T> (Dictionary implements both)
|
||||
if (type is INamedTypeSymbol dictNt && dictNt.IsGenericType)
|
||||
{
|
||||
var orig = dictNt.OriginalDefinition.ToDisplayString();
|
||||
if (orig == "System.Collections.Generic.IDictionary<TKey, TValue>" ||
|
||||
orig == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
|
||||
dictNt.AllInterfaces.Any(ifc => ifc.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IDictionary<TKey, TValue>"))
|
||||
return PropertyTypeKind.Dictionary;
|
||||
}
|
||||
if (type is INamedTypeSymbol nt && nt.AllInterfaces.Any(iface => iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T))
|
||||
return PropertyTypeKind.Collection;
|
||||
if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex;
|
||||
|
|
@ -2110,6 +2491,26 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts key and value types from Dictionary<K,V> or IDictionary<K,V>.
|
||||
/// </summary>
|
||||
private static (ITypeSymbol? keyType, ITypeSymbol? valueType) GetDictionaryKeyValueTypes(ITypeSymbol type)
|
||||
{
|
||||
if (type is INamedTypeSymbol nt && nt.IsGenericType)
|
||||
{
|
||||
var orig = nt.OriginalDefinition.ToDisplayString();
|
||||
if (orig == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
|
||||
orig == "System.Collections.Generic.IDictionary<TKey, TValue>")
|
||||
return (nt.TypeArguments[0], nt.TypeArguments[1]);
|
||||
|
||||
var iface = nt.AllInterfaces.FirstOrDefault(i =>
|
||||
i.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IDictionary<TKey, TValue>");
|
||||
if (iface != null)
|
||||
return (iface.TypeArguments[0], iface.TypeArguments[1]);
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private static bool IsNullableVTKind(PropertyTypeKind k) => k >= PropertyTypeKind.NullableInt32;
|
||||
|
||||
private static PropertyTypeKind Underlying(PropertyTypeKind k) => k switch
|
||||
|
|
@ -2204,6 +2605,24 @@ internal sealed class PropInfo
|
|||
public string? CollectionKind { get; }
|
||||
/// <summary>Full element type name for generated code (e.g. "SharedTag").</summary>
|
||||
public string? ElementFullTypeName { get; }
|
||||
/// <summary>Add method for Counted concrete collections. null → List<T>.Add(), "Add" → HashSet/SortedSet, "Enqueue" → Queue, "AddLast" → LinkedList.</summary>
|
||||
public string? CollectionAddMethod { get; }
|
||||
/// <summary>True if the concrete Counted collection has a capacity constructor (HashSet, Queue).</summary>
|
||||
public bool CollectionHasCapacityCtor { get; }
|
||||
|
||||
// Dictionary metadata — set when TypeKind == Dictionary
|
||||
/// <summary>Key type kind for dictionary properties.</summary>
|
||||
public PropertyTypeKind DictKeyKind { get; }
|
||||
/// <summary>Value type kind for dictionary properties.</summary>
|
||||
public PropertyTypeKind DictValueKind { get; }
|
||||
/// <summary>Key type name for generated code.</summary>
|
||||
public string? DictKeyTypeName { get; }
|
||||
/// <summary>Value type name for generated code.</summary>
|
||||
public string? DictValueTypeName { get; }
|
||||
/// <summary>True if dictionary value type has [AcBinarySerializable].</summary>
|
||||
public bool DictValueHasGeneratedWriter { get; }
|
||||
/// <summary>Generated writer class name for dictionary value type.</summary>
|
||||
public string? DictValueWriterClassName { get; }
|
||||
|
||||
// UseMetadata inline hash-ek (Complex/Collection child típushoz)
|
||||
/// <summary>FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter.</summary>
|
||||
|
|
@ -2244,6 +2663,10 @@ internal sealed class PropInfo
|
|||
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
|
||||
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
|
||||
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
||||
string? collectionAddMethod = null, bool collectionHasCapacityCtor = false,
|
||||
PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown, PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown,
|
||||
string? dictKeyTypeName = null, string? dictValueTypeName = null,
|
||||
bool dictValueHasGeneratedWriter = false, string? dictValueWriterClassName = null,
|
||||
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
|
||||
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null,
|
||||
bool childEnableMetadata = true, bool elementEnableMetadata = true,
|
||||
|
|
@ -2267,6 +2690,14 @@ internal sealed class PropInfo
|
|||
ElementIdTypeName = elementIdTypeName;
|
||||
CollectionKind = collectionKind;
|
||||
ElementFullTypeName = elementFullTypeName;
|
||||
CollectionAddMethod = collectionAddMethod;
|
||||
CollectionHasCapacityCtor = collectionHasCapacityCtor;
|
||||
DictKeyKind = dictKeyKind;
|
||||
DictValueKind = dictValueKind;
|
||||
DictKeyTypeName = dictKeyTypeName;
|
||||
DictValueTypeName = dictValueTypeName;
|
||||
DictValueHasGeneratedWriter = dictValueHasGeneratedWriter;
|
||||
DictValueWriterClassName = dictValueWriterClassName;
|
||||
ChildTypeNameHash = childTypeNameHash;
|
||||
ChildPropertyHashes = childPropertyHashes;
|
||||
ElementTypeNameHash = elementTypeNameHash;
|
||||
|
|
@ -2291,7 +2722,7 @@ internal enum PropertyTypeKind
|
|||
{
|
||||
Unknown, String, Int32, Int64, Int16, Byte, UInt16, UInt32, UInt64,
|
||||
Boolean, Single, Double, Decimal, DateTime, DateTimeOffset, TimeSpan, Guid, Enum,
|
||||
Collection, Complex,
|
||||
Collection, Complex, Dictionary,
|
||||
NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableUInt16, NullableUInt32, NullableUInt64,
|
||||
NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime,
|
||||
NullableDateTimeOffset, NullableTimeSpan, NullableGuid, NullableEnum
|
||||
|
|
|
|||
Loading…
Reference in New Issue