1
0
Fork 0
mirror of https://github.com/EdgeTX/edgetx.git synced 2025-07-25 17:25:10 +03:00

[Simulator] Add asynchronous FIFO buffer for handling debug output/display more efficiently. (#4488)

This commit is contained in:
Max Paperno 2017-02-21 05:04:53 -05:00 committed by Bertrand Songis
parent 24c0f5008d
commit b12bd7d7be
7 changed files with 584 additions and 122 deletions

View file

@ -1,5 +1,6 @@
set(simulation_SRCS set(simulation_SRCS
debugoutput.cpp debugoutput.cpp
filteredtextbuffer.cpp
radiooutputswidget.cpp radiooutputswidget.cpp
simulateduiwidget.cpp simulateduiwidget.cpp
simulateduiwidget9X.cpp simulateduiwidget9X.cpp
@ -34,6 +35,7 @@ set(simulation_UIS
set(simulation_HDRS set(simulation_HDRS
debugoutput.h debugoutput.h
filteredtextbuffer.h
radiooutputswidget.h radiooutputswidget.h
radiouiaction.h radiouiaction.h
simulateduiwidget.h simulateduiwidget.h

View file

@ -22,55 +22,49 @@
#include "ui_debugoutput.h" #include "ui_debugoutput.h"
#include "appdata.h" #include "appdata.h"
#include "filteredtextbuffer.h"
#include <QElapsedTimer>
#include <QMessageBox> #include <QMessageBox>
#include <QRegularExpression>
#include <QScrollBar> #include <QScrollBar>
#include <QThread>
#include <QDebug> #include <QDebug>
#define DEBUG_OUTPUT_STATE_VERSION 1 #define DEBUG_OUTPUT_STATE_VERSION 1
extern AppData g; // ensure what "g" means extern AppData g; // ensure what "g" means
DebugOutput * traceCallbackInstance = 0; FilteredTextBuffer * DebugOutput::m_dataBufferDevice = Q_NULLPTR;
const int DebugOutput::m_dataBufferMaxSize = 500; // lines of text (this is not the display buffer)
const int DebugOutput::m_dataPrintFreqDefault = 8; // ms
const quint16 DebugOutput::m_savedViewStateVersion = 1; const quint16 DebugOutput::m_savedViewStateVersion = 1;
void traceCb(const char * text) void firmwareTraceCb(const char * text)
{ {
// divert C callback into simulator instance if (DebugOutput::m_dataBufferDevice) {
if (traceCallbackInstance) { DebugOutput::m_dataBufferDevice->write(text);
traceCallbackInstance->traceCallback(text);
} }
} }
// TODO: move callback & filter to own thread
DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator):
QWidget(parent), QWidget(parent),
ui(new Ui::DebugOutput), ui(new Ui::DebugOutput),
m_simulator(simulator), m_simulator(simulator),
m_tmrDataPrint(new QTimer()),
m_dataBuffer(QByteArray()),
m_radioProfileId(g.sessionId()), m_radioProfileId(g.sessionId()),
m_dataPrintFreq(m_dataPrintFreqDefault), m_filterEnable(false),
m_running(false), m_filterExclude(false)
m_filterExclude(true),
m_overflowReported(false)
{ {
ui->setupUi(this); ui->setupUi(this);
#ifdef __APPLE__ #ifdef __APPLE__
QFont newFont("Courier", 13); ui->console->setFont(QFont("Courier", 13));
ui->console->setFont(newFont);
#endif #endif
// TODO : allow selecting multiple filters, but needs to be efficient at output stage // TODO : allow selecting multiple filters, but needs to be efficient at output stage
QStringList stockFilters; QStringList stockFilters;
stockFilters << "^lua[A-Z].*"; stockFilters << "/^(lua[A-Z]|script).*/i";
stockFilters << "/(error|warning|-(E|W)-)/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) foreach (const QString & fltr, stockFilters)
ui->filterText->addItem(fltr, "no_delete"); ui->filterText->addItem(fltr, "no_delete");
@ -82,48 +76,49 @@ DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator):
ui->actionWordWrap->setIcon(SimulatorIcon("word_wrap")); ui->actionWordWrap->setIcon(SimulatorIcon("word_wrap"));
ui->actionClearScr->setIcon(SimulatorIcon("eraser")); ui->actionClearScr->setIcon(SimulatorIcon("eraser"));
ui->btnFilter->setDefaultAction(ui->actionToggleFilter);
ui->btnShowFilterHelp->setDefaultAction(ui->actionShowFilterHelp); ui->btnShowFilterHelp->setDefaultAction(ui->actionShowFilterHelp);
ui->btnWordWrap->setDefaultAction(ui->actionWordWrap); ui->btnWordWrap->setDefaultAction(ui->actionWordWrap);
ui->btnClearScr->setDefaultAction(ui->actionClearScr); 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(); restoreState();
ui->bufferSize->setValue(ui->console->maximumBlockCount()); ui->bufferSize->setValue(ui->console->maximumBlockCount());
// install simulator TRACE hook connect(ui->actionToggleFilter, &QAction::toggled, this, &DebugOutput::onFilterToggled);
traceCallbackInstance = this;
m_simulator->installTraceHook(traceCb);
m_tmrDataPrint->setInterval(m_dataPrintFreq);
connect(ui->filterText, &QComboBox::currentTextChanged, this, &DebugOutput::onFilterTextChanged); 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() DebugOutput::~DebugOutput()
{ {
traceCallbackInstance = 0;
stop();
saveState(); 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; delete ui;
} }
void DebugOutput::start()
{
m_tmrDataPrint->start();
m_running = true;
}
void DebugOutput::stop()
{
m_tmrDataPrint->stop();
m_running = false;
}
void DebugOutput::saveState() void DebugOutput::saveState()
{ {
QStringList filters; QStringList filters;
@ -137,7 +132,7 @@ void DebugOutput::saveState()
QDataStream stream(&state, QIODevice::WriteOnly); QDataStream stream(&state, QIODevice::WriteOnly);
stream << m_savedViewStateVersion stream << m_savedViewStateVersion
<< (qint16)ui->filterText->currentIndex() << (qint32)ui->console->maximumBlockCount() << (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(); SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions();
opts.dbgConsoleState = state; opts.dbgConsoleState = state;
@ -159,61 +154,23 @@ void DebugOutput::restoreState()
ui->filterText->insertItems(0, g.simuDbgFilters()); ui->filterText->insertItems(0, g.simuDbgFilters());
ui->filterText->setCurrentIndex(fci); ui->filterText->setCurrentIndex(fci);
ui->btnFilter->setChecked(flten);
ui->console->setMaximumBlockCount(mbc); ui->console->setMaximumBlockCount(mbc);
ui->actionWordWrap->setChecked(wwen); ui->actionWordWrap->setChecked(wwen);
onFilterTextEdited();
}
void DebugOutput::traceCallback(const char * text) onFilterToggled(flten);
{
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();
} }
void DebugOutput::processBytesReceived() void DebugOutput::processBytesReceived()
{ {
QString text;
bool fltMatch;
static quint32 cycleCount = 0;
const QTextCursor savedCursor(ui->console->textCursor()); const QTextCursor savedCursor(ui->console->textCursor());
const int sbValue = ui->console->verticalScrollBar()->value(); const int sbValue = ui->console->verticalScrollBar()->value();
const bool sbAtBottom = (sbValue == ui->console->verticalScrollBar()->maximum()); const bool sbAtBottom = (sbValue == ui->console->verticalScrollBar()->maximum());
qint64 len;
m_tmrDataPrint->stop(); while ((len = m_dataBufferDevice->bytesAvailable()) > 0) {
while (m_dataBuffer.size() > 0) { QString text(m_dataBufferDevice->read(qMin(len, qint64(512))));
m_mtxDataBuffer.lock(); if (text.isEmpty())
text = m_dataBuffer.takeFirst(); break;
m_mtxDataBuffer.unlock();
// filter
if (ui->btnFilter->isChecked()) {
fltMatch = text.contains(m_filterRegEx);
if ((m_filterExclude && fltMatch) || (!m_filterExclude && !fltMatch)) {
continue;
}
}
ui->console->moveCursor(QTextCursor::End); ui->console->moveCursor(QTextCursor::End);
ui->console->textCursor().insertText(text); ui->console->textCursor().insertText(text);
if (sbAtBottom) { if (sbAtBottom) {
@ -226,43 +183,62 @@ void DebugOutput::processBytesReceived()
} }
QCoreApplication::processEvents(); QCoreApplication::processEvents();
} }
m_tmrDataPrint->start(); }
++cycleCount; void DebugOutput::onDataBufferOverflow(const qint64 len)
{
static QElapsedTimer reportTimer;
if (!(cycleCount % (1000 / m_dataPrintFreqDefault * 30))) if (len <= 0) {
m_overflowReported = false; reportTimer.invalidate();
}
else if (!reportTimer.isValid() || reportTimer.elapsed() > 1000 * 30) {
qWarning("Data buffer overflow by %lld bytes!", len);
reportTimer.start();
}
} }
/* /*
* UI handlers * UI handlers
*/ */
void DebugOutput::onFilterTextEdited() void DebugOutput::onFilterStateChanged()
{ {
const QString fText = ui->filterText->currentText(); const QString fText = ui->filterText->currentText();
if (fText.isEmpty()) { if (fText.isEmpty()) {
ui->btnFilter->setChecked(false); onFilterToggled(false);
m_filterRegEx = QRegularExpression();
return; return;
} }
m_filterRegEx = makeRegEx(fText, &m_filterExclude); QRegularExpression filterRegEx = makeRegEx(fText, &m_filterExclude);
if (m_filterRegEx.isValid()) { if (!m_filterEnable || filterRegEx.isValid())
//ui->btnFilter->setChecked(true);
ui->filterText->setStyleSheet(""); ui->filterText->setStyleSheet("");
} else if (m_filterEnable)
else {
ui->btnFilter->setChecked(false);
m_filterRegEx = QRegularExpression();
ui->filterText->setStyleSheet("background-color: rgba(255, 205, 185, 200);"); 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 &) 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() void DebugOutput::on_bufferSize_editingFinished()

View file

@ -29,11 +29,22 @@
#include <QValidator> #include <QValidator>
#include <QWidget> #include <QWidget>
// 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 { namespace Ui {
class DebugOutput; class DebugOutput;
} }
class QAbstractButton; class QAbstractButton;
class FilteredTextBuffer;
using namespace Simulator; using namespace Simulator;
@ -44,18 +55,25 @@ class DebugOutput : public QWidget
public: public:
explicit DebugOutput(QWidget * parent, SimulatorInterface * simulator); explicit DebugOutput(QWidget * parent, SimulatorInterface * simulator);
virtual ~DebugOutput(); virtual ~DebugOutput();
void start();
void stop();
void traceCallback(const char * text);
static QRegularExpression makeRegEx(const QString & input, bool * isExlusive = NULL); 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: protected slots:
void saveState(); void saveState();
void restoreState(); void restoreState();
void processBytesReceived(); void processBytesReceived();
void onFilterTextEdited(); void onDataBufferOverflow(const qint64 len);
void onFilterStateChanged();
void onFilterTextChanged(const QString &); void onFilterTextChanged(const QString &);
void onFilterToggled(bool enable);
void on_bufferSize_editingFinished(); void on_bufferSize_editingFinished();
void on_actionWordWrap_toggled(bool checked); void on_actionWordWrap_toggled(bool checked);
void on_actionClearScr_triggered(); void on_actionClearScr_triggered();
@ -64,19 +82,10 @@ class DebugOutput : public QWidget
protected: protected:
Ui::DebugOutput * ui; Ui::DebugOutput * ui;
SimulatorInterface * m_simulator; SimulatorInterface * m_simulator;
QTimer * m_tmrDataPrint;
QStringList m_dataBuffer;
QMutex m_mtxDataBuffer;
QRegularExpression m_filterRegEx;
int m_radioProfileId; int m_radioProfileId;
int m_dataPrintFreq; bool m_filterEnable;
bool m_running;
bool m_filterExclude; bool m_filterExclude;
bool m_overflowReported;
const static int m_dataBufferMaxSize;
const static int m_dataPrintFreqDefault;
const static quint16 m_savedViewStateVersion; const static quint16 m_savedViewStateVersion;
}; };

View file

@ -304,6 +304,21 @@ To &lt;b&gt;remove a remembered entry&lt;/b&gt; from the filter list, first cho
<string>Clear the output window of all text.</string> <string>Clear the output window of all text.</string>
</property> </property>
</action> </action>
<action name="actionToggleFilter">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../companion.qrc">
<normaloff>:/images/simulator/icons/svg/toggle_lock.svg</normaloff>:/images/simulator/icons/svg/toggle_lock.svg</iconset>
</property>
<property name="text">
<string>Enable &amp;Filter</string>
</property>
<property name="toolTip">
<string>Turn the filter on/off.</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="../companion.qrc"/> <include location="../companion.qrc"/>

View file

@ -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 <QDebug>
#include <QElapsedTimer>
/*
*
* 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<void (QTimer::*)(void)>(&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();
}
}

View file

@ -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 <QBuffer>
#include <QRegularExpression>
#include <QReadWriteLock>
#include <QTimer>
/*
* 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

View file

@ -133,14 +133,14 @@ void SimulatorMainWindow::closeEvent(QCloseEvent *)
{ {
saveUiState(); saveUiState();
if (m_consoleDockWidget)
delete m_consoleDockWidget;
if (m_telemetryDockWidget) if (m_telemetryDockWidget)
delete m_telemetryDockWidget; delete m_telemetryDockWidget;
if (m_trainerDockWidget) if (m_trainerDockWidget)
delete m_trainerDockWidget; delete m_trainerDockWidget;
if (m_outputsDockWidget) if (m_outputsDockWidget)
delete m_outputsDockWidget; delete m_outputsDockWidget;
if (m_consoleDockWidget)
delete m_consoleDockWidget;
if (m_simulatorDockWidget) if (m_simulatorDockWidget)
delete m_simulatorDockWidget; delete m_simulatorDockWidget;
else if (m_simulatorWidget) else if (m_simulatorWidget)
@ -344,9 +344,9 @@ void SimulatorMainWindow::setRadioSizePolicy(int fixType)
m_radioSizeConstraint = 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)); 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)); ui->actionFixedRadioHeight->setChecked((fixType & Qt::Vertical));
} }