From b12bd7d7be78df9900e26ed5ea8bf29d1636cc3b Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Tue, 21 Feb 2017 05:04:53 -0500 Subject: [PATCH] [Simulator] Add asynchronous FIFO buffer for handling debug output/display more efficiently. (#4488) --- companion/src/simulation/CMakeLists.txt | 2 + companion/src/simulation/debugoutput.cpp | 184 ++++------ companion/src/simulation/debugoutput.h | 37 +- companion/src/simulation/debugoutput.ui | 15 + .../src/simulation/filteredtextbuffer.cpp | 342 ++++++++++++++++++ companion/src/simulation/filteredtextbuffer.h | 118 ++++++ .../src/simulation/simulatormainwindow.cpp | 8 +- 7 files changed, 584 insertions(+), 122 deletions(-) create mode 100644 companion/src/simulation/filteredtextbuffer.cpp create mode 100644 companion/src/simulation/filteredtextbuffer.h diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index c1566830c..ed9709e9b 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -1,5 +1,6 @@ set(simulation_SRCS debugoutput.cpp + filteredtextbuffer.cpp radiooutputswidget.cpp simulateduiwidget.cpp simulateduiwidget9X.cpp @@ -34,6 +35,7 @@ set(simulation_UIS set(simulation_HDRS debugoutput.h + filteredtextbuffer.h radiooutputswidget.h radiouiaction.h simulateduiwidget.h diff --git a/companion/src/simulation/debugoutput.cpp b/companion/src/simulation/debugoutput.cpp index 020944fed..66c5e43d6 100644 --- a/companion/src/simulation/debugoutput.cpp +++ b/companion/src/simulation/debugoutput.cpp @@ -22,55 +22,49 @@ #include "ui_debugoutput.h" #include "appdata.h" +#include "filteredtextbuffer.h" +#include #include +#include #include +#include #include #define DEBUG_OUTPUT_STATE_VERSION 1 extern AppData g; // ensure what "g" means -DebugOutput * traceCallbackInstance = 0; -const int DebugOutput::m_dataBufferMaxSize = 500; // lines of text (this is not the display buffer) -const int DebugOutput::m_dataPrintFreqDefault = 8; // ms +FilteredTextBuffer * DebugOutput::m_dataBufferDevice = Q_NULLPTR; const quint16 DebugOutput::m_savedViewStateVersion = 1; -void traceCb(const char * text) +void firmwareTraceCb(const char * text) { - // divert C callback into simulator instance - if (traceCallbackInstance) { - traceCallbackInstance->traceCallback(text); + if (DebugOutput::m_dataBufferDevice) { + DebugOutput::m_dataBufferDevice->write(text); } } -// TODO: move callback & filter to own thread - DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): QWidget(parent), ui(new Ui::DebugOutput), m_simulator(simulator), - m_tmrDataPrint(new QTimer()), - m_dataBuffer(QByteArray()), m_radioProfileId(g.sessionId()), - m_dataPrintFreq(m_dataPrintFreqDefault), - m_running(false), - m_filterExclude(true), - m_overflowReported(false) + m_filterEnable(false), + m_filterExclude(false) { ui->setupUi(this); #ifdef __APPLE__ - QFont newFont("Courier", 13); - ui->console->setFont(newFont); + ui->console->setFont(QFont("Courier", 13)); #endif // TODO : allow selecting multiple filters, but needs to be efficient at output stage QStringList stockFilters; - stockFilters << "^lua[A-Z].*"; + stockFilters << "/^(lua[A-Z]|script).*/i"; stockFilters << "/(error|warning|-(E|W)-)/i"; - stockFilters << "!^(GC Use|(play|load|write|find(True)?)File|convert(To|From)Simu|\\t(not )?found( in map)?:?|eeprom |f_[a-z]+\\(|(push|(p|P)op(up)?|chain)? ?Menu( .+ display)?|RamBackup).+$"; + stockFilters << "!^(GC Use|(play|load|write|find(True)?)File|convert(To|From)Simu|\\t(not found|found( in map|\\:))|eeprom |f_[a-z]+\\(|(push|(p|P)op(up)?|chain)? ?Menu( .+ display)?|RamBackup).*$"; foreach (const QString & fltr, stockFilters) ui->filterText->addItem(fltr, "no_delete"); @@ -82,48 +76,49 @@ DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): ui->actionWordWrap->setIcon(SimulatorIcon("word_wrap")); ui->actionClearScr->setIcon(SimulatorIcon("eraser")); + ui->btnFilter->setDefaultAction(ui->actionToggleFilter); ui->btnShowFilterHelp->setDefaultAction(ui->actionShowFilterHelp); ui->btnWordWrap->setDefaultAction(ui->actionWordWrap); ui->btnClearScr->setDefaultAction(ui->actionClearScr); + m_dataBufferDevice = new FilteredTextBuffer(); + m_dataBufferDevice->setDataBufferMaxSize(DEBUG_OUTPUT_WIDGET_OUT_BUFF_SIZE); + m_dataBufferDevice->setInputBufferMaxSize(DEBUG_OUTPUT_WIDGET_INP_BUFF_SIZE); + m_dataBufferDevice->open(QIODevice::ReadWrite | QIODevice::Text); + + connect(m_dataBufferDevice, &FilteredTextBuffer::readyRead, this, &DebugOutput::processBytesReceived); + connect(m_dataBufferDevice, &FilteredTextBuffer::bufferOverflow, this, &DebugOutput::onDataBufferOverflow); + connect(this, &DebugOutput::filterChanged, m_dataBufferDevice, &FilteredTextBuffer::setLineFilter); + connect(this, &DebugOutput::filterEnabledChanged, m_dataBufferDevice, &FilteredTextBuffer::setLineFilterEnabled); + connect(this, &DebugOutput::filterExprChanged, m_dataBufferDevice, &FilteredTextBuffer::setLineFilterExpr); + connect(this, &DebugOutput::filterExclusiveChanged, m_dataBufferDevice, &FilteredTextBuffer::setLineFilterExclusive); + restoreState(); ui->bufferSize->setValue(ui->console->maximumBlockCount()); - // install simulator TRACE hook - traceCallbackInstance = this; - m_simulator->installTraceHook(traceCb); - - m_tmrDataPrint->setInterval(m_dataPrintFreq); - + connect(ui->actionToggleFilter, &QAction::toggled, this, &DebugOutput::onFilterToggled); connect(ui->filterText, &QComboBox::currentTextChanged, this, &DebugOutput::onFilterTextChanged); - connect(m_tmrDataPrint, &QTimer::timeout, this, &DebugOutput::processBytesReceived); - start(); + // send firmware TRACE events to our data collector + m_simulator->installTraceHook(firmwareTraceCb); } DebugOutput::~DebugOutput() { - traceCallbackInstance = 0; - stop(); saveState(); - if (m_tmrDataPrint) - delete m_tmrDataPrint; + + + if (m_dataBufferDevice) { + disconnect(m_dataBufferDevice, 0, this, 0); + disconnect(this, 0, m_dataBufferDevice, 0); + m_dataBufferDevice->deleteLater(); + m_dataBufferDevice = Q_NULLPTR; + } + delete ui; } -void DebugOutput::start() -{ - m_tmrDataPrint->start(); - m_running = true; -} - -void DebugOutput::stop() -{ - m_tmrDataPrint->stop(); - m_running = false; -} - void DebugOutput::saveState() { QStringList filters; @@ -137,7 +132,7 @@ void DebugOutput::saveState() QDataStream stream(&state, QIODevice::WriteOnly); stream << m_savedViewStateVersion << (qint16)ui->filterText->currentIndex() << (qint32)ui->console->maximumBlockCount() - << ui->btnFilter->isChecked() << ui->actionWordWrap->isChecked(); + << m_filterEnable << ui->actionWordWrap->isChecked(); SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions(); opts.dbgConsoleState = state; @@ -159,61 +154,23 @@ void DebugOutput::restoreState() ui->filterText->insertItems(0, g.simuDbgFilters()); ui->filterText->setCurrentIndex(fci); - ui->btnFilter->setChecked(flten); ui->console->setMaximumBlockCount(mbc); ui->actionWordWrap->setChecked(wwen); - onFilterTextEdited(); -} -void DebugOutput::traceCallback(const char * text) -{ - const static QRegExp blank("^[\\r\\n]+$"); - bool isBlank; - - if (!m_running) - return; - - QString line(text); - isBlank = line.contains(blank); - - m_mtxDataBuffer.lock(); - if (isBlank && m_dataBuffer.size()) { - m_dataBuffer[m_dataBuffer.size()-1] += line; - } - else { - if (m_dataBuffer.size() > m_dataBufferMaxSize) { - m_dataBuffer.removeFirst(); - if (!m_overflowReported) { - m_overflowReported = true; - qWarning() << "Line buffer overflow! size >" << m_dataBufferMaxSize; - } - } - m_dataBuffer.append(text); - } - m_mtxDataBuffer.unlock(); + onFilterToggled(flten); } void DebugOutput::processBytesReceived() { - QString text; - bool fltMatch; - static quint32 cycleCount = 0; const QTextCursor savedCursor(ui->console->textCursor()); const int sbValue = ui->console->verticalScrollBar()->value(); const bool sbAtBottom = (sbValue == ui->console->verticalScrollBar()->maximum()); + qint64 len; - m_tmrDataPrint->stop(); - while (m_dataBuffer.size() > 0) { - m_mtxDataBuffer.lock(); - text = m_dataBuffer.takeFirst(); - m_mtxDataBuffer.unlock(); - // filter - if (ui->btnFilter->isChecked()) { - fltMatch = text.contains(m_filterRegEx); - if ((m_filterExclude && fltMatch) || (!m_filterExclude && !fltMatch)) { - continue; - } - } + while ((len = m_dataBufferDevice->bytesAvailable()) > 0) { + QString text(m_dataBufferDevice->read(qMin(len, qint64(512)))); + if (text.isEmpty()) + break; ui->console->moveCursor(QTextCursor::End); ui->console->textCursor().insertText(text); if (sbAtBottom) { @@ -226,43 +183,62 @@ void DebugOutput::processBytesReceived() } QCoreApplication::processEvents(); } - m_tmrDataPrint->start(); +} - ++cycleCount; +void DebugOutput::onDataBufferOverflow(const qint64 len) +{ + static QElapsedTimer reportTimer; - if (!(cycleCount % (1000 / m_dataPrintFreqDefault * 30))) - m_overflowReported = false; + if (len <= 0) { + reportTimer.invalidate(); + } + else if (!reportTimer.isValid() || reportTimer.elapsed() > 1000 * 30) { + qWarning("Data buffer overflow by %lld bytes!", len); + reportTimer.start(); + } } /* * UI handlers */ -void DebugOutput::onFilterTextEdited() +void DebugOutput::onFilterStateChanged() { const QString fText = ui->filterText->currentText(); if (fText.isEmpty()) { - ui->btnFilter->setChecked(false); - m_filterRegEx = QRegularExpression(); + onFilterToggled(false); return; } - m_filterRegEx = makeRegEx(fText, &m_filterExclude); + QRegularExpression filterRegEx = makeRegEx(fText, &m_filterExclude); - if (m_filterRegEx.isValid()) { - //ui->btnFilter->setChecked(true); + if (!m_filterEnable || filterRegEx.isValid()) ui->filterText->setStyleSheet(""); - } - else { - ui->btnFilter->setChecked(false); - m_filterRegEx = QRegularExpression(); + else if (m_filterEnable) ui->filterText->setStyleSheet("background-color: rgba(255, 205, 185, 200);"); - } + + if (filterRegEx.isValid()) + emit filterChanged(m_filterEnable, m_filterExclude, filterRegEx); + else + onFilterToggled(false); } void DebugOutput::onFilterTextChanged(const QString &) { - onFilterTextEdited(); + onFilterStateChanged(); +} + +void DebugOutput::onFilterToggled(bool enable) +{ + if (enable != m_filterEnable) { + m_filterEnable = enable; + if (ui->actionToggleFilter->isChecked() != enable) + ui->actionToggleFilter->setChecked(enable); + if (enable) + onFilterStateChanged(); + else + emit filterEnabledChanged(false); + } } void DebugOutput::on_bufferSize_editingFinished() diff --git a/companion/src/simulation/debugoutput.h b/companion/src/simulation/debugoutput.h index 5e81e1b3b..33dd4e635 100644 --- a/companion/src/simulation/debugoutput.h +++ b/companion/src/simulation/debugoutput.h @@ -29,11 +29,22 @@ #include #include +// NOTE : The buffer sizes need to be large enough to handle the flood of data when X12/X10 simulator starts up (almost 40K!). +#ifndef DEBUG_OUTPUT_WIDGET_OUT_BUFF_SIZE + // This buffer holds received and processed data until it can be printed to our console. + #define DEBUG_OUTPUT_WIDGET_OUT_BUFF_SIZE (40 * 1024) // [bytes] +#endif +#ifndef DEBUG_OUTPUT_WIDGET_INP_BUFF_SIZE + // This buffer is active if line filter is enabled and holds received data until it can be filtered and placed in output buffer. + #define DEBUG_OUTPUT_WIDGET_INP_BUFF_SIZE (30 * 1024) // [bytes] +#endif + namespace Ui { class DebugOutput; } class QAbstractButton; +class FilteredTextBuffer; using namespace Simulator; @@ -44,18 +55,25 @@ class DebugOutput : public QWidget public: explicit DebugOutput(QWidget * parent, SimulatorInterface * simulator); virtual ~DebugOutput(); - void start(); - void stop(); - void traceCallback(const char * text); static QRegularExpression makeRegEx(const QString & input, bool * isExlusive = NULL); + static FilteredTextBuffer * m_dataBufferDevice; + + signals: + void filterExprChanged(const QRegularExpression & expr); + void filterEnabledChanged(const bool enabled); + void filterExclusiveChanged(const bool exlusive); + void filterChanged(bool enable, bool exclusive, const QRegularExpression & expr); + protected slots: void saveState(); void restoreState(); void processBytesReceived(); - void onFilterTextEdited(); + void onDataBufferOverflow(const qint64 len); + void onFilterStateChanged(); void onFilterTextChanged(const QString &); + void onFilterToggled(bool enable); void on_bufferSize_editingFinished(); void on_actionWordWrap_toggled(bool checked); void on_actionClearScr_triggered(); @@ -64,19 +82,10 @@ class DebugOutput : public QWidget protected: Ui::DebugOutput * ui; SimulatorInterface * m_simulator; - QTimer * m_tmrDataPrint; - QStringList m_dataBuffer; - QMutex m_mtxDataBuffer; - QRegularExpression m_filterRegEx; - int m_radioProfileId; - int m_dataPrintFreq; - bool m_running; + bool m_filterEnable; bool m_filterExclude; - bool m_overflowReported; - const static int m_dataBufferMaxSize; - const static int m_dataPrintFreqDefault; const static quint16 m_savedViewStateVersion; }; diff --git a/companion/src/simulation/debugoutput.ui b/companion/src/simulation/debugoutput.ui index 9a1fce02e..444ef8c44 100644 --- a/companion/src/simulation/debugoutput.ui +++ b/companion/src/simulation/debugoutput.ui @@ -304,6 +304,21 @@ To <b>remove a remembered entry</b> from the filter list, first cho Clear the output window of all text. + + + true + + + + :/images/simulator/icons/svg/toggle_lock.svg:/images/simulator/icons/svg/toggle_lock.svg + + + Enable &Filter + + + Turn the filter on/off. + + diff --git a/companion/src/simulation/filteredtextbuffer.cpp b/companion/src/simulation/filteredtextbuffer.cpp new file mode 100644 index 000000000..75f50cd51 --- /dev/null +++ b/companion/src/simulation/filteredtextbuffer.cpp @@ -0,0 +1,342 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#include "filteredtextbuffer.h" + +#include +#include + +/* + * + * FIFOBufferDevice + * + */ + +FIFOBufferDevice::FIFOBufferDevice(QObject * parent) : + QBuffer(parent), + m_dataBufferMaxSize(20 * 1024), + m_hasOverflow(false) +{ +} + +qint64 FIFOBufferDevice::getDataBufferMaxSize() const +{ + return m_dataBufferMaxSize; +} + +void FIFOBufferDevice::setDataBufferMaxSize(qint64 size) +{ + if (m_dataBufferMaxSize > size) + trimData(m_dataBufferMaxSize - size); + + m_dataBufferMaxSize = size; +} + +// Remove data from beginning of storage array. +// NOT thread-safe, lock data before use (this avoids needing a recursive mutex) +qint64 FIFOBufferDevice::trimData(qint64 len) +{ + if (!isWritable()) + return 0; + + qint64 count = 0; + if (len > 0) { + count = qMin(len, (qint64)buffer().size()); + buffer().remove(0, count); + seek(0); + } + return count; +} + +qint64 FIFOBufferDevice::writeData(const char * data, qint64 len) +{ + if (!isWritable()) + return -1; + + QWriteLocker locker(&m_dataRWLock); + + // Handle overflow + if (size() + len > m_dataBufferMaxSize) { + m_hasOverflow = true; + qint64 tlen = trimData(size() + len - m_dataBufferMaxSize); + emit bufferOverflow(tlen); + } + else if (m_hasOverflow) { + m_hasOverflow = false; + emit bufferOverflow(0); + } + + // Always write to end of stream. + if (!seek(size())) + return -1; + + // Save the data + len = QBuffer::writeData(data, len); + // Make sure we're always ready for reading, this makes bytesAvailable() (et.al.) return correct result. + seek(0); + + return len; +} + +qint64 FIFOBufferDevice::readData(char * data, qint64 len) +{ + if (!isReadable()) + return 0; + + // Do not block + if (m_dataRWLock.tryLockForRead()) { + // Always take data from top + if (seek(0)) { + len = QBuffer::readData(data, len); + trimData(len); + } + m_dataRWLock.unlock(); + } + + return len; +} + +qint64 FIFOBufferDevice::readLine(char * data, qint64 maxSize) +{ + qint64 len = buffer().indexOf('\n', 0); + + if (len < 0 || maxSize <= 0) + return 0; + + ++len; + len = qMin(len, maxSize); + return readData(data, len); +} + +QByteArray FIFOBufferDevice::readLine(qint64 maxSize) +{ + QByteArray ba; + qint64 len = buffer().indexOf('\n', 0); + + if (len < 0) + return ba; + + ++len; + if (maxSize > 0) + len = qMin(len, maxSize); + + ba.fill(0, len); + len = readData(ba.data(), len); + + if (len < 0) + len = 0; + if (len < ba.size()) + ba.resize(len); + + return ba; +} + + +/* + * + * FilteredTextBuffer + * + */ + +FilteredTextBuffer::FilteredTextBuffer(QObject * parent) : + FIFOBufferDevice(parent), + m_inBuffer(new FIFOBufferDevice(this)), + m_bufferFlushTimer(new QTimer(this)), + m_lineFilter(QRegularExpression()), + m_inBuffMaxSize(5 * 1024), + m_inBuffFlushTimeout(1500), + m_lineFilterEnable(false), + m_lineFilterExclusive(false) +{ + m_bufferFlushTimer->setSingleShot(true); + setInputBufferMaxSize(m_inBuffMaxSize); + setInputBufferTimeout(m_inBuffFlushTimeout); + + connect(m_inBuffer, &FIFOBufferDevice::readyRead, this, &FilteredTextBuffer::processInputBuffer); + connect(m_inBuffer, &FIFOBufferDevice::bytesWritten, this, &FilteredTextBuffer::onInputBufferWrite); + connect(m_inBuffer, &FIFOBufferDevice::bufferOverflow, this, &FilteredTextBuffer::onInputBufferOverflow); + connect(m_bufferFlushTimer, &QTimer::timeout, this, &FilteredTextBuffer::processInputBuffer); + connect(this, &FilteredTextBuffer::timerStart, m_bufferFlushTimer, static_cast(&QTimer::start)); + connect(this, &FilteredTextBuffer::timerStop, m_bufferFlushTimer, &QTimer::stop); +} + +FilteredTextBuffer::~FilteredTextBuffer() +{ + if (m_bufferFlushTimer) { + disconnect(m_bufferFlushTimer, 0, this, 0); + disconnect(this, 0, m_bufferFlushTimer, 0); + m_bufferFlushTimer->deleteLater(); + m_bufferFlushTimer = Q_NULLPTR; + } + if (m_inBuffer) { + closeInputBuffer(); + disconnect(m_inBuffer, 0, this, 0); + m_inBuffer->deleteLater(); + m_inBuffer = Q_NULLPTR; + } +} + +qint64 FilteredTextBuffer::getInputBufferMaxSize() const +{ + return m_inBuffMaxSize; +} + +quint32 FilteredTextBuffer::getInputBufferTimeout() const +{ + return m_inBuffFlushTimeout; +} + +void FilteredTextBuffer::setInputBufferMaxSize(qint64 size) +{ + m_inBuffMaxSize = size; + if (m_inBuffer) + m_inBuffer->setDataBufferMaxSize(size); +} + +void FilteredTextBuffer::setInputBufferTimeout(quint32 ms) +{ + m_inBuffFlushTimeout = ms; + if (m_bufferFlushTimer) + m_bufferFlushTimer->setInterval(ms); +} + +void FilteredTextBuffer::setLineFilterExpr(const QRegularExpression & expr) +{ + if (expr.isValid()) + m_lineFilter = expr; + else + setLineFilterEnabled(false); +} + +void FilteredTextBuffer::setLineFilterEnabled(bool enable) +{ + if (!enable && m_lineFilterEnable) + closeInputBuffer(); + + m_lineFilterEnable = enable; +} + +void FilteredTextBuffer::setLineFilterExclusive(bool exclusive) +{ + m_lineFilterExclusive = exclusive; +} + +void FilteredTextBuffer::setLineFilter(bool enable, bool exclusive, const QRegularExpression & expr) +{ + setLineFilterEnabled(enable); + setLineFilterExclusive(exclusive); + setLineFilterExpr(expr); +} + +qint64 FilteredTextBuffer::writeDataSuper(const char * data, qint64 len) +{ + if (len == -1) + len = qstrlen(data); + + return FIFOBufferDevice::writeData(data, len); +} + +qint64 FilteredTextBuffer::writeData(const char * data, qint64 len) +{ + if (!isWritable()) + return -1; + + // if filter is disabled, invalid, or input buffer failure, write directly to output buffer + if (!m_lineFilterEnable || !m_inBuffer || len > m_inBuffMaxSize || (!m_inBuffer->isOpen() && !openInputBuffer())) + return writeDataSuper(data, len); + + // check for input buffer overflow + if (m_inBuffer->bytesAvailable() + len > m_inBuffMaxSize) { + flushInputBuffer(); + onInputBufferOverflow(m_inBuffer->bytesAvailable() + len - m_inBuffMaxSize); + } + + return m_inBuffer->write(data, len); +} + +void FilteredTextBuffer::flushInputBuffer() +{ + emit timerStop(); + + if (m_inBuffer && m_inBuffer->bytesAvailable()) { + // qDebug() << "Flushing input buffer."; + writeDataSuper(m_inBuffer->readAll().constData()); + } +} + +void FilteredTextBuffer::closeInputBuffer() +{ + if (!m_inBuffer || !m_inBuffer->isOpen()) + return; + + flushInputBuffer(); + m_inBuffer->close(); +} + +bool FilteredTextBuffer::openInputBuffer() +{ + if (m_inBuffer && m_inBuffer->isOpen()) + closeInputBuffer(); + + return m_inBuffer->open(ReadWrite); +} + +void FilteredTextBuffer::processInputBuffer() +{ + if (m_lineFilterEnable && m_inBuffer && m_inBuffer->canReadLine()) { + emit timerStop(); + while (m_inBuffer && m_inBuffer->canReadLine()) { + QByteArray text = m_inBuffer->readLine(); + bool fltMatch = QString(text).contains(m_lineFilter); + // check line against filter + if ((m_lineFilterExclusive && !fltMatch) || (!m_lineFilterExclusive && fltMatch)) { + // Write line to output buffer + writeDataSuper(text.constData()); + } + } + // restart timer if unread bytes still remain + if (m_inBuffer && m_inBuffer->bytesAvailable() > 0) + emit timerStart(); + } + else if (!m_lineFilterEnable || !m_inBuffer) { + closeInputBuffer(); + } + else if (m_bufferFlushTimer && !m_bufferFlushTimer->remainingTime()) { + flushInputBuffer(); + //qDebug() << "Input buffer timeout."; + } +} +void FilteredTextBuffer::onInputBufferWrite(qint64) +{ + emit timerStart(); +} + +void FilteredTextBuffer::onInputBufferOverflow(const qint64 len) +{ + static QElapsedTimer reportTimer; + + if (len <= 0) { + reportTimer.invalidate(); + } + else if (!reportTimer.isValid() || reportTimer.elapsed() > 1000 * 15) { + qWarning("Input data buffer overflow by %lld bytes!", len); + reportTimer.start(); + } +} diff --git a/companion/src/simulation/filteredtextbuffer.h b/companion/src/simulation/filteredtextbuffer.h new file mode 100644 index 000000000..893f44e40 --- /dev/null +++ b/companion/src/simulation/filteredtextbuffer.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + */ + +#ifndef FILTEREDTEXTBUFFER_H +#define FILTEREDTEXTBUFFER_H + +#include +#include +#include +#include + +/* + * FIFOBufferDevice implements a thread-safe, asynchronous, buffered, FIFO I/O device based on QBuffer (which is a QIODevice). + * Data is removed from the beginning after each read operation (read(), readAll(), readLine(), etc). + * Also unlike QBuffer, it will NOT grow in an unlmited fashion. Even if never read, size is constrained to dataBufferMaxSize. + * Default maximum buffer size 20KB. + */ +class FIFOBufferDevice : public QBuffer +{ + Q_OBJECT + + public: + explicit FIFOBufferDevice(QObject * parent = Q_NULLPTR); + + qint64 getDataBufferMaxSize() const; + void setDataBufferMaxSize(qint64 size); + + // reimplemented from QIODevice for efficiency + qint64 readLine(char * data, qint64 maxSize); + QByteArray readLine(qint64 maxSize = 0); + + signals: + void bufferOverflow(qint64 len); // len is overflow size, <= 0 means overflow has cleared + + protected: + qint64 trimData(qint64 len); + virtual qint64 writeData(const char * data, qint64 len); + virtual qint64 readData(char * data, qint64 len); + + QReadWriteLock m_dataRWLock; + qint64 m_dataBufferMaxSize; // [bytes] output buffer limit (FIFO). + bool m_hasOverflow; +}; + + +/* + * FilteredTextBuffer implements a FIFOBufferDevice which can, optionally, have a line filter applied to it. + * If no filter is set, it acts exactly like its parent class. + * If a filter is set, incoming data is buffered until a full line (\n terminated) is available. The line is then + * compared against the filter, and either added to the normal output buffer or discarded. + * If a full line is not found after a specified timeout (1500ms by default) then any data in the input buffer is + * flushed anyway. The same is true if the input buffer overflows (max. 5KB by default). + * Another FIFOBufferDevice is used as the input buffer. + */ +class FilteredTextBuffer : public FIFOBufferDevice +{ + Q_OBJECT + + public: + explicit FilteredTextBuffer(QObject * parent = Q_NULLPTR); + ~FilteredTextBuffer(); + + qint64 getInputBufferMaxSize() const; + quint32 getInputBufferTimeout() const; + + public slots: + // input buffer will be flushed if grows > size bytes. + void setInputBufferMaxSize(qint64 size); + // how often to flush the input buffer when less than whole lines are present. + void setInputBufferTimeout(quint32 ms); + void setLineFilterExpr(const QRegularExpression & expr); + void setLineFilterEnabled(bool enable); + void setLineFilterExclusive(bool exclusive); + void setLineFilter(bool enable, bool exclusive, const QRegularExpression & expr); + + signals: + // these are used internally to toggle the input buffer timeout timer (we use signals for thread safety) + void timerStop(); + void timerStart(); + + protected: + qint64 writeDataSuper(const char * data, qint64 len = -1); + virtual qint64 writeData(const char * data, qint64 len); + void flushInputBuffer(); + void closeInputBuffer(); + bool openInputBuffer(); + void processInputBuffer(); + void onInputBufferWrite(qint64); + void onInputBufferOverflow(const qint64 len); + + FIFOBufferDevice * m_inBuffer; + QTimer * m_bufferFlushTimer; + QRegularExpression m_lineFilter; + qint64 m_inBuffMaxSize; // [bytes] + quint32 m_inBuffFlushTimeout; // [ms] + bool m_lineFilterEnable; + bool m_lineFilterExclusive; +}; + + +#endif // FILTEREDTEXTBUFFER_H diff --git a/companion/src/simulation/simulatormainwindow.cpp b/companion/src/simulation/simulatormainwindow.cpp index 1090fd3c5..c530fc82c 100644 --- a/companion/src/simulation/simulatormainwindow.cpp +++ b/companion/src/simulation/simulatormainwindow.cpp @@ -133,14 +133,14 @@ void SimulatorMainWindow::closeEvent(QCloseEvent *) { saveUiState(); - if (m_consoleDockWidget) - delete m_consoleDockWidget; if (m_telemetryDockWidget) delete m_telemetryDockWidget; if (m_trainerDockWidget) delete m_trainerDockWidget; if (m_outputsDockWidget) delete m_outputsDockWidget; + if (m_consoleDockWidget) + delete m_consoleDockWidget; if (m_simulatorDockWidget) delete m_simulatorDockWidget; else if (m_simulatorWidget) @@ -344,9 +344,9 @@ void SimulatorMainWindow::setRadioSizePolicy(int fixType) m_radioSizeConstraint = fixType; - if (ui->actionFixedRadioWidth->isChecked() != (fixType & Qt::Horizontal)) + if (ui->actionFixedRadioWidth->isChecked() != bool(fixType & Qt::Horizontal)) ui->actionFixedRadioWidth->setChecked((fixType & Qt::Horizontal)); - if (ui->actionFixedRadioHeight->isChecked() != (fixType & Qt::Vertical)) + if (ui->actionFixedRadioHeight->isChecked() != bool(fixType & Qt::Vertical)) ui->actionFixedRadioHeight->setChecked((fixType & Qt::Vertical)); }