Class ScyllaEventBus
Sealed, concrete implementation of IScyllaEventBus that forms the core of the Scylla Event eXchange System (SEX).
Implements
Inherited Members
Namespace: Scylla.Core.Events
Assembly: ScyllaCore.dll
Syntax
public sealed class ScyllaEventBus : IScyllaEventBus
Remarks
ScyllaEventBus manages a per-event-type list of ScyllaEventBus.SubscriberEntry records, sorted by ScyllaEventPriority (ascending numeric value) and then by subscription order for stable deterministic dispatch within the same priority level.
Thread safety: All mutations to and reads from the subscription table are guarded by an internal lock, making it safe to subscribe and unsubscribe from background threads. However, dispatch via Publish<TEvent>(TEvent) runs synchronously on the calling thread and invokes handler delegates while the lock is held; handlers should therefore not attempt to subscribe or unsubscribe on the same thread during dispatch to avoid deadlocks.
Exception isolation: When ExceptionIsolation is
true (the default), handler exceptions are caught, logged via
Log.Error, and dispatch continues with the next handler. When false,
the first handler exception propagates out of Publish<TEvent>(TEvent).
Lifecycle management: At each dispatch, subscriber entries whose owner object has been destroyed (Unity objects via Unity's special null semantics, plain C# objects via reference null check) are collected and removed from the subscription table, preventing stale callbacks and memory leaks.
Subscription leak detection: The bus logs a warning when a single event type accumulates more than SubscriberWarningThreshold subscribers, and throws InvalidOperationException at SubscriberLimit subscribers as a hard safety ceiling. Both thresholds are configurable via ScyllaEventConfiguration.
The global singleton instance is accessible via Bus. In tests or custom scenarios, replace it with SetBus(IScyllaEventBus).
Properties
ExceptionIsolation
Gets or sets whether handler exceptions during event dispatch are isolated (caught and
logged) rather than propagated. When true (the default), one faulty handler does
not prevent other handlers from executing. When false, the first handler exception
propagates out of Publish<TEvent>(TEvent).
Declaration
public bool ExceptionIsolation { get; set; }
Property Value
| Type | Description |
|---|---|
| bool | Defaults to DEFAULT_EXCEPTION_ISOLATION ( |
SubscriberLimit
Gets or sets the hard subscriber-count ceiling per event type. If the number of active subscriptions for a given event type reaches this value, Listen<TEvent>(object, Action<TEvent>, ScyllaEventPriority) logs an error and throws InvalidOperationException to prevent unbounded memory growth caused by a subscription leak.
Declaration
public int SubscriberLimit { get; set; }
Property Value
| Type | Description |
|---|---|
| int | Defaults to DEFAULT_SUBSCRIBER_LIMIT (1000). Set by ScyllaBootstrap from ScyllaEventConfiguration at startup. |
SubscriberWarningThreshold
Gets or sets the soft subscriber-count threshold per event type. When the number of active subscriptions for a given event type reaches this value, a warning is logged to indicate a potential subscription leak.
Declaration
public int SubscriberWarningThreshold { get; set; }
Property Value
| Type | Description |
|---|---|
| int | Defaults to DEFAULT_SUBSCRIBER_WARNING_THRESHOLD (100). Set by ScyllaBootstrap from ScyllaEventConfiguration at startup. |
Methods
Listen<TEvent>(object, Action<TEvent>, ScyllaEventPriority)
Registers handler to be called whenever an event of type
TEvent is published on this bus.
Declaration
public ScyllaEventSubscription Listen<TEvent>(object subscriber, Action<TEvent> handler, ScyllaEventPriority priority = ScyllaEventPriority.Medium) where TEvent : ScyllaEvent
Parameters
| Type | Name | Description |
|---|---|---|
| object | subscriber | The object that owns this subscription. The bus retains a strong reference to
this object and checks it during dispatch to determine whether the entry is
still valid. Unity objects are validated using Unity's destroy semantics. Must
not be |
| Action<TEvent> | handler | The callback to invoke when a matching event is dispatched. Must not be
|
| ScyllaEventPriority | priority | The ScyllaEventPriority level that controls when this handler fires relative to other handlers for the same event type. Defaults to Medium. |
Returns
| Type | Description |
|---|---|
| ScyllaEventSubscription | A ScyllaEventSubscription whose |
Type Parameters
| Name | Description |
|---|---|
| TEvent | The concrete event type to subscribe to. Must derive from ScyllaEvent. |
Remarks
A warning is logged when the subscriber count reaches
SubscriberWarningThreshold to help detect leaks before
the hard limit is hit. After a successful registration, the subscription list
for TEvent is marked dirty and will be re-sorted on the
next Publish<TEvent>(TEvent) call.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when |
| InvalidOperationException | Thrown when the current subscriber count for |
See Also
PruneInvalidSubscribers()
Removes every subscription whose subscriber is a destroyed Unity object (or a
plain null reference) across ALL event types, and drops event-type lists
that become empty.
Declaration
public int PruneInvalidSubscribers()
Returns
| Type | Description |
|---|---|
| int | The number of invalid subscriptions that were removed. |
Remarks
Destroyed subscribers are normally pruned lazily: Publish<TEvent>(TEvent) removes them the next time their event type is published. Event types that are never published again keep their stale entries indefinitely, since the bus holds strong references. This method performs the same cleanup eagerly across the whole bus; the scene management subsystem calls it during scene transitions so subscriptions from destroyed scene objects do not accumulate.
The sweep runs under the bus lock and costs O(total subscriptions). Handlers are not invoked; only bookkeeping is touched.
Publish<TEvent>(TEvent)
Dispatches event to all active subscribers registered for
TEvent, calling their handlers in ascending
ScyllaEventPriority order.
Declaration
public void Publish<TEvent>(TEvent @event) where TEvent : ScyllaEvent
Parameters
| Type | Name | Description |
|---|---|---|
| TEvent | event | The event instance to dispatch. If |
Type Parameters
| Name | Description |
|---|---|
| TEvent | The concrete event type to dispatch. Must derive from ScyllaEvent. |
Remarks
The dispatch algorithm:
-
Acquires the internal lock and looks up the subscriber list for
TEvent. - Filters out stale entries (destroyed Unity objects or null C# references) and removes them from the persistent subscription table.
- Sorts the working copy by priority and subscription ID if the dirty flag is set (i.e., new subscribers were added since the last dispatch).
-
Iterates the sorted list and invokes each handler. Handlers are skipped
when IsCancelled is
true, unless the handler's priority is Essential or Cleanup. -
When ExceptionIsolation is
true, any exception thrown by a handler is caught, logged, and dispatch continues with the remaining handlers. Whenfalse, the exception propagates immediately.
The lock is held for the entire duration of the dispatch. Handlers should not attempt to subscribe or unsubscribe synchronously during dispatch on the same thread.