-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Design And Architecture
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.
- Core Design Principles
- Architecture Overview
- Performance Optimizations
- Message Type System
- Registration and Lifecycle
- The Message Bus
- Why DxMessaging is Fast
- Design Decisions and Tradeoffs
DxMessaging was built with these principles:
-
Zero-Allocation Communication
- Messages are
readonly structtypes passed byref. - No boxing, no temporary objects, minimal GC pressure.
- Handlers receive
refparameters for struct messages.
- Messages are
-
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.
-
Predictable Execution
- Priority-based handler ordering (lower priority runs first).
- Three-stage pipeline: Interceptors > Handlers > Post-Processors.
- Deterministic behavior within each priority level.
-
Observable & Debuggable
- Built-in diagnostics via
CyclicBuffer. - Registration logging with
RegistrationLog. - Inspector integration for runtime visibility.
- Built-in diagnostics via
-
Lifecycle Safety
-
MessageRegistrationTokenmanages enable/disable. - Automatic cleanup prevents memory leaks.
- Unity lifecycle integration via
MessageAwareComponent.
-
-
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.
flowchart TB
subgraph App["Application Layer"]
CompA[Component A<br/>Token.Reg]
CompB[Component B<br/>Token.Reg]
CompC[Component C<br/>Token.Reg]
end
subgraph Token["Registration Layer"]
MRT[MessageRegistrationToken<br/>- Stages registrations<br/>- Enable/Disable<br/>- Lifecycle management]
end
subgraph Handler["Handler Layer"]
MH[MessageHandler<br/>- Per-component handler<br/>- Active/Inactive state]
end
subgraph Bus["Message Bus Layer"]
MB[MessageBus<br/>- Interceptors<br/>- Handlers<br/>- 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
- Application Layer - Your Unity components register message handlers
- Registration Layer - Token manages handler lifecycle (enable/disable/cleanup)
- Handler Layer - Per-component state management (active/inactive)
- Message Bus Layer - Routes messages through interceptors > handlers > post-processors
- Struct messages passed by
refto 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.
- 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.
-
MessageRegistrationTokengroups per-component registrations. - Enable/disable toggles all component handlers together.
- Disposal cleans up handlers automatically, preventing leaks.
-
MessageAwareComponentwires Unity lifecycles to tokens for safety.
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
..-Guides-Memory-Reclamation and
..-Reference-Runtime-Settings for the full
policy, tuning recommendations, and diagnostic counters.
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.
- 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.
- 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:
- Getting-Started-Overview
- Getting-Started-Getting-Started
- Getting-Started-Install
- Getting-Started-Quick-Start
- Getting-Started-Visual-Guide
- Concepts-Message-Types
- Concepts-Listening-Patterns
- Concepts-Targeting-And-Context
- Concepts-Interceptors-And-Ordering
- Guides-Patterns
- Guides-Unity-Integration
- Guides-Testing
- Guides-Diagnostics
- Guides-Advanced
- Guides-Migration-Guide
- Advanced-Emit-Shorthands
- Advanced-Message-Bus-Providers
- Advanced-Runtime-Configuration
- Advanced-String-Messages
- Reference-Reference
- Reference-Quick-Reference
- Reference-Helpers
- Reference-Faq
- Reference-Glossary
- Reference-Troubleshooting
- Reference-Compatibility
Links