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