- PIGrabberBase: replace piMSleep(200)/piMinSleep() polling with interruptible PIConditionVariable::waitFor waits; add wakeUp() handler for event-driven polling and setPollDelay()/pollDelay() for the fallback poll interval - PIPipelineThread: single mutex + cv_not_empty/cv_not_full (bounded queue pattern), terminating in wait predicates so stop/clear wake blocked producers/consumers; drop bogus mutex unlocks in stopCalc() - fix potential bug: calculated() was emitted with 2 of 3 arguments - fix potential bug: connectTo() ABI mismatch for POD Tout (event dispatch passes first arg by value, handler took it by reference) - route through a by-value fromCalculated() adapter - PIOBJECT_SUBCLASS/PIOBJECT_PARENT made variadic so template parents with commas (PIPipelineThread<int, int>) can be passed directly - add gtest suites for both classes (tests/thread/)
352 lines
14 KiB
C++
352 lines
14 KiB
C++
//! \~\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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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<typename T = PIByteArray>
|
|
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<T> que;
|
|
PIDiagnostics diag_;
|
|
PISystemTime poll_delay_;
|
|
PIConditionVariable cv;
|
|
mutable PIMutex que_mutex, last_mutex, wait_mutex;
|
|
};
|
|
|
|
#endif // PIGRABBERBASE_H
|