com.wallstop-studios.dxmessaging 2.0.0-rc27.3.1 → 2.0.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.
@@ -14,15 +14,15 @@ See also: `Docs/DesignAndArchitecture.md#performance-optimizations` for design d
14
14
 
15
15
  | Message Tech | Operations / Second | Allocations? |
16
16
  | ---------------------------------- | ------------------- | ------------ |
17
- | Unity | 2,328,200 | Yes |
18
- | DxMessaging (GameObject) - Normal | 8,288,600 | No |
19
- | DxMessaging (Component) - Normal | 8,298,200 | No |
20
- | DxMessaging (GameObject) - No-Copy | 9,329,200 | No |
21
- | DxMessaging (Component) - No-Copy | 9,350,800 | No |
22
- | DxMessaging (Untargeted) - No-Copy | 14,705,000 | No |
23
- | Reflexive (One Argument) | 2,806,800 | No |
24
- | Reflexive (Two Arguments) | 2,325,800 | No |
25
- | Reflexive (Three Arguments) | 2,322,200 | No |
17
+ | Unity | 2,747,000 | Yes |
18
+ | DxMessaging (GameObject) - Normal | 8,412,800 | No |
19
+ | DxMessaging (Component) - Normal | 8,406,200 | No |
20
+ | DxMessaging (GameObject) - No-Copy | 9,358,200 | No |
21
+ | DxMessaging (Component) - No-Copy | 9,177,800 | No |
22
+ | DxMessaging (Untargeted) - No-Copy | 14,922,800 | No |
23
+ | Reflexive (One Argument) | 2,832,800 | No |
24
+ | Reflexive (Two Arguments) | 2,371,200 | No |
25
+ | Reflexive (Three Arguments) | 2,350,600 | No |
26
26
 
27
27
  ## macOS
28
28
 
@@ -434,35 +434,100 @@ START HERE
434
434
 
435
435
  ### "Do I always need MessageAwareComponent?"
436
436
 
437
- **For Unity:** Yes, it's the easiest way! It handles all lifecycle automatically.
437
+ **For Unity:** Yes! It's the easiest way. Think of it like `MonoBehaviour` - you inherit from it and it handles all the messy lifecycle stuff automatically.
438
438
 
439
- **For pure C#:** No, you can use `MessageRegistrationToken` directly.
439
+ **For pure C#:** No, you can use `MessageRegistrationToken` directly if you're not in Unity.
440
+
441
+ **Bottom line:** If you're in Unity, just use `MessageAwareComponent`. It'll save you hours of debugging.
440
442
 
441
443
  ### "Can I send a message to multiple targets?"
442
444
 
443
- **No** - Targeted is for ONE target. Instead:
445
+ **No** - Targeted messages go to ONE specific entity (like mailing a letter to one address).
446
+
447
+ #### Instead, use
448
+
449
+ - **Untargeted** if literally everyone should hear it (like a megaphone announcement)
450
+ - **Broadcast** if it's from one source and many can observe (like a news broadcast)
451
+
452
+ ##### Example
444
453
 
445
- - Use **Untargeted** if everyone should hear it
446
- - Use **Broadcast** if it's from a source and many can observe
454
+ ```csharp
455
+ // DON'T: Try to target multiple entities
456
+ msg.EmitComponentTargeted(player1);
457
+ msg.EmitComponentTargeted(player2); // Feels wrong, right?
458
+
459
+ // ✅ DO: Use broadcast so everyone can listen
460
+ msg.EmitGameObjectBroadcast(enemy); // Now anyone can observe this enemy
461
+ ```
447
462
 
448
463
  ### "What if I forget to unsubscribe?"
449
464
 
450
- **You can't!** DxMessaging handles it automatically when your component is destroyed. That's the magic! ✨
465
+ **You literally can't forget!** 🎉
466
+
467
+ When your component is destroyed, DxMessaging automatically cleans up. No `OnDestroy()` needed. No memory leaks possible.
468
+
469
+ #### Old way (easy to forget)
470
+
471
+ ```csharp
472
+ void OnEnable() { GameManager.OnScoreChanged += Update; }
473
+ void OnDisable() { GameManager.OnScoreChanged -= Update; } // Forgot this? LEAK!
474
+ ```
475
+
476
+ ##### DxMessaging way (impossible to forget)
477
+
478
+ ```csharp
479
+ protected override void RegisterMessageHandlers() {
480
+ _ = Token.RegisterUntargeted<ScoreChanged>(Update);
481
+ }
482
+ // That's it! Automatic cleanup when component dies.
483
+ ```
451
484
 
452
485
  ### "Is it slower than regular events?"
453
486
 
454
- **Barely** (~10ns per handler). You get SO much more (safety, observability, ordering) for negligible cost.
487
+ **Barely** (~10ns per handler = 0.00001 milliseconds).
488
+
489
+ #### Put it this way
490
+
491
+ - Regular C# event: ~50ns
492
+ - DxMessaging: ~60ns
493
+ - The difference: Drinking a coffee takes 3 billion nanoseconds
494
+
495
+ You get automatic lifecycle, zero leaks, full observability, and predictable ordering for a 20% overhead that's **completely negligible** in any real game.
455
496
 
456
497
  ### "Can I cancel a message?"
457
498
 
458
- **Yes!** Use an **Interceptor**:
499
+ #### Yes! That's what interceptors are for
459
500
 
460
501
  ```csharp
461
- _ = token.RegisterInterceptor<Damage>(
462
- (ref Damage msg) => msg.amount > 0 // Return false to cancel
502
+ // Cancel invalid damage
503
+ _ = token.RegisterBroadcastInterceptor<TookDamage>(
504
+ (ref InstanceId source, ref TookDamage msg) => {
505
+ if (msg.amount <= 0) return false; // Cancel invalid damage
506
+ if (IsInvincible(source)) return false; // Cancel during invincibility
507
+ return true; // Allow
508
+ }
463
509
  );
464
510
  ```
465
511
 
512
+ ##### Real-world uses
513
+
514
+ - Block input during cutscenes
515
+ - Cancel damage when invincible
516
+ - Prevent cheating (clamp values)
517
+ - Enforce game rules globally
518
+
519
+ ### "Can I see what messages are firing?"
520
+
521
+ #### Yes! Open any component in the Inspector and scroll down
522
+
523
+ You'll see:
524
+
525
+ - Message history (last 50 messages with timestamps)
526
+ - Active registrations (what you're listening to)
527
+ - Call counts (how many times each handler ran)
528
+
529
+ **No more guessing.** You can literally see your event flow in real-time.
530
+
466
531
  ## ✅ Quick Checklist: Am I Doing It Right
467
532
 
468
533
  - [ ] Using `MessageAwareComponent` for Unity components? ✅
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: f3159f12192b58547a5f8576eeca5fcf
2
+ guid: ac94a51a66736f443b68aa222d32eb04
3
3
  PluginImporter:
4
4
  externalObjects: {}
5
5
  serializedVersion: 2
@@ -14,12 +14,12 @@ PluginImporter:
14
14
  - first:
15
15
  Any:
16
16
  second:
17
- enabled: 0
17
+ enabled: 1
18
18
  settings: {}
19
19
  - first:
20
20
  Editor: Editor
21
21
  second:
22
- enabled: 1
22
+ enabled: 0
23
23
  settings:
24
24
  DefaultValueInitialized: true
25
25
  - first:
@@ -14,12 +14,12 @@ PluginImporter:
14
14
  - first:
15
15
  Any:
16
16
  second:
17
- enabled: 1
17
+ enabled: 0
18
18
  settings: {}
19
19
  - first:
20
20
  Editor: Editor
21
21
  second:
22
- enabled: 0
22
+ enabled: 1
23
23
  settings:
24
24
  DefaultValueInitialized: true
25
25
  - first:
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DxMessaging for Unity — The Modern Event System
2
2
 
3
- [![Unity](https://img.shields.io/badge/Unity-2021.3+-black.svg)](https://unity.com/download)<br/>
3
+ [![Unity](https://img.shields.io/badge/Unity-2021.3+-black.svg)](https://unity.com/releases/editor)<br/>
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)<br/>
5
5
  [![Version](https://img.shields.io/badge/version-2.0.0--rc27-green.svg)](package.json)<br/>
6
6
  [![Performance: OS-Specific Benchmarks](https://img.shields.io/badge/Performance-OS--specific-blueviolet.svg)](Docs/Performance.md)<br/>
@@ -32,17 +32,27 @@ Think of it as **the event system Unity should have built-in** — one that actu
32
32
 
33
33
  ## 30-Second Elevator Pitch
34
34
 
35
- **Problem:** In Unity, you're stuck with manual event management (memory leaks!), tight coupling (everything knows everything!), or messy global event buses (no context, no control!).
35
+ ### If you've ever
36
36
 
37
- **Solution:** DxMessaging gives you three simple message types:
37
+ - Forgotten to unsubscribe from an event and spent hours debugging memory leaks
38
+ - Had UI code tangled with 15 different game systems
39
+ - Wondered "which event fired when?" with no way to see message flow
40
+ - Copy-pasted event boilerplate dozens of times
38
41
 
39
- 1. **Untargeted** - "Hey everyone!" (global events)
40
- 1. **Targeted** - "Hey YOU!" (commands to specific objects)
41
- 1. **Broadcast** - "I did something!" (events from sources)
42
+ #### Then DxMessaging solves your problems
42
43
 
43
- **Result:** Zero memory leaks (automatic lifecycle), zero coupling (no references needed), full observability (see everything in Inspector), and predictable execution (priority-based ordering).
44
+ - **Zero memory leaks** - automatic lifecycle management, no manual unsubscribe
45
+ - **Zero coupling** - systems communicate without knowing each other exist
46
+ - **Full visibility** - see every message in the Inspector with timestamps and payloads
47
+ - **Complete control** - priority-based ordering, validation, and interception
44
48
 
45
- **One line:** It's like C# events, but with superpowers and no footguns. 🚀
49
+ ##### Three simple message types
50
+
51
+ 1. **Untargeted** - "Everyone listen!" (pause game, settings changed)
52
+ 1. **Targeted** - "Tell Player to heal" (commands to specific entities)
53
+ 1. **Broadcast** - "Enemy took damage" (events others can observe)
54
+
55
+ **One line:** It's the event system Unity should have shipped with - type-safe, leak-proof, and actually debuggable. 🚀
46
56
 
47
57
  ---
48
58
 
@@ -152,95 +162,194 @@ Looking for hard numbers? See OS-specific [Performance Benchmarks](Docs/Performa
152
162
 
153
163
  ## Why DxMessaging
154
164
 
155
- ### The Problem You Know
165
+ ### The Problems You've Probably Hit
166
+
167
+ #### Scenario 1: The Memory Leak Nightmare
168
+
169
+ You write this innocent-looking code:
170
+
171
+ ```csharp
172
+ public class GameUI : MonoBehaviour {
173
+ void OnEnable() {
174
+ GameManager.Instance.OnScoreChanged += UpdateScore;
175
+ }
176
+ // Oops, forgot OnDisable... leak! 💀
177
+ }
178
+ ```
179
+
180
+ Months later: "Why is our game using 2GB of RAM after an hour?"
181
+
182
+ ##### Scenario 2: The Spaghetti Mess
156
183
 
157
184
  ```csharp
158
- // C# Events: Manual lifecycle hell
159
185
  public class GameUI : MonoBehaviour {
160
186
  [SerializeField] private Player player;
161
187
  [SerializeField] private EnemySpawner spawner;
188
+ [SerializeField] private InventorySystem inventory;
189
+ [SerializeField] private QuestSystem quests;
190
+ [SerializeField] private AudioManager audio;
191
+ // ... 15 more SerializeFields ...
162
192
 
163
193
  void Awake() {
164
194
  player.OnHealthChanged += UpdateHealth;
165
195
  spawner.OnWaveStart += ShowWave;
166
- }
167
-
168
- void OnDestroy() {
169
- // Easy to forget = memory leaks
170
- player.OnHealthChanged -= UpdateHealth;
171
- spawner.OnWaveStart -= ShowWave;
196
+ inventory.OnItemAdded += RefreshInventory;
197
+ quests.OnQuestCompleted += ShowQuestNotification;
198
+ // ... 20 more subscriptions ...
172
199
  }
173
200
  }
174
201
  ```
175
202
 
176
- Problems:
203
+ **Your UI now depends on EVERYTHING.** Good luck refactoring that.
204
+
205
+ ###### Scenario 3: The Debugging Black Hole
206
+
207
+ Player reports: "My health bar didn't update!"
208
+
209
+ You think: "Okay, which of the 47 events touching health failed? And in what order?"
177
210
 
178
- - Manual subscribe/unsubscribe (memory leaks waiting to happen)
179
- - ❌ Tight coupling (UI needs references to every system)
180
- - No execution order control
181
- - ❌ Impossible to debug ("which event fired when?")
211
+ **30 minutes later:** Still setting breakpoints everywhere...
212
+
213
+ ### Common Problems
214
+
215
+ - ❌ **Memory leaks** from forgotten unsubscribes (every Unity dev's nightmare)
216
+ - ❌ **Tight coupling** making refactoring terrifying ("change one thing, break five others")
217
+ - ❌ **No execution order control** ("why does the UI update before the player takes damage?")
218
+ - ❌ **Impossible to debug** ("what fired when?" has no answer)
219
+ - ❌ **Boilerplate overload** (write 50 lines for 3 events)
182
220
 
183
221
  ### The DxMessaging Solution
184
222
 
223
+ #### Same scenarios, zero pain
224
+
225
+ ##### Scenario 1: No More Memory Leaks
226
+
185
227
  ```csharp
186
- using DxMessaging.Core.Attributes;
187
- using DxMessaging.Core.Extensions;
188
- using DxMessaging.Unity;
228
+ public class GameUI : MessageAwareComponent {
229
+ protected override void RegisterMessageHandlers() {
230
+ base.RegisterMessageHandlers();
231
+ _ = Token.RegisterUntargeted<ScoreChanged>(UpdateScore);
232
+ }
233
+ // That's it! No manual cleanup needed.
234
+ // Token automatically handles OnEnable/OnDisable/OnDestroy
235
+ }
236
+ ```
237
+
238
+ **Automatic lifecycle = impossible to leak.** 🎉
239
+
240
+ ###### Scenario 2: No More Coupling
241
+
242
+ ```csharp
243
+ public class GameUI : MessageAwareComponent {
244
+ // Zero SerializeFields! Zero references!
245
+
246
+ protected override void RegisterMessageHandlers() {
247
+ base.RegisterMessageHandlers();
248
+ _ = Token.RegisterUntargeted<HealthChanged>(OnHealth);
249
+ _ = Token.RegisterUntargeted<WaveStarted>(OnWave);
250
+ _ = Token.RegisterUntargeted<ItemAdded>(OnItem);
251
+ // Listen to anything, from anywhere, no coupling
252
+ }
253
+ }
254
+ ```
255
+
256
+ **Your UI is now independent.** Swap systems freely without breaking anything.
257
+
258
+ ###### Scenario 3: Debugging is Built In
259
+
260
+ Open any `MessageAwareComponent` in the Inspector:
189
261
 
190
- // 1. Define messages (clean, typed, immutable)
262
+ ```text
263
+ Message History (last 50):
264
+ [12:34:56] HealthChanged (amount: 25) → Priority: 0
265
+ [12:34:55] ItemAdded (id: 42, count: 1) → Priority: 5
266
+ [12:34:54] WaveStarted (wave: 3) → Priority: 0
267
+
268
+ Active Registrations:
269
+ ✓ HealthChanged (5 handlers)
270
+ ✓ ItemAdded (2 handlers)
271
+ ```
272
+
273
+ **See exactly what fired, when, and who handled it.** No guesswork.
274
+
275
+ ### How It Transforms Your Code
276
+
277
+ ```csharp
278
+ // 1. Define messages (clean, typed, discoverable)
191
279
  [DxTargetedMessage]
192
280
  [DxAutoConstructor]
193
281
  public readonly partial struct Heal { public readonly int amount; }
194
282
 
195
283
  // 2. Listen (automatic lifecycle - zero leaks)
196
- public class GameUI : MessageAwareComponent {
284
+ public class Player : MessageAwareComponent {
197
285
  protected override void RegisterMessageHandlers() {
198
286
  base.RegisterMessageHandlers();
199
287
  _ = Token.RegisterComponentTargeted<Heal>(this, OnHeal);
200
288
  }
201
289
 
202
- void OnHeal(ref Heal m) => UpdateHealthBar(m.amount);
290
+ void OnHeal(ref Heal m) {
291
+ health += m.amount;
292
+ Debug.Log($"Healed {m.amount}!");
293
+ }
203
294
  }
204
295
 
205
- // 3. Send (discoverable, type-safe)
206
- var heal = new Heal(10);
207
- heal.EmitGameObjectTargeted(gameObject);
296
+ // 3. Send (from anywhere - zero coupling)
297
+ var heal = new Heal(50);
298
+ heal.EmitComponentTargeted(playerComponent);
208
299
  ```
209
300
 
210
- Benefits:
301
+ #### What you get
211
302
 
212
- - ✅ **Zero memory leaks** - automatic lifecycle via tokens
213
- - ✅ **Full decoupling** - no direct references needed
214
- - ✅ **Predictable order** - priority-based execution
215
- - ✅ **Type-safe** - compile-time guarantees
216
- - ✅ **Observable** - built-in Inspector diagnostics
217
- - ✅ **Intercept & validate** - enforce rules before handlers run
303
+ - ✅ **Zero memory leaks** - tokens clean up automatically when components are destroyed
304
+ - ✅ **Zero coupling** - no SerializeFields, no GetComponent, no direct references
305
+ - ✅ **Full visibility** - see message flow in Inspector with timestamps and payloads
306
+ - ✅ **Predictable order** - priority-based execution (no more mystery race conditions)
307
+ - ✅ **Type-safe** - compile-time guarantees, refactor with confidence
308
+ - ✅ **Intercept & validate** - enforce rules before handlers run (clamp damage, block invalid input)
309
+ - ✅ **Extension points everywhere** - interceptors, handlers, post-processors with priorities
218
310
 
219
311
  ## Killer Features
220
312
 
221
- ### 🚀 Performance: Zero-Allocation Design
313
+ Why DxMessaging is different:
314
+
315
+ ### 🚀 Performance: Zero-Allocation, Zero-Leak Design
222
316
 
223
- Messages are `readonly struct` types passed by `ref` no boxing, no GC pressure.
317
+ **The problem with normal events:** Boxing allocations, GC spikes, memory leaks from forgotten unsubscribes.
318
+
319
+ #### DxMessaging solution
224
320
 
225
321
  ```csharp
226
- void OnDamage(ref TookDamage msg) { // No allocations!
227
- health -= msg.amount;
322
+ void OnDamage(ref TookDamage msg) { // Pass by ref = zero allocations
323
+ health -= msg.amount; // No boxing, no GC pressure
228
324
  }
325
+ // Automatic cleanup = zero leaks, guaranteed
229
326
  ```
230
327
 
231
- ### 🎯 Three Message Types That Make Sense
328
+ **Real-world impact:** A game emitting 1000 messages/second uses **zero GC** with DxMessaging vs. 40KB/sec with boxed events.
329
+
330
+ ### 🎯 Three Message Types That Actually Make Sense
331
+
332
+ Most event systems force you into one pattern. DxMessaging gives you the right tool for each job:
232
333
 
233
334
  ```csharp
234
- // Untargeted: Global events anyone can hear
235
- [DxUntargetedMessage] public struct GamePaused { }
335
+ // Untargeted: "Everyone, listen up!" (global announcements)
336
+ [DxUntargetedMessage]
337
+ public struct GamePaused { }
338
+ // ↳ Perfect for: settings, scene transitions, global state
236
339
 
237
- // Targeted: Commands to specific entities
238
- [DxTargetedMessage] public struct Heal { public int amount; }
340
+ // Targeted: "Hey Player, do this!" (commands to specific entities)
341
+ [DxTargetedMessage]
342
+ public struct Heal { public int amount; }
343
+ // ↳ Perfect for: UI actions, direct commands, player input
239
344
 
240
- // Broadcast: Events from a source that others observe
241
- [DxBroadcastMessage] public struct TookDamage { public int amount; }
345
+ // Broadcast: "I took damage!" (events others can observe)
346
+ [DxBroadcastMessage]
347
+ public struct TookDamage { public int amount; }
348
+ // ↳ Perfect for: achievements, analytics, UI updates from entities
242
349
  ```
243
350
 
351
+ **Why this matters:** You're not forcing everything through one generic "Event<T>" pattern. Each message type has clear semantics.
352
+
244
353
  ### 🔄 The Message Pipeline
245
354
 
246
355
  Every message flows through 3 stages with priority control:
@@ -255,46 +364,136 @@ flowchart LR
255
364
  style PP fill:#eef7ee,stroke:#52c41a
256
365
  ```
257
366
 
258
- ### 🎭 Listen to "All Targets" or "All Sources"
367
+ ### 🎭 Global Observers: Listen to EVERYTHING (Unique Feature!)
259
368
 
260
- Perfect for analytics, achievements, and debugging:
369
+ **The problem with normal events:** To track all player damage, enemy damage, and NPC damage, you need 3 separate event subscriptions.
370
+
371
+ **DxMessaging's superpower:** Subscribe ONCE to a message type, receive ALL instances with source information:
261
372
 
262
373
  ```csharp
263
- // Track ALL damage, regardless of source
374
+ // Track ALL damage from ANY source (players, enemies, NPCs, environment)
264
375
  _ = token.RegisterBroadcastWithoutSource<TookDamage>(
265
376
  (InstanceId source, TookDamage msg) => {
377
+ Debug.Log($"{source} took {msg.amount} damage!");
266
378
  Analytics.LogDamage(source, msg.amount);
379
+ CheckAchievements(source, msg.amount);
267
380
  }
268
381
  );
269
382
  ```
270
383
 
271
- ### 🛡️ Interceptors: Validate Before Execution
384
+ #### Real-world use cases
385
+
386
+ - **Achievement system:** Track all kills, deaths, damage across the entire game
387
+ - **Combat log:** "Player took 25 damage, Enemy3 took 50 damage, Boss took 100 damage"
388
+ - **Analytics:** Aggregate stats from all entities without knowing about them upfront
389
+ - **Debug tools:** Watch ALL messages in the Inspector without instrumenting code
390
+
391
+ **Why this is revolutionary:** Traditional event buses require you to know entity types upfront. DxMessaging lets you observe dynamically.
392
+
393
+ ### 🛡️ Interceptors: Validate Before Execution (Safety Built In)
394
+
395
+ **The problem with normal events:** Validation logic duplicated in every handler, or bugs when you forget.
396
+
397
+ **DxMessaging solution:** Validate ONCE before ANY handler runs:
272
398
 
273
399
  ```csharp
274
- // Clamp damage before any handler sees it
400
+ // ONE interceptor protects ALL handlers
275
401
  _ = token.RegisterBroadcastInterceptor<TookDamage>(
276
402
  (ref InstanceId src, ref TookDamage msg) => {
277
- if (msg.amount <= 0) return false; // Cancel
278
- msg = new TookDamage(Math.Min(msg.amount, 999)); // Clamp
403
+ if (msg.amount <= 0) return false; // Cancel invalid
404
+ if (msg.amount > 999) {
405
+ msg = new TookDamage(999); // Clamp excessive
406
+ }
407
+ if (IsGodModeActive(src)) return false; // Block damage
279
408
  return true;
280
- }
409
+ },
410
+ priority: -100 // Run FIRST
281
411
  );
412
+
413
+ // Now ALL handlers receive clean, validated data
414
+ _ = token.RegisterComponentTargeted<TookDamage>(player, OnDamage);
415
+ void OnDamage(ref TookDamage msg) {
416
+ // No validation needed - interceptor guarantees validity!
417
+ health -= msg.amount;
418
+ }
282
419
  ```
283
420
 
284
- ### 🔍 Built-in Inspector Diagnostics
421
+ #### Real-world use cases
422
+
423
+ - Clamp/normalize values (damage, healing, speeds)
424
+ - Enforce game rules ("can't heal above max health")
425
+ - Block messages during cutscenes
426
+ - Log/audit sensitive actions
285
427
 
286
- See message history, handler counts, and registrations live in the Unity Inspector. For screenshots and details, see Unity Integration → Diagnostics.
428
+ ### 🔍 Built-in Inspector Diagnostics (Actually Debuggable!)
429
+
430
+ **The problem with normal events:** "Which event fired? When? Who handled it? In what order?" = 🤷
431
+
432
+ **DxMessaging solution:** Click any `MessageAwareComponent` in the Inspector:
433
+
434
+ ```text
435
+ ┌─────────────────────────────────────────────────────┐
436
+ │ Message History (last 50) │
437
+ ├─────────────────────────────────────────────────────┤
438
+ │ [12:34:56.123] HealthChanged │
439
+ │ → amount: 25 │
440
+ │ → priority: 0 │
441
+ │ → handlers: 3 │
442
+ │ │
443
+ │ [12:34:55.987] ItemAdded │
444
+ │ → itemId: 42, count: 1 │
445
+ │ → priority: 5 │
446
+ │ → handlers: 2 │
447
+ ├─────────────────────────────────────────────────────┤
448
+ │ Active Registrations │
449
+ ├─────────────────────────────────────────────────────┤
450
+ │ ✓ HealthChanged (priority: 0, called: 847 times) │
451
+ │ ✓ ItemAdded (priority: 5, called: 23 times) │
452
+ │ ✓ TookDamage (priority: 10, called: 1,203 times) │
453
+ └─────────────────────────────────────────────────────┘
454
+ ```
287
455
 
288
- ### 🏝️ Local Bus Islands for Testing
456
+ #### Real-world debugging scenarios
289
457
 
290
- Isolate subsystems with their own message buses:
458
+ - "Did my message fire?" Check history, see timestamp
459
+ - "Why didn't my handler run?" → Check registrations, see if it's active
460
+ - "What's firing too often?" → Sort by call count
461
+ - "What's the execution order?" → Sort by priority
462
+
463
+ **No more:** Setting 50 breakpoints and stepping through code for 30 minutes.
464
+
465
+ ### 🏝️ Local Bus Islands for Testing (Actually Testable!)
466
+
467
+ **The problem with normal events:** Global static events contaminate tests. Mock hell. Flaky tests.
468
+
469
+ **DxMessaging solution:** Each test gets its own isolated message bus:
291
470
 
292
471
  ```csharp
293
- var testBus = new MessageBus();
294
- var token = MessageRegistrationToken.Create(handler, testBus);
295
- // Messages here don't affect the global bus!
472
+ [Test]
473
+ public void TestAchievementSystem() {
474
+ // Create isolated bus - zero global state
475
+ var testBus = new MessageBus();
476
+ var handler = new MessageHandler(new InstanceId(1)) { active = true };
477
+ var token = MessageRegistrationToken.Create(handler, testBus);
478
+
479
+ // Test in isolation
480
+ _ = token.RegisterBroadcastWithoutSource<EnemyKilled>(achievements.OnKill);
481
+
482
+ var msg = new EnemyKilled("Boss");
483
+ msg.EmitGameObjectBroadcast(enemy, testBus); // Only this test sees it
484
+
485
+ Assert.IsTrue(achievements.Unlocked("BossSlayer"));
486
+ }
487
+ // Bus destroyed, zero cleanup needed
296
488
  ```
297
489
 
490
+ #### Why this matters
491
+
492
+ - Tests don't interfere with each other
493
+ - No "arrange/act/cleanup" boilerplate
494
+ - No mocking frameworks needed
495
+ - Parallel test execution works perfectly
496
+
298
497
  ## Documentation
299
498
 
300
499
  ### 🎓 Learn