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

[cmd] Allow specifying initial condition of Trigger bindings #7425

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@
* <p>This class is provided by the NewCommands VendorDep
*/
public class Trigger implements BooleanSupplier {
/**
* Enum specifying the initial state to use for a binding. This impacts whether or not the binding
* will be triggered immediately.
*/
public enum InitialState {
/**
* Indicates the binding should use false as the initial value. This causes a rising edge at the
* start if and only if the condition starts true.
*/
kFalse,
/**
* Indicates the binding should use true as the initial value. This causes a falling edge at the
* start if and only if the condition starts false.
*/
kTrue,
/**
* Indicates the binding should use the trigger's condition as the initial value. This never
* causes an edge at the start. This is the default behavior.
*/
kCondition,
/**
* Indicates the binding should use the negated trigger's condition as the initial value. This
* always causes an edge at the start. Rising or falling depends on if the condition starts true
* or false, respectively.
*/
kNegCondition;
}

/** Functional interface for the body of a trigger binding. */
@FunctionalInterface
private interface BindingBody {
Expand Down Expand Up @@ -61,15 +89,30 @@ public Trigger(BooleanSupplier condition) {
this(CommandScheduler.getInstance().getDefaultButtonLoop(), condition);
}

/**
* Gets the initial state for a binding based on an initial state policy.
*
* @param initialState Initial state policy.
* @return The initial state to use.
*/
private boolean getInitialState(InitialState initialState) {
return switch (initialState) {
case kFalse -> false;
case kTrue -> true;
case kCondition -> m_condition.getAsBoolean();
case kNegCondition -> !m_condition.getAsBoolean();
};
}

/**
* Adds a binding to the EventLoop.
*
* @param body The body of the binding to add.
*/
private void addBinding(BindingBody body) {
private void addBinding(BindingBody body, InitialState initialState) {
m_loop.bind(
new Runnable() {
private boolean m_previous = m_condition.getAsBoolean();
private boolean m_previous = getInitialState(initialState);

@Override
public void run() {
Expand All @@ -83,59 +126,97 @@ public void run() {
}

/**
* Starts the command when the condition changes.
* Starts the command when the condition changes. The command is never started immediately.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger onChange(Command command) {
return onChange(command, InitialState.kCondition);
}

/**
* Starts the command when the condition changes.
*
* @param command the command to start
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger onChange(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "onChange");
addBinding(
(previous, current) -> {
if (previous != current) {
command.schedule();
}
});
},
initialState);
return this;
}

/**
* Starts the given command whenever the condition changes from `false` to `true`.
* Starts the given command whenever the condition changes from `false` to `true`. The command is
* never started immediately.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger onTrue(Command command) {
return onTrue(command, InitialState.kCondition);
}

/**
* Starts the given command whenever the condition changes from `false` to `true`.
*
* @param command the command to start
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger onTrue(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "onTrue");
addBinding(
(previous, current) -> {
if (!previous && current) {
command.schedule();
}
});
},
initialState);
return this;
}

/**
* Starts the given command whenever the condition changes from `true` to `false`.
* Starts the given command whenever the condition changes from `true` to `false`. The command is
* never started immediately.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger onFalse(Command command) {
return onFalse(command, InitialState.kCondition);
}

/**
* Starts the given command whenever the condition changes from `true` to `false`.
*
* @param command the command to start
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger onFalse(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "onFalse");
addBinding(
(previous, current) -> {
if (previous && !current) {
command.schedule();
}
});
},
initialState);
return this;
}

/**
* Starts the given command when the condition changes to `true` and cancels it when the condition
* changes to `false`.
* changes to `false`. The command is never started immediately.
*
* <p>Doesn't re-start the command if it ends while the condition is still `true`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
Expand All @@ -144,6 +225,21 @@ public Trigger onFalse(Command command) {
* @return this trigger, so calls can be chained
*/
public Trigger whileTrue(Command command) {
return whileTrue(command, InitialState.kCondition);
}

/**
* Starts the given command when the condition changes to `true` and cancels it when the condition
* changes to `false`.
*
* <p>Doesn't re-start the command if it ends while the condition is still `true`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
*
* @param command the command to start
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger whileTrue(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "whileTrue");
addBinding(
(previous, current) -> {
Expand All @@ -152,13 +248,14 @@ public Trigger whileTrue(Command command) {
} else if (previous && !current) {
command.cancel();
}
});
},
initialState);
return this;
}

/**
* Starts the given command when the condition changes to `false` and cancels it when the
* condition changes to `true`.
* condition changes to `true`. The command is never started immediately.
*
* <p>Doesn't re-start the command if it ends while the condition is still `false`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
Expand All @@ -167,6 +264,21 @@ public Trigger whileTrue(Command command) {
* @return this trigger, so calls can be chained
*/
public Trigger whileFalse(Command command) {
return whileFalse(command, InitialState.kCondition);
}

/**
* Starts the given command when the condition changes to `false` and cancels it when the
* condition changes to `true`.
*
* <p>Doesn't re-start the command if it ends while the condition is still `false`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
*
* @param command the command to start
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger whileFalse(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "whileFalse");
addBinding(
(previous, current) -> {
Expand All @@ -175,17 +287,30 @@ public Trigger whileFalse(Command command) {
} else if (!previous && current) {
command.cancel();
}
});
},
initialState);
return this;
}

/**
* Toggles a command when the condition changes from `false` to `true`.
* Toggles a command when the condition changes from `false` to `true`. The command is never
* toggled immediately.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnTrue(Command command) {
return toggleOnTrue(command, InitialState.kCondition);
}

/**
* Toggles a command when the condition changes from `false` to `true`.
*
* @param command the command to toggle
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnTrue(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "toggleOnTrue");
addBinding(
(previous, current) -> {
Expand All @@ -196,17 +321,30 @@ public Trigger toggleOnTrue(Command command) {
command.schedule();
}
}
});
},
initialState);
return this;
}

/**
* Toggles a command when the condition changes from `true` to `false`.
* Toggles a command when the condition changes from `true` to `false`. The command is never
* toggled immediately.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnFalse(Command command) {
return toggleOnFalse(command, InitialState.kCondition);
}

/**
* Toggles a command when the condition changes from `true` to `false`.
*
* @param command the command to toggle
* @param initialState the initial state to use, kCondition (no initial edge) by default
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnFalse(Command command, InitialState initialState) {
requireNonNullParam(command, "command", "toggleOnFalse");
addBinding(
(previous, current) -> {
Expand All @@ -217,7 +355,8 @@ public Trigger toggleOnFalse(Command command) {
command.schedule();
}
}
});
},
initialState);
return this;
}

Expand Down
Loading
Loading