Add ReadProperties to binary reader for inline deserialization

Introduce ReadProperties to IGeneratedBinaryReader and generated readers, enabling property population into existing instances. Refactor ReadObject to delegate to ReadProperties. Update codegen for complex objects, collections, and dictionaries to use inline property deserialization. Improves efficiency and flexibility for parent-driven instance creation and cache management. Update documentation and comments to reflect new pattern.
This commit is contained in:
Loretta 2026-03-02 06:43:30 +01:00
parent 7e3fbe7a52
commit a1a2a90ef7
2 changed files with 92 additions and 26 deletions

View File

@ -1899,15 +1899,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedReader Instance = new();"); sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedReader Instance = new();");
sb.AppendLine(); sb.AppendLine();
// ReadObject — IGeneratedBinaryReader implementation // ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
sb.AppendLine(" public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex)"); sb.AppendLine(" public void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase"); sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine($" var obj = new {ci.FullTypeName}();"); sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
sb.AppendLine();
sb.AppendLine(" if (cacheIndex >= 0)");
sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
sb.AppendLine();
// Emit property reads — markerless for primitive types, markered for the rest // Emit property reads — markerless for primitive types, markered for the rest
foreach (var p in ci.Properties) foreach (var p in ci.Properties)
@ -1916,7 +1912,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
EmitReadProp(sb, p, " ", ci.EnableMetadata); EmitReadProp(sb, p, " ", ci.EnableMetadata);
} }
sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
// ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
sb.AppendLine(" public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = new {ci.FullTypeName}();");
sb.AppendLine(" if (cacheIndex >= 0)");
sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
sb.AppendLine(" ReadProperties<TInput>(obj, context, depth);");
sb.AppendLine(" return obj;"); sb.AppendLine(" return obj;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine("}"); sb.AppendLine("}");
@ -2103,24 +2109,45 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
if (!p.ChildNeedsRefScan) if (!p.ChildNeedsRefScan)
{ {
// Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream // Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
// Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
if (p.IsNullable) if (p.IsNullable)
{ {
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}"); sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i}else {a} = {cast}{reader}.Instance.ReadObject(context, {nd}, -1)!;"); sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
} }
else else
{ {
// ZERO branches — tc is always Object, just call ReadObject // ZERO branches — tc is always Object
sb.AppendLine($"{i}{a} = {cast}{reader}.Instance.ReadObject(context, {nd}, -1)!;"); sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
} }
} }
else else
{ {
// Ref tracking possible — Object/ObjectRefFirst/ObjectRef dispatch // Ref tracking possible — Object/ObjectRefFirst/ObjectRef dispatch
// Inline: parent creates instance + handles cache registration
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Object)"); sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Object)");
sb.AppendLine($"{i} {a} = {cast}{reader}.Instance.ReadObject(context, {nd}, -1)!;"); sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRefFirst)"); sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRefFirst)");
sb.AppendLine($"{i} {a} = {cast}{reader}.Instance.ReadObject(context, {nd}, (int)context.ReadVarUInt())!;"); sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var ci_{p.Name} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{p.Name}, rc_{p.Name});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
if (p.IsNullable) if (p.IsNullable)
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}"); sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRef)"); sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRef)");
@ -2219,9 +2246,19 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var {vtc} = context.ReadByte();"); sb.AppendLine($"{i} var {vtc} = context.ReadByte();");
sb.AppendLine($"{i} {valType}? dv_{s} = null;"); sb.AppendLine($"{i} {valType}? dv_{s} = null;");
sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)"); sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)");
sb.AppendLine($"{i} dv_{s} = ({valType}){valReader}.Instance.ReadObject(context, nd_{s}, -1)!;"); sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRefFirst)"); 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} {{");
sb.AppendLine($"{i} var rci_{s} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(rci_{s}, rv_{s});");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)"); sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)");
sb.AppendLine($"{i} dv_{s} = ({valType})context.GetInternedObject((int)context.ReadVarUInt())!;"); sb.AppendLine($"{i} dv_{s} = ({valType})context.GetInternedObject((int)context.ReadVarUInt())!;");
sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)"); sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)");
@ -2376,7 +2413,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)"); sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
if (isComplexElement) if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan); EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan);
else else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: true, null); EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: true, null);
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
@ -2391,7 +2428,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)"); sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
if (isComplexElement) if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan, p.CollectionAddMethod); EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan, p.CollectionAddMethod);
else else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, p.CollectionAddMethod); EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, p.CollectionAddMethod);
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
@ -2402,7 +2439,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)"); sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
if (isComplexElement) if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan); EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan);
else else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, null); EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, null);
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
@ -2417,32 +2454,49 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
/// SGen reader = non-metadata mode → no ObjectWithMetadata fallback. /// SGen reader = non-metadata mode → no ObjectWithMetadata fallback.
/// !needsRefScan → only Object/Null possible → 1 branch per element. /// !needsRefScan → only Object/Null possible → 1 branch per element.
/// </summary> /// </summary>
private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan, string? addMethod = null) private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemTypeName, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan, string? addMethod = null)
{ {
var etc = $"etc_{propSuffix}"; var etc = $"etc_{propSuffix}";
sb.AppendLine($"{i}var {etc} = context.ReadByte();"); sb.AppendLine($"{i}var {etc} = context.ReadByte();");
var addCall = addMethod ?? "Add"; var addCall = addMethod ?? "Add";
var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.{addCall}(null!);"; var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.{addCall}(null!);";
var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = " : $"col_{propSuffix}.{addCall}("; var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = re_{propSuffix};" : $"col_{propSuffix}.{addCall}(re_{propSuffix});";
var assignEnd = isArray ? ";" : ");";
if (!needsRefScan) if (!needsRefScan)
{ {
// No ref tracking → only Object or Null in stream — 1 branch // No ref tracking → only Object or Null in stream — inline ReadProperties
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}"); sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}");
sb.AppendLine($"{i}else {assignExpr}{elemCast}{reader}.Instance.ReadObject(context, nd_{propSuffix}, -1)!{assignEnd}"); sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i}}}");
} }
else else
{ {
// Object hot path first, then ref markers // Object hot path first, then ref markers — inline ReadProperties
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Object)"); sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Object)");
sb.AppendLine($"{i} {assignExpr}{elemCast}{reader}.Instance.ReadObject(context, nd_{propSuffix}, -1)!{assignEnd}"); sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.ObjectRefFirst)"); sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.ObjectRefFirst)");
sb.AppendLine($"{i} {assignExpr}{elemCast}{reader}.Instance.ReadObject(context, nd_{propSuffix}, (int)context.ReadVarUInt())!{assignEnd}"); sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var ci_{propSuffix} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{propSuffix}, re_{propSuffix});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}"); sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}");
sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.ObjectRef)"); sb.AppendLine($"{i}else if ({etc} == BinaryTypeCode.ObjectRef)");
sb.AppendLine($"{i} {assignExpr}{elemCast}context.GetInternedObject((int)context.ReadVarUInt())!{assignEnd}"); if (isArray)
sb.AppendLine($"{i} col_{propSuffix}[{indexVar}] = {elemCast}context.GetInternedObject((int)context.ReadVarUInt())!;");
else
sb.AppendLine($"{i} col_{propSuffix}.{addCall}({elemCast}context.GetInternedObject((int)context.ReadVarUInt())!);");
} }
} }

View File

@ -26,4 +26,16 @@ internal interface IGeneratedBinaryReader
/// <returns>The deserialized object, or null if creation failed.</returns> /// <returns>The deserialized object, or null if creation failed.</returns>
object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex) object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex)
where TInput : struct, IBinaryInputBase; where TInput : struct, IBinaryInputBase;
/// <summary>
/// Reads all properties into an existing instance (no object creation, no cache registration).
/// Called from parent SGen reader after instance creation + cache registration.
/// Mirrors WriteProperties: parent handles lifecycle, child handles data.
/// </summary>
/// <param name="value">The pre-created instance to populate. Implementation casts to the concrete type.</param>
/// <param name="context">The deserialization context (owns buffer, position, options).</param>
/// <param name="depth">Current depth in the object graph.</param>
/// <typeparam name="TInput">Input strategy (ArrayBinaryInput or SequenceBinaryInput).</typeparam>
void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth)
where TInput : struct, IBinaryInputBase;
} }