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