diff --git a/OSBindings/Qt/functionthread.h b/OSBindings/Qt/functionthread.h index 75237a882..575f99614 100644 --- a/OSBindings/Qt/functionthread.h +++ b/OSBindings/Qt/functionthread.h @@ -1,36 +1,81 @@ #ifndef FUNCTIONTHREAD_H #define FUNCTIONTHREAD_H +#include +#include +#include +#include #include /*! * \brief The LambdaThread class * - * Provides a QThread which performs a supplied lambda before kicking off its event loop. + * Provides a QThread to which lambdas can be posted. * * Disclaimer: this might be a crutch that reveals a misunderstanding of the Qt * threading infrastructure. We'll see. */ class FunctionThread: public QThread { public: - FunctionThread() : QThread() {} - - void setFunction(const std::function &function) { - this->function = function; + FunctionThread() : QThread() { + // TODO: I've assumed a race condition here with the creation of performer; if QThread + // blocks on completion of `run` when starting then this is redundant. + performerFlag = false; + start(); + while(!performerFlag); } void run() override { - function(); + // Gymnastics here: events posted directly to the QThread will occur on the thread + // that created the QThread. To have events occur within a QThread, they have to be + // posted to an object created on that thread. FunctionPerformer fills that role. + performer = std::make_unique(); + performerFlag = true; exec(); } void stop() { - quit(); + performAsync([this] { + this->quit(); + }); wait(); } + /*! + * \brief Schedules a function to be performed on this thread. Control + * must return to the main event loop for the function to be performed; + * use QCoreApplication::sendPostedEvents() to ensure the function is + * performed before then, if required. + * + * \param function The function to perform. + */ + void performAsync(const std::function &function) { + QApplication::instance()->postEvent(performer.get(), new FunctionEvent(function)); + QCoreApplication::sendPostedEvents(); + } + private: - std::function function; + struct FunctionEvent: public QEvent { + FunctionEvent(const std::function &function) : QEvent(QEvent::Type::User), function(function) {} + std::function function; + }; + + struct FunctionPerformer: public QObject { + FunctionPerformer(): QObject() {} + + bool event(QEvent *event) override { + if(event->type() == QEvent::Type::User) { + const auto functionEvent = dynamic_cast(event); + if(functionEvent) { + functionEvent->function(); + return true; + } + } + return QObject::event(event); + } + }; + std::unique_ptr performer; + std::atomic performerFlag; }; #endif // FUNCTIONTHREAD_H diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 21a2e0b76..6c841f024 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -65,7 +65,9 @@ void MainWindow::open() { MainWindow::~MainWindow() { // Stop the audio output, and its thread. if(audioOutput) { -// QMetaObject::invokeMethod(audioOutput.get(), "stop", Qt::BlockingQueuedConnection); + audioThread.performAsync([this] { + audioOutput->stop(); + }); audioThread.stop(); } @@ -164,7 +166,7 @@ void MainWindow::launchMachine() { speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo); speaker->set_delegate(this); - audioThread.setFunction([this, idealFormat] { + audioThread.performAsync([this, idealFormat] { // Create an audio output. audioOutput = std::make_unique(idealFormat); @@ -174,7 +176,6 @@ void MainWindow::launchMachine() { audioOutput->start(&audioBuffer); audioBuffer.setDepth(audioOutput->bufferSize()); }); - audioThread.start(); } } diff --git a/OSBindings/Qt/timer.cpp b/OSBindings/Qt/timer.cpp index 9dd6d38ef..914107170 100644 --- a/OSBindings/Qt/timer.cpp +++ b/OSBindings/Qt/timer.cpp @@ -5,8 +5,13 @@ #include #include -Timer::Timer(QObject *parent) : QObject(parent) { - thread.setFunction([this] { +Timer::Timer(QObject *parent) : QObject(parent) {} + +void Timer::startWithMachine(MachineTypes::TimedMachine *machine, std::mutex *machineMutex) { + this->machine = machine; + this->machineMutex = machineMutex; + + thread.performAsync([this] { // Set up the emulation timer. Bluffer's guide: the QTimer will post an // event to an event loop. QThread is a thread with an event loop. // My class, Timer, will be wired up to receive the QTimer's events. @@ -18,13 +23,6 @@ Timer::Timer(QObject *parent) : QObject(parent) { }); } -void Timer::startWithMachine(MachineTypes::TimedMachine *machine, std::mutex *machineMutex) { - this->machine = machine; - this->machineMutex = machineMutex; - - thread.start(); -} - void Timer::tick() { const auto now = Time::nanos_now(); const auto duration = std::min(now - lastTickNanos, int64_t(500'000'000)); @@ -35,6 +33,8 @@ void Timer::tick() { } Timer::~Timer() { - QMetaObject::invokeMethod(timer.get(), "stop", Qt::BlockingQueuedConnection); + thread.performAsync([this] { + timer->stop(); + }); thread.stop(); }