com.wallstop-studios.dxmessaging 2.0.0-rc16 → 2.0.0-rc17

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 (29) hide show
  1. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  2. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.pdb.meta +7 -0
  3. package/Editor/SetupCscRsp.cs +24 -7
  4. package/README.md +7 -17
  5. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs +5 -1
  6. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs +5 -1
  7. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs +5 -1
  8. package/Runtime/Core/IMessage.cs +13 -0
  9. package/Runtime/Core/InstanceId.cs +6 -16
  10. package/Runtime/Core/MessageBus/MessageBus.cs +1751 -828
  11. package/Runtime/Core/MessageHandler.cs +408 -292
  12. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +445 -0
  13. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +3 -0
  14. package/Tests/Runtime/Benchmarks/PerformanceTests.cs +41 -26
  15. package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs +19 -0
  16. package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs.meta +3 -0
  17. package/Tests/Runtime/Scripts/Messages/GenericUntargetedMessage.cs +7 -0
  18. package/Tests/Runtime/Scripts/Messages/GenericUntargetedMessage.cs.meta +3 -0
  19. package/package.json +2 -2
  20. package/Runtime/Core/Attributes/DxAutoMessageTypeAttribute.cs +0 -7
  21. package/Runtime/Core/Attributes/DxAutoMessageTypeAttribute.cs.meta +0 -3
  22. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoMessageTypeGenerator.cs +0 -92
  23. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoMessageTypeGenerator.cs.meta +0 -11
  24. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxBroadcastMessageGenerator.cs +0 -94
  25. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxBroadcastMessageGenerator.cs.meta +0 -3
  26. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxTargetedMessageGenerator.cs +0 -94
  27. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxTargetedMessageGenerator.cs.meta +0 -3
  28. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxUntargetedMessageGenerator.cs +0 -94
  29. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxUntargetedMessageGenerator.cs.meta +0 -3
@@ -0,0 +1,445 @@
1
+ namespace WallstopStudios.DxMessaging.SourceGenerators;
2
+
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Collections.Immutable;
6
+ using System.Linq;
7
+ using System.Text;
8
+ using System.Threading;
9
+ using Microsoft.CodeAnalysis;
10
+ using Microsoft.CodeAnalysis.CSharp;
11
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
12
+ using Microsoft.CodeAnalysis.Text;
13
+
14
+ [Generator(LanguageNames.CSharp)]
15
+ public sealed class DxMessageIdGenerator : IIncrementalGenerator
16
+ {
17
+ // --- Constants for Attributes and Interfaces ---
18
+ private const string BaseInterfaceFullName = "DxMessaging.Core.IMessage"; // Base interface
19
+
20
+ private const string BroadcastAttrFullName =
21
+ "DxMessaging.Core.Attributes.DxBroadcastMessageAttribute";
22
+ private const string TargetedAttrFullName =
23
+ "DxMessaging.Core.Attributes.DxTargetedMessageAttribute";
24
+ private const string UntargetedAttrFullName =
25
+ "DxMessaging.Core.Attributes.DxUntargetedMessageAttribute";
26
+
27
+ private const string BroadcastInterfaceFullName = "DxMessaging.Core.Messages.IBroadcastMessage";
28
+ private const string TargetedInterfaceFullName = "DxMessaging.Core.Messages.ITargetedMessage";
29
+ private const string UntargetedInterfaceFullName =
30
+ "DxMessaging.Core.Messages.IUntargetedMessage";
31
+
32
+ // --- Diagnostics ---
33
+ private static readonly DiagnosticDescriptor CollisionError = new DiagnosticDescriptor(
34
+ id: "DXMSG001",
35
+ title: "Message ID Collision",
36
+ messageFormat: "OptimizedMessageId collision detected across different message types. The generated ID '{0}' is shared by the following types: {1}. Please rename one or more types slightly to resolve the hash collision.",
37
+ category: "DxMessaging",
38
+ defaultSeverity: DiagnosticSeverity.Error,
39
+ isEnabledByDefault: true
40
+ );
41
+
42
+ private static readonly DiagnosticDescriptor CollisionWarning = new DiagnosticDescriptor(
43
+ id: "DXMSG003", // New ID
44
+ title: "Message ID Collision Fallback",
45
+ messageFormat: "OptimizedMessageId collision detected for ID '{0}'. The following types will use a fallback implementation (HasOptimizedId = false): {1}.",
46
+ category: "DxMessaging",
47
+ defaultSeverity: DiagnosticSeverity.Warning, // Set severity to Warning
48
+ isEnabledByDefault: true
49
+ );
50
+
51
+ private static readonly DiagnosticDescriptor MultipleAttributesError = new DiagnosticDescriptor(
52
+ id: "DXMSG002",
53
+ title: "Multiple Message Attributes",
54
+ messageFormat: "Type '{0}' cannot have more than one Dx message attribute ([DxBroadcastMessage], [DxTargetedMessage], [DxUntargetedMessage]).",
55
+ category: "DxMessaging",
56
+ defaultSeverity: DiagnosticSeverity.Error,
57
+ isEnabledByDefault: true
58
+ );
59
+
60
+ // Helper record to pass data through the pipeline
61
+ private record struct SemanticTargetInfo(
62
+ INamedTypeSymbol TypeSymbol,
63
+ TypeDeclarationSyntax DeclarationSyntax,
64
+ string TargetInterfaceFullName
65
+ );
66
+
67
+ // Helper record for final processing stage
68
+ private record struct MessageInfo(
69
+ INamedTypeSymbol TypeSymbol,
70
+ TypeDeclarationSyntax DeclarationSyntax,
71
+ string TargetInterfaceFullName,
72
+ int GeneratedId
73
+ );
74
+
75
+ // Helper record for final assignment stage
76
+ private record struct FinalMessageInfo(
77
+ INamedTypeSymbol TypeSymbol,
78
+ TypeDeclarationSyntax DeclarationSyntax,
79
+ string TargetInterfaceFullName,
80
+ int FinalId, // The ID actually assigned (hash or fallback)
81
+ bool WasCollision
82
+ ); // Flag indicating if it was part of a collision
83
+
84
+ public void Initialize(IncrementalGeneratorInitializationContext context)
85
+ {
86
+ // --- Step 1: Find all classes/structs with attribute lists (Potential Candidates) ---
87
+ IncrementalValuesProvider<TypeDeclarationSyntax> potentialTypeDeclarations =
88
+ context.SyntaxProvider.CreateSyntaxProvider(
89
+ predicate: static (node, _) => IsSyntaxTargetForGeneration(node), // Quick syntax filter
90
+ transform: static (ctx, ct) => (TypeDeclarationSyntax)ctx.Node
91
+ ); // Just pass the node
92
+
93
+ // --- Step 2: Get Semantic Info and Filter by Attributes ---
94
+ IncrementalValuesProvider<SemanticTargetInfo?> semanticTargets = potentialTypeDeclarations
95
+ .Select(static (typeDecl, ct) => new { typeDecl, ct }) // Combine with CancellationToken if needed implicitly by GetSemanticTargetForGeneration
96
+ .Combine(context.CompilationProvider)
97
+ .Select(
98
+ static (data, ct) =>
99
+ GetSemanticTargetForGeneration(data.Left.typeDecl, data.Right, ct)
100
+ );
101
+
102
+ // --- Step 3: Filter out invalid targets ---
103
+ IncrementalValuesProvider<SemanticTargetInfo> validSemanticTargets = semanticTargets
104
+ .Where(static target => target.HasValue)
105
+ .Select(static (target, _) => target!.Value); // Use non-null assertion or keep filtering
106
+
107
+ // --- Step 4: Calculate Hash IDs ---
108
+ IncrementalValuesProvider<MessageInfo> typesWithIds = validSemanticTargets.Select(
109
+ static (target, ct) =>
110
+ {
111
+ string fullyQualifiedName = target.TypeSymbol.ToDisplayString(
112
+ SymbolDisplayFormat.FullyQualifiedFormat
113
+ );
114
+ int generatedId = ComputeStableHashCode(fullyQualifiedName);
115
+ return new MessageInfo(
116
+ target.TypeSymbol,
117
+ target.DeclarationSyntax,
118
+ target.TargetInterfaceFullName,
119
+ generatedId
120
+ );
121
+ }
122
+ );
123
+
124
+ // --- Step 5: Collect all valid types with IDs ---
125
+ IncrementalValueProvider<(Compilation, ImmutableArray<MessageInfo>)> compilationAndTypes =
126
+ context.CompilationProvider.Combine(typesWithIds.Collect());
127
+
128
+ // --- Step 6: Generate source or diagnostics ---
129
+ context.RegisterSourceOutput(
130
+ compilationAndTypes,
131
+ static (spc, source) => Execute(source.Item1, source.Item2, spc)
132
+ );
133
+ }
134
+
135
+ // Quick syntax filter: Checks if the node is a class or struct with any attributes
136
+ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
137
+ node is TypeDeclarationSyntax { AttributeLists.Count: > 0 } typeDecl
138
+ && (
139
+ typeDecl.IsKind(SyntaxKind.ClassDeclaration)
140
+ || typeDecl.IsKind(SyntaxKind.StructDeclaration)
141
+ );
142
+
143
+ // Semantic filter: Checks if the type has exactly one of the target attributes
144
+ private static SemanticTargetInfo? GetSemanticTargetForGeneration(
145
+ TypeDeclarationSyntax typeDeclarationSyntax,
146
+ Compilation compilation,
147
+ CancellationToken cancellationToken
148
+ )
149
+ {
150
+ SemanticModel semanticModel = compilation.GetSemanticModel(
151
+ typeDeclarationSyntax.SyntaxTree
152
+ );
153
+ if (
154
+ semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, cancellationToken)
155
+ is not INamedTypeSymbol typeSymbol
156
+ )
157
+ {
158
+ return null;
159
+ }
160
+
161
+ string? foundTargetInterface = null;
162
+ bool multipleAttributes = false;
163
+
164
+ foreach (AttributeData attributeData in typeSymbol.GetAttributes())
165
+ {
166
+ cancellationToken.ThrowIfCancellationRequested();
167
+ if (attributeData.AttributeClass == null)
168
+ continue;
169
+
170
+ string currentAttributeFullName = attributeData.AttributeClass.ToDisplayString();
171
+ string? targetInterfaceForThisAttribute = null;
172
+
173
+ switch (currentAttributeFullName)
174
+ {
175
+ case BroadcastAttrFullName:
176
+ targetInterfaceForThisAttribute = BroadcastInterfaceFullName;
177
+ break;
178
+ case TargetedAttrFullName:
179
+ targetInterfaceForThisAttribute = TargetedInterfaceFullName;
180
+ break;
181
+ case UntargetedAttrFullName:
182
+ targetInterfaceForThisAttribute = UntargetedInterfaceFullName;
183
+ break;
184
+ }
185
+
186
+ if (targetInterfaceForThisAttribute != null)
187
+ {
188
+ if (foundTargetInterface != null)
189
+ {
190
+ // Found more than one relevant attribute!
191
+ multipleAttributes = true;
192
+ break; // No need to check further attributes
193
+ }
194
+ foundTargetInterface = targetInterfaceForThisAttribute;
195
+ }
196
+ }
197
+
198
+ if (multipleAttributes || foundTargetInterface == null)
199
+ {
200
+ // Report error for multiple attributes later in Execute if needed,
201
+ // but don't consider this a valid target for generation now.
202
+ // If foundTargetInterface is null, it didn't have any relevant attribute.
203
+ return null;
204
+ }
205
+
206
+ // Found exactly one relevant attribute
207
+ return new SemanticTargetInfo(typeSymbol, typeDeclarationSyntax, foundTargetInterface);
208
+ }
209
+
210
+ // --- Main execution logic: collision check, warning, and source generation ---
211
+ private static void Execute(
212
+ Compilation compilation,
213
+ ImmutableArray<MessageInfo> typesToGenerate,
214
+ SourceProductionContext context
215
+ )
216
+ {
217
+ if (typesToGenerate.IsDefaultOrEmpty)
218
+ {
219
+ return; // Nothing to do
220
+ }
221
+
222
+ var validTypes = new List<MessageInfo>(typesToGenerate.Length);
223
+ var typesWithMultipleAttributes = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
224
+ var generatedIds = new HashSet<int>();
225
+
226
+ // --- Step 1: Check for Multiple Attributes on the same type ---
227
+ var groupedByType = typesToGenerate.GroupBy(
228
+ m => m.TypeSymbol,
229
+ SymbolEqualityComparer.Default
230
+ );
231
+
232
+ foreach (var group in groupedByType)
233
+ {
234
+ if (group.Count() > 1)
235
+ {
236
+ ISymbol collidingSymbol = group.Key;
237
+ typesWithMultipleAttributes.Add(collidingSymbol);
238
+ context.ReportDiagnostic(
239
+ Diagnostic.Create(
240
+ MultipleAttributesError,
241
+ collidingSymbol.Locations.FirstOrDefault() ?? Location.None,
242
+ collidingSymbol.ToDisplayString()
243
+ )
244
+ );
245
+ }
246
+ else
247
+ {
248
+ // Add types with only one attribute to the list for further processing
249
+ validTypes.Add(group.First());
250
+ }
251
+ }
252
+
253
+ // If no types remain after filtering multi-attribute ones, exit
254
+ if (validTypes.Count == 0)
255
+ {
256
+ return;
257
+ }
258
+
259
+ // --- Step 2: Assign Final IDs (Handling Collisions) ---
260
+ var finalAssignments = new List<FinalMessageInfo>(validTypes.Count);
261
+ var collisionWarnings = new List<Diagnostic>(); // Collect warnings
262
+ int fallbackIdCounter = -1; // Start fallback IDs from -1 and go down
263
+
264
+ // Group by the initial hash ID to find collisions
265
+ var groupedByHash = validTypes.GroupBy(m => m.GeneratedId);
266
+
267
+ foreach (var group in groupedByHash)
268
+ {
269
+ if (group.Count() == 1)
270
+ {
271
+ // No collision for this hash ID
272
+ var info = group.First();
273
+ generatedIds.Add(info.GeneratedId);
274
+ finalAssignments.Add(
275
+ new FinalMessageInfo(
276
+ info.TypeSymbol,
277
+ info.DeclarationSyntax,
278
+ info.TargetInterfaceFullName,
279
+ info.GeneratedId,
280
+ false
281
+ )
282
+ );
283
+ }
284
+ else
285
+ {
286
+ // Collision detected for this hash ID!
287
+ // Sort colliding types deterministically (e.g., by fully qualified name)
288
+ var sortedCollidingTypes = group
289
+ .OrderBy(
290
+ m => m.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
291
+ StringComparer.Ordinal
292
+ )
293
+ .ToList();
294
+
295
+ // The first type keeps the original hash ID (arbitrary but deterministic choice)
296
+ var firstInfo = sortedCollidingTypes[0];
297
+ generatedIds.Add(firstInfo.GeneratedId);
298
+ finalAssignments.Add(
299
+ new FinalMessageInfo(
300
+ firstInfo.TypeSymbol,
301
+ firstInfo.DeclarationSyntax,
302
+ firstInfo.TargetInterfaceFullName,
303
+ firstInfo.GeneratedId,
304
+ true
305
+ )
306
+ ); // Mark as part of collision
307
+ // Prepare warning for this type (kept original ID but was part of collision)
308
+ collisionWarnings.Add(
309
+ Diagnostic.Create(
310
+ CollisionWarning,
311
+ firstInfo.DeclarationSyntax.Identifier.GetLocation(),
312
+ firstInfo.TypeSymbol.ToDisplayString(),
313
+ firstInfo.GeneratedId,
314
+ group.Key
315
+ )
316
+ );
317
+
318
+ // Assign sequential fallback IDs (negative numbers) to the rest
319
+ for (int i = 1; i < sortedCollidingTypes.Count; i++)
320
+ {
321
+ var fallbackInfo = sortedCollidingTypes[i];
322
+ int assignedFallbackId = fallbackIdCounter--; // Assign next negative ID
323
+ while (!generatedIds.Add(assignedFallbackId))
324
+ {
325
+ assignedFallbackId--;
326
+ }
327
+ finalAssignments.Add(
328
+ new FinalMessageInfo(
329
+ fallbackInfo.TypeSymbol,
330
+ fallbackInfo.DeclarationSyntax,
331
+ fallbackInfo.TargetInterfaceFullName,
332
+ assignedFallbackId,
333
+ true
334
+ )
335
+ ); // Mark as part of collision, use new ID
336
+ // Prepare warning for this type (received fallback ID)
337
+ collisionWarnings.Add(
338
+ Diagnostic.Create(
339
+ CollisionWarning,
340
+ fallbackInfo.DeclarationSyntax.Identifier.GetLocation(),
341
+ fallbackInfo.TypeSymbol.ToDisplayString(),
342
+ assignedFallbackId,
343
+ group.Key
344
+ )
345
+ );
346
+ }
347
+ }
348
+ }
349
+
350
+ // --- Step 3: Report all collected warnings ---
351
+ foreach (var warning in collisionWarnings)
352
+ {
353
+ context.ReportDiagnostic(warning);
354
+ }
355
+
356
+ // --- Step 4: Generate Source using the final assigned IDs ---
357
+ foreach (var finalInfo in finalAssignments)
358
+ {
359
+ context.CancellationToken.ThrowIfCancellationRequested();
360
+
361
+ // Generate source using the final ID (hash or fallback)
362
+ string source = GenerateSource( // Call simplified GenerateSource
363
+ finalInfo.TargetInterfaceFullName,
364
+ finalInfo.TypeSymbol,
365
+ finalInfo.FinalId
366
+ ); // Pass the FINAL ID
367
+
368
+ context.AddSource(
369
+ $"{finalInfo.TypeSymbol.Name}_{finalInfo.TargetInterfaceFullName.Split('.').Last()}.g.cs",
370
+ SourceText.From(source, Encoding.UTF8)
371
+ );
372
+ }
373
+ }
374
+
375
+ // --- Stable Hash Function (FNV-1a - remains the same) ---
376
+ private static int ComputeStableHashCode(string text)
377
+ {
378
+ unchecked
379
+ {
380
+ uint hash = 2166136261;
381
+ foreach (char c in text)
382
+ {
383
+ hash = (hash ^ c) * 16777619;
384
+ }
385
+ return (int)hash;
386
+ }
387
+ }
388
+
389
+ // --- Source Generation Logic (Takes target interface name AND optimization flag) ---
390
+ private static string GenerateSource(
391
+ string targetInterfaceFullName,
392
+ INamedTypeSymbol typeSymbol,
393
+ int finalAssignedId
394
+ ) // Now takes the final assigned ID
395
+ {
396
+ string namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
397
+ ? string.Empty
398
+ : typeSymbol.ContainingNamespace.ToDisplayString();
399
+
400
+ string typeNameWithGenerics = typeSymbol.ToDisplayString(
401
+ SymbolDisplayFormat.MinimallyQualifiedFormat
402
+ );
403
+ string fullyQualifiedName = typeSymbol.ToDisplayString(
404
+ SymbolDisplayFormat.FullyQualifiedFormat
405
+ );
406
+
407
+ string typeKind = typeSymbol.TypeKind switch
408
+ {
409
+ TypeKind.Class => "class",
410
+ TypeKind.Struct => "struct",
411
+ _ => throw new InvalidOperationException(
412
+ "Unsupported type kind for message generation"
413
+ ),
414
+ };
415
+
416
+ bool alreadyDeclaresInterface = typeSymbol.Interfaces.Any(iface =>
417
+ iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
418
+ == targetInterfaceFullName
419
+ );
420
+
421
+ string interfaceDeclaration = alreadyDeclaresInterface
422
+ ? ""
423
+ : $": {targetInterfaceFullName}";
424
+
425
+ return $$"""
426
+ // <auto-generated by DxMessageIdGenerator/>
427
+ #pragma warning disable
428
+ #nullable enable annotations
429
+
430
+ namespace {{namespaceName}}
431
+ {
432
+ partial {{typeKind}} {{typeNameWithGenerics}} {{interfaceDeclaration}}
433
+ {
434
+ // Explicitly implement IMessage members using the base interface name
435
+
436
+ /// <inheritdoc/>
437
+ System.Type {{BaseInterfaceFullName}}.MessageType => typeof({{fullyQualifiedName}});
438
+
439
+ /// <inheritdoc/>
440
+ int? {{BaseInterfaceFullName}}.OptimizedMessageId => {{finalAssignedId}};
441
+ }
442
+ }
443
+ """;
444
+ }
445
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 8525b467bd2c430a90ccee97f321a845
3
+ timeCreated: 1744403125
@@ -17,12 +17,14 @@
17
17
 
18
18
  public sealed class PerformanceTests : MessagingTestBase
19
19
  {
20
+ private const int NumInvocationsPerIteration = 250;
21
+
20
22
  protected override bool MessagingDebugEnabled => false;
21
23
 
22
24
  [Test]
23
25
  public void Benchmark()
24
26
  {
25
- TimeSpan timeout = TimeSpan.FromSeconds(2);
27
+ TimeSpan timeout = TimeSpan.FromSeconds(5);
26
28
 
27
29
  Debug.Log("| Message Tech | Operations / Second | Allocations? |");
28
30
  Debug.Log("| ------------ | ------------------- | ------------ | ");
@@ -190,10 +192,13 @@
190
192
  timer.Restart();
191
193
  do
192
194
  {
193
- target.SendMessage(
194
- nameof(SimpleMessageAwareComponent.HandleSlowComplexTargetedMessage),
195
- message
196
- );
195
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
196
+ {
197
+ target.SendMessage(
198
+ nameof(SimpleMessageAwareComponent.HandleSlowComplexTargetedMessage),
199
+ message
200
+ );
201
+ }
197
202
  } while (timer.Elapsed < timeout);
198
203
 
199
204
  bool allocating;
@@ -227,19 +232,23 @@
227
232
  var token = GetToken(component);
228
233
 
229
234
  GameObject go = component.gameObject;
235
+ InstanceId target = go;
230
236
  token.RegisterGameObjectTargeted<ComplexTargetedMessage>(go, Handle);
231
237
  // Pre-warm
232
- message.EmitGameObjectTargeted(go);
238
+ message.EmitTargeted(target);
233
239
 
234
240
  timer.Restart();
235
241
  do
236
242
  {
237
- message.EmitGameObjectTargeted(go);
243
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
244
+ {
245
+ message.EmitTargeted(target);
246
+ }
238
247
  } while (timer.Elapsed < timeout);
239
248
  bool allocating;
240
249
  try
241
250
  {
242
- Assert.That(() => message.EmitGameObjectTargeted(go), Is.Not.AllocatingGCMemory());
251
+ Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
243
252
  allocating = false;
244
253
  }
245
254
  catch
@@ -265,24 +274,25 @@
265
274
  {
266
275
  int count = 0;
267
276
  var token = GetToken(component);
277
+ InstanceId target = component;
268
278
 
269
279
  token.RegisterComponentTargeted<ComplexTargetedMessage>(component, Handle);
270
280
  // Pre-warm
271
- message.EmitComponentTargeted(component);
281
+ message.EmitTargeted(target);
272
282
 
273
283
  timer.Restart();
274
284
  do
275
285
  {
276
- message.EmitComponentTargeted(component);
286
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
287
+ {
288
+ message.EmitTargeted(target);
289
+ }
277
290
  } while (timer.Elapsed < timeout);
278
291
 
279
292
  bool allocating;
280
293
  try
281
294
  {
282
- Assert.That(
283
- () => message.EmitComponentTargeted(component),
284
- Is.Not.AllocatingGCMemory()
285
- );
295
+ Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
286
296
  allocating = false;
287
297
  }
288
298
  catch
@@ -309,22 +319,23 @@
309
319
  var token = GetToken(component);
310
320
 
311
321
  GameObject go = component.gameObject;
322
+ InstanceId target = go;
312
323
  token.RegisterGameObjectTargeted<ComplexTargetedMessage>(go, Handle);
313
324
  // Pre-warm
314
- message.EmitGameObjectTargeted(component.gameObject);
325
+ message.EmitTargeted(target);
315
326
 
316
327
  timer.Restart();
317
328
  do
318
329
  {
319
- message.EmitGameObjectTargeted(component.gameObject);
330
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
331
+ {
332
+ message.EmitTargeted(target);
333
+ }
320
334
  } while (timer.Elapsed < timeout);
321
335
  bool allocating;
322
336
  try
323
337
  {
324
- Assert.That(
325
- () => message.EmitGameObjectTargeted(component.gameObject),
326
- Is.Not.AllocatingGCMemory()
327
- );
338
+ Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
328
339
  allocating = false;
329
340
  }
330
341
  catch
@@ -349,6 +360,7 @@
349
360
  {
350
361
  int count = 0;
351
362
  var token = GetToken(component);
363
+ InstanceId target = component;
352
364
 
353
365
  token.RegisterComponentTargeted<ComplexTargetedMessage>(component, Handle);
354
366
  // Pre-warm
@@ -357,16 +369,16 @@
357
369
  timer.Restart();
358
370
  do
359
371
  {
360
- message.EmitComponentTargeted(component);
372
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
373
+ {
374
+ message.EmitTargeted(target);
375
+ }
361
376
  } while (timer.Elapsed < timeout);
362
377
 
363
378
  bool allocating;
364
379
  try
365
380
  {
366
- Assert.That(
367
- () => message.EmitComponentTargeted(component),
368
- Is.Not.AllocatingGCMemory()
369
- );
381
+ Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
370
382
  allocating = false;
371
383
  }
372
384
  catch
@@ -399,7 +411,10 @@
399
411
  timer.Restart();
400
412
  do
401
413
  {
402
- message.EmitUntargeted();
414
+ for (int i = 0; i < NumInvocationsPerIteration; ++i)
415
+ {
416
+ message.EmitUntargeted();
417
+ }
403
418
  } while (timer.Elapsed < timeout);
404
419
 
405
420
  bool allocating;
@@ -0,0 +1,19 @@
1
+ namespace DxMessaging.Tests.Runtime.Scripts.Components
2
+ {
3
+ using Messages;
4
+ using Unity;
5
+ using UnityEngine;
6
+
7
+ [DisallowMultipleComponent]
8
+ public sealed class GenericMessageAwareComponent : MessageAwareComponent
9
+ {
10
+ protected override void RegisterMessageHandlers()
11
+ {
12
+ base.RegisterMessageHandlers();
13
+ _ = _messageRegistrationToken.RegisterUntargeted(
14
+ (ref GenericUntargetedMessage<int> message) =>
15
+ Debug.Log("Received generic int message.")
16
+ );
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 3b5b962e8c244838be18e01d1a47542c
3
+ timeCreated: 1744426157
@@ -0,0 +1,7 @@
1
+ namespace DxMessaging.Tests.Runtime.Scripts.Messages
2
+ {
3
+ using DxMessaging.Core.Attributes;
4
+
5
+ [DxUntargetedMessage]
6
+ public readonly partial struct GenericUntargetedMessage<T> { }
7
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: f1e39abb3eaa468daa20f7d3d50052d1
3
+ timeCreated: 1744425432
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.dxmessaging",
3
- "version": "2.0.0-rc16",
3
+ "version": "2.0.0-rc17",
4
4
  "displayName": "DxMessaging",
5
5
  "description": "Synchronous Event Bus for Unity",
6
6
  "unity": "2021.3",
7
7
  "dependencies": {
8
- "com.wallstop-studios.unity-helpers" : "2.0.0-rc50"
8
+ "com.wallstop-studios.unity-helpers" : "2.0.0-rc58"
9
9
  },
10
10
  "keywords": [
11
11
  "messaging",
@@ -1,7 +0,0 @@
1
- namespace DxMessaging.Core.Attributes
2
- {
3
- using System;
4
-
5
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
6
- public sealed class DxAutoMessageTypeAttribute : Attribute { }
7
- }
@@ -1,3 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 01e2bf7a935a4389be6e63a79b36723e
3
- timeCreated: 1730779085