Task System
A task is a small, composable unit of robot behavior: it is set up once, then polled every loop until it reports that it is done. Chain and nest tasks with fluent builders to describe an entire autonomous routine — or a single asynchronous action in TeleOp — declaratively.
The task model
Every action extends the abstract Task class, which has exactly two lifecycle
methods:
void init()— called once, before the firstrun(). Set up state and issue starting commands here (for example, tell a follower to begin a path).boolean run()— called every loop afterinit(). Returntruewhen the task is finished, orfalseto keep running.
A task never blocks. Instead of looping internally, it does a tiny slice of work each
run() and returns a "finished yet?" flag, so the OpMode loop stays responsive and can
run many things at once.
TaskMaster — running one task at a time
A TaskMaster drives a single active task to completion, one loop at a time. It
starts idle (holding a NullTask), and you feed it work with
addTask(Task):
- If the master is idle, the new task becomes active and is initialized on the next
update(). - If the master is busy, the new task is appended after the current one — the
master wraps them into a
SequentialTask, so calls toaddTask()queue up in order.
Call update() exactly once per loop. It initializes the active task on the first
call, runs one step, and returns to idle once the task finishes. getStatus() reports
whether the active task finished on the most recent update, and getTask() returns the
currently active task (a NullTask when idle).
TaskMaster master = new TaskMaster();
// every loop:
master.addTask(new SleepTask(500)); // queues; runs after whatever is already active
master.update(); // advance the active task one step
TaskMaster and drives it for you — it is initialized in start() and
update()d every loop(). In an OpMode you usually just call
addTask(...) and let the OpMode handle the rest.Composing tasks
Because run() is just "am I done yet?", tasks nest cleanly. The Task
base class exposes four fluent builders, each returning a new composite Task:
| Builder | Produces | Behavior |
|---|---|---|
andThen(Task... next) | SequentialTask |
Runs this task, then each of the given tasks one after another. |
alongWith(Task... others) | ParallelTask |
Runs this task in parallel with the others; finishes when all finish. |
raceWith(Task... others) | RaceTask |
Runs this task in parallel with the others; finishes when any one finishes. |
withDeadline(Task deadline) | DeadlineTask |
Runs this task until deadline finishes, then stops regardless of state. |
Task routine =
new InstantTask(() -> arm.raise())
.andThen(new SleepTask(300))
.alongWith(new BasicTask(intake::start, intake::hasSample))
.raceWith(new SleepTask(2000)); // bail out after 2s no matter what
master.addTask(routine);
The builders take varargs and return a Task, so you can keep chaining. Under the
hood andThen(a, b) is simply new SequentialTask(this, a, b) — you can
always build the composites directly if you prefer.
Task catalog
The "finishes when" column reflects each class's real run() implementation.
| Task | Purpose | Finishes when… |
|---|---|---|
SequentialTask | Run children in order. | the last child finishes (immediately if there are no children). |
ParallelTask | Run children at once. | all children have finished. |
RaceTask | Run children at once. | any one child finishes (the rest stop being polled). |
DeadlineTask | Run children until a deadline. | the deadline child finishes, regardless of the others. |
RepeatTask | Rebuild & rerun a task forever. | never on its own — run() always returns false; bound it with raceWith/withDeadline. |
DeferredTask | Build the inner task lazily at init(). |
the inner task (built from a Supplier<Task> at init) finishes. |
InstantTask | Run one action, then finish. | immediately — runs a Runnable in init(), run() returns true at once. |
SleepTask | Wait a fixed delay. | the given millisecond delay has elapsed since init(). |
WaitUntilTask | Wait for a condition. | the supplied BooleanSupplier returns true. |
BasicTask | Build a task from lambdas. | the supplied run BooleanSupplier returns true (init runs once first). |
FollowPathTask | Follow a PedroPathing path. | the follower is no longer busy (!follower.isBusy()). |
HoldPointTask | Command the follower to hold a pose. | immediately, after issuing follower.hold(pose) in init(). |
NullTask | Do nothing (idle placeholder). | immediately; this is what TaskMaster holds when idle. |
DeferredTask builds its inner task
once, at init() — handy when the task depends on state not known at
construction time (for example, an obelisk pattern from AprilTag detection). RepeatTask
rebuilds a fresh task from its supplier every time the previous one finishes, and never
stops on its own.PedroPathing tasks
Two tasks bridge to a PedroPathing Follower, letting you drop path following
straight into a task chain.
Pedro 3.0 FollowPathTask
Follows a com.pedropathing.paths.Path. In init() it calls
follower.follow(path); in run() it returns true once
follower.isBusy() is false. An optional third constructor argument is a
speed multiplier (a fraction of the robot's max speed, 1.0 = full speed):
new FollowPathTask(follower, path); // full speed
new FollowPathTask(follower, path, 0.6); // 60% of max speed
Pedro 3 dropped the per-follow speed argument of the old followPath() call. A speed
cap is now a path modifier — Foresight.config.maxPathSpeed, a fraction of the
robot's max speed — so when speed < 1.0 and the follower's algorithm is a
Foresight, the task applies the cap to a copy of the path via
path.with(config.maxPathSpeed.at(speed)). Only Foresight exposes this
knob; with other algorithms the path is followed at full speed.
Pedro 3.0 HoldPointTask
Commands the follower to actively hold a com.pedropathing.math.Pose by calling
follower.hold(pose) in init(), then finishes immediately. Chain it after
a path to lock the robot in place before the next action begins.
An autonomous chain
Path toBasket = /* build a com.pedropathing.paths.Path */;
Path park = /* build a com.pedropathing.paths.Path */;
Task auto =
new FollowPathTask(follower, toBasket)
.andThen(new InstantTask(() -> scorer.deposit()))
.andThen(new SleepTask(400))
.andThen(new FollowPathTask(follower, park, 0.75))
.andThen(new HoldPointTask(follower, park.endPose()));
master.addTask(auto);
Path and Pose come from
com.pedropathing.paths.Path / com.pedropathing.math.Pose, and the
follower is driven with follow() / hold() / isBusy(). This
is a breaking change from older PedroPathing versions — the pre-3.0
followPath(path, speed, holdEnd) signature and legacy pose types no longer apply.