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