Skip to content

Commit

Permalink
Rule features
Browse files Browse the repository at this point in the history
  • Loading branch information
rcahoon committed Nov 11, 2024
1 parent 6ca62c7 commit 473e228
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 58 deletions.
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
147 changes: 129 additions & 18 deletions src/main/java/com/team766/framework3/Rule.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.team766.framework3;

import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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 +29,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(RulePersistence.ONCE_AND_HOLD, () -> new ShooterSpin(shooter)));
* ...
* }
* }
Expand All @@ -49,33 +53,99 @@ enum TriggerType {
FINISHED
}

enum Cancellation {
DO_NOT_CANCEL,
CANCEL_NEWLY_ACTION,
}

public interface RuleFactory {
List<Rule> build(BooleanSupplier parentPredicate);

default List<Rule> build() {
return build(null);
}
}

/**
* Simple Builder for {@link Rule}s. Configure Rules via this Builder; these fields will be immutable
* in the rule the Builder constructs.
*
* Instances of this Builder are created via {@link Rule#create} to simplify syntax.
*/
public static class Builder {
public static class Builder implements RuleFactory {
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 final List<RuleFactory> composedRules = new ArrayList<>();
private final List<RuleFactory> negatedComposedRules = new ArrayList<>();

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 -> {
while (true) {
procedure.run(context);
context.yield();
}
});
};
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);
public Builder withOnTriggeringProcedure(
RulePersistence rulePersistence, Set<Mechanism<?>> reservations, Runnable action) {
applyRulePersistence(
rulePersistence, () -> new FunctionalInstantProcedure(reservations, action));
return this;
}

public Builder withOnTriggeringProcedure(
RulePersistence rulePersistence,
Set<Mechanism<?>> reservations,
Consumer<Context> action) {
applyRulePersistence(
rulePersistence, () -> new FunctionalProcedure(reservations, action));
return this;
}

Expand All @@ -92,9 +162,41 @@ public Builder withFinishedTriggeringProcedure(
return this;
}

public Builder whenTriggering(RuleFactory... rules) {
composedRules.addAll(Arrays.asList(rules));
return this;
}

public Builder whenNotTriggering(RuleFactory... rules) {
negatedComposedRules.addAll(Arrays.asList(rules));
return this;
}

// called by {@link RuleEngine#addRule}.
/* package */ Rule build() {
return new Rule(name, predicate, newlyTriggeringProcedure, finishedTriggeringProcedure);
public List<Rule> build(BooleanSupplier parentPredicate) {
final BooleanSupplier fullPredicate =
parentPredicate == null
? predicate
: () -> parentPredicate.getAsBoolean() && predicate.getAsBoolean();
final BooleanSupplier negativePredicate =
parentPredicate == null
? predicate
: () -> parentPredicate.getAsBoolean() && !predicate.getAsBoolean();
final var rules = new ArrayList<Rule>();
rules.add(
new Rule(
name,
fullPredicate,
onTriggeringProcedure,
cancellationOnFinish,
finishedTriggeringProcedure));
for (var r : composedRules) {
rules.addAll(r.build(fullPredicate));
}
for (var r : negatedComposedRules) {
rules.addAll(r.build(negativePredicate));
}
return rules;
}
}

Expand All @@ -104,6 +206,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 +218,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 +291,10 @@ public String getName() {
return Collections.emptySet();
}

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

/* package */ Procedure getProcedureToRun() {
if (currentTriggerType != TriggerType.NONE) {
if (triggerProcedures.containsKey(currentTriggerType)) {
Expand Down
20 changes: 15 additions & 5 deletions src/main/java/com/team766/framework3/RuleEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

/**
* {@link RuleEngine}s manage and process a set of {@link Rule}s. Subclasses should add rules via
Expand Down Expand Up @@ -41,11 +42,12 @@ public Category getLoggerCategory() {
return Category.RULES;
}

protected void addRule(Rule.Builder builder) {
Rule rule = builder.build();
rules.add(rule);
int priority = rulePriorities.size();
rulePriorities.put(rule, priority);
public void addRule(Rule.RuleFactory builder) {
for (Rule rule : builder.build()) {
rules.add(rule);
int priority = rulePriorities.size();
rulePriorities.put(rule, priority);
}
}

@VisibleForTesting
Expand Down Expand Up @@ -134,6 +136,14 @@ 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
7 changes: 7 additions & 0 deletions src/main/java/com/team766/framework3/RulePersistence.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.team766.framework3;

public enum RulePersistence {
ONCE,
ONCE_AND_HOLD,
REPEATEDLY,
}
Loading

0 comments on commit 473e228

Please sign in to comment.