com.wallstop-studios.dxmessaging 2.1.2 → 2.1.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.
Files changed (67) hide show
  1. package/.github/workflows/dotnet-tests.yml +3 -3
  2. package/.github/workflows/prettier-autofix.yml +0 -7
  3. package/.pre-commit-config.yaml +8 -5
  4. package/AGENTS.md +12 -12
  5. package/CONTRIBUTING.md +8 -2
  6. package/Docs/Comparisons.md +5 -5
  7. package/Docs/InterceptorsAndOrdering.md +1 -1
  8. package/Docs/Performance.md +13 -13
  9. package/Docs/QuickReference.md +1 -1
  10. package/Docs/Reference.md +5 -5
  11. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  12. package/Editor/CustomEditors/MessagingComponentEditor.cs +3 -0
  13. package/Editor/DxMessagingEditorInitializer.cs +58 -1
  14. package/Editor/DxMessagingMenu.cs +38 -0
  15. package/Editor/DxMessagingMenu.cs.meta +11 -0
  16. package/Editor/DxMessagingSceneBuildProcessor.cs +81 -0
  17. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -0
  18. package/Editor/Settings/DxMessagingSettings.cs +37 -6
  19. package/Editor/Settings/DxMessagingSettingsProvider.cs +45 -7
  20. package/README.md +1 -1
  21. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +52 -0
  22. package/Runtime/Core/DataStructure/CyclicBuffer.cs +16 -0
  23. package/Runtime/Core/Diagnostics/MessageEmissionData.cs +1 -1
  24. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs +62 -0
  25. package/Runtime/Core/DxMessagingStaticState.cs +108 -0
  26. package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -0
  27. package/Runtime/Core/Extensions/IListExtensions.cs +24 -0
  28. package/Runtime/Core/Extensions/MessageBusExtensions.cs +142 -0
  29. package/Runtime/Core/Helper/MessageCache.cs +16 -0
  30. package/Runtime/Core/Helper/MessageHelperIndexer.cs +77 -0
  31. package/Runtime/Core/InstanceId.cs +86 -0
  32. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs +31 -0
  33. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -0
  34. package/Runtime/Core/MessageBus/IMessageBus.cs +44 -16
  35. package/Runtime/Core/MessageBus/MessageBus.cs +167 -180
  36. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +44 -0
  37. package/Runtime/Core/MessageBus/MessagingRegistration.cs +60 -2
  38. package/Runtime/Core/MessageBus/RegistrationLog.cs +10 -0
  39. package/Runtime/Core/MessageHandler.cs +107 -6
  40. package/Runtime/Core/MessageRegistrationHandle.cs +59 -0
  41. package/Runtime/Core/MessageRegistrationToken.cs +18 -2
  42. package/Runtime/Core/Messages/ReflexiveMessage.cs +38 -0
  43. package/Runtime/Core/MessagingDebug.cs +16 -1
  44. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +4 -0
  45. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs +19 -0
  46. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -0
  47. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +4 -0
  48. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +17 -0
  49. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +8 -0
  50. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +12 -0
  51. package/Runtime/Unity/MessagingComponent.cs +93 -0
  52. package/Samples~/DI/README.md +13 -13
  53. package/Samples~/Mini Combat/README.md +15 -15
  54. package/Samples~/Mini Combat/Walkthrough.md +12 -12
  55. package/Samples~/UI Buttons + Inspector/README.md +4 -4
  56. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +4 -0
  57. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +4 -0
  58. package/Tests/Runtime/Core/DiagnosticsTests.cs +3 -3
  59. package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs +69 -0
  60. package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs.meta +11 -0
  61. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs +12 -31
  62. package/Tests/Runtime/Core/OrderingManyRegistrationsTests.cs +683 -0
  63. package/Tests/Runtime/Core/OrderingManyRegistrationsTests.cs.meta +11 -0
  64. package/package.json +1 -1
  65. package/scripts/fix-eol.js +38 -3
  66. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj +0 -20
  67. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj.meta +0 -7
@@ -99,6 +99,13 @@ namespace DxMessaging.Core.MessageBus
99
99
  /// </remarks>
100
100
  public readonly struct MessageRegistrationLifecycle
101
101
  {
102
+ /// <summary>
103
+ /// Creates a lifecycle definition with the supplied callbacks.
104
+ /// </summary>
105
+ /// <param name="onBuild">Invoked immediately after the lease is constructed.</param>
106
+ /// <param name="onActivate">Invoked when the lease becomes active.</param>
107
+ /// <param name="onDeactivate">Invoked when the lease transitions from active to inactive.</param>
108
+ /// <param name="onDispose">Invoked during lease disposal.</param>
102
109
  public MessageRegistrationLifecycle(
103
110
  Action<MessageRegistrationToken> onBuild,
104
111
  Action<MessageRegistrationToken> onActivate,
@@ -201,6 +208,9 @@ namespace DxMessaging.Core.MessageBus
201
208
  _isActive = true;
202
209
  }
203
210
 
211
+ /// <summary>
212
+ /// Deactivates the lease, unregistering staged handlers and invoking lifecycle hooks.
213
+ /// </summary>
204
214
  public void Deactivate()
205
215
  {
206
216
  if (_disposed || !_isActive)
@@ -218,6 +228,9 @@ namespace DxMessaging.Core.MessageBus
218
228
  _isActive = false;
219
229
  }
220
230
 
231
+ /// <summary>
232
+ /// Disposes the lease, unregistering handlers and executing lifecycle callbacks once.
233
+ /// </summary>
221
234
  public void Dispose()
222
235
  {
223
236
  if (_disposed)
@@ -259,11 +272,33 @@ namespace DxMessaging.Core.MessageBus
259
272
  private readonly IMessageBusProvider _messageBusProvider;
260
273
  private static int _syntheticOwnerCounter;
261
274
 
275
+ internal static int GetSyntheticOwnerCounter()
276
+ {
277
+ return Volatile.Read(ref _syntheticOwnerCounter);
278
+ }
279
+
280
+ internal static void SetSyntheticOwnerCounter(int value)
281
+ {
282
+ _ = Interlocked.Exchange(ref _syntheticOwnerCounter, value);
283
+ }
284
+
285
+ internal static void ResetSyntheticOwnerCounter()
286
+ {
287
+ SetSyntheticOwnerCounter(0);
288
+ }
289
+
290
+ /// <summary>
291
+ /// Initializes a builder that resolves buses from global state.
292
+ /// </summary>
262
293
  public MessageRegistrationBuilder()
263
294
  {
264
295
  _messageBusProvider = null;
265
296
  }
266
297
 
298
+ /// <summary>
299
+ /// Initializes a builder that uses a custom bus provider.
300
+ /// </summary>
301
+ /// <param name="messageBusProvider">Provider used to resolve message buses for new leases.</param>
267
302
  public MessageRegistrationBuilder(IMessageBusProvider messageBusProvider)
268
303
  {
269
304
  _messageBusProvider = messageBusProvider;
@@ -370,11 +405,20 @@ namespace DxMessaging.Core.MessageBus
370
405
  {
371
406
  private readonly IMessageBus _messageBus;
372
407
 
408
+ /// <summary>
409
+ /// Creates a provider that always returns the supplied bus instance.
410
+ /// </summary>
411
+ /// <param name="messageBus">Bus instance to return when <see cref="Resolve"/> is invoked.</param>
412
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="messageBus"/> is null.</exception>
373
413
  public FixedMessageBusProvider(IMessageBus messageBus)
374
414
  {
375
415
  _messageBus = messageBus ?? throw new ArgumentNullException(nameof(messageBus));
376
416
  }
377
417
 
418
+ /// <summary>
419
+ /// Resolves the configured message bus.
420
+ /// </summary>
421
+ /// <returns>The bus supplied during construction.</returns>
378
422
  public IMessageBus Resolve()
379
423
  {
380
424
  return _messageBus;
@@ -4,30 +4,84 @@ namespace DxMessaging.Core.MessageBus
4
4
  using System.Runtime.Serialization;
5
5
 
6
6
  /// <summary>
7
- /// How the registration was performed.
7
+ /// Indicates whether a registration was added or removed.
8
8
  /// </summary>
9
9
  public enum RegistrationType
10
10
  {
11
+ /// <summary>
12
+ /// The registration was added to the bus.
13
+ /// </summary>
11
14
  Register,
15
+
16
+ /// <summary>
17
+ /// The registration was removed from the bus.
18
+ /// </summary>
12
19
  Deregister,
13
20
  }
14
21
 
15
22
  /// <summary>
16
- /// Exact method of MessagingRegistration.
23
+ /// Exact registration category used when the handler was wired up.
17
24
  /// </summary>
18
25
  public enum RegistrationMethod
19
26
  {
27
+ /// <summary>
28
+ /// Registered as a targeted handler bound to a specific recipient.
29
+ /// </summary>
20
30
  Targeted,
31
+
32
+ /// <summary>
33
+ /// Registered as a global untargeted handler.
34
+ /// </summary>
21
35
  Untargeted,
36
+
37
+ /// <summary>
38
+ /// Registered as a broadcast handler bound to a specific source.
39
+ /// </summary>
22
40
  Broadcast,
41
+
42
+ /// <summary>
43
+ /// Registered as a broadcast handler without an explicit source.
44
+ /// </summary>
23
45
  BroadcastWithoutSource,
46
+
47
+ /// <summary>
48
+ /// Registered as a targeted handler that ignores the runtime target.
49
+ /// </summary>
24
50
  TargetedWithoutTargeting,
51
+
52
+ /// <summary>
53
+ /// Registered as a global catch-all handler.
54
+ /// </summary>
25
55
  GlobalAcceptAll,
56
+
57
+ /// <summary>
58
+ /// Registered as an interceptor (exact type recorded separately).
59
+ /// </summary>
26
60
  Interceptor,
61
+
62
+ /// <summary>
63
+ /// Registered as a post-processor for untargeted messages.
64
+ /// </summary>
27
65
  UntargetedPostProcessor,
66
+
67
+ /// <summary>
68
+ /// Registered as a post-processor for targeted messages.
69
+ /// </summary>
28
70
  TargetedPostProcessor,
71
+
72
+ /// <summary>
73
+ /// Registered as a post-processor for broadcast messages.
74
+ /// </summary>
29
75
  BroadcastPostProcessor,
76
+
77
+ /// <summary>
78
+ /// Registered as a post-processor for targeted messages that ignore the runtime target.
79
+ /// </summary>
30
80
  TargetedWithoutTargetingPostProcessor,
81
+
82
+ /// <summary>
83
+ /// Registered as a post-processor for broadcasts without explicit source information.
84
+ /// </summary>
31
85
  BroadcastWithoutSourcePostProcessor,
32
86
  }
33
87
 
@@ -88,6 +142,10 @@ namespace DxMessaging.Core.MessageBus
88
142
  #endif
89
143
  }
90
144
 
145
+ /// <summary>
146
+ /// Returns a descriptive string that includes key registration metadata for diagnostics.
147
+ /// </summary>
148
+ /// <returns>Human-readable summary of this registration entry.</returns>
91
149
  public override string ToString()
92
150
  {
93
151
  return new
@@ -26,6 +26,12 @@ namespace DxMessaging.Core.MessageBus
26
26
 
27
27
  private bool _enabled;
28
28
 
29
+ /// <summary>
30
+ /// Creates a new registration log.
31
+ /// </summary>
32
+ /// <param name="enabled">
33
+ /// When <c>true</c>, logging starts immediately; otherwise call <see cref="Enabled"/> to enable later.
34
+ /// </param>
29
35
  public RegistrationLog(bool enabled = false)
30
36
  {
31
37
  _enabled = enabled;
@@ -92,6 +98,10 @@ namespace DxMessaging.Core.MessageBus
92
98
  return registrations.ToString();
93
99
  }
94
100
 
101
+ /// <summary>
102
+ /// Serializes the log using the default formatter (<see cref="MessagingRegistration.ToString"/>).
103
+ /// </summary>
104
+ /// <returns>String containing all recorded registrations.</returns>
95
105
  public override string ToString()
96
106
  {
97
107
  return ToString(null);
@@ -345,12 +345,14 @@ namespace DxMessaging.Core
345
345
  )
346
346
  where TMessage : IMessage;
347
347
 
348
+ private static readonly object GlobalResetLock = new object();
349
+
348
350
  /// <summary>
349
351
  /// Global message bus used when no explicit bus is provided.
350
352
  /// </summary>
351
353
  private static IMessageBus _globalMessageBus;
352
354
 
353
- private static readonly MessageBus.MessageBus _defaultGlobalMessageBus = new();
355
+ private static MessageBus.MessageBus _defaultGlobalMessageBus = new MessageBus.MessageBus();
354
356
 
355
357
  /// <summary>
356
358
  /// Gets the process-wide <see cref="IMessageBus"/> used when no explicit bus is supplied.
@@ -363,16 +365,17 @@ namespace DxMessaging.Core
363
365
  public static IMessageBus MessageBus => _globalMessageBus;
364
366
 
365
367
  /// <summary>
366
- /// Gets the original global <see cref="IMessageBus"/> instance created during static initialisation.
368
+ /// Gets the baseline global <see cref="IMessageBus"/> instance used when no custom bus is configured.
367
369
  /// </summary>
368
370
  /// <remarks>
369
- /// This reference never changes even when <see cref="SetGlobalMessageBus(IMessageBus)"/> is invoked.
371
+ /// The instance is recreated when <see cref="DxMessagingStaticState.Reset"/> runs so that domain-reload-disabled
372
+ /// environments can obtain a clean slate.
370
373
  /// </remarks>
371
374
  public static IMessageBus InitialGlobalMessageBus => _defaultGlobalMessageBus;
372
375
 
373
376
  static MessageHandler()
374
377
  {
375
- _globalMessageBus = _defaultGlobalMessageBus;
378
+ ResetStatics();
376
379
  }
377
380
 
378
381
  /// <summary>
@@ -417,11 +420,14 @@ namespace DxMessaging.Core
417
420
  /// Restores the global <see cref="MessageBus.MessageBus"/> to the built-in default instance.
418
421
  /// </summary>
419
422
  /// <remarks>
420
- /// The default instance is created during static initialisation and reused across resets to minimise allocations.
423
+ /// The default instance is recreated by <see cref="ResetStatics"/> when the static state reset utility runs.
421
424
  /// </remarks>
422
425
  public static void ResetGlobalMessageBus()
423
426
  {
424
- _globalMessageBus = _defaultGlobalMessageBus;
427
+ lock (GlobalResetLock)
428
+ {
429
+ _globalMessageBus = _defaultGlobalMessageBus;
430
+ }
425
431
  }
426
432
 
427
433
  /// <summary>
@@ -434,6 +440,21 @@ namespace DxMessaging.Core
434
440
  return new GlobalMessageBusScope(messageBus);
435
441
  }
436
442
 
443
+ /// <summary>
444
+ /// Recreates the built-in global <see cref="MessageBus.MessageBus"/> and assigns it as the active global bus.
445
+ /// </summary>
446
+ /// <remarks>
447
+ /// Invoked by <see cref="DxMessagingStaticState.Reset"/> to provide a clean slate when domain reloads are disabled.
448
+ /// </remarks>
449
+ internal static void ResetStatics()
450
+ {
451
+ lock (GlobalResetLock)
452
+ {
453
+ _defaultGlobalMessageBus.ResetState();
454
+ _globalMessageBus = _defaultGlobalMessageBus;
455
+ }
456
+ }
457
+
437
458
  /// <summary>
438
459
  /// Represents a disposable override scope for the global message bus.
439
460
  /// </summary>
@@ -462,6 +483,9 @@ namespace DxMessaging.Core
462
483
  }
463
484
  }
464
485
 
486
+ /// <summary>
487
+ /// Restores the previously active global message bus when the scope ends.
488
+ /// </summary>
465
489
  public void Dispose()
466
490
  {
467
491
  if (_disposed)
@@ -514,6 +538,14 @@ namespace DxMessaging.Core
514
538
  /// </remarks>
515
539
  public IMessageBus DefaultMessageBus => _defaultMessageBus ?? MessageBus;
516
540
 
541
+ /// <summary>
542
+ /// Initializes a message handler bound to the specified owner and optional default bus.
543
+ /// </summary>
544
+ /// <param name="owner">Identity of the object that owns this handler.</param>
545
+ /// <param name="defaultMessageBus">
546
+ /// Preferred bus to use when registrations do not specify one. Falls back to
547
+ /// <see cref="MessageBus"/> if omitted.
548
+ /// </param>
517
549
  public MessageHandler(InstanceId owner, IMessageBus defaultMessageBus = null)
518
550
  {
519
551
  this.owner = owner;
@@ -1832,11 +1864,21 @@ namespace DxMessaging.Core
1832
1864
  return messageBus.RegisterTargetedInterceptor(interceptor, priority);
1833
1865
  }
1834
1866
 
1867
+ /// <summary>
1868
+ /// Checks equality against another object.
1869
+ /// </summary>
1870
+ /// <param name="obj">Object to compare.</param>
1871
+ /// <returns><c>true</c> when <paramref name="obj"/> is a <see cref="MessageHandler"/> with the same owner.</returns>
1835
1872
  public override bool Equals(object obj)
1836
1873
  {
1837
1874
  return Equals(obj as MessageHandler);
1838
1875
  }
1839
1876
 
1877
+ /// <summary>
1878
+ /// Checks equality against another handler instance.
1879
+ /// </summary>
1880
+ /// <param name="other">Handler to compare.</param>
1881
+ /// <returns><c>true</c> when both handlers share the same <see cref="owner"/>.</returns>
1840
1882
  public bool Equals(MessageHandler other)
1841
1883
  {
1842
1884
  if (other == null)
@@ -1852,11 +1894,20 @@ namespace DxMessaging.Core
1852
1894
  return owner.Equals(other.owner);
1853
1895
  }
1854
1896
 
1897
+ /// <summary>
1898
+ /// Produces a hash code based on the owning instance.
1899
+ /// </summary>
1900
+ /// <returns>Hash code derived from <see cref="owner"/>.</returns>
1855
1901
  public override int GetHashCode()
1856
1902
  {
1857
1903
  return owner.GetHashCode();
1858
1904
  }
1859
1905
 
1906
+ /// <summary>
1907
+ /// Compares this handler with another handler for ordering.
1908
+ /// </summary>
1909
+ /// <param name="other">Handler to compare.</param>
1910
+ /// <returns>Relative ordering based on <see cref="owner"/>.</returns>
1860
1911
  public int CompareTo(MessageHandler other)
1861
1912
  {
1862
1913
  if (other == null)
@@ -1867,11 +1918,22 @@ namespace DxMessaging.Core
1867
1918
  return owner.CompareTo(other.owner);
1868
1919
  }
1869
1920
 
1921
+ /// <summary>
1922
+ /// Compares this handler with an arbitrary object.
1923
+ /// </summary>
1924
+ /// <param name="obj">Object to compare.</param>
1925
+ /// <returns>
1926
+ /// Relative ordering when <paramref name="obj"/> is a <see cref="MessageHandler"/>; otherwise <c>-1</c>.
1927
+ /// </returns>
1870
1928
  public int CompareTo(object obj)
1871
1929
  {
1872
1930
  return CompareTo(obj as MessageHandler);
1873
1931
  }
1874
1932
 
1933
+ /// <summary>
1934
+ /// Returns a human-readable representation containing the owner identifier.
1935
+ /// </summary>
1936
+ /// <returns>String describing the handler.</returns>
1875
1937
  public override string ToString()
1876
1938
  {
1877
1939
  return new { OwnerId = owner }.ToString();
@@ -1965,6 +2027,11 @@ namespace DxMessaging.Core
1965
2027
  {
1966
2028
  internal readonly struct Entry
1967
2029
  {
2030
+ /// <summary>
2031
+ /// Initializes an entry used to track handler invocation counts.
2032
+ /// </summary>
2033
+ /// <param name="handler">Handler delegate being tracked.</param>
2034
+ /// <param name="count">Number of times the handler has been cached.</param>
1968
2035
  public Entry(T handler, int count)
1969
2036
  {
1970
2037
  this.handler = handler;
@@ -2345,6 +2412,12 @@ namespace DxMessaging.Core
2345
2412
  }
2346
2413
  }
2347
2414
 
2415
+ /// <summary>
2416
+ /// Runs untargeted post-processing handlers for the supplied message.
2417
+ /// </summary>
2418
+ /// <param name="message">Message being processed.</param>
2419
+ /// <param name="priority">Priority bucket currently executing.</param>
2420
+ /// <param name="emissionId">Emission identifier used to cache handler stacks.</param>
2348
2421
  public void HandleUntargetedPostProcessing(ref T message, int priority, long emissionId)
2349
2422
  {
2350
2423
  RunFastHandlers(
@@ -2356,6 +2429,13 @@ namespace DxMessaging.Core
2356
2429
  RunHandlers(_untargetedPostProcessingHandlers, ref message, priority, emissionId);
2357
2430
  }
2358
2431
 
2432
+ /// <summary>
2433
+ /// Runs targeted post-processing handlers for the supplied message and recipient.
2434
+ /// </summary>
2435
+ /// <param name="target">Recipient of the message.</param>
2436
+ /// <param name="message">Message being processed.</param>
2437
+ /// <param name="priority">Priority bucket currently executing.</param>
2438
+ /// <param name="emissionId">Emission identifier used to cache handler stacks.</param>
2359
2439
  public void HandleTargetedPostProcessing(
2360
2440
  ref InstanceId target,
2361
2441
  ref T message,
@@ -2379,6 +2459,13 @@ namespace DxMessaging.Core
2379
2459
  );
2380
2460
  }
2381
2461
 
2462
+ /// <summary>
2463
+ /// Runs targeted post-processing handlers that do not require a <see cref="InstanceId"/> target binding.
2464
+ /// </summary>
2465
+ /// <param name="target">Recipient of the message.</param>
2466
+ /// <param name="message">Message being processed.</param>
2467
+ /// <param name="priority">Priority bucket currently executing.</param>
2468
+ /// <param name="emissionId">Emission identifier used to cache handler stacks.</param>
2382
2469
  public void HandleTargetedWithoutTargetingPostProcessing(
2383
2470
  ref InstanceId target,
2384
2471
  ref T message,
@@ -2402,6 +2489,13 @@ namespace DxMessaging.Core
2402
2489
  );
2403
2490
  }
2404
2491
 
2492
+ /// <summary>
2493
+ /// Runs broadcast post-processing handlers that expect a concrete source identifier.
2494
+ /// </summary>
2495
+ /// <param name="source">Origin of the message.</param>
2496
+ /// <param name="message">Message being processed.</param>
2497
+ /// <param name="priority">Priority bucket currently executing.</param>
2498
+ /// <param name="emissionId">Emission identifier used to cache handler stacks.</param>
2405
2499
  public void HandleSourcedBroadcastPostProcessing(
2406
2500
  ref InstanceId source,
2407
2501
  ref T message,
@@ -2425,6 +2519,13 @@ namespace DxMessaging.Core
2425
2519
  );
2426
2520
  }
2427
2521
 
2522
+ /// <summary>
2523
+ /// Runs broadcast post-processing handlers that do not rely on a specific source identifier.
2524
+ /// </summary>
2525
+ /// <param name="source">Origin of the message.</param>
2526
+ /// <param name="message">Message being processed.</param>
2527
+ /// <param name="priority">Priority bucket currently executing.</param>
2528
+ /// <param name="emissionId">Emission identifier used to cache handler stacks.</param>
2428
2529
  public void HandleBroadcastWithoutSourcePostProcessing(
2429
2530
  ref InstanceId source,
2430
2531
  ref T message,
@@ -19,6 +19,21 @@ namespace DxMessaging.Core
19
19
  private readonly long _id;
20
20
  private readonly int _hashCode;
21
21
 
22
+ internal static long GetCurrentIdSeed()
23
+ {
24
+ return Interlocked.Read(ref StaticIdCount);
25
+ }
26
+
27
+ internal static void SetIdSeed(long value)
28
+ {
29
+ _ = Interlocked.Exchange(ref StaticIdCount, value);
30
+ }
31
+
32
+ internal static void ResetIdSeed()
33
+ {
34
+ SetIdSeed(0);
35
+ }
36
+
22
37
  /// <summary>
23
38
  /// Creates a new unique handle.
24
39
  /// </summary>
@@ -49,6 +64,12 @@ namespace DxMessaging.Core
49
64
  return !left.Equals(right);
50
65
  }
51
66
 
67
+ /// <summary>
68
+ /// Determines whether the left handle sorts after the right handle.
69
+ /// </summary>
70
+ /// <param name="left">Left-hand handle.</param>
71
+ /// <param name="right">Right-hand handle.</param>
72
+ /// <returns><c>true</c> when <paramref name="left"/> sorts after <paramref name="right"/>.</returns>
52
73
  public static bool operator >(
53
74
  MessageRegistrationHandle left,
54
75
  MessageRegistrationHandle right
@@ -57,6 +78,12 @@ namespace DxMessaging.Core
57
78
  return left.CompareTo(right) > 0;
58
79
  }
59
80
 
81
+ /// <summary>
82
+ /// Determines whether the left handle sorts before the right handle.
83
+ /// </summary>
84
+ /// <param name="left">Left-hand handle.</param>
85
+ /// <param name="right">Right-hand handle.</param>
86
+ /// <returns><c>true</c> when <paramref name="left"/> sorts before <paramref name="right"/>.</returns>
60
87
  public static bool operator <(
61
88
  MessageRegistrationHandle left,
62
89
  MessageRegistrationHandle right
@@ -81,11 +108,23 @@ namespace DxMessaging.Core
81
108
  return left.CompareTo(right) >= 0;
82
109
  }
83
110
 
111
+ /// <summary>
112
+ /// Compares this handle with another handle for ordering.
113
+ /// </summary>
114
+ /// <param name="other">Other handle to compare with.</param>
115
+ /// <returns>Relative ordering as defined by <see cref="IComparable{T}.CompareTo(T)"/>.</returns>
84
116
  public int CompareTo(MessageRegistrationHandle other)
85
117
  {
86
118
  return _id.CompareTo(other._id);
87
119
  }
88
120
 
121
+ /// <summary>
122
+ /// Compares this handle with an arbitrary object.
123
+ /// </summary>
124
+ /// <param name="obj">Object to compare with.</param>
125
+ /// <returns>
126
+ /// Relative ordering when <paramref name="obj"/> is a <see cref="MessageRegistrationHandle"/>; otherwise <c>-1</c>.
127
+ /// </returns>
89
128
  public int CompareTo(object obj)
90
129
  {
91
130
  if (obj is MessageRegistrationHandle handle)
@@ -96,21 +135,41 @@ namespace DxMessaging.Core
96
135
  return -1;
97
136
  }
98
137
 
138
+ /// <summary>
139
+ /// Checks equality against another object.
140
+ /// </summary>
141
+ /// <param name="other">Object to compare.</param>
142
+ /// <returns>
143
+ /// <c>true</c> when <paramref name="other"/> is a <see cref="MessageRegistrationHandle"/> representing the same registration.
144
+ /// </returns>
99
145
  public override bool Equals(object other)
100
146
  {
101
147
  return other is MessageRegistrationHandle handle && Equals(handle);
102
148
  }
103
149
 
150
+ /// <summary>
151
+ /// Checks equality against another handle.
152
+ /// </summary>
153
+ /// <param name="other">Handle to compare.</param>
154
+ /// <returns><c>true</c> when both handles represent the same registration.</returns>
104
155
  public bool Equals(MessageRegistrationHandle other)
105
156
  {
106
157
  return _id == other._id;
107
158
  }
108
159
 
160
+ /// <summary>
161
+ /// Produces a hash code suitable for dictionary or set lookups.
162
+ /// </summary>
163
+ /// <returns>Hash code derived from the internal identifier.</returns>
109
164
  public override int GetHashCode()
110
165
  {
111
166
  return _hashCode;
112
167
  }
113
168
 
169
+ /// <summary>
170
+ /// Returns a string representation of the handle, including the underlying identifier.
171
+ /// </summary>
172
+ /// <returns>Human-readable representation of the handle.</returns>
114
173
  public override string ToString()
115
174
  {
116
175
  return new { Id = _id }.ToString();
@@ -39,7 +39,7 @@ namespace DxMessaging.Core
39
39
  /// }
40
40
  /// </code>
41
41
  /// </example>
42
- public sealed class MessageRegistrationToken
42
+ public sealed class MessageRegistrationToken : IDisposable
43
43
  {
44
44
  /// <summary>
45
45
  /// Whether the token is currently enabled (registrations are active).
@@ -71,7 +71,7 @@ namespace DxMessaging.Core
71
71
 
72
72
  private IMessageBus _messageBus;
73
73
  private bool _enabled;
74
- private bool _diagnosticMode = IMessageBus.GlobalDiagnosticsMode;
74
+ private bool _diagnosticMode = IMessageBus.ShouldEnableDiagnostics();
75
75
 
76
76
  private MessageRegistrationToken(MessageHandler messageHandler, IMessageBus messageBus)
77
77
  {
@@ -1995,6 +1995,11 @@ namespace DxMessaging.Core
1995
1995
  private readonly MessageRegistrationHandle _handle;
1996
1996
  private bool _valid;
1997
1997
 
1998
+ /// <summary>
1999
+ /// Creates a disposable wrapper that removes a registration when disposed.
2000
+ /// </summary>
2001
+ /// <param name="token">Token that owns the registration.</param>
2002
+ /// <param name="handle">Handle to remove when disposed.</param>
1998
2003
  public RegistrationDisposable(
1999
2004
  MessageRegistrationToken token,
2000
2005
  MessageRegistrationHandle handle
@@ -2005,6 +2010,9 @@ namespace DxMessaging.Core
2005
2010
  _valid = true;
2006
2011
  }
2007
2012
 
2013
+ /// <summary>
2014
+ /// Removes the wrapped registration the first time it is invoked.
2015
+ /// </summary>
2008
2016
  public void Dispose()
2009
2017
  {
2010
2018
  // Best-effort idempotence; AsDisposable instances are short-lived and immutable
@@ -2035,5 +2043,13 @@ namespace DxMessaging.Core
2035
2043
 
2036
2044
  return new MessageRegistrationToken(messageHandler, messageBus);
2037
2045
  }
2046
+
2047
+ /// <summary>
2048
+ /// Removes all staged registrations and releases references to the handler.
2049
+ /// </summary>
2050
+ public void Dispose()
2051
+ {
2052
+ UnregisterAll();
2053
+ }
2038
2054
  }
2039
2055
  }