001// Copyright (c) Choreo contributors
002
003package choreo.auto;
004
005import choreo.Choreo;
006import choreo.Choreo.TrajectoryCache;
007import choreo.Choreo.TrajectoryLogger;
008import choreo.trajectory.SwerveSample;
009import choreo.trajectory.Trajectory;
010import choreo.trajectory.TrajectorySample;
011import edu.wpi.first.math.geometry.Pose2d;
012import edu.wpi.first.wpilibj.DriverStation;
013import edu.wpi.first.wpilibj.RobotBase;
014import edu.wpi.first.wpilibj.event.EventLoop;
015import edu.wpi.first.wpilibj2.command.Command;
016import edu.wpi.first.wpilibj2.command.Commands;
017import edu.wpi.first.wpilibj2.command.Subsystem;
018import edu.wpi.first.wpilibj2.command.button.Trigger;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Optional;
022import java.util.function.BiConsumer;
023import java.util.function.BooleanSupplier;
024import java.util.function.Supplier;
025
026/**
027 * A factory used to create autonomous routines.
028 *
029 * <p>Here is an example of how to use this class to create an auto routine:
030 *
031 * <h2>Example using <code>Trigger</code>s</h2>
032 *
033 * <pre><code>
034 * public AutoRoutine shootThenMove(AutoFactory factory) {
035 *   // Create a new auto routine to return
036 *   var routine = factory.newRoutine();
037 *
038 *   // Create a trajectory that moves the robot 2 meters
039 *   AutoTrajectory trajectory = factory.trajectory("move2meters", routine);
040 *
041 *   // Will automatically run the shoot command when the auto routine is first polled
042 *   routine.enabled().onTrue(shooter.shoot());
043 *
044 *   // Gets a trigger from the shooter to if the shooter has a note, and will run the trajectory
045 *   // command when the shooter does not have a note
046 *   routine.enabled().and(shooter.hasNote()).onFalse(trajectory.cmd());
047 *
048 *   return routine;
049 * }
050 * </code></pre>
051 *
052 * <h2>Example using <code>CommandGroup</code>s</h2>
053 *
054 * <pre><code>
055 * public Command shootThenMove(AutoFactory factory) {
056 *   // Create a trajectory that moves the robot 2 meters
057 *   Command trajectory = factory.trajectoryCommand("move2meters");
058 *
059 *   return shooter.shoot()
060 *      .andThen(trajectory)
061 *      .withName("ShootThenMove");
062 * }
063 * </code></pre>
064 */
065public class AutoFactory {
066  static final AutoRoutine VOID_ROUTINE =
067      new AutoRoutine("VOID-ROUTINE") {
068        private final EventLoop loop = new EventLoop();
069
070        @Override
071        public Command cmd() {
072          return Commands.none().withName("VoidAutoRoutine");
073        }
074
075        @Override
076        public Command cmd(BooleanSupplier _finishCondition) {
077          return cmd();
078        }
079
080        @Override
081        public void poll() {}
082
083        @Override
084        public void reset() {}
085
086        @Override
087        public Trigger running() {
088          return new Trigger(loop, () -> false);
089        }
090      };
091
092  /** A class used to bind commands to events in all trajectories created by this factory. */
093  public static class AutoBindings {
094    private HashMap<String, Command> bindings = new HashMap<>();
095
096    /** Default constructor. */
097    public AutoBindings() {}
098
099    /**
100     * Binds a command to an event in all trajectories created by the factory using this bindings.
101     *
102     * @param name The name of the event to bind the command to.
103     * @param cmd The command to bind to the event.
104     * @return The bindings object for chaining.
105     */
106    public AutoBindings bind(String name, Command cmd) {
107      bindings.put(name, cmd);
108      return this;
109    }
110
111    private void merge(AutoBindings other) {
112      if (other == null) {
113        return;
114      }
115      bindings.putAll(other.bindings);
116    }
117
118    /**
119     * Gets the bindings map.
120     *
121     * @return The bindings map.
122     */
123    HashMap<String, Command> getBindings() {
124      return bindings;
125    }
126  }
127
128  private final TrajectoryCache trajectoryCache = new TrajectoryCache();
129  private final Supplier<Pose2d> poseSupplier;
130  private final BiConsumer<Pose2d, ? extends TrajectorySample<?>> controller;
131  private final BooleanSupplier mirrorTrajectory;
132  private final Subsystem driveSubsystem;
133  private final AutoBindings bindings = new AutoBindings();
134  private final Optional<TrajectoryLogger<? extends TrajectorySample<?>>> trajectoryLogger;
135
136  /**
137   * Its recommended to use the {@link Choreo#createAutoFactory} to create a new instance of this
138   * class.
139   *
140   * @param <SampleType> {@link Choreo#createAutoFactory}
141   * @param poseSupplier {@link Choreo#createAutoFactory}
142   * @param controller {@link Choreo#createAutoFactory}
143   * @param mirrorTrajectory {@link Choreo#createAutoFactory}
144   * @param driveSubsystem {@link Choreo#createAutoFactory}
145   * @param bindings {@link Choreo#createAutoFactory}
146   * @param trajectoryLogger {@link Choreo#createAutoFactory}
147   */
148  public <SampleType extends TrajectorySample<SampleType>> AutoFactory(
149      Supplier<Pose2d> poseSupplier,
150      BiConsumer<Pose2d, SampleType> controller,
151      BooleanSupplier mirrorTrajectory,
152      Subsystem driveSubsystem,
153      AutoBindings bindings,
154      Optional<TrajectoryLogger<SampleType>> trajectoryLogger) {
155    this.poseSupplier = poseSupplier;
156    this.controller = controller;
157    this.mirrorTrajectory = mirrorTrajectory;
158    this.driveSubsystem = driveSubsystem;
159    this.bindings.merge(bindings);
160    this.trajectoryLogger =
161        trajectoryLogger.map(logger -> (TrajectoryLogger<? extends TrajectorySample<?>>) logger);
162  }
163
164  /**
165   * Creates a new {@link AutoRoutine}.
166   *
167   * @param name The name of the {@link AutoRoutine}.
168   * @return A new {@link AutoRoutine}.
169   * @see AutoRoutine
170   * @see #voidRoutine
171   */
172  public AutoRoutine newRoutine(String name) {
173    // Clear cache in simulation to allow a form of "hot-reloading" trajectories
174    if (RobotBase.isSimulation()) {
175      clearCache();
176    }
177
178    return new AutoRoutine(name);
179  }
180
181  /**
182   * An {@link AutoRoutine} that cannot have any side-effects, it stores no state and does nothing
183   * when polled.
184   *
185   * @return A void {@link AutoRoutine}.
186   * @see #newRoutine
187   */
188  public AutoRoutine voidRoutine() {
189    return VOID_ROUTINE;
190  }
191
192  /**
193   * Creates a new auto trajectory to be used in an auto routine.
194   *
195   * @param trajectoryName The name of the trajectory to use.
196   * @param routine The {@link AutoRoutine} to register this trajectory under.
197   * @return A new auto trajectory.
198   */
199  public AutoTrajectory trajectory(String trajectoryName, AutoRoutine routine) {
200    Optional<? extends Trajectory<?>> optTrajectory =
201        trajectoryCache.loadTrajectory(trajectoryName);
202    Trajectory<?> trajectory;
203    if (optTrajectory.isPresent()) {
204      trajectory = optTrajectory.get();
205    } else {
206      DriverStation.reportError("Could not load trajectory: " + trajectoryName, false);
207      trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of());
208    }
209    return trajectory(trajectory, routine);
210  }
211
212  /**
213   * Creates a new auto trajectory to be used in an auto routine.
214   *
215   * @param trajectoryName The name of the trajectory to use.
216   * @param splitIndex The index of the split trajectory to use.
217   * @param routine The {@link AutoRoutine} to register this trajectory under.
218   * @return A new auto trajectory.
219   */
220  public AutoTrajectory trajectory(
221      String trajectoryName, final int splitIndex, AutoRoutine routine) {
222    Optional<? extends Trajectory<?>> optTrajectory =
223        trajectoryCache.loadTrajectory(trajectoryName, splitIndex);
224    Trajectory<?> trajectory;
225    if (optTrajectory.isPresent()) {
226      trajectory = optTrajectory.get();
227    } else {
228      DriverStation.reportError("Could not load trajectory: " + trajectoryName, false);
229      trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of());
230    }
231    return trajectory(trajectory, routine);
232  }
233
234  /**
235   * Creates a new auto trajectory to be used in an auto routine.
236   *
237   * @param <SampleType> The type of the trajectory samples.
238   * @param trajectory The trajectory to use.
239   * @param routine The {@link AutoRoutine} to register this trajectory under.
240   * @return A new auto trajectory.
241   */
242  @SuppressWarnings("unchecked")
243  public <SampleType extends TrajectorySample<SampleType>> AutoTrajectory trajectory(
244      Trajectory<SampleType> trajectory, AutoRoutine routine) {
245    // type solidify everything
246    final Trajectory<SampleType> solidTrajectory = trajectory;
247    final BiConsumer<Pose2d, SampleType> solidController =
248        (BiConsumer<Pose2d, SampleType>) this.controller;
249    final Optional<TrajectoryLogger<SampleType>> solidLogger =
250        this.trajectoryLogger.map(logger -> (TrajectoryLogger<SampleType>) logger);
251    return new AutoTrajectory(
252        trajectory.name(),
253        solidTrajectory,
254        poseSupplier,
255        solidController,
256        mirrorTrajectory,
257        solidLogger,
258        driveSubsystem,
259        routine,
260        bindings);
261  }
262
263  /**
264   * Creates a new auto trajectory command to be used in an auto routine.
265   *
266   * <p><b>Important </b>
267   *
268   * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same
269   * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need
270   * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link
271   * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link
272   * AutoBindings} passed into the factory constructor.
273   *
274   * @param trajectoryName The name of the trajectory to use.
275   * @return A new auto trajectory.
276   */
277  public Command trajectoryCommand(String trajectoryName) {
278    return trajectory(trajectoryName, VOID_ROUTINE).cmd();
279  }
280
281  /**
282   * Creates a new auto trajectory command to be used in an auto routine.
283   *
284   * <p><b>Important </b>
285   *
286   * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same
287   * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need
288   * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link
289   * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link
290   * AutoBindings} passed into the factory constructor.
291   *
292   * @param trajectoryName The name of the trajectory to use.
293   * @param splitIndex The index of the split trajectory to use.
294   * @return A new auto trajectory.
295   */
296  public Command trajectoryCommand(String trajectoryName, final int splitIndex) {
297    return trajectory(trajectoryName, splitIndex, VOID_ROUTINE).cmd();
298  }
299
300  /**
301   * Creates a new auto trajectory command to be used in an auto routine.
302   *
303   * <p><b>Important </b>
304   *
305   * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same
306   * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need
307   * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link
308   * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link
309   * AutoBindings} passed into the factory constructor.
310   *
311   * @param <SampleType> The type of the trajectory samples.
312   * @param trajectory The trajectory to use.
313   * @return A new auto trajectory.
314   */
315  public <SampleType extends TrajectorySample<SampleType>> Command trajectoryCommand(
316      Trajectory<SampleType> trajectory) {
317    return trajectory(trajectory, VOID_ROUTINE).cmd();
318  }
319
320  /**
321   * Creates an {@link AutoRoutine} with the name of the command. The command is the bound to the
322   * routine's enabled trigger. This is useful for adding a {@link Command} composition based auto
323   * to the {@link choreo.auto.AutoChooser}.
324   *
325   * @param cmd The command to bind to the routine.
326   * @return A new auto routine.
327   */
328  public AutoRoutine commandAsAutoRoutine(Command cmd) {
329    AutoRoutine routine = newRoutine(cmd.getName());
330    routine.running().onTrue(cmd);
331    return routine;
332  }
333
334  /**
335   * Binds a command to an event in all trajectories created after this point.
336   *
337   * @param name The name of the trajectory to bind the command to.
338   * @param cmd The command to bind to the trajectory.
339   */
340  public void bind(String name, Command cmd) {
341    bindings.bind(name, cmd);
342  }
343
344  /**
345   * The {@link AutoFactory} caches trajectories with a {@link TrajectoryCache} to avoid reloading
346   * the same trajectory multiple times. This can have the side effect of keeping a single copy of
347   * every trajectory ever loaded in memory aslong as the factory is loaded. This method clears the
348   * cache of all trajectories.
349   *
350   * <p><b>Usage Note:</b>
351   *
352   * <p>Never clearing the cache is unlikely to have an impact on the robots performance on a rio 2
353   */
354  public void clearCache() {
355    trajectoryCache.clear();
356  }
357}