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