/* * Copyright 2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.behaviors; import java.util.ArrayList; import ccre.channel.BooleanInput; import ccre.channel.DerivedBooleanInput; import ccre.channel.EventCell; import ccre.channel.EventInput; import ccre.channel.EventOutput; import ccre.channel.FloatInput; import ccre.cluck.Cluck; import ccre.discrete.DerivedDiscreteInput; import ccre.discrete.DiscreteInput; import ccre.discrete.DiscreteType; import ccre.rconf.RConf; import ccre.rconf.RConfable; import ccre.verifier.FlowPhase; import ccre.verifier.SetupPhase; /** * A Behavior Arbitrator has a prioritized list of behavior states, which it * arbitrates between. Each behavior can be independently attempting to be * active, and the highest-priority active behavior gets to run at any moment. * * Channels can be attached to the arbitrator such that they are controlled with * a specific input channel in each behavior state. This allows easy control of * peripherals or other subsystems based on dataflow chosen by the current * behavior. * * Behavior states are added via {@link #addBehavior(String, BooleanInput)}. * Later calls to that method create behaviors with a higher priority than * previous calls. * * @see Behavior * @see ArbitratedBoolean * @see ArbitratedEvent * @see ArbitratedFloat * @author skeggsc */ public class BehaviorArbitrator implements RConfable { private final ArrayList<Behavior> behaviors = new ArrayList<>(); private final EventCell onActiveUpdateCell = new EventCell(); final EventInput onActiveUpdate = onActiveUpdateCell; Behavior active; private final String name; /** * Creates a BehaviorArbitrator with a given name, such as the name of the * subsystem that it's in. * * @param name the name of the arbitrator, used */ public BehaviorArbitrator(String name) { this.name = name; } /** * Publishes this BehaviorArbitrator over RConf to the PoultryInspector. * This allows for easy inspection of the current state of the * BehaviorArbitrator. * * The publishing name will be based on the subsystem name given when this * BehaviorArbitrator was created. * * @return this instance, for method chaining. */ @SetupPhase public BehaviorArbitrator publish() { Cluck.publishRConf(this.name + " Behavior", this); return this; } @FlowPhase private synchronized void checkUpdate() { Behavior target = null; for (Behavior b : behaviors) { if (b.request.get()) { target = b; } } setActiveBehavior(target); } @FlowPhase private synchronized void setActiveBehavior(Behavior behavior) { if (active == behavior) { return; } active = behavior; onActiveUpdateCell.event(); } /** * Provides an arbitrated Float channel controlled by this behavior. * * @param base the input to use when no behavior is active, or if the * currently active behavior hasn't provided an input for this channel. * @return the arbitrated Float channel * @see #addFloat() */ @SetupPhase public ArbitratedFloat addFloat(FloatInput base) { if (base == null) { throw new NullPointerException(); } return new ArbitratedFloat(this, base); } /** * Provides an arbitrated Float channel controlled by this behavior. * * If no behavior is active, or if the active behavior hasn't provided an * input for this channel, it will be tied to zero. * * @return the arbitrated Float channel * @see #addFloat(FloatInput) */ @SetupPhase public ArbitratedFloat addFloat() { return addFloat(FloatInput.zero); } /** * Provides an arbitrated Boolean channel controlled by this behavior. * * @param base the input to use when no behavior is active, or if the * currently active behavior hasn't provided an input for this channel. * @return the arbitrated Boolean channel * @see #addBoolean() */ @SetupPhase public ArbitratedBoolean addBoolean(BooleanInput base) { if (base == null) { throw new NullPointerException(); } return new ArbitratedBoolean(this, base); } /** * Provides an arbitrated Boolean channel controlled by this behavior. * * If no behavior is active, or if the active behavior hasn't provided an * input for this channel, it will be tied to false. * * @return the arbitrated Boolean channel * @see #addBoolean(BooleanInput) */ @SetupPhase public ArbitratedBoolean addBoolean() { return addBoolean(BooleanInput.alwaysFalse); } /** * Provides an arbitrated Event channel controlled by this behavior. * * @param base the input to use when no behavior is active, or if the * currently active behavior hasn't provided an input for this channel. * @return the arbitrated Event channel * @see #addEvent() */ @SetupPhase public ArbitratedEvent addEvent(EventInput base) { if (base == null) { throw new NullPointerException(); } return new ArbitratedEvent(this, base); } /** * Provides an arbitrated Event channel controlled by this behavior. * * If no behavior is active, or if the active behavior hasn't provided an * input for this channel, it will never fire. * * @return the arbitrated Event channel * @see #addEvent(EventInput) */ @SetupPhase public ArbitratedEvent addEvent() { return addEvent(EventInput.never); } /** * Provides a BooleanInput that is true exactly when there are no active * behaviors - that is, no behaviors attempted to run. * * @return a BooleanInput */ @SetupPhase public BooleanInput getIsInactive() { return new DerivedBooleanInput(onActiveUpdateCell) { @Override protected boolean apply() { return active == null; } }; } /** * Provides a BooleanInput that is true exactly when <code>behavior</code> * is the active behavior - that is, it attempted to run, and no * higher-priority behaviors attempted to run. * * @param behavior the behavior to monitor * @return a BooleanInput */ @SetupPhase public BooleanInput getIsActive(Behavior behavior) { if (behavior == null) { throw new NullPointerException(); } return new DerivedBooleanInput(onActiveUpdateCell) { @Override protected boolean apply() { return active == behavior; } }; } /** * Creates a registered behavior state for this behavior arbitrator, with * <code>name</code> as the name. The behavior will attempt to run while the * <code>request</code> parameter is set to true. * * Later calls to that method create behaviors with a higher priority than * previous calls. * * @param name the name for the behavior state, as displayed to the user * over RConf. * @param request when this behavior should be trying to activate. * @return the created Behavior. */ @SetupPhase public Behavior addBehavior(String name, BooleanInput request) { if (name == null || request == null) { throw new NullPointerException(); } request.onChange(this::checkUpdate); Behavior behavior = new Behavior(this, request, name); behaviors.add(behavior); checkUpdate(); return behavior; } /** * Provides an EventInput that fires whenever the active behavior changes. * * @return the behavior change event. */ @SetupPhase public EventInput onBehaviorChange() { return this.onActiveUpdate; } /** * Fires <code>event</code> whenever the active behavior changes. * * @param event the event to fire */ @SetupPhase public void onBehaviorChange(EventOutput event) { this.onActiveUpdate.send(event); } private final DiscreteType<Behavior> discreteType = new DiscreteType<Behavior>() { @Override public Class<Behavior> getType() { return Behavior.class; } @Override public Behavior[] getOptions() { Behavior[] bhs = new Behavior[behaviors.size() + 1]; System.arraycopy(behaviors.toArray(), 0, bhs, 1, bhs.length - 1); return bhs; } @Override public boolean isOption(Behavior e) { return e == null || e.parent == BehaviorArbitrator.this; } @Override public String toString(Behavior e) { return e == null ? "none" : e.getName(); } @Override public Behavior getDefaultValue() { return null; } }; /** * Gets the current active behavior as a discrete input. * * @return the active behavior. */ @SetupPhase public DiscreteInput<Behavior> getActiveBehavior() { return new DerivedDiscreteInput<Behavior>(discreteType, onActiveUpdate) { @Override protected Behavior apply() { return active; } }; } /** * Gets the name of this behavior arbitrator. * * @return the name. */ public String getName() { return name; } @Override public String toString() { return "[BehaviorArbitrator " + name + "]"; } @Override public RConf.Entry[] queryRConf() { ArrayList<RConf.Entry> ents = new ArrayList<>(); ents.add(RConf.title("Behaviors for " + name)); for (Behavior b : behaviors) { if (b == active) { ents.add(RConf.string(" Active: " + b.getName())); } else if (b.request.get()) { ents.add(RConf.string(" Standby: " + b.getName())); } else { ents.add(RConf.string("Inactive: " + b.getName())); } } if (active == null) { ents.add(RConf.string("No active behavior")); } else { ents.add(RConf.string("Active behavior")); } ents.add(RConf.autoRefresh(1000)); return ents.toArray(new RConf.Entry[ents.size()]); } @Override public boolean signalRConf(int field, byte[] data) { return false; } }