Emulation worker. Currently untested and disconnected.

This commit is contained in:
Christian Speckner 2018-06-07 20:43:56 +02:00
parent 9e4dbd6a3a
commit afb1e1d1e1
4 changed files with 359 additions and 1 deletions

11
.vscode/settings.json vendored
View File

@ -43,6 +43,15 @@
"stdexcept": "cpp",
"fstream": "cpp",
"__locale": "cpp",
"__string": "cpp"
"__string": "cpp",
"__config": "cpp",
"__nullptr": "cpp",
"cstddef": "cpp",
"exception": "cpp",
"initializer_list": "cpp",
"new": "cpp",
"typeinfo": "cpp",
"__mutex_base": "cpp",
"mutex": "cpp"
}
}

View File

@ -0,0 +1,256 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <exception>
#include "EmulationWorker.hxx"
#include "DispatchResult.hxx"
#include "TIA.hxx"
using namespace std::chrono;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationWorker::EmulationWorker(TIA& tia) : myTia(tia)
{
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
std::condition_variable threadInitialized;
myThread = std::thread(
&EmulationWorker::threadMain, this, &threadInitialized, &mutex
);
// Wait until the thread has acquired myMutex and moved on
while (myState == State::initializing) threadInitialized.wait(lock);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationWorker::~EmulationWorker()
{
// This has to run in a block in order to release the mutex before joining
{
std::unique_lock<std::mutex> lock(myMutex);
if (myState != State::exception) {
myPendingSignal = Signal::quit;
mySignalCondition.notify_one();
}
}
myThread.join();
handlePossibleException();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handlePossibleException()
{
if (myState == State::exception && myPendingException) {
std::exception_ptr ex = myPendingException;
// Make sure that the exception is not thrown a second time (destructor!!!)
myPendingException = nullptr;
std::rethrow_exception(ex);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult)
{
// Optimization: run this in a block to unlock the mutex before notifying the thread;
// otherwise, the thread would immediatelly block again until we have released the mutex,
// sacrificing a timeslice
{
// Aquire the mutex -> wait until the thread is suspended
std::unique_lock<std::mutex> lock(myMutex);
// Pass on possible exceptions
handlePossibleException();
// NB: The thread does not suspend execution in State::initialized
if (myState != State::waitingForResume)
throw runtime_error("start called on running or dead worker");
// Store the parameters for emulation
myCyclesPerSecond = cyclesPerSecond;
myMaxCycles = maxCycles;
myMinCycles = minCycles;
myDispatchResult = dispatchResult;
// Set the signal...
myPendingSignal = Signal::resume;
}
// ... and wakeup the thread
mySignalCondition.notify_one();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::stop()
{
// See EmulationWorker::start for the gory details
{
std::unique_lock<std::mutex> lock(myMutex);
handlePossibleException();
// If the worker has stopped on its own, we return
if (myState == State::waitingForResume) return;
// NB: The thread does not suspend execution in State::initialized or State::running
if (myState != State::waitingForStop)
throw runtime_error("stop called on a dead worker");
myPendingSignal = Signal::stop;
}
mySignalCondition.notify_one();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex)
{
std::unique_lock<std::mutex> lock(myMutex);
try {
// Optimization: release the lock before notifying our parent -> saves a timeslice
{
// Wait until our parent releases the lock and sleeps
std::lock_guard<std::mutex> guard(*initializationMutex);
// Update the state...
myState = State::initialized;
}
// ... and wake up our parent to notifiy that we have initialized. From this point, the
// parent can safely assume that we are running while the mutex is locked.
initializedCondition->notify_one();
while (myPendingSignal != Signal::quit) handleWakeup(lock);
}
catch (...) {
myPendingException = std::current_exception();
myState = State::exception;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeup(std::unique_lock<std::mutex>& lock)
{
switch (myState) {
case State::initialized:
myState = State::waitingForResume;
mySignalCondition.wait(lock);
break;
case State::waitingForResume:
handleWakeupFromWaitingForResume(lock);
break;
case State::waitingForStop:
handleWakeupFromWaitingForStop(lock);
break;
default:
throw runtime_error("wakeup in invalid worker state");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock)
{
switch (myPendingSignal) {
case Signal::resume:
myPendingSignal = Signal::none;
dispatchEmulation(lock);
break;
case Signal::none:
mySignalCondition.wait(lock);
break;
case Signal::quit:
break;
default:
throw runtime_error("invalid signal while waiting for resume");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock)
{
switch (myPendingSignal) {
case Signal::stop:
myState = State::waitingForResume;
myPendingSignal = Signal::none;
mySignalCondition.wait(lock);
break;
case Signal::none:
if (myWakeupPoint <= high_resolution_clock::now())
mySignalCondition.wait_until(lock, myWakeupPoint);
else {
myState = State::waitingForResume;
mySignalCondition.wait(lock);
}
break;
case Signal::quit:
break;
default:
throw runtime_error("invalid signal while waiting for stop");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::dispatchEmulation(std::unique_lock<std::mutex>& lock)
{
myState = State::running;
time_point<high_resolution_clock> now = high_resolution_clock::now();
uInt64 totalCycles = 0;
do {
myTia.update(*myDispatchResult, totalCycles > 0 ? myMinCycles - totalCycles : myMaxCycles);
} while (totalCycles < myMinCycles && myDispatchResult->getStatus() == DispatchResult::Status::ok);
if (myDispatchResult->getStatus() == DispatchResult::Status::ok) {
// If emulation finished successfully, we can go for another round
duration<double> timesliceSeconds(static_cast<double>(totalCycles) / static_cast<double>(myCyclesPerSecond));
myWakeupPoint = now + duration_cast<high_resolution_clock::duration>(timesliceSeconds);
myState = State::waitingForStop;
if (myWakeupPoint > high_resolution_clock::now())
// If we can keep up with the emulation, we sleep
mySignalCondition.wait_until(lock, myWakeupPoint);
else {
// If we are already lagging behind, we briefly relinquish control over the mutex
// and yield to scheduler, to make sure that the main thread has a chance to stop us
lock.release();
std::this_thread::yield();
lock.lock();
}
} else {
// If execution trapped, we stop immediatelly.
myState = State::waitingForResume;
mySignalCondition.wait(lock);
}
}

View File

@ -0,0 +1,92 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef EMULATION_WORKER_HXX
#define EMULATION_WORKER_HXX
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <exception>
#include <chrono>
#include "bspf.hxx"
class TIA;
class DispatchResult;
class EmulationWorker
{
public:
enum class State {
initializing, initialized, waitingForResume, running, waitingForStop, exception
};
enum class Signal {
resume, stop, quit, none
};
public:
EmulationWorker(TIA& tia);
~EmulationWorker();
void handlePossibleException();
void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult);
void stop();
private:
void handleException();
// Passing references into a thread is awkward and requires std::ref -> use pointers here
void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex);
void handleWakeup(std::unique_lock<std::mutex>& lock);
void handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock);
void handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock);
void dispatchEmulation(std::unique_lock<std::mutex>& lock);
private:
TIA& myTia;
std::thread myThread;
std::condition_variable mySignalCondition;
std::mutex myMutex;
std::exception_ptr myPendingException;
Signal myPendingSignal;
// The initial access to myState is not synchronized -> make this atomic
std::atomic<State> myState;
uInt32 myCyclesPerSecond;
uInt32 myMaxCycles;
uInt32 myMinCycles;
DispatchResult* myDispatchResult;
std::chrono::time_point<std::chrono::high_resolution_clock> myWakeupPoint;
};
#endif // EMULATION_WORKER_HXX

View File

@ -55,6 +55,7 @@ MODULE_OBJS := \
src/emucore/Driving.o \
src/emucore/EventHandler.o \
src/emucore/EmulationTiming.o \
src/emucore/EmulationWorker.o \
src/emucore/FrameBuffer.o \
src/emucore/FBSurface.o \
src/emucore/FSNode.o \