Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
Rule action persistence policies
Browse files Browse the repository at this point in the history
  • Loading branch information
rcahoon committed Dec 12, 2024
1 parent e7abdd1 commit 6f6c750
Show file tree
Hide file tree
Showing 8 changed files with 418 additions and 40 deletions.
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.*",
"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
111 changes: 95 additions & 16 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,8 @@ public Builder withFinishedTriggeringProcedure(
Maps.newEnumMap(TriggerType.class);
private final Map<TriggerType, Set<Mechanism<?>>> triggerReservations =
Maps.newEnumMap(TriggerType.class);
private final Map<TriggerType, Cancellation> triggerCancellation =
Maps.newEnumMap(TriggerType.class);

private TriggerType currentTriggerType = TriggerType.NONE;

Expand All @@ -114,32 +186,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));
}

triggerCancellation.put(TriggerType.FINISHED, 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 @@ -184,6 +259,10 @@ public String getName() {
return Collections.emptySet();
}

/* package */ Cancellation getCancellation() {
return triggerCancellation.get(currentTriggerType);
}

/* package */ Procedure getProcedureToRun() {
if (currentTriggerType != TriggerType.NONE) {
if (triggerProcedures.containsKey(currentTriggerType)) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/team766/framework3/RuleEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ public final void run() {
}

// we're good to proceed

if (rule.getCancellation() == 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

0 comments on commit 6f6c750

Please sign in to comment.