Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

String tags for commands for easier command identification #6510

Open
LightspeedLazer opened this issue Apr 11, 2024 · 8 comments
Open

String tags for commands for easier command identification #6510

LightspeedLazer opened this issue Apr 11, 2024 · 8 comments

Comments

@LightspeedLazer
Copy link

LightspeedLazer commented Apr 11, 2024

Is your feature request related to a problem? Please describe.
When using SubsystemBase.getCurrentCommand there's no way of identifying the command without using the type or name of the command. This gets even worse if the command is part of a composition (like an autonomous command).

Describe the solution you'd like
Commands would have an internal list of tags and would have functions like

  • Command.addTags(String... tags)
  • Command.withTags(String... tags)
  • Command.hasTags(String... tags)

Compositions would simply inherit all tags of child commands.

In practice these tags would be static Strings inside of either a SubsystemBase or a constants class, as to reduce the amount of String literals that have to match each other.

Example Code:

public class Intake extends SubsystemBase {
    
    public static final String INTAKE_TAG = "Intake Intaking";
    public static final String EJECT_TAG = "Intake Ejecting";
    public static final String FEED_TAG = "Intake Feeding";

    public Command intake() {
        return startEnd(
            this::startIntake,
            this::stopIntake
        )
        .withTags(INTAKE_TAG)
        .withName("Intake");
    }

    public Command eject() {
        return startEnd(
            this::startEject,
            this::stopIntake
        )
        .withTags(EJECT_TAG)
        .withName("Eject");
    }

    public Command feed() {
        return startEnd(
            this::startFeed,
            this::stopIntake
        )
        .withTags(FEED_TAG)
        .withName("Feed");
    }
}
Leds.getInstance().setFeeding(intake.getCurrentCommand().hasTags(Intake.FEED_TAG)); // yes i know getCurrentCommand() can be null but this is just an example

Describe alternatives you've considered
Using the name of a command to identify the command is possible (and is something our team has done with copious amounts of proxies) but is not desirable as the command name is meant for other purposes.

Additional context
This sort of functionality would go well with #6511

@Starlight220
Copy link
Member

I feel like the use cases can be implemented using the name field, without needing to tack on another metadata field

@ArchdukeTim
Copy link
Contributor

Also this likely wouldn't fix the issue of compositions without the same issue being fixed for normal names

@LightspeedLazer
Copy link
Author

I feel like the use cases can be implemented using the name field, without needing to tack on another metadata field

Yes, this is true, however the issue comes when commands are composed. When commands are composed they lose their name (and thus the data stored in the name). Because tags would be inherited by the composing command, the data isn't lost.

Also this likely wouldn't fix the issue of compositions without the same issue being fixed for normal names

I not sure how you would fix the issue of compositions with normal names. Lets take the example of sequencing 3 commands:

Commands.sequence(command1, command2, command3);

What would the name of the composed command be? Currently it's just "SequencedCommandGroup", which doesn't provide any unique data. If the suggested name is "[command1.getName()], [command2.getName()], [command3.getName()]", this is far from readable (the main purpose of command names).
With tags, the name of the composed command could be anything (e.g. "Quick Auto", "Auto Drive To Amp", etc.), while still maintaining the unique data contained in the tags. The tags would also be able to be common across multiple commands (e.g. "Intake Deployed" could be common for the intake commands "Intake", and "Eject").

@PeterJohnson
Copy link
Member

Why can’t you just decorate using .withName()? Eg Commands.sequence(a,b,c).withName(“quick auto”)?

@LightspeedLazer
Copy link
Author

Why can’t you just decorate using .withName()? Eg Commands.sequence(a,b,c).withName(“quick auto”)?

You can, but it still has lost the data from the commands inside.

@LightspeedLazer
Copy link
Author

LightspeedLazer commented May 7, 2024

Here's a real case where these tags would be useful. In our (Team 686) code, we have a shooter subsystem (only controls the flywheels) and a kicker subsystem (rollers that push the note into our flywheels). Lets say that we want to implement an auto-shoot trigger that will schedule Kicker.kick() when the flywheels are at speed. However, our shooter is also responsible for placing the note in the amp and has a separate command for that case. When we want to amp we don't want this auto-shoot trigger to fire (because we might still be trying to align to the amp). If we have tags then the trigger is as simple as this:

new Trigger(shooter::atDesiredSpeed).and(() -> shooter.getCurrentCommand().hasTags(Shooter.AUTO_SHOOT_TAG)).onTrue(kicker.kick());

In our shooter commands, we would have only the commands that are attempting to fire at the speaker have the auto-shoot tag.
The benefit of over other ways is that if we compose the shooter command with other commands (e.g. shooter.autoAim().alongWith(pivot.autoAim(), drive.autoAim())) the composed command still retains the Shooter.AUTO_SHOOT_TAG data and the trigger still works (without having to remember to make sure the parallel command name contains some arbitrary string or to wrap shooter.autoAim() in a proxy).

@ArchdukeTim
Copy link
Contributor

I imagine other will say that basing triggers not off the state of the system but off the state of the command schedule is an anti-pattern.

Why not have

new Trigger(shooter::preppedForShot).onTrue(kicker.kick());

and preppedForShot handles checking for the right state

@LightspeedLazer
Copy link
Author

I imagine other will say that basing triggers not off the state of the system but off the state of the command schedule is an anti-pattern.

Is the command not the state of the system? From my point of view the command scheduler is a beefed-up state machine without the convenience of easy state checking.

Why not have ... and preppedForShot handles checking for the right state

How would preppedForShot do this without checking the name of the command nor checking toggle flags (that the commands have to remember to set and reset).

I have realized a potential issue with tags where a sequenced command will have the tags of commands that might not be currently running, which might cause undesired behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants