com.wallstop-studios.dxmessaging 3.0.1 → 3.1.0

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +211 -2
  2. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +69 -62
  3. package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
  4. package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +3 -3
  5. package/Editor/Analyzers/System.Collections.Immutable.dll.meta +3 -3
  6. package/Editor/Analyzers/System.Reflection.Metadata.dll.meta +3 -3
  7. package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +3 -3
  8. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  9. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +15 -2
  10. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  11. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +2 -2
  12. package/Editor/AssemblyInfo.cs.meta +9 -1
  13. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +24 -15
  14. package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +9 -1
  15. package/Editor/DxMessagingEditorIdle.cs +62 -0
  16. package/{Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta → Editor/DxMessagingEditorIdle.cs.meta} +1 -1
  17. package/Editor/DxMessagingEditorInitializer.cs +112 -15
  18. package/Editor/DxMessagingEditorInitializer.cs.meta +9 -1
  19. package/Editor/DxMessagingEditorLog.cs +32 -0
  20. package/Editor/DxMessagingEditorLog.cs.meta +11 -0
  21. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +135 -12
  22. package/Editor/Settings/DxMessagingSettings.cs +92 -31
  23. package/Editor/Settings/DxMessagingSettings.cs.meta +9 -1
  24. package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +9 -1
  25. package/Editor/SetupCscRsp.cs +339 -173
  26. package/Editor/SetupCscRsp.cs.meta +9 -1
  27. package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
  28. package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +9 -1
  29. package/README.md +17 -18
  30. package/Runtime/AssemblyInfo.cs.meta +9 -1
  31. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +9 -1
  32. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +9 -1
  33. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +2 -4
  34. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +9 -1
  35. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +9 -1
  36. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +9 -1
  37. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs +56 -0
  38. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs.meta +11 -0
  39. package/Runtime/Core/DataStructure/CyclicBuffer.cs +44 -26
  40. package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +9 -1
  41. package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +9 -1
  42. package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +9 -1
  43. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +9 -1
  44. package/Runtime/Core/Extensions/EnumExtensions.cs +6 -5
  45. package/Runtime/Core/Extensions/EnumExtensions.cs.meta +9 -1
  46. package/Runtime/Core/Extensions/IListExtensions.cs.meta +9 -1
  47. package/Runtime/Core/Extensions/MessageExtensions.cs +0 -60
  48. package/Runtime/Core/Helper/MessageCache.cs.meta +9 -1
  49. package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +9 -1
  50. package/Runtime/Core/InstanceId.cs +25 -1
  51. package/Runtime/Core/Internal/DxUnsafe.cs +60 -0
  52. package/Runtime/Core/Internal/DxUnsafe.cs.meta +11 -0
  53. package/Runtime/Core/Internal/FlatDispatch.cs +198 -0
  54. package/Runtime/Core/Internal/FlatDispatch.cs.meta +11 -0
  55. package/Runtime/Core/Internal/TypedSlots.cs +5 -21
  56. package/Runtime/Core/MessageBus/IMessageBus.cs +12 -12
  57. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +1 -0
  58. package/Runtime/Core/MessageBus/Internal/BusSlots.cs +7 -6
  59. package/Runtime/Core/MessageBus/MessageBus.cs +2313 -2936
  60. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +187 -14
  61. package/Runtime/Core/MessageHandler.cs +1023 -1143
  62. package/Runtime/Core/MessageRegistrationToken.cs +425 -47
  63. package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +9 -1
  64. package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +9 -1
  65. package/Runtime/Core/Messages/StringMessage.cs.meta +9 -1
  66. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +9 -1
  67. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +9 -1
  68. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +9 -1
  69. package/Runtime/Unity/MessageAwareComponent.cs +46 -1
  70. package/Runtime/Unity/MessagingComponent.cs +43 -10
  71. package/Runtime/WallstopStudios.DxMessaging.asmdef +1 -1
  72. package/Samples~/DI/README.md +7 -7
  73. package/SourceGenerators/Directory.Build.props +50 -3
  74. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +96 -63
  75. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +745 -87
  76. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +9 -1
  77. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +39 -46
  78. package/SourceGenerators/global.json +7 -0
  79. package/SourceGenerators/global.json.meta +7 -0
  80. package/package.json +27 -40
  81. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +0 -51
@@ -11,8 +11,8 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
11
11
  using Microsoft.CodeAnalysis.CSharp.Syntax;
12
12
  using Microsoft.CodeAnalysis.Text;
13
13
 
14
- [Generator(LanguageNames.CSharp)]
15
- public sealed class DxMessageIdGenerator : IIncrementalGenerator
14
+ [Generator]
15
+ public sealed class DxMessageIdGenerator : ISourceGenerator
16
16
  {
17
17
  private static readonly DiagnosticDescriptor NonPartialContainerDiagnostic = new(
18
18
  id: "DXMSG003",
@@ -67,62 +67,137 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
67
67
  INamedTypeSymbol TypeSymbol,
68
68
  TypeDeclarationSyntax DeclarationSyntax,
69
69
  string TargetInterfaceFullName,
70
+ bool RegistersUntargetedAotBridge,
71
+ bool RegistersTargetedAotBridge,
72
+ bool RegistersBroadcastAotBridge,
70
73
  bool HasConflictingMessageAttributes
71
74
  );
72
75
 
76
+ private record struct AotRegistrarInfo(
77
+ INamedTypeSymbol TypeSymbol,
78
+ bool RegistersUntargetedAotBridge,
79
+ bool RegistersTargetedAotBridge,
80
+ bool RegistersBroadcastAotBridge,
81
+ bool HasMessageAttribute
82
+ );
83
+
84
+ private record struct AotRegistrarSourceInfo(
85
+ AotRegistrarInfo Info,
86
+ string TypeName,
87
+ string Suffix
88
+ );
89
+
90
+ private readonly struct SemanticTargetInfo
91
+ {
92
+ public SemanticTargetInfo(
93
+ MessageToGenerateInfo? messageToGenerate,
94
+ AotRegistrarInfo? aotRegistrar
95
+ )
96
+ {
97
+ MessageToGenerate = messageToGenerate;
98
+ AotRegistrar = aotRegistrar;
99
+ }
100
+
101
+ public MessageToGenerateInfo? MessageToGenerate { get; }
102
+ public AotRegistrarInfo? AotRegistrar { get; }
103
+ }
104
+
105
+ private enum AotBridgeKind
106
+ {
107
+ Untargeted,
108
+ Targeted,
109
+ Broadcast,
110
+ }
111
+
73
112
  /// <summary>
74
- /// Configures the incremental generator pipeline that assigns deterministic message identifiers.
113
+ /// Configures syntax collection for deterministic message identifier generation.
75
114
  /// </summary>
76
115
  /// <param name="context">Initialization context provided by Roslyn.</param>
77
- public void Initialize(IncrementalGeneratorInitializationContext context)
116
+ public void Initialize(GeneratorInitializationContext context)
117
+ {
118
+ context.RegisterForSyntaxNotifications(static () => new TypeSyntaxReceiver());
119
+ }
120
+
121
+ public void Execute(GeneratorExecutionContext context)
78
122
  {
79
- // Find all class/struct/record declarations with attributes
80
- IncrementalValuesProvider<TypeDeclarationSyntax> potentialTypeDeclarations =
81
- context.SyntaxProvider.CreateSyntaxProvider(
82
- predicate: static (node, _) => IsSyntaxTargetForGeneration(node),
83
- transform: static (ctx, _) => (TypeDeclarationSyntax)ctx.Node
123
+ if (context.SyntaxReceiver is not TypeSyntaxReceiver receiver)
124
+ {
125
+ return;
126
+ }
127
+
128
+ List<MessageToGenerateInfo> typesToGenerate = new();
129
+ List<AotRegistrarInfo> aotTypes = new();
130
+ foreach (TypeDeclarationSyntax typeDeclarationSyntax in receiver.Candidates)
131
+ {
132
+ SemanticTargetInfo? targetInfo = GetSemanticTarget(
133
+ typeDeclarationSyntax,
134
+ context.Compilation,
135
+ context.CancellationToken
84
136
  );
137
+ if (!targetInfo.HasValue)
138
+ {
139
+ continue;
140
+ }
85
141
 
86
- // Get semantic info for potential types
87
- IncrementalValuesProvider<MessageToGenerateInfo?> semanticTargets =
88
- potentialTypeDeclarations
89
- .Combine(context.CompilationProvider)
90
- .Select(
91
- static (data, ct) =>
92
- GetSemanticTargetForGeneration(data.Left, data.Right, ct)
93
- );
142
+ if (targetInfo.Value.MessageToGenerate.HasValue)
143
+ {
144
+ typesToGenerate.Add(targetInfo.Value.MessageToGenerate.Value);
145
+ }
146
+ if (targetInfo.Value.AotRegistrar.HasValue)
147
+ {
148
+ aotTypes.Add(targetInfo.Value.AotRegistrar.Value);
149
+ }
150
+ }
94
151
 
95
- // Filter out nulls (types that aren't valid messages)
96
- IncrementalValuesProvider<MessageToGenerateInfo> validSemanticTargets = semanticTargets
97
- .Where(static target => target.HasValue)
98
- .Select(static (target, _) => target!.Value);
152
+ Execute(typesToGenerate.ToImmutableArray(), aotTypes.ToImmutableArray(), context);
153
+ }
99
154
 
100
- // Group by type symbol to handle partial classes correctly and check for multiple attributes
101
- IncrementalValueProvider<ImmutableArray<MessageToGenerateInfo>> collectedTargets =
102
- validSemanticTargets.Collect();
155
+ private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
156
+ {
157
+ if (node is not TypeDeclarationSyntax typeDecl || !IsSupportedTypeDeclaration(typeDecl))
158
+ {
159
+ return false;
160
+ }
103
161
 
104
- IncrementalValueProvider<(
105
- Compilation,
106
- ImmutableArray<MessageToGenerateInfo>
107
- )> compilationAndTypes = context.CompilationProvider.Combine(collectedTargets);
162
+ return typeDecl.AttributeLists.Count > 0 || HasRelevantMessageBaseType(typeDecl);
163
+ }
108
164
 
109
- // Register the source output step
110
- context.RegisterSourceOutput(
111
- compilationAndTypes,
112
- static (spc, source) => Execute(source.Item1, source.Item2, spc)
113
- );
165
+ private static bool IsSupportedTypeDeclaration(TypeDeclarationSyntax typeDeclarationSyntax)
166
+ {
167
+ return typeDeclarationSyntax.IsKind(SyntaxKind.ClassDeclaration)
168
+ || typeDeclarationSyntax.IsKind(SyntaxKind.StructDeclaration)
169
+ || typeDeclarationSyntax.IsKind(SyntaxKind.RecordDeclaration)
170
+ || string.Equals(
171
+ typeDeclarationSyntax.Kind().ToString(),
172
+ "RecordStructDeclaration",
173
+ StringComparison.Ordinal
174
+ );
114
175
  }
115
176
 
116
- private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
117
- node is TypeDeclarationSyntax { AttributeLists.Count: > 0 } typeDecl
118
- && (
119
- typeDecl.IsKind(SyntaxKind.ClassDeclaration)
120
- || typeDecl.IsKind(SyntaxKind.StructDeclaration)
121
- || typeDecl.IsKind(SyntaxKind.RecordDeclaration)
122
- || typeDecl.IsKind(SyntaxKind.RecordStructDeclaration)
123
- );
177
+ private static bool HasRelevantMessageBaseType(TypeDeclarationSyntax typeDeclarationSyntax)
178
+ {
179
+ if (typeDeclarationSyntax.BaseList is null)
180
+ {
181
+ return false;
182
+ }
183
+
184
+ foreach (BaseTypeSyntax baseType in typeDeclarationSyntax.BaseList.Types)
185
+ {
186
+ string baseTypeName = baseType.Type.ToString();
187
+ if (
188
+ baseTypeName.IndexOf("IUntargetedMessage", StringComparison.Ordinal) >= 0
189
+ || baseTypeName.IndexOf("ITargetedMessage", StringComparison.Ordinal) >= 0
190
+ || baseTypeName.IndexOf("IBroadcastMessage", StringComparison.Ordinal) >= 0
191
+ )
192
+ {
193
+ return true;
194
+ }
195
+ }
124
196
 
125
- private static MessageToGenerateInfo? GetSemanticTargetForGeneration(
197
+ return false;
198
+ }
199
+
200
+ private static SemanticTargetInfo? GetSemanticTarget(
126
201
  TypeDeclarationSyntax typeDeclarationSyntax,
127
202
  Compilation compilation,
128
203
  CancellationToken cancellationToken
@@ -150,6 +225,9 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
150
225
 
151
226
  string foundTargetInterface = null;
152
227
  bool multipleAttributes = false;
228
+ bool hasUntargetedAttribute = false;
229
+ bool hasTargetedAttribute = false;
230
+ bool hasBroadcastAttribute = false;
153
231
 
154
232
  // Check attributes to find the specific message type (Broadcast, Targeted, etc.)
155
233
  foreach (AttributeData attributeData in typeSymbol.GetAttributes())
@@ -162,12 +240,15 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
162
240
  {
163
241
  case BroadcastAttrFullName:
164
242
  targetInterfaceForThisAttribute = BroadcastInterfaceFullName;
243
+ hasBroadcastAttribute = true;
165
244
  break;
166
245
  case TargetedAttrFullName:
167
246
  targetInterfaceForThisAttribute = TargetedInterfaceFullName;
247
+ hasTargetedAttribute = true;
168
248
  break;
169
249
  case UntargetedAttrFullName:
170
250
  targetInterfaceForThisAttribute = UntargetedInterfaceFullName;
251
+ hasUntargetedAttribute = true;
171
252
  break;
172
253
  }
173
254
 
@@ -192,28 +273,92 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
192
273
 
193
274
  if (foundTargetInterface == null && !multipleAttributes)
194
275
  {
195
- return null;
276
+ bool registersUntargeted = ImplementsInterface(
277
+ typeSymbol,
278
+ UntargetedInterfaceFullName
279
+ );
280
+ bool registersTargeted = ImplementsInterface(typeSymbol, TargetedInterfaceFullName);
281
+ bool registersBroadcast = ImplementsInterface(
282
+ typeSymbol,
283
+ BroadcastInterfaceFullName
284
+ );
285
+
286
+ if (!registersUntargeted && !registersTargeted && !registersBroadcast)
287
+ {
288
+ return null;
289
+ }
290
+
291
+ AotRegistrarInfo? manualRegistrar = ContainsTypeParameters(typeSymbol)
292
+ ? null
293
+ : new AotRegistrarInfo(
294
+ typeSymbol,
295
+ registersUntargeted,
296
+ registersTargeted,
297
+ registersBroadcast,
298
+ HasMessageAttribute: false
299
+ );
300
+ return new SemanticTargetInfo(null, manualRegistrar);
196
301
  }
197
302
 
198
- return new MessageToGenerateInfo(
303
+ bool implementsUntargeted = ImplementsInterface(
304
+ typeSymbol,
305
+ UntargetedInterfaceFullName
306
+ );
307
+ bool implementsTargeted = ImplementsInterface(typeSymbol, TargetedInterfaceFullName);
308
+ bool implementsBroadcast = ImplementsInterface(typeSymbol, BroadcastInterfaceFullName);
309
+
310
+ MessageToGenerateInfo messageToGenerate = new MessageToGenerateInfo(
199
311
  typeSymbol,
200
312
  typeDeclarationSyntax,
201
313
  foundTargetInterface,
314
+ hasUntargetedAttribute || implementsUntargeted,
315
+ hasTargetedAttribute || implementsTargeted,
316
+ hasBroadcastAttribute || implementsBroadcast,
202
317
  multipleAttributes
203
318
  );
319
+
320
+ AotRegistrarInfo? attributedRegistrar =
321
+ multipleAttributes || ContainsTypeParameters(typeSymbol)
322
+ ? null
323
+ : new AotRegistrarInfo(
324
+ typeSymbol,
325
+ hasUntargetedAttribute || implementsUntargeted,
326
+ hasTargetedAttribute || implementsTargeted,
327
+ hasBroadcastAttribute || implementsBroadcast,
328
+ HasMessageAttribute: true
329
+ );
330
+
331
+ return new SemanticTargetInfo(messageToGenerate, attributedRegistrar);
204
332
  }
205
333
 
206
- private static void Execute(
207
- Compilation compilation,
208
- ImmutableArray<MessageToGenerateInfo> typesToGenerate,
209
- SourceProductionContext context
334
+ private static bool ImplementsInterface(
335
+ INamedTypeSymbol typeSymbol,
336
+ string interfaceFullName
210
337
  )
211
338
  {
212
- if (typesToGenerate.IsDefaultOrEmpty)
339
+ foreach (INamedTypeSymbol interfaceSymbol in typeSymbol.AllInterfaces)
213
340
  {
214
- return;
341
+ if (
342
+ string.Equals(
343
+ interfaceSymbol.ToDisplayString(),
344
+ interfaceFullName,
345
+ StringComparison.Ordinal
346
+ )
347
+ )
348
+ {
349
+ return true;
350
+ }
215
351
  }
216
352
 
353
+ return false;
354
+ }
355
+
356
+ private static void Execute(
357
+ ImmutableArray<MessageToGenerateInfo> typesToGenerate,
358
+ ImmutableArray<AotRegistrarInfo> aotTypes,
359
+ GeneratorExecutionContext context
360
+ )
361
+ {
217
362
  // --- Step 1: Filter out types with multiple attributes applied ---
218
363
  Dictionary<ISymbol, MessageToGenerateInfo> uniqueTypes = new Dictionary<
219
364
  ISymbol,
@@ -282,11 +427,6 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
282
427
  }
283
428
  }
284
429
 
285
- if (uniqueTypes.Count == 0)
286
- {
287
- return;
288
- }
289
-
290
430
  List<MessageToGenerateInfo> validSingleAttrTypes = new List<MessageToGenerateInfo>();
291
431
  foreach (KeyValuePair<ISymbol, MessageToGenerateInfo> entry in uniqueTypes)
292
432
  {
@@ -298,12 +438,10 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
298
438
  validSingleAttrTypes.Add(entry.Value);
299
439
  }
300
440
 
301
- if (validSingleAttrTypes.Count == 0)
302
- {
303
- return;
304
- }
305
-
306
441
  // --- Step 2: Generate sources for each valid message type ---
442
+ HashSet<ISymbol> generatedAttributedTypes = new HashSet<ISymbol>(
443
+ SymbolEqualityComparer.Default
444
+ );
307
445
  foreach (MessageToGenerateInfo messageInfo in validSingleAttrTypes)
308
446
  {
309
447
  context.CancellationToken.ThrowIfCancellationRequested();
@@ -363,10 +501,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
363
501
  }
364
502
 
365
503
  // Generate the partial IMessage implementation source
366
- string implSource = GenerateImplementationSource(
367
- targetInterfaceFullName,
368
- messageInfo.TypeSymbol
369
- );
504
+ string implSource = GenerateImplementationSource(messageInfo);
370
505
  string implHintName =
371
506
  $"{messageInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.IMessage.g.cs"
372
507
  .Replace("global::", "")
@@ -375,15 +510,31 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
375
510
  .Replace(",", "_"); // Clean hint name
376
511
 
377
512
  context.AddSource(implHintName, SourceText.From(implSource, Encoding.UTF8));
513
+ generatedAttributedTypes.Add(messageInfo.TypeSymbol);
514
+ }
515
+
516
+ GenerateTopLevelAotRegistrar(aotTypes, generatedAttributedTypes, context);
517
+ }
518
+
519
+ private sealed class TypeSyntaxReceiver : ISyntaxReceiver
520
+ {
521
+ public List<TypeDeclarationSyntax> Candidates { get; } =
522
+ new List<TypeDeclarationSyntax>();
523
+
524
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
525
+ {
526
+ if (IsSyntaxTargetForGeneration(syntaxNode))
527
+ {
528
+ Candidates.Add((TypeDeclarationSyntax)syntaxNode);
529
+ }
378
530
  }
379
531
  }
380
532
 
381
533
  // Generates the partial class/struct implementing IMessage
382
- private static string GenerateImplementationSource(
383
- string targetInterfaceFullName, // e.g., IBroadcastMessage
384
- INamedTypeSymbol typeSymbol
385
- )
534
+ private static string GenerateImplementationSource(MessageToGenerateInfo messageInfo)
386
535
  {
536
+ string targetInterfaceFullName = messageInfo.TargetInterfaceFullName;
537
+ INamedTypeSymbol typeSymbol = messageInfo.TypeSymbol;
387
538
  string namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
388
539
  ? string.Empty
389
540
  : typeSymbol.ContainingNamespace.ToDisplayString();
@@ -418,10 +569,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
418
569
  _ => "internal",
419
570
  };
420
571
 
572
+ bool containerIsRecord = IsRecordDeclaration(container);
421
573
  string containerKind = container.TypeKind switch
422
574
  {
423
- TypeKind.Class => container.IsRecord ? "record class" : "class",
424
- TypeKind.Struct => container.IsRecord ? "record struct" : "struct",
575
+ TypeKind.Class => containerIsRecord ? "record class" : "class",
576
+ TypeKind.Struct => containerIsRecord ? "record struct" : "struct",
425
577
  _ => "class",
426
578
  };
427
579
 
@@ -454,10 +606,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
454
606
  SymbolDisplayFormat.FullyQualifiedFormat
455
607
  );
456
608
 
609
+ bool typeIsRecord = IsRecordDeclaration(typeSymbol);
457
610
  string typeKind = typeSymbol.TypeKind switch
458
611
  {
459
- TypeKind.Class => typeSymbol.IsRecord ? "record class" : "class",
460
- TypeKind.Struct => typeSymbol.IsRecord ? "record struct" : "struct",
612
+ TypeKind.Class => typeIsRecord ? "record class" : "class",
613
+ TypeKind.Struct => typeIsRecord ? "record struct" : "struct",
461
614
  _ => throw new InvalidOperationException("Unsupported type kind"),
462
615
  };
463
616
 
@@ -473,6 +626,13 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
473
626
  };
474
627
 
475
628
  string interfaceDeclaration = $", global::{targetInterfaceFullName}";
629
+ string aotBridgeSource = ContainsTypeParameters(typeSymbol)
630
+ ? string.Empty
631
+ : GenerateTypeScopedAotBridgeSource(
632
+ messageInfo,
633
+ fullyQualifiedName,
634
+ innerIndent + Indent
635
+ );
476
636
 
477
637
  // Close containers string
478
638
  for (int i = 0; i < containers.Count; i++)
@@ -484,21 +644,519 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
484
644
  containersClose.Append(currentIndent).AppendLine("}");
485
645
  }
486
646
 
487
- return $$"""
488
- // <auto-generated by DxMessageIdGenerator/>
489
- #pragma warning disable
490
- #nullable enable annotations
491
-
492
- {{namespaceBlockOpen}}
493
- {{containersOpen}}{{innerIndent}}// Partial implementation for {{typeNameWithGenerics}} to implement {{BaseInterfaceFullName}}
494
- {{innerIndent}}{{accessibility}} partial {{typeKind}} {{typeNameWithGenerics}} : global::{{BaseInterfaceFullName}} {{interfaceDeclaration}}
495
- {{innerIndent}}{
496
- {{innerIndent}} /// <inheritdoc/>
497
- {{innerIndent}} public global::System.Type MessageType => typeof({{fullyQualifiedName}});
498
- {{innerIndent}}}
499
- {{containersClose}}
500
- {{namespaceBlockClose}}
501
- """;
647
+ return string.Join(
648
+ "\r\n",
649
+ new[]
650
+ {
651
+ "// <auto-generated by DxMessageIdGenerator/>",
652
+ "#pragma warning disable",
653
+ "#nullable enable annotations",
654
+ string.Empty,
655
+ namespaceBlockOpen,
656
+ $"{containersOpen}{innerIndent}// Partial implementation for {typeNameWithGenerics} to implement {BaseInterfaceFullName}",
657
+ $"{innerIndent}{accessibility} partial {typeKind} {typeNameWithGenerics} : global::{BaseInterfaceFullName} {interfaceDeclaration}",
658
+ $"{innerIndent}{{",
659
+ $"{innerIndent} /// <inheritdoc/>",
660
+ $"{innerIndent} public global::System.Type MessageType => typeof({fullyQualifiedName});",
661
+ $"{aotBridgeSource}",
662
+ $"{innerIndent}}}",
663
+ $"{containersClose}",
664
+ namespaceBlockClose,
665
+ string.Empty,
666
+ }
667
+ );
668
+ }
669
+
670
+ private static string GenerateTypeScopedAotBridgeSource(
671
+ MessageToGenerateInfo messageInfo,
672
+ string fullyQualifiedName,
673
+ string indent
674
+ )
675
+ {
676
+ var builder = new StringBuilder();
677
+ builder.AppendLine();
678
+ builder.Append(indent).AppendLine("#if ENABLE_IL2CPP && UNITY_2021_3_OR_NEWER");
679
+ builder.Append(indent).AppendLine("[global::UnityEngine.Scripting.Preserve]");
680
+ builder
681
+ .Append(indent)
682
+ .AppendLine(
683
+ "[global::UnityEngine.RuntimeInitializeOnLoadMethod(global::UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]"
684
+ );
685
+ builder
686
+ .Append(indent)
687
+ .AppendLine("private static void __DxMessagingRegisterAotBridges()");
688
+ builder.Append(indent).AppendLine("{");
689
+ if (messageInfo.RegistersUntargetedAotBridge)
690
+ {
691
+ AppendAotRegisterCall(
692
+ builder,
693
+ indent + " ",
694
+ "RegisterAotUntargetedBridge",
695
+ AotBridgeKind.Untargeted,
696
+ fullyQualifiedName,
697
+ "__DxMessagingAotUntargetedBridge"
698
+ );
699
+ }
700
+ if (messageInfo.RegistersTargetedAotBridge)
701
+ {
702
+ AppendAotRegisterCall(
703
+ builder,
704
+ indent + " ",
705
+ "RegisterAotTargetedBridge",
706
+ AotBridgeKind.Targeted,
707
+ fullyQualifiedName,
708
+ "__DxMessagingAotTargetedBridge"
709
+ );
710
+ }
711
+ if (messageInfo.RegistersBroadcastAotBridge)
712
+ {
713
+ AppendAotRegisterCall(
714
+ builder,
715
+ indent + " ",
716
+ "RegisterAotSourcedBridge",
717
+ AotBridgeKind.Broadcast,
718
+ fullyQualifiedName,
719
+ "__DxMessagingAotSourcedBridge"
720
+ );
721
+ }
722
+ builder.Append(indent).AppendLine("}");
723
+
724
+ AppendAotReflectionHelper(builder, indent);
725
+ if (messageInfo.RegistersUntargetedAotBridge)
726
+ {
727
+ AppendUntargetedBridgeMethod(
728
+ builder,
729
+ indent,
730
+ "__DxMessagingAotUntargetedBridge",
731
+ fullyQualifiedName
732
+ );
733
+ }
734
+ if (messageInfo.RegistersTargetedAotBridge)
735
+ {
736
+ AppendTargetedBridgeMethod(
737
+ builder,
738
+ indent,
739
+ "__DxMessagingAotTargetedBridge",
740
+ fullyQualifiedName
741
+ );
742
+ }
743
+ if (messageInfo.RegistersBroadcastAotBridge)
744
+ {
745
+ AppendBroadcastBridgeMethod(
746
+ builder,
747
+ indent,
748
+ "__DxMessagingAotSourcedBridge",
749
+ fullyQualifiedName
750
+ );
751
+ }
752
+
753
+ builder.Append(indent).Append("#endif");
754
+ return builder.ToString();
755
+ }
756
+
757
+ private static void GenerateTopLevelAotRegistrar(
758
+ ImmutableArray<AotRegistrarInfo> aotTypes,
759
+ HashSet<ISymbol> generatedAttributedTypes,
760
+ GeneratorExecutionContext context
761
+ )
762
+ {
763
+ if (aotTypes.IsDefaultOrEmpty)
764
+ {
765
+ return;
766
+ }
767
+
768
+ List<AotRegistrarInfo> registrarTypes = aotTypes
769
+ .Where(info =>
770
+ !info.HasMessageAttribute
771
+ && !generatedAttributedTypes.Contains(info.TypeSymbol)
772
+ && IsAccessibleFromTopLevelRegistrar(info.TypeSymbol)
773
+ )
774
+ .GroupBy(info => info.TypeSymbol, SymbolEqualityComparer.Default)
775
+ .Select(group =>
776
+ {
777
+ AotRegistrarInfo merged = default;
778
+ bool hasValue = false;
779
+ foreach (AotRegistrarInfo info in group)
780
+ {
781
+ if (!hasValue)
782
+ {
783
+ merged = info;
784
+ hasValue = true;
785
+ continue;
786
+ }
787
+
788
+ merged = new AotRegistrarInfo(
789
+ info.TypeSymbol,
790
+ merged.RegistersUntargetedAotBridge
791
+ || info.RegistersUntargetedAotBridge,
792
+ merged.RegistersTargetedAotBridge || info.RegistersTargetedAotBridge,
793
+ merged.RegistersBroadcastAotBridge || info.RegistersBroadcastAotBridge,
794
+ merged.HasMessageAttribute || info.HasMessageAttribute
795
+ );
796
+ }
797
+
798
+ return merged;
799
+ })
800
+ .OrderBy(
801
+ info =>
802
+ info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
803
+ StringComparer.Ordinal
804
+ )
805
+ .ToList();
806
+
807
+ if (registrarTypes.Count == 0)
808
+ {
809
+ return;
810
+ }
811
+
812
+ string source = GenerateTopLevelAotRegistrarSource(registrarTypes);
813
+ context.AddSource(
814
+ "DxMessaging.Il2CppMessageRegistrar.g.cs",
815
+ SourceText.From(source, Encoding.UTF8)
816
+ );
817
+ }
818
+
819
+ private static string GenerateTopLevelAotRegistrarSource(List<AotRegistrarInfo> types)
820
+ {
821
+ const string Indent = " ";
822
+ List<AotRegistrarSourceInfo> sourceTypes = types
823
+ .Select(
824
+ (info, index) =>
825
+ {
826
+ string typeName = info.TypeSymbol.ToDisplayString(
827
+ SymbolDisplayFormat.FullyQualifiedFormat
828
+ );
829
+ return new AotRegistrarSourceInfo(
830
+ info,
831
+ typeName,
832
+ index + "_" + SanitizeIdentifier(typeName)
833
+ );
834
+ }
835
+ )
836
+ .ToList();
837
+
838
+ var builder = new StringBuilder();
839
+ builder.AppendLine("// <auto-generated by DxMessageIdGenerator/>");
840
+ builder.AppendLine("#pragma warning disable");
841
+ builder.AppendLine("#nullable enable annotations");
842
+ builder.AppendLine("#if ENABLE_IL2CPP && UNITY_2021_3_OR_NEWER");
843
+ builder.AppendLine("namespace DxMessaging.Generated");
844
+ builder.AppendLine("{");
845
+ builder.AppendLine(" [global::UnityEngine.Scripting.Preserve]");
846
+ builder.AppendLine(" internal static class DxMessagingIl2CppMessageRegistrar");
847
+ builder.AppendLine(" {");
848
+ builder.AppendLine(
849
+ " [global::UnityEngine.RuntimeInitializeOnLoadMethod(global::UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]"
850
+ );
851
+ builder.AppendLine(" private static void Register()");
852
+ builder.AppendLine(" {");
853
+ foreach (AotRegistrarSourceInfo sourceInfo in sourceTypes)
854
+ {
855
+ AotRegistrarInfo info = sourceInfo.Info;
856
+ string typeName = sourceInfo.TypeName;
857
+ string suffix = sourceInfo.Suffix;
858
+ if (info.RegistersUntargetedAotBridge)
859
+ {
860
+ AppendAotRegisterCall(
861
+ builder,
862
+ Indent + " ",
863
+ "RegisterAotUntargetedBridge",
864
+ AotBridgeKind.Untargeted,
865
+ typeName,
866
+ "__DxMessagingAotUntargetedBridge_" + suffix
867
+ );
868
+ }
869
+ if (info.RegistersTargetedAotBridge)
870
+ {
871
+ AppendAotRegisterCall(
872
+ builder,
873
+ Indent + " ",
874
+ "RegisterAotTargetedBridge",
875
+ AotBridgeKind.Targeted,
876
+ typeName,
877
+ "__DxMessagingAotTargetedBridge_" + suffix
878
+ );
879
+ }
880
+ if (info.RegistersBroadcastAotBridge)
881
+ {
882
+ AppendAotRegisterCall(
883
+ builder,
884
+ Indent + " ",
885
+ "RegisterAotSourcedBridge",
886
+ AotBridgeKind.Broadcast,
887
+ typeName,
888
+ "__DxMessagingAotSourcedBridge_" + suffix
889
+ );
890
+ }
891
+ }
892
+ builder.AppendLine(" }");
893
+
894
+ AppendAotReflectionHelper(builder, Indent);
895
+ foreach (AotRegistrarSourceInfo sourceInfo in sourceTypes)
896
+ {
897
+ AotRegistrarInfo info = sourceInfo.Info;
898
+ string typeName = sourceInfo.TypeName;
899
+ string suffix = sourceInfo.Suffix;
900
+ if (info.RegistersUntargetedAotBridge)
901
+ {
902
+ AppendUntargetedBridgeMethod(
903
+ builder,
904
+ Indent,
905
+ "__DxMessagingAotUntargetedBridge_" + suffix,
906
+ typeName
907
+ );
908
+ }
909
+ if (info.RegistersTargetedAotBridge)
910
+ {
911
+ AppendTargetedBridgeMethod(
912
+ builder,
913
+ Indent,
914
+ "__DxMessagingAotTargetedBridge_" + suffix,
915
+ typeName
916
+ );
917
+ }
918
+ if (info.RegistersBroadcastAotBridge)
919
+ {
920
+ AppendBroadcastBridgeMethod(
921
+ builder,
922
+ Indent,
923
+ "__DxMessagingAotSourcedBridge_" + suffix,
924
+ typeName
925
+ );
926
+ }
927
+ }
928
+
929
+ builder.AppendLine(" }");
930
+ builder.AppendLine("}");
931
+ builder.AppendLine("#endif");
932
+ return builder.ToString();
933
+ }
934
+
935
+ private static void AppendAotRegisterCall(
936
+ StringBuilder builder,
937
+ string indent,
938
+ string runtimeMethodName,
939
+ AotBridgeKind bridgeKind,
940
+ string fullyQualifiedName,
941
+ string bridgeMethodName
942
+ )
943
+ {
944
+ string delegateType = GetAotDelegateType(bridgeKind);
945
+ builder
946
+ .Append(indent)
947
+ .Append("__DxMessagingRegisterAotBridge(\"")
948
+ .Append(runtimeMethodName)
949
+ .Append("\", typeof(")
950
+ .Append(fullyQualifiedName)
951
+ .Append("), (")
952
+ .Append(delegateType)
953
+ .Append(")")
954
+ .Append(bridgeMethodName)
955
+ .AppendLine(");");
956
+ }
957
+
958
+ private static string GetAotDelegateType(AotBridgeKind bridgeKind)
959
+ {
960
+ return bridgeKind switch
961
+ {
962
+ AotBridgeKind.Untargeted =>
963
+ "global::System.Action<global::DxMessaging.Core.MessageBus.IMessageBus, global::DxMessaging.Core.Messages.IUntargetedMessage>",
964
+ AotBridgeKind.Targeted =>
965
+ "global::System.Action<global::DxMessaging.Core.MessageBus.IMessageBus, global::DxMessaging.Core.InstanceId, global::DxMessaging.Core.Messages.ITargetedMessage>",
966
+ AotBridgeKind.Broadcast =>
967
+ "global::System.Action<global::DxMessaging.Core.MessageBus.IMessageBus, global::DxMessaging.Core.InstanceId, global::DxMessaging.Core.Messages.IBroadcastMessage>",
968
+ _ => throw new ArgumentOutOfRangeException(nameof(bridgeKind)),
969
+ };
970
+ }
971
+
972
+ private static void AppendAotReflectionHelper(StringBuilder builder, string indent)
973
+ {
974
+ builder.AppendLine();
975
+ builder
976
+ .Append(indent)
977
+ .AppendLine(
978
+ "private static void __DxMessagingRegisterAotBridge(string methodName, global::System.Type messageType, global::System.Delegate bridge)"
979
+ );
980
+ builder.Append(indent).AppendLine("{");
981
+ builder
982
+ .Append(indent)
983
+ .AppendLine(
984
+ " global::System.Reflection.MethodInfo method = typeof(global::DxMessaging.Core.MessageBus.MessageBus).GetMethod(methodName, global::System.Reflection.BindingFlags.Static | global::System.Reflection.BindingFlags.NonPublic);"
985
+ );
986
+ builder.Append(indent).AppendLine(" if (method == null)");
987
+ builder.Append(indent).AppendLine(" {");
988
+ builder
989
+ .Append(indent)
990
+ .AppendLine(
991
+ " throw new global::System.MissingMethodException(\"DxMessaging AOT bridge registration hook was not found: \" + methodName);"
992
+ );
993
+ builder.Append(indent).AppendLine(" }");
994
+ builder
995
+ .Append(indent)
996
+ .AppendLine(" method.Invoke(null, new object[] { messageType, bridge });");
997
+ builder.Append(indent).AppendLine("}");
998
+ }
999
+
1000
+ private static void AppendUntargetedBridgeMethod(
1001
+ StringBuilder builder,
1002
+ string indent,
1003
+ string methodName,
1004
+ string fullyQualifiedName
1005
+ )
1006
+ {
1007
+ builder.AppendLine();
1008
+ builder
1009
+ .Append(indent)
1010
+ .AppendLine(
1011
+ "private static void "
1012
+ + methodName
1013
+ + "(global::DxMessaging.Core.MessageBus.IMessageBus messageBus, global::DxMessaging.Core.Messages.IUntargetedMessage message)"
1014
+ );
1015
+ builder.Append(indent).AppendLine("{");
1016
+ builder
1017
+ .Append(indent)
1018
+ .Append(" ")
1019
+ .Append(fullyQualifiedName)
1020
+ .AppendLine(" typedMessage = (" + fullyQualifiedName + ")message;");
1021
+ builder
1022
+ .Append(indent)
1023
+ .AppendLine(" messageBus.UntargetedBroadcast(ref typedMessage);");
1024
+ builder.Append(indent).AppendLine("}");
1025
+ }
1026
+
1027
+ private static void AppendTargetedBridgeMethod(
1028
+ StringBuilder builder,
1029
+ string indent,
1030
+ string methodName,
1031
+ string fullyQualifiedName
1032
+ )
1033
+ {
1034
+ builder.AppendLine();
1035
+ builder
1036
+ .Append(indent)
1037
+ .AppendLine(
1038
+ "private static void "
1039
+ + methodName
1040
+ + "(global::DxMessaging.Core.MessageBus.IMessageBus messageBus, global::DxMessaging.Core.InstanceId target, global::DxMessaging.Core.Messages.ITargetedMessage message)"
1041
+ );
1042
+ builder.Append(indent).AppendLine("{");
1043
+ builder
1044
+ .Append(indent)
1045
+ .Append(" ")
1046
+ .Append(fullyQualifiedName)
1047
+ .AppendLine(" typedMessage = (" + fullyQualifiedName + ")message;");
1048
+ builder
1049
+ .Append(indent)
1050
+ .AppendLine(" messageBus.TargetedBroadcast(ref target, ref typedMessage);");
1051
+ builder.Append(indent).AppendLine("}");
1052
+ }
1053
+
1054
+ private static void AppendBroadcastBridgeMethod(
1055
+ StringBuilder builder,
1056
+ string indent,
1057
+ string methodName,
1058
+ string fullyQualifiedName
1059
+ )
1060
+ {
1061
+ builder.AppendLine();
1062
+ builder
1063
+ .Append(indent)
1064
+ .AppendLine(
1065
+ "private static void "
1066
+ + methodName
1067
+ + "(global::DxMessaging.Core.MessageBus.IMessageBus messageBus, global::DxMessaging.Core.InstanceId source, global::DxMessaging.Core.Messages.IBroadcastMessage message)"
1068
+ );
1069
+ builder.Append(indent).AppendLine("{");
1070
+ builder
1071
+ .Append(indent)
1072
+ .Append(" ")
1073
+ .Append(fullyQualifiedName)
1074
+ .AppendLine(" typedMessage = (" + fullyQualifiedName + ")message;");
1075
+ builder
1076
+ .Append(indent)
1077
+ .AppendLine(" messageBus.SourcedBroadcast(ref source, ref typedMessage);");
1078
+ builder.Append(indent).AppendLine("}");
1079
+ }
1080
+
1081
+ private static bool IsAccessibleFromTopLevelRegistrar(INamedTypeSymbol typeSymbol)
1082
+ {
1083
+ if (ContainsTypeParameters(typeSymbol))
1084
+ {
1085
+ return false;
1086
+ }
1087
+
1088
+ for (
1089
+ INamedTypeSymbol current = typeSymbol;
1090
+ current != null;
1091
+ current = current.ContainingType
1092
+ )
1093
+ {
1094
+ if (!IsAccessibleFromSameAssemblyTopLevel(current.DeclaredAccessibility))
1095
+ {
1096
+ return false;
1097
+ }
1098
+ }
1099
+
1100
+ return true;
1101
+ }
1102
+
1103
+ private static bool IsAccessibleFromSameAssemblyTopLevel(Accessibility accessibility)
1104
+ {
1105
+ return accessibility == Accessibility.Public
1106
+ || accessibility == Accessibility.Internal
1107
+ || accessibility == Accessibility.ProtectedOrInternal;
1108
+ }
1109
+
1110
+ private static bool ContainsTypeParameters(INamedTypeSymbol typeSymbol)
1111
+ {
1112
+ for (
1113
+ INamedTypeSymbol current = typeSymbol;
1114
+ current != null;
1115
+ current = current.ContainingType
1116
+ )
1117
+ {
1118
+ if (current.TypeParameters.Length > 0)
1119
+ {
1120
+ return true;
1121
+ }
1122
+ }
1123
+
1124
+ return false;
1125
+ }
1126
+
1127
+ private static string SanitizeIdentifier(string value)
1128
+ {
1129
+ var builder = new StringBuilder(value.Length);
1130
+ foreach (char c in value)
1131
+ {
1132
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
1133
+ {
1134
+ builder.Append(c);
1135
+ }
1136
+ else
1137
+ {
1138
+ builder.Append('_');
1139
+ }
1140
+ }
1141
+
1142
+ return builder.ToString();
1143
+ }
1144
+
1145
+ private static bool IsRecordDeclaration(INamedTypeSymbol symbol)
1146
+ {
1147
+ foreach (SyntaxReference syntaxReference in symbol.DeclaringSyntaxReferences)
1148
+ {
1149
+ if (syntaxReference.GetSyntax() is TypeDeclarationSyntax declaration)
1150
+ {
1151
+ string kind = declaration.Kind().ToString();
1152
+ if (kind.IndexOf("Record", StringComparison.Ordinal) >= 0)
1153
+ {
1154
+ return true;
1155
+ }
1156
+ }
1157
+ }
1158
+
1159
+ return false;
502
1160
  }
503
1161
 
504
1162
  private static List<INamedTypeSymbol> GetNonPartialContainers(INamedTypeSymbol typeSymbol)