com.wallstop-studios.dxmessaging 2.0.0-rc26.2 → 2.0.0-rc26.4

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.
@@ -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
- int callCount = token._callCounts.GetValueOrDefault(handle, 0);
495
+ using (new EditorGUILayout.VerticalScope("box"))
496
+ {
497
+ int callCount = token._callCounts.GetValueOrDefault(handle, 0);
445
498
 
446
- string messageName = metadata.type?.Name ?? string.Empty;
447
- EditorGUILayout.LabelField(messageName, EditorStyles.boldLabel);
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
- EditorGUI.indentLevel++;
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
- EditorGUILayout.LabelField("Type", metadata.registrationType.ToString());
452
- EditorGUILayout.LabelField("Priority", metadata.priority.ToString());
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
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: bef6adb9baf545129cb9a841b13da451
3
+ timeCreated: 1749776355
@@ -0,0 +1,250 @@
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) || typeInfo.FieldsToInject.Length == 0)
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
+
175
+ string namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
176
+ ? string.Empty
177
+ : typeSymbol.ContainingNamespace.ToDisplayString();
178
+ string namespaceBlockOpen = string.IsNullOrEmpty(namespaceName)
179
+ ? string.Empty
180
+ : $"namespace {namespaceName}\n{{";
181
+ string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
182
+ const string indent = " ";
183
+
184
+ string typeName = typeSymbol.ToDisplayString(
185
+ SymbolDisplayFormat.MinimallyQualifiedFormat
186
+ );
187
+ string typeKind = typeSymbol.TypeKind switch
188
+ {
189
+ TypeKind.Class => typeSymbol.IsRecord ? "record class" : "class",
190
+ TypeKind.Struct => typeSymbol.IsRecord ? "record struct" : "struct",
191
+ _ => throw new InvalidOperationException(
192
+ "Unsupported type kind for constructor generation"
193
+ ),
194
+ };
195
+
196
+ string typeAccessibility = typeSymbol.DeclaredAccessibility switch
197
+ {
198
+ Accessibility.Public => "public",
199
+ Accessibility.Protected => "protected",
200
+ Accessibility.Private => "private",
201
+ Accessibility.Internal => "internal",
202
+ _ => "internal", // Default to internal if not public or protected
203
+ };
204
+
205
+ string constructorAccessibility = "public"; // Always public as requested
206
+
207
+ // Generate constructor parameters and body assignments
208
+ StringBuilder constructorParams = new();
209
+ StringBuilder constructorBody = new();
210
+
211
+ SymbolDisplayFormat fieldTypeFormat =
212
+ SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
213
+ SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
214
+ );
215
+
216
+ for (int i = 0; i < fieldsToInject.Length; i++)
217
+ {
218
+ IFieldSymbol field = fieldsToInject[i];
219
+ string fieldType = field.Type.ToDisplayString(fieldTypeFormat);
220
+ string fieldName = field.Name;
221
+
222
+ constructorParams.Append($"{fieldType} {fieldName}");
223
+ if (i < fieldsToInject.Length - 1)
224
+ {
225
+ constructorParams.Append(", ");
226
+ }
227
+ constructorBody.AppendLine($"{indent}{indent} this.{fieldName} = {fieldName};");
228
+ }
229
+
230
+ return $$"""
231
+ // <auto-generated by DxAutoGenConstructorGenerator/>
232
+ #pragma warning disable
233
+ #nullable enable annotations
234
+
235
+ {{namespaceBlockOpen}}
236
+ {{indent}}{{typeAccessibility}} partial {{typeKind}} {{typeName}}
237
+ {{indent}}{
238
+ {{indent}} /// <summary>
239
+ {{indent}} /// Auto-generated constructor by DxAutoGenConstructorGenerator.
240
+ {{indent}} /// </summary>
241
+ {{indent}} {{constructorAccessibility}} {{typeSymbol.Name}}({{constructorParams}})
242
+ {{indent}} {
243
+ {{constructorBody}}
244
+ {{indent}} }
245
+ {{indent}}}
246
+ {{namespaceBlockClose}}
247
+ """;
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 1b72dc77d3697c1d6bc2f3e1d262f9af
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -258,9 +258,9 @@
258
258
  ? string.Empty
259
259
  : typeSymbol.ContainingNamespace.ToDisplayString();
260
260
  string namespaceBlockOpen = string.IsNullOrEmpty(namespaceName)
261
- ? "namespace\n{"
261
+ ? string.Empty
262
262
  : $"namespace {namespaceName}\n{{";
263
- const string namespaceBlockClose = "}";
263
+ string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
264
264
  const string indent = " ";
265
265
 
266
266
  string typeNameWithGenerics = typeSymbol.ToDisplayString(
@@ -279,10 +279,12 @@
279
279
 
280
280
  string accessibility = typeSymbol.DeclaredAccessibility switch
281
281
  {
282
- Accessibility.Public => "public ",
283
- Accessibility.Internal => "internal ",
282
+ Accessibility.Public => "public",
283
+ Accessibility.Protected => "protected",
284
+ Accessibility.Private => "private",
285
+ Accessibility.Internal => "internal",
284
286
  // Add others if necessary, default to internal if restrictive
285
- _ => "internal ",
287
+ _ => "internal",
286
288
  };
287
289
 
288
290
  string interfaceDeclaration = $", global::{targetInterfaceFullName}";
@@ -294,7 +296,7 @@
294
296
 
295
297
  {{namespaceBlockOpen}}
296
298
  {{indent}}// Partial implementation for {{typeNameWithGenerics}} to implement {{BaseInterfaceFullName}}
297
- {{indent}}{{accessibility}}partial {{typeKind}} {{typeNameWithGenerics}} : global::{{BaseInterfaceFullName}} {{interfaceDeclaration}}
299
+ {{indent}}{{accessibility}} partial {{typeKind}} {{typeNameWithGenerics}} : global::{{BaseInterfaceFullName}} {{interfaceDeclaration}}
298
300
  {{indent}}{
299
301
  {{indent}} /// <inheritdoc/>
300
302
  {{indent}} public global::System.Type MessageType => typeof({{fullyQualifiedName}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.dxmessaging",
3
- "version": "2.0.0-rc26.2",
3
+ "version": "2.0.0-rc26.4",
4
4
  "displayName": "DxMessaging",
5
5
  "description": "Synchronous Event Bus for Unity",
6
6
  "unity": "2021.3",
@@ -34,3 +34,5 @@
34
34
  }
35
35
 
36
36
 
37
+
38
+