1 #include "emuthread.h"
2
3 #include "../../core/control.h"
4 #include "../../core/cpu.h"
5 #include "../../core/emu.h"
6 #include "../../core/extras.h"
7 #include "../../core/link.h"
8 #include "../../tests/autotester/autotester.h"
9 #include "../../tests/autotester/crc32.hpp"
10 #include "capture/animated-png.h"
11
12 #include <cassert>
13 #include <cstdarg>
14 #include <thread>
15
16 static EmuThread *emu;
17
18 // reimplemented callbacks
19
gui_console_clear(void)20 void gui_console_clear(void) {
21 emu->consoleClear();
22 }
23
gui_console_printf(const char * format,...)24 void gui_console_printf(const char *format, ...) {
25 va_list args;
26 va_start(args, format);
27 emu->writeConsole(EmuThread::ConsoleNorm, format, args);
28 va_end(args);
29 }
30
gui_console_err_printf(const char * format,...)31 void gui_console_err_printf(const char *format, ...) {
32 va_list args;
33 va_start(args, format);
34 emu->writeConsole(EmuThread::ConsoleErr, format, args);
35 va_end(args);
36 }
37
gui_debug_open(int reason,uint32_t data)38 void gui_debug_open(int reason, uint32_t data) {
39 emu->debugOpen(reason, data);
40 }
41
gui_debug_close(void)42 void gui_debug_close(void) {
43 emu->debugDisable();
44 }
45
EmuThread(QObject * parent)46 EmuThread::EmuThread(QObject *parent) : QThread{parent}, write{CONSOLE_BUFFER_SIZE},
47 m_speed{100}, m_throttle{true},
48 m_lastTime{std::chrono::steady_clock::now()},
49 m_debug{false} {
50 assert(emu == nullptr);
51 emu = this;
52 }
53
run()54 void EmuThread::run() {
55 while (cpu.abort != CPU_ABORT_EXIT) {
56 emu_run(1u);
57 doStuff();
58 throttleWait();
59 }
60 asic_free();
61 }
62
writeConsole(int console,const char * format,va_list args)63 void EmuThread::writeConsole(int console, const char *format, va_list args) {
64 if (type != console) {
65 write.acquire(CONSOLE_BUFFER_SIZE);
66 type = console;
67 write.release(CONSOLE_BUFFER_SIZE);
68 }
69 int available = write.available();
70 int remaining = CONSOLE_BUFFER_SIZE - writePos;
71 int space = available < remaining ? available : remaining;
72 int size;
73 va_list argsCopy;
74 va_copy(argsCopy, args);
75 size = vsnprintf(buffer + writePos, space, format, argsCopy);
76 va_end(argsCopy);
77 if (size >= 0 && size < space) {
78 if (size == 0) {
79 return;
80 }
81 write.acquire(size);
82 writePos += size;
83 read.release(size);
84 emit consoleStr();
85 } else {
86 if (size < 0) {
87 va_copy(argsCopy, args);
88 size = vsnprintf(nullptr, 0, format, argsCopy);
89 va_end(argsCopy);
90 if (size <= 0) {
91 return;
92 }
93 }
94 char *tmp = size < available - remaining ? buffer : new char[size + 1];
95 if (tmp && vsnprintf(tmp, size + 1, format, args) >= 0) {
96 int tmpPos = 0;
97 while (size >= remaining) {
98 write.acquire(remaining);
99 memcpy(buffer + writePos, tmp + tmpPos, remaining);
100 tmpPos += remaining;
101 size -= remaining;
102 writePos = 0;
103 read.release(remaining);
104 remaining = CONSOLE_BUFFER_SIZE;
105 emit consoleStr();
106 }
107 if (size) {
108 write.acquire(size);
109 memmove(buffer + writePos, tmp + tmpPos, size);
110 writePos += size;
111 read.release(size);
112 emit consoleStr();
113 }
114 }
115 if (tmp != buffer) {
116 delete [] tmp;
117 }
118 }
119 }
120
doStuff()121 void EmuThread::doStuff() {
122 const std::chrono::steady_clock::time_point cur_time = std::chrono::steady_clock::now();
123
124 while (!m_reqQueue.isEmpty()) {
125 int req = m_reqQueue.dequeue();
126 switch (+req) {
127 default:
128 break;
129 case RequestPause:
130 block(req);
131 break;
132 case RequestReset:
133 cpu_crash("user request");
134 break;
135 case RequestSend:
136 sendFiles();
137 break;
138 case RequestReceive:
139 block(req);
140 break;
141 case RequestDebugger:
142 debug_open(DBG_USER, 0);
143 break;
144 case RequestSave:
145 emit saved(emu_save(m_saveType, m_savePath.toStdString().c_str()));
146 break;
147 case RequestLoad:
148 emit loaded(emu_load(m_loadType, m_loadPath.toStdString().c_str()), m_loadType);
149 break;
150 case RequestAutoTester:
151 uint32_t run_rate_prev = emu_get_run_rate();
152 emu_set_run_rate(1000);
153 if (!autotester::doTestSequence()) {
154 emit tested(1);
155 } else {
156 emit tested(0);
157 }
158 emu_set_run_rate(run_rate_prev);
159 break;
160 }
161 }
162
163 {
164 QMutexLocker locker(&m_keyQueueMutex);
165 while (!m_keyQueue.isEmpty() && sendKey(m_keyQueue.head())) {
166 m_keyQueue.dequeue();
167 }
168 }
169
170 m_lastTime += std::chrono::steady_clock::now() - cur_time;
171 }
172
throttleWait()173 void EmuThread::throttleWait() {
174 int speed;
175 bool throttle;
176 {
177 std::unique_lock<std::mutex> lockSpeed(m_mutexSpeed);
178 speed = m_speed;
179 if (!speed) {
180 sendSpeed(0);
181 m_cvSpeed.wait(lockSpeed, [this] { return m_speed != 0; });
182 speed = m_speed;
183 m_lastTime = std::chrono::steady_clock::now();
184 }
185 throttle = m_throttle;
186 }
187 std::chrono::duration<int, std::ratio<100, 60>> unit(1);
188 std::chrono::steady_clock::duration interval(std::chrono::duration_cast<std::chrono::steady_clock::duration>
189 (std::chrono::duration<int, std::ratio<1, 60 * 1000000>>(1000000 * 100 / speed)));
190 std::chrono::steady_clock::time_point cur_time = std::chrono::steady_clock::now(), next_time = m_lastTime + interval;
191 if (throttle && cur_time < next_time) {
192 sendSpeed(speed);
193 m_lastTime = next_time;
194 std::this_thread::sleep_until(next_time);
195 } else {
196 if (m_lastTime != cur_time) {
197 sendSpeed(unit / (cur_time - m_lastTime));
198 m_lastTime = cur_time;
199 }
200 std::this_thread::yield();
201 }
202 }
203
unblock()204 void EmuThread::unblock() {
205 m_mutex.lock();
206 m_cv.notify_all();
207 m_mutex.unlock();
208 }
209
reset()210 void EmuThread::reset() {
211 req(RequestReset);
212 }
213
receive()214 void EmuThread::receive() {
215 req(RequestReceive);
216 }
217
send(const QStringList & list,int location)218 void EmuThread::send(const QStringList &list, int location) {
219 m_vars = list;
220 m_sendLoc = location;
221 req(RequestSend);
222 }
223
enqueueKeys(quint16 key1,quint16 key2,bool repeat)224 void EmuThread::enqueueKeys(quint16 key1, quint16 key2, bool repeat) {
225 if (!repeat || m_keyQueue.isEmpty() ||
226 (m_keyQueue.front() != key1 && m_keyQueue.front() != key2)) {
227 QMutexLocker locker(&m_keyQueueMutex);
228 for (auto key : {key1, key2}) {
229 if (key) {
230 m_keyQueue.enqueue(key);
231 }
232 }
233 }
234 }
235
test(const QString & config,bool run)236 void EmuThread::test(const QString &config, bool run) {
237 m_autotesterPath = config;
238 m_autotesterRun = run;
239 req(RequestAutoTester);
240 }
241
save(emu_data_t type,const QString & path)242 void EmuThread::save(emu_data_t type, const QString &path) {
243 m_savePath = path;
244 m_saveType = type;
245 req(RequestSave);
246 }
247
setSpeed(int value)248 void EmuThread::setSpeed(int value) {
249 {
250 std::unique_lock<std::mutex> lockSpeed(m_mutexSpeed);
251 m_speed = value;
252 }
253 if (value) {
254 m_cvSpeed.notify_one();
255 }
256 }
257
setThrottle(bool state)258 void EmuThread::setThrottle(bool state) {
259 std::unique_lock<std::mutex> lockSpeed(m_mutexSpeed);
260 m_throttle = state;
261 }
262
debugOpen(int reason,uint32_t data)263 void EmuThread::debugOpen(int reason, uint32_t data) {
264 std::unique_lock<std::mutex> lock(m_mutexDebug);
265 m_debug = true;
266 emit debugCommand(reason, data);
267 m_cvDebug.wait(lock, [this](){ return !m_debug; });
268 }
269
resume()270 void EmuThread::resume() {
271 {
272 std::lock_guard<std::mutex> lock(m_mutexDebug);
273 m_debug = false;
274 }
275 m_cvDebug.notify_all();
276 }
277
debug(bool state)278 void EmuThread::debug(bool state) {
279 bool oldState;
280 {
281 std::lock_guard<std::mutex> lock(m_mutexDebug);
282 oldState = m_debug;
283 }
284 if (oldState && !state) {
285 resume();
286 }
287 if (state) {
288 req(RequestDebugger);
289 }
290 }
291
load(emu_data_t type,const QString & path)292 void EmuThread::load(emu_data_t type, const QString &path) {
293
294 /* if loading an image or rom, we need to restart emulation */
295 if (type == EMU_DATA_IMAGE || type == EMU_DATA_ROM) {
296 setTerminationEnabled();
297 stop();
298
299 emit loaded(emu_load(type, path.toStdString().c_str()), type);
300 } else if (type == EMU_DATA_RAM) {
301 m_loadPath = path;
302 m_loadType = type;
303 req(RequestLoad);
304 }
305 }
306
stop()307 void EmuThread::stop() {
308 if (!isRunning()) {
309 return;
310 }
311 emu_exit();
312 if (!wait(200)) {
313 terminate();
314 wait(300);
315 }
316 }
317