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.Vector; 012 013import static java.util.Objects.requireNonNull; 014 015/** 016 * A {@link CommandGroup} is a list of commands which are executed in sequence. 017 * 018 * <p> Commands in a {@link CommandGroup} are added using the {@link 019 * CommandGroup#addSequential(Command) addSequential(...)} method and are called sequentially. 020 * {@link CommandGroup CommandGroups} are themselves {@link Command commands} and can be given to 021 * other {@link CommandGroup CommandGroups}. </p> 022 * 023 * <p> {@link CommandGroup CommandGroups} will carry all of the requirements of their {@link Command 024 * subcommands}. Additional requirements can be specified by calling {@link 025 * CommandGroup#requires(Subsystem) requires(...)} normally in the constructor. </P> 026 * 027 * <p> CommandGroups can also execute commands in parallel, simply by adding them using {@link 028 * CommandGroup#addParallel(Command) addParallel(...)}. </p> 029 * 030 * @see Command 031 * @see Subsystem 032 * @see IllegalUseOfCommandException 033 */ 034public class CommandGroup extends Command { 035 /** 036 * The commands in this group (stored in entries). 037 */ 038 private final Vector<Entry> m_commands = new Vector<>(); 039 /* 040 * Intentionally package private 041 */ 042 /** 043 * The active children in this group (stored in entries). 044 */ 045 final Vector<Entry> m_children = new Vector<>(); 046 /** 047 * The current command, -1 signifies that none have been run. 048 */ 049 private int m_currentCommandIndex = -1; 050 051 /** 052 * Creates a new {@link CommandGroup CommandGroup}. The name of this command will be set to its 053 * class name. 054 */ 055 public CommandGroup() { 056 } 057 058 /** 059 * Creates a new {@link CommandGroup CommandGroup} with the given name. 060 * 061 * @param name the name for this command group 062 * @throws IllegalArgumentException if name is null 063 */ 064 public CommandGroup(String name) { 065 super(name); 066 } 067 068 /** 069 * Adds a new {@link Command Command} to the group. The {@link Command Command} will be started 070 * after all the previously added {@link Command Commands}. 071 * 072 * <p> Note that any requirements the given {@link Command Command} has will be added to the 073 * group. For this reason, a {@link Command Command's} requirements can not be changed after being 074 * added to a group. </p> 075 * 076 * <p> It is recommended that this method be called in the constructor. </p> 077 * 078 * @param command The {@link Command Command} to be added 079 * @throws IllegalUseOfCommandException if the group has been started before or been given to 080 * another group 081 * @throws IllegalArgumentException if command is null 082 */ 083 public final synchronized void addSequential(Command command) { 084 validate("Can not add new command to command group"); 085 if (command == null) { 086 throw new IllegalArgumentException("Given null command"); 087 } 088 089 command.setParent(this); 090 091 m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE)); 092 for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) { 093 requires((Subsystem) e.nextElement()); 094 } 095 } 096 097 /** 098 * Adds a new {@link Command Command} to the group with a given timeout. The {@link Command 099 * Command} will be started after all the previously added commands. 100 * 101 * <p> Once the {@link Command Command} is started, it will be run until it finishes or the time 102 * expires, whichever is sooner. Note that the given {@link Command Command} will have no 103 * knowledge that it is on a timer. </p> 104 * 105 * <p> Note that any requirements the given {@link Command Command} has will be added to the 106 * group. For this reason, a {@link Command Command's} requirements can not be changed after being 107 * added to a group. </p> 108 * 109 * <p> It is recommended that this method be called in the constructor. </p> 110 * 111 * @param command The {@link Command Command} to be added 112 * @param timeout The timeout (in seconds) 113 * @throws IllegalUseOfCommandException if the group has been started before or been given to 114 * another group or if the {@link Command Command} has been 115 * started before or been given to another group 116 * @throws IllegalArgumentException if command is null or timeout is negative 117 */ 118 public final synchronized void addSequential(Command command, double timeout) { 119 validate("Can not add new command to command group"); 120 if (command == null) { 121 throw new IllegalArgumentException("Given null command"); 122 } 123 if (timeout < 0) { 124 throw new IllegalArgumentException("Can not be given a negative timeout"); 125 } 126 127 command.setParent(this); 128 129 m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE, timeout)); 130 for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) { 131 requires((Subsystem) e.nextElement()); 132 } 133 } 134 135 /** 136 * Adds a new child {@link Command} to the group. The {@link Command} will be started after all 137 * the previously added {@link Command Commands}. 138 * 139 * <p> Instead of waiting for the child to finish, a {@link CommandGroup} will have it run at the 140 * same time as the subsequent {@link Command Commands}. The child will run until either it 141 * finishes, a new child with conflicting requirements is started, or the main sequence runs a 142 * {@link Command} with conflicting requirements. In the latter two cases, the child will be 143 * canceled even if it says it can't be interrupted. </p> 144 * 145 * <p> Note that any requirements the given {@link Command Command} has will be added to the 146 * group. For this reason, a {@link Command Command's} requirements can not be changed after being 147 * added to a group. </p> 148 * 149 * <p> It is recommended that this method be called in the constructor. </p> 150 * 151 * @param command The command to be added 152 * @throws IllegalUseOfCommandException if the group has been started before or been given to 153 * another command group 154 * @throws IllegalArgumentException if command is null 155 */ 156 public final synchronized void addParallel(Command command) { 157 requireNonNull(command, "Provided command was null"); 158 validate("Can not add new command to command group"); 159 160 command.setParent(this); 161 162 m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD)); 163 for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) { 164 requires((Subsystem) e.nextElement()); 165 } 166 } 167 168 /** 169 * Adds a new child {@link Command} to the group with the given timeout. The {@link Command} will 170 * be started after all the previously added {@link Command Commands}. 171 * 172 * <p> Once the {@link Command Command} is started, it will run until it finishes, is interrupted, 173 * or the time expires, whichever is sooner. Note that the given {@link Command Command} will have 174 * no knowledge that it is on a timer. </p> 175 * 176 * <p> Instead of waiting for the child to finish, a {@link CommandGroup} will have it run at the 177 * same time as the subsequent {@link Command Commands}. The child will run until either it 178 * finishes, the timeout expires, a new child with conflicting requirements is started, or the 179 * main sequence runs a {@link Command} with conflicting requirements. In the latter two cases, 180 * the child will be canceled even if it says it can't be interrupted. </p> 181 * 182 * <p> Note that any requirements the given {@link Command Command} has will be added to the 183 * group. For this reason, a {@link Command Command's} requirements can not be changed after being 184 * added to a group. </p> 185 * 186 * <p> It is recommended that this method be called in the constructor. </p> 187 * 188 * @param command The command to be added 189 * @param timeout The timeout (in seconds) 190 * @throws IllegalUseOfCommandException if the group has been started before or been given to 191 * another command group 192 * @throws IllegalArgumentException if command is null 193 */ 194 public final synchronized void addParallel(Command command, double timeout) { 195 requireNonNull(command, "Provided command was null"); 196 if (timeout < 0) { 197 throw new IllegalArgumentException("Can not be given a negative timeout"); 198 } 199 validate("Can not add new command to command group"); 200 201 command.setParent(this); 202 203 m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD, timeout)); 204 for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) { 205 requires((Subsystem) e.nextElement()); 206 } 207 } 208 209 @SuppressWarnings("MethodName") 210 void _initialize() { 211 m_currentCommandIndex = -1; 212 } 213 214 @SuppressWarnings("MethodName") 215 void _execute() { 216 Entry entry = null; 217 Command cmd = null; 218 boolean firstRun = false; 219 if (m_currentCommandIndex == -1) { 220 firstRun = true; 221 m_currentCommandIndex = 0; 222 } 223 224 while (m_currentCommandIndex < m_commands.size()) { 225 226 if (cmd != null) { 227 if (entry.isTimedOut()) { 228 cmd._cancel(); 229 } 230 if (cmd.run()) { 231 break; 232 } else { 233 cmd.removed(); 234 m_currentCommandIndex++; 235 firstRun = true; 236 cmd = null; 237 continue; 238 } 239 } 240 241 entry = m_commands.elementAt(m_currentCommandIndex); 242 cmd = null; 243 244 switch (entry.m_state) { 245 case Entry.IN_SEQUENCE: 246 cmd = entry.m_command; 247 if (firstRun) { 248 cmd.startRunning(); 249 cancelConflicts(cmd); 250 } 251 firstRun = false; 252 break; 253 case Entry.BRANCH_PEER: 254 m_currentCommandIndex++; 255 entry.m_command.start(); 256 break; 257 case Entry.BRANCH_CHILD: 258 m_currentCommandIndex++; 259 cancelConflicts(entry.m_command); 260 entry.m_command.startRunning(); 261 m_children.addElement(entry); 262 break; 263 default: 264 break; 265 } 266 } 267 268 // Run Children 269 for (int i = 0; i < m_children.size(); i++) { 270 entry = m_children.elementAt(i); 271 Command child = entry.m_command; 272 if (entry.isTimedOut()) { 273 child._cancel(); 274 } 275 if (!child.run()) { 276 child.removed(); 277 m_children.removeElementAt(i--); 278 } 279 } 280 } 281 282 @SuppressWarnings("MethodName") 283 void _end() { 284 // Theoretically, we don't have to check this, but we do if teams override 285 // the isFinished method 286 if (m_currentCommandIndex != -1 && m_currentCommandIndex < m_commands.size()) { 287 Command cmd = m_commands.elementAt(m_currentCommandIndex).m_command; 288 cmd._cancel(); 289 cmd.removed(); 290 } 291 292 Enumeration children = m_children.elements(); 293 while (children.hasMoreElements()) { 294 Command cmd = ((Entry) children.nextElement()).m_command; 295 cmd._cancel(); 296 cmd.removed(); 297 } 298 m_children.removeAllElements(); 299 } 300 301 @SuppressWarnings("MethodName") 302 void _interrupted() { 303 _end(); 304 } 305 306 /** 307 * Returns true if all the {@link Command Commands} in this group have been started and have 308 * finished. 309 * 310 * <p> Teams may override this method, although they should probably reference super.isFinished() 311 * if they do. </p> 312 * 313 * @return whether this {@link CommandGroup} is finished 314 */ 315 protected boolean isFinished() { 316 return m_currentCommandIndex >= m_commands.size() && m_children.isEmpty(); 317 } 318 319 // Can be overwritten by teams 320 protected void initialize() { 321 } 322 323 // Can be overwritten by teams 324 protected void execute() { 325 } 326 327 // Can be overwritten by teams 328 protected void end() { 329 } 330 331 // Can be overwritten by teams 332 protected void interrupted() { 333 } 334 335 /** 336 * Returns whether or not this group is interruptible. A command group will be uninterruptible if 337 * {@link CommandGroup#setInterruptible(boolean) setInterruptable(false)} was called or if it is 338 * currently running an uninterruptible command or child. 339 * 340 * @return whether or not this {@link CommandGroup} is interruptible. 341 */ 342 public synchronized boolean isInterruptible() { 343 if (!super.isInterruptible()) { 344 return false; 345 } 346 347 if (m_currentCommandIndex != -1 && m_currentCommandIndex < m_commands.size()) { 348 Command cmd = m_commands.elementAt(m_currentCommandIndex).m_command; 349 if (!cmd.isInterruptible()) { 350 return false; 351 } 352 } 353 354 for (int i = 0; i < m_children.size(); i++) { 355 if (!m_children.elementAt(i).m_command.isInterruptible()) { 356 return false; 357 } 358 } 359 360 return true; 361 } 362 363 private void cancelConflicts(Command command) { 364 for (int i = 0; i < m_children.size(); i++) { 365 Command child = m_children.elementAt(i).m_command; 366 367 Enumeration requirements = command.getRequirements(); 368 369 while (requirements.hasMoreElements()) { 370 Object requirement = requirements.nextElement(); 371 if (child.doesRequire((Subsystem) requirement)) { 372 child._cancel(); 373 child.removed(); 374 m_children.removeElementAt(i--); 375 break; 376 } 377 } 378 } 379 } 380 381 private static class Entry { 382 private static final int IN_SEQUENCE = 0; 383 private static final int BRANCH_PEER = 1; 384 private static final int BRANCH_CHILD = 2; 385 private final Command m_command; 386 private final int m_state; 387 private final double m_timeout; 388 389 Entry(Command command, int state) { 390 m_command = command; 391 m_state = state; 392 m_timeout = -1; 393 } 394 395 Entry(Command command, int state, double timeout) { 396 m_command = command; 397 m_state = state; 398 m_timeout = timeout; 399 } 400 401 boolean isTimedOut() { 402 if (m_timeout == -1) { 403 return false; 404 } else { 405 double time = m_command.timeSinceInitialized(); 406 return time != 0 && time >= m_timeout; 407 } 408 } 409 } 410}