mirror of https://github.com/stella-emu/stella.git
Documentation.
This commit is contained in:
parent
8781889a7f
commit
0fbd875783
|
@ -16,8 +16,8 @@
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class is the core of stella's real time scheduling. Scheduling is a two step
|
* This class is the core of stella's scheduling. Scheduling is a two step process
|
||||||
* process that is shared between the main loop in OSystem and this class.
|
* that is shared between the main loop in OSystem and this class.
|
||||||
*
|
*
|
||||||
* In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop
|
* In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop
|
||||||
* instructs the emulation worker to start emulation on a separate thread and then proceeds
|
* instructs the emulation worker to start emulation on a separate thread and then proceeds
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
* timeslice (as calculated from the 6507 cycles that have passed) has been reached. After
|
* timeslice (as calculated from the 6507 cycles that have passed) has been reached. After
|
||||||
* that, it iterates.
|
* that, it iterates.
|
||||||
*
|
*
|
||||||
* The emulation worker contains its own microscheduling. After emulating a timeslice, it sleeps
|
* The emulation worker does its own microscheduling. After emulating a timeslice, it sleeps
|
||||||
* until either the allotted time is up or it has been signalled to stop. If the time is up
|
* until either the allotted time is up or it has been signalled to stop. If the time is up
|
||||||
* without the signal, the worker will emulate another timeslice, etc.
|
* without being signalled, the worker will emulate another timeslice, etc.
|
||||||
*
|
*
|
||||||
* In combination, the scheduling in the main loop and the microscheduling in the worker
|
* In combination, the scheduling in the main loop and the microscheduling in the worker
|
||||||
* ensure that the emulation continues to run even if rendering blocks, ensuring the real
|
* ensure that the emulation continues to run even if rendering blocks, ensuring the real
|
||||||
|
@ -52,19 +52,16 @@ class DispatchResult;
|
||||||
|
|
||||||
class EmulationWorker
|
class EmulationWorker
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
enum class State {
|
|
||||||
initializing, initialized, waitingForResume, running, waitingForStop, exception
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Signal {
|
|
||||||
resume, stop, quit, none
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
The constructor starts the worker thread and waits until it has initialized.
|
||||||
|
*/
|
||||||
EmulationWorker();
|
EmulationWorker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
The destructor signals quit to the worker and joins.
|
||||||
|
*/
|
||||||
~EmulationWorker();
|
~EmulationWorker();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,52 +76,127 @@ class EmulationWorker
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check whether an exception occurred on the thread and rethrow if appicable.
|
||||||
|
*/
|
||||||
void handlePossibleException();
|
void handlePossibleException();
|
||||||
|
|
||||||
// Passing references into a thread is awkward and requires std::ref -> use pointers here
|
/**
|
||||||
|
The main thread entry point.
|
||||||
|
Passing references into a thread is awkward and requires std::ref -> use pointers here
|
||||||
|
*/
|
||||||
void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex);
|
void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handle thread wakeup after sleep depending on the thread state.
|
||||||
|
*/
|
||||||
void handleWakeup(std::unique_lock<std::mutex>& lock);
|
void handleWakeup(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handle wakeup while sleeping and waiting to be resumed.
|
||||||
|
*/
|
||||||
void handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock);
|
void handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handle wakeup while sleeping and waiting to be stopped (or for the timeslice
|
||||||
|
to expire).
|
||||||
|
*/
|
||||||
void handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock);
|
void handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Run the emulation, adjust the thread state according to the result and sleep.
|
||||||
|
*/
|
||||||
void dispatchEmulation(std::unique_lock<std::mutex>& lock);
|
void dispatchEmulation(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clear any pending signal and wake up the main thread (if it is waiting for the signal
|
||||||
|
to be cleared).
|
||||||
|
*/
|
||||||
void clearSignal();
|
void clearSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal quit and wake up the main thread if applicable.
|
||||||
|
*/
|
||||||
void signalQuit();
|
void signalQuit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Wait and sleep until a pending signal has been processed (or quit sigmalled).
|
||||||
|
This is called from the main thread.
|
||||||
|
*/
|
||||||
void waitUntilPendingSignalHasProcessed();
|
void waitUntilPendingSignalHasProcessed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Log a fatal error to cerr and throw a runtime exception.
|
||||||
|
*/
|
||||||
void fatal(string message);
|
void fatal(string message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
Thread state.
|
||||||
|
*/
|
||||||
|
enum class State {
|
||||||
|
// Initial state
|
||||||
|
initializing,
|
||||||
|
// Thread has initialized. From the point, myThreadIsRunningMutex is locked if and only if
|
||||||
|
// the thread is running.
|
||||||
|
initialized,
|
||||||
|
// Sleeping and waiting for emulation to be resumed
|
||||||
|
waitingForResume,
|
||||||
|
// Running and emulating
|
||||||
|
running,
|
||||||
|
// Sleeping and waiting for emulation to be stopped
|
||||||
|
waitingForStop,
|
||||||
|
// An exception occurred and the thread has terminated (or is terminating)
|
||||||
|
exception
|
||||||
|
};
|
||||||
|
|
||||||
TIA* myTia;
|
/**
|
||||||
|
Thread behavior is controlled by signals that are raised prior to waking up the thread.
|
||||||
|
*/
|
||||||
|
enum class Signal {
|
||||||
|
// Resume emulation
|
||||||
|
resume,
|
||||||
|
// Stop emulation
|
||||||
|
stop,
|
||||||
|
// Quit (either during destruction or after an exception)
|
||||||
|
quit,
|
||||||
|
// No pending signal
|
||||||
|
none
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Worker thread
|
||||||
std::thread myThread;
|
std::thread myThread;
|
||||||
|
|
||||||
// Condition variable for waking up the thread
|
// Condition variable for waking up the thread
|
||||||
std::condition_variable myWakeupCondition;
|
std::condition_variable myWakeupCondition;
|
||||||
// THe thread is running while this mutex is locked
|
// The thread is running if and only if while this mutex is locked
|
||||||
std::mutex myThreadIsRunningMutex;
|
std::mutex myThreadIsRunningMutex;
|
||||||
|
|
||||||
|
// Condition variable to signal changes to the pending signal
|
||||||
std::condition_variable mySignalChangeCondition;
|
std::condition_variable mySignalChangeCondition;
|
||||||
|
// This mutex guards reading and writing the pending signal.
|
||||||
std::mutex mySignalChangeMutex;
|
std::mutex mySignalChangeMutex;
|
||||||
|
|
||||||
|
// Any exception on the worker thread is saved here to be rethrown on the main thread.
|
||||||
std::exception_ptr myPendingException;
|
std::exception_ptr myPendingException;
|
||||||
|
|
||||||
|
// Any pending signal (or Signal::none)
|
||||||
Signal myPendingSignal;
|
Signal myPendingSignal;
|
||||||
// The initial access to myState is not synchronized -> make this atomic
|
// The initial access to myState is not synchronized -> make this atomic
|
||||||
std::atomic<State> myState;
|
std::atomic<State> myState;
|
||||||
|
|
||||||
|
// Emulation parameters
|
||||||
|
TIA* myTia;
|
||||||
uInt32 myCyclesPerSecond;
|
uInt32 myCyclesPerSecond;
|
||||||
uInt32 myMaxCycles;
|
uInt32 myMaxCycles;
|
||||||
uInt32 myMinCycles;
|
uInt32 myMinCycles;
|
||||||
DispatchResult* myDispatchResult;
|
DispatchResult* myDispatchResult;
|
||||||
|
|
||||||
|
// Total number of cycles during this emulation run
|
||||||
uInt64 myTotalCycles;
|
uInt64 myTotalCycles;
|
||||||
|
// 6507 time
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> myVirtualTime;
|
std::chrono::time_point<std::chrono::high_resolution_clock> myVirtualTime;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -646,9 +646,14 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
|
||||||
EmulationTiming& timing(myConsole->emulationTiming());
|
EmulationTiming& timing(myConsole->emulationTiming());
|
||||||
DispatchResult dispatchResult;
|
DispatchResult dispatchResult;
|
||||||
|
|
||||||
|
// Check whether we have a frame pending for rendering...
|
||||||
bool framePending = tia.newFramePending();
|
bool framePending = tia.newFramePending();
|
||||||
|
// ... and copy it to the frame buffer. It is important to do this before
|
||||||
|
// the worker is started to avoid racing.
|
||||||
if (framePending) tia.renderToFrameBuffer();
|
if (framePending) tia.renderToFrameBuffer();
|
||||||
|
|
||||||
|
// Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time
|
||||||
|
// and will run until we stop the worker.
|
||||||
emulationWorker.start(
|
emulationWorker.start(
|
||||||
timing.cyclesPerSecond(),
|
timing.cyclesPerSecond(),
|
||||||
timing.maxCyclesPerTimeslice(),
|
timing.maxCyclesPerTimeslice(),
|
||||||
|
@ -657,15 +662,21 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
|
||||||
&tia
|
&tia
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Render the frame. This may block, but emulation will continue to run on the worker, so the
|
||||||
|
// audio pipeline is kept fed :)
|
||||||
if (framePending) myFrameBuffer->updateInEmulationMode();
|
if (framePending) myFrameBuffer->updateInEmulationMode();
|
||||||
|
|
||||||
|
// Stop the worker and wait until it has finished
|
||||||
uInt64 totalCycles = emulationWorker.stop();
|
uInt64 totalCycles = emulationWorker.stop();
|
||||||
|
|
||||||
|
// Break or trap? -> start debugger
|
||||||
if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start();
|
if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start();
|
||||||
|
|
||||||
|
// Handle frying
|
||||||
if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
|
if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
|
||||||
myConsole->fry();
|
myConsole->fry();
|
||||||
|
|
||||||
|
// Return the 6507 time used in seconds
|
||||||
return static_cast<double>(totalCycles) / static_cast<double>(timing.cyclesPerSecond());
|
return static_cast<double>(totalCycles) / static_cast<double>(timing.cyclesPerSecond());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +685,9 @@ void OSystem::mainLoop()
|
||||||
{
|
{
|
||||||
// Sleep-based wait: good for CPU, bad for graphical sync
|
// Sleep-based wait: good for CPU, bad for graphical sync
|
||||||
bool busyWait = mySettings->getString("timing") != "sleep";
|
bool busyWait = mySettings->getString("timing") != "sleep";
|
||||||
|
// 6507 time
|
||||||
time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
|
time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
|
||||||
|
// The emulation worker
|
||||||
EmulationWorker emulationWorker;
|
EmulationWorker emulationWorker;
|
||||||
|
|
||||||
for(;;)
|
for(;;)
|
||||||
|
@ -685,16 +698,19 @@ void OSystem::mainLoop()
|
||||||
double timesliceSeconds;
|
double timesliceSeconds;
|
||||||
|
|
||||||
if (myEventHandler->state() == EventHandlerState::EMULATION)
|
if (myEventHandler->state() == EventHandlerState::EMULATION)
|
||||||
|
// Dispatch emulation and render frame (if applicable)
|
||||||
timesliceSeconds = dispatchEmulation(emulationWorker);
|
timesliceSeconds = dispatchEmulation(emulationWorker);
|
||||||
else {
|
else {
|
||||||
|
// Render the GUI with 30 Hz in all other modes
|
||||||
timesliceSeconds = 1. / 30.;
|
timesliceSeconds = 1. / 30.;
|
||||||
myFrameBuffer->update();
|
myFrameBuffer->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
duration<double> timeslice(timesliceSeconds);
|
duration<double> timeslice(timesliceSeconds);
|
||||||
|
|
||||||
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
|
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
|
||||||
time_point<high_resolution_clock> now = high_resolution_clock::now();
|
time_point<high_resolution_clock> now = high_resolution_clock::now();
|
||||||
|
|
||||||
|
// We allow 6507 time to lag behind by one frame max
|
||||||
double maxLag = myConsole
|
double maxLag = myConsole
|
||||||
? (
|
? (
|
||||||
static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
|
static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
|
||||||
|
@ -703,8 +719,10 @@ void OSystem::mainLoop()
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
|
if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
|
||||||
|
// If 6507 time is lagging behind more than one frame we reset it to real time
|
||||||
virtualTime = now;
|
virtualTime = now;
|
||||||
else if (virtualTime > now) {
|
else if (virtualTime > now) {
|
||||||
|
// Wait until we have caught up with 6507 time
|
||||||
if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) {
|
if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) {
|
||||||
while (high_resolution_clock::now() < virtualTime);
|
while (high_resolution_clock::now() < virtualTime);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue