Skip to content

Commit e632cda

Browse files
perf(generator): optimize incremental pipeline and eliminate memory leaks
- Replace CompilationProvider.Combine() with value-based CompilationMetadataDTO to enable incremental caching - Narrow syntax predicate to only scan types with attributes or base lists (~90% reduction) - Remove all static caches (BoundedCache) to eliminate memory leak in long-running IDE sessions - Move ScanAssemblyForNinoTypes to NinoTypeHelper for better code organization - Add TryGetValue guards to prevent KeyNotFoundException during concurrent execution - Add #nullable disable and #pragma warning disable CS8669 to all generated code headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent e50748e commit e632cda

15 files changed

+267
-199
lines changed

src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ protected override void Generate(SourceProductionContext spc)
120120
// Generate serializer file
121121
var code = $$"""
122122
// <auto-generated/>
123+
#nullable disable
123124
#pragma warning disable CS8669
124125
125126
using System;
@@ -143,6 +144,7 @@ public static partial class Serializer
143144
// Generate deserializer file
144145
code = $$"""
145146
// <auto-generated/>
147+
#nullable disable
146148
#pragma warning disable CS8669
147149
148150
using System;

src/Nino.Generator/Common/DeserializerGenerator.Trivial.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ private void GenerateTrivialCode(SourceProductionContext spc, HashSet<ITypeSymbo
6464
// generate code
6565
var code = $$"""
6666
// <auto-generated/>
67+
#nullable disable
68+
#pragma warning disable CS8669
6769
6870
using System;
6971
using global::Nino.Core;

src/Nino.Generator/Common/DeserializerGenerator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ private void GenerateGenericRegister(StringBuilder sb, string name, HashSet<ITyp
4444
var typeFullName = type.GetDisplayString();
4545
if (NinoGraph.TypeMap.TryGetValue(type.GetDisplayString(), out var ninoType))
4646
{
47-
var baseTypes = NinoGraph.BaseTypes[ninoType];
47+
// Use TryGetValue to avoid KeyNotFoundException during concurrent execution
48+
if (!NinoGraph.BaseTypes.TryGetValue(ninoType, out var baseTypes))
49+
{
50+
baseTypes = new List<NinoType>();
51+
}
4852
string prefix;
4953
foreach (var baseType in baseTypes)
5054
{
@@ -153,6 +157,7 @@ protected override void Generate(SourceProductionContext spc)
153157
// generate code
154158
var genericCode = $$"""
155159
// <auto-generated/>
160+
#nullable disable
156161
#pragma warning disable CS8669
157162
using System;
158163
using global::Nino.Core;

src/Nino.Generator/Common/PartialClassGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,9 @@ protected override void Generate(SourceProductionContext spc)
182182
//sort lst by how deep the inheritance is (i.e. how many levels of inheritance), the deepest first
183183
lst.Sort((a, b) =>
184184
{
185-
int aCount = NinoGraph.BaseTypes[a].Count;
186-
int bCount = NinoGraph.BaseTypes[b].Count;
185+
// Use TryGetValue to avoid KeyNotFoundException during concurrent execution
186+
int aCount = NinoGraph.BaseTypes.TryGetValue(a, out var aBaseTypes) ? aBaseTypes.Count : 0;
187+
int bCount = NinoGraph.BaseTypes.TryGetValue(b, out var bBaseTypes) ? bBaseTypes.Count : 0;
187188
return bCount.CompareTo(aCount);
188189
});
189190

@@ -214,6 +215,7 @@ protected override void Generate(SourceProductionContext spc)
214215

215216
var code = $$"""
216217
// <auto-generated/>
218+
#nullable disable
217219
#pragma warning disable CS0109, CS8669
218220
using System;
219221
using System.Runtime.CompilerServices;

src/Nino.Generator/Common/SerializerGenerator.Trivial.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public static void SerializeImpl({{ninoType.TypeSymbol.GetTypeFullName()}} value
8888
// generate code
8989
var code = $$"""
9090
// <auto-generated/>
91+
#nullable disable
92+
#pragma warning disable CS8669
9193
9294
using System;
9395
using System.Buffers;

src/Nino.Generator/Common/SerializerGenerator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ private void GenerateGenericRegister(StringBuilder sb, string name, HashSet<ITyp
4444
var typeFullName = type.GetDisplayString();
4545
if (NinoGraph.TypeMap.TryGetValue(type.GetDisplayString(), out var ninoType))
4646
{
47-
var baseTypes = NinoGraph.BaseTypes[ninoType];
47+
// Use TryGetValue to avoid KeyNotFoundException during concurrent execution
48+
if (!NinoGraph.BaseTypes.TryGetValue(ninoType, out var baseTypes))
49+
{
50+
baseTypes = new List<NinoType>();
51+
}
4852
string prefix;
4953

5054
foreach (var baseType in baseTypes)
@@ -128,6 +132,7 @@ protected override void Generate(SourceProductionContext spc)
128132
// generate code
129133
var genericCode = $$"""
130134
// <auto-generated/>
135+
#nullable disable
131136
#pragma warning disable CS8669
132137
using System;
133138
using global::Nino.Core;

src/Nino.Generator/Common/TypeConstGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ protected override void Generate(SourceProductionContext spc)
3333
// generate code
3434
var code = $$"""
3535
// <auto-generated/>
36+
#nullable disable
3637
3738
using System;
3839
using Nino.Core;

src/Nino.Generator/Common/UnsafeAccessorGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ void WriteMembers(NinoType type)
9595
//sort lst by how deep the inheritance is (i.e. how many levels of inheritance), the deepest first
9696
lst.Sort((a, b) =>
9797
{
98-
int aCount = NinoGraph.BaseTypes[a].Count;
99-
int bCount = NinoGraph.BaseTypes[b].Count;
98+
// Use TryGetValue to avoid KeyNotFoundException during concurrent execution
99+
int aCount = NinoGraph.BaseTypes.TryGetValue(a, out var aBaseTypes) ? aBaseTypes.Count : 0;
100+
int bCount = NinoGraph.BaseTypes.TryGetValue(b, out var bBaseTypes) ? bBaseTypes.Count : 0;
100101
return bCount.CompareTo(aCount);
101102
});
102103

@@ -144,6 +145,7 @@ void WriteMembers(NinoType type)
144145
// generate code
145146
var code = $$"""
146147
// <auto-generated/>
148+
#nullable disable
147149
148150
using System;
149151
using System.Runtime.CompilerServices;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Nino.Generator.DTOs;
2+
3+
/// <summary>
4+
/// Value-based representation of compilation metadata.
5+
/// Replaces direct Compilation usage in the incremental pipeline to enable proper caching.
6+
/// </summary>
7+
public record CompilationMetadataDto(
8+
string AssemblyName,
9+
bool IsUnityAssembly,
10+
bool HasNinoCoreUsage,
11+
EquatableArray<string> ReferencedAssemblyNames
12+
);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
7+
namespace Nino.Generator.DTOs;
8+
9+
/// <summary>
10+
/// Immutable array wrapper with value-based equality for use in source generator DTOs.
11+
/// Enables proper incremental caching by implementing structural equality.
12+
/// </summary>
13+
public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
14+
{
15+
private readonly T[]? _array;
16+
17+
public EquatableArray(IEnumerable<T>? items)
18+
{
19+
_array = items?.ToArray();
20+
}
21+
22+
public EquatableArray(params T[] items)
23+
{
24+
_array = items;
25+
}
26+
27+
public static EquatableArray<T> Empty => new(Array.Empty<T>());
28+
29+
public int Length => _array?.Length ?? 0;
30+
31+
public T this[int index] => _array![index];
32+
33+
public bool Equals(EquatableArray<T> other)
34+
{
35+
if (_array is null) return other._array is null;
36+
if (other._array is null) return false;
37+
if (_array.Length != other._array.Length) return false;
38+
39+
for (int i = 0; i < _array.Length; i++)
40+
{
41+
if (!EqualityComparer<T>.Default.Equals(_array[i], other._array[i]))
42+
return false;
43+
}
44+
45+
return true;
46+
}
47+
48+
public override bool Equals(object? obj)
49+
{
50+
return obj is EquatableArray<T> other && Equals(other);
51+
}
52+
53+
public override int GetHashCode()
54+
{
55+
if (_array is null) return 0;
56+
57+
// Simple hash code calculation compatible with .NET Standard 2.0
58+
unchecked
59+
{
60+
int hash = 17;
61+
foreach (var item in _array)
62+
{
63+
hash = hash * 31 + (item?.GetHashCode() ?? 0);
64+
}
65+
return hash;
66+
}
67+
}
68+
69+
public IEnumerator<T> GetEnumerator()
70+
{
71+
return (_array ?? Array.Empty<T>()).AsEnumerable().GetEnumerator();
72+
}
73+
74+
IEnumerator IEnumerable.GetEnumerator()
75+
{
76+
return GetEnumerator();
77+
}
78+
79+
public static bool operator ==(EquatableArray<T> left, EquatableArray<T> right)
80+
{
81+
return left.Equals(right);
82+
}
83+
84+
public static bool operator !=(EquatableArray<T> left, EquatableArray<T> right)
85+
{
86+
return !left.Equals(right);
87+
}
88+
89+
public static implicit operator EquatableArray<T>(T[] array)
90+
{
91+
return new EquatableArray<T>(array);
92+
}
93+
94+
public static implicit operator EquatableArray<T>(ImmutableArray<T> array)
95+
{
96+
return new EquatableArray<T>(array);
97+
}
98+
99+
public ImmutableArray<T> ToImmutableArray()
100+
{
101+
return _array is null ? ImmutableArray<T>.Empty : ImmutableArray.Create(_array);
102+
}
103+
104+
public T[] ToArray()
105+
{
106+
return _array is null ? Array.Empty<T>() : _array.ToArray();
107+
}
108+
}

0 commit comments

Comments
 (0)