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++).
The new command-based framework is located in the
wpilibj2 package for Java, and in the
frc2 namespace for C++. 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.
SubsystemBase abstract base classes are still provided for convenience, but are not required. For more information, see Commands and Subsystems.
Multiple Command Group Classes
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 Compositions.
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 Included Command Types.
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
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
For more information on subsystems, see Subsystems.
As noted earlier,
Subsystemis now an interface (Java, C++); the closest equivalent of the old
Subsystemis the new
SubsystemBaseclass. 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.
initDefaultCommandhas been removed; subsystems no longer need to “know about” their default commands, which are instead registered directly with the
CommandScheduler. The new
setDefaultCommandmethod simply wraps the
Subsystems no longer “know about” the commands currently requiring them; this is handled exclusively by the
CommandScheduler. A convenience wrapper on the
CommandSchedulermethod is provided, however.
Changes to Command
For more information on commands, see Commands.
As noted earlier,
Commandis now an interface (Java, C++); the closest equivalent of the old
Commandis the new
CommandBaseclass. 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.
interrupted()method has been rolled into the
end()method, which now takes a parameter specifying whether the command was interrupted (
falseif it ended normally).
requires()method has been renamed to
void setRunsWhenDisabled(boolean disabled)has been replaced by an overridable runsWhenDisabled method.
void setInterruptible(boolean interruptible)has been replaced by an overridable getInterruptionBehavior method.
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
CommandHelperclass. Any user-defined Command subclass
Baseis the desired base class.
Changes to PIDSubsystem/PIDCommand
For more information, see PID Control through PIDSubsystems and PIDCommands, and PID Control in WPILib
Following the changes to PIDController, these classes now run synchronously from the main robot loop.
PIDControlleris now injected through the constructor, removing many of the forwarding methods. It can be modified after construction with
PIDCommandis intended largely for inline use, as shown in the GyroDriveCommands example (Java, C++).
If users wish to use PIDCommand more “traditionally,” overriding the protected
usePIDOutput(double output)methods has been replaced by modifying the protected
m_useOutputfields. Similarly, rather than calling
setSetpoint, users can modify the protected