/* * Copyright 2013-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.channel; import java.io.Serializable; import ccre.log.Logger; import ccre.time.Time; import ccre.verifier.FlowPhase; import ccre.verifier.SetupPhase; /** * An event output or consumer. This can be fired (or produced or triggered or * activated or a number of other verbs that all mean the same thing), which * does something depending on where it came from. * * @author skeggsc */ public interface EventOutput extends Serializable { /** * An EventOutput that, when fired, does absolutely nothing. */ public static final EventOutput ignored = new EventOutput() { @Override public void event() { // do nothing } // Important to the functioning of static EventOutput.combine @Override public EventOutput combine(EventOutput other) { if (other == null) { throw new NullPointerException(); } return other; }; }; /** * Fires the event. In other words, causes whatever it represents to happen. * * If any exception occurs during the propagation of the event, it will be * passed on by <code>event</code>. * * @see #safeEvent() for a version that catches any errors that occur. */ @FlowPhase public void event(); /** * Fires the event. In other words, causes whatever it represents to happen. * * If any exception occurs during the propagation of the changes, * <code>safeEvent</code> will catch and log it as a * {@link ccre.log.LogLevel#SEVERE} error. * * @see #event() for a version that throws any errors that occur. */ @FlowPhase public default void safeEvent() { try { event(); } catch (Throwable ex) { Logger.severe("Error during event propagation", ex); } } /** * Provides a combined version of this EventOutput and <code>other</code>, * such that when the provided EventOutput is fired, both this EventOutput * and <code>other</code> are fired. * * If any error occurs during propagation of events to either EventOutput, * the other output will still be fired. If both throw exceptions, then one * of the exceptions will be added as a suppressed exception to the other. * * @param other the other EventOutput to include. * @return the combined version of the EventOutputs. */ @SetupPhase public default EventOutput combine(EventOutput other) { if (other == null) { throw new NullPointerException(); } EventOutput original = this; return () -> { try { original.event(); } catch (Throwable thr) { try { other.event(); } catch (Throwable thr2) { thr.addSuppressed(thr2); } throw thr; } other.event(); }; } /** * Combines any number of EventOutputs into a single EventOutput, such that * when the returned EventOutput is fired, all of the EventOutputs are * fired. * * If any error occurs during propagation of events to any EventOutput, the * other outputs will still be fired. If multiple outputs throw exceptions, * then one of them will be chosen arbitrarily and all of the others will be * added as suppressed exceptions. * * @param outputs the EventOutputs to include. * @return the combined version of the EventOutputs. */ @SetupPhase public static EventOutput combine(EventOutput... outputs) { // This works without including 'ignored' in the actual data structure // by having 'ignored' drop itself during combine. EventOutput o = ignored; for (EventOutput eo : outputs) { o = o.combine(eo); } return o; } /** * Provides a version of this EventOutput that only propagates events when * <code>allow</code> is true. * * @param allow if events should be allowed through the provided * EventOutput. * @return the lockable version of this EventOutput. */ @SetupPhase public default EventOutput filter(BooleanInput allow) { if (allow == null) { throw new NullPointerException(); } EventOutput original = this; return () -> { if (allow.get()) { original.event(); } }; } /** * Provides a version of this EventOutput that only propagates events when * <code>deny</code> is false. * * @param deny if events should be disallowed through the provided * EventOutput. * @return the lockable version of this EventOutput. */ @SetupPhase public default EventOutput filterNot(BooleanInput deny) { return this.filter(deny.not()); } /** * Provides a debounced version of this EventOutput, such that if it is * fired within <code>minMillis</code> of the last time it was fired, it * will be ignored. * * Only events that are propagated reset the timer: if an event is ignored, * it does not extend the reactivation deadline. * * @param minMillis the minimum amount of time between events. * @return the debounced version of this EventOutput. */ @SetupPhase public default EventOutput debounce(final long minMillis) { if (minMillis <= 0) { throw new IllegalArgumentException("debounce() parameter must be positive!"); } EventOutput original = this; return new EventOutput() { private long nextFire = 0; @Override public synchronized void event() { long now = Time.currentTimeMillis(); if (now < nextFire) { return; // Ignore event. } nextFire = now + minMillis; original.event(); } }; } /** * Fires this event when the specified event fires. * * <code>what.on(when)</code> is equivalent to <code>when.send(what)</code> * * @param when when to fire this event. * @return an EventOutput that deregisters the registered EventOutput. DO * NOT FIRE THIS RETURNED EVENT MORE THAN ONCE: UNDEFINED BEHAVIOR MAY * RESULT. */ @SetupPhase public default CancelOutput on(EventInput when) { return when.send(this); } /** * Returns a EventIO version of this event. If it is already a EventIO, it * will be returned directly. If it is not, a new EventCell will be created * around this EventOutput. * * @return a new IO */ @SetupPhase public default EventIO cell() { return new EventCell(this); } }