mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-01 02:41:48 +00:00
[commands] Add RepeatCommand (#4009)
Co-authored-by: Starlight220 <53231611+Starlight220@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
88222daa3d
commit
1b26e2d5da
@@ -257,6 +257,22 @@ public interface Command {
|
||||
return new PerpetualCommand(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates this command to run repeatedly, restarting it when it ends, until this command is
|
||||
* interrupted. The decorated command can still be canceled.
|
||||
*
|
||||
* <p>Note: This decorator works by composing this command within a CommandGroup. The command
|
||||
* cannot be used independently after being decorated, or be re-decorated with a different
|
||||
* decorator, unless it is manually cleared from the list of grouped commands with {@link
|
||||
* CommandGroupBase#clearGroupedCommand(Command)}. The decorated command can, however, be further
|
||||
* decorated without issue.
|
||||
*
|
||||
* @return the decorated command
|
||||
*/
|
||||
default RepeatCommand repeat() {
|
||||
return new RepeatCommand(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates this command to run "by proxy" by wrapping it in a {@link ProxyScheduleCommand}. This
|
||||
* is useful for "forking off" from command groups when the user does not wish to extend the
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 edu.wpi.first.wpilibj2.command;
|
||||
|
||||
import static edu.wpi.first.wpilibj2.command.CommandGroupBase.registerGroupedCommands;
|
||||
import static edu.wpi.first.wpilibj2.command.CommandGroupBase.requireUngrouped;
|
||||
|
||||
/**
|
||||
* A command that runs another command repeatedly, restarting it when it ends, until this command is
|
||||
* interrupted. While this class does not extend {@link CommandGroupBase}, it is still considered a
|
||||
* CommandGroup, as it allows one to compose another command within it; the command instances that
|
||||
* are passed to it cannot be added to any other groups, or scheduled individually.
|
||||
*
|
||||
* <p>As a rule, CommandGroups require the union of the requirements of their component commands.
|
||||
*
|
||||
* <p>This class is provided by the NewCommands VendorDep
|
||||
*/
|
||||
public class RepeatCommand extends CommandBase {
|
||||
protected final Command m_command;
|
||||
|
||||
/**
|
||||
* Creates a new RepeatCommand. Will run another command repeatedly, restarting it whenever it
|
||||
* ends, until this command is interrupted.
|
||||
*
|
||||
* @param command the command to run repeatedly
|
||||
*/
|
||||
public RepeatCommand(Command command) {
|
||||
requireUngrouped(command);
|
||||
registerGroupedCommands(command);
|
||||
m_command = command;
|
||||
m_requirements.addAll(command.getRequirements());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
m_command.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
m_command.execute();
|
||||
if (m_command.isFinished()) {
|
||||
// restart command
|
||||
m_command.end(false);
|
||||
m_command.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(boolean interrupted) {
|
||||
m_command.end(interrupted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runsWhenDisabled() {
|
||||
return m_command.runsWhenDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepeatCommand repeat() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "frc2/command/ParallelRaceGroup.h"
|
||||
#include "frc2/command/PerpetualCommand.h"
|
||||
#include "frc2/command/ProxyScheduleCommand.h"
|
||||
#include "frc2/command/RepeatCommand.h"
|
||||
#include "frc2/command/SequentialCommandGroup.h"
|
||||
#include "frc2/command/WaitCommand.h"
|
||||
#include "frc2/command/WaitUntilCommand.h"
|
||||
@@ -87,6 +88,10 @@ PerpetualCommand Command::Perpetually() && {
|
||||
return PerpetualCommand(std::move(*this).TransferOwnership());
|
||||
}
|
||||
|
||||
RepeatCommand Command::Repeat() && {
|
||||
return RepeatCommand(std::move(*this).TransferOwnership());
|
||||
}
|
||||
|
||||
ProxyScheduleCommand Command::AsProxy() {
|
||||
return ProxyScheduleCommand(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
|
||||
#include "frc2/command/RepeatCommand.h"
|
||||
|
||||
using namespace frc2;
|
||||
|
||||
RepeatCommand::RepeatCommand(std::unique_ptr<Command>&& command) {
|
||||
if (!CommandGroupBase::RequireUngrouped(*command)) {
|
||||
return;
|
||||
}
|
||||
m_command = std::move(command);
|
||||
m_command->SetGrouped(true);
|
||||
AddRequirements(m_command->GetRequirements());
|
||||
}
|
||||
|
||||
void RepeatCommand::Initialize() {
|
||||
m_command->Initialize();
|
||||
}
|
||||
|
||||
void RepeatCommand::Execute() {
|
||||
m_command->Execute();
|
||||
if (m_command->IsFinished()) {
|
||||
// restart command
|
||||
m_command->End(false);
|
||||
m_command->Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool RepeatCommand::IsFinished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RepeatCommand::End(bool interrupted) {
|
||||
m_command->End(interrupted);
|
||||
}
|
||||
|
||||
bool RepeatCommand::RunsWhenDisabled() const {
|
||||
return m_command->RunsWhenDisabled();
|
||||
}
|
||||
|
||||
RepeatCommand RepeatCommand::Repeat() && {
|
||||
return std::move(*this);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class ParallelDeadlineGroup;
|
||||
class SequentialCommandGroup;
|
||||
class PerpetualCommand;
|
||||
class ProxyScheduleCommand;
|
||||
class RepeatCommand;
|
||||
|
||||
/**
|
||||
* A state machine representing a complete action to be performed by the robot.
|
||||
@@ -185,6 +186,14 @@ class Command {
|
||||
*/
|
||||
virtual PerpetualCommand Perpetually() &&;
|
||||
|
||||
/**
|
||||
* Decorates this command to run repeatedly, restarting it when it ends, until
|
||||
* this command is interrupted. The decorated command can still be canceled.
|
||||
*
|
||||
* @return the decorated command
|
||||
*/
|
||||
virtual RepeatCommand Repeat() &&;
|
||||
|
||||
/**
|
||||
* Decorates this command to run "by proxy" by wrapping it in a
|
||||
* ProxyScheduleCommand. This is useful for "forking off" from command groups
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4521)
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "frc2/command/CommandBase.h"
|
||||
#include "frc2/command/CommandGroupBase.h"
|
||||
#include "frc2/command/CommandHelper.h"
|
||||
|
||||
namespace frc2 {
|
||||
/**
|
||||
* A command that runs another command repeatedly, restarting it when it ends,
|
||||
* until this command is interrupted. While this class does not extend {@link
|
||||
* CommandGroupBase}, it is still considered a CommandGroup, as it allows one to
|
||||
* compose another command within it; the command instances that are passed to
|
||||
* it cannot be added to any other groups, or scheduled individually.
|
||||
*
|
||||
* <p>As a rule, CommandGroups require the union of the requirements of their
|
||||
* component commands.
|
||||
*
|
||||
* <p>This class is provided by the NewCommands VendorDep
|
||||
*/
|
||||
class RepeatCommand : public CommandHelper<CommandBase, RepeatCommand> {
|
||||
public:
|
||||
/**
|
||||
* Creates a new RepeatCommand. Will run another command repeatedly,
|
||||
* restarting it whenever it ends, until this command is interrupted.
|
||||
*
|
||||
* @param command the command to run repeatedly
|
||||
*/
|
||||
explicit RepeatCommand(std::unique_ptr<Command>&& command);
|
||||
|
||||
/**
|
||||
* Creates a new RepeatCommand. Will run another command repeatedly,
|
||||
* restarting it whenever it ends, until this command is interrupted.
|
||||
*
|
||||
* @param command the command to run repeatedly
|
||||
*/
|
||||
template <class T, typename = std::enable_if_t<std::is_base_of_v<
|
||||
Command, std::remove_reference_t<T>>>>
|
||||
explicit RepeatCommand(T&& command)
|
||||
: RepeatCommand(std::make_unique<std::remove_reference_t<T>>(
|
||||
std::forward<T>(command))) {}
|
||||
|
||||
RepeatCommand(RepeatCommand&& other) = default;
|
||||
|
||||
// No copy constructors for command groups
|
||||
RepeatCommand(const RepeatCommand& other) = delete;
|
||||
|
||||
// Prevent template expansion from emulating copy ctor
|
||||
RepeatCommand(RepeatCommand&) = delete;
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
void Execute() override;
|
||||
|
||||
bool IsFinished() override;
|
||||
|
||||
void End(bool interrupted) override;
|
||||
|
||||
bool RunsWhenDisabled() const override;
|
||||
|
||||
RepeatCommand Repeat() && override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Command> m_command;
|
||||
};
|
||||
} // namespace frc2
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 edu.wpi.first.wpilibj2.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RepeatCommandTest {
|
||||
@Test
|
||||
void callsMethodsCorrectly() {
|
||||
HAL.initialize(500, 0);
|
||||
// enable so that we don't need to mess with `runsWhenDisabled` for each command
|
||||
DriverStationSim.setEnabled(true);
|
||||
|
||||
var initCounter = new AtomicInteger(0);
|
||||
var exeCounter = new AtomicInteger(0);
|
||||
var isFinishedCounter = new AtomicInteger(0);
|
||||
var endCounter = new AtomicInteger(0);
|
||||
var isFinishedHook = new AtomicBoolean(false);
|
||||
|
||||
final var command =
|
||||
new RepeatCommand(
|
||||
new FunctionalCommand(
|
||||
initCounter::incrementAndGet,
|
||||
exeCounter::incrementAndGet,
|
||||
interrupted -> endCounter.incrementAndGet(),
|
||||
() -> {
|
||||
isFinishedCounter.incrementAndGet();
|
||||
return isFinishedHook.get();
|
||||
}));
|
||||
|
||||
assertEquals(0, initCounter.get());
|
||||
assertEquals(0, exeCounter.get());
|
||||
assertEquals(0, isFinishedCounter.get());
|
||||
assertEquals(0, endCounter.get());
|
||||
|
||||
CommandScheduler.getInstance().schedule(command);
|
||||
assertEquals(1, initCounter.get());
|
||||
assertEquals(0, exeCounter.get());
|
||||
assertEquals(0, isFinishedCounter.get());
|
||||
assertEquals(0, endCounter.get());
|
||||
|
||||
isFinishedHook.set(false);
|
||||
CommandScheduler.getInstance().run();
|
||||
assertEquals(1, initCounter.get());
|
||||
assertEquals(1, exeCounter.get());
|
||||
assertEquals(1, isFinishedCounter.get());
|
||||
assertEquals(0, endCounter.get());
|
||||
|
||||
isFinishedHook.set(true);
|
||||
CommandScheduler.getInstance().run();
|
||||
assertEquals(2, initCounter.get());
|
||||
assertEquals(2, exeCounter.get());
|
||||
assertEquals(2, isFinishedCounter.get());
|
||||
assertEquals(1, endCounter.get());
|
||||
|
||||
isFinishedHook.set(false);
|
||||
CommandScheduler.getInstance().run();
|
||||
assertEquals(2, initCounter.get());
|
||||
assertEquals(3, exeCounter.get());
|
||||
assertEquals(3, isFinishedCounter.get());
|
||||
assertEquals(1, endCounter.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.
|
||||
|
||||
#include "CommandTestBase.h"
|
||||
#include "frc2/command/FunctionalCommand.h"
|
||||
#include "frc2/command/RepeatCommand.h"
|
||||
|
||||
using namespace frc2;
|
||||
class RepeatCommandTest : public CommandTestBase {};
|
||||
|
||||
TEST_F(RepeatCommandTest, CallsMethodsCorrectly) {
|
||||
CommandScheduler scheduler = GetScheduler();
|
||||
|
||||
int initCounter = 0;
|
||||
int exeCounter = 0;
|
||||
int isFinishedCounter = 0;
|
||||
int endCounter = 0;
|
||||
bool isFinishedHook = false;
|
||||
|
||||
auto command =
|
||||
FunctionalCommand([&initCounter] { initCounter++; },
|
||||
[&exeCounter] { exeCounter++; },
|
||||
[&endCounter](bool interrupted) { endCounter++; },
|
||||
[&isFinishedCounter, &isFinishedHook] {
|
||||
isFinishedCounter++;
|
||||
return isFinishedHook;
|
||||
})
|
||||
.Repeat();
|
||||
|
||||
EXPECT_EQ(0, initCounter);
|
||||
EXPECT_EQ(0, exeCounter);
|
||||
EXPECT_EQ(0, isFinishedCounter);
|
||||
EXPECT_EQ(0, endCounter);
|
||||
|
||||
scheduler.Schedule(&command);
|
||||
EXPECT_EQ(1, initCounter);
|
||||
EXPECT_EQ(0, exeCounter);
|
||||
EXPECT_EQ(0, isFinishedCounter);
|
||||
EXPECT_EQ(0, endCounter);
|
||||
|
||||
isFinishedHook = false;
|
||||
scheduler.Run();
|
||||
EXPECT_EQ(1, initCounter);
|
||||
EXPECT_EQ(1, exeCounter);
|
||||
EXPECT_EQ(1, isFinishedCounter);
|
||||
EXPECT_EQ(0, endCounter);
|
||||
|
||||
isFinishedHook = true;
|
||||
scheduler.Run();
|
||||
EXPECT_EQ(2, initCounter);
|
||||
EXPECT_EQ(2, exeCounter);
|
||||
EXPECT_EQ(2, isFinishedCounter);
|
||||
EXPECT_EQ(1, endCounter);
|
||||
|
||||
isFinishedHook = false;
|
||||
scheduler.Run();
|
||||
EXPECT_EQ(2, initCounter);
|
||||
EXPECT_EQ(3, exeCounter);
|
||||
EXPECT_EQ(3, isFinishedCounter);
|
||||
EXPECT_EQ(1, endCounter);
|
||||
}
|
||||
Reference in New Issue
Block a user