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

Support encounter views #14

Merged
merged 2 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -20,7 +20,7 @@

public static int AeroDiagId = 1;

public static readonly DiagnosticDescriptor InvalidTypeWarning = new DiagnosticDescriptor(id: "Aero1",

Check warning on line 23 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero1'
title: "Unsupported type",
messageFormat: "'{0}' isn't supported for serialisation, sorry :<",
category: "Aero.Gen",
Expand All @@ -28,48 +28,62 @@
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor GenericError = new DiagnosticDescriptor(
id: $"Aero2",

Check warning on line 31 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero2'
title: "An exception was thrown by the Aero.Gen generator",
messageFormat: "An exception was thrown by the Aero.Gen generator: '{0}'",
category: "Aero.Gen",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor GenericInfo = new DiagnosticDescriptor(id: $"Aero3",

Check warning on line 38 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero3'
title: "GenericInfo",
messageFormat: "'{0}'",
category: "Aero.Gen",
DiagnosticSeverity.Info,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor NoArrayAttributeError = new DiagnosticDescriptor(id: $"Aero4",

Check warning on line 45 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero4'
title: "Array doesn't have an AeroArray attribute",
messageFormat: "Field '{0}' doesn't have an AeroArray attribute, you need to add one to tell me how to handle this! D:",
category: "Aero.Gen",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor StructNotMarkedAsAeroBlockError = new DiagnosticDescriptor(id: $"Aero4",

Check warning on line 52 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero4'
title: "Included struct isn't marked as an AeroBlock",
messageFormat: "Field '{0}' uses type '{1}' that isn't marked as an AeroBlock, please add the attribute for this to get serialised",
category: "Aero.Gen",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor ClassNotAllowedInAeroError = new DiagnosticDescriptor(id: $"Aero5",

Check warning on line 59 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero5'
title: "Class no allowed",
messageFormat: "Field '{0}' uses type '{1}' that is a class and not a struct so can't be used, sorry :<",
category: "Aero.Gen",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor MultipleMessageIdsForTheSameType = new DiagnosticDescriptor(id: "Aero6",

Check warning on line 66 in Aero/Aero.Gen/AeroGenrator.cs

View workflow job for this annotation

GitHub Actions / build

Enable analyzer release tracking for the analyzer project containing rule 'Aero6'
title: "Multiple MessageIds For The Same Type",
messageFormat: "There already is a class marked with this message id, '{0}' ",
category: "Aero.Gen",
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 @@
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 @@
}
}
}

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
Loading