命令调度程序

The CommandScheduler (Java, C++) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted.

“CommandScheduler”还运行每个已注册“Subsystem”的“periodic()”方法。

使用命令调度程序

“CommandScheduler” 是一个 单例类 ,这意味着它是一个只有一个实例的全局类。因此,为了访问调度程序,用户必须调用 “CommandScheduler.getInstance()” 命令。

在大多数情况下,用户不必直接调用调度程序方法——几乎所有重要的调度程序方法在其他位置都有方便包装器(例如,在“Command”和“Subsystem”接口中)。

但是,有一个例外:用户必须从其“Robot”类的“robotPeriodic()”方法中调用“CommandScheduler.getInstance().run()”。如果不这样做,则调度程序将永远不会运行,并且命令框架将无法运行。提供的基于命令的项目模板已包含此调用。

“schedule()”方法

To schedule a command, users call the schedule() method (Java, C++). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its initialize() method is called.

This method walks through the following steps:

  1. Verifies that the command isn’t in a composition.

  2. No-op if scheduler is disabled, command is already scheduled, or robot is disabled and command doesn’t <commands:runsWhenDisabled>.

  3. If requirements are in use: * If all conflicting commands are interruptible, cancel them. * If not, don’t schedule the new command.

  4. Call initialize().

202  private void schedule(Command command) {
203    if (command == null) {
204      DriverStation.reportWarning("Tried to schedule a null command", true);
205      return;
206    }
207    if (m_inRunLoop) {
208      m_toSchedule.add(command);
209      return;
210    }
211
212    requireNotComposed(command);
213
214    // Do nothing if the scheduler is disabled, the robot is disabled and the command doesn't
215    // run when disabled, or the command is already scheduled.
216    if (m_disabled
217        || isScheduled(command)
218        || RobotState.isDisabled() && !command.runsWhenDisabled()) {
219      return;
220    }
221
222    Set<Subsystem> requirements = command.getRequirements();
223
224    // Schedule the command if the requirements are not currently in-use.
225    if (Collections.disjoint(m_requirements.keySet(), requirements)) {
226      initCommand(command, requirements);
227    } else {
228      // Else check if the requirements that are in use have all have interruptible commands,
229      // and if so, interrupt those commands and schedule the new command.
230      for (Subsystem requirement : requirements) {
231        Command requiring = requiring(requirement);
232        if (requiring != null
233            && requiring.getInterruptionBehavior() == InterruptionBehavior.kCancelIncoming) {
234          return;
235        }
236      }
237      for (Subsystem requirement : requirements) {
238        Command requiring = requiring(requirement);
239        if (requiring != null) {
240          cancel(requiring);
241        }
242      }
243      initCommand(command, requirements);
244    }
245  }
181  private void initCommand(Command command, Set<Subsystem> requirements) {
182    m_scheduledCommands.add(command);
183    for (Subsystem requirement : requirements) {
184      m_requirements.put(requirement, command);
185    }
186    command.initialize();
187    for (Consumer<Command> action : m_initActions) {
188      action.accept(command);
189    }
190
191    m_watchdog.addEpoch(command.getName() + ".initialize()");

调度程序运行顺序

备注

每个“Command”的“initialize()”方法都是在调度命令时调用的,而不必在调度程序运行时调用(除非该命令已绑定到按钮)。

What does a single iteration of the scheduler’s run() method (Java, C++) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code (Java, C++).

步骤1:运行子系统定期方法

First, the scheduler runs the periodic() method of each registered Subsystem. In simulation, each subsystem’s simulationPeriodic() method is called as well.

278    // Run the periodic method of all registered subsystems.
279    for (Subsystem subsystem : m_subsystems.keySet()) {
280      subsystem.periodic();
281      if (RobotBase.isSimulation()) {
282        subsystem.simulationPeriodic();
283      }
284      m_watchdog.addEpoch(subsystem.getClass().getSimpleName() + ".periodic()");
285    }

步骤2:轮询命令调度触发器

备注

有关触发器绑定如何工作的更多信息,请参阅:doc:binding-commands-to-triggers

其次,调度程序轮询所有已注册触发器的状态,以查看是否应调度已绑定到这些触发器的任何新命令。如果满足调度绑定命令的条件,则对命令进行调度并运行其“Initialize()”方法。

290    // Poll buttons for new commands to add.
291    loopCache.poll();
292    m_watchdog.addEpoch("buttons.run()");

步骤3:运行/完成计划的命令

第三,调度程序调用每个当前调度的命令的“execute()”方法,然后通过调用“isFinished()”方法检查命令是否完成。如果命令已完成,则还将调用“end()”方法,并对该命令进行调度,并释放其所需的子系统。

请注意,此调用顺序是针对每个命令按顺序进行的——因此,一个命令可能会先调用其“end()”方法,而另一命令可能会调用其“execute()”方法。命令按计划的顺序处理。

295    // Run scheduled commands, remove finished commands.
296    for (Iterator<Command> iterator = m_scheduledCommands.iterator(); iterator.hasNext(); ) {
297      Command command = iterator.next();
298
299      if (!command.runsWhenDisabled() && RobotState.isDisabled()) {
300        command.end(true);
301        for (Consumer<Command> action : m_interruptActions) {
302          action.accept(command);
303        }
304        m_requirements.keySet().removeAll(command.getRequirements());
305        iterator.remove();
306        m_watchdog.addEpoch(command.getName() + ".end(true)");
307        continue;
308      }
309
310      command.execute();
311      for (Consumer<Command> action : m_executeActions) {
312        action.accept(command);
313      }
314      m_watchdog.addEpoch(command.getName() + ".execute()");
315      if (command.isFinished()) {
316        command.end(false);
317        for (Consumer<Command> action : m_finishActions) {
318          action.accept(command);
319        }
320        iterator.remove();
321
322        m_requirements.keySet().removeAll(command.getRequirements());
323        m_watchdog.addEpoch(command.getName() + ".end(false)");
324      }
325    }

步骤4:调度默认命令

最后,任何已注册的“子系统”都有其默认命令(如果有的话)。注意,此时将调用默认命令的“initialize()”方法。

340    // Add default commands for un-required registered subsystems.
341    for (Map.Entry<Subsystem, Command> subsystemCommand : m_subsystems.entrySet()) {
342      if (!m_requirements.containsKey(subsystemCommand.getKey())
343          && subsystemCommand.getValue() != null) {
344        schedule(subsystemCommand.getValue());
345      }
346    }

禁用调度程序

可以通过调用“CommandScheduler.getInstance().disable()”来禁用调度程序。禁用时,调度程序的“schedule()”和“run()”命令将不执行任何操作。

可以通过调用“CommandScheduler.getInstance.()enable()”来重新启用调度程序。

命令事件方法

Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods:

  • onCommandInitialize (Java, C++) runs a specified action whenever a command is initialized.

  • onCommandExecute (Java, C++) runs a specified action whenever a command is executed.

  • onCommandFinish (Java, C++) runs a specified action whenever a command finishes normally (i.e. the isFinished() method returned true).

  • onCommandInterrupt (Java, C++) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements).

A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project (Java, C++):

73    // Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish
74    CommandScheduler.getInstance()
75        .onCommandInitialize(
76            command ->
77                Shuffleboard.addEventMarker(
78                    "Command initialized", command.getName(), EventImportance.kNormal));
79    CommandScheduler.getInstance()
80        .onCommandInterrupt(
81            command ->
82                Shuffleboard.addEventMarker(
83                    "Command interrupted", command.getName(), EventImportance.kNormal));
84    CommandScheduler.getInstance()
85        .onCommandFinish(
86            command ->
87                Shuffleboard.addEventMarker(
88                    "Command finished", command.getName(), EventImportance.kNormal));