Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions trikControl/src/tonePlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
case QAudio::SuspendedState:
mOutput->resume();
break;
case QAudio::StoppedState:

Check warning on line 65 in trikControl/src/tonePlayer.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikControl/src/tonePlayer.cpp:65:3 [bugprone-branch-clone]

switch has 2 consecutive identical branches
mOutput->start(mDevice);
break;
case QAudio::IdleState:
Expand All @@ -81,6 +81,10 @@
void TonePlayer::stop()
{
mTimer.stop();
mOutput->suspend();
mOutput->reset();
// Calling suspend()/reset() on an already-stopped output crashes ALSA with
// snd_pcm_drain assertion failure (pcm == nullptr).
if (mOutput->state() != QAudio::StoppedState) {
mOutput->suspend();
mOutput->reset();
}
}
21 changes: 14 additions & 7 deletions trikGui/qml/MainMenu.qml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ Rectangle {
}

function onShowRunningCodeComponent(programName) {
// If a RunningCodeComponent is already on the stack, replace it
// instead of pushing a second one (happens when a new script starts
// before the previous one's hide signal arrives).
if (runningCodeObject !== null) {
if (runningCodeObject === stack.currentItem) {
stack.pop();
}
runningCodeObject = null;
}
var page = stack.push("RunningCodeComponent.qml");
if (page) {
if (programName === "direct command") {
Expand Down Expand Up @@ -88,14 +97,12 @@ Rectangle {
// "Object destroyed while signal handler is in progress" crash.
var saved = _mainMenuView.graphicsWidgetObject;
_mainMenuView.graphicsWidgetObject = null;
Qt.callLater(function () {
if (saved === stack.currentItem) {
stack.pop();
if (stack.currentItem && stack.currentItem.idList) {
stack.currentItem.idList.focus = true;
}
if (saved === stack.currentItem) {
stack.pop();
if (stack.currentItem && stack.currentItem.idList) {
stack.currentItem.idList.focus = true;
}
});
}
} else {
_mainMenuView.graphicsWidgetObject = null;
}
Expand Down
8 changes: 7 additions & 1 deletion trikGui/runningCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include "runningCode.h"

#include <QTimer>

using namespace trikGui;

RunningCode::RunningCode(Controller &controller, QObject *parent) : QObject(parent), mController(controller) {
Expand All @@ -36,9 +38,13 @@ void RunningCode::setProgram(const QString &programName, int scriptId) {
mProgramName = programName;
mScriptId = scriptId;
}

void RunningCode::abortScript()
{
mController.abortExecution();
// Defer to the next event loop iteration so the QML signal handler that
// called us (RunningCodeComponent or GraphicsWidget) finishes before abort()
// runs and eventually destroys the component.
QTimer::singleShot(0, &mController, &Controller::abortExecution);
}

int RunningCode::scriptId() const { return mScriptId; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@
bool wasError() override { return false; }

public Q_SLOTS:
void run(const QString &script, const QString &fileName = "") override;

Check warning on line 57 in trikScriptRunner/include/trikScriptRunner/trikJavaScriptRunner.h

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/include/trikScriptRunner/trikJavaScriptRunner.h:57:7 [google-default-arguments]

default arguments on virtual or override methods are prohibited
void runDirectCommand(const QString &command) override;
void abort() override;
void resetBrick() override;
void brickBeep() override;
void setWorkingDirectory(const QString &workingDir) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@
bool wasError() override;

public Q_SLOTS:
void run(const QString &script, const QString &fileName = "") override;

Check warning on line 56 in trikScriptRunner/include/trikScriptRunner/trikPythonRunner.h

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/include/trikScriptRunner/trikPythonRunner.h:56:7 [google-default-arguments]

default arguments on virtual or override methods are prohibited
void runDirectCommand(const QString &command) override;
void abort() override;
void resetBrick() override;
void brickBeep() override;
void setWorkingDirectory(const QString &workingDir) override;

Expand Down
15 changes: 15 additions & 0 deletions trikScriptRunner/include/trikScriptRunner/trikScriptRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,24 +71,39 @@
void sendMailboxMessage(QString msg);
public Q_SLOTS:
/// See corresponding TrikScriptRunnerInterface method
void run(const QString &script, const QString &fileName = "") override;

Check warning on line 74 in trikScriptRunner/include/trikScriptRunner/trikScriptRunner.h

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/include/trikScriptRunner/trikScriptRunner.h:74:7 [google-default-arguments]

default arguments on virtual or override methods are prohibited
/// See corresponding TrikScriptRunnerInterface method
void runDirectCommand(const QString &command) override;
/// See corresponding TrikScriptRunnerInterface method
void abort() override;
/// See corresponding TrikScriptRunnerInterface method
void resetBrick() override;
/// See corresponding TrikScriptRunnerInterface method
void brickBeep() override;

void setWorkingDirectory(const QString &workingDir) override;

private:
TrikScriptRunnerInterface * fetchRunner(ScriptType stype);
void runPending();


// For the script for deferred launch if a start signal
// arrives before the previous one has stopped.
struct PendingRun {
QString script;
ScriptType stype;
QString fileName;
};

trikControl::BrickInterface &mBrick;
QPointer<trikNetwork::MailboxInterface> mMailbox;
QPointer<TrikScriptControlInterface> mScriptControl;
std::vector<QSharedPointer<TrikScriptRunnerInterface>> mScriptRunnerArray;
ScriptType mLastRunner;
bool mScriptRunning = false;
bool mAbortInProgress = false;
QScopedPointer<PendingRun> mPendingRun;
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ public Q_SLOTS:
/// be stopped as well.
virtual void abort() = 0;

/// Resets brick hardware (motors, sensors, audio) after script has stopped.
/// Must be called only after completed() signal is received.
virtual void resetBrick() = 0;

/// Plays "beep" sound.
virtual void brickBeep() = 0;

Expand Down
7 changes: 6 additions & 1 deletion trikScriptRunner/src/trikJavaScriptRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,10 @@
{mScriptEngineWorker->setWorkingDir(workingDir);});
}

void TrikJavaScriptRunner::run(const QString &script, const QString &fileName)

Check warning on line 103 in trikScriptRunner/src/trikJavaScriptRunner.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/src/trikJavaScriptRunner.cpp:103:28 [google-default-arguments]

default arguments on virtual or override methods are prohibited
{
const int scriptId = mMaxScriptId++;
QLOG_INFO() << "TrikJavaScriptRunner: new script" << scriptId << "from file" << fileName;
mScriptEngineWorker->stopScript();

if (!fileName.isEmpty()) {
mScriptFileNames[scriptId] = fileName;
Expand All @@ -124,6 +123,12 @@
// Ugly and unsafe to call these methods from an incorrect thread, but who cares ...
if (mScriptEngineWorker && !mFinishing ) {
mScriptEngineWorker->stopScript();
}
}

void TrikJavaScriptRunner::resetBrick()
{
if (mScriptEngineWorker && !mFinishing) {
mScriptEngineWorker->resetBrick();
}
}
Expand Down
7 changes: 6 additions & 1 deletion trikScriptRunner/src/trikPythonRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,9 @@
}
}

void TrikPythonRunner::run(const QString &script, const QString &fileName)

Check warning on line 83 in trikScriptRunner/src/trikPythonRunner.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/src/trikPythonRunner.cpp:83:24 [google-default-arguments]

default arguments on virtual or override methods are prohibited
{
QFileInfo scriptFile = QFileInfo(fileName);
mScriptEngineWorker->stopScript();
mScriptEngineWorker->run(script, scriptFile);
}

Expand Down Expand Up @@ -121,6 +120,12 @@
{
if (mScriptEngineWorker) {
mScriptEngineWorker->stopScript();
}
}

void TrikPythonRunner::resetBrick()
{
if (mScriptEngineWorker) {
mScriptEngineWorker->resetBrick();
}
}
Expand Down
75 changes: 70 additions & 5 deletions trikScriptRunner/src/trikScriptRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
}
mScriptControl->setParent(this);

connect(this, &TrikScriptRunnerInterface::startedScript,
this, [this](const QString &, int) { mScriptRunning = true; });
connect(this, &TrikScriptRunnerInterface::startedDirectScript,
this, [this](int) { mScriptRunning = true; });
connect(this, &TrikScriptRunnerInterface::completed,
this, [this](const QString &, int) { mScriptRunning = false; });

#ifndef TRIK_NOPYTHON
// TrikPythonRunner must be initialized early during trikGui startup;
// otherwise the first script execution can take over 6 seconds.
Expand Down Expand Up @@ -159,15 +166,39 @@
return cell.data();
}

void TrikScriptRunner::run(const QString &script, ScriptType stype, const QString &fileName)

Check warning on line 169 in trikScriptRunner/src/trikScriptRunner.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/src/trikScriptRunner.cpp:169:24 [misc-no-recursion]

function 'run' is within a recursive call chain
{
if (mAbortInProgress) {
// Abort is in progress (we're inside wait.exec()). Save as pending — will run after abort finishes.
QLOG_INFO() << "Run deferred, abort in progress";
mPendingRun.reset(new PendingRun{script, stype, fileName});
return;
}

abort();
auto prevRunner = mLastRunner;
auto runner = fetchRunner(stype);
if (prevRunner != stype) {
runner->abort();

// Abort() calls runPending() at the end — if a newer run arrived during the wait
// or during resetBrick()'s inner loop, it was already started (mScriptRunning=true).
// Skip this older run to avoid double-starting.
if (mScriptRunning) {
QLOG_INFO() << "Run skipped, pending script was started inside abort";
return;
}

// startedScript arrives via queued connection (worker thread),
// so the next run() call could arrive before mScriptRunning becomes true from the signal.
mScriptRunning = true;
fetchRunner(stype)->run(script, fileName);
}

void TrikScriptRunner::runPending()

Check warning on line 194 in trikScriptRunner/src/trikScriptRunner.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/src/trikScriptRunner.cpp:194:24 [misc-no-recursion]

function 'runPending' is within a recursive call chain
{
if (!mPendingRun) {
return;
}
runner->run(script, fileName);
auto pending = *mPendingRun;
mPendingRun.reset();
run(pending.script, pending.stype, pending.fileName);
}

void TrikScriptRunner::runDirectCommand(const QString &command)
Expand All @@ -175,9 +206,43 @@
fetchRunner(mLastRunner)->runDirectCommand(command);
}

void TrikScriptRunner::abort()

Check warning on line 209 in trikScriptRunner/src/trikScriptRunner.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-latest (5.15, 3.11)

trikScriptRunner/src/trikScriptRunner.cpp:209:24 [misc-no-recursion]

function 'abort' is within a recursive call chain
{
mPendingRun.reset();

if (!mScriptRunning) {
return;
}

if (mAbortInProgress) {
return;
}

mAbortInProgress = true;
QEventLoop wait;
auto conn = connect(this, &TrikScriptRunnerInterface::completed,
&wait, &QEventLoop::quit);
fetchRunner(mLastRunner)->abort();
// Do not proceed with further script launch code — instead wait for complete
// termination until the completed signal arrives. Since everything is asynchronous,
// exact on-time stopping is not guaranteed, but this works in practice and fixes
// the button-spamming hang in TrikStudio and on the controller.
wait.exec();
disconnect(conn);

// Keep mAbortInProgress=true during resetBrick() — it internally spins its own
// QEventLoop (cross-thread invoke). Any run() calls arriving there will be
// deferred as pending and started after abort() fully returns.
fetchRunner(mLastRunner)->resetBrick();
mAbortInProgress = false;
// If a run() arrived during wait.exec() or resetBrick()'s inner loop, start it now.
// This covers the case where abort() is called directly (not via run()), e.g. from abortExecution().
runPending();
}

void TrikScriptRunner::resetBrick()
{
fetchRunner(mLastRunner)->resetBrick();
}

void TrikScriptRunner::brickBeep()
Expand Down
Loading