Skip to content

Commit 7d5022f

Browse files
committed
More work on source generator for type registration
1 parent 4a1aaf6 commit 7d5022f

File tree

5 files changed

+255
-70
lines changed

5 files changed

+255
-70
lines changed
Binary file not shown.

Packages/com.unity.inputsystem/InputSystem/Editor/Unity.InputSystem.SourceGenerator.dll.meta

Lines changed: 75 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/bin
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Roslyn Incremental Source Generator to support automatic registration of Input System type extensions via
2+
// in-memory generated source performing manual registration of types.
3+
//
4+
// Potential further improvements:
5+
// - Assembly filtering to narrow scope.
6+
// - For ImplementsInterface, consider type.AllInterfaces.Contains(symbol, SymbolEqualityComparer.Default);
7+
// - Generate diagnostic warnings for types implementing interfaces with private visibility?
8+
// - Improve generated type name generation to guarantee no clash.
9+
10+
using System.Collections.Immutable;
11+
using System.Text;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CSharp;
14+
using Microsoft.CodeAnalysis.CSharp.Syntax;
15+
using Microsoft.CodeAnalysis.Text;
16+
17+
namespace Unity.InputSystem.SourceGenerator;
18+
19+
/// <summary>
20+
/// Syntax and symbol helpers.
21+
/// </summary>
22+
static class Helpers
23+
{
24+
public static bool IsEffectivelyPublic(INamedTypeSymbol type)
25+
{
26+
// The type itself must be public
27+
if (type.DeclaredAccessibility != Accessibility.Public)
28+
return false;
29+
30+
// Every containing type must also be public
31+
for (var container = type.ContainingType; container is not null; container = container.ContainingType)
32+
{
33+
if (container.DeclaredAccessibility != Accessibility.Public)
34+
return false;
35+
}
36+
37+
return true;
38+
}
39+
40+
public static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol)
41+
=> type.AllInterfaces.Contains(interfaceSymbol);
42+
43+
public static bool ExtendsClass(INamedTypeSymbol type, INamedTypeSymbol baseSymbol)
44+
=> SymbolEqualityComparer.Default.Equals(type, baseSymbol);
45+
46+
public static bool IsOrInheritsFrom(INamedTypeSymbol type, INamedTypeSymbol baseSymbol)
47+
{
48+
// If you *don't* want to match A itself, start from type.BaseType instead.
49+
for (var current = type; current is not null; current = current.BaseType)
50+
{
51+
if (SymbolEqualityComparer.Default.Equals(current, baseSymbol))
52+
return true;
53+
}
54+
55+
return false;
56+
}
57+
}
58+
59+
[Generator]
60+
public class InterfaceTypeRegistrationGenerator : IIncrementalGenerator
61+
{
62+
internal const string RegistrationTemplateBegin = @"using System;
63+
using UnityEngine;
64+
using UnityEngine.InputSystem;
65+
66+
#if UNITY_EDITOR
67+
[UnityEditor.InitializeOnLoad]
68+
#endif
69+
class @C
70+
{
71+
#if UNITY_EDITOR
72+
static @C() { Register(); }
73+
#endif
74+
75+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
76+
static void Register()
77+
{
78+
Debug.Log(""Auto-registering @T via source generated type @C"");";
79+
80+
internal const string RegistrationTemplateEnd = @"
81+
}
82+
}
83+
";
84+
85+
private readonly string _interface;
86+
private readonly string _template;
87+
private readonly System.Func<INamedTypeSymbol, INamedTypeSymbol, bool> _accept;
88+
89+
protected InterfaceTypeRegistrationGenerator(string @interface, string template,
90+
System.Func<INamedTypeSymbol, INamedTypeSymbol, bool> accept)
91+
{
92+
_interface = @interface;
93+
_template = template;
94+
_accept = accept;
95+
}
96+
97+
public void Initialize(IncrementalGeneratorInitializationContext context)
98+
{
99+
// Filter syntax
100+
var typeDeclarations = context.SyntaxProvider
101+
.CreateSyntaxProvider(
102+
static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax,
103+
static (ctx, _) => (TypeDeclarationSyntax)ctx.Node)
104+
.Where(t => t is not null);
105+
106+
// Combine syntax with compilation so we can do symbol checks
107+
var candidateTypes = context.CompilationProvider.Combine(typeDeclarations.Collect());
108+
109+
// Finally, register source output generator
110+
context.RegisterSourceOutput(candidateTypes, (spc, source) =>
111+
{
112+
var (compilation, typeDecls) = source;
113+
Process(spc, compilation, typeDecls, _interface, _template, _accept);
114+
});
115+
}
116+
117+
private static void Process(SourceProductionContext context, Compilation compilation,
118+
ImmutableArray<TypeDeclarationSyntax> declarations, string @interface, string template,
119+
System.Func<INamedTypeSymbol, INamedTypeSymbol, bool> accept)
120+
{
121+
var interfaceSymbol = compilation.GetTypeByMetadataName(@interface);
122+
if (interfaceSymbol is null)
123+
return; // symbol not in this compilation
124+
125+
foreach (var decl in declarations)
126+
{
127+
// Get semantic model
128+
var model = compilation.GetSemanticModel(decl.SyntaxTree);
129+
if (model.GetDeclaredSymbol(decl) is not { } typeSymbol)
130+
continue;
131+
132+
// Skip if we shouldn't accept type
133+
if (!accept(typeSymbol, interfaceSymbol))
134+
continue;
135+
136+
// Generate type registration code and add to source
137+
var source = GenerateFor(typeSymbol, template);
138+
context.AddSource($"{typeSymbol.Name}_Generated.g.cs", SourceText.From(source, Encoding.UTF8));
139+
}
140+
}
141+
142+
143+
144+
private static string GenerateFor(INamedTypeSymbol type, string template)
145+
{
146+
var fullyQualifiedTypeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
147+
return template.Replace("@C", type.Name + "Registration").Replace("@T", fullyQualifiedTypeName);
148+
}
149+
}
150+
151+
/// <summary>
152+
/// Source generator that registers public types implementing IInputProcessor.
153+
/// </summary>
154+
[Generator]
155+
public sealed class ProcessorRegistration() : InterfaceTypeRegistrationGenerator(Base, Template,
156+
static (symbol, baseSymbol) => Helpers.IsEffectivelyPublic(symbol) &&
157+
Helpers.IsOrInheritsFrom(symbol, baseSymbol))
158+
{
159+
private const string Base = "UnityEngine.InputSystem.InputProcessor";
160+
161+
private const string Template = RegistrationTemplateBegin +
162+
" InputSystem.RegisterProcessor(typeof(@T));" +
163+
RegistrationTemplateEnd;
164+
}
165+
166+
/// <summary>
167+
/// Source generator that registers public types implementing IInputInteraction.
168+
/// </summary>
169+
[Generator]
170+
public sealed class InteractionRegistration() : InterfaceTypeRegistrationGenerator(Interface, Template,
171+
static (symbol, @interface) => Helpers.IsEffectivelyPublic(symbol) &&
172+
Helpers.ImplementsInterface(symbol, @interface))
173+
{
174+
private const string Interface = "UnityEngine.InputSystem.IInputInteraction";
175+
176+
private const string Template = RegistrationTemplateBegin +
177+
" InputSystem.RegisterInteraction(typeof(@T));" +
178+
RegistrationTemplateEnd;
179+
}

Tools/Unity.InputSystem.SourceGenerator/TypeRegistrationGenerator.cs

Lines changed: 0 additions & 70 deletions
This file was deleted.

0 commit comments

Comments
 (0)