rough outline
This commit is contained in:
parent
e09b82884b
commit
6386a9ec16
10 changed files with 3 additions and 237 deletions
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using RaiNote.PacketMediator;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PacketMediator.Generator\PacketMediator.Generator.csproj"/>
|
||||
<ProjectReference Include="..\..\RaiNote.PacketMediator\RaiNote.PacketMediator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using PacketMediator.Generator.Tests.Utils;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using RaiNote.PacketMediator;
|
||||
using Xunit;
|
||||
|
||||
namespace PacketMediator.Generator.Tests;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using RaiNote.PacketMediator;
|
||||
using Xunit;
|
||||
|
||||
namespace PacketMediator.Generator.Tests;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
|
||||
<RootNamespace>PacketMediator.Generator</RootNamespace>
|
||||
<PackageId>PacketMediator.Generator</PackageId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
|
@ -1,164 +0,0 @@
|
|||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace PacketMediator.Generator;
|
||||
|
||||
[Generator]
|
||||
public class PacketMediatorGenerator : IIncrementalGenerator {
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context) {
|
||||
context.RegisterPostInitializationOutput(ctx => {
|
||||
ctx.AddSource("PacketMediatorStatic.g.cs", SourceText.From(@"
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RaiNote.PacketMediator;
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
|
||||
public abstract class PacketIdAttribute<TPacketIdEnum> : Attribute where TPacketIdEnum : Enum
|
||||
{
|
||||
protected PacketIdAttribute(TPacketIdEnum code)
|
||||
{
|
||||
Code = code;
|
||||
}
|
||||
|
||||
public TPacketIdEnum Code { get; }
|
||||
}
|
||||
public interface IPacket;
|
||||
public interface IIncomingPacket : IPacket
|
||||
{
|
||||
public void Deserialize(byte[] data);
|
||||
}
|
||||
public interface IOutgoingPacket : IPacket
|
||||
{
|
||||
public byte[] Serialize();
|
||||
}
|
||||
|
||||
public interface IBidirectionalPacket : IOutgoingPacket, IIncomingPacket;
|
||||
|
||||
public interface IPacketHandler<in TIncomingPacket, in TSession> : IPacketHandler<TSession>
|
||||
where TIncomingPacket : IIncomingPacket
|
||||
{
|
||||
async Task<bool> IPacketHandler<TSession>.TryHandleAsync(IIncomingPacket packet, TSession session,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (packet is not TIncomingPacket tPacket)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var activity = new ActivitySource(nameof(PacketMediator)).StartActivity(nameof(HandleAsync));
|
||||
activity?.AddTag(""Handler"", ToString());
|
||||
activity?.AddTag(""Packet"", packet.ToString());
|
||||
await HandleAsync(tPacket, session, cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public Task HandleAsync(TIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IPacketHandler<in TSession>
|
||||
{
|
||||
Task<bool> TryHandleAsync(IIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
", Encoding.UTF8));
|
||||
});
|
||||
// Find all struct declarations
|
||||
var structsWithAttributes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: (node, _) => node is StructDeclarationSyntax structSyntax &&
|
||||
structSyntax.AttributeLists.Count > 0,
|
||||
transform: (syntaxContext, _) => {
|
||||
var structDeclaration = (StructDeclarationSyntax)syntaxContext.Node;
|
||||
var model = syntaxContext.SemanticModel;
|
||||
var symbol = model.GetDeclaredSymbol(structDeclaration) as INamedTypeSymbol;
|
||||
var requiredInterfaces = new[] { "IPacket", "IIncomingPacket", "IOutgoingPacket", "IBidirectionalPacket" };
|
||||
var implementsInterface = symbol != null && symbol.AllInterfaces
|
||||
.Any(i => requiredInterfaces.Contains(i.Name));
|
||||
// Check for the marker attribute
|
||||
var attribute = symbol?.GetAttributes()
|
||||
.FirstOrDefault(attr =>
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
while (attrClass != null)
|
||||
{
|
||||
if (attrClass.Name == "PacketIdAttribute" && attrClass.ContainingNamespace.ToDisplayString() == "PacketMediator.Generator")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
attrClass = attrClass.BaseType;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (attribute == null) {
|
||||
return default;
|
||||
}
|
||||
|
||||
var attributeConstructorArgument = attribute.ConstructorArguments[0];
|
||||
var enumType = attributeConstructorArgument.Type;
|
||||
var enumValue = attributeConstructorArgument.Value;
|
||||
var enumMember = enumType?.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.FirstOrDefault(f => f.ConstantValue?.Equals(enumValue) == true);
|
||||
var enumMaxValue = enumType?.GetMembers()
|
||||
.OfType<IFieldSymbol>().Max(x => x.ConstantValue);
|
||||
return (symbol, structDeclaration.Identifier.Text, value: enumValue,enumType,enumMember,enumMaxValue, implementsInterface);
|
||||
|
||||
})
|
||||
.Where(result => result != default);
|
||||
|
||||
// Collect and generate the dictionary
|
||||
context.RegisterSourceOutput(structsWithAttributes.Collect(), (ctx, result) => {
|
||||
var usedValues = new List<long>();
|
||||
var highestValue = long.Parse(result.FirstOrDefault().enumMaxValue?.ToString() ?? throw new InvalidOperationException());
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("using System.Collections.Generic;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("public static class StructDictionary");
|
||||
sb.AppendLine("{");
|
||||
var enumTypeString = result.FirstOrDefault().enumType?.ToDisplayString();
|
||||
sb.AppendLine($" public static readonly Dictionary<string, {enumTypeString}> Values = new()");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
foreach (var (symbol, structName, value, _,enumMember,_, implementsInterface) in result) {
|
||||
if (!implementsInterface) {
|
||||
var diagnostic = Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
id: "MYGEN001",
|
||||
title: "Struct does not implement required interface",
|
||||
messageFormat: $"The struct '{{0}}' must implement at least one of: \"IPacket\", \"IIncomingPacket\", \"IOutgoingPacket\", \"IBidirectionalPacket\" ",
|
||||
category: "SourceGenerator",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true),
|
||||
symbol?.Locations.FirstOrDefault(), structName);
|
||||
|
||||
ctx.ReportDiagnostic(diagnostic);
|
||||
continue;
|
||||
}
|
||||
var tempVal = long.Parse(value?.ToString() ?? throw new InvalidOperationException());
|
||||
usedValues.Add(tempVal);
|
||||
sb.AppendLine($" {{ \"{structName}{highestValue}\", {enumMember} }},");
|
||||
}
|
||||
|
||||
|
||||
for (long i = 0; i <= highestValue; i++) {
|
||||
if(!usedValues.Contains(i))
|
||||
sb.AppendLine($" {{ \"Dead\", (({enumTypeString}){i}) }},");
|
||||
}
|
||||
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine("}");
|
||||
|
||||
ctx.AddSource("StructDictionary.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"DebugRoslynSourceGenerator": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "../PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
# Roslyn Source Generators Sample
|
||||
|
||||
A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs.
|
||||
|
||||
## Content
|
||||
### PacketMediator.Generator
|
||||
A .NET Standard project with implementations of sample source generators.
|
||||
**You must build this project to see the result (generated code) in the IDE.**
|
||||
|
||||
- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry).
|
||||
- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute.
|
||||
|
||||
### PacketMediator.Generator.Sample
|
||||
A project that references source generators. Note the parameters of `ProjectReference` in [PacketMediator.Generator.Sample.csproj](../PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj), they make sure that the project is referenced as a set of source generators.
|
||||
|
||||
### PacketMediator.Generator.Tests
|
||||
Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests.
|
||||
|
||||
## How To?
|
||||
### How to debug?
|
||||
- Use the [launchSettings.json](Properties/launchSettings.json) profile.
|
||||
- Debug tests.
|
||||
|
||||
### How can I determine which syntax nodes I should expect?
|
||||
Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/).
|
||||
|
||||
### How to learn more about wiring source generators?
|
||||
Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI)
|
||||
The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md).
|
|
@ -1,8 +1,3 @@
|
|||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
Console.WriteLine("Hello, World!");
|
||||
|
||||
Workspace workspace = Workspace.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<Solution>
|
||||
<Project Path="PacketMediator.Generator\PacketMediator.Generator.Sample\PacketMediator.Generator.Sample.csproj" Type="Classic C#" />
|
||||
<Project Path="PacketMediator.Generator\PacketMediator.Generator.Tests\PacketMediator.Generator.Tests.csproj" Type="Classic C#" />
|
||||
<Project Path="PacketMediator.Generator\PacketMediator.Generator\PacketMediator.Generator.csproj" Type="Classic C#" />
|
||||
<Project Path="PacketMediator.Samples\PacketMediator.Samples.csproj" Type="Classic C#" />
|
||||
<Project Path="PacketMediator.Tests\PacketMediator.Tests.csproj" Type="Classic C#" />
|
||||
<Project Path="RaiNote.PacketMediator\RaiNote.PacketMediator.csproj" Type="Classic C#" />
|
||||
|
|
Loading…
Reference in a new issue