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 | |
50 | class TIA; |
51 | class DispatchResult; |
52 | |
53 | class 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 | |