Skip to content

Commit

Permalink
Merge pull request #14 from SzymonKaminski/encounter-views
Browse files Browse the repository at this point in the history
Support encounter views
  • Loading branch information
Xsear authored Jun 2, 2024
2 parents 3f63616 + b77807e commit ee17404
Show file tree
Hide file tree
Showing 10 changed files with 692 additions and 28 deletions.
7 changes: 7 additions & 0 deletions Aero/Aero.Gen/AGUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ public static bool IsViewClass(ClassDeclarationSyntax cd, SemanticModel sm)
return false;
}

public static bool IsEncounterClass(ClassDeclarationSyntax cd, SemanticModel sm)
{
var aeroAttr = NodeWithName<AttributeSyntax>(cd, AeroEncounterAttribute.Name);

return aeroAttr != null;
}

/*{
return NodeWithName<AttributeSyntax>(fd, name);
fd.DescendantNodes().OfType<AttributeSyntax>()
Expand Down
60 changes: 59 additions & 1 deletion Aero/Aero.Gen/AeroGenrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ public class AeroGenerator : ISourceGenerator
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor InvalidTypeInEncounterView = new DiagnosticDescriptor(id: "Aero7",
title: "Invalid type in encounter view",
messageFormat: "Field '{0}' uses invalid type '{1}', use byte/bool/ushort/uint/float/ulong/Timer/EntityId or fixed size array of any of these types",
category: "Aero.Gen",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor InvalidArrayModeInEncounterView = new DiagnosticDescriptor(id: "Aero8",
title: "Invalid array mode in encounter view",
messageFormat: "Field '{0}' uses array mode '{1}', but only fixed size arrays are supported in encounter views",
category: "Aero.Gen",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

#endregion

public static FieldDeclarationSyntax LastCheckedField;
Expand Down Expand Up @@ -111,6 +125,9 @@ public void Execute(GeneratorExecutionContext context)
var routing = CreateRouting(snRecv, config);
Debug.WriteLine(routing);
context.AddSource("AeroRouting.cs", SourceText.From(routing, Encoding.UTF8));

var encounters = CreateEncounters(snRecv);
context.AddSource("AeroEncounters.cs", SourceText.From(encounters, Encoding.UTF8));
}
}
catch (Exception e) {
Expand Down Expand Up @@ -243,5 +260,46 @@ void AddPacketSwitch(IEnumerable<AeroMessageIdAttribute> msgs)
}
}
}

private string CreateEncounters(AeroSyntaxReceiver snRecv)
{
var sb = new StringBuilder();

AddLine(sb, "using System;");
AddLine(sb, "using Aero.Gen;");

AddLine(sb, $"public static class AeroEncounters");
AddLine(sb, "{");

Indent();
{
AddLine(sb, "public static IAeroEncounter GetEncounterClass(string encounterType)");
AddLineAndIndent(sb, "{");
{
AddLineAndIndent(sb, "IAeroEncounter encounter = encounterType switch {");
{
foreach (var encounter in snRecv.AeroEncounterClasses)
{
var attr = AgUtils.NodeWithName<AttributeSyntax>(encounter, AeroEncounterAttribute.Name);

var type = attr.ArgumentList.Arguments[0].Expression.ToString();

AddLine(sb, $"{type} => new {encounter.GetFullName()}(),");
}

AddLine(sb, "_ => null,");
}

UnIndentAndAddLine(sb, "};");
AddLine(sb, "");
AddLine(sb, "return encounter;");
}

UnIndentAndAddLine(sb, "}");
UnIndentAndAddLine(sb, "}");

return sb.ToString();
}
}
}
}
}
12 changes: 9 additions & 3 deletions Aero/Aero.Gen/AeroSyntaxReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ public class AeroSyntaxReceiver : ISyntaxReceiver
public GeneratorExecutionContext Context;
public List<ClassDeclarationSyntax> ClassesToAugment { get; private set; } = new();

public List<ClassDeclarationSyntax> AeroClasses { get; private set; } = new();
public Dictionary<string, StructDeclarationSyntax> AeroBlockLookup { get; private set; } = new();
public Dictionary<string, AeroMessageIdAttribute> AeroMessageIds { get; private set; } = new();
public List<ClassDeclarationSyntax> AeroClasses { get; private set; } = new();
public List<ClassDeclarationSyntax> AeroEncounterClasses { get; private set; } = new();
public Dictionary<string, StructDeclarationSyntax> AeroBlockLookup { get; private set; } = new();
public Dictionary<string, AeroMessageIdAttribute> AeroMessageIds { get; private set; } = new();

public static bool HasAttribute(ClassDeclarationSyntax cds, string attributeName) => cds.AttributeLists.Any(x => x.Attributes.Any(y => (y.Name is IdentifierNameSyntax ins && ins.Identifier.Text == attributeName) ||
(y.Name is QualifiedNameSyntax qns && qns.ToString() == attributeName)));
Expand Down Expand Up @@ -44,6 +45,11 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
}
}
}

if (HasAttribute(cds, "AeroEncounter"))
{
AeroEncounterClasses.Add(cds);
}
}

if (syntaxNode is StructDeclarationSyntax sds && sds.AttributeLists.Count > 0 && HasAttribute(sds, AeroBlockAttribute.Name)) {
Expand Down
17 changes: 17 additions & 0 deletions Aero/Aero.Gen/Attributes/AeroEncounterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Aero.Gen.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class AeroEncounterAttribute : Attribute
{
public static string Name = "AeroEncounter";

public string EncounterType;

public AeroEncounterAttribute(string encounterType)
{
EncounterType = encounterType;
}
}
}
133 changes: 133 additions & 0 deletions Aero/Aero.Gen/GenV2.Encounters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Text;
using Aero.Gen.Attributes;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Aero.Gen
{
public partial class Genv2
{
private void GenerateEncounterFunctions(ClassDeclarationSyntax cd, SemanticModel sm)
{
GenerateEncounterHeader(cd);

GenerateViewUpdateUnpacker(cd, sm);
GenerateViewUpdatePacker(cd, sm);
GenerateClearViewChanges(cd);
GenerateGetPackedChangesSize(cd, sm);
}

private void GenerateEncounterHeader(ClassDeclarationSyntax cd)
{
var encounterAttr = AgUtils.NodeWithName<AttributeSyntax>(cd, AeroEncounterAttribute.Name);

var encounterType = encounterAttr.ArgumentList.Arguments[0].Expression.ToString().Trim('"');

string NameToHex(string name)
{
var bytes = Encoding.UTF8.GetBytes(name + '\0');

var hex = new StringBuilder();

foreach (var b in bytes)
{
hex.AppendFormat("0x{0:X2}, ", b);
}

return hex.ToString().Trim();
}

var rootNode = AeroSourceGraphGen.BuildTree(SyntaxReceiver, cd);
byte fieldIdx = 0;

AddLine("public static readonly byte[] Header = {");
Indent();

AddLine($"{NameToHex(encounterType)} // {encounterType}");

AeroSourceGraphGen.WalkTree(rootNode, node =>
{
if (node.Depth != 0)
{
return;
}
var name = node.Name;
byte count = 1;
if (node is AeroArrayNode arr)
{
if (arr.Mode != AeroArrayNode.Modes.Fixed)
{
Context.ReportDiagnostic(
Diagnostic.Create(AeroGenerator.InvalidArrayModeInEncounterView, cd.GetLocation(), name, arr.Mode)
);
}
name = arr.Nodes[0].Name;
count = (byte)arr.Length;
}
var typeStr = node.TypeStr.ToLower();
if (node is AeroFieldNode { IsEnum: true } fieldNode)
{
typeStr = TypeAlias(fieldNode.EnumStr).ToLower();
}
byte byteType = typeStr switch
{
"uint" => 0,
"float" => 1,
string t when t.EndsWith("entityid") => 2,
"ulong" => 3,
"byte" => 4,
// there's no type 5
"ushort" => 6,
string t when t.EndsWith("timer") => 7,
"bool" => 8,
"uint[]" => 128,
"float[]" => 129,
string t when t.EndsWith("entityid[]") => 130,
"ulong[]" => 131,
"byte[]" => 132,
// there's no type 133 either
"ushort[]" => 134,
string t when t.EndsWith("timer[]") => 135,
"bool[]" => 136,
_ => 255,
};
if (byteType == 255)
{
Context.ReportDiagnostic(
Diagnostic.Create(AeroGenerator.InvalidTypeInEncounterView, cd.GetLocation(), name, typeStr)
);
}
var hexIdx = BitConverter.ToString(new[]{ fieldIdx });
var hexType = BitConverter.ToString(new[]{ byteType });
var hexCount = BitConverter.ToString(new[]{ count });
AddLine(
$"0x{hexIdx}, 0x{hexType}, 0x{hexCount}, // idx: {fieldIdx}, type: {typeStr}, count: {count}, name: {name}");
AddLine(NameToHex(name));
fieldIdx++;
});

UnIndent();
AddLine("};"); // end static Header

using (Function("public byte[] GetHeader()"))
{
AddLine("return Header;");
}

AddLine();
}
}
}
31 changes: 22 additions & 9 deletions Aero/Aero.Gen/GenV2.Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ private void GenerateViewUpdateUnpacker(ClassDeclarationSyntax cd, SemanticModel
AddLine();

var rootNode = AeroSourceGraphGen.BuildTree(SyntaxReceiver, cd);
var isEncounterClass = AgUtils.IsEncounterClass(cd, sm);

using (DoWhile("offset < data.Length")) {
AddLine("var id = data[offset++];");
Expand All @@ -187,7 +188,7 @@ private void GenerateViewUpdateUnpacker(ClassDeclarationSyntax cd, SemanticModel
using (Block($"case {shadowFieldIdx}: // {node.GetFullName()}")) {
CreateLogicFlow(node,
CreateUnpackerPreNode,
node => { CreateUnpackerOnNode(false, node, ref nullableIdx); });
node => { CreateUnpackerOnNode(false, node, ref nullableIdx, isEncounterClass); });
AddLine("break;");
}
Expand Down Expand Up @@ -222,6 +223,7 @@ private void GenerateViewUpdatePacker(ClassDeclarationSyntax cd, SemanticModel s
AddLine("int offset = 0;");
AddLine();
var rootNode = AeroSourceGraphGen.BuildTree(SyntaxReceiver, cd);
var isEncounterClass = AgUtils.IsEncounterClass(cd, sm);
var fieldIdx = 0;
AeroSourceGraphGen.WalkTree(rootNode, node =>
{
Expand All @@ -230,15 +232,15 @@ private void GenerateViewUpdatePacker(ClassDeclarationSyntax cd, SemanticModel s
using (If($"{GenerateViewFieldIdx(fieldIdx, DIRTY_FIELD_BASE_NAME)}")) {
if (node.IsNullable) {
using (If($"{node.GetFullName()}Prop.HasValue")) { // TODO: change to use nullable bits
CreatePacker(fieldIdx, node, true);
CreatePacker(fieldIdx, node, isEncounterClass);
}
using (Else()) {
AddLine($"buffer[offset++] = {fieldIdx + 128}; // was null so set the clear, I think this is right");
}
}
else {
CreatePacker(fieldIdx, node, false);
CreatePacker(fieldIdx, node, isEncounterClass);
}
}
Expand All @@ -253,12 +255,22 @@ private void GenerateViewUpdatePacker(ClassDeclarationSyntax cd, SemanticModel s
AddLine($"return offset;");
}

void CreatePacker(int fieldIdx, AeroNode node, bool noNullableCheck)
void CreatePacker(int fieldIdx, AeroNode node, bool isEncounter)
{
AddLine($"buffer[offset++] = {fieldIdx};");
CreateLogicFlow(node,
(node) => CreatePackerPreNode(node),
(node) => CreatePackerOnNode(node, false));
// For arrays in encounters we add idx inside the for loop in PackerOnNode
if (isEncounter && node is AeroArrayNode)
{
CreateLogicFlow(node,
(node) => CreatePackerPreNode(node),
(node) => CreatePackerOnNode(node, false, true, fieldIdx));
}
else
{
AddLine($"buffer[offset++] = {fieldIdx};");
CreateLogicFlow(node,
(node) => CreatePackerPreNode(node),
(node) => CreatePackerOnNode(node, false));
}
}
}

Expand All @@ -269,6 +281,7 @@ private void GenerateGetPackedChangesSize(ClassDeclarationSyntax cd, SemanticMod
AddLine("int offset = 0;");
AddLine();
var rootNode = AeroSourceGraphGen.BuildTree(SyntaxReceiver, cd);
var isEncounter = AgUtils.IsEncounterClass(cd, SyntaxReceiver.Context.Compilation.GetSemanticModel(cd.SyntaxTree));
var fieldIdx = 0;
AeroSourceGraphGen.WalkTree(rootNode, node =>
{
Expand All @@ -278,7 +291,7 @@ private void GenerateGetPackedChangesSize(ClassDeclarationSyntax cd, SemanticMod
AddLine("offset++;");
CreateLogicFlow(node,
preNode: GetPackedSizePreNode,
onNode: node => { GetPackedSizeOnNode(true, node); });
onNode: node => { GetPackedSizeOnNode(true, node, isEncounter); });
}
fieldIdx++;
Expand Down
Loading

1 comment on commit ee17404

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark result (use as a guide):

BenchmarkDotNet=v0.12.1, OS=ubuntu 22.04
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=5.0.408
 [Host] : .NET Core 5.0.17 (CoreCLR 5.0.1722.21314, CoreFX 5.0.1722.21314), X64 RyuJIT
 Job-GNLQFJ : .NET Core 5.0.17 (CoreCLR 5.0.1722.21314, CoreFX 5.0.1722.21314), X64 RyuJIT

Runtime=.NET Core 5.0 IterationCount=10 LaunchCount=1 
WarmupCount=5 
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
BinaryReaderBaseLine 238.2458 ns 2.8569 ns 1.7001 ns 0.0043 - - 376 B
BinaryWriterBaseLine 382.9315 ns 2.0795 ns 1.0876 ns 0.0057 - - 496 B
SimpleTypesRead 8.8070 ns 0.0207 ns 0.0123 ns - - - -
SubTypesRead 5.3822 ns 0.0461 ns 0.0241 ns - - - -
ArraysCombinedRead 1,235.1116 ns 9.8391 ns 6.5079 ns 0.0687 - - 5800 B
ByteArrayRead 84.8423 ns 3.7370 ns 2.4718 ns 0.0014 - - 128 B
IntArrayRead 133.7337 ns 1.5136 ns 1.0011 ns 0.0050 - - 424 B
BlockArrayRead 910.0786 ns 19.7958 ns 13.0937 ns 0.0572 0.0010 - 4824 B
FixedLengthString 30.2859 ns 0.1308 ns 0.0778 ns 0.0005 - - 40 B
PrefixedLengthString 30.1752 ns 0.1475 ns 0.0976 ns 0.0005 - - 40 B
NullTerminatedLengthString 35.7773 ns 0.2728 ns 0.1427 ns 0.0005 - - 40 B
Vector2Array2Read 13.1095 ns 0.0567 ns 0.0375 ns 0.0005 - - 40 B
GetPackedLengthSimple 0.6461 ns 0.0167 ns 0.0111 ns - - - -
GetPackedLengthComplex 0.5829 ns 0.0125 ns 0.0074 ns - - - -
PackSimple 4.2288 ns 0.0359 ns 0.0237 ns - - - -
PackSubTypesSimple 4.7321 ns 0.0233 ns 0.0154 ns - - - -
PackComplex 141.7490 ns 0.8075 ns 0.4805 ns - - - -
PackArraysCombined 939.0218 ns 7.4608 ns 4.9348 ns - - - -
PackByteArray 69.0442 ns 0.1926 ns 0.1008 ns - - - -
PackIntArray 99.6461 ns 0.7056 ns 0.3691 ns - - - -
PackBlockArray 665.2041 ns 4.1552 ns 2.7484 ns - - - -
PackFixedLengthString 25.3351 ns 0.1419 ns 0.0742 ns 0.0005 - - 40 B
PackPrefixedLengthString 25.5102 ns 0.0918 ns 0.0546 ns 0.0005 - - 40 B
PackNullTerminatedLengthString 25.3133 ns 0.1714 ns 0.1134 ns 0.0005 - - 40 B

Please sign in to comment.