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:
parent
7e3fbe7a52
commit
a1a2a90ef7
|
|
@ -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())!);");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue