Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions commandsv3/src/main/java/org/wpilib/command3/ParallelGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ public final class ParallelGroup implements Command {
* this is empty, then the group will finish when <i>any</i> optional command completes.
* @param optionalCommands The commands that do not need to complete for the group to finish. If
* this is empty, then the group will finish when <i>all</i> required commands complete.
* @param inheritRequirements Whether the group should inherit the requirements of the subcommands.
* @param additionalRequirements Any additional mechanism requirements. Additional requirements must be
* requirements of at least one subcommand.
*/
ParallelGroup(
String name, Collection<Command> requiredCommands, Collection<Command> optionalCommands) {
String name, Collection<Command> requiredCommands, Collection<Command> optionalCommands,
boolean inheritRequirements, Set<Mechanism> additionalRequirements) {
requireNonNullParam(name, "name", "ParallelGroup");
requireNonNullParam(requiredCommands, "requiredCommands", "ParallelGroup");
requireNonNullParam(optionalCommands, "optionalCommands", "ParallelGroup");
requireNonNullParam(additionalRequirements, "additionalRequirements", "ParallelGroup");

int i = 0;
for (Command requiredCommand : requiredCommands) {
Expand All @@ -51,6 +56,12 @@ public final class ParallelGroup implements Command {
i++;
}

i = 0;
for(Mechanism requirement : additionalRequirements) {
requireNonNullParam(requirement, "additionalRequirements[" + i + "]", "ParallelGroup");
i++;
}

var allCommands = new LinkedHashSet<Command>();
allCommands.addAll(requiredCommands);
allCommands.addAll(optionalCommands);
Expand All @@ -61,8 +72,18 @@ public final class ParallelGroup implements Command {
m_optionalCommands.addAll(optionalCommands);
m_requiredCommands.addAll(requiredCommands);

for (var command : allCommands) {
m_requirements.addAll(command.requirements());
// if all subcommand requirements are inherited, no need to check additional requirements
if(inheritRequirements) {
for (var command : allCommands) {
m_requirements.addAll(command.requirements());
}
} else {
// otherwise, only inherit requirements that are wanted
for (var command : allCommands) {
Set<Mechanism> required = command.requirements();
required.retainAll(additionalRequirements);
m_requirements.addAll(required);
}
}

m_priority =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static org.wpilib.util.ErrorMessages.requireNonNullParam;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.BooleanSupplier;
Expand All @@ -23,7 +25,9 @@
public class ParallelGroupBuilder {
private final Set<Command> m_optionalCommands = new LinkedHashSet<>();
private final Set<Command> m_requiredCommands = new LinkedHashSet<>();
private final Set<Mechanism> m_additionalRequirements = new HashSet<>();
private BooleanSupplier m_endCondition;
private boolean m_inheritRequirements = true;

/**
* Creates a new parallel group builder. The builder will have no commands and have no preapplied
Expand Down Expand Up @@ -91,6 +95,67 @@ public ParallelGroupBuilder until(BooleanSupplier condition) {
return this;
}

/**
* Specifies whether the command group should inherit requirements from the subcommands.
* By default, the command group inherits the requirements from its subcommands, and will require
* any mechanism used in the group until the entire group finishes. If inheriting is disabled,
* it allows for mechanisms to return back to their default command, or be used by other commands.
*
* @param shouldInherit Whether the group should inherit the requirements of the subcommands
* @return The builder object, for chaining
*/
public ParallelGroupBuilder inheritRequirements(boolean shouldInherit) {
m_inheritRequirements = shouldInherit;
return this;
}

/**
* Specifies whether the command group has any additional mechanism requirements. By default,
* the group will automatically inherit any mechanisms from its subcommands. However, if requirement
* inheriting is disabled ({@link #inheritRequirements(boolean)}), it can be useful to add
* requirements for specific mechanisms.
*
* @param requirement The first required mechanism. Cannot be null.
* @param extra Any optional extra required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return The builder object, for chaining
*/
public ParallelGroupBuilder withAdditionalRequirements(Mechanism requirement, Mechanism... extra) {
requireNonNullParam(requirement, "requirement", "ParallelGroupBuilder.withAdditionalRequirements");
requireNonNullParam(extra, "extra", "ParallelGroupBuilder.withAdditionalRequirements");

for (int i = 0; i < extra.length; i++) {
requireNonNullParam(extra[i], "extra[" + i + "]", "ParallelGroupBuilder.withAdditionalRequirements");
}

m_additionalRequirements.add(requirement);
m_additionalRequirements.addAll(Arrays.asList(extra));

return this;
}

/**
* Specifies whether the command group has any additional mechanism requirements. By default,
* the group will automatically inherit any mechanisms from its subcommands. However, if requirement
* inheriting is disabled ({@link #inheritRequirements(boolean)}), it can be useful to add
* requirements for specific mechanisms.
*
* @param requirements A collection of required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return The builder object, for chaining
*/
public ParallelGroupBuilder withAdditionalRequirements(Collection<Mechanism> requirements) {
requireNonNullParam(requirements, "requirements", "ParallelGroupBuilder.withAdditionalRequirements");
int i = 0;
for (var mechanism : requirements) {
requireNonNullParam(mechanism, "requirements[" + i + "]", "ParallelGroupBuilder.withAdditionalRequirements");
i++;
}

m_additionalRequirements.addAll(requirements);
return this;
}

/**
* Creates the group, using the provided named. The group will require everything that the
* commands in the group require, and will have a priority level equal to the highest priority
Expand All @@ -102,7 +167,8 @@ public ParallelGroupBuilder until(BooleanSupplier condition) {
public ParallelGroup named(String name) {
requireNonNullParam(name, "name", "ParallelGroupBuilder.named");

var group = new ParallelGroup(name, m_requiredCommands, m_optionalCommands);
var group = new ParallelGroup(
name, m_requiredCommands, m_optionalCommands, m_inheritRequirements, m_additionalRequirements);
if (m_endCondition == null) {
// No custom end condition, return the group as is
return group;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,38 @@ public final class SequentialGroup implements Command {
*
* @param name the name of the sequence
* @param commands the commands to execute within the sequence
* @param inheritRequirements whether the group should inherit the requirements of the subcommands
* @param additionalRequirements Any additional mechanism requirements. Additional requirements must be
* requirements of at least one subcommand.
*/
SequentialGroup(String name, List<Command> commands) {
SequentialGroup(String name, List<Command> commands, boolean inheritRequirements, Set<Mechanism> additionalRequirements) {
requireNonNullParam(name, "name", "SequentialGroup");
requireNonNullParam(commands, "commands", "SequentialGroup");
requireNonNullParam(additionalRequirements, "additionalRequirements", "SequentialGroup");
for (int i = 0; i < commands.size(); i++) {
requireNonNullParam(commands.get(i), "commands[" + i + "]", "SequentialGroup");
}
int i = 0;
for(Mechanism requirement : additionalRequirements) {
requireNonNullParam(requirement, "additionalRequirements[" + i + "]", "SequentialGroup");
i++;
}

m_name = name;
m_commands.addAll(commands);

for (var command : commands) {
m_requirements.addAll(command.requirements());
// if all subcommand requirements are inherited, no need to check additional requirements
if(inheritRequirements) {
for (var command : commands) {
m_requirements.addAll(command.requirements());
}
} else {
// otherwise, only inherit requirements that are wanted
for (var command : commands) {
Set<Mechanism> required = command.requirements();
required.retainAll(additionalRequirements);
m_requirements.addAll(required);
}
}

m_priority =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@
import static org.wpilib.util.ErrorMessages.requireNonNullParam;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import org.wpilib.annotation.NoDiscard;

/**
* A builder class to configure and then create a {@link SequentialGroup}. Like {@link
* StagedCommandBuilder}, the final command is created by calling the terminal {@link
* SequentialGroupBuilder}, the final command is created by calling the terminal {@link
* #named(String)} method, or with an automatically generated name using {@link
* #withAutomaticName()}.
*/
@NoDiscard
public class SequentialGroupBuilder {
private final List<Command> m_steps = new ArrayList<>();
private final Set<Mechanism> m_additionalRequirements = new HashSet<>();
private BooleanSupplier m_endCondition;
private boolean m_inheritRequirements = true;

/**
* Creates new SequentialGroupBuilder. The builder will have no commands and have no preapplied
Expand Down Expand Up @@ -75,14 +80,76 @@ public SequentialGroupBuilder until(BooleanSupplier endCondition) {
return this;
}

/**
* Specifies whether the command group should inherit requirements from the subcommands.
* By default, the command group inherits the requirements from its subcommands, and will require
* any mechanism used in the group until the entire group finishes. If inheriting is disabled,
* it allows for mechanisms to return back to their default command, or be used by other commands.
*
* @param shouldInherit Whether the group should inherit the requirements of the subcommands
* @return The builder object, for chaining
*/
public SequentialGroupBuilder inheritRequirements(boolean shouldInherit) {
m_inheritRequirements = shouldInherit;
return this;
}

/**
* Specifies whether the command group has any additional mechanism requirements. By default,
* the group will automatically inherit any mechanisms from its subcommands. However, if requirement
* inheriting is disabled ({@link #inheritRequirements(boolean)}), it can be useful to add
* requirements for specific mechanisms.
*
* @param requirement The first required mechanism. Cannot be null.
* @param extra Any optional extra required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return The builder object, for chaining
*/
public SequentialGroupBuilder withAdditionalRequirements(Mechanism requirement, Mechanism... extra) {
requireNonNullParam(requirement, "requirement", "SequentialGroupBuilder.withAdditionalRequirements");
requireNonNullParam(extra, "extra", "SequentialGroupBuilder.withAdditionalRequirements");

for (int i = 0; i < extra.length; i++) {
requireNonNullParam(extra[i], "extra[" + i + "]", "SequentialGroupBuilder.withAdditionalRequirements");
}

m_additionalRequirements.add(requirement);
m_additionalRequirements.addAll(Arrays.asList(extra));

return this;
}

/**
* Specifies whether the command group has any additional mechanism requirements. By default,
* the group will automatically inherit any mechanisms from its subcommands. However, if requirement
* inheriting is disabled ({@link #inheritRequirements(boolean)}), it can be useful to add
* requirements for specific mechanisms.
*
* @param requirements A collection of required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return The builder object, for chaining
*/
public SequentialGroupBuilder withAdditionalRequirements(Collection<Mechanism> requirements) {

requireNonNullParam(requirements, "requirements", "SequentialGroupBuilder.withAdditionalRequirements");
int i = 0;
for (var mechanism : requirements) {
requireNonNullParam(mechanism, "requirements[" + i + "]", "SequentialGroupBuilder.withAdditionalRequirements");
i++;
}

m_additionalRequirements.addAll(requirements);
return this;
}

/**
* Creates the sequence command, giving it the specified name.
*
* @param name The name of the sequence command
* @return The built command
*/
public Command named(String name) {
var seq = new SequentialGroup(name, m_steps);
var seq = new SequentialGroup(name, m_steps, m_inheritRequirements, m_additionalRequirements);
if (m_endCondition == null) {
// No custom end condition, return the group as is
return seq;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void parallelAll() {
})
.named("C2");

var parallel = new ParallelGroup("Parallel", List.of(c1, c2), List.of());
var parallel = new ParallelGroup("Parallel", List.of(c1, c2), List.of(), true, Set.of());
m_scheduler.schedule(parallel);

// First call to run() should schedule and start the commands
Expand Down Expand Up @@ -106,7 +106,7 @@ void race() {
})
.named("C2");

var race = new ParallelGroup("Race", List.of(), List.of(c1, c2));
var race = new ParallelGroup("Race", List.of(), List.of(c1, c2), true, Set.of());
m_scheduler.schedule(race);

// First call to run() should schedule the commands
Expand Down Expand Up @@ -149,8 +149,8 @@ void nested() {
})
.named("Command");

var inner = new ParallelGroup("Inner", Set.of(command), Set.of());
var outer = new ParallelGroup("Outer", Set.of(), Set.of(inner));
var inner = new ParallelGroup("Inner", Set.of(command), Set.of(), true, Set.of());
var outer = new ParallelGroup("Outer", Set.of(), Set.of(inner), true, Set.of());

// Scheduling: Outer group should be on deck
m_scheduler.schedule(outer);
Expand Down Expand Up @@ -217,7 +217,7 @@ void inheritsRequirements() {
var mech2 = new Mechanism("Mech 2", m_scheduler);
var command1 = mech1.run(Coroutine::park).named("Command 1");
var command2 = mech2.run(Coroutine::park).named("Command 2");
var group = new ParallelGroup("Group", Set.of(command1, command2), Set.of());
var group = new ParallelGroup("Group", Set.of(command1, command2), Set.of(), true, Set.of());
assertEquals(Set.of(mech1, mech2), group.requirements(), "Requirements were not inherited");
}

Expand All @@ -227,7 +227,7 @@ void inheritsPriority() {
var mech2 = new Mechanism("Mech 2", m_scheduler);
var command1 = mech1.run(Coroutine::park).withPriority(100).named("Command 1");
var command2 = mech2.run(Coroutine::park).withPriority(200).named("Command 2");
var group = new ParallelGroup("Group", Set.of(command1, command2), Set.of());
var group = new ParallelGroup("Group", Set.of(command1, command2), Set.of(), true, Set.of());
assertEquals(200, group.priority(), "Priority was not inherited");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -357,7 +358,7 @@ void runsOnCancelWhenCancelingParent() {
var mechanism = new Mechanism("The mechanism", m_scheduler);
var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd");

var group = new SequentialGroup("Seq", Collections.singletonList(cmd));
var group = new SequentialGroup("Seq", Collections.singletonList(cmd), true, Set.of());
m_scheduler.schedule(group);
m_scheduler.run();
m_scheduler.cancel(group);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class SequentialGroupTest extends CommandTestBase {
void single() {
var command = Command.noRequirements(Coroutine::yield).named("The Command");

var sequence = new SequentialGroup("The Sequence", List.of(command));
var sequence = new SequentialGroup("The Sequence", List.of(command), true, Set.of());
m_scheduler.schedule(sequence);

// First run - the composed command starts and yields; sequence yields
Expand All @@ -36,7 +36,7 @@ void twoCommands() {
var c1 = Command.noRequirements(Coroutine::yield).named("C1");
var c2 = Command.noRequirements(Coroutine::yield).named("C2");

var sequence = new SequentialGroup("C1 > C2", List.of(c1, c2));
var sequence = new SequentialGroup("C1 > C2", List.of(c1, c2), true, Set.of());
m_scheduler.schedule(sequence);

// First run - c1 is scheduled and starts
Expand Down Expand Up @@ -66,7 +66,7 @@ void inheritsRequirements() {
var mech2 = new Mechanism("Mech 2", m_scheduler);
var command1 = mech1.run(Coroutine::park).named("Command 1");
var command2 = mech2.run(Coroutine::park).named("Command 2");
var sequence = new SequentialGroup("Sequence", List.of(command1, command2));
var sequence = new SequentialGroup("Sequence", List.of(command1, command2), true, Set.of());
assertEquals(Set.of(mech1, mech2), sequence.requirements(), "Requirements were not inherited");
}

Expand All @@ -76,7 +76,7 @@ void inheritsPriority() {
var mech2 = new Mechanism("Mech 2", m_scheduler);
var command1 = mech1.run(Coroutine::park).withPriority(100).named("Command 1");
var command2 = mech2.run(Coroutine::park).withPriority(200).named("Command 2");
var sequence = new SequentialGroup("Sequence", List.of(command1, command2));
var sequence = new SequentialGroup("Sequence", List.of(command1, command2), true, Set.of());
assertEquals(200, sequence.priority(), "Priority was not inherited");
}
}
Loading