1
0
Fork 0
mirror of https://github.com/EdgeTX/edgetx.git synced 2025-07-24 00:35:14 +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

@ -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()