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