[LOADED_DOCS: 2 files, no new loads]

Refactor AcBinary SGen: switch dispatch + new TODOs

Refactored the AcBinary source generator to use switch-based dispatch for deserialization, replacing sequential if-else chains for improved performance and clarity. Updated comments to document the new logic. Added several new feature and refactor TODOs in BINARY_TODO.md, including Memory/Span overloads, async Stream serialization modes, non-generic Type-based APIs, attribute-driven polymorphism, and a thread-safety fix for serializer options. Each TODO includes rationale and acceptance criteria.
This commit is contained in:
Loretta 2026-05-01 20:14:09 +02:00
parent 3b45de6de3
commit 329c9c2928
2 changed files with 357 additions and 52 deletions

View File

@ -1930,35 +1930,47 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
}
else
{
// Ref tracking possible — Object/ObjectRefFirst/ObjectRef/FixObj dispatch
// Inline: parent creates instance + handles cache registration
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Object)");
// Ref tracking possible — switch on tc (Object / ObjectRefFirst / [Null] / ObjectRef / <Object).
// The 4 known TypeCode constants are emitted as switch cases — the JIT compiles them as a
// jump-table for O(1) dispatch (vs the previous if-else chain's sequential ==-compares).
// The polymorphic FixObj range-check (tc < Object) goes into the default branch — runtime
// bridge path is rare on a typical SGen graph, so default fall-through is acceptable.
// Inline: parent creates instance + handles cache registration.
sb.AppendLine($"{i}switch ({tc})");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
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}{{");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRefFirst:");
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}}}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
if (p.IsNullable)
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRef)");
sb.AppendLine($"{i} case BinaryTypeCode.Null: break;");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRef:");
sb.AppendLine($"{i} {a} = {cast}context.GetInternedObject((int)context.ReadVarUInt())!;");
// FixObj slot (0..SlotCount-1): same type via FixObj marker (non-meta, non-ref mode)
sb.AppendLine($"{i} break;");
// FixObj slot (0..SlotCount-1): same type via FixObj marker (non-meta, non-ref mode).
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
sb.AppendLine($"{i}else if ({tc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} default:");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc});");
sb.AppendLine($"{i} if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1;");
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} break;");
sb.AppendLine($"{i}}}");
}
}
@ -2286,36 +2298,48 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
}
else
{
// Object hot path first, then ref markers, then FixObj — inline ReadProperties
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Object)");
// Switch on etc (Object / ObjectRefFirst / Null / ObjectRef / <Object). The JIT emits the
// 4 known TypeCode constants as a jump-table (O(1) dispatch); the polymorphic FixObj
// range-check (etc < Object) goes into the default branch. Object hot-path stays first.
sb.AppendLine($"{i}switch ({etc})");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
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}{{");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRefFirst:");
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.ObjectRef)");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.Null:");
sb.AppendLine($"{i} {assignNull}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRef:");
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())!);");
// FixObj slot (0..SlotCount-1): same type via FixObj marker
sb.AppendLine($"{i} break;");
// FixObj slot (0..SlotCount-1): same type via FixObj marker.
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
sb.AppendLine($"{i}else if ({etc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} default:");
sb.AppendLine($"{i} if ({etc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context.GetWrapper(typeof({elemTypeName}), {etc});");
sb.AppendLine($"{i} if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1;");
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} break;");
sb.AppendLine($"{i}}}");
}
}

File diff suppressed because one or more lines are too long