Skip to content

Commit 5c0b06c

Browse files
Linwenxuan04NoirHare
authored andcommitted
[Core] Drop reflection and use srcgen for the services, messages and events
1 parent 5350e3b commit 5c0b06c

12 files changed

Lines changed: 739 additions & 141 deletions
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using System.Text;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
namespace Lagrange.Core.Generator;
9+
10+
[Generator(LanguageNames.CSharp)]
11+
public sealed class EventLogicSourceGenerator : IIncrementalGenerator
12+
{
13+
private const string LogicInterfaceFullName = "Lagrange.Core.Internal.Logic.ILogic";
14+
private const string EventSubscribeAttributeNamespace = "Lagrange.Core.Internal.Events";
15+
private const string EventSubscribeAttributeName = "EventSubscribeAttribute";
16+
17+
private static readonly SymbolDisplayFormat TypeDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat;
18+
19+
public void Initialize(IncrementalGeneratorInitializationContext context)
20+
{
21+
var candidates = context.SyntaxProvider.CreateSyntaxProvider(
22+
static (node, _) => node is ClassDeclarationSyntax { BaseList: not null },
23+
static (context, _) => ToEventLogicInfo(context)
24+
);
25+
26+
var logics = candidates.SelectMany(static (logic, _) =>
27+
logic.HasValue ? [logic.GetValueOrDefault()] : ImmutableArray<EventLogicInfo>.Empty);
28+
29+
context.RegisterSourceOutput(logics.Collect(), static (context, logics) => Output(context, logics));
30+
}
31+
32+
private static EventLogicInfo? ToEventLogicInfo(GeneratorSyntaxContext context)
33+
{
34+
if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol { TypeKind: TypeKind.Class } typeSymbol || typeSymbol.IsAbstract) return null;
35+
36+
var logicInterface = context.SemanticModel.Compilation.GetTypeByMetadataName(LogicInterfaceFullName);
37+
if (logicInterface is null || !typeSymbol.AllInterfaces.Any(type => SymbolEqualityComparer.Default.Equals(type, logicInterface))) return null;
38+
39+
var events = ImmutableArray.CreateBuilder<EventSubscriptionInfo>();
40+
foreach (var attribute in typeSymbol.GetAttributes())
41+
{
42+
if (TryGetEventSubscription(attribute, out var subscription))
43+
{
44+
events.Add(subscription);
45+
}
46+
}
47+
48+
var location = typeSymbol.Locations.FirstOrDefault(static location => location.IsInSource);
49+
string sortPath = location?.SourceTree?.FilePath ?? string.Empty;
50+
int sortStart = location?.SourceSpan.Start ?? 0;
51+
52+
return new EventLogicInfo(
53+
typeSymbol.ToDisplayString(TypeDisplayFormat),
54+
events.ToImmutable(),
55+
sortPath,
56+
sortStart
57+
);
58+
}
59+
60+
private static bool TryGetEventSubscription(AttributeData attribute, out EventSubscriptionInfo subscription)
61+
{
62+
subscription = default;
63+
64+
if (attribute.AttributeClass is not { Name: EventSubscribeAttributeName } attributeClass || attributeClass.ContainingNamespace.ToDisplayString() != EventSubscribeAttributeNamespace)
65+
{
66+
return false;
67+
}
68+
69+
ITypeSymbol? eventType;
70+
if (attributeClass.Arity == 1)
71+
{
72+
eventType = attributeClass.TypeArguments[0];
73+
}
74+
else
75+
{
76+
if (attribute.ConstructorArguments.Length == 0) return false;
77+
eventType = attribute.ConstructorArguments[0].Value as ITypeSymbol;
78+
}
79+
80+
if (eventType is null) return false;
81+
82+
subscription = new EventSubscriptionInfo(eventType.ToDisplayString(TypeDisplayFormat));
83+
return true;
84+
}
85+
86+
private static void Output(SourceProductionContext context, ImmutableArray<EventLogicInfo> logics)
87+
{
88+
var orderedLogics = logics
89+
.OrderBy(static logic => logic.SortPath, StringComparer.Ordinal)
90+
.ThenBy(static logic => logic.SortStart)
91+
.ThenBy(static logic => logic.LogicType)
92+
.ToList();
93+
94+
var source = new StringBuilder();
95+
source.AppendLine("#nullable enable");
96+
source.AppendLine();
97+
source.AppendLine("using System.Collections.Frozen;");
98+
source.AppendLine();
99+
source.AppendLine("namespace Lagrange.Core.Internal.Context;");
100+
source.AppendLine();
101+
source.AppendLine("internal static class EventLogicRegistry");
102+
source.AppendLine("{");
103+
source.AppendLine(" internal static (global::System.Collections.Frozen.FrozenDictionary<global::System.Type, global::System.Collections.Generic.List<global::Lagrange.Core.Internal.Logic.ILogic>> Events, global::System.Collections.Frozen.FrozenDictionary<global::System.Type, global::Lagrange.Core.Internal.Logic.ILogic> Logics) Create(global::Lagrange.Core.BotContext context)");
104+
source.AppendLine(" {");
105+
source.AppendLine(" var events = new global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Collections.Generic.List<global::Lagrange.Core.Internal.Logic.ILogic>>();");
106+
source.AppendLine(" var logics = new global::System.Collections.Generic.Dictionary<global::System.Type, global::Lagrange.Core.Internal.Logic.ILogic>();");
107+
source.AppendLine();
108+
109+
for (int i = 0; i < orderedLogics.Count; i++)
110+
{
111+
source.Append(" Register").Append(i).AppendLine("(context, events, logics);");
112+
}
113+
114+
source.AppendLine();
115+
source.AppendLine(" return (events.ToFrozenDictionary(), logics.ToFrozenDictionary());");
116+
source.AppendLine(" }");
117+
source.AppendLine();
118+
source.AppendLine(" private static void AddEvent(global::System.Type eventType, global::Lagrange.Core.Internal.Logic.ILogic logic, global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Collections.Generic.List<global::Lagrange.Core.Internal.Logic.ILogic>> events)");
119+
source.AppendLine(" {");
120+
source.AppendLine(" if (!events.TryGetValue(eventType, out var list))");
121+
source.AppendLine(" {");
122+
source.AppendLine(" list = [];");
123+
source.AppendLine(" events.Add(eventType, list);");
124+
source.AppendLine(" }");
125+
source.AppendLine();
126+
source.AppendLine(" list.Add(logic);");
127+
source.AppendLine(" }");
128+
129+
for (int i = 0; i < orderedLogics.Count; i++)
130+
{
131+
AppendRegisterMethod(source, i, orderedLogics[i]);
132+
}
133+
134+
source.AppendLine("}");
135+
136+
context.AddSource("Lagrange.Core.Internal.Context.EventLogicRegistry.g.cs", source.ToString());
137+
}
138+
139+
private static void AppendRegisterMethod(StringBuilder source, int index, EventLogicInfo logic)
140+
{
141+
source.AppendLine();
142+
source.Append(" private static void Register").Append(index).AppendLine("(global::Lagrange.Core.BotContext context, global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Collections.Generic.List<global::Lagrange.Core.Internal.Logic.ILogic>> events, global::System.Collections.Generic.Dictionary<global::System.Type, global::Lagrange.Core.Internal.Logic.ILogic> logics)");
143+
source.AppendLine(" {");
144+
source.Append(" var logic = new ").Append(logic.LogicType).AppendLine("(context);");
145+
146+
foreach (var subscription in logic.Subscriptions)
147+
{
148+
source.Append(" AddEvent(typeof(").Append(subscription.EventType).AppendLine("), logic, events);");
149+
}
150+
151+
source.Append(" logics[typeof(").Append(logic.LogicType).AppendLine(")] = logic;");
152+
source.AppendLine(" }");
153+
}
154+
155+
private readonly record struct EventLogicInfo(
156+
string LogicType,
157+
ImmutableArray<EventSubscriptionInfo> Subscriptions,
158+
string SortPath,
159+
int SortStart
160+
);
161+
162+
private readonly record struct EventSubscriptionInfo(string EventType);
163+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace System.Runtime.CompilerServices;
2+
3+
internal static class IsExternalInit;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netstandard2.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<LangVersion>12.0</LangVersion>
6+
<AnalyzerLanguage>cs</AnalyzerLanguage>
7+
<DevelopmentDependency>true</DevelopmentDependency>
8+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
9+
<IsPackable>false</IsPackable>
10+
<IsRoslynComponent>true</IsRoslynComponent>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
19+
</ItemGroup>
20+
</Project>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using System.Text;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
namespace Lagrange.Core.Generator;
9+
10+
[Generator(LanguageNames.CSharp)]
11+
public sealed class MessageEntitySourceGenerator : IIncrementalGenerator
12+
{
13+
private const string MessageEntityInterfaceFullName = "Lagrange.Core.Message.Entities.IMessageEntity";
14+
15+
private static readonly SymbolDisplayFormat TypeDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat;
16+
17+
public void Initialize(IncrementalGeneratorInitializationContext context)
18+
{
19+
var candidates = context.SyntaxProvider.CreateSyntaxProvider(
20+
static (node, _) => node is ClassDeclarationSyntax { BaseList: not null },
21+
static (context, _) => ToMessageEntityInfo(context)
22+
);
23+
24+
var entities = candidates.SelectMany(static (entity, _) =>
25+
entity.HasValue ? [entity.GetValueOrDefault()] : ImmutableArray<MessageEntityInfo>.Empty);
26+
27+
context.RegisterSourceOutput(entities.Collect(), static (context, entities) => Output(context, entities));
28+
}
29+
30+
private static MessageEntityInfo? ToMessageEntityInfo(GeneratorSyntaxContext context)
31+
{
32+
if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol { TypeKind: TypeKind.Class } typeSymbol || typeSymbol.IsAbstract) return null;
33+
34+
var messageEntityInterface = context.SemanticModel.Compilation.GetTypeByMetadataName(MessageEntityInterfaceFullName);
35+
if (messageEntityInterface is null || !typeSymbol.AllInterfaces.Any(type => SymbolEqualityComparer.Default.Equals(type, messageEntityInterface))) return null;
36+
37+
var location = typeSymbol.Locations.FirstOrDefault(static location => location.IsInSource);
38+
string sortPath = location?.SourceTree?.FilePath ?? string.Empty;
39+
int sortStart = location?.SourceSpan.Start ?? 0;
40+
41+
return new MessageEntityInfo(
42+
typeSymbol.ToDisplayString(TypeDisplayFormat),
43+
sortPath,
44+
sortStart
45+
);
46+
}
47+
48+
private static void Output(SourceProductionContext context, ImmutableArray<MessageEntityInfo> entities)
49+
{
50+
var orderedEntities = entities
51+
.OrderBy(static entity => entity.SortPath, StringComparer.Ordinal)
52+
.ThenBy(static entity => entity.SortStart)
53+
.ThenBy(static entity => entity.EntityType)
54+
.ToList();
55+
56+
var source = new StringBuilder();
57+
source.AppendLine("#nullable enable");
58+
source.AppendLine();
59+
source.AppendLine("namespace Lagrange.Core.Message;");
60+
source.AppendLine();
61+
source.AppendLine("internal static class MessageEntityRegistry");
62+
source.AppendLine("{");
63+
source.AppendLine(" internal static global::System.Collections.Generic.List<global::Lagrange.Core.Message.Entities.IMessageEntity> Create()");
64+
source.AppendLine(" {");
65+
source.Append(" var entities = new global::System.Collections.Generic.List<global::Lagrange.Core.Message.Entities.IMessageEntity>(").Append(orderedEntities.Count).AppendLine(");");
66+
67+
foreach (var entity in orderedEntities)
68+
{
69+
source.Append(" entities.Add(new ").Append(entity.EntityType).AppendLine("());");
70+
}
71+
72+
source.AppendLine(" return entities;");
73+
source.AppendLine(" }");
74+
source.AppendLine("}");
75+
76+
context.AddSource("Lagrange.Core.Message.MessageEntityRegistry.g.cs", source.ToString());
77+
}
78+
79+
private readonly record struct MessageEntityInfo(string EntityType, string SortPath, int SortStart);
80+
}

0 commit comments

Comments
 (0)