[commands] Add Commands v3 framework (#6518)

The framework fundamentally relies on the continuation API added in Java 21 (which is currently internal to the JDK). Continuations allow for call stacks to be saved to the heap and resumed later.

The async framework allows command bodies to be written in an imperative style. However, an async command will need to be actively cooperative and periodically call coroutine.yield() in loops to yield control back to the command scheduler to let it process other commands.

There are also some other additions like priority levels (as opposed to a blanket yes/no for ignoring incoming commands), factories requiring names be provided for commands, and the scheduler tracking all running commands and not just the highest-level groups. However, those changes aren't unique to an async framework, and could just as easily be used in a traditional command framework.
This commit is contained in:
Sam Carlberg
2025-10-10 16:47:22 -04:00
committed by GitHub
parent 33f91589b4
commit b37e2d9343
77 changed files with 12259 additions and 3 deletions

View File

@@ -0,0 +1,80 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A sequence of commands that run one after another. Each successive command only starts after its
* predecessor completes execution. The priority of a sequence is equal to the highest priority of
* any of its commands. If all commands in the sequence are able to run while the robot is disabled,
* then the sequence itself will be allowed to run while the robot is disabled.
*
* <p>The sequence will <i>own</i> all mechanisms required by all commands in the sequence for the
* entire duration of the sequence. This means that a mechanism owned by one command in the
* sequence, but not by a later one, will be <i>uncommanded</i> while that later command executes.
*/
public final class SequentialGroup implements Command {
private final String m_name;
private final List<Command> m_commands = new ArrayList<>();
private final Set<Mechanism> m_requirements = new HashSet<>();
private final int m_priority;
/**
* Creates a new command sequence.
*
* @param name the name of the sequence
* @param commands the commands to execute within the sequence
*/
SequentialGroup(String name, List<Command> commands) {
requireNonNullParam(name, "name", "SequentialGroup");
requireNonNullParam(commands, "commands", "SequentialGroup");
for (int i = 0; i < commands.size(); i++) {
requireNonNullParam(commands.get(i), "commands[" + i + "]", "SequentialGroup");
}
m_name = name;
m_commands.addAll(commands);
for (var command : commands) {
m_requirements.addAll(command.requirements());
}
m_priority =
commands.stream().mapToInt(Command::priority).max().orElse(Command.DEFAULT_PRIORITY);
}
@Override
public void run(Coroutine coroutine) {
for (var command : m_commands) {
coroutine.await(command);
}
}
@Override
public String name() {
return m_name;
}
@Override
public Set<Mechanism> requirements() {
return m_requirements;
}
@Override
public int priority() {
return m_priority;
}
@Override
public String toString() {
return "SequentialGroup[name=" + m_name + "]";
}
}