# WPILib Commands Version 3

[TOC]

## Problem Statement {#problem-statement}

The WPILib command framework uses cooperative multitasking to execute many commands concurrently;
each command has its own `execute()` function called periodically by the scheduler’s `run()` loop.
This API share has a steep learning curve, since new students learn looping by writing their own
`for` or `while` loops - not by using a framework that does the looping for them. Programmers
unfamiliar with the command framework commonly run into a problem where they write a while loop
inside their command’s `execute()` function, thus stalling the scheduler (and, by extension, the
entire robot program) by never ceding control until its end condition is met.

Version 3 of the commands framework proposes to fix this issue by leveraging a new API introduced in
Java 21: continuations. While they are currently an internal JDK API used to underpin virtual thread
runtimes, we can still access and use it. Continuations are used by the JDK to take and save a
snapshot of a mounted function’s call stack and registers, and can be remounted later to continue
from where it left off; a canceled command would simply never be remounted, and its continuation
left for the garbage collector to clean up.

## Goals

### Make command logic look like normal functions

The hardest part of learning the v1 and v2 command frameworks is the splitting out of logic into
separate init/execute/end/isFinished methods. Coroutines allow us to merge those all back into a
single function:

```java
void commandBody(Coroutine coroutine) {
  initialize();
  while (!isFinished()) {
    execute();
    coroutine.yield();
  }
  end();
}
```

Coroutines allow commands to be normal methods that yield control back to the scheduler mid-method;
the scheduler will issue a continuation to each command, and the JVM can unmount a continuation -
saving the stack and registers - which can be remounted later. **The primary goal of the new
framework is to allow for commands to be written as normal methods by taking advantage of this
mount/unmount feature.** Everything else is focused on quality of life improvements to the
framework.

### Forced Naming

The v2 commands framework makes naming opt-in; it is very easy to forget to set a name on a command
in a factory method, making telemetry less useful than it should be; seeing a
“SequentialCommandGroup” in logs isn’t very useful. The v3 framework will make command names
required in order for commands to be constructed. See [Telemetry](#telemetry) for details.

### Uncommanded Behavior

Command groups - or, more generally, commands that schedule other commands - need to own all
mechanisms needed by all their inner commands. However, if they don’t *directly* control a mechanism,
then they will still own the mechanism whenever they’re not running an inner command that uses it,
causing the mechanism to be in an *uncommanded state*. This can cause unexpected behavior with
parallel groups where inner commands don’t all end at the same time, and especially with sequential
groups. For example, something as seemingly basic as `elevator.toL4().andThen(coral.score())` would
*own* the elevator and coral mechanisms for the entire sequence, but not control the elevator during
the coral scoring section, nor control the coral scoring mechanism while the elevator is moving.

The v2 framework worked around this problem with so-called proxy commands, which have no
requirements of their own and simply schedule and await the *real* command. This removed the
requirement for their used subsystems from the parent group, and allowed for the subsystem’s default
command to run after the proxied command completed. It also meant that *every* command that required
that subsystem would also need to be proxied; otherwise, the group would still have ownership of the
subsystem, and the proxied command would interrupt it (and thus itself).

The v3 framework allows nested commands to use mechanisms not required by the parent command;
however, the built-in parallel and sequential groups will take full ownership of all mechanisms
required by their nested commands for safety and to keep behavior parity with previous command
frameworks.

```java
public Command outerCommand() {
  // The outer command only requires the outer mechanism
  return run(coroutine -> {
    // But can schedule a command that requires any other mechanism
    coroutine.await(innerMechanism.innerCommand());
    // And only requires that inner mechanism while its command runs
    coroutine.await(otherMechanism().otherCommand());
  }).named("Outer");
}
```

Scheduling a command that requires an inner mechanism will also cancel its parent, even if the parent
does not require that inner mechanism. Using the above example, directly scheduling a command that
requires `innerMechanism` will cancel a running `outerCommand` - but only if `outerCommand` is
*currently using* the inner mechanism at the time.

**Effectively, all child commands in v3 are "proxied"**, using the v2 framework's definition, unless
using the built-in ParallelGroup and Sequence compositions or explicitly adding child command
requirements to the parent. However, child commands _cannot_ interrupt their parent, even if they
share requirements, unlike proxy commands in v2.

### Priority Levels

Priority levels allow for finer-grained control over when commands should be interruptible than a
binary interrupt/ignore interrupt flag. This can be particularly helpful for LED control, where
teams use lights to indicate robot activity or error states with error indicators taking priority.

### Suspend/Resume

Allow commands to be temporarily paused while a higher priority or interrupting command runs, then
be automatically resumed after that higher priority command finishes. Note that resuming commands
must check for a condition to determine if they should still be running, since the time between
pause and resume can be arbitrarily long.

Suspending a command will suspend all its children, regardless of if those children have declared
themselves suspendable or not. Likewise, cancelling a non-suspendable command will also cancel all
its children, even if some of those children are suspendable.

Suspending a command that already has suspended children will still suspend everything; however,
upon resume, those suspended children will remain suspended.

### Inner Triggers

Teams often want to use triggers only within the scope of certain commands. For example, running an
autonomous mode that follows a particular path, and do certain actions when various points along the
path are reached, but not have those actions be bound to globally visible triggers that may fire at
other times during a match. In v2, such triggers need to be composed either a with a function that
checks if a valid autonomous mode is running or with the same trigger that can cause that routine to
run. This leads to many, many triggers being defined in robot programs and makes it difficult to
understand when all those triggers may be used.

The v3 framework will track the scopes in which trigger bindings are created, and automatically
delete those bindings (and cancel any running commands bound to them) when their scopes become
inactive. Users who manually schedule a command create a binding in a "global" scope that is always
active. Binding commands to a trigger, however, will use whatever the narrowest available scope is
at the time - creating a binding inside a running command will be scoped to that command's lifetime;
creating a binding outside a command (for example, in a main Robot class constructor) will also use
the "global" scope.

```java
void bindDriveButtons() {
  // Globally bound and will always be active
  controller.a().onTrue(...)
}

Trigger atScoringPosition = new Trigger(() -> getPosition().isNear(kScoringPosition));

Command autonomous() {
  return Command.noRequirements(coroutine -> {
    // This binding only exists while the autonomous command is running
    atScoringPosition.onTrue(score());

    coroutine.await(driveToScoringPosition());
  }).named("Autonomous");
}
```

### Improved Transparency

The v2 commands framework only runs top-level commands in the scheduler; commands nested within a
composition like a sequential group or a simple `withTimeout` decoration are hidden from the
scheduler and do not appear in its sendable implementation, making telemetry difficult. A
“SequentialCommandGroup” in logs is less useful than “Wait 10 seconds -> Drive Forward”. Command
groups will automatically include the names of all their constituent commands, with options to
override when desired.

The v3 framework will also provide a map of what command each mechanism is owned by. The v2 framework
only provides a list of the names of the running commands, without mapping those to subsystems. This
also makes telemetry difficult, since the order of commands in the list does not correspond with
subsystems; a command at a particular index may require different subsystems than a different
command at that same index that’s running at a later point in time, making data analysis in
AdvantageScope difficult since we can’t rely on consistent ordering.

Telemetry will send lists of the on-deck and running commands. Commands in those lists will appear
as an ID number, parent command ID (if any; top level commands have no parent), name, names of
the required mechanisms, priority level, last time to process, and total processing time. Separate
runs of the same command object have different ID numbers and processing time data. The total time
spent in the scheduler loop will also be included, but not the aggregate total of _all_ loops.

### Scoping

There are three possible scopes for commands and bindings. These scopes apply to triggers, default commands,
and manually scheduled commands.

1. The global scope, where a command is scheduled or trigger binding is created outside of any running opmode
2. An opmode scope, where the command is scheduled or trigger binding is created inside of an opmode
3. A command scope, where the command is scheduled or trigger binding is created inside of another command

A command scheduled in a certain scope, a trigger binding created in a certain scope, or a default command set in
a certain scope will only exist _within that scope_. Changing the default command for a mechanism within a custom opmode
will only be applied while that opmode is active; when the opmode exits, the default command specified in the robot
constructor (or mechanism constructor) will be reapplied. Triggers created in a scope will be unbound from the event
loop and be allowed to be garbage collected if no existing references exist.

```java
class Robot extends OpModeRobot {
  final ExampleMechanism mechanism = new ExampleMechanism();
  final CommandGamepad gamepad = new CommandGamepad(1);

  public Robot() {
    // The default command will be used whenever it's not overridden in an opmode or inside a command
    mechanism.setDefaultCommand(mechanism.run(...).named("Global Default Command"));
  }
}

class ExampleOpmode extends PeriodicOpMode {
  public ExampleOpMode(Robot robot) {
    // This overrides the default command set in the Robot constructor.
    // Once this opmode exits, the global default command will be used again.
    robot.mechanism.setDefaultCommand(robot.mechanism.run(...).named("Opmode Default Command"));

    // This trigger binding will be cleaned up when the opmode exits.
    // It will only be able to fire while this opmode is running.
    // If the opmode exits and then restarts, the binding will be created anew when the opmode is reinstantiated.
    robot.gamepad.leftBumper().onTrue(robot.mechanism.run(...).named("Bound Command"));

    // A manually scheduled command in an opmode will be canceled when the opmode exits if it hasn't already exited on its own
    Scheduler.getInstance().schedule(Command.noRequirements(coroutine -> {
      // Change the default command while this command is running.
      // It will be reset to the opmode-scoped default command when this command exits.
      // If no opmode-scoped default command exists, it will be reset to the global default command.
      robot.mechanism.setDefaultCommand(...);

      // This trigger binding will only be active while this command is running.
      // It will be unbound and garbage collected when this command exits.
      robot.gamepad.rightBumper().onTrue(...);
    }).named("Scoping Command"));
  }

  @Override
  public void periodic() {
    Scheduler.getDefault().run();
  }
}
```

## Non-Goals

### Preemptive Multitasking

It is not a goal of the v3 framework to offer preemptive multitasking (that is, having the scheduler
forcibly suspend a long-running command in order to run another queued one). This would require
support from the JDK for allowing custom virtual thread schedulers, which it does not currently
offer, and a custom thread scheduler would be complicated and difficult to maintain.

### Multithreading Support

Like the previous iterations of the command-based framework, v3 will be designed for a
single-threaded environment. All commands will be run by a single thread, which is expected to be
the same thread on which commands are scheduled and canceled via triggers. No guarantees are made
for stability or proper functioning if used in a multithreaded environment.

### Unbounded use of coroutines

Coroutines are intended to be used within the context of a running command or sideloaded periodic
function, and rely on their backing continuations to be mounted in order to run. Using a coroutine
outside its command makes no sense, and an error will be thrown if attempting to do so:

```java
Coroutine coroutine;
var badCommand = Command.noRequirements(co -> {
  coroutine = co;
}).named("Do not do this");

Scheduler.getInstance().schedule(badCommand);
Scheduler.getInstance().run();

// Doing anything with a captured coroutine will throw an error
co.fork(...); // IllegalStateException
co.yield(); // IllegalStateException
```

## Implementation Details

### Nomenclature

**Schedule**: Adding a command to a queue in the scheduler, requesting that that command start
running in the next call to the scheduler’s `run()` method.

**Mount**: Make a command active by resuming its stack and executing it on the scheduler’s thread.

**Unmount**: Freeze a command’s stack and state and remove it from the scheduler’s thread. An
unmounted command may be re-mounted in the future.

**Run**: Adding a command to the scheduler and have it begin executing. Every unfinished command
will be run by the scheduler until they reach a `Coroutine.yield()` call, at which point the command
is unmounted and the next command can begin.

**Cancel**: Request that a command stop running. A command that is scheduled and hasn’t yet started
running will be removed from the queue of scheduled commands. Cancellation requests are handled at
the end of each `run() `invocation. Cancelled commands are unmounted and will not be re-mounted;
they must be rescheduled and be issued new carrier coroutines.

**Interrupt**: Scheduling a command that requires one or more mechanisms already in use by a running
command will interrupt and cancel that running command, so long as the running command has an equal
or lower priority level. Higher-priority commands cannot be interrupted by lower-priority ones.

### Coroutines and Continuations

`Continuation` and `ContinuationScope` are JDK-internal classes that manage stack saving and
loading (“unmounting” and “mounting”, respectively). Mounting a continuation essentially means
placing the continuation’s saved stack on top of the current stack. Calling
`Continuation.yield(ContinuationScope)` pauses the code that the continuation is evaluating and
cedes control back to the caller, which can then unmount the continuation. The scheduler uses this
by looping over every running command, mounting the continuation for the command, running the
command - which eventually either returns or calls `coroutine.yield()` - then unmounts the
continuation and moves on to the next command in the list.

Coroutines are essentially wrappers around a continuation primitive that allows additional access to
the scheduler and provides async/await-like functionality. For example, `Coroutine.await(Command)`
will schedule a given command and call `yield()` in a loop until that command has completed
execution (note: this does *not* naively call `command.run(coroutine)`, which would hide the inner
command from the scheduler and potentially sidestep requirement mutexing)

### Command Function Bodies

Command logic lives in a single function `run` that accepts a `Coroutine` object argument. All
yielding is done via that coroutine, as is any management of nested commands (scheduling, awaiting,
cancelling, etc).

The coroutine’s `yield()` method always returns `true`. When a command is cancelled, it is removed
from the set of running commands and its carrier objects left to be garbage collected. An
`onCancel()` method on the cancelled command will be invoked to let the command do any necessary
cleanup; this is needed because the command body will not be re-mounted and run, so a separate
function is used. Command builders allow this to be specified with a `whenCancelled(Runnable)` step.

### Command Lifetimes

All commands are managed and executed by the scheduler. No commands should manually run their inner
commands (which is what the old sequential and parallel groups did), since that hides those inner
commands from the scheduler and telemetry. However, this is not prohibited, nor would there be a way
to detect it.

Inner commands are bound to the lifetime of their parents. Cancelling a higher-level command must
also cancel all the commands it scheduled (and so on down the hierarchy). Suspending a higher-level
command must likewise also suspend all inner commands, and resuming the higher-level command must
resume all suspended inner commands. Inner commands cannot be resumed before their parent command,
but may be suspended while the parent command still runs.

When a command is running, the scheduler tracks in a wrapper CommandState object that includes the
command, the command that scheduled it (if applicable), and the coroutine object carrying the
command.

Each command run has its own coroutine to back its execution. When a command finishes, its backing
continuation is done, and the command is removed from the scheduler. Any unfinished inner commands
are cancelled at this point.

### Factory Methods and Builders

In contrast with v2’s decorator API that returns a new command object for each modification (which
had some issues with naming and making telemetry difficult with deeply nested wrapper objects), v3
uses builder objects that allow configuration to be set before creating a single command at the end.
Builders are defined in stages, with a different type representing each stage, in order to leverage
the type system to prevent users from creating invalid commands and only discovering so at runtime.
Failure to apply required configuration options (eg the main command function or a name) will leave
users with an intermediate-stage builder that cannot be used to create a command, resulting in a
compilation error.

```java
// OK - requirements, body, and name are all provided
// Each builder method returns a different builder object that provides methods
// for progressing to the next stage.
Command command = Command.noRequirements(...).named("Name");

// Compilation error! Missing the command body
Command command = Command.noRequirements(...);
```

#### Forced Naming

Builder objects will only permit command creation when a name for the command has been provided.

#### Automatic Naming

Parallel and sequential groups and builders permit automatically generated names to be used instead
of manually specified ones.

Automatic sequence names will simply be the list of all the commands in the sequence, separated by
a “->” arrow sign; for example, “Step 1 -> Step 2 -> Step 3”

Automatic parallel group names will be the list of the names of the commands in the group, separated
by a pipe “|”. Required commands will be in their own group, separated from non-required commands.
If either subgroup is empty, then there will be no indication in the output (ie, no empty group
token like “()” will appear)

### Cooperative Multitasking

All commands *must* yield control back to the coroutine in their control loops. Because we cannot
preempt a running command at any arbitrary point, the command must explicitly and deliberately cede
control back to the scheduler in order for the framework to function. Commands may do this by
calling `coroutine.yield()` on the injected coroutine object, which ultimately delegates to
`Continuation.yield()` to pause the carrier continuation object.

The scheduler has a single `ContinuationScope` object. When a command is mounted, the scheduler will
create a new `Continuation` object bound to that scope, and a `Coroutine` object bound to the
continuation. `ContinuationScope` and `Continuation` are currently JDK-private classes; the build
needs to open the module to the wpilib/unnamed module for reflective access at compile and runtime.
Because access is entirely reflective, wrapper classes at the library level will be used to make
access easier.

### Scheduling

Scheduling a command with `Scheduler.schedule()` will add the command to a queue of pending
commands. Any command in the queue that conflicts and has a lower priority will be removed from the
queue without calling the `onCancel` hook. At the start of `Scheduler.run()`, all commands that
conflict with those in the queue are cancelled, then the queued commands are promoted to running.

Scheduling an inner command will bypass the queue and immediately begin to run. This avoids delays
caused by loop timings for deeply nested commands.

### Command scopes

Trigger bindings, default command settings, and manually scheduled commands all have a _scope_ in
which they are valid. When a scope exits, all bindings that were created in that scope are removed,
any mechanisms with default commands set during that scope have their default commands reset to the
default command set in the enclosing scope, and all commands scheduled in that scope are canceled.

Scopes are represented by `BindingScope`, and commands bound to a scope are tracked with a `Binding`
object. `BindingScope` is a sealed interface with three implementations: `Global` (the always-active
scope, eg `Robot` class initialization), `OpMode` (a scope while a particular opmode is running),
and `Command` (a scope for a specific running command).

### Scheduler `run()` Cycle

1. Cancel commands bound to inactive scopes
2. Cancel triggers bound to inactive scopes
3. Run periodic sideload functions
4. Poll the event loop for triggers (which may queue or cancel commands)
5. Schedule default commands for the next iteration to pick up and start running
6. Promote scheduled commands to running
    1. Cancels any running commands that conflict with scheduled ones
7. Iterate over running commands
    1. Mount
    2. Run until yield point is reached or an error is raised
    3. Unmount
    4. Evict if the command finished
        1. Any inner command still running is canceled

### Interruption

* Caused by scheduling a command that requires mechanisms already in use by one or more running
  commands
* Results in cancellation of the conflicting commands
    * Commands are moved to a pending-cancellation state
    * Then when `run()` is next called, they are removed from the scheduler
        * This occurs prior to the scheduled commands being promoted to the running state
    * The conflicting command’s entire tree (all ancestors and children) will be cancelled
* Siblings in a composition can interrupt each other
    * eg `fork(command1); fork(command2)`, where `command1` and `command2` share at least one
      common requirement, would cause `command1` to be interrupted by `command2`

### Telemetry

Scheduler state is serialized using protobuf. The scheduler will send a list of the currently queued
commands and a list of the current running commands. Commands are serialized as (id: uint32,
parent_id: uint32, name: string, priority: int32, requirements: string array,
last_time_ms: double, total_time_ms: double). Consumers can use the `id` and `parent_id` attributes
to reconstruct the tree structure, if desired. `id` and `parent_id` marginally increase the size of
serialized data, but make the schema and deserialization quite simple.

Command IDs are unique across all commands, including those that are not currently running. Every time
a command is scheduled, the scheduler will assign that run a unique ID. Note that this is stored as a
32-bit signed integer, so the maximum number of unique IDs is 2^31. No guarantee is made that IDs will
be unique across runs of the same program, nor to the order in which ID numbers are assigned; however,
IDs will be issued in a monotonically increasing fashion - a command scheduled before another will
always have a lower ID number.

Records in the serialized output will be ordered by scheduling order. As a result, child commands
will always appear _after_ their parent.

For example, if a scheduler is running a command like this:

```java
Mechansism m1, m2;

Command theCommand() {
  return ParallelGroup.all(
      m1.run(/* ... */).withPriority(1).named("Command 1"),
      m2.run(/* ... */).withPriority(2).named("Command 2")
  ).withAutomaticName();
}
```

Telemetry for commands in the scheduler would look like:

| `id`   | `parent_id` | `name`                    | `priority` | `requirements` | `last_time_ms` | `total_time_ms` |
|--------|-------------|---------------------------|------------|----------------|----------------|-----------------|
| 347123 | --          | "(Command 1 & Command 2)" | 2          | ["M1", "M2"]   | 0.210          | 5.122           |
| 998712 | 347123      | "Command 1"               | 1          | ["M1"]         | 0.051          | 1.241           |
| 591564 | 347123      | "Command 2"               | 2          | ["M2"]         | 0.108          | 3.249           |
