docs
Systems
Messaging
SignalSystem
Architecture and Design

Design patterns in use

  • Publish-subscribe through a bus: Signals are raised and listened for through the bus. Signal is the message, and SignalKey is the routing key. Listeners subscribe to a key, and raising a signal delivers it to them. The routing and handler storage live in the BusSystem, not in this folder.
  • Value identity for keys: SignalKey is a readonly struct that implements IEquatable and compares by name. Two keys with the same name refer to the same signal, so keys can be used as dictionary and set keys.
  • Compile-time name construction: SignalKeyNames builds each signal name from const strings, so the names are fixed at compile time and can be shared as constants.
  • Shared catalog of static keys: SaffronLibrarySignals holds one static readonly key per signal. Systems refer to these fields rather than creating their own keys from strings.

Connections to other systems

  • BusSystem (SignalBus): SignalBus stores which signals have been raised and runs the listeners waiting for each one. SignalKey is the routing key for that bus.
  • BusSystem (BusUtility): Signals are raised with BusUtility.Raise by wrapping a Signal around a SignalKey.
  • BusSystem (MessageBus): The base bus can gate a routing key on a ready signal through SetMessageQueueReadySignal. Messages for that key are queued until the signal is raised, and then released in order.
  • ApplicationSystem (ServiceLocator): Buses are reached through ServiceLocator.GetBusRegistry(). Signal raising and waiting work only after the bus registry has been initialized during bootstrap.
  • BusSystem (BusRegistry): Holds the SignalBus instance that all of the above use.

Key concepts and terminology

  • Signal: A fact about the system that becomes true once and stays true, such as readiness. Signals are not repeated events.
  • SignalKey: The unique name of a signal. Keys are defined once as static fields and reused.
  • Raised: A signal is raised when its owner sends it. A signal can be raised once. Raising it again has no effect.
  • Late subscriber: A listener that waits for a signal that has already been raised. It is run immediately.
  • Ready gating: A bus routing key can require a ready signal before its messages are delivered. Messages sent earlier are queued and released once the signal is raised.

Event and data flow

// Owner raises a readiness signal once during startup.
BusUtility.Raise(new Signal(SaffronLibrarySignals.SceneReady));
 
// Dependent system waits for it. If the signal is already raised,
// the callback runs immediately instead.
ServiceLocator.GetBusRegistry().SignalBus.WhenRaised(
    SaffronLibrarySignals.SceneReady,
    () => BeginSceneWork());
  1. A bootstrap step initializes the bus registry through ServiceLocator.
  2. An owning system raises a Signal built from a SaffronLibrarySignals key.
  3. The signal bus records the key as raised, runs every listener waiting on it, and removes those listeners, so each runs once.
  4. A listener that subscribes after the raise runs at once.
  5. Messages for a gated routing key stay queued until its ready signal is raised, then are delivered in order.

Notes on the code

  • SignalKeyNames declares private segments Initialization and Complete. No public signal name uses them.
  • The SignalKey constructor does not check for null. A default SignalKey has a null Id.
  • The code contains no locking or synchronization. The collections used by the bus are plain Dictionary and HashSet instances, so the code assumes single-threaded use.