diff --git a/src/main/java/com/team766/framework3/FunctionalInstantProcedure.java b/src/main/java/com/team766/framework3/FunctionalInstantProcedure.java index abfecdef1..c81624685 100644 --- a/src/main/java/com/team766/framework3/FunctionalInstantProcedure.java +++ b/src/main/java/com/team766/framework3/FunctionalInstantProcedure.java @@ -6,7 +6,11 @@ public final class FunctionalInstantProcedure extends InstantProcedure { private final Runnable runnable; public FunctionalInstantProcedure(Set reservations, Runnable runnable) { - super(runnable.toString(), reservations); + this(runnable.toString(), reservations, runnable); + } + + public FunctionalInstantProcedure(String name, Set reservations, Runnable runnable) { + super(name, reservations); this.runnable = runnable; } diff --git a/src/main/java/com/team766/framework3/FunctionalProcedure.java b/src/main/java/com/team766/framework3/FunctionalProcedure.java index 311bf9159..2dd283a3a 100644 --- a/src/main/java/com/team766/framework3/FunctionalProcedure.java +++ b/src/main/java/com/team766/framework3/FunctionalProcedure.java @@ -7,7 +7,12 @@ public final class FunctionalProcedure extends Procedure { private final Consumer runnable; public FunctionalProcedure(Set reservations, Consumer runnable) { - super(runnable.toString(), reservations); + this(runnable.toString(), reservations, runnable); + } + + public FunctionalProcedure( + String name, Set reservations, Consumer runnable) { + super(name, reservations); this.runnable = runnable; } diff --git a/src/main/java/com/team766/framework3/Rule.java b/src/main/java/com/team766/framework3/Rule.java index 99d09f4ad..c2bff6f9b 100644 --- a/src/main/java/com/team766/framework3/Rule.java +++ b/src/main/java/com/team766/framework3/Rule.java @@ -49,26 +49,82 @@ 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, + } + private final String name; private final BooleanSupplier predicate; private final Map> triggerProcedures = Maps.newEnumMap(TriggerType.class); private final Map> triggerReservations = Maps.newEnumMap(TriggerType.class); + private final Cancellation cancellationOnFinish; private TriggerType currentTriggerType = TriggerType.NONE; private boolean sealed = false; /* package */ Rule( - String name, BooleanSupplier predicate, Supplier newlyTriggeringProcedure) { + String name, + BooleanSupplier predicate, + RulePersistence rulePersistence, + Supplier onTriggeringProcedure) { 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."); } + final Supplier newlyTriggeringProcedure = + switch (rulePersistence) { + case ONCE -> { + this.cancellationOnFinish = Cancellation.DO_NOT_CANCEL; + yield onTriggeringProcedure; + } + case ONCE_AND_HOLD -> { + this.cancellationOnFinish = Cancellation.CANCEL_NEWLY_ACTION; + yield () -> { + final Procedure procedure = onTriggeringProcedure.get(); + if (procedure == null) { + return null; + } + return new FunctionalProcedure( + procedure.getName(), + procedure.reservations(), + context -> { + procedure.run(context); + context.waitFor(() -> false); + }); + }; + } + case REPEATEDLY -> { + this.cancellationOnFinish = Cancellation.CANCEL_NEWLY_ACTION; + yield () -> { + final Procedure procedure = onTriggeringProcedure.get(); + if (procedure == null) { + return null; + } + return new FunctionalProcedure( + procedure.getName(), + procedure.reservations(), + context -> { + Procedure currentProcedure = procedure; + while (currentProcedure != null) { + context.runSync(currentProcedure); + context.yield(); + currentProcedure = onTriggeringProcedure.get(); + } + }); + }; + } + }; + this.name = name; this.predicate = predicate; if (newlyTriggeringProcedure != null) { @@ -95,7 +151,7 @@ public Rule withFinishedTriggeringProcedure(Set reservations, Runnabl () -> new FunctionalInstantProcedure(reservations, action)); } - private Set getReservationsForProcedure(Supplier supplier) { + private static Set getReservationsForProcedure(Supplier supplier) { if (supplier != null) { Procedure procedure = supplier.get(); if (procedure != null) { @@ -142,10 +198,11 @@ public String getName() { } /* package */ Set 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() { diff --git a/src/main/java/com/team766/framework3/RuleEngine.java b/src/main/java/com/team766/framework3/RuleEngine.java index 7f7bb70bd..1a9021ad7 100644 --- a/src/main/java/com/team766/framework3/RuleEngine.java +++ b/src/main/java/com/team766/framework3/RuleEngine.java @@ -1,5 +1,7 @@ package com.team766.framework3; +import static com.team766.framework3.RulePersistence.ONCE_AND_HOLD; + import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -14,6 +16,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -43,18 +46,87 @@ public Category getLoggerCategory() { return Category.RULES; } - protected Rule addRule(String name, BooleanSupplier condition, Supplier action) { - Rule rule = new Rule(name, condition, action); + protected Rule addRule( + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Supplier action) { + Rule rule = new Rule(name, condition, rulePersistence, action); rules.put(name, rule); int priority = rulePriorities.size(); rulePriorities.put(rule, priority); return rule; } + protected Rule addRule(String name, BooleanSupplier condition, Supplier action) { + return addRule(name, condition, ONCE_AND_HOLD, action); + } + protected Rule addRule( - String name, BooleanSupplier condition, Mechanism mechanism, Runnable action) { + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Set mechanisms, + Consumer action) { + return addRule( + name, + condition, + rulePersistence, + () -> new FunctionalProcedure(mechanisms, action)); + } + + protected Rule addRule( + String name, + BooleanSupplier condition, + Set mechanisms, + Consumer action) { + return addRule(name, condition, ONCE_AND_HOLD, mechanisms, action); + } + + protected Rule addRule( + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Mechanism mechanism, + Consumer action) { + return addRule(name, condition, rulePersistence, Set.of(mechanism), action); + } + + protected Rule addRule( + String name, BooleanSupplier condition, Mechanism mechanism, Consumer action) { + return addRule(name, condition, ONCE_AND_HOLD, mechanism, action); + } + + protected Rule addRule( + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Set mechanisms, + Runnable action) { return addRule( - name, condition, () -> new FunctionalInstantProcedure(Set.of(mechanism), action)); + name, + condition, + rulePersistence, + () -> new FunctionalInstantProcedure(mechanisms, action)); + } + + protected Rule addRule( + String name, BooleanSupplier condition, Set mechanisms, Runnable action) { + return addRule(name, condition, ONCE_AND_HOLD, mechanisms, action); + } + + protected Rule addRule( + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Mechanism mechanism, + Runnable action) { + return addRule(name, condition, rulePersistence, Set.of(mechanism), action); + } + + protected Rule addRule( + String name, BooleanSupplier condition, Mechanism mechanism, Runnable action) { + return addRule(name, condition, ONCE_AND_HOLD, mechanism, action); } @VisibleForTesting @@ -107,7 +179,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); @@ -169,6 +241,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; diff --git a/src/main/java/com/team766/framework3/RulePersistence.java b/src/main/java/com/team766/framework3/RulePersistence.java new file mode 100644 index 000000000..b6e5dafd7 --- /dev/null +++ b/src/main/java/com/team766/framework3/RulePersistence.java @@ -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, +} diff --git a/src/test/java/com/team766/framework3/RuleEngineTest.java b/src/test/java/com/team766/framework3/RuleEngineTest.java index 3c0b9edd4..95db1acba 100644 --- a/src/test/java/com/team766/framework3/RuleEngineTest.java +++ b/src/test/java/com/team766/framework3/RuleEngineTest.java @@ -1,6 +1,10 @@ package com.team766.framework3; +import static com.team766.framework3.RulePersistence.ONCE; +import static com.team766.framework3.RulePersistence.ONCE_AND_HOLD; +import static com.team766.framework3.RulePersistence.REPEATEDLY; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -10,6 +14,7 @@ import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.CommandScheduler; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import org.junit.jupiter.api.Test; @@ -64,7 +69,7 @@ public void testSeal() { RuleEngine rulesOne = new RuleEngine() { { - addRule("rule1_1", () -> true, () -> Procedure.NO_OP) + addRule("rule1_1", () -> true, ONCE, () -> Procedure.NO_OP) .withFinishedTriggeringProcedure(() -> Procedure.NO_OP); } }; @@ -74,7 +79,7 @@ public void testSeal() { RuleEngine rulesTwo = new RuleEngine() { { - addRule("rule2_1", () -> true, () -> Procedure.NO_OP); + addRule("rule2_1", () -> true, ONCE, () -> Procedure.NO_OP); } }; rulesTwo.run(); @@ -97,10 +102,12 @@ public void testAddRuleAndGetPriority() { addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure(2, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure(2, Set.of(fm1))); } }; @@ -125,10 +132,12 @@ public void testRunNonConflictingRules() { addRule( "fm1", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure(2, Set.of(fm1))); addRule( "fm2", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure(2, Set.of(fm2))); } }; @@ -175,6 +184,7 @@ public void testFinishedProcedureBumpsNewlyProcedureForSameRule() { addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 1, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> @@ -213,15 +223,18 @@ public void testRunRulePriorities() { addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1proc_p0", 0, Set.of(fm1, fm2))); addRule( "fm1_p1", new PeriodicPredicate(2), + ONCE, () -> new FakeProcedure("fm1proc_p1", 0, Set.of(fm1, fm3))); addRule( "fm3_p2", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm3proc_p2", 0, Set.of(fm3))); } }; @@ -265,15 +278,18 @@ public void testRunHigherPriorityRuleStillBeingRun() { addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1proc_p0", 2, Set.of(fm1, fm2))); addRule( "fm1_p1", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1proc_p1", 2, Set.of(fm1, fm2))); addRule( "fm1_p2", new ScheduledPredicate(3), + ONCE, () -> new FakeProcedure("fm1proc_p2", 2, Set.of(fm1, fm2))); } }; @@ -324,10 +340,12 @@ public void testRunLowerPriorityRuleBumped() { addRule( "fm1_p0", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1proc_p0", 2, Set.of(fm1, fm2))); addRule( "fm1_p1", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1proc_p1", 4, Set.of(fm1, fm2))); } }; @@ -358,10 +376,12 @@ public void testRuleResetIgnoredLowerPriorityRule() { addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 2, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 1, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 1, Set.of(fm2))); @@ -393,10 +413,12 @@ public void testRuleResetIgnoredLowerPriorityRuleHigherPriorityRulePreviouslySch addRule( "fm1_p0", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 2, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 1, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 1, Set.of(fm2))); @@ -436,10 +458,12 @@ public void testRuleResetBumpedLowerPriorityRule() { addRule( "fm1_p0", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 2, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(0), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 2, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 2, Set.of(fm2))); @@ -471,12 +495,14 @@ public void testLowerPriorityRuleRunsWhenProcedureFromHigherPriorityRuleFinishes addRule( "fm1_p0", new ScheduledPredicate(0, 4), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 0, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p0", 0, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 0, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 0, Set.of(fm1))); @@ -532,12 +558,14 @@ public void testRuleCalledAgainAfterBeingReset() { addRule( "fm1_p0", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 0, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p0", 0, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(0, 4), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 1, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 1, Set.of(fm1))); @@ -594,12 +622,14 @@ public void testRuleResetPreventsFinishedForLongTrigger() { addRule( "fm1_p0", new ScheduledPredicate(1), + ONCE, () -> new FakeProcedure("fm1procnew_p0", 0, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p0", 0, Set.of(fm1))); addRule( "fm1_p1", new ScheduledPredicate(0, 3), + ONCE, () -> new FakeProcedure("fm1procnew_p1", 1, Set.of(fm1))) .withFinishedTriggeringProcedure( () -> new FakeProcedure("fm1procfin_p1", 1, Set.of(fm1))); @@ -638,4 +668,200 @@ public void testRuleResetPreventsFinishedForLongTrigger() { step(); // 3 } + + /** Test ONCE RulePersistence policy */ + @Test + public void testOncePersistence() { + AtomicReference predicateEndsFirstProc = new AtomicReference<>(); + AtomicReference actionEndsFirstProc = new AtomicReference<>(); + RuleEngine myRules = + new RuleEngine() { + { + addRule( + "predicate_ends_first", + new ScheduledPredicate(0, 1), + ONCE, + () -> { + var proc = + new FakeProcedure( + "predicate_ends_first_proc", 10, Set.of(fm1)); + predicateEndsFirstProc.set(proc); + return proc; + }); + addRule( + "action_ends_first", + new ScheduledPredicate(0, 10), + ONCE, + () -> { + var proc = + new FakeProcedure( + "action_ends_first_proc", 1, Set.of(fm2)); + actionEndsFirstProc.set(proc); + return proc; + }); + } + }; + + myRules.run(); + + // check that both action Procedures are scheduled + Command cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("predicate_ends_first_proc")); + Command cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + + step(); + myRules.run(); + step(); + myRules.run(); + + // ONCE actions should be allowed to run after the rule has stopped triggering. + assertEquals(2, predicateEndsFirstProc.get().age()); + assertFalse(predicateEndsFirstProc.get().isEnded()); + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("predicate_ends_first_proc")); + + // If a ONCE action completes, it should end and mechanism reservations released. + assertTrue(actionEndsFirstProc.get().isEnded()); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNull(cmd2); + } + + /** Test ONCE_AND_HOLD RulePersistence policy */ + @Test + public void testOnceAndHoldPersistence() { + AtomicReference predicateEndsFirstProc = new AtomicReference<>(); + AtomicReference actionEndsFirstProc = new AtomicReference<>(); + RuleEngine myRules = + new RuleEngine() { + { + addRule( + "predicate_ends_first", + new ScheduledPredicate(0, 1), + ONCE_AND_HOLD, + () -> { + var proc = + new FakeProcedure( + "predicate_ends_first_proc", 10, Set.of(fm1)); + predicateEndsFirstProc.set(proc); + return proc; + }); + addRule( + "action_ends_first", + new ScheduledPredicate(0, 10), + ONCE_AND_HOLD, + () -> { + var proc = + new FakeProcedure( + "action_ends_first_proc", 1, Set.of(fm2)); + actionEndsFirstProc.set(proc); + return proc; + }); + } + }; + + myRules.run(); + + // check that both action Procedures are scheduled + Command cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("predicate_ends_first_proc")); + Command cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + + step(); + myRules.run(); + step(); + myRules.run(); + + // ONCE_AND_HOLD actions should be cancelled after the rule has stopped triggering. + assertTrue(predicateEndsFirstProc.get().isEnded()); + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNull(cmd1); + + // If a ONCE_AND_HOLD action completes, it should end but mechanism reservations are + // retained. + assertTrue(actionEndsFirstProc.get().isEnded()); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + } + + /** Test REPEATEDLY RulePersistence policy */ + @Test + public void testRepeatedlyPersistence() { + AtomicReference predicateEndsFirstProc = new AtomicReference<>(); + AtomicReference actionEndsFirstProc = new AtomicReference<>(); + RuleEngine myRules = + new RuleEngine() { + { + addRule( + "predicate_ends_first", + new ScheduledPredicate(0, 1), + REPEATEDLY, + () -> { + var proc = + new FakeProcedure( + "predicate_ends_first_proc", 10, Set.of(fm1)); + predicateEndsFirstProc.set(proc); + return proc; + }); + addRule( + "action_ends_first", + new ScheduledPredicate(0, 10), + REPEATEDLY, + () -> { + var proc = + new FakeProcedure( + "action_ends_first_proc", 1, Set.of(fm2)); + actionEndsFirstProc.set(proc); + return proc; + }); + } + }; + + myRules.run(); + + // check that both action Procedures are scheduled + Command cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("predicate_ends_first_proc")); + Command cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + + step(); + myRules.run(); + step(); + myRules.run(); + step(); + + // REPEATEDLY actions should be cancelled after the rule has stopped triggering. + assertTrue(predicateEndsFirstProc.get().isEnded()); + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNull(cmd1); + + // If a REPEATEDLY action completes, another instance should be started. + assertFalse(actionEndsFirstProc.get().isEnded()); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + + final FakeProcedure previousActionInstance = actionEndsFirstProc.get(); + + myRules.run(); + step(); + myRules.run(); + step(); + + assertTrue(previousActionInstance.isEnded()); + assertFalse(actionEndsFirstProc.get().isEnded()); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); + } } diff --git a/src/test/java/com/team766/framework3/RuleTest.java b/src/test/java/com/team766/framework3/RuleTest.java index b76e568ce..bd66d1e8e 100644 --- a/src/test/java/com/team766/framework3/RuleTest.java +++ b/src/test/java/com/team766/framework3/RuleTest.java @@ -1,9 +1,13 @@ package com.team766.framework3; +import static com.team766.framework3.RulePersistence.ONCE; +import static com.team766.framework3.RulePersistence.ONCE_AND_HOLD; +import static com.team766.framework3.RulePersistence.REPEATEDLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import com.team766.framework3.Rule.Cancellation; import com.team766.framework3.Rule.TriggerType; import java.util.Collections; import java.util.Set; @@ -41,7 +45,7 @@ protected void setUp() {} @Test public void testCreate() { - Rule alwaysTrue = new Rule("always true", () -> true, () -> Procedure.NO_OP); + Rule alwaysTrue = new Rule("always true", () -> true, ONCE, () -> Procedure.NO_OP); assertNotNull(alwaysTrue); assertEquals("always true", alwaysTrue.getName()); } @@ -49,7 +53,7 @@ public void testCreate() { @Test public void testEvaluate() { // start with simple test of a NONE->NEWLY->CONTINUING->CONTINUING sequence - Rule alwaysTrue = new Rule("always true", () -> true, () -> Procedure.NO_OP); + Rule alwaysTrue = new Rule("always true", () -> true, ONCE, () -> Procedure.NO_OP); assertEquals(Rule.TriggerType.NONE, alwaysTrue.getCurrentTriggerType()); alwaysTrue.evaluate(); assertEquals(TriggerType.NEWLY, alwaysTrue.getCurrentTriggerType()); @@ -63,6 +67,7 @@ public void testEvaluate() { new Rule( "duck duck goose goose", new DuckDuckGooseGoosePredicate(), + ONCE, () -> Procedure.NO_OP); assertEquals(Rule.TriggerType.NONE, duckDuckGooseGoose.getCurrentTriggerType()); duckDuckGooseGoose.evaluate(); @@ -77,6 +82,35 @@ public void testEvaluate() { assertEquals(TriggerType.NEWLY, duckDuckGooseGoose.getCurrentTriggerType()); } + @Test + public void testGetCancellation() { + Rule ruleWithOnce = + new Rule( + "always true", + new DuckDuckGooseGoosePredicate(), + ONCE, + () -> Procedure.NO_OP); + assertEquals(Cancellation.DO_NOT_CANCEL, ruleWithOnce.getCancellationOnFinish()); + + Rule ruleWithOnceAndHold = + new Rule( + "always true", + new DuckDuckGooseGoosePredicate(), + ONCE_AND_HOLD, + () -> Procedure.NO_OP); + assertEquals( + Cancellation.CANCEL_NEWLY_ACTION, ruleWithOnceAndHold.getCancellationOnFinish()); + + Rule ruleWithRepeatedly = + new Rule( + "always true", + new DuckDuckGooseGoosePredicate(), + REPEATEDLY, + () -> Procedure.NO_OP); + assertEquals( + Cancellation.CANCEL_NEWLY_ACTION, ruleWithRepeatedly.getCancellationOnFinish()); + } + @Test public void testGetMechanismsToReserve() { final Set newlyMechanisms = Set.of(new FakeMechanism1(), new FakeMechanism2()); @@ -86,6 +120,7 @@ public void testGetMechanismsToReserve() { new Rule( "duck duck goose goose", new DuckDuckGooseGoosePredicate(), + ONCE, () -> new FunctionalInstantProcedure(newlyMechanisms, () -> {})) .withFinishedTriggeringProcedure(finishedMechanisms, () -> {}); @@ -120,6 +155,7 @@ public void testGetProcedureToRun() { new Rule( "duck duck goose goose", new DuckDuckGooseGoosePredicate(), + ONCE, () -> new TrivialProcedure("newly")) .withFinishedTriggeringProcedure(() -> new TrivialProcedure("finished"));