1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-08-16 16:28:59 +00:00

Makes a firmer attempt at enforcing safe shutdown.

This commit is contained in:
Thomas Harte 2020-06-16 22:33:50 -04:00
parent 495024d6fe
commit 17bb3dce26
3 changed files with 67 additions and 21 deletions

View File

@ -1,36 +1,81 @@
#ifndef FUNCTIONTHREAD_H #ifndef FUNCTIONTHREAD_H
#define FUNCTIONTHREAD_H #define FUNCTIONTHREAD_H
#include <atomic>
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QThread> #include <QThread>
/*! /*!
* \brief The LambdaThread class * \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 * Disclaimer: this might be a crutch that reveals a misunderstanding of the Qt
* threading infrastructure. We'll see. * threading infrastructure. We'll see.
*/ */
class FunctionThread: public QThread { class FunctionThread: public QThread {
public: public:
FunctionThread() : QThread() {} FunctionThread() : QThread() {
// TODO: I've assumed a race condition here with the creation of performer; if QThread
void setFunction(const std::function<void(void)> &function) { // blocks on completion of `run` when starting then this is redundant.
this->function = function; performerFlag = false;
start();
while(!performerFlag);
} }
void run() override { 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<FunctionPerformer>();
performerFlag = true;
exec(); exec();
} }
void stop() { void stop() {
quit(); performAsync([this] {
this->quit();
});
wait(); 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<void(void)> &function) {
QApplication::instance()->postEvent(performer.get(), new FunctionEvent(function));
QCoreApplication::sendPostedEvents();
}
private: private:
struct FunctionEvent: public QEvent {
FunctionEvent(const std::function<void(void)> &function) : QEvent(QEvent::Type::User), function(function) {}
std::function<void(void)> function; std::function<void(void)> function;
}; };
struct FunctionPerformer: public QObject {
FunctionPerformer(): QObject() {}
bool event(QEvent *event) override {
if(event->type() == QEvent::Type::User) {
const auto functionEvent = dynamic_cast<FunctionEvent *>(event);
if(functionEvent) {
functionEvent->function();
return true;
}
}
return QObject::event(event);
}
};
std::unique_ptr<FunctionPerformer> performer;
std::atomic<bool> performerFlag;
};
#endif // FUNCTIONTHREAD_H #endif // FUNCTIONTHREAD_H

View File

@ -65,7 +65,9 @@ void MainWindow::open() {
MainWindow::~MainWindow() { MainWindow::~MainWindow() {
// Stop the audio output, and its thread. // Stop the audio output, and its thread.
if(audioOutput) { if(audioOutput) {
// QMetaObject::invokeMethod(audioOutput.get(), "stop", Qt::BlockingQueuedConnection); audioThread.performAsync([this] {
audioOutput->stop();
});
audioThread.stop(); audioThread.stop();
} }
@ -164,7 +166,7 @@ void MainWindow::launchMachine() {
speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo); speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo);
speaker->set_delegate(this); speaker->set_delegate(this);
audioThread.setFunction([this, idealFormat] { audioThread.performAsync([this, idealFormat] {
// Create an audio output. // Create an audio output.
audioOutput = std::make_unique<QAudioOutput>(idealFormat); audioOutput = std::make_unique<QAudioOutput>(idealFormat);
@ -174,7 +176,6 @@ void MainWindow::launchMachine() {
audioOutput->start(&audioBuffer); audioOutput->start(&audioBuffer);
audioBuffer.setDepth(audioOutput->bufferSize()); audioBuffer.setDepth(audioOutput->bufferSize());
}); });
audioThread.start();
} }
} }

View File

@ -5,8 +5,13 @@
#include <algorithm> #include <algorithm>
#include <QDebug> #include <QDebug>
Timer::Timer(QObject *parent) : QObject(parent) { Timer::Timer(QObject *parent) : QObject(parent) {}
thread.setFunction([this] {
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 // 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. // 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. // 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() { void Timer::tick() {
const auto now = Time::nanos_now(); const auto now = Time::nanos_now();
const auto duration = std::min(now - lastTickNanos, int64_t(500'000'000)); const auto duration = std::min(now - lastTickNanos, int64_t(500'000'000));
@ -35,6 +33,8 @@ void Timer::tick() {
} }
Timer::~Timer() { Timer::~Timer() {
QMetaObject::invokeMethod(timer.get(), "stop", Qt::BlockingQueuedConnection); thread.performAsync([this] {
timer->stop();
});
thread.stop(); thread.stop();
} }