001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.command;
006
007import edu.wpi.first.hal.FRCNetComm.tInstances;
008import edu.wpi.first.hal.FRCNetComm.tResourceType;
009import edu.wpi.first.hal.HAL;
010import edu.wpi.first.networktables.NTSendable;
011import edu.wpi.first.networktables.NTSendableBuilder;
012import edu.wpi.first.networktables.NetworkTableEntry;
013import edu.wpi.first.util.sendable.SendableRegistry;
014import edu.wpi.first.wpilibj.buttons.Trigger.ButtonScheduler;
015import edu.wpi.first.wpilibj.livewindow.LiveWindow;
016import java.util.Enumeration;
017import java.util.Hashtable;
018import java.util.Map;
019import java.util.Vector;
020
021/**
022 * The {@link Scheduler} is a singleton which holds the top-level running commands. It is in charge
023 * of both calling the command's {@link Command#run() run()} method and to make sure that there are
024 * no two commands with conflicting requirements running.
025 *
026 * <p>It is fine if teams wish to take control of the {@link Scheduler} themselves, all that needs
027 * to be done is to call {@link Scheduler#getInstance() Scheduler.getInstance()}.{@link
028 * Scheduler#run() run()} often to have {@link Command Commands} function correctly. However, this
029 * is already done for you if you use the CommandBased Robot template.
030 *
031 * @see Command
032 */
033public final class Scheduler implements NTSendable, AutoCloseable {
034  /** The Singleton Instance. */
035  private static Scheduler instance;
036
037  /**
038   * Returns the {@link Scheduler}, creating it if one does not exist.
039   *
040   * @return the {@link Scheduler}
041   */
042  public static synchronized Scheduler getInstance() {
043    if (instance == null) {
044      instance = new Scheduler();
045    }
046    return instance;
047  }
048
049  /** A hashtable of active {@link Command Commands} to their {@link LinkedListElement}. */
050  private final Map<Command, LinkedListElement> m_commandTable = new Hashtable<>();
051  /** The {@link Set} of all {@link Subsystem Subsystems}. */
052  private final Set m_subsystems = new Set();
053  /** The first {@link Command} in the list. */
054  private LinkedListElement m_firstCommand;
055  /** The last {@link Command} in the list. */
056  private LinkedListElement m_lastCommand;
057  /** Whether or not we are currently adding a command. */
058  private boolean m_adding;
059  /** Whether or not we are currently disabled. */
060  private boolean m_disabled;
061  /** A list of all {@link Command Commands} which need to be added. */
062  @SuppressWarnings("PMD.UseArrayListInsteadOfVector")
063  private final Vector<Command> m_additions = new Vector<>();
064  /**
065   * A list of all {@link edu.wpi.first.wpilibj.buttons.Trigger.ButtonScheduler Buttons}. It is
066   * created lazily.
067   */
068  private Vector<ButtonScheduler> m_buttons;
069
070  private boolean m_runningCommandsChanged;
071
072  /** Instantiates a {@link Scheduler}. */
073  private Scheduler() {
074    HAL.report(tResourceType.kResourceType_Command, tInstances.kCommand_Scheduler);
075    SendableRegistry.addLW(this, "Scheduler");
076    LiveWindow.setEnabledListener(
077        () -> {
078          disable();
079          removeAll();
080        });
081    LiveWindow.setDisabledListener(
082        () -> {
083          enable();
084        });
085  }
086
087  @Override
088  public void close() {
089    SendableRegistry.remove(this);
090    LiveWindow.setEnabledListener(null);
091    LiveWindow.setDisabledListener(null);
092  }
093
094  /**
095   * Adds the command to the {@link Scheduler}. This will not add the {@link Command} immediately,
096   * but will instead wait for the proper time in the {@link Scheduler#run()} loop before doing so.
097   * The command returns immediately and does nothing if given null.
098   *
099   * <p>Adding a {@link Command} to the {@link Scheduler} involves the {@link Scheduler} removing
100   * any {@link Command} which has shared requirements.
101   *
102   * @param command the command to add
103   */
104  public void add(Command command) {
105    if (command != null) {
106      m_additions.addElement(command);
107    }
108  }
109
110  /**
111   * Adds a button to the {@link Scheduler}. The {@link Scheduler} will poll the button during its
112   * {@link Scheduler#run()}.
113   *
114   * @param button the button to add
115   */
116  @SuppressWarnings("PMD.UseArrayListInsteadOfVector")
117  public void addButton(ButtonScheduler button) {
118    if (m_buttons == null) {
119      m_buttons = new Vector<>();
120    }
121    m_buttons.addElement(button);
122  }
123
124  /**
125   * Adds a command immediately to the {@link Scheduler}. This should only be called in the {@link
126   * Scheduler#run()} loop. Any command with conflicting requirements will be removed, unless it is
127   * uninterruptible. Giving <code>null</code> does nothing.
128   *
129   * @param command the {@link Command} to add
130   */
131  @SuppressWarnings("MethodName")
132  private void _add(Command command) {
133    if (command == null) {
134      return;
135    }
136
137    // Check to make sure no adding during adding
138    if (m_adding) {
139      System.err.println("WARNING: Can not start command from cancel method.  Ignoring:" + command);
140      return;
141    }
142
143    // Only add if not already in
144    if (!m_commandTable.containsKey(command)) {
145      // Check that the requirements can be had
146      Enumeration<?> requirements = command.getRequirements();
147      while (requirements.hasMoreElements()) {
148        Subsystem lock = (Subsystem) requirements.nextElement();
149        if (lock.getCurrentCommand() != null && !lock.getCurrentCommand().isInterruptible()) {
150          return;
151        }
152      }
153
154      // Give it the requirements
155      m_adding = true;
156      requirements = command.getRequirements();
157      while (requirements.hasMoreElements()) {
158        Subsystem lock = (Subsystem) requirements.nextElement();
159        if (lock.getCurrentCommand() != null) {
160          lock.getCurrentCommand().cancel();
161          remove(lock.getCurrentCommand());
162        }
163        lock.setCurrentCommand(command);
164      }
165      m_adding = false;
166
167      // Add it to the list
168      LinkedListElement element = new LinkedListElement();
169      element.setData(command);
170      if (m_firstCommand == null) {
171        m_firstCommand = m_lastCommand = element;
172      } else {
173        m_lastCommand.add(element);
174        m_lastCommand = element;
175      }
176      m_commandTable.put(command, element);
177
178      m_runningCommandsChanged = true;
179
180      command.startRunning();
181    }
182  }
183
184  /**
185   * Runs a single iteration of the loop. This method should be called often in order to have a
186   * functioning {@link Command} system. The loop has five stages:
187   *
188   * <ol>
189   *   <li>Poll the Buttons
190   *   <li>Execute/Remove the Commands
191   *   <li>Send values to SmartDashboard
192   *   <li>Add Commands
193   *   <li>Add Defaults
194   * </ol>
195   */
196  public void run() {
197    m_runningCommandsChanged = false;
198
199    if (m_disabled) {
200      return;
201    } // Don't run when m_disabled
202
203    // Get button input (going backwards preserves button priority)
204    if (m_buttons != null) {
205      for (int i = m_buttons.size() - 1; i >= 0; i--) {
206        m_buttons.elementAt(i).execute();
207      }
208    }
209
210    // Call every subsystem's periodic method
211    Enumeration<?> subsystems = m_subsystems.getElements();
212    while (subsystems.hasMoreElements()) {
213      ((Subsystem) subsystems.nextElement()).periodic();
214    }
215
216    // Loop through the commands
217    LinkedListElement element = m_firstCommand;
218    while (element != null) {
219      Command command = element.getData();
220      element = element.getNext();
221      if (!command.run()) {
222        remove(command);
223        m_runningCommandsChanged = true;
224      }
225    }
226
227    // Add the new things
228    for (int i = 0; i < m_additions.size(); i++) {
229      _add(m_additions.elementAt(i));
230    }
231    m_additions.removeAllElements();
232
233    // Add in the defaults
234    Enumeration<?> locks = m_subsystems.getElements();
235    while (locks.hasMoreElements()) {
236      Subsystem lock = (Subsystem) locks.nextElement();
237      if (lock.getCurrentCommand() == null) {
238        _add(lock.getDefaultCommand());
239      }
240      lock.confirmCommand();
241    }
242  }
243
244  /**
245   * Registers a {@link Subsystem} to this {@link Scheduler}, so that the {@link Scheduler} might
246   * know if a default {@link Command} needs to be run. All {@link Subsystem Subsystems} should call
247   * this.
248   *
249   * @param system the system
250   */
251  void registerSubsystem(Subsystem system) {
252    if (system != null) {
253      m_subsystems.add(system);
254    }
255  }
256
257  /**
258   * Removes the {@link Command} from the {@link Scheduler}.
259   *
260   * @param command the command to remove
261   */
262  void remove(Command command) {
263    if (command == null || !m_commandTable.containsKey(command)) {
264      return;
265    }
266    LinkedListElement element = m_commandTable.get(command);
267    m_commandTable.remove(command);
268
269    if (element.equals(m_lastCommand)) {
270      m_lastCommand = element.getPrevious();
271    }
272    if (element.equals(m_firstCommand)) {
273      m_firstCommand = element.getNext();
274    }
275    element.remove();
276
277    Enumeration<?> requirements = command.getRequirements();
278    while (requirements.hasMoreElements()) {
279      ((Subsystem) requirements.nextElement()).setCurrentCommand(null);
280    }
281
282    command.removed();
283  }
284
285  /** Removes all commands. */
286  public void removeAll() {
287    // TODO: Confirm that this works with "uninteruptible" commands
288    while (m_firstCommand != null) {
289      remove(m_firstCommand.getData());
290    }
291  }
292
293  /** Disable the command scheduler. */
294  public void disable() {
295    m_disabled = true;
296  }
297
298  /** Enable the command scheduler. */
299  public void enable() {
300    m_disabled = false;
301  }
302
303  @Override
304  public void initSendable(NTSendableBuilder builder) {
305    builder.setSmartDashboardType("Scheduler");
306    final NetworkTableEntry namesEntry = builder.getEntry("Names");
307    final NetworkTableEntry idsEntry = builder.getEntry("Ids");
308    final NetworkTableEntry cancelEntry = builder.getEntry("Cancel");
309    builder.setUpdateTable(
310        () -> {
311          if (namesEntry != null && idsEntry != null && cancelEntry != null) {
312            // Get the commands to cancel
313            double[] toCancel = cancelEntry.getDoubleArray(new double[0]);
314            if (toCancel.length > 0) {
315              for (LinkedListElement e = m_firstCommand; e != null; e = e.getNext()) {
316                for (double d : toCancel) {
317                  if (e.getData().hashCode() == d) {
318                    e.getData().cancel();
319                  }
320                }
321              }
322              cancelEntry.setDoubleArray(new double[0]);
323            }
324
325            if (m_runningCommandsChanged) {
326              // Set the the running commands
327              int number = 0;
328              for (LinkedListElement e = m_firstCommand; e != null; e = e.getNext()) {
329                number++;
330              }
331              String[] commands = new String[number];
332              double[] ids = new double[number];
333              number = 0;
334              for (LinkedListElement e = m_firstCommand; e != null; e = e.getNext()) {
335                commands[number] = e.getData().getName();
336                ids[number] = e.getData().hashCode();
337                number++;
338              }
339              namesEntry.setStringArray(commands);
340              idsEntry.setDoubleArray(ids);
341            }
342          }
343        });
344  }
345}