001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2016-2017. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj;
009
010import java.util.concurrent.atomic.AtomicInteger;
011import java.util.concurrent.locks.ReentrantLock;
012
013import edu.wpi.first.wpilibj.hal.NotifierJNI;
014
015public class Notifier {
016
017  private static class Process implements NotifierJNI.NotifierJNIHandlerFunction {
018    // The lock for the process information.
019    private final ReentrantLock m_processLock = new ReentrantLock();
020    // The C pointer to the notifier object. We don't use it directly, it is
021    // just passed to the JNI bindings.
022    AtomicInteger m_notifier = new AtomicInteger();
023    // The time, in microseconds, at which the corresponding handler should be
024    // called. Has the same zero as Utility.getFPGATime().
025    private double m_expirationTime = 0;
026    // The handler passed in by the user which should be called at the
027    // appropriate interval.
028    private Runnable m_handler;
029    // Whether we are calling the handler just once or periodically.
030    private boolean m_periodic = false;
031    // If periodic, the period of the calling; if just once, stores how long it
032    // is until we call the handler.
033    private double m_period = 0;
034    // Lock on the handler so that the handler is not called before it has
035    // completed. This is only relevant if the handler takes a very long time
036    // to complete (or the period is very short) and when everything is being
037    // destructed.
038    private final ReentrantLock m_handlerLock = new ReentrantLock();
039
040    public Process(Runnable run) {
041      m_handler = run;
042      m_notifier.set(NotifierJNI.initializeNotifier(this));
043    }
044
045    @Override
046    @SuppressWarnings("NoFinalizer")
047    protected void finalize() {
048      int handle = m_notifier.getAndSet(0);
049      NotifierJNI.cleanNotifier(handle);
050      m_handlerLock.lock();
051    }
052
053    /**
054     * Update the alarm hardware to reflect the next alarm.
055     */
056    private void updateAlarm() {
057      NotifierJNI.updateNotifierAlarm(m_notifier.get(), (long) (m_expirationTime * 1e6));
058    }
059
060    /**
061     * Handler which is called by the HAL library; it handles the subsequent calling of the user
062     * handler.
063     */
064    @Override
065    public void apply(long time) {
066      m_processLock.lock();
067      if (m_periodic) {
068        m_expirationTime += m_period;
069        updateAlarm();
070      }
071
072      m_handlerLock.lock();
073      m_processLock.unlock();
074
075      m_handler.run();
076      m_handlerLock.unlock();
077    }
078
079    public void start(double period, boolean periodic) {
080      synchronized (m_processLock) {
081        m_periodic = periodic;
082        m_period = period;
083        m_expirationTime = Utility.getFPGATime() * 1e-6 + period;
084        updateAlarm();
085      }
086    }
087
088    public void stop() {
089      NotifierJNI.stopNotifierAlarm(m_notifier.get());
090
091      // Wait for a currently executing handler to complete before returning
092      // from stop()
093      m_handlerLock.lock();
094      m_handlerLock.unlock();
095    }
096  }
097
098  private Process m_process;
099
100  /**
101   * Create a Notifier for timer event notification.
102   *
103   * @param run The handler that is called at the notification time which is set using StartSingle
104   *            or StartPeriodic.
105   */
106  public Notifier(Runnable run) {
107    m_process = new Process(run);
108  }
109
110  /**
111   * Register for single event notification. A timer event is queued for a single event after the
112   * specified delay.
113   *
114   * @param delay Seconds to wait before the handler is called.
115   */
116  public void startSingle(double delay) {
117    m_process.start(delay, false);
118  }
119
120  /**
121   * Register for periodic event notification. A timer event is queued for periodic event
122   * notification. Each time the interrupt occurs, the event will be immediately requeued for the
123   * same time interval.
124   *
125   * @param period Period in seconds to call the handler starting one period after the call to this
126   *               method.
127   */
128  public void startPeriodic(double period) {
129    m_process.start(period, true);
130  }
131
132  /**
133   * Stop timer events from occuring. Stop any repeating timer events from occuring. This will also
134   * remove any single notification events from the queue. If a timer-based call to the registered
135   * handler is in progress, this function will block until the handler call is complete.
136   */
137  public void stop() {
138    m_process.stop();
139  }
140}