001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2016-2018 FIRST. 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  // The thread waiting on the HAL alarm.
017  private final Thread m_thread;
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  private final 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
035  @Override
036  @SuppressWarnings("NoFinalizer")
037  protected void finalize() {
038    int handle = m_notifier.getAndSet(0);
039    NotifierJNI.stopNotifier(handle);
040    // Join the thread to ensure the handler has exited.
041    if (m_thread.isAlive()) {
042      try {
043        m_thread.interrupt();
044        m_thread.join();
045      } catch (InterruptedException ex) {
046        Thread.currentThread().interrupt();
047      }
048    }
049    NotifierJNI.cleanNotifier(handle);
050  }
051
052  /**
053   * Update the alarm hardware to reflect the next alarm.
054   */
055  private void updateAlarm() {
056    int notifier = m_notifier.get();
057    if (notifier == 0) {
058      return;
059    }
060    NotifierJNI.updateNotifierAlarm(notifier, (long) (m_expirationTime * 1e6));
061  }
062
063  /**
064   * Create a Notifier for timer event notification.
065   *
066   * @param run The handler that is called at the notification time which is set using StartSingle
067   *            or StartPeriodic.
068   */
069  public Notifier(Runnable run) {
070    m_handler = run;
071    m_notifier.set(NotifierJNI.initializeNotifier());
072
073    m_thread = new Thread(() -> {
074      while (!Thread.interrupted()) {
075        int notifier = m_notifier.get();
076        if (notifier == 0) {
077          break;
078        }
079        long curTime = NotifierJNI.waitForNotifierAlarm(notifier);
080        if (curTime == 0) {
081          break;
082        }
083
084        Runnable handler = null;
085        m_processLock.lock();
086        try {
087          handler = m_handler;
088          if (m_periodic) {
089            m_expirationTime += m_period;
090            updateAlarm();
091          }
092        } finally {
093          m_processLock.unlock();
094        }
095
096        if (handler != null) {
097          handler.run();
098        }
099      }
100    });
101    m_thread.setDaemon(true);
102    m_thread.start();
103  }
104
105  /**
106   * Change the handler function.
107   *
108   * @param handler Handler
109   */
110  public void setHandler(Runnable handler) {
111    m_processLock.lock();
112    try {
113      m_handler = handler;
114    } finally {
115      m_processLock.unlock();
116    }
117  }
118
119  /**
120   * Register for single event notification. A timer event is queued for a single event after the
121   * specified delay.
122   *
123   * @param delay Seconds to wait before the handler is called.
124   */
125  public void startSingle(double delay) {
126    m_processLock.lock();
127    try {
128      m_periodic = false;
129      m_period = delay;
130      m_expirationTime = RobotController.getFPGATime() * 1e-6 + delay;
131      updateAlarm();
132    } finally {
133      m_processLock.unlock();
134    }
135  }
136
137  /**
138   * Register for periodic event notification. A timer event is queued for periodic event
139   * notification. Each time the interrupt occurs, the event will be immediately requeued for the
140   * same time interval.
141   *
142   * @param period Period in seconds to call the handler starting one period after the call to this
143   *               method.
144   */
145  public void startPeriodic(double period) {
146    m_processLock.lock();
147    try {
148      m_periodic = true;
149      m_period = period;
150      m_expirationTime = RobotController.getFPGATime() * 1e-6 + period;
151      updateAlarm();
152    } finally {
153      m_processLock.unlock();
154    }
155  }
156
157  /**
158   * Stop timer events from occurring. Stop any repeating timer events from occurring. This will
159   * also remove any single notification events from the queue. If a timer-based call to the
160   * registered handler is in progress, this function will block until the handler call is complete.
161   */
162  public void stop() {
163    NotifierJNI.cancelNotifierAlarm(m_notifier.get());
164  }
165}