Ivy is released! Learn more
Pedro Pathing LogoPedro Pathing

Class API

The second way to create commands is by implementing the Command interface directly as a Java class. For most commands, the Command Builder is the better choice. If you need reusability or parameters, a static method that returns a builder command (as described on the previous page) is usually enough.

The Class API exists for the rare cases where your command truly needs internal mutable state that persists across its lifecycle. For example, a command that tracks how many times it has been run, or one that accumulates sensor readings over time. If your command doesn't need that, use the Command Builder API.

Arm Command Class

To create a command class, implement the Command interface and provide all of its required methods:

import com.pedropathing.ivy.Command;
import com.pedropathing.ivy.behaviors.*;

import java.util.Set;

public class RaiseArm implements Command {
    private final DcMotor armMotor;
    private final PIDController pid;
    private final double target;

    public RaiseArm(DcMotor armMotor, PIDController pid, double target) {
        this.armMotor = armMotor;
        this.pid = pid;
        this.target = target;
    }

    @Override
    public void start() {
        pid.setTarget(target);
    }

    @Override
    public void execute() {
        armMotor.setPower(pid.calculate(armMotor.getCurrentPosition()));
    }

    @Override
    public boolean done() {
        return Math.abs(target - armMotor.getCurrentPosition()) < 10;
    }

    @Override
    public void end(EndCondition endCondition) {
        armMotor.setPower(0);
    }

    @Override
    public Set<Object> requirements() {
        return Set.of(armMotor);
    }

    @Override
    public int priority() {
        return 0;
    }

    @Override
    public InterruptedBehavior interruptedBehavior() {
        return InterruptedBehavior.END;
    }

    @Override
    public BlockedBehavior blockedBehavior() {
        return BlockedBehavior.CANCEL;
    }

    @Override
    public ConflictBehavior conflictBehavior() {
        return ConflictBehavior.OVERRIDE;
    }
}

You can then create instances of this class:

Command raise = new RaiseArm(armMotor, pid, RAISED_POSITION);

Compare this with the equivalent Command Builder version:

Command raise = Command.build()
    .setStart(() -> pid.setTarget(RAISED_POSITION))
    .setExecute(() -> armMotor.setPower(pid.calculate(armMotor.getCurrentPosition())))
    .setDone(() -> Math.abs(RAISED_POSITION - armMotor.getCurrentPosition()) < 10)
    .setEnd(endCondition -> armMotor.setPower(0))
    .requiring(armMotor);

Both create the same command. The class version is more verbose but gives you a named type with internal state. Note that you don't need the class version just for reusability or parameters. As described on the Command Builder page, a static method that returns a builder command achieves the same thing with less boilerplate and without introducing mutable state.

Defaults You Must Provide

When you use the Command Builder, it fills in sensible defaults for everything you don't set:

MethodDefault
requirements()empty (no requirements)
priority()0
interruptedBehavior()InterruptedBehavior.END
blockedBehavior()BlockedBehavior.CANCEL
conflictBehavior()ConflictBehavior.OVERRIDE
start()does nothing
execute()does nothing
done()returns false (runs forever)
end(endCondition)does nothing

When implementing the Command interface directly, you must provide all of these yourself. If you want the same defaults as the builder, use the values in the table above.

Why Stateless Commands Are Preferred

Commands built with the Command Builder are stateless. They don't hold onto mutable fields between runs, which means:

  • There's no risk of leftover state from a previous run leaking into the next one.
  • They are safe to reuse, compose, and schedule multiple times without worrying about shared mutable state.
  • They are easier to debug, since the command's behavior is fully determined by the lambdas you pass in and the external objects they reference.

Class-based commands, by contrast, can hold mutable fields that change as the command runs. This makes them harder to reuse safely. If you schedule the same instance twice, the second run might see state left over from the first. In general, if you use the Class API, you will want to create a new command instance each time you need to schedule that command.

When to Use Each API

Use the Command Builder for the vast majority of commands. If you need reusability or parameters, wrap the builder in a static method (as described on the Command Builder page).

Use the Class API only when your command truly needs internal mutable state that persists across its lifecycle, like tracking a running total or accumulating sensor data over time.

Last updated on