Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version specific packet implementation pattern #78

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
40d2e66
overhauled packet interfaces + made ProtocolVersion an enum
Klotzi111 Aug 15, 2024
eb07f6e
refactored all packet classes to use IPacketStatic
Klotzi111 Aug 15, 2024
3debcdb
renamed PacketReadDelegate to PacketFactory
Klotzi111 Aug 15, 2024
a5f22b5
fixed PacketVersionSubTypeLookup
Klotzi111 Aug 15, 2024
49364e8
added all versions for SetExperiencePacket
Klotzi111 Aug 15, 2024
4bcfe8f
improved HexDump performance
Klotzi111 Aug 16, 2024
706db10
improved WaitForPacketWhere to return the packet
Klotzi111 Aug 18, 2024
bd3ac7a
added GetHotbarSlots
Klotzi111 Aug 18, 2024
39731e9
fixed bug in StreamLoop that resulted in deadlock after tcp connectio…
Klotzi111 Aug 18, 2024
2908fda
fixed Task cancellation for WaitForPacketWhere + added EatFoodItem me…
Klotzi111 Aug 18, 2024
c243b92
added missing Packets - Part 5
Klotzi111 Aug 18, 2024
ddce30d
merged with upstream
Klotzi111 Aug 19, 2024
604e76f
Merge remote-tracking branch 'upstream/main'
Klotzi111 Aug 19, 2024
26263a9
added IParticleData implementations + used it in new ExplosionPacket
Klotzi111 Aug 19, 2024
ee74c33
added missing Packets - Part 6
Klotzi111 Aug 19, 2024
8383fc1
fixed name for CommandBlockUpdatePacket
Klotzi111 Aug 19, 2024
dfdfecc
added TODO to EntityStatusPacket
Klotzi111 Aug 19, 2024
33b5bac
merged with main
Klotzi111 Aug 19, 2024
29f88de
moved ClickContainerButtonPacket to correct namespace
Klotzi111 Aug 19, 2024
dcab703
fixed EntityEffectPacket contained types
Klotzi111 Aug 19, 2024
d5e5f63
improved XML doc for version specific packet classes
Klotzi111 Aug 19, 2024
299ac36
unified packet nested types
Klotzi111 Aug 20, 2024
8c88b39
created PacketSourceGenerator project
Klotzi111 Aug 21, 2024
4e36b04
removed code from packet types that is now generated by source generator
Klotzi111 Aug 21, 2024
7abf3f4
PacketSourceGenerator: fixed GetAllNamedTypeSymbols to get sub types …
Klotzi111 Aug 21, 2024
bc83edd
fixed all non "Missing XML comment" build warnings
Klotzi111 Aug 21, 2024
8e188b1
added missing packet PingResponsePacket
Klotzi111 Aug 21, 2024
ff543f0
merged with main
Klotzi111 Aug 21, 2024
6758647
changed source generator debug file name
Klotzi111 Aug 21, 2024
56e2805
added IPacketClientbound and IPacketServerbound
Klotzi111 Aug 21, 2024
9829697
PacketSourceGenerator: now generates IPacketVersionSubTypeStatic<,> i…
Klotzi111 Aug 21, 2024
f6eec5a
fixed EntityStatusPacket
Klotzi111 Aug 21, 2024
7c5ad95
expanded usage of IPacketClientbound
Klotzi111 Aug 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Components/MineSharp.Commands/CommandTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private static CommandNode ReadNode(PacketBuffer buffer, MinecraftData data)

Identifier name;

if (data.Version.Protocol < ProtocolVersion.V_1_19)
if (data.Version.Protocol < ProtocolVersion.V_1_19_0)
{
// in 1.18.x, the parser was specified by its name.
name = buffer.ReadIdentifier();
Expand Down
4 changes: 2 additions & 2 deletions Components/MineSharp.Commands/Parser/ParserRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ public static Identifier GetParserNameById(int parserId, MinecraftData data)
{
var mapping = data.Version.Protocol switch
{
ProtocolVersion.V_1_19 => Mapping119,
ProtocolVersion.V_1_19_2 => Mapping119,
ProtocolVersion.V_1_19_0 => Mapping119,
ProtocolVersion.V_1_19_1 => Mapping119,
ProtocolVersion.V_1_19_3 => Mapping1193,
>= ProtocolVersion.V_1_19_4 and < ProtocolVersion.V_1_20_3 => Mapping1194,
>= ProtocolVersion.V_1_20_3 => Mapping1203,
Expand Down
105 changes: 105 additions & 0 deletions Components/MineSharp.PacketSourceGenerator/CommonSymbolHolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.CodeDom.Compiler;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using MineSharp.PacketSourceGenerator.Utils;

namespace MineSharp.PacketSourceGenerator;

public record CommonSymbolHolder(
Compilation Compilation,

INamedTypeSymbol PacketBuffer,
INamedTypeSymbol MinecraftData,
INamedTypeSymbol PacketType,
INamedTypeSymbol ProtocolVersion,

INamedTypeSymbol IPacket,
INamedTypeSymbol IPacketStatic,
INamedTypeSymbol IPacketStaticOfTSelf,
IMethodSymbol IPacketStatic_Read,
IPropertySymbol IPacketStatic_StaticType,

INamedTypeSymbol IPacketClientbound,
INamedTypeSymbol IPacketServerbound,

INamedTypeSymbol IPacketVersionSubType,
INamedTypeSymbol IPacketVersionSubTypeOfTBasePacket,
INamedTypeSymbol IPacketVersionSubTypeStatic,
INamedTypeSymbol IPacketVersionSubTypeStaticOfTBasePacket,
INamedTypeSymbol IPacketVersionSubTypeStaticOfTSelfAndTBasePacket,
IMethodSymbol IPacketVersionSubTypeStatic_Read,

INamedTypeSymbol PacketVersionSubTypeLookup,

INamedTypeSymbol ISerializable,
INamedTypeSymbol ISerializableWithMinecraftData,

INamedTypeSymbol Object,
INamedTypeSymbol GeneratedCodeAttribute
)
{
private static T CheckNotNull<T>(T? symbol, [CallerArgumentExpression(nameof(symbol))] string? symbolArgumentExpression = null)
{
if (symbol is null)
{
throw new ArgumentNullException(symbolArgumentExpression);
}
return symbol;
}

public const string PacketNamespace = "MineSharp.Protocol.Packets";
public const string SerializationNamespace = "MineSharp.Core.Serialization";

public static CommonSymbolHolder Create(Compilation compilation)
{
var PacketType = CheckNotNull(compilation.GetTypeByMetadataName($"MineSharp.Data.Protocol.PacketType"));
var IPacket = CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacket"));
var IPacketStatic = CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketStatic"));
var IPacketVersionSubTypeStatic = CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketVersionSubTypeStatic"));

return new CommonSymbolHolder(
compilation,

CheckNotNull(compilation.GetTypeByMetadataName($"{SerializationNamespace}.PacketBuffer")),
CheckNotNull(compilation.GetTypeByMetadataName($"MineSharp.Data.MinecraftData")),
PacketType,
CheckNotNull(compilation.GetTypeByMetadataName($"MineSharp.Core.ProtocolVersion")),

CheckNotNull(IPacket),
IPacketStatic,
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketStatic`1")),
CheckNotNull(GetReadMethodSymbol(IPacketStatic, IPacket)),
CheckNotNull(GetStaticTypePropertySymbol(IPacketStatic, PacketType)),

CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketClientbound")),
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketServerbound")),

CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketVersionSubType")),
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketVersionSubType`1")),
IPacketVersionSubTypeStatic,
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketVersionSubTypeStatic`1")),
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.IPacketVersionSubTypeStatic`2")),
CheckNotNull(GetReadMethodSymbol(IPacketVersionSubTypeStatic, IPacket)),

CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.PacketVersionSubTypeLookup`1")),

CheckNotNull(compilation.GetTypeByMetadataName($"{SerializationNamespace}.ISerializable`1")),
CheckNotNull(compilation.GetTypeByMetadataName($"{PacketNamespace}.NetworkTypes.ISerializableWithMinecraftData`1")),

CheckNotNull(compilation.GetSpecialType(SpecialType.System_Object)),
CheckNotNull(compilation.GetTypeByMetadataName(typeof(GeneratedCodeAttribute).FullName))
);
}

public static IMethodSymbol? GetReadMethodSymbol(INamedTypeSymbol type, INamedTypeSymbol packet)
{
return type.GetMembers("Read").OfType<IMethodSymbol>().FirstOrDefault(p => SymbolEqualityComparer.IncludeNullability.Equals(p.ReturnType, packet.WithNullable(false)));
}

private static IPropertySymbol? GetStaticTypePropertySymbol(INamedTypeSymbol type, INamedTypeSymbol packetType)
{
return type.GetMembers("StaticType").OfType<IPropertySymbol>().FirstOrDefault(p => SymbolEqualityComparer.IncludeNullability.Equals(p.Type, packetType.WithNullable(false)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;

namespace MineSharp.PacketSourceGenerator;

public sealed record GeneratedSourceFileInfo(string Name, string Content)
{
private static readonly ImmutableArray<char> s_disallowedFileChars = ImmutableArray.Create('<', '>', ':', '"', '/', '\\', '|', '?', '*', // not allowed in file names
','); // not nice to have in file names

public static string EscapeFileName(string fileName)
{
return s_disallowedFileChars.Aggregate(new StringBuilder(fileName), (s, c) => s.Replace(c, '_')).ToString();
}

public static string BuildFileName(INamedTypeSymbol type)
{
var symbolDisplayString = type.ToDisplayString();
var fileName = $"{EscapeFileName(symbolDisplayString)}.{SourceGenerator.SourceGeneratorShortName}.g.cs";
return fileName;
}
}
13 changes: 13 additions & 0 deletions Components/MineSharp.PacketSourceGenerator/GeneratorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace MineSharp.PacketSourceGenerator;

public readonly struct GeneratorOptions
{
public readonly string Indent;
public readonly string NewLine;

public GeneratorOptions(string indent, string newLine)
{
Indent = indent;
NewLine = newLine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using MineSharp.PacketSourceGenerator.Utils;

namespace MineSharp.PacketSourceGenerator.Generators;

public abstract class AbstractPacketGenerator
{
#region Constants

protected const string InheritDocComment = "/// <inheritdoc/>";

#endregion

private static string BuildGeneratedCodeAttributeDeclaration(string attributeTypeName)
{
return $"[{attributeTypeName}(\"{SourceGenerator.SourceGeneratorName}\", \"{SourceGenerator.SourceGeneratorVersionString}\")]";
}

public readonly GeneratorArguments Args;
protected readonly HashSet<ImportItem> Imports = new();

protected AbstractPacketGenerator(GeneratorArguments args)
{
Args = args;
}

protected string GeneratedCodeAttributeDeclaration => BuildGeneratedCodeAttributeDeclaration(BuildTypeName(Args.CommonSymbolHolder.GeneratedCodeAttribute));

public abstract string? BuildBodyForType();

public virtual string? GenerateFileSource()
{
return BuildFileSource();
}

public virtual GeneratedSourceFileInfo? GenerateFile()
{
var source = BuildFileSource();
if (source is null)
{
return null;
}

var fileName = GeneratedSourceFileInfo.BuildFileName(Args.TypeSymbol);
return new GeneratedSourceFileInfo(fileName, source);
}

private string? BuildFileSource(bool includeSelf = false)
{
var result = BuildBodyForType();
if (result is null)
{
return null;
}

var containingSymbols = Args.TypeSymbol.GetContainingNamespaceAndTypes(includeSelf);
foreach (var symbol in containingSymbols)
{
if (symbol.IsNamespace)
{
var namespaceName = symbol.ToDisplayString(SymbolHelper.FullyQualifiedFormatWithoutGlobalPrefix);

var importsStr = BuildImports();
if (!string.IsNullOrEmpty(importsStr))
{
importsStr += Args.GeneratorOptions.NewLine;
}

var newResult = $$"""
{{GeneratorHelper.AutoGeneratedSourceFileHeader}}

{{importsStr ?? ""}}
""";

if (!string.IsNullOrEmpty(namespaceName))
{
newResult += $$"""
namespace {{namespaceName}}
{
{{result.IndentLines(indentString: Args.GeneratorOptions.Indent, ignoreFirstLine: true)}}
}
""";
}
else
{
newResult += result;
}

result = newResult;
}
else
{
var keyword = ((INamedTypeSymbol)symbol).GetTypeKeywordFromSymbol();

var minimalTypeName = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
result = $$"""
partial {{keyword}} {{minimalTypeName}}
{
{{result.IndentLines(indentString: Args.GeneratorOptions.Indent, ignoreFirstLine: true)}}
}
""";
}
}

return result;
}

protected string BuildTypeSource(IReadOnlyCollection<string> methods, string extendsString = "")
{
var keyword = Args.TypeSymbol.GetTypeKeywordFromSymbol();

var minimalTypeName = Args.TypeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
return $$"""
partial {{keyword}} {{minimalTypeName}}{{extendsString}}
{
{{methods.Join(Args.GeneratorOptions.NewLine.Repeat(2)).IndentLines(indentString: Args.GeneratorOptions.Indent, ignoreFirstLine: true)}}
}
""";
}

#region Methods

protected string BuildPacketReadMethod(IMethodSymbol interfaceMethodToImplement)
{
var interfaceTypeString = BuildTypeName(interfaceMethodToImplement.ContainingType);
var interfaceMethodName = interfaceMethodToImplement.Name;
var returnTypeString = BuildTypeName(interfaceMethodToImplement.ReturnType);

// sanity check that the method has the correct signature
if (!interfaceMethodToImplement.IsStatic)
{
throw new InvalidOperationException("Read method must be static.");
}
if (interfaceMethodToImplement.Parameters.Length != 2)
{
throw new InvalidOperationException("Read method must have exactly 2 parameters.");
}
var packetBufferType = Args.CommonSymbolHolder.PacketBuffer;
var minecraftDataType = Args.CommonSymbolHolder.MinecraftData;
if (!interfaceMethodToImplement.Parameters.Select(p => p.Type).SequenceEqual([packetBufferType, minecraftDataType], SymbolEqualityComparer.Default))
{
throw new InvalidOperationException($"First parameter of Read method must be of type {packetBufferType.Name} and the second must be of type {minecraftDataType.Name}.");
}
var packetBufferTypeString = BuildTypeName(packetBufferType);
var minecraftDataTypeString = BuildTypeName(minecraftDataType);

return $$"""
{{GeneratedCodeAttributeDeclaration}}
static {{returnTypeString}} {{interfaceTypeString}}.{{interfaceMethodName}}({{packetBufferTypeString}} buffer, {{minecraftDataTypeString}} data)
{
return Read(buffer, data);
}
""";
}

#endregion

#region Imports

private string? BuildImports()
{
var sb = new StringBuilder();

foreach (var import in Imports.OrderBy(x => x))
{
sb.Append(import.ToString());
sb.Append(Args.GeneratorOptions.NewLine);
}

if (sb.Length == 0)
{
return null;
}

return sb.ToString();
}

protected void AddTypeImport(ITypeSymbol typeSymbol)
{
var import = typeSymbol.GetFqnPath(addGlobalPrefix: false);
if (import is not null)
{
Imports.Add(import.Value);
}

// also add the generic type argument types
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
{
foreach (var typeParameter in namedTypeSymbol.TypeParameters)
{
import = typeParameter.GetFqnPath(addGlobalPrefix: false);
if (import is not null)
{
Imports.Add(import.Value);
}
}
}
}

protected string BuildTypeName(ITypeSymbol typeSymbol, bool skipImport = false)
{
if (DebugHelper.UseFullyQualifiedNames)
{
return typeSymbol.ToFqn();
}

var typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
if (!skipImport)
{
AddTypeImport(typeSymbol);
}
return typeName;
}

#endregion
}
Loading
Loading