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