Schedule a command without calling initialize()

I am trying to schedule multiple RamseteCommand instances in one class and so I would like to schedule them all at once. According to WPIlib, when a command is scheduled, it automatically calls the command’s initialize() method. Is there a way to do this without initializing each command? I would assume that if the scheduler is initializing each command before executing them, something might go wrong with the path following as the last command would be initialized before execution instead of the first command. This was the code I had in mind:

    @Override
    public void initialize() {
        current = 0;
        mDriveTrain.resetOdometry(mTrajectories.get(current).getTrajectory().getInitialPose());
        for (int i = 0; i < mRamseteCommands.size(); i++) {
            mRamseteCommands.get(i).schedule();
        }
    }

or would I have to do something like this:

    @Override
    public void initialize() {
        current = 0;
        mDriveTrain.resetOdometry(mTrajectories.get(current).getTrajectory().getInitialPose());
        mRamseteCommands.get(0).schedule();
    }

    @Override
    public void execute() {
        if (mRamseteCommands.get(current).isFinished()) {
            current += 1;
            mDriveTrain.resetOdometry(mTrajectories.get(current).getTrajectory().getInitialPose());
            mRamseteCommands.get(current).schedule();
        }

        if (current == mRamseteCommands.size()) {
            finished = true;
        }
    }

Are you thinking it calls initialize() when you instantiate the command? initialize() is called on the first loop where the command is running and wasn’t before.

Screenshot 2022-08-09 204432

This is where I got the information. Am I interpreting this wrong?

Read it over again and your interpretation seems correct. However I think what you’re looking for is a SequentialCommandGroup. This lets you run several commands in sequences or parallels (with the parallel() function) and seems to fit exactly with what you’re trying to do.

This is what we did (five ball for reference–util.getPathPlannerSwerveControllerCommand() is equivalent to a ramsete command).

If you assemble the commands into a SequentialCommandGroup and then schedule the group, each command (after the first) will not be initialized until the preceding command has ended.

So in a sequential command group when I instantiate each command, it’ll initialize and run first before the next one initializes?

Correct

so if mCommands is a SequentialCommandGroup, this code would function correctly?

    @Override
    public void initialize() {
        mCommands.schedule();
    }

    @Override
    public void execute() {
        if (mCommands.isFinished()) {
            finished = true;
        }
    }

I suggest you read the section on Command Groups:

1 Like

After reading, the only problem I see with a sequentialcommandgroup is that if the previous command doesn’t finish, the command group won’t proceed to the next command. I’d have to find a way to see if each command finishes where I want them to.

The sequential command group is pretty much equivalent to the second example on your original post (minus the reset odometry part). If you want more logic for isFinished in each ramsete command you could subclass it and override isFinished.

Logic for checking if a command is complete should be handled by the command itself-that’s the point of the isFinished function.

Commands also generally shouldn’t be manually scheduled from other commands- there’s almost always a better way to do what you’re trying to do.

3 Likes

For the scenario of running several commands in sequence/parallel then Sequential/ParallelCommandGroup should be used… but if you mean to never call .schedule() then I agree

1 Like

why is it bad to schedule commands within other commands?

It’s not that it won’t work, it’s just that it’s a bad pattern (an “anti-pattern”) because it’s working against the way things are intended to work.

There are four main ways that commands get scheduled:

  • As the autonomous command returned by getAutonomousCommand() and scheduled by autonomousInit(). link
  • As the default command for a subsystem, set by CommandScheduler.getInstance().setDefaultCommand. link
  • As the command to run when a button is pressed on the driver station. link
  • As part of a compound command, using command groups or other convenience features.

If you find yourself scheduling a command any other way, that’s a clue that you’re structuring your code against the intended paradigm, and you should find a better way to do it.

3 Likes

It sounds like you’re reimplementing a version of a sequential command group, and not in the most reliable way. I’d recommend using WPILib’s SequentialCommandGroup: it’s more tested, documented, and it’ll be easier for others to help if needed.

One important note: when using WPILib-provided command classes (especially the groups), do not override the lifecycle methods!! (initialize, execute, isFinished, end) – it causes issues that are difficult to debug, especially without knowledge about command internals. If you find that you need to override a lifecycle method, there’s probably a better way to structure things.
I recommend using inline commands, as opposed to writing old-style command classes – it’s much less verbose, and in my opinion more intuitive to structure.

Looking at code examples others here have linked is also a great way to learn.

Calling schedule manually is also somewhat of a code smell; see @bovlb’s comment. The Trigger API is expressive enough that it should cover almost any use case (and it’s getting enhanced for 2023).

Theoretically, there’s the encapsulation concern that commands shouldn’t know about other commands’ scheduling status and the like. Practically, it’s also a tricky setup with requirements, and even more practically – the scheduler loop assumes internal data structures are mutated only at specific points, and we’ve seen countless issues stemming from that assumption being defied (by team code calling schedule() from command lifecycle methods, for instance), with effects ranging from small wrong behaviors (commands not getting scheduled, etc) to complete crashes (thrown exceptions, infinite recursion, etc).

2 Likes

That makes sense! After reading the docs on inline commands, I have a couple of questions.

Can inline commands replace command classes that extend CommandBase in every case or are there cases where writing these classes would be appropriate?

When you say not to override the lifecycle methods in command classes, is that true for all command classes? The way I have previously written commands is by subclassing CommandBase and overriding these lifecycle methods.

Thanks so much for helping! I’m not too experienced with the WPIlib code structure.

1 Like

Command classes are useful for highly stateful commands. These shouldn’t be very common, but sometimes are necessary. Inline commands should be your “bread and butter.”

1 Like

so inline commands are used in the robot container when configuring the button bindings?

You can also write methods that return them, called “factories.” These let you write lengthy/complicated inline compositions in other places in code (such as the subsystem classes, or in a static utility class). It also lets you easily reuse commands defined via inline syntax (since the instances themselves cannot be reused).