Skip to content

Commit c6fc821

Browse files
committed
Add FixedStringGenerator
1 parent d4bf2b8 commit c6fc821

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using FFXIVClientStructs.InteropGenerator;
2+
using FFXIVClientStructs.InteropSourceGenerators.Extensions;
3+
using FFXIVClientStructs.InteropSourceGenerators.Models;
4+
using LanguageExt;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using static LanguageExt.Prelude;
8+
using static FFXIVClientStructs.InteropSourceGenerators.DiagnosticDescriptors;
9+
10+
namespace FFXIVClientStructs.InteropSourceGenerators;
11+
12+
[Generator]
13+
internal sealed class FixedStringGenerator : IIncrementalGenerator
14+
{
15+
private const string AttributeName = "FFXIVClientStructs.Interop.Attributes.FixedStringAttribute";
16+
17+
public void Initialize(IncrementalGeneratorInitializationContext context)
18+
{
19+
IncrementalValuesProvider<(Validation<DiagnosticInfo, StructInfo> StructInfo, Validation<DiagnosticInfo, FixedStringInfo> FixedStringInfo)> structAndFixedStringInfos =
20+
context.SyntaxProvider
21+
.ForAttributeWithMetadataName(
22+
AttributeName,
23+
static (node, _) => node is VariableDeclaratorSyntax
24+
{
25+
Parent: VariableDeclarationSyntax
26+
{
27+
Parent: FieldDeclarationSyntax
28+
{
29+
Parent: StructDeclarationSyntax, AttributeLists.Count: > 0
30+
}
31+
}
32+
},
33+
static (context, _) =>
34+
{
35+
StructDeclarationSyntax structSyntax = (StructDeclarationSyntax)context.TargetNode.Parent!.Parent!.Parent!;
36+
37+
IFieldSymbol fieldSymbol = (IFieldSymbol)context.TargetSymbol;
38+
39+
return (Struct: StructInfo.GetFromSyntax(structSyntax),
40+
Info: FixedStringInfo.GetFromRoslyn(fieldSymbol));
41+
});
42+
43+
// group by struct
44+
IncrementalValuesProvider<(Validation<DiagnosticInfo, StructInfo> StructInfo, Validation<DiagnosticInfo, Seq<FixedStringInfo>> FixedStringInfos)> groupedStructInfoWithFixedStringInfos =
45+
structAndFixedStringInfos.TupleGroupByValidation();
46+
47+
// make sure caching is working
48+
IncrementalValuesProvider<Validation<DiagnosticInfo, StructWithFixedStringInfos>> structWithFixedInfos =
49+
groupedStructInfoWithFixedStringInfos.Select(static (item, _) =>
50+
(item.StructInfo, item.FixedStringInfos).Apply(static (si, fsi) =>
51+
new StructWithFixedStringInfos(si, fsi))
52+
);
53+
54+
context.RegisterSourceOutput(structWithFixedInfos, (sourceContext, item) =>
55+
{
56+
item.Match(
57+
Fail: diagnosticInfos =>
58+
{
59+
diagnosticInfos.Iter(dInfo => sourceContext.ReportDiagnostic(dInfo.ToDiagnostic()));
60+
},
61+
Succ: structWithFixedInfo =>
62+
{
63+
sourceContext.AddSource(structWithFixedInfo.GetFileName(), structWithFixedInfo.RenderSource());
64+
});
65+
});
66+
}
67+
68+
internal sealed record FixedStringInfo(string FieldName, int MaxLength, string PropertyName)
69+
{
70+
public static Validation<DiagnosticInfo, FixedStringInfo> GetFromRoslyn(IFieldSymbol fieldSymbol)
71+
{
72+
Validation<DiagnosticInfo, IFieldSymbol> validSymbol =
73+
(fieldSymbol.IsFixedSizeBuffer
74+
? Success<DiagnosticInfo, IFieldSymbol>(fieldSymbol)
75+
: Fail<DiagnosticInfo, IFieldSymbol>(
76+
DiagnosticInfo.Create(FixedSizedAttributeOnInvalidField,
77+
fieldSymbol)))
78+
.Bind(symbol =>
79+
{
80+
IPointerTypeSymbol pointerType = (symbol.Type as IPointerTypeSymbol)!; // we know its a pointer
81+
ITypeSymbol pointedToType = pointerType.PointedAtType;
82+
if (pointedToType.SpecialType != SpecialType.System_Byte)
83+
return Fail<DiagnosticInfo, IFieldSymbol>(
84+
DiagnosticInfo.Create(FixedSizedAttributeOnInvalidField,
85+
fieldSymbol));
86+
87+
return Success<DiagnosticInfo, IFieldSymbol>(fieldSymbol);
88+
});
89+
Option<AttributeData> attribute = fieldSymbol.GetFirstAttributeDataByTypeName(AttributeName);
90+
91+
Validation<DiagnosticInfo, string> validPropertyName = attribute.GetValidAttributeArgument<string>("PropertyName", 0, AttributeName, fieldSymbol);
92+
93+
return (validSymbol, validPropertyName).Apply((symbol, propertyName) =>
94+
new FixedStringInfo(symbol.Name, symbol.FixedSize, string.IsNullOrEmpty(propertyName) ? $"{symbol.Name}String" : propertyName));
95+
}
96+
97+
public void RenderFixedString(IndentedStringBuilder builder)
98+
{
99+
builder.AppendLine($"public string {PropertyName} {{ get {{ fixed (byte* p = {FieldName}) {{ var str = Encoding.UTF8.GetString(p, {MaxLength}); var nullIdx = str.IndexOf('\0'); return nullIdx >= 0 ? str[..nullIdx] : str; }} }} }}");
100+
}
101+
}
102+
103+
private sealed record StructWithFixedStringInfos(StructInfo StructInfo, Seq<FixedStringInfo> FixedStringInfos)
104+
{
105+
public string RenderSource()
106+
{
107+
IndentedStringBuilder builder = new();
108+
109+
builder.AppendLine("using System.Runtime.CompilerServices;");
110+
builder.AppendLine("using System.Text;");
111+
112+
StructInfo.RenderStart(builder);
113+
114+
foreach (FixedStringInfo fsi in FixedStringInfos)
115+
fsi.RenderFixedString(builder);
116+
117+
StructInfo.RenderEnd(builder);
118+
119+
return builder.ToString();
120+
}
121+
122+
public string GetFileName()
123+
{
124+
return $"{StructInfo.Namespace}.{StructInfo.Name}.FixedStrings.g.cs";
125+
}
126+
}
127+
}

FFXIVClientStructs/FFXIV/Client/Game/SavedAppearanceManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ public unsafe partial struct SavedAppearanceManager {
1111
}
1212

1313
[StructLayout(LayoutKind.Explicit, Size = 0x140)]
14-
public unsafe struct SavedAppearanceSlot {
14+
public unsafe partial struct SavedAppearanceSlot {
1515
[FieldOffset(0x00)] public uint Magic; // Should be 0x2013_FF14
1616
[FieldOffset(0x04)] public uint Version;
1717
[FieldOffset(0x10)] public fixed byte Customize[0x1A];
18-
[FieldOffset(0x30)] public fixed byte Label[0x40];
18+
[FieldOffset(0x30), FixedString("Label")] public fixed byte LabelBytes[0x40];
1919
[FieldOffset(0x134)] public bool IsCreated;
2020
[FieldOffset(0x13C)] public byte SlotIndex;
2121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FFXIVClientStructs.Interop.Attributes;
2+
3+
[AttributeUsage(AttributeTargets.Field)]
4+
public class FixedStringAttribute : Attribute {
5+
public FixedStringAttribute(string propertyName = "") {
6+
this.PropertyName = propertyName;
7+
}
8+
9+
public string? PropertyName { get; }
10+
}

0 commit comments

Comments
 (0)