5 Action system
Vectornaut edited this page 2026-03-19 04:05:11 +00:00

We plan for all user actions to be mediated through action objects that can be described by commands; saved in an undo history; and triggered by buttons, menu items, and keyboard shortcuts. A prototype of an action system can be found on the Vectornaut:actions branch. Here are some design considerations.

Organizing execution code

Action trait

In the current prototype, an action is described by an implementation of the Action trait. An action has a value and an effect on the application state. The effect is carried out by the trait's execute method, which takes an application state reference as input. The value is determined at the time the action is constructed; it's revealed by the trait's peek method, and returned by execute. This organization helps streamline the construction of actions in two ways:

  • The execution code doesn't need to be written in the place where the action is constructed.
  • It's easy to build actions in tree form, with values passed up the tree and execution propagating down the tree.

Actions are single-use. We enforce this through the lifetime system by making execute consume the action. As an action implementer, you may assume that each instance of your action will be constructed in the context of a given application state and executed on the same state. Correspondingly, as an action user, you must leave no opportunity for the application state to change between constructing and executing an action.

Stored closures

Alternatively, an action could be represented by a structure that stores the execution code as a pointer to a closure, like a Box or an Rc. This would help us to define actions more flexibly, without having to create a special structure for each one.

Taking input parameters

It can be useful to think of a button, menu item, or keyboard shortcut as having an action that depends on a reactive input parameter. In the current prototype, where actions are single-use, it's natural to implement this idea using actors, which are designed to construct and execute actions on demand. A ParametricActor<T> structure contains:

  • A reactive input parameter, stored as a ReadSignal with a value of type Result<T, String>.
  • A mapping from parameter values to actions, stored as a boxed closure that maps T values to boxed Action trait objects.

The type parameter T lets us enforce type consistency between the input parameter and the action generator at compile time. Since the input parameter has type Result<T, String>, we still have room to produce the input parameter through runtime type conversion, setting the input parameter to Err if conversion fails.