-
Notifications
You must be signed in to change notification settings - Fork 671
[commands v3] Make Mechanism an interface #8303
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
base: 2027
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,51 +10,25 @@ | |
| import org.wpilib.annotation.NoDiscard; | ||
|
|
||
| /** | ||
| * Generic base class to represent mechanisms on a robot. Commands can require sole ownership of a | ||
| * Generic interface to represent mechanisms on a robot. Commands can require sole ownership of a | ||
| * mechanism; when a command that requires a mechanism is running, no other commands may use it at | ||
| * the same time. | ||
| * | ||
| * <p>Even though this class is named "Mechanism", it may be used to represent other physical | ||
| * <p>Unlike the Subsystem interface of commands v2, Mechanism does not need to be explicitly | ||
| * registered in the constructor. | ||
| * | ||
| * <p>Even though this interface is named "Mechanism", it may be used to represent other physical | ||
| * hardware on a robot that should be controlled with commands - for example, an LED strip or a | ||
| * vision processor that can switch between different pipelines could be represented as mechanisms. | ||
| */ | ||
| public class Mechanism { | ||
| private final String m_name; | ||
|
|
||
| private final Scheduler m_registeredScheduler; | ||
|
|
||
| /** | ||
| * Creates a new mechanism registered with the default scheduler instance and named using the name | ||
| * of the class. Intended to be used by subclasses to get sane defaults without needing to | ||
| * manually declare a constructor. | ||
| */ | ||
| @SuppressWarnings("this-escape") | ||
| protected Mechanism() { | ||
| m_name = getClass().getSimpleName(); | ||
| m_registeredScheduler = Scheduler.getDefault(); | ||
| setDefaultCommand(idle()); | ||
| } | ||
|
|
||
| public interface Mechanism { | ||
| /** | ||
| * Creates a new mechanism, registered with the default scheduler instance. | ||
| * Gets the scheduler that registered this subsystem. | ||
| * | ||
| * @param name The name of the mechanism. Cannot be null. | ||
| * @return The registered scheduler. | ||
| */ | ||
| public Mechanism(String name) { | ||
| this(name, Scheduler.getDefault()); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new mechanism, registered with the given scheduler instance. | ||
| * | ||
| * @param name The name of the mechanism. Cannot be null. | ||
| * @param scheduler The registered scheduler. Cannot be null. | ||
| */ | ||
| @SuppressWarnings("this-escape") | ||
| public Mechanism(String name, Scheduler scheduler) { | ||
| m_name = name; | ||
| m_registeredScheduler = scheduler; | ||
| setDefaultCommand(idle()); | ||
| default Scheduler getRegisteredScheduler() { | ||
| return Scheduler.getDefault(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -63,8 +37,8 @@ public Mechanism(String name, Scheduler scheduler) { | |
| * @return The name of the mechanism. | ||
| */ | ||
| @NoDiscard | ||
| public String getName() { | ||
| return m_name; | ||
| default String getName() { | ||
| return this.getClass().getSimpleName(); | ||
Daniel1464 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -79,8 +53,8 @@ public String getName() { | |
| * | ||
| * @param defaultCommand the new default command | ||
| */ | ||
| public void setDefaultCommand(Command defaultCommand) { | ||
| m_registeredScheduler.setDefaultCommand(this, defaultCommand); | ||
| default void setDefaultCommand(Command defaultCommand) { | ||
| getRegisteredScheduler().setDefaultCommand(this, defaultCommand); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -89,8 +63,8 @@ public void setDefaultCommand(Command defaultCommand) { | |
| * | ||
| * @return The currently configured default command | ||
| */ | ||
| public Command getDefaultCommand() { | ||
| return m_registeredScheduler.getDefaultCommandFor(this); | ||
| default Command getDefaultCommand() { | ||
| return getRegisteredScheduler().getDefaultCommandFor(this); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -99,7 +73,7 @@ public Command getDefaultCommand() { | |
| * @param commandBody The main function body of the command. | ||
| * @return The command builder, for further configuration. | ||
| */ | ||
| public NeedsNameBuilderStage run(Consumer<Coroutine> commandBody) { | ||
| default NeedsNameBuilderStage run(Consumer<Coroutine> commandBody) { | ||
| return new StagedCommandBuilder().requiring(this).executing(commandBody); | ||
| } | ||
|
|
||
|
|
@@ -111,7 +85,7 @@ public NeedsNameBuilderStage run(Consumer<Coroutine> commandBody) { | |
| * @param loopBody The body of the infinite loop. | ||
| * @return The command builder, for further configuration. | ||
| */ | ||
| public NeedsNameBuilderStage runRepeatedly(Runnable loopBody) { | ||
| default NeedsNameBuilderStage runRepeatedly(Runnable loopBody) { | ||
| return run( | ||
| coroutine -> { | ||
| while (true) { | ||
|
|
@@ -131,7 +105,7 @@ public NeedsNameBuilderStage runRepeatedly(Runnable loopBody) { | |
| * | ||
| * @return A new idle command. | ||
| */ | ||
| public Command idle() { | ||
| default Command idle() { | ||
| return run(Coroutine::park).withPriority(Command.LOWEST_PRIORITY).named(getName() + "[IDLE]"); | ||
| } | ||
|
|
||
|
|
@@ -141,7 +115,7 @@ public Command idle() { | |
| * @param duration How long the mechanism should idle for. | ||
| * @return A new idle command. | ||
| */ | ||
| public Command idleFor(Time duration) { | ||
| default Command idleFor(Time duration) { | ||
| return idle().withTimeout(duration); | ||
| } | ||
|
|
||
|
|
@@ -154,12 +128,7 @@ public Command idleFor(Time duration) { | |
| * @return The currently running commands that require the mechanism. | ||
| */ | ||
| @NoDiscard | ||
| public List<Command> getRunningCommands() { | ||
| return m_registeredScheduler.getRunningCommandsFor(this); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any concerns about losing this default
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had initially (when the idea to convert first came up) suggested a split to Mechanism (interface) and MechanismBase a la v2 for this reason, though I don't feel super strongly about it (and in fact don't really like "MechanismBase" as a name). In practice I don't think many teams give their subsystems names different from their classes, but it could show up in the case of having multiple copies of a subsystem on the robot (which I actually have done before!). If it is deemed an uncommon case it is probably fine to just tell teams to manually override it in their subsystem if they want to change the string representation (maybe include that in the Mechanism template used in the project generator?).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that teams wanting subsystems to have names other than the class names can override My main concern is about the default
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the only way people will see
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, the idea was to let ppl override getName() instead. |
||
| return m_name; | ||
| default List<Command> getRunningCommands() { | ||
| return getRegisteredScheduler().getRunningCommandsFor(this); | ||
| } | ||
| } | ||
Daniel1464 marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // 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.commands3; | ||
|
|
||
| /** A dummy mechanism that allows inline scheduler and name specification, for use in unit tests. */ | ||
| class DummyMechanism implements Mechanism { | ||
| private final String m_name; | ||
| private final Scheduler m_scheduler; | ||
|
|
||
| /** | ||
| * Creates a dummy mechanism. | ||
| * | ||
| * @param name The name of this dummy mechanism. | ||
| * @param scheduler The registered scheduler. Cannot be null. | ||
| */ | ||
| DummyMechanism(String name, Scheduler scheduler) { | ||
| m_name = name; | ||
| m_scheduler = scheduler; | ||
| } | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return m_name; | ||
| } | ||
|
|
||
| @Override | ||
| public Scheduler getRegisteredScheduler() { | ||
| return m_scheduler; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ class SchedulerCancellationTests extends CommandTestBase { | |
| void cancelOnInterruptDoesNotResume() { | ||
| var count = new AtomicInteger(0); | ||
|
|
||
| var mechanism = new Mechanism("mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("mechanism", m_scheduler); | ||
|
|
||
| var interrupter = | ||
| Command.requiring(mechanism) | ||
|
|
@@ -54,7 +54,7 @@ void cancelOnInterruptDoesNotResume() { | |
| void defaultCommandResumesAfterInterruption() { | ||
| var count = new AtomicInteger(0); | ||
|
|
||
| var mechanism = new Mechanism("mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("mechanism", m_scheduler); | ||
| var defaultCmd = | ||
| mechanism | ||
| .run( | ||
|
|
@@ -169,7 +169,9 @@ void cancelAllDoesNotCallOnCancelHookForQueuedCommands() { | |
| void cancelAllStartsDefaults() { | ||
| var mechanisms = new ArrayList<Mechanism>(10); | ||
| for (int i = 1; i <= 10; i++) { | ||
| mechanisms.add(new Mechanism("System " + i, m_scheduler)); | ||
| var mechanism = new DummyMechanism("System " + i, m_scheduler); | ||
| mechanism.setDefaultCommand(mechanism.idle()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not put a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the default command of Mechanism is now null instead of idle(), and since DummyMechanism is just supposed to be a Mechanism itself, I don't think having it implicitly do this is the best. I could be wrong tho
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Yeah, that seems fair. I guess another option would be to have a decorator that sets the default command: public DummyMechanism withIdleDefaultCommand() {
setDefaultCommand(idle());
return this;
}
// Then we can do e.g. mechanisms.add(new DummyMechanism("System " + i, m_scheduler).withIdleDefaultCommand());This is just for illustrative purposes- Exact signature (return type and name) can vary. |
||
| mechanisms.add(mechanism); | ||
| } | ||
|
|
||
| var command = Command.requiring(mechanisms).executing(Coroutine::yield).named("Big Command"); | ||
|
|
@@ -237,7 +239,7 @@ void cancelDeeplyNestedCompositions() { | |
|
|
||
| @Test | ||
| void compositionsDoNotSelfCancel() { | ||
| var mech = new Mechanism("The mechanism", m_scheduler); | ||
| var mech = new DummyMechanism("The mechanism", m_scheduler); | ||
| var group = | ||
| mech.run( | ||
| co -> { | ||
|
|
@@ -263,7 +265,7 @@ void compositionsDoNotSelfCancel() { | |
|
|
||
| @Test | ||
| void compositionsDoNotCancelParent() { | ||
| var mech = new Mechanism("The mechanism", m_scheduler); | ||
| var mech = new DummyMechanism("The mechanism", m_scheduler); | ||
| var group = | ||
| mech.run( | ||
| co -> { | ||
|
|
@@ -286,7 +288,7 @@ void compositionsDoNotCancelParent() { | |
| void doesNotRunOnCancelWhenInterruptingOnDeck() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
| var interrupter = mechanism.run(Coroutine::yield).named("Interrupter"); | ||
| m_scheduler.schedule(cmd); | ||
|
|
@@ -300,7 +302,7 @@ void doesNotRunOnCancelWhenInterruptingOnDeck() { | |
| void doesNotRunOnCancelWhenCancelingOnDeck() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
| m_scheduler.schedule(cmd); | ||
| // canceling before calling .run() | ||
|
|
@@ -314,7 +316,7 @@ void doesNotRunOnCancelWhenCancelingOnDeck() { | |
| void runsOnCancelWhenInterruptingCommand() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::park).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
| var interrupter = mechanism.run(Coroutine::park).named("Interrupter"); | ||
| m_scheduler.schedule(cmd); | ||
|
|
@@ -329,7 +331,7 @@ void runsOnCancelWhenInterruptingCommand() { | |
| void doesNotRunOnCancelWhenCompleting() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
| m_scheduler.schedule(cmd); | ||
| m_scheduler.run(); | ||
|
|
@@ -343,7 +345,7 @@ void doesNotRunOnCancelWhenCompleting() { | |
| void runsOnCancelWhenCanceling() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
| m_scheduler.schedule(cmd); | ||
| m_scheduler.run(); | ||
|
|
@@ -356,7 +358,7 @@ void runsOnCancelWhenCanceling() { | |
| void runsOnCancelWhenCancelingParent() { | ||
| var ran = new AtomicBoolean(false); | ||
|
|
||
| var mechanism = new Mechanism("The mechanism", m_scheduler); | ||
| var mechanism = new DummyMechanism("The mechanism", m_scheduler); | ||
| var cmd = mechanism.run(Coroutine::yield).whenCanceled(() -> ran.set(true)).named("cmd"); | ||
|
|
||
| var group = new SequentialGroup("Seq", Collections.singletonList(cmd)); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.