mirror of https://github.com/stella-emu/stella.git
Emulation worker. Currently untested and disconnected.
This commit is contained in:
parent
9e4dbd6a3a
commit
afb1e1d1e1
|
@ -43,6 +43,15 @@
|
||||||
"stdexcept": "cpp",
|
"stdexcept": "cpp",
|
||||||
"fstream": "cpp",
|
"fstream": "cpp",
|
||||||
"__locale": "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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -55,6 +55,7 @@ MODULE_OBJS := \
|
||||||
src/emucore/Driving.o \
|
src/emucore/Driving.o \
|
||||||
src/emucore/EventHandler.o \
|
src/emucore/EventHandler.o \
|
||||||
src/emucore/EmulationTiming.o \
|
src/emucore/EmulationTiming.o \
|
||||||
|
src/emucore/EmulationWorker.o \
|
||||||
src/emucore/FrameBuffer.o \
|
src/emucore/FrameBuffer.o \
|
||||||
src/emucore/FBSurface.o \
|
src/emucore/FBSurface.o \
|
||||||
src/emucore/FSNode.o \
|
src/emucore/FSNode.o \
|
||||||
|
|
Loading…
Reference in New Issue