// 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.command2;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.wpilib.util.sendable.SendableBuilder;
/**
* A command composition that runs a set of commands in parallel, ending only when a specific
* command (the "deadline") ends, interrupting all other commands that are still running at that
* point.
*
*
The rules for command compositions apply: command instances that are passed to it cannot be
* added to any other composition or scheduled individually, and the composition requires all
* subsystems its components require.
*
*
This class is provided by the Commands v2 VendorDep
*/
public class ParallelDeadlineGroup extends Command {
// maps commands in this composition to whether they are still running
// LinkedHashMap guarantees we iterate over commands in the order they were added (Note that
// changing the value associated with a command does NOT change the order)
private final Map m_commands = new LinkedHashMap<>();
private boolean m_runWhenDisabled = true;
private boolean m_finished = true;
private Command m_deadline;
private InterruptionBehavior m_interruptBehavior = InterruptionBehavior.kCancelIncoming;
/**
* Creates a new ParallelDeadlineGroup. The given commands, including the deadline, will be
* executed simultaneously. The composition will finish when the deadline finishes, interrupting
* all other still-running commands. If the composition is interrupted, only the commands still
* running will be interrupted.
*
* @param deadline the command that determines when the composition ends
* @param otherCommands the other commands to be executed
* @throws IllegalArgumentException if the deadline command is also in the otherCommands argument
*/
@SuppressWarnings("this-escape")
public ParallelDeadlineGroup(Command deadline, Command... otherCommands) {
setDeadline(deadline);
addCommands(otherCommands);
}
/**
* Sets the deadline to the given command. The deadline is added to the group if it is not already
* contained.
*
* @param deadline the command that determines when the group ends
* @throws IllegalArgumentException if the deadline command is already in the composition
*/
public final void setDeadline(Command deadline) {
@SuppressWarnings("PMD.CompareObjectsWithEquals")
boolean isAlreadyDeadline = deadline == m_deadline;
if (isAlreadyDeadline) {
return;
}
if (m_commands.containsKey(deadline)) {
throw new IllegalArgumentException(
"The deadline command cannot also be in the other commands!");
}
addCommands(deadline);
m_deadline = deadline;
}
/**
* Adds the given commands to the group.
*
* @param commands Commands to add to the group.
*/
public final void addCommands(Command... commands) {
if (!m_finished) {
throw new IllegalStateException(
"Commands cannot be added to a composition while it's running");
}
CommandScheduler.getInstance().registerComposedCommands(commands);
for (Command command : commands) {
if (!Collections.disjoint(command.getRequirements(), getRequirements())) {
throw new IllegalArgumentException(
"Multiple commands in a parallel group cannot require the same subsystems");
}
m_commands.put(command, false);
addRequirements(command.getRequirements());
m_runWhenDisabled &= command.runsWhenDisabled();
if (command.getInterruptionBehavior() == InterruptionBehavior.kCancelSelf) {
m_interruptBehavior = InterruptionBehavior.kCancelSelf;
}
}
}
@Override
public final void initialize() {
for (Map.Entry commandRunning : m_commands.entrySet()) {
commandRunning.getKey().initialize();
commandRunning.setValue(true);
}
m_finished = false;
}
@Override
public final void execute() {
for (Map.Entry commandRunning : m_commands.entrySet()) {
if (!commandRunning.getValue()) {
continue;
}
commandRunning.getKey().execute();
if (commandRunning.getKey().isFinished()) {
commandRunning.getKey().end(false);
commandRunning.setValue(false);
if (commandRunning.getKey().equals(m_deadline)) {
m_finished = true;
}
}
}
}
@Override
public final void end(boolean interrupted) {
for (Map.Entry commandRunning : m_commands.entrySet()) {
if (commandRunning.getValue()) {
commandRunning.getKey().end(true);
}
}
}
@Override
public final boolean isFinished() {
return m_finished;
}
@Override
public boolean runsWhenDisabled() {
return m_runWhenDisabled;
}
@Override
public InterruptionBehavior getInterruptionBehavior() {
return m_interruptBehavior;
}
@Override
public void initSendable(SendableBuilder builder) {
super.initSendable(builder);
builder.addStringProperty("deadline", m_deadline::getName, null);
}
}