//! \~\file pigrabberbase.h //! \~\ingroup Thread //! \~\brief //! \~english Grabber thread base class //! \~russian Базовый класс потока-граббера /* PIP - Platform Independent Primitives Grabber thread base class Andrey Bychkov work.a.b@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #ifndef PIGRABBERBASE_H #define PIGRABBERBASE_H #include "piconditionvar.h" #include "pidiagnostics.h" #include "pithread.h" //! \~\ingroup Thread //! \~\brief //! \~english Base polling grabber thread with a pending queue, recording virtual methods, and a last-item snapshot. //! \~russian Базовый поток-граббер с очередью ожидающих элементов, виртуальными методами записи и снимком последнего элемента. //! \~\details //! \~english //! Captured items are appended to the internal queue and also copied into \a last(). The snapshot is independent from the queue. //! \~russian //! Захваченные элементы добавляются во внутреннюю очередь и также копируются в \a last(). Этот снимок не зависит от очереди. template class PIGrabberBase: public PIThread { PIOBJECT_SUBCLASS(PIGrabberBase, PIThread); public: //! \~english Constructs a closed non-recording grabber. //! \~russian Создает закрытый граббер без записи. PIGrabberBase() { is_opened = false; is_recording = false; poll_delay_ = PISystemTime::fromMilliseconds(PIP_MIN_MSLEEP); } //! \~english Stops the grabber thread and releases recording/open state. //! \~russian Останавливает поток-граббер и освобождает состояние записи и открытия. virtual ~PIGrabberBase() { stopGrabber(false); } //! \~english Returns whether the grabber is currently opened. //! \~russian Возвращает, открыт ли сейчас граббер. virtual bool isOpened() const { return is_opened; } //! \~english Returns whether captured items are currently being recorded. //! \~russian Возвращает, записываются ли сейчас захваченные элементы. virtual bool isRecording() const { return is_recording; } //! \~english Starts recording subsequent captured items if the grabber is opened and recording is not active yet. //! \~russian Запускает запись последующих захваченных элементов, если граббер открыт и запись еще не активна. virtual void startRecord(const PIString & filename) { if (!isOpened()) return; if (isRecording()) return; rec_mutex.lock(); startRecordInternal(filename); is_recording = true; rec_mutex.unlock(); } //! \~english Stops recording if it is active. //! \~russian Останавливает запись, если она активна. virtual void stopRecord() { if (!isOpened()) return; if (!isRecording()) return; rec_mutex.lock(); is_recording = false; stopRecordInternal(); rec_mutex.unlock(); } //! \~english Returns a copy of the last successfully captured item. This snapshot is kept separately from the pending queue and is not //! consumed by \a dequeue(). //! \~russian Возвращает копию последнего успешно захваченного элемента. Этот снимок хранится отдельно от очереди ожидания и не //! извлекается через \a dequeue(). T last() const { T ret; last_mutex.lock(); ret = last_; last_mutex.unlock(); return ret; } //! \~english Returns whether the pending queue is empty. //! \~russian Возвращает, пуста ли очередь ожидающих элементов. bool isEmpty() { bool ret; que_mutex.lock(); ret = que.isEmpty(); que_mutex.unlock(); return ret; } //! \~english Returns the current number of pending captured items. //! \~russian Возвращает текущее количество ожидающих захваченных элементов. int queSize() { int ret; que_mutex.lock(); ret = que.size(); que_mutex.unlock(); return ret; } //! \~english Dequeues and returns the oldest pending captured item. If the queue is empty, returns a default-constructed value. //! \~russian Извлекает и возвращает самый старый ожидающий захваченный элемент. Если очередь пуста, возвращает значение, созданное //! конструктором по умолчанию. T dequeue() { T ret; // piCoutObj << "start"; que_mutex.lock(); if (!que.isEmpty()) { // piCoutObj << "dequeue"; ret = que.dequeue(); } // piCoutObj << "end"; que_mutex.unlock(); return ret; } //! \~english Stops the grabber thread. With \a wait_forever equal to \c false the method waits briefly and may terminate the thread //! forcibly. //! \~russian Останавливает поток-граббер. При \a wait_forever равном \c false метод ждет недолго и может принудительно завершить поток. void stopGrabber(bool wait_forever = true) { if (isRunning()) { stop(); cv.notifyAll(); if (wait_forever) waitForFinish(); else { if (!waitForFinish(100)) terminate(); stopRecord(); close(); } } } //! \~english Opens the grabber immediately. The thread loop also tries to open it automatically when running in a closed state. //! \~russian Немедленно открывает граббер. Цикл потока также пытается открыть его автоматически, если поток работает в закрытом //! состоянии. bool open() { bool ret = openInternal(); if (!is_opened && ret) opened(); is_opened = ret; return ret; } //! \~english Closes the grabber and resets the last-item snapshot. //! \~russian Закрывает граббер и сбрасывает снимок последнего элемента. void close() { bool em = is_opened; closeInternal(); last_ = T(); if (em) closed(); is_opened = false; } //! \~english Returns diagnostics collected for captured and recorded items. //! \~russian Возвращает диагностику для захваченных и записанных элементов. const PIDiagnostics & diag() const { return diag_; } //! \~english Clears only the pending queue. The value returned by \a last() is not changed. //! \~russian Очищает только очередь ожидающих элементов. Значение, возвращаемое \a last(), не изменяется. void clear() { que_mutex.lock(); que.clear(); que_mutex.unlock(); } //! \~english Clears the pending queue and closes the grabber. //! \~russian Очищает очередь ожидания и закрывает граббер. void restart() { clear(); close(); } //! \events //! \{ //! \fn void dataReady() //! \brief //! \~english Raised after a new item has been captured, queued, and copied into \a last(). //! \~russian Вызывается после захвата нового элемента, его добавления в очередь и копирования в \a last(). EVENT(dataReady); //! \fn void opened() //! \brief //! \~english Raised when \a open() switches the grabber into the opened state. //! \~russian Вызывается, когда \a open() переводит граббер в открытое состояние. EVENT(opened); //! \fn void closed() //! \brief //! \~english Raised when \a close() closes a previously opened grabber. //! \~russian Вызывается, когда \a close() закрывает ранее открытый граббер. EVENT(closed); //! \} //! \handlers //! \{ //! \~\fn void wakeUp() //! \~\brief //! \~english Wakes the grabber loop from its wait between polls. //! \~russian Пробуждает цикл граббера из ожидания между опросами. //! \~\details //! \~english //! Call this (directly or via event connection) from the device layer when new data may be available. //! \~It is safe to call from any thread. The next \a get() poll happens immediately instead of after the //! \a pollDelay() interval. //! \~russian //! Вызывайте это (напрямую или через подключение события) из устройства, когда могут появиться новые данные. //! \~Потокбезопасно из любого потока. Следующий опрос \a get() произойдет немедленно, а не через интервал //! \a pollDelay(). EVENT_HANDLER0(void, wakeUp) { cv.notifyAll(); } //! \} //! \~english Returns the fallback interval between polls when no wake-up was requested. //! \~russian Возвращает резервный интервал между опросами, если не было запрошено пробуждения. PISystemTime pollDelay() const { PIMutexLocker locker(wait_mutex); return poll_delay_; } //! \~english Sets the fallback interval between polls when no wake-up was requested. //! \~russian Устанавливает резервный интервал между опросами, если не было запрошено пробуждения. //! \~\details //! \~english //! The grabber loop waits on a condition variable for up to this interval between \a get() polls and wakes //! immediately on \a wakeUp() or stop. A larger value saves CPU when data is rare; a smaller value reduces //! worst-case capture latency. Default is \a PIP_MIN_MSLEEP milliseconds. //! \~russian //! Цикл граббера ожидает на переменной условия не дольше этого интервала между опросами \a get() и //! пробуждается немедленно по \a wakeUp() или при остановке. Большее значение экономит CPU при редких //! данных; меньшее уменьшает худшую задержку захвата. По умолчанию \a PIP_MIN_MSLEEP миллисекунд. //! \~\note //! \~english If the grabber is currently waiting, it is woken up to apply the new interval. //! \~russian Если граббер сейчас ожидает, он пробуждается для применения нового интервала. void setPollDelay(PISystemTime d) { PIMutexLocker locker(wait_mutex); poll_delay_ = d; cv.notifyAll(); } protected: //! \~english Virtual method executed once when the thread starts, before polling begins. //! \~russian Виртуальный метод, выполняемый один раз при старте потока до начала опроса. virtual void init() {} //! \~english Opens the underlying grabber resource. //! \~russian Открывает базовый ресурс граббера. virtual bool openInternal() = 0; //! \~english Closes the underlying grabber resource. //! \~russian Закрывает базовый ресурс граббера. virtual void closeInternal() = 0; //! \~english Polls the next item into \a val. Return \c 0 when a new item was captured, a positive value when no item is ready yet, and //! a negative value on failure that should close the grabber. //! \~russian Опрашивает следующий элемент в \a val. Возвращает \c 0, когда новый элемент захвачен, положительное значение, когда //! элемент еще не готов, и отрицательное значение при ошибке, после которой граббер должен закрыться. virtual int get(T & val) = 0; //! \~english Records a captured item when recording mode is active. //! \~russian Записывает захваченный элемент, когда активен режим записи. virtual void record(const T & val) {} //! \~english Virtual method called before the recording flag becomes active. //! \~russian Виртуальный метод, вызываемый перед активацией флага записи. virtual void startRecordInternal(const PIString & filename) {} //! \~english Virtual method called after the recording flag is cleared. //! \~russian Виртуальный метод, вызываемый после сброса флага записи. virtual void stopRecordInternal() {} bool is_opened, is_recording; mutable PIMutex rec_mutex; private: void begin() override { init(); } void run() override { if (!isOpened()) { open(); diag_.reset(); if (!is_opened) { wait_mutex.lock(); cv.waitFor(wait_mutex, PISystemTime::fromMilliseconds(200)); wait_mutex.unlock(); return; } } if (isOpened()) { T c; int ret = get(c); if (ret < 0) { close(); return; } if (ret > 0) { wait_mutex.lock(); cv.waitFor(wait_mutex, poll_delay_); wait_mutex.unlock(); return; } diag_.received(1); que_mutex.lock(); que.enqueue(c); que_mutex.unlock(); if (isRecording()) { rec_mutex.lock(); record(c); rec_mutex.unlock(); diag_.sended(1); } last_mutex.lock(); last_ = c; last_mutex.unlock(); dataReady(); } } void end() override { stopRecord(); close(); } T last_; PIQueue que; PIDiagnostics diag_; PISystemTime poll_delay_; PIConditionVariable cv; mutable PIMutex que_mutex, last_mutex, wait_mutex; }; #endif // PIGRABBERBASE_H