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:
parent
24c0f5008d
commit
b12bd7d7be
7 changed files with 584 additions and 122 deletions
|
@ -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
|
||||
|
|
|
@ -22,55 +22,49 @@
|
|||
#include "ui_debugoutput.h"
|
||||
|
||||
#include "appdata.h"
|
||||
#include "filteredtextbuffer.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QScrollBar>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
|
||||
#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()
|
||||
|
|
|
@ -29,11 +29,22 @@
|
|||
#include <QValidator>
|
||||
#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 {
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -304,6 +304,21 @@ To <b>remove a remembered entry</b> from the filter list, first cho
|
|||
<string>Clear the output window of all text.</string>
|
||||
</property>
|
||||
</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 &Filter</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Turn the filter on/off.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../companion.qrc"/>
|
||||
|
|
342
companion/src/simulation/filteredtextbuffer.cpp
Normal file
342
companion/src/simulation/filteredtextbuffer.cpp
Normal 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();
|
||||
}
|
||||
}
|
118
companion/src/simulation/filteredtextbuffer.h
Normal file
118
companion/src/simulation/filteredtextbuffer.h
Normal 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
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue