2020 Command-Based Rewrite: What Changed?¶
This article provides a summary of changes from the original command-based framework to the 2020 rewrite. This summary is not necessarily comprehensive - for rigorous documentation, as always, refer to the API docs (Java, C++).
Package Location¶
The new command-based framework is located in the wpilibj2
package for Java, and in the frc2
namespace for C++. The old command-based framework is still available in the original location. The new framework must be installed using the instructions: WPILib Command Libraries.
Major Architectural Changes¶
The overall structure of the command-based framework has remained largely the same. However, there are some still a few major architectural changes that users should be aware of:
Commands and Subsystems as Interfaces¶
Command
(Java, C++) and Subsystem
(Java, C++) are both now interfaces as opposed to abstract classes, allowing advanced users more potential flexibility. CommandBase
and SubsystemBase
abstract base classes are still provided for convenience, but are not required. For more information, see Commands and Subsystems.
Multiple Command Group Classes¶
The CommandGroup
class no longer exists, and has been replaced by a number of narrower classes that can be recursively composed to create more-complicated group structures. For more information see Command Groups.
Inline Command Definitions¶
Previously, users were required to write a subclass of Command
in almost all cases where a command was needed. Many of the new commands are designed to allow inline definition of command functionality, and so can be used without the need for an explicit subclass. For more information, see Convenience Features.
Injection of Command Dependencies¶
While not an actual change to the coding of the library, the recommended use pattern for the new command-based framework utilizes injection of subsystem dependencies into commands, so that subsystems are not declared as globals. This is a cleaner, more maintainable, and more reusable pattern than the global subsystem pattern promoted previously. For more information, see Structuring a Command-Based Robot Project.
Command Ownership (C++ Only)¶
The previous command framework required users to use raw pointers for all commands, resulting in nearly-unavoidable memory leaks in all C++ command-based projects, as well as leaving room for common errors such as double-allocating commands within command-groups.
The new command framework offers ownership management for all commands. Default commands and commands bound to buttons are typically owned by the scheduler, and component commands are owned by their encapsulating command groups. As a result, users should generally never heap-allocate a command with new
unless there is a very good reason to do so.
Transfer of ownership is done using perfect forwarding, meaning rvalues will be moved and lvalues will be copied (rvalue/lvalue explanation).
Changes to the Scheduler¶
Interruptibility of commands is now the responsibility of the scheduler, not the commands, and can be specified during the call to
schedule
.Users can now pass actions to the scheduler which are taken whenever a command is scheduled, interrupted, or ends normally. This is highly useful for cases such as event logging.
Changes to Subsystem¶
Note
For more information on subsystems, see Subsystems.
As noted earlier,
Subsystem
is now an interface (Java, C++); the closest equivalent of the oldSubsystem
is the newSubsystemBase
class. Many of the Sendable-related constructor overloads have been removed to reduce clutter; users can call the setters directly from their own constructor, if needed.initDefaultCommand
has been removed; subsystems no longer need to “know about” their default commands, which are instead registered directly with theCommandScheduler
. The newsetDefaultCommand
method simply wraps theCommandScheduler
call.Subsystems no longer “know about” the commands currently requiring them; this is handled exclusively by the
CommandScheduler
. A convenience wrapper on theCommandScheduler
method is provided, however.
Changes to Command¶
Note
For more information on commands, see Commands.
As noted earlier,
Command
is now an interface (Java, C++); the closest equivalent of the oldCommand
is the newCommandBase
class. Many of the Sendable-related constructor overloads have been removed to reduce clutter; users can call the setters directly from their own constructor, if needed.Commands no longer handle their own scheduling state; this is now the responsibility of the scheduler.
The
interrupted()
method has been rolled into theend()
method, which now takes a parameter specifying whether the command was interrupted (false
if it ended normally).The
requires()
method has been renamed toaddRequirement()
.void setRunsWhenDisabled(boolean disabled)
has been replaced by an overrideablerunsWhenDisabled()
method. Commands that should run when disabled should override this method to return true.void setInterruptible(boolean interruptible)
has been removed; interruptibility is no longer an innate property of commands, and can be set when the command is scheduled.Several “decorator” methods have been added to allow easy inline modification of commands (e.g. adding a timeout).
(C++ only) In order to allow the decorators to work with the command ownership model, a CRTP is used via the
CommandHelper
class. Any user-defined Command subclassFoo
must extendCommandHelper<Foo, Base>
whereBase
is the desired base class.
Changes to PIDSubsystem/PIDCommand¶
Note
For more information, see PID Control through PIDSubsystems and PIDCommands
Following the changes to PIDController, these classes now run synchronously from the main robot loop.
The
PIDController
is now injected through the constructor, removing many of the forwarding methods. It can be modifier after construction withgetController()
.PIDCommand
is intended largely for inline use, as shown in the GyroDriveCommands example (Java, C++).If users wish to use PIDCommand more “traditionally,” overriding the protected
returnPIDInput()
andusePIDOutput(double output)
methods has been replaced by modifying the protectedm_measurement
andm_useOutput
fields. Similarly, rather than callingsetSetpoint
, users can modify the protectedm_setpoint
field.