Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ 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
73 changes: 65 additions & 8 deletions src/main/java/com/team766/framework3/Rule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TriggerType, Supplier<Procedure>> triggerProcedures =
Maps.newEnumMap(TriggerType.class);
private final Map<TriggerType, Set<Mechanism>> 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<Procedure> newlyTriggeringProcedure) {
String name,
BooleanSupplier predicate,
RulePersistence rulePersistence,
Comment thread
rcahoon marked this conversation as resolved.
Supplier<Procedure> 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<Procedure> 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) {
Expand All @@ -95,7 +151,7 @@ public Rule withFinishedTriggeringProcedure(Set<Mechanism> reservations, Runnabl
() -> new FunctionalInstantProcedure(reservations, action));
}

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 @@ -142,10 +198,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
93 changes: 88 additions & 5 deletions src/main/java/com/team766/framework3/RuleEngine.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -43,18 +46,87 @@ public Category getLoggerCategory() {
return Category.RULES;
}

protected Rule addRule(String name, BooleanSupplier condition, Supplier<Procedure> action) {
Rule rule = new Rule(name, condition, action);
protected Rule addRule(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we hold off on merging this PR until the RuleEngine API simplification goes through?

I think that would remove the need for a lot of these overloaded versions of addRule().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n/m about the first paragraph since this is based on that other PR.. do you think we could reduce the overloading and let devs more simply specify a withRulePersistence if they want something other than ONCE_AND_HOLD (if we want that to be the default)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withRulePersistence seems weird to me, because withFinishedTriggeringProcedure is specifying a whole other action, whereas withRulePersistence would be modifying just the onTriggering action. i.e. it would be possible to say addRule(...).withFinishedTriggeringProcedure(...).withRulePersistence(...), which seems misleading.

does standard Java style discourage the use of overloads?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not at all - there are just a lot of variants. it's too bad java doesn't have named and optoinal parameters. :)

just looked - there are articles like this that also speak to alternatives to method overloading when there are a larger number of parameters.

I'm fine either way - was just wondering how to keep this scalable esp if and as we do add more parameters.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a good point. we can discuss again if we add more parameters

String name,
BooleanSupplier condition,
RulePersistence rulePersistence,
Supplier<Procedure> 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<Procedure> 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<Mechanism> mechanisms,
Consumer<Context> action) {
return addRule(
name,
condition,
rulePersistence,
() -> new FunctionalProcedure(mechanisms, action));
}

protected Rule addRule(
String name,
BooleanSupplier condition,
Set<Mechanism> mechanisms,
Consumer<Context> action) {
return addRule(name, condition, ONCE_AND_HOLD, mechanisms, action);
}

protected Rule addRule(
String name,
BooleanSupplier condition,
RulePersistence rulePersistence,
Mechanism mechanism,
Consumer<Context> action) {
return addRule(name, condition, rulePersistence, Set.of(mechanism), action);
}

protected Rule addRule(
String name, BooleanSupplier condition, Mechanism mechanism, Consumer<Context> action) {
return addRule(name, condition, ONCE_AND_HOLD, mechanism, action);
}

protected Rule addRule(
String name,
BooleanSupplier condition,
RulePersistence rulePersistence,
Set<Mechanism> 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<Mechanism> 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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
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