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