mirror of https://github.com/snes9xgit/snes9x.git
Threading.
This commit is contained in:
parent
177a802186
commit
a438d3fa42
|
@ -1 +1 @@
|
|||
Subproject commit 3ebb72cc7429f0ab8218104dc3687c659c0f364d
|
||||
Subproject commit 9c7fd1a33e5cecbe465e1cd70170167d5e40d398
|
|
@ -26,6 +26,7 @@ EmuApplication::~EmuApplication()
|
|||
|
||||
void EmuApplication::restartAudio()
|
||||
{
|
||||
suspendThread();
|
||||
sound_driver.reset();
|
||||
core->sound_output_function = nullptr;
|
||||
|
||||
|
@ -55,6 +56,8 @@ void EmuApplication::restartAudio()
|
|||
core->sound_output_function = [&](int16_t *data, int samples) {
|
||||
writeSamples(data, samples);
|
||||
};
|
||||
|
||||
unsuspendThread();
|
||||
}
|
||||
|
||||
#ifdef SOUND_BUFFER_WINDOW
|
||||
|
@ -124,13 +127,14 @@ void EmuApplication::writeSamples(int16_t *data, int samples)
|
|||
|
||||
void EmuApplication::startGame()
|
||||
{
|
||||
suspendThread();
|
||||
if (!sound_driver)
|
||||
restartAudio();
|
||||
|
||||
core->screen_output_function = [&](uint16_t *data, int width, int height, int stride_bytes, double frame_rate) {
|
||||
if (window->canvas)
|
||||
{
|
||||
QMetaObject::invokeMethod(window.get(), "output", Qt::ConnectionType::QueuedConnection,
|
||||
QMetaObject::invokeMethod(window.get(), "output", Qt::ConnectionType::DirectConnection,
|
||||
Q_ARG(uint8_t *, (uint8_t *)data),
|
||||
Q_ARG(int, width),
|
||||
Q_ARG(int, height),
|
||||
|
@ -143,7 +147,12 @@ void EmuApplication::startGame()
|
|||
updateSettings();
|
||||
updateBindings();
|
||||
|
||||
startIdleLoop();
|
||||
emu_thread->setMainLoop([&] {
|
||||
mainLoop();
|
||||
});
|
||||
|
||||
unsuspendThread();
|
||||
unpause();
|
||||
}
|
||||
|
||||
bool EmuApplication::isPaused()
|
||||
|
@ -151,21 +160,61 @@ bool EmuApplication::isPaused()
|
|||
return (pause_count != 0);
|
||||
}
|
||||
|
||||
void EmuApplication::suspendThread()
|
||||
{
|
||||
suspend_count++;
|
||||
|
||||
if (!emu_thread)
|
||||
return;
|
||||
|
||||
if (suspend_count > 0)
|
||||
{
|
||||
printf("Suspend %d\n", suspend_count);
|
||||
emu_thread->runOnThread([&] { emu_thread->setStatusBits(EmuThread::eSuspended); });
|
||||
emu_thread->waitForStatusBit(EmuThread::eSuspended);
|
||||
}
|
||||
}
|
||||
|
||||
void EmuApplication::unsuspendThread()
|
||||
{
|
||||
suspend_count--;
|
||||
assert(suspend_count >= 0);
|
||||
|
||||
if (!emu_thread)
|
||||
return;
|
||||
|
||||
printf("Un Suspend %d\n", suspend_count);
|
||||
|
||||
if (suspend_count == 0)
|
||||
{
|
||||
emu_thread->runOnThread([&] { emu_thread->unsetStatusBits(EmuThread::eSuspended); });
|
||||
emu_thread->waitForStatusBitCleared(EmuThread::eSuspended);
|
||||
}
|
||||
}
|
||||
|
||||
void EmuApplication::pause()
|
||||
{
|
||||
pause_count++;
|
||||
if (pause_count > 0)
|
||||
{
|
||||
if (emu_thread)
|
||||
emu_thread->pause();
|
||||
core->setPaused(true);
|
||||
if (sound_driver)
|
||||
sound_driver->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void EmuApplication::stopIdleLoop()
|
||||
void EmuApplication::stopThread()
|
||||
{
|
||||
idle_loop->stop();
|
||||
pause_count = 0;
|
||||
if (!emu_thread)
|
||||
return;
|
||||
|
||||
emu_thread->setStatusBits(EmuThread::eQuit);
|
||||
while (!emu_thread->isFinished())
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
void EmuApplication::unpause()
|
||||
|
@ -179,47 +228,42 @@ void EmuApplication::unpause()
|
|||
core->setPaused(false);
|
||||
if (core->active && sound_driver)
|
||||
sound_driver->start();
|
||||
if (emu_thread)
|
||||
emu_thread->unpause();
|
||||
}
|
||||
|
||||
void EmuApplication::startIdleLoop()
|
||||
void EmuApplication::startThread()
|
||||
{
|
||||
if (!idle_loop)
|
||||
if (!emu_thread)
|
||||
{
|
||||
idle_loop = std::make_unique<QTimer>();
|
||||
idle_loop->setTimerType(Qt::TimerType::PreciseTimer);
|
||||
idle_loop->setInterval(0);
|
||||
idle_loop->setSingleShot(false);
|
||||
idle_loop->callOnTimeout([&]{ idleLoop(); });
|
||||
pause_count = 0;
|
||||
}
|
||||
|
||||
idle_loop->start();
|
||||
}
|
||||
|
||||
void EmuApplication::idleLoop()
|
||||
{
|
||||
if (core->active && pause_count == 0)
|
||||
{
|
||||
idle_loop->setInterval(0);
|
||||
pollJoysticks();
|
||||
bool muted = config->mute_audio || (config->mute_audio_during_alternate_speed && core->isAbnormalSpeed());
|
||||
core->mute(muted);
|
||||
core->mainLoop();
|
||||
}
|
||||
else
|
||||
{
|
||||
pollJoysticks();
|
||||
idle_loop->setInterval(32);
|
||||
emu_thread = std::make_unique<EmuThread>(QThread::currentThread());
|
||||
emu_thread->start();
|
||||
emu_thread->waitForStatusBit(EmuThread::ePaused);
|
||||
emu_thread->moveToThread(emu_thread.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool EmuApplication::openFile(std::string filename)
|
||||
{
|
||||
suspendThread();
|
||||
auto result = core->openFile(filename);
|
||||
unsuspendThread();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmuApplication::mainLoop()
|
||||
{
|
||||
if (!core->active)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Here\n");
|
||||
core->mainLoop();
|
||||
}
|
||||
|
||||
void EmuApplication::reportBinding(EmuBinding b, bool active)
|
||||
{
|
||||
if (binding_callback && active)
|
||||
|
@ -238,7 +282,7 @@ void EmuApplication::reportBinding(EmuBinding b, bool active)
|
|||
return;
|
||||
}
|
||||
|
||||
core->reportBinding(b, active);
|
||||
emu_thread->runOnThread([&, b, active] { core->reportBinding(b, active); });
|
||||
}
|
||||
|
||||
void EmuApplication::updateBindings()
|
||||
|
@ -273,7 +317,9 @@ void EmuApplication::updateBindings()
|
|||
}
|
||||
}
|
||||
|
||||
suspendThread();
|
||||
core->updateBindings(config.get());
|
||||
unsuspendThread();
|
||||
}
|
||||
|
||||
void EmuApplication::handleBinding(std::string name, bool pressed)
|
||||
|
@ -296,14 +342,18 @@ void EmuApplication::handleBinding(std::string name, bool pressed)
|
|||
save_slot++;
|
||||
if (save_slot > 999)
|
||||
save_slot = 0;
|
||||
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||
emu_thread->runOnThread([&] {
|
||||
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||
});
|
||||
}
|
||||
else if (name == "DecreaseSlot")
|
||||
{
|
||||
save_slot--;
|
||||
if (save_slot < 0)
|
||||
save_slot = 999;
|
||||
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||
emu_thread->runOnThread([&] {
|
||||
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||
});
|
||||
}
|
||||
else if (name == "SaveState")
|
||||
{
|
||||
|
@ -349,7 +399,9 @@ void EmuApplication::updateSettings()
|
|||
config->input_rate /= 4;
|
||||
}
|
||||
|
||||
core->updateSettings(config.get());
|
||||
emu_thread->runOnThread([&] {
|
||||
core->updateSettings(config.get());
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::pollJoysticks()
|
||||
|
@ -401,39 +453,63 @@ void EmuApplication::pollJoysticks()
|
|||
}
|
||||
}
|
||||
|
||||
void EmuApplication::startInputTimer()
|
||||
{
|
||||
poll_input_timer = std::make_unique<QTimer>();
|
||||
poll_input_timer->setTimerType(Qt::TimerType::PreciseTimer);
|
||||
poll_input_timer->setInterval(4);
|
||||
poll_input_timer->setSingleShot(false);
|
||||
poll_input_timer->callOnTimeout([&] { pollJoysticks(); });
|
||||
poll_input_timer->start();
|
||||
}
|
||||
|
||||
void EmuApplication::loadState(int slot)
|
||||
{
|
||||
core->loadState(slot);
|
||||
emu_thread->runOnThread([&] {
|
||||
core->loadState(slot);
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::loadState(std::string filename)
|
||||
{
|
||||
core->loadState(filename);
|
||||
emu_thread->runOnThread([&] {
|
||||
core->loadState(filename);
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::saveState(int slot)
|
||||
{
|
||||
core->saveState(slot);
|
||||
emu_thread->runOnThread([&] {
|
||||
core->saveState(slot);
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::saveState(std::string filename)
|
||||
{
|
||||
core->saveState(filename);
|
||||
emu_thread->runOnThread([&] {
|
||||
core->saveState(filename);
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::reset()
|
||||
{
|
||||
core->softReset();
|
||||
emu_thread->runOnThread([&] {
|
||||
core->softReset();
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::powerCycle()
|
||||
{
|
||||
core->reset();
|
||||
emu_thread->runOnThread([&] {
|
||||
core->reset();
|
||||
});
|
||||
}
|
||||
|
||||
void EmuApplication::loadUndoState()
|
||||
{
|
||||
core->loadUndoState();
|
||||
emu_thread->runOnThread([&] {
|
||||
core->loadUndoState();
|
||||
});
|
||||
}
|
||||
|
||||
std::string EmuApplication::getStateFolder()
|
||||
|
@ -445,3 +521,109 @@ bool EmuApplication::isCoreActive()
|
|||
{
|
||||
return core->active;
|
||||
}
|
||||
|
||||
void EmuThread::runOnThread(std::function<void()> func)
|
||||
{
|
||||
if (QThread::currentThread() != this)
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "runOnThread", Q_ARG(std::function<void()>, func));
|
||||
return;
|
||||
}
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
EmuThread::EmuThread(QThread *main_thread_)
|
||||
: main_thread(main_thread_), QThread()
|
||||
{
|
||||
qRegisterMetaType<std::function<void()>>("std::function<void()>");
|
||||
}
|
||||
|
||||
void EmuThread::setStatusBits(int new_status)
|
||||
{
|
||||
std::unique_lock lock(status_mutex);
|
||||
status |= new_status;
|
||||
lock.unlock();
|
||||
status_cond.notify_all();
|
||||
}
|
||||
|
||||
void EmuThread::unsetStatusBits(int new_status)
|
||||
{
|
||||
printf("Old: %08x, new: %08x\n", status, new_status);
|
||||
std::unique_lock lock(status_mutex);
|
||||
status &= ~new_status;
|
||||
printf("Final: %08x\n", status);
|
||||
lock.unlock();
|
||||
status_cond.notify_all();
|
||||
}
|
||||
|
||||
void EmuThread::waitForStatusBit(int new_status)
|
||||
{
|
||||
if (status & new_status)
|
||||
return;
|
||||
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock lock(status_mutex);
|
||||
status_cond.wait_for(lock, std::chrono::milliseconds(500));
|
||||
if (status & new_status)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::waitForStatusBitCleared(int new_status)
|
||||
{
|
||||
if (!(status & new_status))
|
||||
return;
|
||||
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock lock(status_mutex);
|
||||
status_cond.wait_for(lock, std::chrono::milliseconds(500));
|
||||
if (!(status & new_status))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::pause()
|
||||
{
|
||||
runOnThread([&] { setStatusBits(ePaused); });
|
||||
waitForStatusBit(ePaused);
|
||||
}
|
||||
|
||||
void EmuThread::unpause()
|
||||
{
|
||||
runOnThread([&] { unsetStatusBits(ePaused); });
|
||||
waitForStatusBitCleared(ePaused);
|
||||
}
|
||||
|
||||
void EmuThread::run()
|
||||
{
|
||||
auto event_loop = new QEventLoop();
|
||||
|
||||
setStatusBits(ePaused);
|
||||
|
||||
while (1)
|
||||
{
|
||||
event_loop->processEvents();
|
||||
|
||||
if (status & eQuit)
|
||||
break;
|
||||
|
||||
if (status & (ePaused | eSuspended))
|
||||
{
|
||||
std::this_thread::sleep_for(2ms);
|
||||
printf("Paused: %08x\n", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Loop\n");
|
||||
if (main_loop)
|
||||
main_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::setMainLoop(std::function<void ()> loop)
|
||||
{
|
||||
main_loop = loop;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <QApplication>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
|
||||
#include "EmuMainWindow.hpp"
|
||||
#include "EmuConfig.hpp"
|
||||
|
@ -8,6 +9,39 @@
|
|||
#include "Snes9xController.hpp"
|
||||
#include "common/audio/s9x_sound_driver.hpp"
|
||||
|
||||
struct EmuThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
EmuThread(QThread *main_thread);
|
||||
QThread *main_thread;
|
||||
void run() override;
|
||||
void waitForStatusBit(int);
|
||||
void waitForStatusBitCleared(int);
|
||||
void setStatusBits(int);
|
||||
void unsetStatusBits(int);
|
||||
void pause();
|
||||
void unpause();
|
||||
void setMainLoop(std::function<void()> loop);
|
||||
|
||||
std::function<void()> main_loop = nullptr;
|
||||
|
||||
enum StatusBits
|
||||
{
|
||||
eDead = 0,
|
||||
ePaused = 1,
|
||||
eSuspended = 2,
|
||||
eRunning = 4,
|
||||
eQuit = 8
|
||||
};
|
||||
|
||||
int status = eDead;
|
||||
std::mutex status_mutex;
|
||||
std::condition_variable status_cond;
|
||||
|
||||
public slots:
|
||||
void runOnThread(std::function<void()> func);
|
||||
};
|
||||
|
||||
struct EmuApplication
|
||||
{
|
||||
|
@ -16,6 +50,7 @@ struct EmuApplication
|
|||
std::unique_ptr<SDLInputManager> input_manager;
|
||||
std::unique_ptr<EmuMainWindow> window;
|
||||
std::unique_ptr<S9xSoundDriver> sound_driver;
|
||||
std::unique_ptr<EmuThread> emu_thread;
|
||||
Snes9xController *core;
|
||||
|
||||
EmuApplication();
|
||||
|
@ -26,12 +61,16 @@ struct EmuApplication
|
|||
void updateBindings();
|
||||
bool isBound(EmuBinding b);
|
||||
void reportBinding(EmuBinding b, bool active);
|
||||
void startInputTimer();
|
||||
void pollJoysticks();
|
||||
void restartAudio();
|
||||
void writeSamples(int16_t *data, int samples);
|
||||
void mainLoop();
|
||||
void pause();
|
||||
void reset();
|
||||
void powerCycle();
|
||||
void suspendThread();
|
||||
void unsuspendThread();
|
||||
bool isPaused();
|
||||
void unpause();
|
||||
void loadState(int slot);
|
||||
|
@ -41,9 +80,8 @@ struct EmuApplication
|
|||
std::string getStateFolder();
|
||||
void loadUndoState();
|
||||
void startGame();
|
||||
void startIdleLoop();
|
||||
void stopIdleLoop();
|
||||
void idleLoop();
|
||||
void startThread();
|
||||
void stopThread();
|
||||
bool isCoreActive();
|
||||
|
||||
enum Handler
|
||||
|
@ -52,9 +90,10 @@ struct EmuApplication
|
|||
UI = 1
|
||||
};
|
||||
std::map<uint32_t, std::pair<std::string, Handler>> bindings;
|
||||
std::unique_ptr<QTimer> idle_loop;
|
||||
std::unique_ptr<QTimer> poll_input_timer;
|
||||
std::function<void(EmuBinding)> binding_callback = nullptr;
|
||||
std::function<void()> joypads_changed_callback = nullptr;
|
||||
int save_slot = 0;
|
||||
int pause_count = 0;
|
||||
int suspend_count = 0;
|
||||
};
|
|
@ -14,6 +14,7 @@ class EmuCanvas : public QWidget
|
|||
virtual void draw() = 0;
|
||||
void output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate);
|
||||
void throttle();
|
||||
void resizeEvent(QResizeEvent *event) override = 0;
|
||||
|
||||
virtual std::vector<std::string> getDeviceList()
|
||||
{
|
||||
|
|
|
@ -45,3 +45,8 @@ void EmuCanvasQt::paintEvent(QPaintEvent *event)
|
|||
|
||||
paint.drawImage(dest, image, QRect(0, 0, output_data.width, output_data.height));
|
||||
}
|
||||
|
||||
void EmuCanvasQt::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
|
@ -15,6 +15,7 @@ class EmuCanvasQt : public EmuCanvas
|
|||
virtual void draw() override;
|
||||
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -109,8 +109,10 @@ void EmuMainWindow::createCanvas()
|
|||
|
||||
void EmuMainWindow::recreateCanvas()
|
||||
{
|
||||
app->suspendThread();
|
||||
destroyCanvas();
|
||||
createCanvas();
|
||||
app->unsuspendThread();
|
||||
}
|
||||
|
||||
void EmuMainWindow::setCoreActionsEnabled(bool enable)
|
||||
|
@ -528,6 +530,15 @@ void EmuMainWindow::toggleFullscreen()
|
|||
|
||||
bool EmuMainWindow::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == canvas && event->type() == QEvent::Resize)
|
||||
{
|
||||
app->suspendThread();
|
||||
canvas->resizeEvent((QResizeEvent *)event);
|
||||
event->accept();
|
||||
app->unsuspendThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -26,12 +26,11 @@ int main(int argc, char *argv[])
|
|||
emu.window->show();
|
||||
|
||||
emu.updateBindings();
|
||||
emu.startIdleLoop();
|
||||
|
||||
|
||||
|
||||
emu.startInputTimer();
|
||||
emu.startThread();
|
||||
emu.qtapp->exec();
|
||||
|
||||
emu.stopThread();
|
||||
emu.config->saveFile(EmuConfig::findConfigFile());
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in New Issue