# Design & Architecture: Under the Hood This document explains DxMessaging's internal design, performance optimizations, and architectural decisions. Read this to understand how and why DxMessaging works the way it does. ## Table of Contents - [Core Design Principles](#core-design-principles) - [Architecture Overview](#architecture-overview) - [Performance Optimizations](#performance-optimizations) - [Message Type System](#message-type-system) - [Registration and Lifecycle](#registration-and-lifecycle) - [The Message Bus](#the-message-bus) - [Why DxMessaging is Fast](#why-dxmessaging-is-fast) - [Design Decisions and Tradeoffs](#design-decisions-and-tradeoffs) ## Core Design Principles DxMessaging was built with these principles: 1. Zero-Allocation Communication - Messages are `readonly struct` types passed by `ref`. - No boxing, no temporary objects, minimal GC pressure. - Handlers receive `ref` parameters for struct messages. 1. Type-Safe by Default - Compile-time guarantees via generic constraints. - No string-based dispatch (unlike Unity's `SendMessage`). - Source generators provide boilerplate-free message definitions. 1. Predictable Execution - Priority-based handler ordering (lower priority runs first). - Three-stage pipeline: Interceptors > Handlers > Post-Processors. - Deterministic behavior within each priority level. 1. Observable & Debuggable - Built-in diagnostics via `CyclicBuffer`. - Registration logging with `RegistrationLog`. - Inspector integration for runtime visibility. 1. Lifecycle Safety - `MessageRegistrationToken` manages enable/disable. - Automatic cleanup prevents memory leaks. - Unity lifecycle integration via `MessageAwareComponent`. 1. Decoupled by Nature - Three semantic categories: Untargeted, Targeted, Broadcast. - No direct references between producers and consumers. - Context-aware (who sent, who received) without tight coupling. ## Architecture Overview ```mermaid flowchart TB subgraph App["Application Layer"] CompA[Component A
Token.Reg] CompB[Component B
Token.Reg] CompC[Component C
Token.Reg] end subgraph Token["Registration Layer"] MRT[MessageRegistrationToken
- Stages registrations
- Enable/Disable
- Lifecycle management] end subgraph Handler["Handler Layer"] MH[MessageHandler
- Per-component handler
- Active/Inactive state] end subgraph Bus["Message Bus Layer"] MB[MessageBus
- Interceptors
- Handlers
- Post-Processors] end CompA ==> MRT CompB ==> MRT CompC ==> MRT MRT ==> MH MH ==> MB classDef primary stroke-width:3px class App,CompA,CompB,CompC primary classDef warning stroke-width:3px class Token,MRT warning classDef accent stroke-width:3px class Handler,MH accent classDef success stroke-width:3px class Bus,MB success ``` ### Layer Responsibilities 1. **Application Layer** - Your Unity components register message handlers 1. **Registration Layer** - Token manages handler lifecycle (enable/disable/cleanup) 1. **Handler Layer** - Per-component state management (active/inactive) 1. **Message Bus Layer** - Routes messages through interceptors > handlers > post-processors ## Performance Optimizations - Struct messages passed by `ref` to avoid copying and GC. - Minimal allocations in hot paths; logging and diagnostics use ring buffers. - Pre-allocated internal collections for common operations. - Handlers are sorted by priority once during registration. Emitting a message iterates through all active handlers in that order. ## Message Type System - Untargeted: broadcast-like notifications without an explicit receiver. - Targeted: deliver to a specific target (e.g., GameObject, `InstanceId`). - Broadcast: deliver to all listeners (optionally capturing the source). Attributes like `[DxTargetedMessage]` and `[DxBroadcastMessage]` (with source generators) provide strong typing with minimal boilerplate. ## Registration and Lifecycle - `MessageRegistrationToken` groups per-component registrations. - Enable/disable toggles all component handlers together. - Disposal cleans up handlers automatically, preventing leaks. - `MessageAwareComponent` wires Unity lifecycles to tokens for safety. ### Memory Reclamation Subsystem A separate memory-reclamation subsystem bounds the empty per-message-type and per-context slots a long-running bus retains, plus the shared collection pools `DxPools` and the bus-owned context dictionary use. Idle sweeps run on a wall-clock cadence and through a Unity PlayerLoop hook; `IMessageBus.Trim` exposes the same sweep synchronously. Active registrations are never reclaimed. See the [Memory Reclamation guide](../guides/memory-reclamation.md) and [Runtime Settings reference](../reference/runtime-settings.md) for the full policy, tuning recommendations, and diagnostic counters. ## The Message Bus Message flow: Interceptors > Handlers > Post-Processors. - Interceptors may transform or cancel messages before delivery. - Handlers execute in priority order; lower number executes first. - Post-processors observe outcomes and can emit follow-up messages. ## Why DxMessaging is Fast - No reflection for dispatch; compile-time generics and static typing. - No string dispatch or dynamic lookup. - Ref-based delivery avoids copies and allocations. - Tight internal data structures tuned for Unity hot loops. ## Design Decisions and Tradeoffs - Priorities are numeric for clarity and control; predictable ordering beats implicit timing. - Strong typing over dynamic flexibility; safer refactoring and IDE support. - Diagnostics are opt-in and lightweight to keep runtime overhead minimal. See also: - [Interceptors and Ordering](../concepts/interceptors-and-ordering.md) - [Diagnostics](../guides/diagnostics.md) - [Message Types](../concepts/message-types.md)