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 first run(). Set up state and issue starting commands here (for example, tell a follower to begin a path).
  • boolean run() — called every loop after init(). Return true when the task is finished, or false to 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 to addTask() 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
Note: ModularOpMode already owns a 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:

BuilderProducesBehavior
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.

TaskPurposeFinishes when…
SequentialTaskRun children in order. the last child finishes (immediately if there are no children).
ParallelTaskRun children at once. all children have finished.
RaceTaskRun children at once. any one child finishes (the rest stop being polled).
DeadlineTaskRun children until a deadline. the deadline child finishes, regardless of the others.
RepeatTaskRebuild & rerun a task forever. never on its own — run() always returns false; bound it with raceWith/withDeadline.
DeferredTaskBuild the inner task lazily at init(). the inner task (built from a Supplier<Task> at init) finishes.
InstantTaskRun one action, then finish. immediately — runs a Runnable in init(), run() returns true at once.
SleepTaskWait a fixed delay. the given millisecond delay has elapsed since init().
WaitUntilTaskWait for a condition. the supplied BooleanSupplier returns true.
BasicTaskBuild a task from lambdas. the supplied run BooleanSupplier returns true (init runs once first).
FollowPathTaskFollow a PedroPathing path. the follower is no longer busy (!follower.isBusy()).
HoldPointTaskCommand the follower to hold a pose. immediately, after issuing follower.hold(pose) in init().
NullTaskDo nothing (idle placeholder). immediately; this is what TaskMaster holds when idle.
Lazy vs. repeating: 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);
Pedro 3.0 API. 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.