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