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}