1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18/*
19 * This class is the core of stella's scheduling. Scheduling is a two step process
20 * that is shared between the main loop in OSystem and this class.
21 *
22 * In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop
23 * instructs the emulation worker to start emulation on a separate thread and then proceeds
24 * to render the last frame produced by the TIA (if any). After the frame has been rendered,
25 * the worker is stopped, and the main thread sleeps until the time allotted to the emulation
26 * timeslice (as calculated from the 6507 cycles that have passed) has been reached. After
27 * that, it iterates.
28 *
29 * The emulation worker does its own microscheduling. After emulating a timeslice, it sleeps
30 * until either the allotted time is up or it has been signalled to stop. If the time is up
31 * without being signalled, the worker will emulate another timeslice, etc.
32 *
33 * In combination, the scheduling in the main loop and the microscheduling in the worker
34 * ensure that the emulation continues to run even if rendering blocks, ensuring the real
35 * time scheduling required for cycle exact audio to work.
36 */
37
38#ifndef EMULATION_WORKER_HXX
39#define EMULATION_WORKER_HXX
40
41#include <atomic>
42#include <mutex>
43#include <condition_variable>
44#include <thread>
45#include <exception>
46#include <chrono>
47
48#include "bspf.hxx"
49
50class TIA;
51class DispatchResult;
52
53class EmulationWorker
54{
55 public:
56
57 /**
58 The constructor starts the worker thread and waits until it has initialized.
59 */
60 EmulationWorker();
61
62 /**
63 The destructor signals quit to the worker and joins.
64 */
65 ~EmulationWorker();
66
67 /**
68 Wake up the worker and start emulation with the specified parameters.
69 */
70 void start(uInt32 cyclesPerSecond, uInt64 maxCycles, uInt64 minCycles, DispatchResult* dispatchResult, TIA* tia);
71
72 /**
73 Stop emulation and return the number of 6507 cycles emulated.
74 */
75 uInt64 stop();
76
77 private:
78
79 /**
80 Check whether an exception occurred on the thread and rethrow if appicable.
81 */
82 void handlePossibleException();
83
84 /**
85 The main thread entry point.
86 Passing references into a thread is awkward and requires std::ref -> use pointers here
87 */
88 void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex);
89
90 /**
91 Handle thread wakeup after sleep depending on the thread state.
92 */
93 void handleWakeup(std::unique_lock<std::mutex>& lock);
94
95 /**
96 Handle wakeup while sleeping and waiting to be resumed.
97 */
98 void handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock);
99
100 /**
101 Handle wakeup while sleeping and waiting to be stopped (or for the timeslice
102 to expire).
103 */
104 void handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock);
105
106 /**
107 Run the emulation, adjust the thread state according to the result and sleep.
108 */
109 void dispatchEmulation(std::unique_lock<std::mutex>& lock);
110
111 /**
112 Clear any pending signal and wake up the main thread (if it is waiting for the signal
113 to be cleared).
114 */
115 void clearSignal();
116
117 /**
118 * Signal quit and wake up the main thread if applicable.
119 */
120 void signalQuit();
121
122 /**
123 Wait and sleep until a pending signal has been processed (or quit sigmalled).
124 This is called from the main thread.
125 */
126 void waitUntilPendingSignalHasProcessed();
127
128 /**
129 Log a fatal error to cerr and throw a runtime exception.
130 */
131 [[noreturn]] void fatal(string message);
132
133 private:
134 /**
135 Thread state.
136 */
137 enum class State {
138 // Initial state
139 initializing,
140 // Thread has initialized. From the point, myThreadIsRunningMutex is locked if and only if
141 // the thread is running.
142 initialized,
143 // Sleeping and waiting for emulation to be resumed
144 waitingForResume,
145 // Running and emulating
146 running,
147 // Sleeping and waiting for emulation to be stopped
148 waitingForStop,
149 // An exception occurred and the thread has terminated (or is terminating)
150 exception
151 };
152
153 /**
154 Thread behavior is controlled by signals that are raised prior to waking up the thread.
155 */
156 enum class Signal {
157 // Resume emulation
158 resume,
159 // Stop emulation
160 stop,
161 // Quit (either during destruction or after an exception)
162 quit,
163 // No pending signal
164 none
165 };
166
167 private:
168
169 // Worker thread
170 std::thread myThread;
171
172 // Condition variable for waking up the thread
173 std::condition_variable myWakeupCondition;
174 // The thread is running if and only if while this mutex is locked
175 std::mutex myThreadIsRunningMutex;
176
177 // Condition variable to signal changes to the pending signal
178 std::condition_variable mySignalChangeCondition;
179 // This mutex guards reading and writing the pending signal.
180 std::mutex mySignalChangeMutex;
181
182 // Any exception on the worker thread is saved here to be rethrown on the main thread.
183 std::exception_ptr myPendingException;
184
185 // Any pending signal (or Signal::none)
186 Signal myPendingSignal;
187 // The initial access to myState is not synchronized -> make this atomic
188 std::atomic<State> myState;
189
190 // Emulation parameters
191 TIA* myTia;
192 uInt64 myCyclesPerSecond;
193 uInt64 myMaxCycles;
194 uInt64 myMinCycles;
195 DispatchResult* myDispatchResult;
196
197 // Total number of cycles during this emulation run
198 uInt64 myTotalCycles;
199 // 6507 time
200 std::chrono::time_point<std::chrono::high_resolution_clock> myVirtualTime;
201
202 private:
203
204 EmulationWorker(const EmulationWorker&) = delete;
205
206 EmulationWorker(EmulationWorker&&) = delete;
207
208 EmulationWorker& operator=(const EmulationWorker&) = delete;
209
210 EmulationWorker& operator=(EmulationWorker&&) = delete;
211};
212
213#endif // EMULATION_WORKER_HXX
214