From ee3b4621e565d4f21eb0b4141e6f68ce6eebda2d Mon Sep 17 00:00:00 2001
From: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
Date: Sun, 30 Apr 2023 14:09:02 -0700
Subject: [PATCH] [commands] Add onlyWhile and onlyIf (#5291)
---
.../wpi/first/wpilibj2/command/Command.java | 49 +++++++++++++++++--
.../main/native/cpp/frc2/command/Command.cpp | 8 +++
.../native/cpp/frc2/command/CommandPtr.cpp | 10 ++++
.../native/include/frc2/command/Command.h | 22 +++++++++
.../native/include/frc2/command/CommandPtr.h | 22 +++++++++
.../command/CommandDecoratorTest.java | 36 ++++++++++++++
.../cpp/frc2/command/CommandDecoratorTest.cpp | 39 +++++++++++++++
7 files changed, 183 insertions(+), 3 deletions(-)
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
index 0383099495..702bf4054f 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
@@ -82,9 +82,7 @@ public interface Command {
/**
* Decorates this command with an interrupt condition. If the specified condition becomes true
- * before the command finishes normally, the command will be interrupted and un-scheduled. Note
- * that this only applies to the command returned by this method; the calling command is not
- * itself changed.
+ * before the command finishes normally, the command will be interrupted and un-scheduled.
*
*
Note: This decorator works by adding this command to a composition. The command the
* decorator was called on cannot be scheduled independently or be added to a different
@@ -94,11 +92,30 @@ public interface Command {
*
* @param condition the interrupt condition
* @return the command with the interrupt condition added
+ * @see #onlyWhile(BooleanSupplier)
*/
default ParallelRaceGroup until(BooleanSupplier condition) {
return raceWith(new WaitUntilCommand(condition));
}
+ /**
+ * Decorates this command with a run condition. If the specified condition becomes false before
+ * the command finishes normally, the command will be interrupted and un-scheduled.
+ *
+ *
Note: This decorator works by adding this command to a composition. The command the
+ * decorator was called on cannot be scheduled independently or be added to a different
+ * composition (namely, decorators), unless it is manually cleared from the list of composed
+ * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
+ * returned from this method can be further decorated without issue.
+ *
+ * @param condition the interrupt condition
+ * @return the command with the interrupt condition added
+ * @see #until(BooleanSupplier)
+ */
+ default ParallelRaceGroup onlyWhile(BooleanSupplier condition) {
+ return until(() -> !condition.getAsBoolean());
+ }
+
/**
* Decorates this command with an interrupt condition. If the specified condition becomes true
* before the command finishes normally, the command will be interrupted and un-scheduled. Note
@@ -301,13 +318,39 @@ public interface Command {
* running and the condition changes to true, the command will not stop running. The requirements
* of this command will be kept for the new conditional command.
*
+ *
Note: This decorator works by adding this command to a composition. The command the
+ * decorator was called on cannot be scheduled independently or be added to a different
+ * composition (namely, decorators), unless it is manually cleared from the list of composed
+ * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
+ * returned from this method can be further decorated without issue.
+ *
* @param condition the condition that will prevent the command from running
* @return the decorated command
+ * @see #onlyIf(BooleanSupplier)
*/
default ConditionalCommand unless(BooleanSupplier condition) {
return new ConditionalCommand(new InstantCommand(), this, condition);
}
+ /**
+ * Decorates this command to only run if this condition is met. If the command is already running
+ * and the condition changes to false, the command will not stop running. The requirements of this
+ * command will be kept for the new conditional command.
+ *
+ *
Note: This decorator works by adding this command to a composition. The command the
+ * decorator was called on cannot be scheduled independently or be added to a different
+ * composition (namely, decorators), unless it is manually cleared from the list of composed
+ * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
+ * returned from this method can be further decorated without issue.
+ *
+ * @param condition the condition that will allow the command to run
+ * @return the decorated command
+ * @see #unless(BooleanSupplier)
+ */
+ default ConditionalCommand onlyIf(BooleanSupplier condition) {
+ return unless(() -> !condition.getAsBoolean());
+ }
+
/**
* Decorates this command to run or stop when disabled.
*
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
index b173ada496..f802915359 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
@@ -41,6 +41,10 @@ CommandPtr Command::Until(std::function condition) && {
return std::move(*this).ToPtr().Until(std::move(condition));
}
+CommandPtr Command::OnlyWhile(std::function condition) && {
+ return std::move(*this).ToPtr().OnlyWhile(std::move(condition));
+}
+
CommandPtr Command::IgnoringDisable(bool doesRunWhenDisabled) && {
return std::move(*this).ToPtr().IgnoringDisable(doesRunWhenDisabled);
}
@@ -96,6 +100,10 @@ CommandPtr Command::Unless(std::function condition) && {
return std::move(*this).ToPtr().Unless(std::move(condition));
}
+CommandPtr Command::OnlyIf(std::function condition) && {
+ return std::move(*this).ToPtr().OnlyIf(std::move(condition));
+}
+
CommandPtr Command::FinallyDo(std::function end) && {
return std::move(*this).ToPtr().FinallyDo(std::move(end));
}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
index 1bb8bf0815..5a9a50427d 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
@@ -151,6 +151,11 @@ CommandPtr CommandPtr::Until(std::function condition) && {
return std::move(*this);
}
+CommandPtr CommandPtr::OnlyWhile(std::function condition) && {
+ AssertValid();
+ return std::move(*this).Until(std::not_fn(std::move(condition)));
+}
+
CommandPtr CommandPtr::Unless(std::function condition) && {
AssertValid();
m_ptr = std::make_unique(
@@ -159,6 +164,11 @@ CommandPtr CommandPtr::Unless(std::function condition) && {
return std::move(*this);
}
+CommandPtr CommandPtr::OnlyIf(std::function condition) && {
+ AssertValid();
+ return std::move(*this).Unless(std::not_fn(std::move(condition)));
+}
+
CommandPtr CommandPtr::DeadlineWith(CommandPtr&& parallel) && {
AssertValid();
std::vector> vec;
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Command.h b/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
index 189433bf11..1e93dfa3f7 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
@@ -137,6 +137,17 @@ class Command {
*/
[[nodiscard]] CommandPtr Until(std::function condition) &&;
+ /**
+ * Decorates this command with a run condition. If the specified condition
+ * becomes false before the command finishes normally, the command will be
+ * interrupted and un-scheduled. Note that this only applies to the command
+ * returned by this method; the calling command is not itself changed.
+ *
+ * @param condition the interrupt condition
+ * @return the command with the interrupt condition added
+ */
+ [[nodiscard]] CommandPtr OnlyWhile(std::function condition) &&;
+
/**
* Decorates this command with an interrupt condition. If the specified
* condition becomes true before the command finishes normally, the command
@@ -245,6 +256,17 @@ safe) semantics.
*/
[[nodiscard]] CommandPtr Unless(std::function condition) &&;
+ /**
+ * Decorates this command to only run if this condition is met. If the command
+ * is already running and the condition changes to false, the command will not
+ * stop running. The requirements of this command will be kept for the new
+ * conditional command.
+ *
+ * @param condition the condition that will allow the command to run
+ * @return the decorated command
+ */
+ [[nodiscard]] CommandPtr OnlyIf(std::function condition) &&;
+
/**
* Decorates this command to run or stop when disabled.
*
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
index cc61106b78..23027cdaa8 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
@@ -159,6 +159,17 @@ class CommandPtr final {
*/
[[nodiscard]] CommandPtr Until(std::function condition) &&;
+ /**
+ * Decorates this command with a run condition. If the specified condition
+ * becomes false before the command finishes normally, the command will be
+ * interrupted and un-scheduled. Note that this only applies to the command
+ * returned by this method; the calling command is not itself changed.
+ *
+ * @param condition the interrupt condition
+ * @return the command with the interrupt condition added
+ */
+ [[nodiscard]] CommandPtr OnlyWhile(std::function condition) &&;
+
/**
* Decorates this command to only run if this condition is not met. If the
* command is already running and the condition changes to true, the command
@@ -170,6 +181,17 @@ class CommandPtr final {
*/
[[nodiscard]] CommandPtr Unless(std::function condition) &&;
+ /**
+ * Decorates this command to only run if this condition is met. If the command
+ * is already running and the condition changes to false, the command will not
+ * stop running. The requirements of this command will be kept for the new
+ * conditional command.
+ *
+ * @param condition the condition that will allow the command to run
+ * @return the decorated command
+ */
+ [[nodiscard]] CommandPtr OnlyIf(std::function condition) &&;
+
/**
* Decorates this command with a set of commands to run parallel to it, ending
* when the calling command ends and interrupting all the others. Often more
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
index 392c23ce08..d164a9b99d 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
@@ -57,6 +57,22 @@ class CommandDecoratorTest extends CommandTestBase {
}
}
+ @Test
+ void onlyWhileTest() {
+ try (CommandScheduler scheduler = new CommandScheduler()) {
+ AtomicBoolean condition = new AtomicBoolean(true);
+
+ Command command = new WaitCommand(10).onlyWhile(condition::get);
+
+ scheduler.schedule(command);
+ scheduler.run();
+ assertTrue(scheduler.isScheduled(command));
+ condition.set(false);
+ scheduler.run();
+ assertFalse(scheduler.isScheduled(command));
+ }
+ }
+
@Test
void ignoringDisableTest() {
try (CommandScheduler scheduler = new CommandScheduler()) {
@@ -221,6 +237,26 @@ class CommandDecoratorTest extends CommandTestBase {
}
}
+ @Test
+ void onlyIfTest() {
+ try (CommandScheduler scheduler = new CommandScheduler()) {
+ AtomicBoolean onlyIfCondition = new AtomicBoolean(false);
+ AtomicBoolean hasRunCondition = new AtomicBoolean(false);
+
+ Command command =
+ new InstantCommand(() -> hasRunCondition.set(true)).onlyIf(onlyIfCondition::get);
+
+ scheduler.schedule(command);
+ scheduler.run();
+ assertFalse(hasRunCondition.get());
+
+ onlyIfCondition.set(true);
+ scheduler.schedule(command);
+ scheduler.run();
+ assertTrue(hasRunCondition.get());
+ }
+ }
+
@Test
void finallyDoTest() {
try (CommandScheduler scheduler = new CommandScheduler()) {
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
index 5ab184cbc9..405d44dc82 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
@@ -54,6 +54,24 @@ TEST_F(CommandDecoratorTest, Until) {
EXPECT_FALSE(scheduler.IsScheduled(command));
}
+TEST_F(CommandDecoratorTest, OnlyWhile) {
+ CommandScheduler scheduler = GetScheduler();
+
+ bool run = true;
+
+ auto command = RunCommand([] {}, {}).OnlyWhile([&run] { return run; });
+
+ scheduler.Schedule(command);
+
+ scheduler.Run();
+ EXPECT_TRUE(scheduler.IsScheduled(command));
+
+ run = false;
+
+ scheduler.Run();
+ EXPECT_FALSE(scheduler.IsScheduled(command));
+}
+
TEST_F(CommandDecoratorTest, IgnoringDisable) {
CommandScheduler scheduler = GetScheduler();
@@ -140,6 +158,27 @@ TEST_F(CommandDecoratorTest, Unless) {
EXPECT_TRUE(hasRun);
}
+TEST_F(CommandDecoratorTest, OnlyIf) {
+ CommandScheduler scheduler = GetScheduler();
+
+ bool hasRun = false;
+ bool onlyIfBool = false;
+
+ auto command =
+ InstantCommand([&hasRun] { hasRun = true; }, {}).OnlyIf([&onlyIfBool] {
+ return onlyIfBool;
+ });
+
+ scheduler.Schedule(command);
+ scheduler.Run();
+ EXPECT_FALSE(hasRun);
+
+ onlyIfBool = true;
+ scheduler.Schedule(command);
+ scheduler.Run();
+ EXPECT_TRUE(hasRun);
+}
+
TEST_F(CommandDecoratorTest, FinallyDo) {
CommandScheduler scheduler = GetScheduler();
int first = 0;