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}