Command Decorators¶
Every Command carries a set of decorator methods that wrap it in new behavior and return a new command. Decorators chain fluently, letting you build complex behavior without writing a single class:
driveCommand
.until(robot::atTarget) // stop early when at target
.andThen(intakeCommand.withTimeout(1)) // then intake for up to 1 s
.finallyDo(() -> drive.stop()); // always stop the drive at the end
Ending a command early¶
withTimeout(double seconds)¶
Interrupts the command if it hasn't finished within the given time.
until(BooleanSupplier condition)¶
Ends the command as soon as the condition becomes true (checked every cycle).
onlyWhile(BooleanSupplier condition)¶
The inverse of until — the command runs only while the condition stays true, and ends the moment it turns false. (A gamepad Trigger is a BooleanSupplier, so it can be passed directly.)
Conditional execution¶
unless(BooleanSupplier condition)¶
Skips the command entirely if the condition is true at the moment it starts.
Adding behavior around a command¶
beforeStarting(Runnable action)¶
Runs an action once before the command initializes.
finallyDo(Runnable action)¶
Runs an action once when the command ends, whether it finished or was interrupted. This is the right place to stop motors defensively.
andThen(Command... next) / andThen(Runnable action)¶
Runs commands in sequence. The Runnable overload wraps the action in an InstantCommand for you.
Repetition¶
repeatedly()¶
Restarts the command every time it finishes — it never ends on its own (interrupt it, or combine with until/withTimeout).
Running commands together¶
alongWith(Command... others)¶
Runs this command in parallel with the others; the group finishes when all commands have finished.
raceWith(Command... others)¶
Runs in parallel; the group finishes as soon as any command finishes (all others are interrupted).
deadlineWith(Command... others)¶
Runs in parallel; the group finishes when this command (the deadline) finishes — the others are interrupted at that point.
// Intake runs only for as long as the path takes:
Command cycle = followPath.deadlineWith(runIntake);
Decorator cheat sheet¶
| Decorator | Group semantics | Ends when |
|---|---|---|
withTimeout(t) |
wraps | inner finishes or t elapses |
until(cond) |
wraps | inner finishes or cond true |
onlyWhile(cond) |
wraps | inner finishes or cond false |
unless(cond) |
wraps | skipped entirely if cond true at start |
beforeStarting(r) |
wraps | inner finishes (runs r first) |
finallyDo(r) |
wraps | inner finishes (runs r at end, always) |
repeatedly() |
wraps | never (restarts on finish) |
andThen(...) |
sequential | last command finishes |
alongWith(...) |
parallel | all finish |
raceWith(...) |
parallel | first finishes |
deadlineWith(...) |
parallel | the deadline finishes |
Interruption semantics¶
Decorators preserve honest end(interrupted) signals for the wrapped command:
- If the inner command finishes naturally, it receives
end(false). - If a decorator cuts it short — a timeout elapses, an
until/onlyWhilecondition trips, it loses araceWith, or adeadlineWithdeadline completes first — the inner command receivesend(true), exactly as if the scheduler had canceled it. - A command skipped by
unlessis never initialized and never receivesend(...)at all.
This means cleanup logic keyed on the interrupted flag (stop motors, log an abort) behaves identically whether the command was canceled by the scheduler or by a decorator.
Under the hood¶
Each decorator returns a small wrapper class from com.vcs.valleylib.core.command.decorators:
TimeoutCommand, UntilCommand, OnlyWhileCommand, UnlessCommand, BeforeStartingCommand, FinallyCommand, RepeatCommand, SequentialCommandGroup, ParallelCommandGroup, RaceCommand, DeadlineCommand, and ConditionalCommand.
You can instantiate these directly (see Command Groups), but the fluent methods read better and are the recommended style. To build your own decorator, extend CommandWrapper.