com.wallstop-studios.dxmessaging 2.0.0-rc26.2 → 2.0.0-rc26.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/Editor/CustomEditors/MessagingComponentEditor.cs +92 -32
- package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs +14 -0
- package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +3 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +249 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +11 -0
- package/package.json +2 -1
|
Binary file
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
private GUIStyle _matchingStyle;
|
|
28
28
|
private GUIStyle _potentialMatchStyle;
|
|
29
29
|
private GUIStyle _defaultStyle;
|
|
30
|
+
private GUIStyle _leftAlignedStyle;
|
|
31
|
+
private GUIStyle _rightAlignedStyle;
|
|
30
32
|
|
|
31
33
|
private void OnEnable()
|
|
32
34
|
{
|
|
@@ -48,6 +50,14 @@
|
|
|
48
50
|
fontStyle = FontStyle.Bold,
|
|
49
51
|
};
|
|
50
52
|
_defaultStyle ??= new GUIStyle(EditorStyles.label);
|
|
53
|
+
_leftAlignedStyle ??= new GUIStyle(EditorStyles.label)
|
|
54
|
+
{
|
|
55
|
+
alignment = TextAnchor.MiddleLeft,
|
|
56
|
+
};
|
|
57
|
+
_rightAlignedStyle ??= new GUIStyle(EditorStyles.label)
|
|
58
|
+
{
|
|
59
|
+
alignment = TextAnchor.MiddleRight,
|
|
60
|
+
};
|
|
51
61
|
|
|
52
62
|
MessagingComponent component = target as MessagingComponent;
|
|
53
63
|
if (component == null)
|
|
@@ -209,11 +219,6 @@
|
|
|
209
219
|
{
|
|
210
220
|
style = _defaultStyle;
|
|
211
221
|
}
|
|
212
|
-
EditorGUILayout.LabelField(
|
|
213
|
-
"Context",
|
|
214
|
-
$"{unityObject.name} - {unityObject.GetType().Name}",
|
|
215
|
-
style
|
|
216
|
-
);
|
|
217
222
|
}
|
|
218
223
|
else
|
|
219
224
|
{
|
|
@@ -231,6 +236,33 @@
|
|
|
231
236
|
);
|
|
232
237
|
|
|
233
238
|
EditorGUILayout.LabelField(labelContent, valueContent, style);
|
|
239
|
+
if (context?.Object != null)
|
|
240
|
+
{
|
|
241
|
+
Object unityObject = context.Value.Object;
|
|
242
|
+
string label = "Context";
|
|
243
|
+
if (
|
|
244
|
+
typeof(ITargetedMessage).IsAssignableFrom(
|
|
245
|
+
globalEmissionData.message.MessageType
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
{
|
|
249
|
+
label = "Target";
|
|
250
|
+
}
|
|
251
|
+
else if (
|
|
252
|
+
typeof(IBroadcastMessage).IsAssignableFrom(
|
|
253
|
+
globalEmissionData.message.MessageType
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
{
|
|
257
|
+
label = "Source";
|
|
258
|
+
}
|
|
259
|
+
EditorGUILayout.ObjectField(
|
|
260
|
+
label,
|
|
261
|
+
unityObject,
|
|
262
|
+
typeof(Object),
|
|
263
|
+
true
|
|
264
|
+
);
|
|
265
|
+
}
|
|
234
266
|
}
|
|
235
267
|
}
|
|
236
268
|
}
|
|
@@ -306,16 +338,6 @@
|
|
|
306
338
|
{
|
|
307
339
|
using (new EditorGUILayout.VerticalScope("box"))
|
|
308
340
|
{
|
|
309
|
-
InstanceId? context = globalEmissionData.context;
|
|
310
|
-
if (context?.Object != null)
|
|
311
|
-
{
|
|
312
|
-
Object unityObject = context.Value.Object;
|
|
313
|
-
EditorGUILayout.LabelField(
|
|
314
|
-
"Context",
|
|
315
|
-
$"{unityObject.name} - {unityObject.GetType().Name}"
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
341
|
GUIContent labelContent = new("Message Type");
|
|
320
342
|
GUIContent valueContent = new(
|
|
321
343
|
globalEmissionData.message.MessageType.Name,
|
|
@@ -323,6 +345,35 @@
|
|
|
323
345
|
);
|
|
324
346
|
|
|
325
347
|
EditorGUILayout.LabelField(labelContent, valueContent);
|
|
348
|
+
|
|
349
|
+
InstanceId? context = globalEmissionData.context;
|
|
350
|
+
if (context?.Object != null)
|
|
351
|
+
{
|
|
352
|
+
Object unityObject = context.Value.Object;
|
|
353
|
+
string label = "Context";
|
|
354
|
+
if (
|
|
355
|
+
typeof(ITargetedMessage).IsAssignableFrom(
|
|
356
|
+
globalEmissionData.message.MessageType
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
{
|
|
360
|
+
label = "Target";
|
|
361
|
+
}
|
|
362
|
+
else if (
|
|
363
|
+
typeof(IBroadcastMessage).IsAssignableFrom(
|
|
364
|
+
globalEmissionData.message.MessageType
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
{
|
|
368
|
+
label = "Source";
|
|
369
|
+
}
|
|
370
|
+
EditorGUILayout.ObjectField(
|
|
371
|
+
label,
|
|
372
|
+
unityObject,
|
|
373
|
+
typeof(Object),
|
|
374
|
+
true
|
|
375
|
+
);
|
|
376
|
+
}
|
|
326
377
|
}
|
|
327
378
|
}
|
|
328
379
|
}
|
|
@@ -441,27 +492,36 @@
|
|
|
441
492
|
) in pagedRegistrations
|
|
442
493
|
)
|
|
443
494
|
{
|
|
444
|
-
|
|
495
|
+
using (new EditorGUILayout.VerticalScope("box"))
|
|
496
|
+
{
|
|
497
|
+
int callCount = token._callCounts.GetValueOrDefault(handle, 0);
|
|
445
498
|
|
|
446
|
-
|
|
447
|
-
|
|
499
|
+
string messageName = metadata.type?.Name ?? string.Empty;
|
|
500
|
+
GUIContent labelContent = new(messageName, $"Priority: {metadata.priority}");
|
|
501
|
+
GUIContent valueContent = new(
|
|
502
|
+
metadata.registrationType.ToString(),
|
|
503
|
+
$"Priority: {metadata.priority}"
|
|
504
|
+
);
|
|
448
505
|
|
|
449
|
-
|
|
506
|
+
EditorGUILayout.LabelField(labelContent, valueContent);
|
|
507
|
+
if (metadata.context?.Object != null)
|
|
508
|
+
{
|
|
509
|
+
Object unityObject = metadata.context.Value.Object;
|
|
510
|
+
string label = "Context";
|
|
511
|
+
if (typeof(ITargetedMessage).IsAssignableFrom(metadata.type))
|
|
512
|
+
{
|
|
513
|
+
label = "Target";
|
|
514
|
+
}
|
|
515
|
+
else if (typeof(IBroadcastMessage).IsAssignableFrom(metadata.type))
|
|
516
|
+
{
|
|
517
|
+
label = "Source";
|
|
518
|
+
}
|
|
519
|
+
EditorGUILayout.ObjectField(label, unityObject, typeof(Object), true);
|
|
520
|
+
}
|
|
450
521
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
EditorGUILayout.LabelField("Call Count", callCount.ToString());
|
|
454
|
-
if (metadata.context?.Object != null)
|
|
455
|
-
{
|
|
456
|
-
Object unityObject = metadata.context.Value.Object;
|
|
457
|
-
EditorGUILayout.LabelField(
|
|
458
|
-
"Context",
|
|
459
|
-
$"{unityObject.name} - {unityObject.GetType().Name}"
|
|
460
|
-
);
|
|
522
|
+
EditorGUILayout.LabelField("Call Count", callCount.ToString());
|
|
523
|
+
EditorGUILayout.Space();
|
|
461
524
|
}
|
|
462
|
-
|
|
463
|
-
EditorGUI.indentLevel--;
|
|
464
|
-
EditorGUILayout.Space();
|
|
465
525
|
}
|
|
466
526
|
EditorGUI.indentLevel--;
|
|
467
527
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
namespace DxMessaging.Core.Attributes
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
|
|
5
|
+
[AttributeUsage(
|
|
6
|
+
AttributeTargets.Class | AttributeTargets.Struct,
|
|
7
|
+
Inherited = false,
|
|
8
|
+
AllowMultiple = false
|
|
9
|
+
)]
|
|
10
|
+
public sealed class DxAutoConstructorAttribute : Attribute
|
|
11
|
+
{
|
|
12
|
+
public DxAutoConstructorAttribute() { }
|
|
13
|
+
}
|
|
14
|
+
}
|
package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Collections.Immutable;
|
|
6
|
+
using System.Diagnostics;
|
|
7
|
+
using System.Linq;
|
|
8
|
+
using System.Text;
|
|
9
|
+
using System.Threading;
|
|
10
|
+
using Microsoft.CodeAnalysis;
|
|
11
|
+
using Microsoft.CodeAnalysis.CSharp;
|
|
12
|
+
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
13
|
+
using Microsoft.CodeAnalysis.Text;
|
|
14
|
+
|
|
15
|
+
[Generator(LanguageNames.CSharp)]
|
|
16
|
+
public sealed class DxAutoConstructorGenerator : IIncrementalGenerator
|
|
17
|
+
{
|
|
18
|
+
private const string AutoGenConstructorAttrFullName =
|
|
19
|
+
"DxMessaging.Core.Attributes.DxAutoConstructorAttribute";
|
|
20
|
+
|
|
21
|
+
// Info needed during generation for a valid type
|
|
22
|
+
private record struct TypeToGenerateInfo(
|
|
23
|
+
INamedTypeSymbol TypeSymbol,
|
|
24
|
+
TypeDeclarationSyntax DeclarationSyntax,
|
|
25
|
+
ImmutableArray<IFieldSymbol> FieldsToInject // Public readonly non-static fields
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
29
|
+
{
|
|
30
|
+
// Find all class/struct/record declarations that have attribute lists
|
|
31
|
+
IncrementalValuesProvider<TypeDeclarationSyntax> potentialTypeDeclarations =
|
|
32
|
+
context.SyntaxProvider.CreateSyntaxProvider(
|
|
33
|
+
predicate: static (node, _) => IsSyntaxTargetForGeneration(node),
|
|
34
|
+
transform: static (ctx, _) => (TypeDeclarationSyntax)ctx.Node
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Get semantic info for potential types
|
|
38
|
+
IncrementalValuesProvider<TypeToGenerateInfo?> semanticTargets =
|
|
39
|
+
potentialTypeDeclarations
|
|
40
|
+
.Combine(context.CompilationProvider)
|
|
41
|
+
.Select(
|
|
42
|
+
static (data, ct) =>
|
|
43
|
+
GetSemanticTargetForGeneration(data.Left, data.Right, ct)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Filter out nulls (types that aren't valid for auto-gen constructor)
|
|
47
|
+
IncrementalValuesProvider<TypeToGenerateInfo> validSemanticTargets = semanticTargets
|
|
48
|
+
.Where(static target => target.HasValue)
|
|
49
|
+
.Select(static (target, _) => target!.Value);
|
|
50
|
+
|
|
51
|
+
// Collect all valid types for generation
|
|
52
|
+
IncrementalValueProvider<ImmutableArray<TypeToGenerateInfo>> collectedTargets =
|
|
53
|
+
validSemanticTargets.Collect();
|
|
54
|
+
|
|
55
|
+
IncrementalValueProvider<(
|
|
56
|
+
Compilation,
|
|
57
|
+
ImmutableArray<TypeToGenerateInfo>
|
|
58
|
+
)> compilationAndTypes = context.CompilationProvider.Combine(collectedTargets);
|
|
59
|
+
|
|
60
|
+
// Register the source output step
|
|
61
|
+
context.RegisterSourceOutput(
|
|
62
|
+
compilationAndTypes,
|
|
63
|
+
static (spc, source) => Execute(source.Item1, source.Item2, spc)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
|
|
68
|
+
node is TypeDeclarationSyntax { AttributeLists.Count: > 0 } typeDecl
|
|
69
|
+
&& (
|
|
70
|
+
typeDecl.IsKind(SyntaxKind.ClassDeclaration)
|
|
71
|
+
|| typeDecl.IsKind(SyntaxKind.StructDeclaration)
|
|
72
|
+
|| typeDecl.IsKind(SyntaxKind.RecordDeclaration)
|
|
73
|
+
|| typeDecl.IsKind(SyntaxKind.RecordStructDeclaration)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
private static TypeToGenerateInfo? GetSemanticTargetForGeneration(
|
|
77
|
+
TypeDeclarationSyntax typeDeclarationSyntax,
|
|
78
|
+
Compilation compilation,
|
|
79
|
+
CancellationToken cancellationToken
|
|
80
|
+
)
|
|
81
|
+
{
|
|
82
|
+
SemanticModel semanticModel = compilation.GetSemanticModel(
|
|
83
|
+
typeDeclarationSyntax.SyntaxTree
|
|
84
|
+
);
|
|
85
|
+
if (
|
|
86
|
+
semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, cancellationToken)
|
|
87
|
+
is not { } typeSymbol
|
|
88
|
+
)
|
|
89
|
+
{
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if the type has the DxAutoGenConstructor attribute
|
|
94
|
+
bool hasAutoGenConstructorAttribute = false;
|
|
95
|
+
foreach (AttributeData attributeData in typeSymbol.GetAttributes())
|
|
96
|
+
{
|
|
97
|
+
cancellationToken.ThrowIfCancellationRequested();
|
|
98
|
+
if (
|
|
99
|
+
attributeData.AttributeClass?.ToDisplayString()
|
|
100
|
+
== AutoGenConstructorAttrFullName
|
|
101
|
+
)
|
|
102
|
+
{
|
|
103
|
+
hasAutoGenConstructorAttribute = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!hasAutoGenConstructorAttribute)
|
|
109
|
+
{
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Find public readonly non-static fields in declaration order
|
|
114
|
+
ImmutableArray<IFieldSymbol> fieldsToInject = typeSymbol
|
|
115
|
+
.GetMembers()
|
|
116
|
+
.OfType<IFieldSymbol>()
|
|
117
|
+
.Where(f => f.DeclaredAccessibility == Accessibility.Public && !f.IsStatic)
|
|
118
|
+
.OrderBy(f => f.DeclaringSyntaxReferences.FirstOrDefault()?.Span.Start ?? 0) // Order by declaration in source
|
|
119
|
+
.ToImmutableArray();
|
|
120
|
+
|
|
121
|
+
// If there are no relevant fields, we don't need to generate a constructor
|
|
122
|
+
if (fieldsToInject.Length == 0)
|
|
123
|
+
{
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return new TypeToGenerateInfo(typeSymbol, typeDeclarationSyntax, fieldsToInject);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static void Execute(
|
|
131
|
+
Compilation compilation,
|
|
132
|
+
ImmutableArray<TypeToGenerateInfo> typesToGenerate,
|
|
133
|
+
SourceProductionContext context
|
|
134
|
+
)
|
|
135
|
+
{
|
|
136
|
+
if (typesToGenerate.IsDefaultOrEmpty)
|
|
137
|
+
{
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Use a HashSet to track types already processed to avoid duplicate generation for partial classes
|
|
142
|
+
HashSet<INamedTypeSymbol> processedTypes = new(SymbolEqualityComparer.Default);
|
|
143
|
+
|
|
144
|
+
foreach (TypeToGenerateInfo typeInfo in typesToGenerate)
|
|
145
|
+
{
|
|
146
|
+
if (!processedTypes.Add(typeInfo.TypeSymbol))
|
|
147
|
+
{
|
|
148
|
+
continue; // Already processed this type (e.g., from another partial definition)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
context.CancellationToken.ThrowIfCancellationRequested();
|
|
152
|
+
|
|
153
|
+
// Generate the partial class/struct with the constructor
|
|
154
|
+
string generatedSource = GenerateConstructorSource(
|
|
155
|
+
typeInfo.TypeSymbol,
|
|
156
|
+
typeInfo.FieldsToInject
|
|
157
|
+
);
|
|
158
|
+
string hintName =
|
|
159
|
+
$"{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.AutoGenConstructor.g.cs"
|
|
160
|
+
.Replace("global::", "")
|
|
161
|
+
.Replace("<", "_")
|
|
162
|
+
.Replace(">", "_")
|
|
163
|
+
.Replace(",", "_"); // Clean hint name for file system
|
|
164
|
+
|
|
165
|
+
context.AddSource(hintName, SourceText.From(generatedSource, Encoding.UTF8));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private static string GenerateConstructorSource(
|
|
170
|
+
INamedTypeSymbol typeSymbol,
|
|
171
|
+
ImmutableArray<IFieldSymbol> fieldsToInject
|
|
172
|
+
)
|
|
173
|
+
{
|
|
174
|
+
string namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
|
|
175
|
+
? string.Empty
|
|
176
|
+
: typeSymbol.ContainingNamespace.ToDisplayString();
|
|
177
|
+
string namespaceBlockOpen = string.IsNullOrEmpty(namespaceName)
|
|
178
|
+
? "namespace\n{"
|
|
179
|
+
: $"namespace {namespaceName}\n{{";
|
|
180
|
+
const string namespaceBlockClose = "}";
|
|
181
|
+
const string indent = " ";
|
|
182
|
+
|
|
183
|
+
string typeName = typeSymbol.ToDisplayString(
|
|
184
|
+
SymbolDisplayFormat.MinimallyQualifiedFormat
|
|
185
|
+
);
|
|
186
|
+
string typeKind = typeSymbol.TypeKind switch
|
|
187
|
+
{
|
|
188
|
+
TypeKind.Class => typeSymbol.IsRecord ? "record class" : "class",
|
|
189
|
+
TypeKind.Struct => typeSymbol.IsRecord ? "record struct" : "struct",
|
|
190
|
+
_ => throw new InvalidOperationException(
|
|
191
|
+
"Unsupported type kind for constructor generation"
|
|
192
|
+
),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
string typeAccessibility = typeSymbol.DeclaredAccessibility switch
|
|
196
|
+
{
|
|
197
|
+
Accessibility.Public => "public",
|
|
198
|
+
Accessibility.Protected => "protected",
|
|
199
|
+
Accessibility.Private => "private",
|
|
200
|
+
Accessibility.Internal => "internal",
|
|
201
|
+
_ => "internal", // Default to internal if not public or protected
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
string constructorAccessibility = "public"; // Always public as requested
|
|
205
|
+
|
|
206
|
+
// Generate constructor parameters and body assignments
|
|
207
|
+
StringBuilder constructorParams = new();
|
|
208
|
+
StringBuilder constructorBody = new();
|
|
209
|
+
|
|
210
|
+
SymbolDisplayFormat fieldTypeFormat =
|
|
211
|
+
SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
|
|
212
|
+
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
for (int i = 0; i < fieldsToInject.Length; i++)
|
|
216
|
+
{
|
|
217
|
+
IFieldSymbol field = fieldsToInject[i];
|
|
218
|
+
string fieldType = field.Type.ToDisplayString(fieldTypeFormat);
|
|
219
|
+
string fieldName = field.Name;
|
|
220
|
+
|
|
221
|
+
constructorParams.Append($"{fieldType} {fieldName}");
|
|
222
|
+
if (i < fieldsToInject.Length - 1)
|
|
223
|
+
{
|
|
224
|
+
constructorParams.Append(", ");
|
|
225
|
+
}
|
|
226
|
+
constructorBody.AppendLine($"{indent}{indent} this.{fieldName} = {fieldName};");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return $$"""
|
|
230
|
+
// <auto-generated by DxAutoGenConstructorGenerator/>
|
|
231
|
+
#pragma warning disable
|
|
232
|
+
#nullable enable annotations
|
|
233
|
+
|
|
234
|
+
{{namespaceBlockOpen}}
|
|
235
|
+
{{indent}}{{typeAccessibility}} partial {{typeKind}} {{typeName}}
|
|
236
|
+
{{indent}}{
|
|
237
|
+
{{indent}} /// <summary>
|
|
238
|
+
{{indent}} /// Auto-generated constructor by DxAutoGenConstructorGenerator.
|
|
239
|
+
{{indent}} /// </summary>
|
|
240
|
+
{{indent}} {{constructorAccessibility}} {{typeSymbol.Name}}({{constructorParams}})
|
|
241
|
+
{{indent}} {
|
|
242
|
+
{{constructorBody}}
|
|
243
|
+
{{indent}} }
|
|
244
|
+
{{indent}}}
|
|
245
|
+
{{namespaceBlockClose}}
|
|
246
|
+
""";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
package/package.json
CHANGED