001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.cscore;
006
007import java.util.HashMap;
008import java.util.Map;
009import java.util.concurrent.locks.Condition;
010import java.util.concurrent.locks.ReentrantLock;
011import java.util.function.Consumer;
012
013/**
014 * An event listener. This calls back to a designated callback function when an event matching the
015 * specified mask is generated by the library.
016 */
017public class VideoListener implements AutoCloseable {
018  /**
019   * Create an event listener.
020   *
021   * @param listener Listener function
022   * @param eventMask Bitmask of VideoEvent.Type values
023   * @param immediateNotify Whether callback should be immediately called with a representative set
024   *     of events for the current library state.
025   */
026  public VideoListener(Consumer<VideoEvent> listener, int eventMask, boolean immediateNotify) {
027    s_lock.lock();
028    try {
029      if (s_poller == 0) {
030        s_poller = CameraServerJNI.createListenerPoller();
031        startThread();
032      }
033      m_handle = CameraServerJNI.addPolledListener(s_poller, eventMask, immediateNotify);
034      s_listeners.put(m_handle, listener);
035    } finally {
036      s_lock.unlock();
037    }
038  }
039
040  @Override
041  public synchronized void close() {
042    if (m_handle != 0) {
043      s_lock.lock();
044      try {
045        s_listeners.remove(m_handle);
046      } finally {
047        s_lock.unlock();
048      }
049      CameraServerJNI.removeListener(m_handle);
050      m_handle = 0;
051    }
052  }
053
054  /**
055   * Returns true if the video listener handle is valid.
056   *
057   * @return True if the video listener handle is valid.
058   */
059  public boolean isValid() {
060    return m_handle != 0;
061  }
062
063  private int m_handle;
064
065  private static final ReentrantLock s_lock = new ReentrantLock();
066  private static final Map<Integer, Consumer<VideoEvent>> s_listeners = new HashMap<>();
067
068  @SuppressWarnings("PMD.SingularField")
069  private static Thread s_thread;
070
071  private static int s_poller;
072  private static boolean s_waitQueue;
073  private static final Condition s_waitQueueCond = s_lock.newCondition();
074
075  private static void startThread() {
076    s_thread =
077        new Thread(
078            () -> {
079              boolean wasInterrupted = false;
080              while (!Thread.interrupted()) {
081                VideoEvent[] events;
082                try {
083                  events = CameraServerJNI.pollListener(s_poller);
084                } catch (InterruptedException ex) {
085                  s_lock.lock();
086                  try {
087                    if (s_waitQueue) {
088                      s_waitQueue = false;
089                      s_waitQueueCond.signalAll();
090                      continue;
091                    }
092                  } finally {
093                    s_lock.unlock();
094                  }
095                  Thread.currentThread().interrupt();
096                  // don't try to destroy poller, as its handle is likely no longer valid
097                  wasInterrupted = true;
098                  break;
099                }
100                for (VideoEvent event : events) {
101                  Consumer<VideoEvent> listener;
102                  s_lock.lock();
103                  try {
104                    listener = s_listeners.get(event.listener);
105                  } finally {
106                    s_lock.unlock();
107                  }
108                  if (listener != null) {
109                    try {
110                      listener.accept(event);
111                    } catch (Throwable throwable) {
112                      System.err.println(
113                          "Unhandled exception during listener callback: " + throwable);
114                      throwable.printStackTrace();
115                    }
116                  }
117                }
118              }
119              s_lock.lock();
120              try {
121                if (!wasInterrupted) {
122                  CameraServerJNI.destroyListenerPoller(s_poller);
123                }
124                s_poller = 0;
125              } finally {
126                s_lock.unlock();
127              }
128            },
129            "VideoListener");
130    s_thread.setDaemon(true);
131    s_thread.start();
132  }
133}