Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,17 @@
"diffEditor.ignoreTrimWhitespace": false,
"java.format.settings.url": "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml",
"java.checkstyle.configuration": "${workspaceFolder}/.github/linters/checkstyle.xml",
"java.checkstyle.version": "9.3"
"java.checkstyle.version": "9.3",
"java.completion.favoriteStaticMembers": [
"com.team766.framework3.RulePersistence.*",
Comment thread
dejabot marked this conversation as resolved.
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*",
"org.mockito.Mockito.*",
"org.mockito.ArgumentMatchers.*",
"org.mockito.Answers.*"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ public final class FunctionalInstantProcedure extends InstantProcedure {
private final Runnable runnable;

public FunctionalInstantProcedure(Set<Mechanism<?>> reservations, Runnable runnable) {
super(runnable.toString(), reservations);
this(runnable.toString(), reservations, runnable);
}

public FunctionalInstantProcedure(
String name, Set<Mechanism<?>> reservations, Runnable runnable) {
super(name, reservations);
this.runnable = runnable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ public final class FunctionalProcedure extends Procedure {
private final Consumer<Context> runnable;

public FunctionalProcedure(Set<Mechanism<?>> reservations, Consumer<Context> runnable) {
super(runnable.toString(), reservations);
this(runnable.toString(), reservations, runnable);
}

public FunctionalProcedure(
String name, Set<Mechanism<?>> reservations, Consumer<Context> runnable) {
super(name, reservations);
this.runnable = runnable;
}

Expand Down
115 changes: 95 additions & 20 deletions src/main/java/com/team766/framework3/Rule.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
Expand All @@ -25,7 +26,7 @@
* public MyRules() {
* // add rule to spin up the shooter when the boxop presses the right trigger on the gamepad
* rules.add(Rule.create("spin up shooter", gamepad.getButton(InputConstants.XBOX_RT)).
* withNewlyTriggeringProcedure(() -> new ShooterSpin(shooter)));
* withOnTriggeringProcedure(ONCE_AND_HOLD, () -> new ShooterSpin(shooter)));
* ...
* }
* }
Expand All @@ -49,6 +50,14 @@ enum TriggerType {
FINISHED
}

/** Policy for canceling actions when the rule is in a given state. */
enum Cancellation {
/** Do not cancel any previous actions. */
DO_NOT_CANCEL,
/** Cancel the action previously scheduled when the rule was in the NEWLY state. */
CANCEL_NEWLY_ACTION,
}

/**
* Simple Builder for {@link Rule}s. Configure Rules via this Builder; these fields will be immutable
* in the rule the Builder constructs.
Expand All @@ -58,24 +67,79 @@ enum TriggerType {
public static class Builder {
private final String name;
private final BooleanSupplier predicate;
private Supplier<Procedure> newlyTriggeringProcedure;
private Supplier<Procedure> onTriggeringProcedure;
private Cancellation cancellationOnFinish = Cancellation.DO_NOT_CANCEL;
private Supplier<Procedure> finishedTriggeringProcedure;

private Builder(String name, BooleanSupplier predicate) {
this.name = name;
this.predicate = predicate;
}

private void applyRulePersistence(
RulePersistence rulePersistence, Supplier<Procedure> action) {
switch (rulePersistence) {
case ONCE -> {
this.onTriggeringProcedure = action;
this.cancellationOnFinish = Cancellation.DO_NOT_CANCEL;
}
case ONCE_AND_HOLD -> {
this.onTriggeringProcedure =
() -> {
final Procedure procedure = action.get();
return new FunctionalProcedure(
procedure.getName(),
procedure.reservations(),
context -> {
procedure.run(context);
context.waitFor(() -> false);
});
};
this.cancellationOnFinish = Cancellation.CANCEL_NEWLY_ACTION;
}
case REPEATEDLY -> {
this.onTriggeringProcedure =
() -> {
final Procedure procedure = action.get();
return new FunctionalProcedure(
procedure.getName(),
procedure.reservations(),
context -> {
Procedure currentProcedure = procedure;
while (true) {
context.runSync(currentProcedure);
context.yield();
currentProcedure = action.get();
}
});
};
this.cancellationOnFinish = Cancellation.CANCEL_NEWLY_ACTION;
}
}
}

/** Specify a creator for the Procedure that should be run when this rule starts triggering. */
public Builder withNewlyTriggeringProcedure(Supplier<Procedure> action) {
this.newlyTriggeringProcedure = action;
public Builder withOnTriggeringProcedure(
RulePersistence rulePersistence, Supplier<Procedure> action) {
applyRulePersistence(rulePersistence, action);
return this;
}

public Builder withNewlyTriggeringProcedure(
Set<Mechanism<?>> reservations, Runnable action) {
this.newlyTriggeringProcedure =
() -> new FunctionalInstantProcedure(reservations, action);
/** Specify a creator for the Procedure that should be run when this rule starts triggering. */
public Builder withOnTriggeringProcedure(
RulePersistence rulePersistence, Set<Mechanism<?>> reservations, Runnable action) {
applyRulePersistence(
rulePersistence, () -> new FunctionalInstantProcedure(reservations, action));
return this;
}

/** Specify a creator for the Procedure that should be run when this rule starts triggering. */
public Builder withOnTriggeringProcedure(
RulePersistence rulePersistence,
Set<Mechanism<?>> reservations,
Consumer<Context> action) {
applyRulePersistence(
rulePersistence, () -> new FunctionalProcedure(reservations, action));
return this;
}

Expand All @@ -85,6 +149,7 @@ public Builder withFinishedTriggeringProcedure(Supplier<Procedure> action) {
return this;
}

/** Specify a creator for the Procedure that should be run when this rule was triggering before and is no longer triggering. */
public Builder withFinishedTriggeringProcedure(
Set<Mechanism<?>> reservations, Runnable action) {
this.finishedTriggeringProcedure =
Expand All @@ -94,7 +159,12 @@ public Builder withFinishedTriggeringProcedure(

// called by {@link RuleEngine#addRule}.
/* package */ Rule build() {
return new Rule(name, predicate, newlyTriggeringProcedure, finishedTriggeringProcedure);
return new Rule(
name,
predicate,
onTriggeringProcedure,
cancellationOnFinish,
finishedTriggeringProcedure);
}
}

Expand All @@ -104,6 +174,7 @@ public Builder withFinishedTriggeringProcedure(
Maps.newEnumMap(TriggerType.class);
private final Map<TriggerType, Set<Mechanism<?>>> triggerReservations =
Maps.newEnumMap(TriggerType.class);
private final Cancellation cancellationOnFinish;

private TriggerType currentTriggerType = TriggerType.NONE;

Expand All @@ -114,32 +185,35 @@ public static Builder create(String name, BooleanSupplier predicate) {
private Rule(
String name,
BooleanSupplier predicate,
Supplier<Procedure> newlyTriggeringProcedure,
Supplier<Procedure> onTriggeringProcedure,
Cancellation cancellationOnFinish,
Supplier<Procedure> finishedTriggeringProcedure) {
if (predicate == null) {
throw new IllegalArgumentException("Rule predicate has not been set.");
}

if (newlyTriggeringProcedure == null) {
throw new IllegalArgumentException("Newly triggering Procedure is not defined.");
if (onTriggeringProcedure == null) {
throw new IllegalArgumentException("On-triggering Procedure is not defined.");
}

this.name = name;
this.predicate = predicate;
if (newlyTriggeringProcedure != null) {
triggerProcedures.put(TriggerType.NEWLY, newlyTriggeringProcedure);
if (onTriggeringProcedure != null) {
triggerProcedures.put(TriggerType.NEWLY, onTriggeringProcedure);
triggerReservations.put(
TriggerType.NEWLY, getReservationsForProcedure(newlyTriggeringProcedure));
TriggerType.NEWLY, getReservationsForProcedure(onTriggeringProcedure));
}

this.cancellationOnFinish = cancellationOnFinish;

if (finishedTriggeringProcedure != null) {
triggerProcedures.put(TriggerType.FINISHED, finishedTriggeringProcedure);
triggerReservations.put(
TriggerType.FINISHED, getReservationsForProcedure(finishedTriggeringProcedure));
}
}

private Set<Mechanism<?>> getReservationsForProcedure(Supplier<Procedure> supplier) {
private static Set<Mechanism<?>> getReservationsForProcedure(Supplier<Procedure> supplier) {
if (supplier != null) {
Procedure procedure = supplier.get();
if (procedure != null) {
Expand Down Expand Up @@ -182,10 +256,11 @@ public String getName() {
}

/* package */ Set<Mechanism<?>> getMechanismsToReserve() {
if (triggerReservations.containsKey(currentTriggerType)) {
return triggerReservations.get(currentTriggerType);
}
return Collections.emptySet();
return triggerReservations.getOrDefault(currentTriggerType, Collections.emptySet());
}

/* package */ Cancellation getCancellationOnFinish() {
return cancellationOnFinish;
}

/* package */ Procedure getProcedureToRun() {
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/com/team766/framework3/RuleEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public final void run() {
rule.evaluate();

// see if the rule is triggering
Rule.TriggerType triggerType = rule.getCurrentTriggerType();
final Rule.TriggerType triggerType = rule.getCurrentTriggerType();
if (triggerType != Rule.TriggerType.NONE) {
log(Severity.INFO, "Rule " + rule.getName() + " triggering: " + triggerType);

Expand Down Expand Up @@ -148,6 +148,17 @@ public final void run() {
}

// we're good to proceed

if (triggerType == Rule.TriggerType.FINISHED
&& rule.getCancellationOnFinish()
== Rule.Cancellation.CANCEL_NEWLY_ACTION) {
var newlyCommand =
ruleMap.inverse().get(new RuleAction(rule, Rule.TriggerType.NEWLY));
if (newlyCommand != null) {
newlyCommand.cancel();
}
}

Procedure procedure = rule.getProcedureToRun();
if (procedure == null) {
continue;
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/team766/framework3/RulePersistence.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.team766.framework3;

/**
* Policies for how to handle a Rule's action when the action completes or the Rule stops triggering.
*/
public enum RulePersistence {
/**
* When the action completes, don't do anything. Any Mechanism reservations that the action held
* are released. Also, the action may continue running after the Rule stops triggering.
*/
ONCE,
/**
* When the action completes, don't do anything but retain the Mechanism reservations that the
* action held until the Rule stops triggering. If the Rule stops triggering before the action
* has completed, then the action will be terminated.
*/
ONCE_AND_HOLD,
/**
* When the action completes, start executing the action again, until the Rule stops triggering.
* The action will be terminated when the Rule stops triggering.
*/
REPEATEDLY,
}
Loading