Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[commands] Clarified error messages for parallel composition commands #7161

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 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
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,37 @@ public WrapperCommand withName(String name) {
return wrapper;
}

/**
* Throws an error if a parallel group already shares
* one or more requirements with a command
* that will be added to it.
Comment on lines +585 to +587
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an FYI, this will need to be formatted (and since your main branch is a PR branch, /format on your PRs won't work). You can look at the comment-command .yml file to see what steps you should run to format. (pip3 install wpiformat==2024.45, wpiformat, and ./gradlew spotlessApply)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah forgot to run /format

*
* @param toAdd The command that will be added to the parallel group.
*/
protected void ensureDisjointRequirements(Command toAdd) {
var sharedRequirements = new HashSet<>(getRequirements());
sharedRequirements.retainAll(toAdd.getRequirements());
if (!sharedRequirements.isEmpty()) {
StringBuilder sharedRequirementsStr = new StringBuilder();
boolean first = true;
for (Subsystem requirement: sharedRequirements) {
if (first) {
first = false;
} else {
sharedRequirementsStr.append(", ");
}
sharedRequirementsStr.append(requirement.getName());
}
throw new IllegalArgumentException(
String.format(
"Command %s could not be added to this parallel group"
+ " because the subsystems [%s] are already required in this command."
+ " Multiple commands in a parallel composition cannot require"
+ " the same subsystems.",
toAdd.getName(), sharedRequirementsStr));
}
}

@Override
public void initSendable(SendableBuilder builder) {
builder.setSmartDashboardType("Command");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package edu.wpi.first.wpilibj2.command;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

Expand Down Expand Up @@ -51,10 +50,7 @@ public final void addCommands(Command... commands) {
CommandScheduler.getInstance().registerComposedCommands(commands);

for (Command command : commands) {
if (!Collections.disjoint(command.getRequirements(), getRequirements())) {
throw new IllegalArgumentException(
"Multiple commands in a parallel composition cannot require the same subsystems");
}
ensureDisjointRequirements(command);
m_commands.put(command, false);
addRequirements(command.getRequirements());
m_runWhenDisabled &= command.runsWhenDisabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package edu.wpi.first.wpilibj2.command;

import edu.wpi.first.util.sendable.SendableBuilder;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

Expand Down Expand Up @@ -81,10 +80,7 @@ public final void addCommands(Command... commands) {
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");
}
ensureDisjointRequirements(command);
m_commands.put(command, false);
addRequirements(command.getRequirements());
m_runWhenDisabled &= command.runsWhenDisabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package edu.wpi.first.wpilibj2.command;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

Expand Down Expand Up @@ -52,10 +51,7 @@ public final void addCommands(Command... commands) {
CommandScheduler.getInstance().registerComposedCommands(commands);

for (Command command : commands) {
if (!Collections.disjoint(command.getRequirements(), getRequirements())) {
throw new IllegalArgumentException(
"Multiple commands in a parallel composition cannot require the same subsystems");
}
ensureDisjointRequirements(command);
m_commands.add(command);
addRequirements(command.getRequirements());
m_runWhenDisabled &= command.runsWhenDisabled();
Expand Down
37 changes: 26 additions & 11 deletions wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <string>
#include <utility>
#include <vector>
Daniel1464 marked this conversation as resolved.
Show resolved Hide resolved

#include <wpi/StackTrace.h>
#include <wpi/sendable/SendableBuilder.h>
Expand Down Expand Up @@ -185,6 +186,31 @@ std::optional<std::string> Command::GetPreviousCompositionSite() const {
return m_previousComposition;
}

void Command::EnsureDisjointRequirements(Command* toAdd) {
std::string sharedRequirementsStr = "";
bool hasSharedRequirements = false;
auto&& requirementsToAdd = toAdd->GetRequirements();
for (auto&& requirement : this->GetRequirements()) {
if (requirementsToAdd.find(requirement) != requirementsToAdd.end()) {
if (!hasSharedRequirements) {
hasSharedRequirements = true; // ensures formatting like "a, b, c"
} else {
sharedRequirementsStr.append(", ");
}
sharedRequirementsStr.append(requirement->GetName());
}
}
if (hasSharedRequirements) {
throw FRC_MakeError(
frc::err::CommandIllegalUse,
"Command {} could not be added to this Parallel Group"
" because the subsystems [{}] are already required in this command."
" Multiple commands in a parallel composition cannot require the "
"same subsystems.",
toAdd->GetName(), sharedRequirementsStr);
}
}

void Command::InitSendable(wpi::SendableBuilder& builder) {
builder.SetSmartDashboardType("Command");
builder.AddStringProperty(".name", [this] { return GetName(); }, nullptr);
Expand Down Expand Up @@ -216,14 +242,3 @@ void Command::InitSendable(wpi::SendableBuilder& builder) {
builder.AddBooleanProperty(
"runsWhenDisabled", [this] { return RunsWhenDisabled(); }, nullptr);
}

namespace frc2 {
bool RequirementsDisjoint(Command* first, Command* second) {
bool disjoint = true;
auto&& requirements = second->GetRequirements();
for (auto&& requirement : first->GetRequirements()) {
disjoint &= requirements.find(requirement) == requirements.end();
}
return disjoint;
}
} // namespace frc2
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "frc2/command/ParallelCommandGroup.h"

#include <string>
Daniel1464 marked this conversation as resolved.
Show resolved Hide resolved
#include <utility>
#include <vector>

Expand Down Expand Up @@ -75,19 +76,14 @@ void ParallelCommandGroup::AddCommands(
}

for (auto&& command : commands) {
if (RequirementsDisjoint(this, command.get())) {
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command), false);
} else {
throw FRC_MakeError(frc::err::CommandIllegalUse,
"Multiple commands in a parallel group cannot "
"require the same subsystems");
EnsureDisjointRequirements(command.get());
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command), false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "frc2/command/ParallelDeadlineGroup.h"

#include <memory>
#include <string>
Daniel1464 marked this conversation as resolved.
Show resolved Hide resolved
#include <utility>
#include <vector>

Expand Down Expand Up @@ -75,20 +76,15 @@ void ParallelDeadlineGroup::AddCommands(
}

for (auto&& command : commands) {
if (RequirementsDisjoint(this, command.get())) {
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command), false);
} else {
throw FRC_MakeError(frc::err::CommandIllegalUse,
"Multiple commands in a parallel group cannot "
"require the same subsystems");
EnsureDisjointRequirements(command.get());
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command), false);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "frc2/command/ParallelRaceGroup.h"

#include <string>
Daniel1464 marked this conversation as resolved.
Show resolved Hide resolved
#include <utility>
#include <vector>

Expand Down Expand Up @@ -62,19 +63,14 @@ void ParallelRaceGroup::AddCommands(
}

for (auto&& command : commands) {
if (RequirementsDisjoint(this, command.get())) {
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command));
} else {
throw FRC_MakeError(frc::err::CommandIllegalUse,
"Multiple commands in a parallel group cannot "
"require the same subsystems");
EnsureDisjointRequirements(command.get());
command->SetComposed(true);
AddRequirements(command->GetRequirements());
m_runWhenDisabled &= command->RunsWhenDisabled();
if (command->GetInterruptionBehavior() ==
Command::InterruptionBehavior::kCancelSelf) {
m_interruptBehavior = Command::InterruptionBehavior::kCancelSelf;
}
m_commands.emplace_back(std::move(command));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <memory>
#include <optional>
#include <string>
#include <vector>
Daniel1464 marked this conversation as resolved.
Show resolved Hide resolved

#include <units/time.h>
#include <wpi/Demangle.h>
Expand Down Expand Up @@ -484,19 +485,21 @@ class Command : public wpi::Sendable, public wpi::SendableHelper<Command> {
protected:
Command();

/**
* Throws an error if a parallel group already shares
* one or more requirements with a command
* that will be added to it.
*
* @param parallelGroup The parallel group command.
* @param toAdd The command that will be added to the parallel group.
*/
void EnsureDisjointRequirements(Command* toAdd);

private:
/// Requirements set.
wpi::SmallSet<Subsystem*, 4> m_requirements;

std::optional<std::string> m_previousComposition;
};

/**
* Checks if two commands have disjoint requirement sets.
*
* @param first The first command to check.
* @param second The second command to check.
* @return False if first and second share a requirement.
*/
bool RequirementsDisjoint(Command* first, Command* second);
} // namespace frc2
} // namespace frc2
Loading