docs
Systems
Messaging
CommandSystem
Architecture and Design

CommandSystem treats commands as a form of routed message. Each Command carries a Domain indicating which system should handle it, and a verb (stored as an int) describing the specific action within that domain. The system does not enforce a verb enum; instead, each domain defines its own verb enum, and callers cast it to int when creating a command. This design avoids compile-time coupling between domains.

Routing and delivery:

Commands are sent via BusUtility.Send, which forwards them to the CommandBus. The bus routes each command to the flow controller registered for its Domain. That flow controller's HandleCommand method receives the command and decides how to process it-typically by looking up a registered handler using the verb or a parameter such as RegistrationKey, then delegating to that handler's ProcessCommand method. This two-level dispatch (bus-to-flow-controller, then flow-controller-to-handler) allows the flow controller to mediate between the global routing layer and domain-specific logic.

Parameters and type safety:

Commands carry their data as a Params bag-a dictionary mapping ParamKey<T> keys to typed values. Unlike raw dictionaries, the ParamKey<T> generic ensures that callers and receivers agree on the type of each parameter. A command creator calls command.Params.Set(key, value) to populate it; a receiver calls command.Params.Get(key) to retrieve values. If a value is missing or mistyped, the Params class throws a clear error rather than silently failing.

Verb as domain-specific behavior:

The verb is an opaque int. Each domain defines its own enum of verbs (e.g., UICommandVerb.Open, AudioCommandVerb.Play), and callers cast the enum value to int when creating a command. The receiving flow controller knows its domain's verbs and uses the verb to decide what to do. This indirection eliminates enum coupling across domains and allows each domain to define its verbs independently.

Connection to related systems:

  • The CommandBus inherits from MessageBus<ICommand, Domain>, using Domain as the routing key. It queues commands if their target domain is not yet ready, ensuring no command is lost during startup.
  • The flow controller registers its HandleCommand method on the command bus during Init, becoming the single handler for its domain.
  • Commands are created via the static Command.Create factory, often wrapped by a domain-specific builder or factory that hides the verb casting and parameter setup.
  • The system is fully synchronous: sending a command directly invokes the handler (or queues it if the handler is not ready), and control returns to the caller once the handler finishes.

Thread safety:

The command bus and message routing are not thread-safe. All command creation, sending, and handling must occur on the main thread, consistent with Unity's single-threaded event model.