Implement handshake Hello NetControlMessage, recv and send
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Prospect.Unreal.Generator
|
||||
{
|
||||
internal class NetControlMessageEntry
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public List<string> Params { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Prospect.Unreal.Generator
|
||||
{
|
||||
internal class NetControlMessageParser
|
||||
{
|
||||
internal IReadOnlyList<NetControlMessageEntry> Parse(ImmutableArray<AttributeSyntax> attributes)
|
||||
{
|
||||
var entries = new List<NetControlMessageEntry>();
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
var attributeParams = attribute.ArgumentList.Arguments;
|
||||
var attributeEntry = new NetControlMessageEntry();
|
||||
|
||||
attributeEntry.Name = ((LiteralExpressionSyntax)attributeParams[0].Expression).Token.ValueText; // StringLiteralExpression
|
||||
attributeEntry.Index = (int)((LiteralExpressionSyntax)attributeParams[1].Expression).Token.Value; // NumericLiteralExpression
|
||||
|
||||
for (int i = 2; i < attributeParams.Count; i++)
|
||||
{
|
||||
var paramType = ((TypeOfExpressionSyntax)attributeParams[i].Expression);
|
||||
var paramKeyword = paramType.Type;
|
||||
if (paramKeyword is IdentifierNameSyntax paramIdentifierName)
|
||||
{
|
||||
attributeEntry.Params.Add(paramIdentifierName.Identifier.ValueText);
|
||||
}
|
||||
else if (paramKeyword is PredefinedTypeSyntax predefinedTypeSyntax)
|
||||
{
|
||||
attributeEntry.Params.Add(predefinedTypeSyntax.Keyword.ValueText);
|
||||
}
|
||||
}
|
||||
|
||||
entries.Add(attributeEntry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Prospect.Unreal.Generator.Util;
|
||||
using Scriban;
|
||||
using Scriban.Runtime;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Prospect.Unreal.Generator
|
||||
{
|
||||
[Generator]
|
||||
internal class NetControlMessageSourceIncrementalGenerator : IIncrementalGenerator
|
||||
{
|
||||
public const string Namespace = "Prospect.Unreal.Net.Packets.Control";
|
||||
public const string AttributeName = "NetControlMessage";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var attributes = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: IsSyntaxTargetForGeneration,
|
||||
transform: GetSemanticTargetForGeneration)
|
||||
.Where(static m => m is not null);
|
||||
|
||||
var compilationAndAttributes = context.CompilationProvider.Combine(attributes.Collect());
|
||||
|
||||
context.RegisterSourceOutput(compilationAndAttributes, static (spc, source) => Execute(source.Left, source.Right, spc));
|
||||
}
|
||||
|
||||
private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) =>
|
||||
node is AttributeSyntax attribute && attribute.Name is IdentifierNameSyntax;
|
||||
|
||||
private static AttributeSyntax GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken _)
|
||||
{
|
||||
var attribute = (AttributeSyntax)context.Node;
|
||||
var attributeName = (IdentifierNameSyntax)attribute.Name;
|
||||
|
||||
if (attributeName.Identifier.ValueText != AttributeName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation compilation, ImmutableArray<AttributeSyntax> attributes, SourceProductionContext context)
|
||||
{
|
||||
var parser = new NetControlMessageParser();
|
||||
var results = parser.Parse(attributes);
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.AddSource("NMTEnum.g.cs", GenerateEnum(results));
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
context.AddSource($"NMT_{result.Name}.g.cs", GenerateMessage(result));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateEnum(IReadOnlyList<NetControlMessageEntry> entries)
|
||||
{
|
||||
var templateData = EmbeddedResource.GetContent("Templates/NMTEnum.sbntxt");
|
||||
var template = Template.Parse(templateData);
|
||||
|
||||
return template.Render(new
|
||||
{
|
||||
cnamespace = Namespace,
|
||||
entries = entries
|
||||
}, member => member.Name).Trim();
|
||||
}
|
||||
|
||||
private static string GenerateMessage(NetControlMessageEntry entry)
|
||||
{
|
||||
var scriptObject = new ScriptObject();
|
||||
|
||||
scriptObject.Import(typeof(ScribanFunctions));
|
||||
scriptObject.Add("cnamespace", Namespace);
|
||||
scriptObject.Add("entry", entry);
|
||||
|
||||
var context = new TemplateContext();
|
||||
|
||||
context.PushGlobal(scriptObject);
|
||||
|
||||
var templateData = EmbeddedResource.GetContent("Templates/NMTMessage.sbntxt");
|
||||
var template = Template.Parse(templateData);
|
||||
|
||||
return template.Render(scriptObject, member => member.Name).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ScribanFunctions
|
||||
{
|
||||
[ScriptMemberIgnore]
|
||||
private static readonly Dictionary<string, ParamDef> TypeDefinitions = new Dictionary<string, ParamDef>
|
||||
{
|
||||
{ "byte", new ParamDef("bunch.ReadByte()", "bunch.WriteByte({0})") },
|
||||
{ "int", new ParamDef("bunch.ReadInt32()", "bunch.WriteInt32({0})") },
|
||||
{ "uint", new ParamDef("bunch.ReadUInt32()", "bunch.WriteUInt32({0})") },
|
||||
{ "FString", new ParamDef("bunch.ReadString()", "bunch.WriteString({0})") },
|
||||
};
|
||||
|
||||
public static string SendParams(List<string> args)
|
||||
{
|
||||
return MethodParameter(args);
|
||||
}
|
||||
|
||||
public static string ReadOutParams(List<string> args)
|
||||
{
|
||||
return MethodParameter(args, "out");
|
||||
}
|
||||
|
||||
public static string ReadOut(string paramType, int index)
|
||||
{
|
||||
var paramName = (char)('a' + index);
|
||||
var paramRead = ReadType(paramType);
|
||||
if (paramRead.StartsWith("throw")) {
|
||||
return paramRead;
|
||||
}
|
||||
|
||||
return $"{paramName} = {paramRead}";
|
||||
}
|
||||
|
||||
public static string SendType(string paramType, int index)
|
||||
{
|
||||
if (TypeDefinitions.TryGetValue(paramType, out var type))
|
||||
{
|
||||
return string.Format(type.Write, (char)('a' + index));
|
||||
}
|
||||
|
||||
return $"throw new NotImplementedException(\"Unsupported type {paramType}\")";
|
||||
}
|
||||
|
||||
public static string ReadType(string param)
|
||||
{
|
||||
if (TypeDefinitions.TryGetValue(param, out var type))
|
||||
{
|
||||
return type.Read;
|
||||
}
|
||||
|
||||
return $"throw new NotImplementedException(\"Unsupported type {param}\")";
|
||||
}
|
||||
|
||||
[ScriptMemberIgnore]
|
||||
private static string MethodParameter(List<string> args, string prefix = null)
|
||||
{
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < args.Count; i++)
|
||||
{
|
||||
if (i < args.Count)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
var paramName = (char)('a' + i);
|
||||
var paramType = args[i];
|
||||
if (paramType == "FString")
|
||||
{
|
||||
paramType = "string";
|
||||
}
|
||||
|
||||
if (prefix != null)
|
||||
{
|
||||
builder.AppendFormat("{0} {1} {2}", prefix, paramType, paramName);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendFormat("{0} {1}", paramType, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Prospect.Unreal.Generator
|
||||
{
|
||||
internal class NetControlMessageSyntaxReceiver : ISyntaxContextReceiver
|
||||
{
|
||||
public const string AttributeName = "NetControlMessage";
|
||||
|
||||
public List<NetControlMessageEntry> Entries { get; } = new List<NetControlMessageEntry>();
|
||||
|
||||
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
|
||||
{
|
||||
if (context.Node is not AttributeSyntax attribute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (attribute.Name is not IdentifierNameSyntax identifierName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (identifierName.Identifier.ValueText != AttributeName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attributeParams = attribute.ArgumentList.Arguments;
|
||||
var attributeEntry = new NetControlMessageEntry();
|
||||
|
||||
attributeEntry.Name = ((LiteralExpressionSyntax)attributeParams[0].Expression).Token.ValueText; // StringLiteralExpression
|
||||
attributeEntry.Index = (int) ((LiteralExpressionSyntax)attributeParams[1].Expression).Token.Value; // NumericLiteralExpression
|
||||
|
||||
for (int i = 2; i < attributeParams.Count; i++)
|
||||
{
|
||||
var paramType = ((TypeOfExpressionSyntax)attributeParams[i].Expression);
|
||||
var paramKeyword = paramType.Type;
|
||||
if (paramKeyword is IdentifierNameSyntax paramIdentifierName)
|
||||
{
|
||||
attributeEntry.Params.Add(paramIdentifierName.Identifier.ValueText);
|
||||
}
|
||||
else if (paramKeyword is PredefinedTypeSyntax predefinedTypeSyntax)
|
||||
{
|
||||
attributeEntry.Params.Add(predefinedTypeSyntax.Keyword.ValueText);
|
||||
}
|
||||
}
|
||||
|
||||
Entries.Add(attributeEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Roslyn": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "..\\Prospect.Unreal\\Prospect.Unreal.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Templates\*.sbntxt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Templates\NMTMessage.sbntxt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Scriban" Version="2.1.2" GeneratePathProperty="true" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="GetDependencyTargetPaths">
|
||||
<ItemGroup>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGScriban)\lib\netstandard2.0\Scriban.dll" IncludeRuntimeDependency="false" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace {{ cnamespace }};
|
||||
|
||||
public enum NMT
|
||||
{
|
||||
{{~ for entry in entries ~}}
|
||||
{{ entry.Name }} = {{ entry.Index }},
|
||||
{{~ end ~}}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Prospect.Unreal.Core;
|
||||
using Prospect.Unreal.Net;
|
||||
using Prospect.Unreal.Net.Packets.Bunch;
|
||||
|
||||
namespace {{ cnamespace }};
|
||||
|
||||
public static class NMT_{{ entry.Name }}
|
||||
{
|
||||
public static void Send(UNetConnection conn{{send_params entry.Params}})
|
||||
{
|
||||
if (conn.Channels[0] != null && !conn.Channels[0].Closing)
|
||||
{
|
||||
using var bunch = new FControlChannelOutBunch(conn.Channels[0], false);
|
||||
bunch.WriteByte({{ entry.Index }});
|
||||
{{~ for param in entry.Params ~}}
|
||||
{{send_type param for.index}};
|
||||
{{~ end ~}}
|
||||
conn.Channels[0].SendBunch(bunch, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Receive(FInBunch bunch{{read_out_params entry.Params}})
|
||||
{
|
||||
{{~ for param in entry.Params ~}}
|
||||
{{read_out param for.index}};
|
||||
{{~ end ~}}
|
||||
return !bunch.IsError();
|
||||
}
|
||||
|
||||
public static void Discard(FInBunch bunch)
|
||||
{
|
||||
{{~ for param in entry.Params ~}}
|
||||
{{read_type param}};
|
||||
{{~ end ~}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Prospect.Unreal.Generator.Util
|
||||
{
|
||||
internal static class EmbeddedResource
|
||||
{
|
||||
public static string GetContent(string relativePath)
|
||||
{
|
||||
var baseName = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
var resourceName = relativePath
|
||||
.TrimStart('.')
|
||||
.Replace(Path.DirectorySeparatorChar, '.')
|
||||
.Replace(Path.AltDirectorySeparatorChar, '.');
|
||||
|
||||
using var stream = Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceStream(baseName + "." + resourceName);
|
||||
|
||||
if (stream == null)
|
||||
throw new NotSupportedException();
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Prospect.Unreal.Generator.Util
|
||||
{
|
||||
internal class ParamDef
|
||||
{
|
||||
public ParamDef(string read, string write)
|
||||
{
|
||||
Read = read;
|
||||
Write = write;
|
||||
}
|
||||
|
||||
public string Read { get; }
|
||||
public string Write { get; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user