# DxMessaging Visual Guide for Beginners
If you're brand new to messaging systems, this visual guide will help you understand DxMessaging in minutes.
## What Problem Does It Solve
### The Old Way (Spaghetti Code)
```mermaid
flowchart LR
Player[Player]
Enemy[Enemy]
Inventory[Inventory]
UI[UI]
Audio[Audio]
Player -->|direct ref| UI
Player -->|direct ref| Audio
Enemy -->|direct ref| UI
Enemy -->|direct ref| Audio
Inventory -->|direct ref| Audio
classDef danger stroke-width:2px
class Player,Enemy,Inventory danger
classDef success stroke-width:2px
class UI,Audio success
```
#### Problems
- Everyone needs to know everyone else
- Hard to add/remove systems
- Memory leaks from forgotten unsubscribes
### The DxMessaging Way (Clean Separation)
```mermaid
flowchart TB
Player[Player]
Enemy[Enemy]
Inventory[Inventory]
Bus((Message
Bus))
UI[UI]
Audio[Audio]
Analytics[Analytics]
Player -->|message| Bus
Enemy -->|message| Bus
Inventory -->|message| Bus
Bus -->|notify| UI
Bus -->|notify| Audio
Bus -->|notify| Analytics
classDef primary stroke-width:2px
class Player,Enemy,Inventory primary
classDef warning stroke-width:3px
class Bus warning
classDef success stroke-width:2px
class UI,Audio,Analytics success
```
#### Benefits
- Nobody knows about anyone else
- Easy to add/remove systems
- Automatic cleanup (prevents common leaks)
## The Three Message Types (Simple!)
Think of messages like different kinds of mail:
### 1. Untargeted (Announcement to Everyone)
Like a megaphone announcement in a stadium - everyone hears it.
```csharp
// Define the announcement
[DxUntargetedMessage]
[DxAutoConstructor]
public readonly partial struct GamePaused { }
// Anyone can announce
var msg = new GamePaused();
msg.Emit();
// Anyone can listen
_ = token.RegisterUntargeted(OnPause);
```
#### Real-world uses
- "Game paused!"
- "Settings changed!"
- "Level loaded!"
### 2. Targeted (Letter to One Person)
Like mailing a letter to a specific address - only that recipient gets it.
```csharp
// Define the letter
[DxTargetedMessage]
[DxAutoConstructor]
public readonly partial struct Heal { public readonly int amount; }
// Send to specific person
var heal = new Heal(50);
heal.EmitGameObjectTargeted(playerObject);
// Only the player listens
_ = token.RegisterComponentTargeted(this, OnHeal);
```
#### Real-world uses
- "Player, heal yourself!"
- "Enemy #3, take damage!"
- "Button, update your text!"
### 3. Broadcast (News from One Source)
Like a news broadcast - comes from one source, anyone can tune in.
```csharp
// Define the news
[DxBroadcastMessage]
[DxAutoConstructor]
public readonly partial struct TookDamage { public readonly int amount; }
// Broadcast from enemy
var dmg = new TookDamage(25);
dmg.EmitGameObjectBroadcast(enemyObject);
// UI can listen to specific enemy
_ = token.RegisterGameObjectBroadcast(enemyObject, OnThisEnemy);
// OR achievement system can listen to ALL enemies
_ = token.RegisterBroadcastWithoutSource(OnAnyEnemy);
```
#### Real-world uses
- "I (player) took damage!"
- "I (enemy) died!"
- "I (chest) was opened!"
## The Message Journey (Step by Step)
When you send a message, here's what happens:
```mermaid
sequenceDiagram
participant You as Your Code
participant Msg as Message
participant Int as Interceptors
(Optional)
participant H0 as Handler
priority: 0
participant H5 as Handler
priority: 5
participant H10 as Handler
priority: 10
participant PP as Post-Processors
(Optional)
Note over You: 1. Create message
You->>Msg: var heal = new Heal(10);
Note over You,Msg: 2. Emit message
You->>Msg: heal.EmitGameObjectTargeted(player);
Note over Int: 3. Validate & Normalize
Msg->>Int: Check message
Int->>Int: Is valid? (>0)
Clamp if needed (<999)
alt Invalid message
Int--xMsg: Cancel
else Valid message
Int->>H0: Continue
end
Note over H0,H10: 4. Execute handlers (by priority)
H0->>H0: SaveSystem runs first
H0->>H5: Next priority
H5->>H5: AudioSystem runs
H5->>H10: Next priority
H10->>H10: UISystem runs last
Note over PP: 5. Analytics & Logging
H10->>PP: All handlers complete
PP->>PP: Analytics.Track(...)
Debug.Log(...)
```
### Key points
- **Step 1-2:** You create and emit the message
- **Step 3 (Optional):** Interceptors can validate, modify, or cancel
- **Step 4:** Handlers run in priority order (lower number = earlier)
- **Step 5 (Optional):** Post-processors run after everything (suitable for analytics)
## Your First Message (3 Easy Steps)
### Step 1: Define It
```csharp
using DxMessaging.Core.Attributes;
[DxTargetedMessage] // <- What kind of message?
[DxAutoConstructor] // <- Auto-make a constructor
public readonly partial struct Heal {
public readonly int amount;
}
```
#### What are those `[DxSomething]` tags?
These are **attributes** that work with C# source generators to produce boilerplate code at compile time:
- **`[DxTargetedMessage]`** - Marks this struct as a targeted message and generates the required emit methods
- **`[DxAutoConstructor]`** - Generates a constructor that initializes all fields
For example, `[DxAutoConstructor]` generates this constructor automatically:
```csharp
public Heal(int amount) { this.amount = amount; }
```
**Why `partial`?** The `partial` keyword allows the source generator to add the generated code to your type in a separate file during compilation.
**Want to learn more?** See [Helpers & Source Generation](../reference/helpers.md) for the full explanation!
### Step 2: Listen for It
```csharp
using DxMessaging.Unity;
public class Player : MessageAwareComponent {
protected override void RegisterMessageHandlers() {
base.RegisterMessageHandlers();
// "When someone sends Heal to ME, call OnHeal"
_ = Token.RegisterComponentTargeted(this, OnHeal);
}
void OnHeal(ref Heal msg) {
health += msg.amount;
Debug.Log($"Healed {msg.amount}!");
}
}
```
**Automatic:** `MessageAwareComponent` handles all the lifecycle automatically.
- Creates registration in `Awake()`
- Activates in `OnEnable()`
- Deactivates in `OnDisable()`
- Cleans up in `OnDestroy()`
> **Important**: If you override `Awake`, `OnEnable`, `OnDisable`, `OnDestroy`, or `RegisterMessageHandlers` on a `MessageAwareComponent`, call `base.X()` first or your handlers stop working silently. See the [analyzer reference](../reference/analyzers.md#dxmsg006-missing-base-call).
### Step 3: Send It
```csharp
// From anywhere in your code:
var healMsg = new Heal(50);
healMsg.EmitComponentTargeted(playerComponent);
// Player will receive this message.
```
## Common Patterns Visualized
### Pattern: Scene Transition
```mermaid
sequenceDiagram
participant SM as SceneManager
participant Bus as Message Bus
participant Audio as AudioSystem
participant Save as SaveSystem
Note over SM: Scene is changing
SM->>Bus: SceneChanged(sceneIndex: 2)
Note over Bus: Message broadcast to all listeners
Bus->>Audio: SceneChanged received
Audio->>Audio: FadeOutMusic()
Bus->>Save: SceneChanged received
Save->>Save: SaveGame()
Note over Audio,Save: All independent,
no coupling
```
**Why this works:** AudioSystem and SaveSystem don't know about SceneManager or each other. They just listen for `SceneChanged` messages and react independently.
Code:
```csharp
// Define
[DxUntargetedMessage]
[DxAutoConstructor]
public readonly partial struct SceneChanged { public readonly int sceneIndex; }
// Anyone can send
var msg = new SceneChanged(2);
msg.Emit();
// Many can listen independently
_ = audioToken.RegisterUntargeted(OnScene);
_ = saveToken.RegisterUntargeted(OnScene);
```
### Pattern: Player Input -> Action
```mermaid
sequenceDiagram
participant Input as InputSystem
participant Bus as Message Bus
participant Player as Player
Note over Input: User presses Space
Input->>Bus: Jump(force: 10f)
[Targeted to Player]
Bus->>Player: Jump message received
Player->>Player: ApplyForce()
rb.AddForce(...)
Note over Input,Player: Decoupled!
Input doesn't reference Player
```
**Why this works:** InputSystem doesn't need a reference to Player. It just sends a `Jump` message targeted at the player, and the player responds.
Code:
```csharp
// Input system (doesn't know about Player!)
void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
var jump = new Jump(10f);
jump.EmitComponentTargeted(playerController);
}
}
// Player (doesn't know about Input system!)
_ = token.RegisterComponentTargeted(this, OnJump);
void OnJump(ref Jump msg) {
rb.AddForce(Vector3.up * msg.force, ForceMode.Impulse);
}
```
### Pattern: Achievement Tracking
```mermaid
sequenceDiagram
participant E as Enemy
participant P as Player
participant C as Chest
participant Bus as Message Bus
participant Ach as Achievement System
Note over Ach: Listens to ALL messages
E->>Bus: EnemyKilled
Bus->>Ach: CheckProgress()
UnlockIfReady()
P->>Bus: LevelCompleted
Bus->>Ach: CheckProgress()
UnlockIfReady()
C->>Bus: ChestOpened
Bus->>Ach: CheckProgress()
UnlockIfReady()
Note over Ach: Sees EVERYTHING
without coupling!
```
**Why this works:** Achievement System uses `RegisterGlobalAcceptAll()` to observe every message type, tracking progress across the entire game without any system knowing about it.
Code:
```csharp
public class AchievementSystem : MessageAwareComponent {
protected override void RegisterMessageHandlers() {
base.RegisterMessageHandlers();
// Listen to EVERYTHING
_ = Token.RegisterGlobalAcceptAll(
(ref IUntargetedMessage m) => Check(m),
(ref InstanceId t, ref ITargetedMessage m) => Check(m),
(ref InstanceId s, ref IBroadcastMessage m) => Check(m)
);
}
}
```
## When to Use Which Message Type
### Use Untargeted When
- Global game state changes (pause, settings, scene load)
- System-wide announcements
- Configuration updates
### Use Targeted When
- Commanding a specific object ("You, do this!")
- UI updates for specific elements
- Direct communication (A -> B)
### Use Broadcast When
- Events others should know about ("I did this!")
- Analytics tracking
- Achievement triggers
- Notifications from specific sources
## Mental Model: Restaurant Analogy
Think of DxMessaging like a restaurant:
### Untargeted = Restaurant Announcement
to "Attention all customers: We're closing in 10 minutes!"
> to -> Everyone hears it
### Targeted = Waiter Delivering Food
to "Order for table 5: Here's your burger"
> to -> Only table 5 gets it
### Broadcast = Customer Calling Waiter
to "Excuse me, I need a refill!" (from table 3)
> to -> Comes from table 3
>
> to -> Any available waiter can respond
>
> to -> Manager might track it for statistics
## Debugging Visualized
DxMessaging has built-in Inspector support!
### MessagingComponent Inspector
#### Message History (last 10)
- `12:34:05 - Heal -> Player (50)`
- `12:34:03 - Jump -> Player`
- `12:34:01 - GamePaused (global)`
##### Registrations
- Heal (priority: 0, 5 calls)
- Jump (priority: 0, 2 calls)
- TookDamage (priority: 10)
## Performance at a Glance
| Metric | Traditional C# Events | DxMessaging |
| ------------ | --------------------- | ----------------------------------- |
| **Speed** | (baseline) | (~10ns slower, negligible) |
| **Memory** | Can leak! | Automatic cleanup (struct messages) |
| **Coupling** | Tight coupling | Zero coupling |
**Bottom line:** Slightly slower than raw events, but:
- Prevents common memory leaks
- Zero coupling
- Full observability
- Predictable ordering
## Learning Path
```mermaid
flowchart TD
Start[START HERE
Read this Visual Guide
5 min]
Start --> Step2[Try Quick Start example
5 min
Define -> Listen -> Send]
Step2 --> Step3[Import Mini Combat sample
10 min
See it in action!]
Step3 --> Step4[Read Common Patterns
15 min
Real-world solutions]
Step4 --> Step5[Build your first feature!
30 min
You're ready!]
classDef primary stroke-width:3px
class Start primary
classDef success stroke-width:2px
class Step5 success
classDef secondary stroke-width:2px
class Step2,Step3,Step4 secondary
```
## Common Beginner Questions
### "Do I always need MessageAwareComponent?"
**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.
**For pure C#:** No, you can use `MessageRegistrationToken` directly if you're not in Unity.
**Bottom line:** If you're in Unity, use `MessageAwareComponent`. It handles subscription lifecycle automatically, which can reduce debugging related to memory leaks.
### "Can I send a message to multiple targets?"
**No** - Targeted messages go to ONE specific entity (like mailing a letter to one address).
#### Instead, use
- **Untargeted** if literally everyone should hear it (like a megaphone announcement)
- **Broadcast** if it's from one source and many can observe (like a news broadcast)
##### Example
```csharp
// DON'T: Try to target multiple entities
msg.EmitComponentTargeted(player1);
msg.EmitComponentTargeted(player2); // Feels wrong, right?
// DO: Use broadcast so everyone can listen
msg.EmitGameObjectBroadcast(enemy); // Now anyone can observe this enemy
```
### "What if I forget to unsubscribe?"
#### The system handles cleanup automatically
When your component is destroyed, DxMessaging cleans up registrations for you. No `OnDestroy()` needed. This reduces the likelihood of common memory leak patterns.
#### Old way (easy to forget)
```csharp
void OnEnable() { GameManager.OnScoreChanged += Update; }
void OnDisable() { GameManager.OnScoreChanged -= Update; } // Forgot this? LEAK!
```
##### DxMessaging way (automatic management)
```csharp
protected override void RegisterMessageHandlers() {
_ = Token.RegisterUntargeted(Update);
}
// Automatic cleanup when component is destroyed.
```
### "Is it slower than regular events?"
**Barely** (~10ns per handler = 0.00001 milliseconds).
#### Benchmark comparison
- Regular C# event: ~50ns
- DxMessaging: ~60ns
- Difference: ~10ns per invocation
You get automatic lifecycle, automatic cleanup, full observability, and predictable ordering for approximately 20% overhead compared to direct method calls.
### "Can I cancel a message?"
#### Yes! That's what interceptors are for
```csharp
// Cancel invalid damage
_ = token.RegisterBroadcastInterceptor(
(ref InstanceId source, ref TookDamage msg) => {
if (msg.amount <= 0) return false; // Cancel invalid damage
if (IsInvincible(source)) return false; // Cancel during invincibility
return true; // Allow
}
);
```
##### Real-world uses
- Block input during cutscenes
- Cancel damage when invincible
- Prevent cheating (clamp values)
- Enforce game rules globally
### "Can I see what messages are firing?"
#### Yes! Open any component in the Inspector and scroll down
You'll see:
- Message history (last 50 messages with timestamps)
- Active registrations (what you're listening to)
- Call counts (how many times each handler ran)
**No more guessing.** You can literally see your event flow in real-time.
## Quick Checklist: Am I Doing It Right
- [ ] Using `MessageAwareComponent` for Unity components?
- [ ] Defining messages as `readonly struct`?
- [ ] Using `[DxAutoConstructor]` to avoid boilerplate?
- [ ] Storing struct in variable before emitting?
- [ ] Choosing the right message type (Untargeted/Targeted/Broadcast)?
- [ ] Using GameObject/Component emit helpers?
If you checked all these, you are following best practices.
## Next Steps
Ready for more?
1. **[Mental Model](../concepts/mental-model.md)** - Understand the philosophy
1. **[Getting Started Guide](getting-started.md)** - Full guide with more details
1. **[Common Patterns](../guides/patterns.md)** - Real-world examples
1. **[Message Types](../concepts/message-types.md)** - When to pick Untargeted, Targeted, or Broadcast
1. **[Diagnostics](../guides/diagnostics.md)** - Master the Inspector tools
---
**Summary:** DxMessaging provides a structured approach to inter-component communication. You define the message, specify recipients, and the system handles delivery.