From a6e3b3e09366a5ae51e53ae5df095828587aa90f Mon Sep 17 00:00:00 2001 From: Ryan Cahoon Date: Wed, 11 Dec 2024 03:47:02 -0800 Subject: [PATCH] Hierarchical rules --- .../java/com/team766/framework3/Rule.java | 111 ++++++++-- .../com/team766/framework3/RuleEngine.java | 96 +------- .../com/team766/framework3/RuleGroup.java | 21 ++ .../com/team766/framework3/RuleGroupBase.java | 109 +++++++++ .../com/team766/framework3/FakeMechanism.java | 2 + .../team766/framework3/RuleEngineTest.java | 206 +++++++++++++++++- .../java/com/team766/framework3/RuleTest.java | 48 ++-- 7 files changed, 456 insertions(+), 137 deletions(-) create mode 100644 src/main/java/com/team766/framework3/RuleGroup.java create mode 100644 src/main/java/com/team766/framework3/RuleGroupBase.java diff --git a/src/main/java/com/team766/framework3/Rule.java b/src/main/java/com/team766/framework3/Rule.java index c2bff6f9b..fee0a609c 100644 --- a/src/main/java/com/team766/framework3/Rule.java +++ b/src/main/java/com/team766/framework3/Rule.java @@ -57,28 +57,36 @@ enum Cancellation { CANCEL_NEWLY_ACTION, } + private RuleGroupBase container; private final String name; - private final BooleanSupplier predicate; + private BooleanSupplier predicate; private final Map> triggerProcedures = Maps.newEnumMap(TriggerType.class); private final Map> triggerReservations = Maps.newEnumMap(TriggerType.class); - private final Cancellation cancellationOnFinish; + private Cancellation cancellationOnFinish; private TriggerType currentTriggerType = TriggerType.NONE; private boolean sealed = false; - /* package */ Rule( - String name, - BooleanSupplier predicate, - RulePersistence rulePersistence, - Supplier onTriggeringProcedure) { + /* package */ Rule(RuleGroupBase container, String name, BooleanSupplier predicate) { if (predicate == null) { throw new IllegalArgumentException("Rule predicate has not been set."); } - if (onTriggeringProcedure == null) { - throw new IllegalArgumentException("On-triggering Procedure is not defined."); + this.container = container; + this.name = name; + this.predicate = predicate; + } + + public Rule withOnTriggeringProcedure( + RulePersistence rulePersistence, Supplier onTriggeringProcedure) { + if (sealed) { + throw new IllegalStateException( + "Cannot modify rules once they've been evaluated in the RuleEngine"); + } + if (triggerProcedures.containsKey(TriggerType.NEWLY)) { + throw new IllegalStateException("This trigger already has an OnTriggering action"); } final Supplier newlyTriggeringProcedure = @@ -125,13 +133,22 @@ enum Cancellation { } }; - this.name = name; - this.predicate = predicate; - if (newlyTriggeringProcedure != null) { - triggerProcedures.put(TriggerType.NEWLY, newlyTriggeringProcedure); - triggerReservations.put( - TriggerType.NEWLY, getReservationsForProcedure(newlyTriggeringProcedure)); - } + triggerProcedures.put(TriggerType.NEWLY, newlyTriggeringProcedure); + triggerReservations.put( + TriggerType.NEWLY, getReservationsForProcedure(newlyTriggeringProcedure)); + + return this; + } + + public Rule withOnTriggeringProcedure( + RulePersistence rulePersistence, Set reservations, Runnable action) { + return withOnTriggeringProcedure( + rulePersistence, () -> new FunctionalInstantProcedure(reservations, action)); + } + + public Rule withOnTriggeringProcedure( + RulePersistence rulePersistence, Mechanism reservation, Runnable action) { + return withOnTriggeringProcedure(rulePersistence, Set.of(reservation), action); } /** Specify a creator for the Procedure that should be run when this rule was triggering before and is no longer triggering. */ @@ -140,6 +157,10 @@ public Rule withFinishedTriggeringProcedure(Supplier action) { throw new IllegalStateException( "Cannot modify rules once they've been evaluated in the RuleEngine"); } + if (triggerProcedures.containsKey(TriggerType.FINISHED)) { + throw new IllegalStateException( + "This trigger already has an FinishedTriggering action"); + } triggerProcedures.put(TriggerType.FINISHED, action); triggerReservations.put(TriggerType.FINISHED, getReservationsForProcedure(action)); @@ -151,6 +172,55 @@ public Rule withFinishedTriggeringProcedure(Set reservations, Runnabl () -> new FunctionalInstantProcedure(reservations, action)); } + public Rule withFinishedTriggeringProcedure(Mechanism reservation, Runnable action) { + return withFinishedTriggeringProcedure(Set.of(reservation), action); + } + + /** Specify Rules which should only trigger when this Rule is also triggering. */ + public Rule whenTriggering(RuleGroup rules) { + if (sealed) { + throw new IllegalStateException( + "Cannot modify rules once they've been evaluated in the RuleEngine"); + } + rules.mergeInto(container, this, true); + return this; + } + + /** Specify Rules which should only trigger when this Rule is not triggering. */ + public Rule whenNotTriggering(RuleGroup rules) { + if (sealed) { + throw new IllegalStateException( + "Cannot modify rules once they've been evaluated in the RuleEngine"); + } + rules.mergeInto(container, this, false); + return this; + } + + /* package */ void attachTo(RuleGroupBase container, Rule parent, boolean triggerValue) { + if (sealed) { + throw new IllegalStateException( + "Cannot modify rules once they've been evaluated in the RuleEngine"); + } + this.container = container; + if (parent != null) { + final var previousPredicate = this.predicate; + this.predicate = + triggerValue + // Important! These composed predicates shouldn't invoke the parent's + // `predicate`. Each Rule's `predicate` should be invoked only once + // per call to RuleEngine.run(), so having all rules in the hierarchy + // call it would not work as expected. Instead, we have the child rules + // query the triggering state of the parent rule. + // Also Important! The order of these conditions matters: we want the + // user's predicate to be invoked only when this rule is active + // (i.e. when its parent condition is satisfied), so we put the user's + // predicate second, so it gets short-circuited when the rule is not + // active. + ? () -> parent.isTriggering() && previousPredicate.getAsBoolean() + : () -> !parent.isTriggering() && previousPredicate.getAsBoolean(); + } + } + private static Set getReservationsForProcedure(Supplier supplier) { if (supplier != null) { Procedure procedure = supplier.get(); @@ -169,6 +239,15 @@ public String getName() { return currentTriggerType; } + /* package */ boolean isTriggering() { + return switch (currentTriggerType) { + case NEWLY -> true; + case CONTINUING -> true; + case FINISHED -> false; + case NONE -> false; + }; + } + /* package */ void seal() { sealed = true; } diff --git a/src/main/java/com/team766/framework3/RuleEngine.java b/src/main/java/com/team766/framework3/RuleEngine.java index 1a9021ad7..e65181086 100644 --- a/src/main/java/com/team766/framework3/RuleEngine.java +++ b/src/main/java/com/team766/framework3/RuleEngine.java @@ -1,11 +1,8 @@ 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; -import com.team766.logging.Category; import com.team766.logging.LoggerExceptionUtils; import com.team766.logging.Severity; import edu.wpi.first.wpilibj2.command.Command; @@ -15,8 +12,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -30,7 +25,7 @@ * For a {@link Rule} to trigger, its predicate must be satisfied -- and, the {@link Mechanism}s the corresponding {@link Procedure} would reserve * must not be in use or about to be in use from a higher priority {@link Rule}. */ -public class RuleEngine implements StatusesMixin, LoggingBase { +public class RuleEngine extends RuleGroupBase { private static record RuleAction(Rule rule, Rule.TriggerType triggerType) {} @@ -42,91 +37,14 @@ private static record RuleAction(Rule rule, Rule.TriggerType triggerType) {} protected RuleEngine() {} @Override - public Category getLoggerCategory() { - return Category.RULES; - } - - protected Rule addRule( - String name, - BooleanSupplier condition, - RulePersistence rulePersistence, - Supplier action) { - Rule rule = new Rule(name, condition, rulePersistence, action); - rules.put(name, rule); + protected void addRule(Rule rule) { + if (sealed) { + throw new IllegalStateException( + "Cannot add rules after the RuleEngine has started running"); + } + rules.put(rule.getName(), 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, - 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, - 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 diff --git a/src/main/java/com/team766/framework3/RuleGroup.java b/src/main/java/com/team766/framework3/RuleGroup.java new file mode 100644 index 000000000..4d9935ea1 --- /dev/null +++ b/src/main/java/com/team766/framework3/RuleGroup.java @@ -0,0 +1,21 @@ +package com.team766.framework3; + +import java.util.ArrayList; +import java.util.List; + +public class RuleGroup extends RuleGroupBase { + private final List rules = new ArrayList<>(); + + @Override + /* package */ void addRule(Rule rule) { + rules.add(rule); + } + + /* package */ void mergeInto(RuleGroupBase container, Rule parent, boolean triggerValue) { + for (var rule : rules) { + rule.attachTo(container, parent, triggerValue); + container.addRule(rule); + } + rules.clear(); + } +} diff --git a/src/main/java/com/team766/framework3/RuleGroupBase.java b/src/main/java/com/team766/framework3/RuleGroupBase.java new file mode 100644 index 000000000..a4fb22cc2 --- /dev/null +++ b/src/main/java/com/team766/framework3/RuleGroupBase.java @@ -0,0 +1,109 @@ +package com.team766.framework3; + +import static com.team766.framework3.RulePersistence.ONCE_AND_HOLD; + +import com.team766.logging.Category; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/* package */ abstract class RuleGroupBase implements LoggingBase { + protected static final BooleanSupplier UNCONDITIONAL = () -> true; + + @Override + public Category getLoggerCategory() { + return Category.RULES; + } + + /* package */ abstract void addRule(Rule rule); + + protected Rule addRule(String name, BooleanSupplier condition) { + final Rule rule = new Rule(this, name, condition); + addRule(rule); + return rule; + } + + protected Rule addRule( + String name, + BooleanSupplier condition, + RulePersistence rulePersistence, + Supplier action) { + return addRule(name, condition).withOnTriggeringProcedure(rulePersistence, action); + } + + protected Rule addRule(String name, BooleanSupplier condition, Supplier action) { + return addRule(name, condition, ONCE_AND_HOLD, action); + } + + protected Rule addRule( + 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, + 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); + } + + protected final void addRules(RuleGroup group) { + group.mergeInto(this, null, true); + } +} diff --git a/src/test/java/com/team766/framework3/FakeMechanism.java b/src/test/java/com/team766/framework3/FakeMechanism.java index b1ef4b41c..fc3dff4d7 100644 --- a/src/test/java/com/team766/framework3/FakeMechanism.java +++ b/src/test/java/com/team766/framework3/FakeMechanism.java @@ -13,3 +13,5 @@ class FakeMechanism1 extends FakeMechanism {} class FakeMechanism2 extends FakeMechanism {} class FakeMechanism3 extends FakeMechanism {} + +class FakeMechanism4 extends FakeMechanism {} diff --git a/src/test/java/com/team766/framework3/RuleEngineTest.java b/src/test/java/com/team766/framework3/RuleEngineTest.java index 95db1acba..0e02b3f90 100644 --- a/src/test/java/com/team766/framework3/RuleEngineTest.java +++ b/src/test/java/com/team766/framework3/RuleEngineTest.java @@ -62,6 +62,7 @@ public boolean getAsBoolean() { private final FakeMechanism1 fm1 = new FakeMechanism1(); private final FakeMechanism2 fm2 = new FakeMechanism2(); private final FakeMechanism3 fm3 = new FakeMechanism3(); + private final FakeMechanism4 fm4 = new FakeMechanism4(); @Test public void testSeal() { @@ -69,7 +70,7 @@ public void testSeal() { RuleEngine rulesOne = new RuleEngine() { { - addRule("rule1_1", () -> true, ONCE, () -> Procedure.NO_OP) + addRule("rule1_1", UNCONDITIONAL, ONCE, () -> Procedure.NO_OP) .withFinishedTriggeringProcedure(() -> Procedure.NO_OP); } }; @@ -79,7 +80,7 @@ public void testSeal() { RuleEngine rulesTwo = new RuleEngine() { { - addRule("rule2_1", () -> true, ONCE, () -> Procedure.NO_OP); + addRule("rule2_1", UNCONDITIONAL, ONCE, () -> Procedure.NO_OP); } }; rulesTwo.run(); @@ -864,4 +865,205 @@ public void testRepeatedlyPersistence() { assertNotNull(cmd2); assertTrue(cmd2.getName().endsWith("action_ends_first_proc")); } + + /** Test hierarchical Rules triggering */ + @Test + public void testRuleHierarchy() { + RuleEngine myRules = + new RuleEngine() { + { + addRule("root", new ScheduledPredicate(0, 2)) + .withOnTriggeringProcedure( + ONCE_AND_HOLD, + () -> new FakeProcedure("root_proc", 10, Set.of(fm1))) + .whenTriggering( + new RuleGroup() { + { + addRule( + "positive_combinator", + new ScheduledPredicate(1, 3)) + .withOnTriggeringProcedure( + ONCE_AND_HOLD, + () -> + new FakeProcedure( + "positive_combinator_proc", + 10, + Set.of(fm2))) + .whenNotTriggering( + new RuleGroup() { + { + // Test multi-level + // combinator + addRule( + "positive_then_negative_combinator", + () -> true, + ONCE_AND_HOLD, + () -> + new FakeProcedure( + "positive_then_negative_combinator_proc", + 0, + Set + .of( + fm4))); + } + }); + } + }) + .whenNotTriggering( + new RuleGroup() { + { + addRule( + "negative_combinator", + // Note: This predicate is only + // evaluated when the `root` rule is + // not triggering, so this triggers + // on frame 2, even though its + // start/end arguments say it + // triggers on frame 0. + new ScheduledPredicate(0, 1)) + .withOnTriggeringProcedure( + ONCE_AND_HOLD, + () -> + new FakeProcedure( + "negative_combinator_proc", + 10, + Set.of(fm3))); + } + }); + } + }; + + myRules.run(); + + Command cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("root_proc")); + Command cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNull(cmd2); + Command cmd3 = CommandScheduler.getInstance().requiring(fm3); + assertNull(cmd3); + Command cmd4 = CommandScheduler.getInstance().requiring(fm4); + assertNotNull(cmd4); + assertTrue(cmd4.getName().endsWith("positive_then_negative_combinator_proc")); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("root_proc")); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNotNull(cmd2); + assertTrue(cmd2.getName().endsWith("positive_combinator_proc")); + cmd3 = CommandScheduler.getInstance().requiring(fm3); + assertNull(cmd3); + cmd4 = CommandScheduler.getInstance().requiring(fm4); + assertNull(cmd4); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNull(cmd1); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNull(cmd2); + cmd3 = CommandScheduler.getInstance().requiring(fm3); + assertNotNull(cmd3); + assertTrue(cmd3.getName().endsWith("negative_combinator_proc")); + cmd4 = CommandScheduler.getInstance().requiring(fm4); + assertNull(cmd4); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNull(cmd1); + cmd2 = CommandScheduler.getInstance().requiring(fm2); + assertNull(cmd2); + cmd3 = CommandScheduler.getInstance().requiring(fm3); + assertNull(cmd3); + cmd4 = CommandScheduler.getInstance().requiring(fm4); + assertNull(cmd4); + } + + /** Test that the root Rule takes precedence over child rules triggering */ + @Test + public void testRuleHierarchyPriorities() { + RuleEngine myRules = + new RuleEngine() { + { + addRule("root", new ScheduledPredicate(0, 2)) + .withOnTriggeringProcedure( + ONCE, + () -> new FakeProcedure("root_newly_proc", 0, Set.of(fm1))) + .withFinishedTriggeringProcedure( + () -> + new FakeProcedure( + "root_finished_proc", 0, Set.of(fm1))) + .whenTriggering( + new RuleGroup() { + { + addRule( + "positive_combinator", + new ScheduledPredicate(0, 2)) + .withOnTriggeringProcedure( + ONCE_AND_HOLD, + () -> + new FakeProcedure( + "positive_combinator_proc", + 10, + Set.of(fm1))); + } + }) + .whenNotTriggering( + new RuleGroup() { + { + addRule( + "negative_combinator", + // Note: This predicate is only + // evaluated when the `root` rule is + // not triggering, so this triggers + // on frames 2-3, even though its + // start/end arguments say it + // triggers on frame 0-1. + new ScheduledPredicate(0, 2)) + .withOnTriggeringProcedure( + ONCE_AND_HOLD, + () -> + new FakeProcedure( + "negative_combinator_proc", + 10, + Set.of(fm1))); + } + }); + } + }; + + myRules.run(); + + Command cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("root_newly_proc")); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("positive_combinator_proc")); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("root_finished_proc")); + + step(); + myRules.run(); + + cmd1 = CommandScheduler.getInstance().requiring(fm1); + assertNotNull(cmd1); + assertTrue(cmd1.getName().endsWith("negative_combinator_proc")); + } } diff --git a/src/test/java/com/team766/framework3/RuleTest.java b/src/test/java/com/team766/framework3/RuleTest.java index bd66d1e8e..64dcc981e 100644 --- a/src/test/java/com/team766/framework3/RuleTest.java +++ b/src/test/java/com/team766/framework3/RuleTest.java @@ -45,7 +45,9 @@ protected void setUp() {} @Test public void testCreate() { - Rule alwaysTrue = new Rule("always true", () -> true, ONCE, () -> Procedure.NO_OP); + Rule alwaysTrue = + new Rule(null, "always true", () -> true) + .withOnTriggeringProcedure(ONCE, () -> Procedure.NO_OP); assertNotNull(alwaysTrue); assertEquals("always true", alwaysTrue.getName()); } @@ -53,7 +55,9 @@ 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, ONCE, () -> Procedure.NO_OP); + Rule alwaysTrue = + new Rule(null, "always true", () -> true) + .withOnTriggeringProcedure(ONCE, () -> Procedure.NO_OP); assertEquals(Rule.TriggerType.NONE, alwaysTrue.getCurrentTriggerType()); alwaysTrue.evaluate(); assertEquals(TriggerType.NEWLY, alwaysTrue.getCurrentTriggerType()); @@ -64,11 +68,8 @@ public void testEvaluate() { // test a full cycle: NONE->NEWLY->CONTINUING->FINISHED->NONE->NEWLY->... Rule duckDuckGooseGoose = - new Rule( - "duck duck goose goose", - new DuckDuckGooseGoosePredicate(), - ONCE, - () -> Procedure.NO_OP); + new Rule(null, "duck duck goose goose", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure(ONCE, () -> Procedure.NO_OP); assertEquals(Rule.TriggerType.NONE, duckDuckGooseGoose.getCurrentTriggerType()); duckDuckGooseGoose.evaluate(); assertEquals(TriggerType.NEWLY, duckDuckGooseGoose.getCurrentTriggerType()); @@ -85,28 +86,19 @@ public void testEvaluate() { @Test public void testGetCancellation() { Rule ruleWithOnce = - new Rule( - "always true", - new DuckDuckGooseGoosePredicate(), - ONCE, - () -> Procedure.NO_OP); + new Rule(null, "always true", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure(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); + new Rule(null, "always true", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure(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); + new Rule(null, "always true", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure(REPEATEDLY, () -> Procedure.NO_OP); assertEquals( Cancellation.CANCEL_NEWLY_ACTION, ruleWithRepeatedly.getCancellationOnFinish()); } @@ -117,9 +109,8 @@ public void testGetMechanismsToReserve() { final Set finishedMechanisms = Set.of(new FakeMechanism()); Rule duckDuckGooseGoose = - new Rule( - "duck duck goose goose", - new DuckDuckGooseGoosePredicate(), + new Rule(null, "duck duck goose goose", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure( ONCE, () -> new FunctionalInstantProcedure(newlyMechanisms, () -> {})) .withFinishedTriggeringProcedure(finishedMechanisms, () -> {}); @@ -152,11 +143,8 @@ public void testGetMechanismsToReserve() { @Test public void testGetProcedureToRun() { Rule duckDuckGooseGoose = - new Rule( - "duck duck goose goose", - new DuckDuckGooseGoosePredicate(), - ONCE, - () -> new TrivialProcedure("newly")) + new Rule(null, "duck duck goose goose", new DuckDuckGooseGoosePredicate()) + .withOnTriggeringProcedure(ONCE, () -> new TrivialProcedure("newly")) .withFinishedTriggeringProcedure(() -> new TrivialProcedure("finished")); // NONE