mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Makes a firmer attempt at enforcing safe shutdown.
This commit is contained in:
parent
495024d6fe
commit
17bb3dce26
@ -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:
|
||||||
std::function<void(void)> function;
|
struct FunctionEvent: public QEvent {
|
||||||
|
FunctionEvent(const std::function<void(void)> &function) : QEvent(QEvent::Type::User), function(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
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user