Messaging API

Use this catalog to look up the exact shape of a messaging member: the broker and its typed channels, keyed and async channels, the OnityEvent facade, OnityEventHub, the reactive and DI bridges, and the OnityTask consumers. The Events and Messaging guide explains when to use what.

The core (Onity.Messaging) is engine-free. OnityEventHub, the reactive bridge and the DI binding helper live in Onity.Unity.Messaging, the static OnityEvent facade in Onity.Unity, and the OnityTask consumers in Onity.Unity.Async.

using System;
using Onity.Messaging;          // MessageBroker, Publish, Subscribe

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

MessageChannel<T>.Publish allocates nothing in steady state, a handler may unsubscribe from inside a publish pass, and Publish or Subscribe after Dispose() throw ObjectDisposedException. Publish and subscribe on the Unity main thread: the broker locks its channel table, never the publish path. Handlers run in subscription order until an unsubscribe outside a publish pass moves the last handler into the freed slot, so the order is not a priority contract.

MessageBroker (with IMessageBroker) and OnityEventHub are bound by every OnityContext, so a service injects either with no installer line. BindMessageChannel<T>() is needed only to inject IPublisher<T> or ISubscriber<T> directly.


Broker and typed channels

API Signature Namespace Notes
IMessageBroker.GetPublisher<TMessage> GetPublisher<TMessage>() -> IPublisher<TMessage> Onity.Messaging The channel for the message type, created on first request.
IMessageBroker.GetSubscriber<TMessage> GetSubscriber<TMessage>() -> ISubscriber<TMessage> Onity.Messaging The same channel instance as the publisher.
IPublisher<TMessage>.Publish Publish(TMessage message) -> void Onity.Messaging Delivers message to every current subscriber. A throwing handler propagates to this call; later handlers for that message do not run.
ISubscriber<TMessage>.Subscribe Subscribe(MessageHandler<TMessage> handler) -> IDisposable Onity.Messaging Registers handler; dispose the token to unsubscribe. A null handler throws ArgumentNullException.
MessageHandler<TMessage> delegate void MessageHandler<TMessage>(TMessage message) Onity.Messaging The handler signature.

Broker-level convenience

MessageBrokerExtensions skips the explicit GetPublisher / GetSubscriber step.

API Signature Namespace Notes
Publish<TMessage> Publish<TMessage>(this IMessageBroker broker, TMessage message) -> void Onity.Messaging GetPublisher<TMessage>().Publish(message). A null broker throws.
Subscribe<TMessage> Subscribe<TMessage>(this IMessageBroker broker, MessageHandler<TMessage> handler) -> IDisposable Onity.Messaging GetSubscriber<TMessage>().Subscribe(handler). A null broker or handler throws.

Message broker

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

API Signature Namespace Notes
ChannelCount int { get; } Onity.Messaging Number of created channels. Throws ObjectDisposedException after dispose.
GetDiagnostics GetDiagnostics(List<MessageChannelDiagnostics> results) -> void Onity.Messaging Clears results, then adds one entry per channel. A null list throws.
Dispose Dispose() -> void Onity.Messaging Disposes every owned channel; GetPublisher / GetSubscriber throw afterwards.

Message channel

sealed class MessageChannel<TMessage> : IPublisher<TMessage>, ISubscriber<TMessage>, IDisposable. The broker creates channels for you; construct one directly for a private channel, a test, or through DeclareMessage<T>().

API Signature Namespace Notes
Publish Publish(TMessage message) -> void Onity.Messaging See IPublisher<TMessage>.
Subscribe Subscribe(MessageHandler<TMessage> handler) -> IDisposable Onity.Messaging See ISubscriber<TMessage>.
SubscriberCount int { get; } Onity.Messaging Active subscriber count.
Dispose Dispose() -> void Onity.Messaging Afterwards Publish and Subscribe throw; disposing an old token is a no-op.

Channel diagnostics

MessageChannelDiagnostics (Onity.Messaging) is a readonly struct with Type MessageType (the channel key) and int SubscriberCount.


Keyed channels

sealed class KeyedMessageChannel<TKey, TMessage> : IKeyedPublisher<TKey, TMessage>, IKeyedSubscriber<TKey, TMessage>, IDisposable. Each key owns an inner MessageChannel<TMessage>, created by the first Subscribe for that key. The broker does not create keyed channels; construct one and bind it with BindInstance.

API Signature Namespace Notes
IKeyedPublisher<TKey, TMessage>.Publish Publish(TKey key, TMessage message) -> void Onity.Messaging Delivers to the subscribers of key; a key without subscribers is a no-op. A null key throws.
IKeyedSubscriber<TKey, TMessage>.Subscribe Subscribe(TKey key, MessageHandler<TMessage> handler) -> IDisposable Onity.Messaging Subscribes to one key. A null key or handler throws.
KeyCount int { get; } Onity.Messaging Keys that have a channel (a channel stays after its subscribers leave).
GetSubscriberCount GetSubscriberCount(TKey key) -> int Onity.Messaging Subscribers of one key; 0 for an unknown key. A null key throws.
Dispose Dispose() -> void Onity.Messaging Disposes every per-key channel; afterwards every member throws ObjectDisposedException.

Async channels

sealed class AsyncMessageChannel<TMessage> : IAsyncPublisher<TMessage>, IAsyncSubscriber<TMessage>, IDisposable. PublishAsync awaits each handler before calling the next and checks the token before each one. A pass delivers to the handlers registered when it started: a handler disposed during the pass still receives that message, and one added during the pass first receives the next. It never throws synchronously; a disposed channel, a canceled token or a throwing handler completes the returned ValueTask as an async method would. Handlers run inline until the first pending one; the rest of the pass runs in an awaiting continuation. A handler that is not itself an async method runs its synchronous work in the caller’s context, so an AsyncLocal<T> value or a SynchronizationContext it sets stays visible to the caller after PublishAsync returns; an async handler keeps its own scope, and the handlers inside the continuation leave the caller’s context unchanged.

API Signature Namespace Notes
IAsyncPublisher<TMessage>.PublishAsync PublishAsync(TMessage message, CancellationToken ct) -> ValueTask Onity.Messaging Sequential, awaited delivery to the handlers registered when the call starts. ct is checked before each handler; a canceled token, or a handler that throws OperationCanceledException, completes the task as canceled (OperationCanceledException on await); any other handler exception or a disposed channel completes it as faulted; nothing is thrown synchronously. Handlers run inline until the first pending one.
IAsyncSubscriber<TMessage>.Subscribe Subscribe(Func<TMessage, CancellationToken, ValueTask> handler) -> IDisposable Onity.Messaging Registers an awaitable handler. A null handler throws.
SubscriberCount int { get; } Onity.Messaging Active subscriber count.
Dispose Dispose() -> void Onity.Messaging Afterwards Subscribe throws ObjectDisposedException and PublishAsync returns a task faulted with it; a pass in flight finishes its own start set.
PublishOnityTask PublishOnityTask<TMessage>(this IAsyncPublisher<TMessage> publisher, TMessage message, CancellationToken cancellationToken) -> OnityTask Onity.Unity.Async PublishAsync as an OnityTask.
SubscribeOnityTask SubscribeOnityTask<TMessage>(this IAsyncSubscriber<TMessage> subscriber, Func<TMessage, OnityTask> handler) -> IDisposable Onity.Unity.Async Subscribes a handler that returns OnityTask; also with a Func<TMessage, CancellationToken, OnityTask> handler.

Native async consumption

OnityMessagingAsyncStreamExtensions (Onity.Unity.Async). Subscribe, publish and stop on the channel’s thread; tokens may be canceled from any thread. The calling assembly definition must reference Onity.Messaging, Onity.Reactive and Onity.Unity (and Onity.Core when it touches Unit).

API Signature Namespace Notes
ReceiveAsync ReceiveAsync<T>(this ISubscriber<T> subscriber, CancellationToken cancellationToken = default) -> OnityTask<T> Onity.Unity.Async The next message. Receives on one subscriber share one subscription and are pooled; a canceled receive gets no later message; a receive started on a disposed channel is canceled.
ReceiveAsync (predicate) ReceiveAsync<T>(this ISubscriber<T> subscriber, Func<T, bool> predicate, CancellationToken cancellationToken = default) -> OnityTask<T> Onity.Unity.Async The next matching message; a throwing predicate faults the task.
ReceiveAllAsync ReceiveAllAsync<T>(this ISubscriber<T> subscriber, int capacity, OnityBufferOverflow overflow = OnityBufferOverflow.Fault) -> IOnityAsyncEnumerable<T> Onity.Unity.Async Buffers up to capacity messages per enumeration in publish order. Fault stops accepting at the first overflow, yields the buffered messages, then faults with InvalidOperationException; DropOldest and DropNewest keep going. Cancellation and disposal discard the buffer; the stream never ends on its own.
SubscribeQueued SubscribeQueued<T>(this IAsyncSubscriber<T> subscriber, Func<T, CancellationToken, OnityTask> handler, int capacity, CancellationToken lifetimeToken) -> IDisposable Onity.Unity.Async A bounded queue between the channel and one consumer that runs handler per message in order. PublishAsync waits only while the queue is full. Stopping (dispose or lifetimeToken) unsubscribes, cancels the running handler, discards queued messages and releases waiting publishers without an exception. A handler exception is logged and the next message is handled.
OnityBufferOverflow enum { Fault, DropOldest, DropNewest } Onity.Unity.Async The full-buffer policy for ReceiveAllAsync.

Unity facade and bridges

OnityEvent

Static shorthand for Unity code (Onity.Unity). The no-owner members use the default context: the most recently activated SceneContext, then ProjectContext.Instance, then any other active context. The owner members use the context on owner or its nearest parent (inactive parents included) and fall back to the default context. When no context resolves, Publish, Subscribe, Observe and GetEventHub throw InvalidOperationException; a null owner throws ArgumentNullException.

API Signature Namespace Notes
Publish<TMessage> Publish<TMessage>(TMessage message) -> void Onity.Unity Through the default context.
Publish<TMessage> (owner) Publish<TMessage>(Component owner, TMessage message) -> void Onity.Unity Through the nearest context.
Subscribe<TMessage> Subscribe<TMessage>(MessageHandler<TMessage> handler) -> IDisposable Onity.Unity Through the default context; the caller owns the token.
Subscribe<TMessage> (owner) Subscribe<TMessage>(Component owner, MessageHandler<TMessage> handler) -> IDisposable Onity.Unity Through the nearest context; the token is also disposed when owner is destroyed.
Observe<TMessage> Observe<TMessage>() -> IOnityObservable<TMessage> Onity.Unity The hub’s cached stream for the type.
Observe<TMessage> (owner) Observe<TMessage>(Component owner) -> IOnityObservable<TMessage> Onity.Unity From the nearest context.
GetEventHub GetEventHub() -> OnityEventHub; GetEventHub(Component owner) -> OnityEventHub Onity.Unity The resolved hub.
TryGetEventHub TryGetEventHub(out OnityEventHub eventHub) -> bool; TryGetEventHub(Component owner, out OnityEventHub eventHub) -> bool Onity.Unity false instead of throwing (also for a null owner).

OnityEventHub

sealed class OnityEventHub; new OnityEventHub(IMessageBroker broker). Bound by every context over the scoped broker.

API Signature Namespace Notes
Publish<TMessage> Publish<TMessage>(TMessage message) -> void Onity.Unity.Messaging Delegates to the broker.
Subscribe<TMessage> Subscribe<TMessage>(MessageHandler<TMessage> handler) -> IDisposable Onity.Unity.Messaging Delegates to the broker.
Observe<TMessage> Observe<TMessage>() -> IOnityObservable<TMessage> Onity.Unity.Messaging The message type as a stream; one cached instance per type.

Reactive bridge

OnityMessageReactiveExtensions turns a channel into the operator chain; see Reactive Operators.

API Signature Namespace Notes
Observe<TMessage> (broker) Observe<TMessage>(this IMessageBroker broker) -> IOnityObservable<TMessage> Onity.Unity.Messaging GetSubscriber<TMessage>().Observe().
Observe<TMessage> (subscriber) Observe<TMessage>(this ISubscriber<TMessage> subscriber) -> IOnityObservable<TMessage> Onity.Unity.Messaging Each subscription to the stream is one channel subscription; observers run inside the channel’s handler.
using Onity.Reactive;           // Where, Select, Subscribe
using Onity.Unity.Messaging;    // Observe<T>
using Onity.Unity.Reactive;     // AddTo
using UnityEngine;              // Debug

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

DI bindings

API Signature Namespace Notes
BindMessageChannel<TMessage> BindMessageChannel<TMessage>(this OnityContainer container) -> void Onity.Unity.Messaging Binds IPublisher<TMessage> and ISubscriber<TMessage> to the scope broker’s channel, so OnityEvent and the hub reach the same subscribers. Resolves IMessageBroker at bind time: a context binds it before installers run; a bare container needs BindInterfacesAndSelfTo<MessageBroker>().AsSingle() first.
DeclareMessage<T> DeclareMessage<T>(this OnityContainer container) -> MessageChannel<T> Onity.Composition Creates a separate MessageChannel<T> and binds it as MessageChannel<T>, IPublisher<T> and ISubscriber<T>; the container disposes it. The broker, OnityEvent and OnityEventHub do not see this channel.
DeclareAsyncMessage<T> DeclareAsyncMessage<T>(this OnityContainer container) -> AsyncMessageChannel<T> Onity.Composition Creates an AsyncMessageChannel<T> and binds it as AsyncMessageChannel<T>, IAsyncPublisher<T> and IAsyncSubscriber<T>; the container disposes it.

Component extensions

OnityEventComponentExtensions (Onity.Unity.Messaging) aliases the owner overloads on any Component: owner.Publish(message), owner.Subscribe<TMessage>(handler), owner.Observe<TMessage>() and owner.GetEventHub().


Exceptions

Type Raised when
A handler’s own exception Propagates out of Publish (or faults PublishAsync); the remaining handlers for that message are skipped. Catch inside the handler when one listener must not break the others.
ObjectDisposedException Publish or Subscribe after Dispose(); PublishAsync after Dispose() returns a task faulted with it; any MessageBroker member after the broker was disposed.
ArgumentNullException A null handler, key, broker, list or owner component.
InvalidOperationException OnityEvent found no active context; a ReceiveAllAsync buffer overflowed with OnityBufferOverflow.Fault.
OperationCanceledException The token passed to PublishAsync, ReceiveAsync or ReceiveAllAsync was canceled. Normal cancellation.
OnityMessagingException Declared (sealed, in Onity.Messaging) for messaging-core failures; the shipped channels throw the standard exceptions above.

Choosing a channel type

Use When
OnityEvent, OnityEventHub, IPublisher<T> / ISubscriber<T> A transient notification with zero or more decoupled listeners. Late subscribers miss earlier messages; there is no replay.
KeyedMessageChannel<TKey, T> The same message type, delivered only to the listeners of one key.
AsyncMessageChannel<T> The publisher must await delivery. Add SubscribeQueued when a slow handler must not stall publishers.
ReceiveAsync / ReceiveAllAsync An OnityTask flow waits for one message, or consumes messages as a stream.
ReactiveProperty<T> (through DI) Current state a new listener must know at once; subscribing emits the current value first.
A direct service call A command or query with one owner, a return value, or a synchronous result.

Buffered or replayed messages, handler priority and request-response are not shipped. See the Events and Messaging guide for the decision rule and recipes, and Reactive Operators for the chain Observe<T>() feeds.