1243 lines
65 KiB
C#
1243 lines
65 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace AyCode.Core.Serializers.SourceGenerator;
|
|
|
|
/// <summary>
|
|
/// Writer-side emit pass: generates the <c>IGeneratedBinaryWriter</c> implementation for each
|
|
/// <c>[AcBinarySerializable]</c> type. Emits <c>WriteProperties</c> (write pass), <c>ScanObject</c>
|
|
/// (scan pass for ref-handling + interning), and <c>ScanForDuplicates</c> (write-plan builder).
|
|
///
|
|
/// <para>Sub-passes:</para>
|
|
/// <list type="bullet">
|
|
/// <item><c>EmitProp</c> — per-property write emit (markered + markerless variants).</item>
|
|
/// <item><c>EmitScan*</c> — per-property scan emit (intern + ref tracking).</item>
|
|
/// <item><c>EmitDirect*Write</c> — inline collection / dictionary write loops.</item>
|
|
/// <item><c>EmitSkip</c> / <c>EmitVal</c> / <c>EmitMarkerless</c> — primitive write fragments.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public partial class AcBinarySourceGenerator
|
|
{
|
|
private static string GenWriter(SerializableClassInfo ci)
|
|
{
|
|
var sb = new StringBuilder(4096);
|
|
sb.AppendLine("// <auto-generated/>");
|
|
sb.AppendLine("#nullable enable");
|
|
sb.AppendLine("using System.Runtime.CompilerServices;");
|
|
sb.AppendLine("using System.Runtime.InteropServices;");
|
|
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
|
|
// IGeneratedBinaryWriter and other serializer types
|
|
sb.AppendLine("using AyCode.Core.Serializers;");
|
|
sb.AppendLine();
|
|
if (!string.IsNullOrEmpty(ci.Namespace))
|
|
sb.AppendLine($"namespace {ci.Namespace};");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedWriter : IGeneratedBinaryWriter");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
|
|
sb.AppendLine($" internal static readonly int s_wrapperSlot = AcBinarySerializer.AllocateWrapperSlot();");
|
|
sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};");
|
|
sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ ");
|
|
sb.Append(string.Join(", ", ci.PropertyNameHashes));
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" public void WriteProperties<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" // Depth check + EnterRecursion happens at the CALLER (before marker write).");
|
|
sb.AppendLine(" // Body just runs property writes; ExitRecursion at the end balances the caller's EnterRecursion.");
|
|
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
|
|
|
|
foreach (var p in ci.Properties)
|
|
{
|
|
sb.AppendLine();
|
|
EmitProp(sb, p, " ", ci.FullTypeName, ci.EnableMetadata, ci.EnablePropertyFilter, ci.EnablePolymorphDetect);
|
|
}
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine(" context.ExitRecursion();");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
// ScanObject — full scan pass (null/depth + self ref tracking + property scan)
|
|
GenScanProperties(sb, ci);
|
|
|
|
sb.AppendLine();
|
|
|
|
// ScanForDuplicates — instance method on IGeneratedBinaryWriter, called from Serialize
|
|
sb.AppendLine(" public void ScanForDuplicates<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context)");
|
|
sb.AppendLine(" where TOutput : struct, IBinaryOutputBase");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" if (!context.HasCaching) return;");
|
|
sb.AppendLine(" ScanObject(value, context);");
|
|
sb.AppendLine(" context.SortWritePlan();");
|
|
sb.AppendLine(" }");
|
|
|
|
sb.AppendLine("}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the ScanObject method — full scan pass entry point for this type.
|
|
/// Includes: null/depth check, self ref tracking (IId or All mode), property scan.
|
|
/// Only emits code for reference properties (strings + complex types) — primitives are skipped.
|
|
/// </summary>
|
|
private static void GenScanProperties(StringBuilder sb, SerializableClassInfo ci)
|
|
{
|
|
sb.AppendLine(" public void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase");
|
|
sb.AppendLine(" {");
|
|
|
|
// Compile-time proven: no scan work needed for this type
|
|
if (!ci.NeedsScan)
|
|
{
|
|
sb.AppendLine(" // NeedsScan=false: no ref tracking, no string interning, no scannable children");
|
|
sb.AppendLine(" }");
|
|
return;
|
|
}
|
|
|
|
// Early return: skip scan when no active runtime feature matches this type's needs
|
|
if (!ci.NeedsIdScan)
|
|
{
|
|
if (ci.NeedsAllRefScan && ci.NeedsInternScan)
|
|
sb.AppendLine(" if (!context.HasAllRefHandling && !context.HasStringInterning) return;");
|
|
else if (ci.NeedsAllRefScan)
|
|
sb.AppendLine(" if (!context.HasAllRefHandling) return;");
|
|
else if (ci.NeedsInternScan)
|
|
sb.AppendLine(" if (!context.HasStringInterning) return;");
|
|
}
|
|
|
|
// Null guard — MaxDepth option removed (was: cycle protection via runtime depth check).
|
|
// Cycle safety now comes from IId-tracking; future [AcBinaryCircular] attr will mark non-IId circular refs.
|
|
sb.AppendLine(" if (value == null) return;");
|
|
sb.AppendLine();
|
|
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
|
|
|
|
// Self ref tracking — inline TryTrack via wrapper (no bridge method overhead)
|
|
// Only emitted when the corresponding feature flag is enabled.
|
|
if (ci.IsIId)
|
|
{
|
|
var tryTrackMethod = ci.IdTypeName switch
|
|
{
|
|
"int" => "TryTrackInt32",
|
|
"long" => "TryTrackInt64",
|
|
"System.Guid" => "TryTrackGuid",
|
|
_ => "TryTrackInt32"
|
|
};
|
|
sb.AppendLine();
|
|
sb.AppendLine(" if (context.HasRefHandling)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var wrapper = context.GetWrapper(typeof({ci.FullTypeName}), s_wrapperSlot);");
|
|
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
|
sb.AppendLine($" if (!wrapper.{tryTrackMethod}(obj.Id, visitIndex, ref context.NextCacheIndexRef, out var cacheIndex, out var firstVisitIndex))");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" if (firstVisitIndex >= 0)");
|
|
sb.AppendLine(" context.AddWriteDuplicateEntry(firstVisitIndex, cacheIndex, isFirst: true, value: null);");
|
|
sb.AppendLine(" context.AddWriteDuplicateEntry(visitIndex, cacheIndex, isFirst: false, value: null);");
|
|
sb.AppendLine(" return;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine(" }");
|
|
}
|
|
if (ci.EnableRefHandling && !ci.IsIId)
|
|
{
|
|
// Non-IId type: track via wrapper.TryTrackInt32 with RuntimeHelpers.GetHashCode
|
|
sb.AppendLine();
|
|
sb.AppendLine(" if (context.HasAllRefHandling)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var wrapper = context.GetWrapper(typeof({ci.FullTypeName}), s_wrapperSlot);");
|
|
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
|
sb.AppendLine(" if (!wrapper.TryTrackInt32(System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value), visitIndex, ref context.NextCacheIndexRef, out var cacheIndex, out var firstVisitIndex))");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" if (firstVisitIndex >= 0)");
|
|
sb.AppendLine(" context.AddWriteDuplicateEntry(firstVisitIndex, cacheIndex, isFirst: true, value: null);");
|
|
sb.AppendLine(" context.AddWriteDuplicateEntry(visitIndex, cacheIndex, isFirst: false, value: null);");
|
|
sb.AppendLine(" return;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine(" }");
|
|
}
|
|
|
|
// Collect scannable properties
|
|
var scanProps = ci.Properties.Where(p =>
|
|
p.TypeKind == PropertyTypeKind.String ||
|
|
p.TypeKind == PropertyTypeKind.Complex ||
|
|
p.TypeKind == PropertyTypeKind.Collection ||
|
|
p.TypeKind == PropertyTypeKind.Dictionary).ToList();
|
|
|
|
// 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.Dictionary && (p.DictKeyKind == PropertyTypeKind.String || p.DictValueKind == PropertyTypeKind.String) && p.InterningFlags != 0));
|
|
|
|
// Combined check+inc — gated inside TryEnterRecursion (checks NeedsDepthCheck).
|
|
// Emitted AFTER all early returns (NeedsScan=false, feature-flag, null guard, IId 2nd-occurrence)
|
|
// and BEFORE the property scan loop that recurses into children.
|
|
// On limit hit: helper method (cold path, NoInlining) dispatches Throw or Truncate (return).
|
|
sb.AppendLine();
|
|
sb.AppendLine(" if (context.TryEnterRecursion(hasTruncatePath: false)) return; // scan: skip children");
|
|
|
|
if (hasStringScan)
|
|
{
|
|
// Use pre-computed InternBit from context (avoids Options.UseStringInterning field chain + shift per object).
|
|
// Per-property InterningFlags check uses internBit directly.
|
|
// Cannot combine flags (OR) because different properties may have different flags
|
|
// and Attribute mode must NOT scan All-only properties.
|
|
sb.AppendLine();
|
|
sb.AppendLine(" var internBit = context.InternBit;");
|
|
sb.AppendLine(" int minIntern = 0, maxIntern = 0;");
|
|
sb.AppendLine(" if (internBit > 1) { minIntern = context.MinStringInternLength; maxIntern = context.MaxStringInternLength; }");
|
|
}
|
|
|
|
var hasAnyScanProp = false;
|
|
foreach (var p in scanProps)
|
|
{
|
|
sb.AppendLine();
|
|
hasAnyScanProp = true;
|
|
EmitScanProp(sb, p, " ", ci.FullTypeName, ci.EnablePropertyFilter);
|
|
}
|
|
|
|
if (!hasAnyScanProp)
|
|
{
|
|
sb.AppendLine(" // No reference properties to scan");
|
|
}
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine(" context.ExitRecursion();");
|
|
sb.AppendLine(" }");
|
|
}
|
|
|
|
private static void EmitProp(StringBuilder sb, PropInfo p, string i, string fullTypeName, bool enableMetadata, bool enablePropertyFilter, bool enablePolymorphDetect)
|
|
{
|
|
var a = $"obj.{p.Name}";
|
|
|
|
// Markerless types: write raw value only, no type marker, no PropertySkip
|
|
// Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
|
|
// NEVER filtered (runtime doesn't filter markerless properties either)
|
|
// When EnableMetadataFeature=false: always markerless (no UseMetadata branch needed)
|
|
// When EnableMetadataFeature=true: UseMetadata=true uses markered path (EmitSkip)
|
|
if (IsMarkerless(p.TypeKind))
|
|
{
|
|
if (!enableMetadata)
|
|
EmitMarkerless(sb, p.TypeKind, a, i);
|
|
else
|
|
EmitPropertyBridge(sb, p.TypeKind, a, i);
|
|
return;
|
|
}
|
|
|
|
// All non-markerless properties: emit PropertyFilter guard
|
|
// When filter returns false, write PropertySkip and skip the property write.
|
|
// Gated by `[AcBinarySerializable(EnablePropertyFilterFeature = ...)]` — when false, the entire
|
|
// filter-check block is omitted from emit (no `HasPropertyFilter` test, no filter-context allocation,
|
|
// no lambda-call) → leaner generated code on hot-path types that never use a property-filter.
|
|
if (enablePropertyFilter)
|
|
{
|
|
sb.AppendLine($"{i}if (context.HasPropertyFilter)");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var fc_{p.Name} = new BinaryPropertyFilterContext(obj, typeof({fullTypeName}), \"{p.Name}\", typeof({p.TypeNameForTypeof}), static o => (({fullTypeName})o).{p.Name});");
|
|
sb.AppendLine($"{i} if (!context.PropertyFilter!(in fc_{p.Name}))");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i} goto skip_{p.Name};");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
// Nullable value types always use markered path (need Null marker)
|
|
if (IsNullableVTKind(p.TypeKind))
|
|
{
|
|
sb.AppendLine($"{i}if ({a}.HasValue)");
|
|
sb.AppendLine($"{i}{{");
|
|
EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", p.TypeNameForTypeof, i + " ");
|
|
sb.AppendLine($"{i}}}");
|
|
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}skip_{p.Name}:;");
|
|
return;
|
|
}
|
|
|
|
// Non-markerless types: write WITH type marker byte (markered path)
|
|
switch (p.TypeKind)
|
|
{
|
|
case PropertyTypeKind.String:
|
|
// Inlined string-property write -- streamlined chain (bypasses WriteStringGenerated /
|
|
// WriteString): FastWire -> markerless UTF-16; else null -> PropertySkip, empty ->
|
|
// StringEmpty, content -> interning attempt (eligible props) + WriteStringWithDispatch.
|
|
// A local pins the single getter evaluation; the chain is small + branch-friendly so
|
|
// the JIT folds it into WriteProperties (no per-string call frame).
|
|
sb.AppendLine($"{i}var str_{p.Name} = {a};");
|
|
sb.AppendLine($"{i}if (context.FastWire) context.WriteStringUtf16Markerless(str_{p.Name});");
|
|
sb.AppendLine($"{i}else if (str_{p.Name} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else if (str_{p.Name}.Length == 0) context.WriteByte(BinaryTypeCode.StringEmpty);");
|
|
if (p.InterningFlags == 0)
|
|
{
|
|
sb.AppendLine($"{i}else context.WriteStringWithDispatch(str_{p.Name});");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{i}else");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} context.StringInternEligible = ({p.InterningFlags} & context.InternBit) != 0;");
|
|
sb.AppendLine($"{i} if (!context.TryWriteInternedString(str_{p.Name})) context.WriteStringWithDispatch(str_{p.Name});");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
break;
|
|
case PropertyTypeKind.Complex:
|
|
// Complex object: direct write bypasses GetWrapper + WriteObject pipeline entirely
|
|
// when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise.
|
|
if (p.HasGeneratedWriter)
|
|
EmitDirectObjectWrite(sb, p, a, i);
|
|
else if (p.IsObjectDeclaredType)
|
|
{
|
|
// System.Object property: runtime type unknown at compile time.
|
|
// Write ObjectWithTypeName prefix so deserializer can resolve the concrete type.
|
|
// Use value.GetType() for runtime type dispatch (not typeof(object)).
|
|
// Gated by `[AcBinarySerializable(enablePolymorphDetectFeature: ...)]` — `false`
|
|
// skips the type-name emit entirely. Misuse (false + non-null object prop at runtime)
|
|
// is caught at build time by ACBIN002 in DetectAndReportPolymorphicMisuse.
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else");
|
|
sb.AppendLine($"{i}{{");
|
|
if (enablePolymorphDetect)
|
|
{
|
|
sb.AppendLine($"{i} if (!context.UseMetadata)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithTypeName);");
|
|
sb.AppendLine($"{i} context.WriteStringUtf8({a}.GetType().AssemblyQualifiedName!);");
|
|
sb.AppendLine($"{i} }}");
|
|
}
|
|
sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context);");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
else
|
|
{
|
|
// Reference type properties can always be null at runtime regardless of nullable
|
|
// annotation — runtime can violate the nullable-disabled contract via EF lazy-load,
|
|
// projection gaps, detached navigation properties, etc. Mirrors the EmitDirectObjectWrite
|
|
// (line ~828) and EmitDirectCollectionWrite (line ~877) defensive null-check.
|
|
// Reader-side compat: every markered property is wrapped in `if (tc != PropertySkip)`
|
|
// by EmitReadProperty (GenReader.cs line ~137), so the marker is uniformly handled.
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
|
|
}
|
|
break;
|
|
case PropertyTypeKind.Collection:
|
|
// Direct collection write for List<T>/T[] with Complex element types that have generated writers
|
|
if (p.ElementHasGeneratedWriter && p.CollectionKind != null)
|
|
EmitDirectCollectionWrite(sb, p, a, i);
|
|
else
|
|
{
|
|
// Reference type collections (with non-SGen elements, falling onto the
|
|
// WriteValueGenerated bridge) can always be null at runtime regardless of nullable
|
|
// annotation — runtime can violate the nullable-disabled contract via EF lazy-load,
|
|
// projection gaps, missing initializers, etc. Mirrors EmitDirectCollectionWrite
|
|
// (line ~877) and EmitDirectObjectWrite (line ~828) defensive null-check.
|
|
// Reader-side compat: every markered property is wrapped in `if (tc != PropertySkip)`
|
|
// by EmitReadProperty (GenReader.cs line ~137).
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
|
|
}
|
|
break;
|
|
case PropertyTypeKind.Dictionary:
|
|
EmitDirectDictionaryWrite(sb, p, a, i);
|
|
break;
|
|
default:
|
|
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
|
break;
|
|
}
|
|
|
|
sb.AppendLine($"{i}skip_{p.Name}:;");
|
|
}
|
|
|
|
// IsMarkerless moved to AcBinarySourceGenerator.TypeAnalysis.cs (shared writer/reader utility).
|
|
|
|
/// <summary>
|
|
/// Emits raw value only — no type marker, no PropertySkip.
|
|
/// Matches runtime WritePropertyMarkerless exactly.
|
|
/// </summary>
|
|
private static void EmitMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
|
|
{
|
|
switch (k)
|
|
{
|
|
case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteVarInt({a});"); break;
|
|
case PropertyTypeKind.Int64: sb.AppendLine($"{i}context.WriteVarLong({a});"); break;
|
|
case PropertyTypeKind.Double: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
|
|
case PropertyTypeKind.Single: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
|
|
case PropertyTypeKind.Decimal: sb.AppendLine($"{i}context.WriteDecimalBits({a});"); break;
|
|
case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteDateTimeBits({a});"); break;
|
|
case PropertyTypeKind.Guid: sb.AppendLine($"{i}context.WriteGuidBits({a});"); break;
|
|
case PropertyTypeKind.Byte: sb.AppendLine($"{i}context.WriteByte({a});"); break;
|
|
case PropertyTypeKind.Int16: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
|
|
case PropertyTypeKind.UInt16: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
|
|
case PropertyTypeKind.UInt32: sb.AppendLine($"{i}context.WriteVarUInt({a});"); break;
|
|
case PropertyTypeKind.UInt64: sb.AppendLine($"{i}context.WriteVarULong({a});"); break;
|
|
case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}context.WriteRaw({a}.Ticks);"); break;
|
|
case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}context.WriteDateTimeOffsetBits({a});"); break;
|
|
case PropertyTypeKind.Boolean: sb.AppendLine($"{i}context.WriteByte({a} ? (byte)1 : (byte)0);"); break;
|
|
case PropertyTypeKind.Enum: sb.AppendLine($"{i}context.WriteVarInt((int){a});"); break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits a single bridge method call for markerless property types with enableMetadata=true.
|
|
/// The bridge method on BinarySerializationContext handles both UseMetadata=true (markered+skip)
|
|
/// and UseMetadata=false (markerless) paths internally. Replaces 7-11 lines of generated code with 1 line.
|
|
/// </summary>
|
|
private static void EmitPropertyBridge(StringBuilder sb, PropertyTypeKind k, string a, string i)
|
|
{
|
|
var call = k switch
|
|
{
|
|
PropertyTypeKind.Int32 => $"context.WriteInt32Property({a});",
|
|
PropertyTypeKind.Int64 => $"context.WriteInt64Property({a});",
|
|
PropertyTypeKind.Boolean => $"context.WriteBoolProperty({a});",
|
|
PropertyTypeKind.Double => $"context.WriteFloat64Property({a});",
|
|
PropertyTypeKind.Single => $"context.WriteFloat32Property({a});",
|
|
PropertyTypeKind.Decimal => $"context.WriteDecimalProperty({a});",
|
|
PropertyTypeKind.DateTime => $"context.WriteDateTimeProperty({a});",
|
|
PropertyTypeKind.Guid => $"context.WriteGuidProperty({a});",
|
|
PropertyTypeKind.Byte => $"context.WriteByteProperty({a});",
|
|
PropertyTypeKind.Int16 => $"context.WriteInt16Property({a});",
|
|
PropertyTypeKind.UInt16 => $"context.WriteUInt16Property({a});",
|
|
PropertyTypeKind.UInt32 => $"context.WriteUInt32Property({a});",
|
|
PropertyTypeKind.UInt64 => $"context.WriteUInt64Property({a});",
|
|
PropertyTypeKind.Enum => $"context.WriteEnumInt32Property((int){a});",
|
|
PropertyTypeKind.TimeSpan => $"context.WriteTimeSpanProperty({a});",
|
|
PropertyTypeKind.DateTimeOffset => $"context.WriteDateTimeOffsetProperty({a});",
|
|
_ => null
|
|
};
|
|
|
|
if (call != null)
|
|
sb.AppendLine($"{i}{call}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits direct object write — bypasses GetWrapper + WriteObject entirely.
|
|
#region Scan Pass Code Generation
|
|
|
|
/// <summary>
|
|
/// Compile-time check: will EmitScanProp produce any scan work for this property?
|
|
/// When false, the entire block (including PropertyFilter guard) is skipped.
|
|
/// </summary>
|
|
private static bool HasScanWork(PropInfo p) => p.TypeKind switch
|
|
{
|
|
PropertyTypeKind.String => p.InterningFlags != 0,
|
|
PropertyTypeKind.Complex when p.HasGeneratedWriter => p.ChildNeedsScan,
|
|
PropertyTypeKind.Complex => true,
|
|
PropertyTypeKind.Collection => HasCollectionScanWork(p),
|
|
PropertyTypeKind.Dictionary => HasDictionaryScanWork(p),
|
|
_ => false
|
|
};
|
|
|
|
private static bool HasCollectionScanWork(PropInfo p) => p.ElementKind switch
|
|
{
|
|
PropertyTypeKind.String => p.InterningFlags != 0,
|
|
PropertyTypeKind.Complex when p.ElementHasGeneratedWriter && p.CollectionKind != null => p.ElementNeedsScan,
|
|
PropertyTypeKind.Complex => true,
|
|
_ => false
|
|
};
|
|
|
|
private static bool HasDictionaryScanWork(PropInfo p)
|
|
{
|
|
if (p.DictKeyKind == PropertyTypeKind.String && p.InterningFlags != 0) return true;
|
|
if (p.DictValueKind == PropertyTypeKind.String && p.InterningFlags != 0) return true;
|
|
if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter) return p.DictValueNeedsScan;
|
|
if (p.DictValueKind == PropertyTypeKind.Complex) return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits scan pass code for a single property.
|
|
/// String: interning check + ScanInternString.
|
|
/// Complex (SGen): ref tracking via slot IdentityMap + recursive ScanProperties.
|
|
/// Complex (no SGen): fallback to ScanValueGenerated (runtime wrapper lookup).
|
|
/// Collection: iterate elements with same patterns.
|
|
/// </summary>
|
|
private static void EmitScanProp(StringBuilder sb, PropInfo p, string i, string fullTypeName, bool enablePropertyFilter)
|
|
{
|
|
// Compile-time proven: no scan work for this property — skip entirely (including PropertyFilter guard)
|
|
if (!HasScanWork(p)) return;
|
|
|
|
var a = $"obj.{p.Name}";
|
|
|
|
// PropertyFilter: must match write pass — if filter skips property, scan must skip too
|
|
// Only for non-markerless properties (matching EmitProp behavior).
|
|
// Gated by `[AcBinarySerializable(EnablePropertyFilterFeature = ...)]` — same gate as the writer pass.
|
|
if (enablePropertyFilter && !IsMarkerless(p.TypeKind))
|
|
{
|
|
sb.AppendLine($"{i}if (context.HasPropertyFilter)");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var fc_{p.Name} = new BinaryPropertyFilterContext(obj, typeof({fullTypeName}), \"{p.Name}\", typeof({p.TypeNameForTypeof}), static o => (({fullTypeName})o).{p.Name});");
|
|
sb.AppendLine($"{i} if (!context.PropertyFilter!(in fc_{p.Name}))");
|
|
sb.AppendLine($"{i} goto scanskip_{p.Name};");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
switch (p.TypeKind)
|
|
{
|
|
case PropertyTypeKind.String:
|
|
EmitScanString(sb, p, a, i);
|
|
break;
|
|
|
|
case PropertyTypeKind.Complex:
|
|
if (p.HasGeneratedWriter)
|
|
EmitScanComplexSGen(sb, p, a, i);
|
|
else
|
|
EmitScanComplexRuntime(sb, p, a, i);
|
|
break;
|
|
|
|
case PropertyTypeKind.Collection:
|
|
EmitScanCollection(sb, p, a, i);
|
|
break;
|
|
|
|
case PropertyTypeKind.Dictionary:
|
|
EmitScanDictionary(sb, p, a, i);
|
|
break;
|
|
}
|
|
|
|
if (!IsMarkerless(p.TypeKind))
|
|
sb.AppendLine($"{i}scanskip_{p.Name}:;");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits scan pass code for a string property: interning flags check + ScanInternString.
|
|
/// </summary>
|
|
private static void EmitScanString(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
if (p.InterningFlags == 0)
|
|
{
|
|
// Never interned (explicit [AcStringIntern(false)] or no flags) — skip entirely
|
|
return;
|
|
}
|
|
|
|
// Per-property InterningFlags check with hoisted internBit (context.Options read once)
|
|
sb.AppendLine($"{i}if (({p.InterningFlags} & internBit) != 0)");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var str_{p.Name} = {a};");
|
|
sb.AppendLine($"{i} if (str_{p.Name} != null)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var slen_{p.Name} = str_{p.Name}.Length;");
|
|
sb.AppendLine($"{i} if (slen_{p.Name} >= minIntern && (maxIntern == 0 || slen_{p.Name} <= maxIntern))");
|
|
sb.AppendLine($"{i} context.ScanInternString(str_{p.Name});");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits scan pass code for a Complex property with SGen writer.
|
|
/// No parent-side ref tracking — child ScanObject handles its own (ScanTrackObjectXxx).
|
|
/// </summary>
|
|
private static void EmitScanComplexSGen(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
// Compile-time proven: child scan is no-op — skip entirely
|
|
if (!p.ChildNeedsScan) return;
|
|
|
|
var writer = p.WriterClassName;
|
|
var childVar = $"sc_{p.Name}";
|
|
|
|
// 3-axis guard: IId → always call, AllRef → guard All mode, Intern → guard UseStringInterning
|
|
string? guard = null;
|
|
if (!p.ChildNeedsIdScan)
|
|
{
|
|
if (p.ChildNeedsAllRefScan && p.ChildNeedsInternScan)
|
|
guard = "context.HasAllRefHandling || context.HasStringInterning";
|
|
else if (p.ChildNeedsAllRefScan)
|
|
guard = "context.HasAllRefHandling";
|
|
else if (p.ChildNeedsInternScan)
|
|
guard = "context.HasStringInterning";
|
|
}
|
|
|
|
if (guard != null)
|
|
{
|
|
sb.AppendLine($"{i}if ({guard})");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var {childVar} = {a};");
|
|
sb.AppendLine($"{i} if ({childVar} != null)");
|
|
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
else
|
|
{
|
|
// IId in subtree — always call (active in OnlyId + All)
|
|
sb.AppendLine($"{i}var {childVar} = {a};");
|
|
sb.AppendLine($"{i}if ({childVar} != null)");
|
|
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits scan pass code for a Complex property without SGen writer (runtime fallback).
|
|
/// System.Object properties use value.GetType() for runtime type dispatch.
|
|
/// </summary>
|
|
private static void EmitScanComplexRuntime(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
var childVar = $"sc_{p.Name}";
|
|
sb.AppendLine($"{i}var {childVar} = {a};");
|
|
sb.AppendLine($"{i}if ({childVar} != null)");
|
|
if (p.IsObjectDeclaredType)
|
|
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context);");
|
|
else
|
|
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context);");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits scan pass code for a Collection property.
|
|
/// Handles string collections (interning) and complex element collections (SGen or runtime fallback).
|
|
/// </summary>
|
|
private static void EmitScanCollection(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
// String element collection
|
|
if (p.ElementKind == PropertyTypeKind.String)
|
|
{
|
|
if (p.InterningFlags == 0) return; // never interned
|
|
|
|
sb.AppendLine($"{i}var scol_{p.Name} = {a};");
|
|
sb.AppendLine($"{i}if (scol_{p.Name} != null && ({p.InterningFlags} & internBit) != 0)");
|
|
sb.AppendLine($"{i}{{");
|
|
|
|
if (p.CollectionKind == "Array")
|
|
{
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Length; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else if (p.CollectionKind == "List")
|
|
{
|
|
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan(scol_{p.Name});");
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < span_{p.Name}.Length; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = span_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else if (p.CollectionKind == "IndexedCollection")
|
|
{
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Count; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{i} foreach (var se_{p.Name} in scol_{p.Name})");
|
|
sb.AppendLine($"{i} {{");
|
|
}
|
|
|
|
sb.AppendLine($"{i} if (se_{p.Name} != null)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var slen_{p.Name} = se_{p.Name}.Length;");
|
|
sb.AppendLine($"{i} if (slen_{p.Name} >= minIntern && (maxIntern == 0 || slen_{p.Name} <= maxIntern))");
|
|
sb.AppendLine($"{i} context.ScanInternString(se_{p.Name});");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
return;
|
|
}
|
|
|
|
// Complex element collection with SGen writer
|
|
if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter && p.CollectionKind != null)
|
|
{
|
|
// Compile-time proven: element scan is no-op — skip entirely
|
|
if (!p.ElementNeedsScan) return;
|
|
|
|
var writer = p.ElementWriterClassName;
|
|
|
|
// 3-axis guard: IId → always scan, AllRef → guard All mode, Intern → guard UseStringInterning
|
|
string? elemGuard = null;
|
|
if (!p.ElementNeedsIdScan)
|
|
{
|
|
if (p.ElementNeedsAllRefScan && p.ElementNeedsInternScan)
|
|
elemGuard = "context.HasAllRefHandling || context.HasStringInterning";
|
|
else if (p.ElementNeedsAllRefScan)
|
|
elemGuard = "context.HasAllRefHandling";
|
|
else if (p.ElementNeedsInternScan)
|
|
elemGuard = "context.HasStringInterning";
|
|
}
|
|
|
|
// Guard entire collection scan with runtime check when no IId in element subtree
|
|
if (elemGuard != null)
|
|
sb.AppendLine($"{i}if ({elemGuard})");
|
|
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var scol_{p.Name} = {a};");
|
|
sb.AppendLine($"{i} if (scol_{p.Name} != null)");
|
|
sb.AppendLine($"{i} {{");
|
|
|
|
if (p.CollectionKind == "Array")
|
|
{
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Length; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else if (p.CollectionKind == "List")
|
|
{
|
|
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan(scol_{p.Name});");
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < span_{p.Name}.Length; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = span_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else if (p.CollectionKind == "IndexedCollection")
|
|
{
|
|
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Count; si_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
|
}
|
|
else // Counted (foreach)
|
|
{
|
|
sb.AppendLine($"{i} foreach (var se_{p.Name} in scol_{p.Name})");
|
|
sb.AppendLine($"{i} {{");
|
|
}
|
|
|
|
var e = $"se_{p.Name}";
|
|
// Null check only — ScanObject handles depth + ref tracking internally
|
|
sb.AppendLine($"{i} if ({e} == null) continue;");
|
|
sb.AppendLine($"{i} {writer}.Instance.ScanObject({e}, context);");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
return;
|
|
}
|
|
|
|
// Complex element collection without SGen writer — runtime fallback
|
|
if (p.ElementKind == PropertyTypeKind.Complex)
|
|
{
|
|
sb.AppendLine($"{i}var scol_{p.Name} = {a};");
|
|
sb.AppendLine($"{i}if (scol_{p.Name} != null)");
|
|
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated(scol_{p.Name}, typeof({p.TypeNameForTypeof}), context);");
|
|
return;
|
|
}
|
|
|
|
// Primitive element collection — no scanning needed
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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.HasAllRefHandling || context.HasStringInterning";
|
|
else if (p.DictValueNeedsAllRefScan)
|
|
complexGuard = "context.HasAllRefHandling";
|
|
else if (p.DictValueNeedsInternScan)
|
|
complexGuard = "context.HasStringInterning";
|
|
}
|
|
|
|
// 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} 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);");
|
|
sb.AppendLine($"{i} }}");
|
|
}
|
|
else
|
|
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context);");
|
|
}
|
|
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Emits inline object write for a Complex property that has a generated writer.
|
|
/// Compile-time ChildNeedsRefScan eliminates TryConsumeWritePlanEntry when scan never tracks child.
|
|
/// !ChildNeedsRefScan + !ChildEnableMetadata → ZERO branches: just Object + WriteProperties.
|
|
/// </summary>
|
|
private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
var writer = p.WriterClassName;
|
|
var refSuffix = p.IsIId ? "IId" : "All";
|
|
|
|
// Reference type properties can always be null at runtime regardless of nullable annotation
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
|
|
if (!p.ChildNeedsRefScan && !p.ChildEnableMetadata)
|
|
{
|
|
// Compile-time proven: no ref, no metadata. Combined check+inc BEFORE marker write so Truncate writes
|
|
// Null wire-correctly. TryEnterRecursion inc'd on success; ExitRecursion at WriteProperties end.
|
|
sb.AppendLine($"{i}else if (context.TryEnterRecursion(hasTruncatePath: true)) {{ /* truncated: Null written */ }}");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({a}, context); }}");
|
|
}
|
|
else if (p.ChildNeedsRefScan && !p.ChildEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context);");
|
|
}
|
|
else if (!p.ChildNeedsRefScan && p.ChildEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context);");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context);");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits inline metadata write: typeNameHash + (if first) propCount + property hashes.
|
|
/// All values are compile-time constants.
|
|
/// </summary>
|
|
private static void EmitInlineMetadata(StringBuilder sb, int typeNameHash, int[] propertyHashes, string isFirstVar, string i)
|
|
{
|
|
sb.AppendLine($"{i}context.WriteRaw({typeNameHash});");
|
|
sb.AppendLine($"{i}if ({isFirstVar})");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} context.WriteVarUInt({(uint)propertyHashes.Length});");
|
|
foreach (var hash in propertyHashes)
|
|
sb.AppendLine($"{i} context.WriteRaw({hash});");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits inline collection write for List<T> / T[] where T is a Complex type with generated writer.
|
|
/// Bypasses GetWrapper + WriteArray + WriteValue per-element dispatch entirely.
|
|
/// Wire format: [Array marker][VarUInt count][elem₁ marker+props][elem₂ marker+props]...
|
|
/// Handles both UseMetadata=true and false inline — no fallback to WriteValueGenerated.
|
|
/// </summary>
|
|
private static void EmitDirectCollectionWrite(StringBuilder sb, PropInfo p, string a, string i)
|
|
{
|
|
var writer = p.ElementWriterClassName;
|
|
|
|
// Reference type collections can always be null at runtime regardless of nullable annotation
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else");
|
|
sb.AppendLine($"{i}{{");
|
|
|
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Array);");
|
|
|
|
// Get count and iteration based on collection kind
|
|
if (p.CollectionKind == "Array")
|
|
{
|
|
sb.AppendLine($"{i} var arr_{p.Name} = {a};");
|
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)arr_{p.Name}.Length);");
|
|
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < arr_{p.Name}.Length; i_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];");
|
|
}
|
|
else if (p.CollectionKind == "Counted")
|
|
{
|
|
sb.AppendLine($"{i} var col_{p.Name} = {a};");
|
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);");
|
|
sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})");
|
|
sb.AppendLine($"{i} {{");
|
|
}
|
|
else if (p.CollectionKind == "IndexedCollection")
|
|
{
|
|
sb.AppendLine($"{i} var col_{p.Name} = {a};");
|
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);");
|
|
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < col_{p.Name}.Count; i_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var elem_{p.Name} = col_{p.Name}[i_{p.Name}];");
|
|
}
|
|
else // List — CollectionsMarshal.AsSpan for zero-overhead iteration
|
|
{
|
|
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan({a});");
|
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)span_{p.Name}.Length);");
|
|
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < span_{p.Name}.Length; i_{p.Name}++)");
|
|
sb.AppendLine($"{i} {{");
|
|
sb.AppendLine($"{i} var elem_{p.Name} = span_{p.Name}[i_{p.Name}];");
|
|
}
|
|
|
|
// Per-element write
|
|
var e = $"elem_{p.Name}";
|
|
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
|
|
|
var elemRefSuffix = p.ElementIsIId ? "IId" : "All";
|
|
|
|
// Ref-aware emit decision routed through RefAwareEmitPredicate — symmetric counterpart to the
|
|
// reader-side EmitReadCollectionElement / EmitReadComplex guards. Single source of truth so the
|
|
// writer-emit (which marker variants may appear on the wire) and the reader-emit (which marker
|
|
// variants are decoded) NEVER drift apart on the same PropInfo.
|
|
var elementEmitsRefMarker = RefAwareEmitPredicate.ElementEmitsRefMarker(p);
|
|
|
|
if (!elementEmitsRefMarker && !p.ElementEnableMetadata)
|
|
{
|
|
// Compile-time proven: no ref, no metadata. Combined check+inc before marker write.
|
|
sb.AppendLine($"{i} if (context.TryEnterRecursion(hasTruncatePath: true)) continue;");
|
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context);");
|
|
}
|
|
else if (elementEmitsRefMarker && !p.ElementEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context);");
|
|
}
|
|
else if (!elementEmitsRefMarker && p.ElementEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i} if (context.WriteObjectMetaMarker({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context);");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context);");
|
|
}
|
|
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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";
|
|
|
|
// Reference type dictionaries can always be null at runtime regardless of nullable annotation
|
|
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
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} 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} & context.InternBit) != 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);");
|
|
}
|
|
|
|
// 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} & context.InternBit) != 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);");
|
|
}
|
|
|
|
sb.AppendLine($"{i} }}");
|
|
sb.AppendLine($"{i}}}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits inline write for a Complex+SGen dictionary value with ref tracking and metadata support.
|
|
/// Delegates marker logic to runtime WriteObjectRefMarker/MetaMarker/FullMarker bridge.
|
|
/// </summary>
|
|
private static void EmitDictValueComplexWrite(StringBuilder sb, PropInfo p, string v, string s, string i)
|
|
{
|
|
var writer = p.DictValueWriterClassName!;
|
|
|
|
sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}");
|
|
|
|
var dvRefSuffix = p.DictValueIsIId ? "IId" : "All";
|
|
|
|
if (!p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
|
|
{
|
|
// No ref, no metadata. Combined check+inc before marker write.
|
|
sb.AppendLine($"{i}else if (context.TryEnterRecursion(hasTruncatePath: true)) {{ /* truncated: Null written */ }}");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context); }}");
|
|
}
|
|
else if (p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context);");
|
|
}
|
|
else if (!p.DictValueNeedsRefScan && p.DictValueEnableMetadata)
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context);");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context);");
|
|
}
|
|
}
|
|
|
|
private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i)
|
|
{
|
|
switch (k)
|
|
{
|
|
case PropertyTypeKind.Int32:
|
|
{
|
|
// Mirrors runtime WritePropertyOrSkip → WriteInt32 (TinyInt optimization)
|
|
var s32 = a.Replace(".", "_");
|
|
sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt({a}, out var ti_{s32})) context.WriteByte(ti_{s32});");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}");
|
|
break;
|
|
}
|
|
case PropertyTypeKind.Int64:
|
|
{
|
|
// Mirrors runtime WritePropertyOrSkip → WriteInt64 → WriteInt32 (int range + TinyInt)
|
|
var s64 = a.Replace(".", "_");
|
|
sb.AppendLine($"{i}if ({a} == 0L) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else if ({a} >= int.MinValue && {a} <= int.MaxValue)");
|
|
sb.AppendLine($"{i}{{");
|
|
sb.AppendLine($"{i} var iv_{s64} = (int){a};");
|
|
sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(iv_{s64}, out var ti_{s64})) context.WriteByte(ti_{s64});");
|
|
sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(iv_{s64}); }}");
|
|
sb.AppendLine($"{i}}}");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}");
|
|
break;
|
|
}
|
|
case PropertyTypeKind.Boolean:
|
|
sb.AppendLine($"{i}if (!{a}) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.True);");
|
|
break;
|
|
case PropertyTypeKind.Double:
|
|
sb.AppendLine($"{i}if ({a} == 0.0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.Single:
|
|
sb.AppendLine($"{i}if ({a} == 0f) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.Decimal:
|
|
sb.AppendLine($"{i}if ({a} == 0m) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ 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}if ({a} == System.Guid.Empty) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.Byte:
|
|
sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt8); context.WriteByte({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.Int16:
|
|
sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int16); context.WriteRaw({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.UInt16:
|
|
sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt16); context.WriteRaw({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.UInt32:
|
|
sb.AppendLine($"{i}if ({a} == 0U) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt32); context.WriteVarUInt({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.UInt64:
|
|
sb.AppendLine($"{i}if ({a} == 0UL) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt64); context.WriteVarULong({a}); }}");
|
|
break;
|
|
case PropertyTypeKind.Enum:
|
|
var s = a.Replace(".", "_");
|
|
sb.AppendLine($"{i}var ev_{s} = (int){a};");
|
|
sb.AppendLine($"{i}if (ev_{s} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
|
sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt(ev_{s}, out var te_{s})) {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(te_{s}); }}");
|
|
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(ev_{s}); }}");
|
|
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;
|
|
default:
|
|
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context);");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void EmitVal(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i)
|
|
{
|
|
switch (k)
|
|
{
|
|
case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a});"); break;
|
|
case PropertyTypeKind.Int64: sb.AppendLine($"{i}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;
|
|
default: EmitSkip(sb, k, a, typeName, i); break;
|
|
}
|
|
}
|
|
}
|