Messaging API Reference

A complete catalog of Onity’s typed pub/sub surface. The core (Onity.Messaging) is engine-free; the facade and reactive/DI bridges live in Onity.Unity.Messaging. A MessageChannel<T> uses the same SubscriptionEntry[] design as Subject<T>: steady-state Publish is designed to avoid per-call managed allocation, unsubscribing from inside a handler is safe (swap-back removal deferred until the publish pass ends), and Publish / Subscribe after Dispose() throw ObjectDisposedException.

using Onity.Messaging;

using MessageBroker broker = new MessageBroker();
IDisposable token = broker.Subscribe<PlayerDamaged>(d => { /* handle */ });
broker.Publish(new PlayerDamaged(10));
token.Dispose();

Threading: publish and subscribe on the Unity main thread. Channel creation in the broker is locked; per-channel publish is not internally locked. Initial delivery follows subscription order, but unsubscribe uses swap-back removal, so handler order is not a stable priority contract.

MessageBroker (and thus IPublisher<T> / ISubscriber<T> via GetPublisher / GetSubscriber) and OnityEventHub are auto-bound in every OnityContext, so a service can inject OnityEventHub or IMessageBroker with no installer line. BindMessageChannel<T>() is only needed to inject the typed IPublisher<T> / ISubscriber<T> directly.

Onity has no non-Unity third-party runtime dependencies; the messaging core does not use System.Linq.


Broker and typed channels (Onity.Messaging)

API Signature Notes
IMessageBroker.GetPublisher<TMessage> GetPublisher<TMessage>() -> IPublisher<TMessage> Returns the typed channel for the message type (created on first request).
IMessageBroker.GetSubscriber<TMessage> GetSubscriber<TMessage>() -> ISubscriber<TMessage> Same channel instance as the matching publisher.
IPublisher<TMessage>.Publish Publish(TMessage message) -> void Deliver message to all current subscribers. Initial insertion order is observable, but must not be used as a priority guarantee after removals.
ISubscriber<TMessage>.Subscribe Subscribe(MessageHandler<TMessage> handler) -> IDisposable Register handler. Dispose the token to unsubscribe. Null handler throws ArgumentNullException.
MessageHandler<TMessage> delegate void MessageHandler<TMessage>(TMessage message) The subscription callback signature.

Broker-level convenience (MessageBrokerExtensions)

Skip the explicit GetPublisher / GetSubscriber step.

API Signature Notes
Publish<TMessage> Publish<TMessage>(this IMessageBroker broker, TMessage message) -> void Resolves the publisher and publishes in one call. Null broker throws.
Subscribe<TMessage> Subscribe<TMessage>(this IMessageBroker broker, MessageHandler<TMessage> handler) -> IDisposable Resolves the subscriber and subscribes in one call. Null broker/handler throws.

MessageBroker (default IMessageBroker)

sealed class MessageBroker : IMessageBroker, IDisposable. Constructor: new MessageBroker().

API Signature Notes
ChannelCount int { get; } Number of created channels. Throws ObjectDisposedException after dispose.
GetDiagnostics GetDiagnostics(List<MessageChannelDiagnostics> results) -> void Fill a caller-supplied list (cleared first) with one entry per channel. Null list throws ArgumentNullException.
Dispose Dispose() -> void Dispose every owned channel and clear the map.

MessageChannel<TMessage>

sealed class MessageChannel<TMessage> : IPublisher<TMessage>, ISubscriber<TMessage>, IDisposable. The concrete channel type; you rarely construct it directly (the broker owns it), but it is public for tests and diagnostics.

API Signature Notes
Publish Publish(TMessage message) -> void See IPublisher<TMessage>.
Subscribe Subscribe(MessageHandler<TMessage> handler) -> IDisposable See ISubscriber<TMessage>.
SubscriberCount int { get; } Active subscriber count (diagnostics).
Dispose Dispose() -> void After dispose, Publish / Subscribe throw ObjectDisposedException.

MessageChannelDiagnostics

readonly struct returned in the GetDiagnostics list.

Member Type Description
MessageType Type The message payload type used as the channel key.
SubscriberCount int Active subscriber count for that channel.

Keyed channels — per-key routing (Onity.Messaging)

Route a published message only to subscribers registered for a matching key. Each key owns an inner MessageChannel<TMessage>.

API Signature Notes
IKeyedPublisher<TKey, TMessage>.Publish Publish(TKey key, TMessage message) -> void Deliver to subscribers of key only. A key with no subscribers is a no-op.
IKeyedSubscriber<TKey, TMessage>.Subscribe Subscribe(TKey key, MessageHandler<TMessage> handler) -> IDisposable Subscribe to one key. Null key/handler throws ArgumentNullException.
KeyedMessageChannel<TKey, TMessage> new KeyedMessageChannel<TKey, TMessage>() Concrete type implementing both interfaces and IDisposable.
KeyedMessageChannel.KeyCount int { get; } Number of keys that currently own a subscriber channel.
KeyedMessageChannel.GetSubscriberCount GetSubscriberCount(TKey key) -> int Subscriber count for one key (zero if unknown). Null key throws.
KeyedMessageChannel.Dispose Dispose() -> void Dispose every per-key channel.

Async channels — awaitable delivery (Onity.Messaging)

Sequential awaitable handlers: PublishAsync awaits each handler before invoking the next, iterating over a pooled snapshot so subscribe/unsubscribe from inside a handler cannot corrupt the in-flight pass.

API Signature Notes
IAsyncPublisher<TMessage>.PublishAsync PublishAsync(TMessage message, CancellationToken ct) -> ValueTask Deliver to all async subscribers sequentially; awaits delivery. Honors ct between handlers.
IAsyncSubscriber<TMessage>.Subscribe Subscribe(Func<TMessage, CancellationToken, ValueTask> handler) -> IDisposable Register an awaitable handler. Null handler throws.
AsyncMessageChannel<TMessage> new AsyncMessageChannel<TMessage>() Concrete type implementing both interfaces and IDisposable.
AsyncMessageChannel.SubscriberCount int { get; } Active subscriber count (diagnostics).
AsyncMessageChannel.Dispose Dispose() -> void After dispose, PublishAsync / Subscribe throw ObjectDisposedException.

Unity facade and bridges

Static Unity shortcut (Onity.Unity.OnityEvent)

Use this from MonoBehaviours and simple Unity code when constructor injection is unnecessary. The no-owner overloads resolve the active scene context first, then the project context. Owner overloads choose the nearest OnityContext to the component, which is useful for GameObjectContext scopes.

API Signature Notes
Publish<TMessage> Publish<TMessage>(TMessage message) -> void Publish through the default active context.
Publish<TMessage> Publish<TMessage>(Component owner, TMessage message) -> void Publish through the nearest context to owner.
Subscribe<TMessage> Subscribe<TMessage>(MessageHandler<TMessage> handler) -> IDisposable Subscribe through the default active context. Caller owns the token.
Subscribe<TMessage> Subscribe<TMessage>(Component owner, MessageHandler<TMessage> handler) -> IDisposable Subscribe through the nearest context to owner; token is also tied to owner destroy.
Observe<TMessage> Observe<TMessage>() -> IOnityObservable<TMessage> Observe from the default active context.
Observe<TMessage> Observe<TMessage>(Component owner) -> IOnityObservable<TMessage> Observe from the nearest context to owner.
GetEventHub / TryGetEventHub GetEventHub(...), TryGetEventHub(...) Resolve the underlying OnityEventHub when advanced access is needed.
using System;
using Onity.Unity;

OnityEvent.Publish(new PlayerDamaged(10));
IDisposable token = OnityEvent.Subscribe<PlayerDamaged>(message => { /* handle */ });

OnityEventHub (Onity.Unity.Messaging)

sealed class OnityEventHub. Auto-bound in every OnityContext over the scoped broker; inject it with no installer line. Constructor: new OnityEventHub(IMessageBroker broker).

API Signature Notes
Publish<TMessage> Publish<TMessage>(TMessage message) -> void Publish into the current scope (delegates to the broker).
Subscribe<TMessage> Subscribe<TMessage>(MessageHandler<TMessage> handler) -> IDisposable Subscribe in the current scope (delegates to the broker).
Observe<TMessage> Observe<TMessage>() -> IOnityObservable<TMessage> Observe the message type as a reactive stream. Caches one stream instance per message type.

Reactive bridge (OnityMessageReactiveExtensions)

Turn messages into the full reactive operator chain. The returned IOnityObservable<T> flows into Where / Select / Subscribe etc. (see Reactive Operators).

API Signature Notes
Observe<TMessage> (broker) Observe<TMessage>(this IMessageBroker broker) -> IOnityObservable<TMessage> Observe a broker channel as a stream. Null broker throws.
Observe<TMessage> (subscriber) Observe<TMessage>(this ISubscriber<TMessage> subscriber) -> IOnityObservable<TMessage> Observe a typed subscriber as a stream. Null subscriber throws.

DI binding helper (OnityMessageBindingExtensions)

API Signature Notes
BindMessageChannel<TMessage> BindMessageChannel<TMessage>(this OnityContainer container) -> void Bind IPublisher<TMessage> and ISubscriber<TMessage> instances (sourced from the scope’s IMessageBroker) so services can constructor-inject them directly.

Component extensions (OnityEventComponentExtensions)

These are optional aliases over the owner overloads above:

API Signature Notes
Publish<TMessage> Publish<TMessage>(this Component owner, TMessage message) -> void Same as OnityEvent.Publish(owner, message).
Subscribe<TMessage> Subscribe<TMessage>(this Component owner, MessageHandler<TMessage> handler) -> IDisposable Same as OnityEvent.Subscribe(owner, handler).
Observe<TMessage> Observe<TMessage>(this Component owner) -> IOnityObservable<TMessage> Same as OnityEvent.Observe<TMessage>(owner).
using Onity.Unity.Messaging;     // Observe<T>, OnityEventHub
using Onity.Reactive;            // Where, Select, Subscribe
using Onity.Unity.Reactive;      // AddTo

broker.Observe<DamageEvent>()
      .Where(e => e.Amount > 0)
      .Select(e => e.Amount)
      .Subscribe(amount => Debug.Log($"Took {amount}"))
      .AddTo(this);

Exceptions

Type Notes
OnityMessagingException sealed : Exception, in Onity.Messaging, for messaging-layer failures.
ObjectDisposedException Publish / Subscribe / PublishAsync after Dispose(). Tie subscriptions to lifetime with AddTo(this) / AddTo(bag).
ArgumentNullException Null broker, handler, or key passed to a broker/channel API.
OperationCanceledException The CancellationToken passed to PublishAsync cancelled delivery. Normal cancellation, not a bug.

Choosing a channel type

Use When
IPublisher<T> / ISubscriber<T> (or OnityEventHub) Standard transient, fire-and-forget notification with 0..N decoupled listeners. Late subscribers miss past messages by design (no replay).
IKeyedPublisher / IKeyedSubscriber The same message type but only listeners registered for a specific key should receive it.
IAsyncPublisher / IAsyncSubscriber The publisher must await delivery (sequential async handlers).
ReactiveProperty<T> (in DI) Current state a new listener must immediately know (health, score, connection status). Subscribing emits the current value first. This is the only “replay current value” primitive.
Direct service call (injected interface) A command/query with exactly one owner where you need a return value, ordering, or a synchronous result.

Intentionally not shipped (non-goals): buffered/replay events, handler priority, request-response. Model “the last message a late subscriber needs” as a ReactiveProperty<T>. See the Events & Messaging guide for the full decision rule and recipes.