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 desigated 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  public boolean isValid() {
055    return m_handle != 0;
056  }
057
058  private int m_handle;
059
060  private static final ReentrantLock s_lock = new ReentrantLock();
061  private static final Map<Integer, Consumer<VideoEvent>> s_listeners = new HashMap<>();
062  private static Thread s_thread;
063  private static int s_poller;
064  private static boolean s_waitQueue;
065  private static final Condition s_waitQueueCond = s_lock.newCondition();
066
067  @SuppressWarnings("PMD.AvoidCatchingThrowable")
068  private static void startThread() {
069    s_thread =
070        new Thread(
071            () -> {
072              boolean wasInterrupted = false;
073              while (!Thread.interrupted()) {
074                VideoEvent[] events;
075                try {
076                  events = CameraServerJNI.pollListener(s_poller);
077                } catch (InterruptedException ex) {
078                  s_lock.lock();
079                  try {
080                    if (s_waitQueue) {
081                      s_waitQueue = false;
082                      s_waitQueueCond.signalAll();
083                      continue;
084                    }
085                  } finally {
086                    s_lock.unlock();
087                  }
088                  Thread.currentThread().interrupt();
089                  // don't try to destroy poller, as its handle is likely no longer valid
090                  wasInterrupted = true;
091                  break;
092                }
093                for (VideoEvent event : events) {
094                  Consumer<VideoEvent> listener;
095                  s_lock.lock();
096                  try {
097                    listener = s_listeners.get(event.listener);
098                  } finally {
099                    s_lock.unlock();
100                  }
101                  if (listener != null) {
102                    try {
103                      listener.accept(event);
104                    } catch (Throwable throwable) {
105                      System.err.println(
106                          "Unhandled exception during listener callback: " + throwable.toString());
107                      throwable.printStackTrace();
108                    }
109                  }
110                }
111              }
112              s_lock.lock();
113              try {
114                if (!wasInterrupted) {
115                  CameraServerJNI.destroyListenerPoller(s_poller);
116                }
117                s_poller = 0;
118              } finally {
119                s_lock.unlock();
120              }
121            },
122            "VideoListener");
123    s_thread.setDaemon(true);
124    s_thread.start();
125  }
126}