src/platform/qt/CoreController.cpp (view raw)
1/* Copyright (c) 2013-2017 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "CoreController.h"
7
8#include "ConfigController.h"
9#include "InputController.h"
10#include "LogController.h"
11#include "MultiplayerController.h"
12#include "Override.h"
13
14#include <QDateTime>
15#include <QMutexLocker>
16
17#include <mgba/core/serialize.h>
18#include <mgba/feature/video-logger.h>
19#ifdef M_CORE_GBA
20#include <mgba/internal/gba/gba.h>
21#include <mgba/internal/gba/renderers/tile-cache.h>
22#include <mgba/internal/gba/sharkport.h>
23#endif
24#ifdef M_CORE_GB
25#include <mgba/internal/gb/gb.h>
26#include <mgba/internal/gb/renderers/tile-cache.h>
27#endif
28#include <mgba-util/math.h>
29#include <mgba-util/vfs.h>
30
31using namespace QGBA;
32
33
34CoreController::CoreController(mCore* core, QObject* parent)
35 : QObject(parent)
36 , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
37 , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
38{
39 m_threadContext.core = core;
40 m_threadContext.userData = this;
41
42 QSize size(256, 512);
43 m_buffers[0].resize(toPow2(size.width()) * size.height() * sizeof(color_t));
44 m_buffers[1].resize(toPow2(size.width()) * size.height() * sizeof(color_t));
45 m_buffers[0].fill(0xFF);
46 m_buffers[1].fill(0xFF);
47 m_activeBuffer = &m_buffers[0];
48
49 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());
50
51 m_threadContext.startCallback = [](mCoreThread* context) {
52 CoreController* controller = static_cast<CoreController*>(context->userData);
53
54 switch (context->core->platform(context->core)) {
55#ifdef M_CORE_GBA
56 case PLATFORM_GBA:
57 context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance());
58 break;
59#endif
60 default:
61 break;
62 }
63
64 if (controller->m_override) {
65 controller->m_override->identify(context->core);
66 controller->m_override->apply(context->core);
67 }
68
69 if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) {
70 mCoreDeleteState(context->core, 0);
71 }
72
73 if (controller->m_multiplayer) {
74 controller->m_multiplayer->attachGame(controller);
75 }
76
77 QMetaObject::invokeMethod(controller, "started");
78 };
79
80 m_threadContext.resetCallback = [](mCoreThread* context) {
81 CoreController* controller = static_cast<CoreController*>(context->userData);
82 for (auto action : controller->m_resetActions) {
83 action();
84 }
85 controller->m_resetActions.clear();
86
87 QSize size = controller->screenDimensions();
88 controller->m_buffers[0].resize(toPow2(size.width()) * size.height() * sizeof(color_t));
89 controller->m_buffers[1].resize(toPow2(size.width()) * size.height() * sizeof(color_t));
90 controller->m_buffers[0].fill(0xFF);
91 controller->m_buffers[1].fill(0xFF);
92 controller->m_activeBuffer = &controller->m_buffers[0];
93
94 context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer->data()), toPow2(size.width()));
95
96 controller->finishFrame();
97 };
98
99 m_threadContext.frameCallback = [](mCoreThread* context) {
100 CoreController* controller = static_cast<CoreController*>(context->userData);
101
102 controller->finishFrame();
103 };
104
105 m_threadContext.cleanCallback = [](mCoreThread* context) {
106 CoreController* controller = static_cast<CoreController*>(context->userData);
107
108 controller->clearMultiplayerController();
109 QMetaObject::invokeMethod(controller, "stopping");
110 };
111
112 m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
113 mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
114 mCoreThread* context = logContext->p;
115
116 static const char* savestateMessage = "State %i loaded";
117 static const char* savestateFailedMessage = "State %i failed to load";
118 static int biosCat = -1;
119 static int statusCat = -1;
120 if (!context) {
121 return;
122 }
123 CoreController* controller = static_cast<CoreController*>(context->userData);
124 QString message;
125 if (biosCat < 0) {
126 biosCat = mLogCategoryById("gba.bios");
127 }
128 if (statusCat < 0) {
129 statusCat = mLogCategoryById("core.status");
130 }
131#ifdef M_CORE_GBA
132 if (level == mLOG_STUB && category == biosCat) {
133 va_list argc;
134 va_copy(argc, args);
135 int immediate = va_arg(argc, int);
136 va_end(argc);
137 QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
138 } else
139#endif
140 if (category == statusCat) {
141 // Slot 0 is reserved for suspend points
142 if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
143 va_list argc;
144 va_copy(argc, args);
145 int slot = va_arg(argc, int);
146 va_end(argc);
147 if (slot == 0) {
148 format = "Loaded suspend state";
149 }
150 } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) {
151 va_list argc;
152 va_copy(argc, args);
153 int slot = va_arg(argc, int);
154 va_end(argc);
155 if (slot == 0) {
156 return;
157 }
158 }
159 message = QString().vsprintf(format, args);
160 QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
161 }
162 if (level == mLOG_FATAL) {
163 mCoreThreadMarkCrashed(controller->thread());
164 QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args)));
165 }
166 message = QString().vsprintf(format, args);
167 QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
168 };
169}
170
171CoreController::~CoreController() {
172 endVideoLog();
173 stop();
174 disconnect();
175
176 if (m_tileCache) {
177 mTileCacheDeinit(m_tileCache.get());
178 m_tileCache.reset();
179 }
180
181 mCoreThreadJoin(&m_threadContext);
182
183 mCoreConfigDeinit(&m_threadContext.core->config);
184 m_threadContext.core->deinit(m_threadContext.core);
185}
186
187color_t* CoreController::drawContext() {
188 QMutexLocker locker(&m_mutex);
189 if (!m_completeBuffer) {
190 return nullptr;
191 }
192 return reinterpret_cast<color_t*>(m_completeBuffer->data());
193}
194
195bool CoreController::isPaused() {
196 return mCoreThreadIsPaused(&m_threadContext);
197}
198
199mPlatform CoreController::platform() const {
200 return m_threadContext.core->platform(m_threadContext.core);
201}
202
203QSize CoreController::screenDimensions() const {
204 unsigned width, height;
205 m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
206
207 return QSize(width, height);
208}
209
210void CoreController::loadConfig(ConfigController* config) {
211 Interrupter interrupter(this);
212 m_loadStateFlags = config->getOption("loadStateExtdata").toInt();
213 m_saveStateFlags = config->getOption("saveStateExtdata").toInt();
214 m_fastForwardRatio = config->getOption("fastForwardRatio").toFloat();
215 m_videoSync = config->getOption("videoSync").toInt();
216 m_audioSync = config->getOption("audioSync").toInt();
217 m_fpsTarget = config->getOption("fpsTarget").toFloat();
218 updateFastForward();
219 mCoreLoadForeignConfig(m_threadContext.core, config->config());
220 mCoreThreadRewindParamsChanged(&m_threadContext);
221}
222
223#ifdef USE_DEBUGGERS
224void CoreController::setDebugger(mDebugger* debugger) {
225 Interrupter interrupter(this);
226 if (debugger) {
227 mDebuggerAttach(debugger, m_threadContext.core);
228 } else {
229 m_threadContext.core->detachDebugger(m_threadContext.core);
230 }
231}
232#endif
233
234void CoreController::setMultiplayerController(MultiplayerController* controller) {
235 if (controller == m_multiplayer) {
236 return;
237 }
238 clearMultiplayerController();
239 m_multiplayer = controller;
240 if (!mCoreThreadHasStarted(&m_threadContext)) {
241 return;
242 }
243 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
244 CoreController* controller = static_cast<CoreController*>(thread->userData);
245 controller->m_multiplayer->attachGame(controller);
246 });
247}
248
249void CoreController::clearMultiplayerController() {
250 if (!m_multiplayer) {
251 return;
252 }
253 m_multiplayer->detachGame(this);
254 m_multiplayer = nullptr;
255}
256
257mTileCache* CoreController::tileCache() {
258 if (m_tileCache) {
259 return m_tileCache.get();
260 }
261 Interrupter interrupter(this);
262 switch (platform()) {
263#ifdef M_CORE_GBA
264 case PLATFORM_GBA: {
265 GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
266 m_tileCache = std::make_unique<mTileCache>();
267 GBAVideoTileCacheInit(m_tileCache.get());
268 GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video);
269 mTileCacheSetPalette(m_tileCache.get(), 0);
270 break;
271 }
272#endif
273#ifdef M_CORE_GB
274 case PLATFORM_GB: {
275 GB* gb = static_cast<GB*>(m_threadContext.core->board);
276 m_tileCache = std::make_unique<mTileCache>();
277 GBVideoTileCacheInit(m_tileCache.get());
278 GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video);
279 mTileCacheSetPalette(m_tileCache.get(), 0);
280 break;
281 }
282#endif
283 default:
284 return nullptr;
285 }
286 return m_tileCache.get();
287}
288
289void CoreController::setOverride(std::unique_ptr<Override> override) {
290 Interrupter interrupter(this);
291 m_override = std::move(override);
292 m_override->identify(m_threadContext.core);
293}
294
295void CoreController::setInputController(InputController* inputController) {
296 m_inputController = inputController;
297 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource());
298 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble());
299 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource());
300}
301
302void CoreController::setLogger(LogController* logger) {
303 disconnect(m_log);
304 m_log = logger;
305 m_threadContext.logger.d.filter = logger->filter();
306 connect(this, &CoreController::logPosted, m_log, &LogController::postLog);
307}
308
309void CoreController::start() {
310 if (!m_patched) {
311 mCoreAutoloadPatch(m_threadContext.core);
312 }
313 if (!mCoreThreadStart(&m_threadContext)) {
314 emit failed();
315 emit stopping();
316 }
317}
318
319void CoreController::stop() {
320#ifdef USE_DEBUGGERS
321 setDebugger(nullptr);
322#endif
323 setPaused(false);
324 mCoreThreadEnd(&m_threadContext);
325 emit stopping();
326}
327
328void CoreController::reset() {
329 bool wasPaused = isPaused();
330 setPaused(false);
331 Interrupter interrupter(this);
332 mCoreThreadReset(&m_threadContext);
333 if (wasPaused) {
334 setPaused(true);
335 }
336}
337
338void CoreController::setPaused(bool paused) {
339 if (paused == isPaused()) {
340 return;
341 }
342 if (paused) {
343 QMutexLocker locker(&m_mutex);
344 m_frameActions.append([this]() {
345 mCoreThreadPauseFromThread(&m_threadContext);
346 QMetaObject::invokeMethod(this, "paused");
347 });
348 } else {
349 mCoreThreadUnpause(&m_threadContext);
350 emit unpaused();
351 }
352}
353
354void CoreController::frameAdvance() {
355 QMutexLocker locker(&m_mutex);
356 m_frameActions.append([this]() {
357 mCoreThreadPauseFromThread(&m_threadContext);
358 });
359 setPaused(false);
360}
361
362void CoreController::setSync(bool sync) {
363 if (sync) {
364 m_threadContext.impl->sync.audioWait = m_audioSync;
365 m_threadContext.impl->sync.videoFrameWait = m_videoSync;
366 } else {
367 m_threadContext.impl->sync.audioWait = false;
368 m_threadContext.impl->sync.videoFrameWait = false;
369 }
370}
371
372void CoreController::setRewinding(bool rewind) {
373 if (!m_threadContext.core->opts.rewindEnable) {
374 return;
375 }
376 if (rewind && m_multiplayer && m_multiplayer->attached() > 1) {
377 return;
378 }
379
380 if (rewind && isPaused()) {
381 setPaused(false);
382 // TODO: restore autopausing
383 }
384 mCoreThreadSetRewinding(&m_threadContext, rewind);
385}
386
387void CoreController::rewind(int states) {
388 {
389 Interrupter interrupter(this);
390 if (!states) {
391 states = INT_MAX;
392 }
393 for (int i = 0; i < states; ++i) {
394 if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) {
395 break;
396 }
397 }
398 }
399 emit frameAvailable();
400 emit rewound();
401}
402
403void CoreController::setFastForward(bool enable) {
404 m_fastForward = enable;
405 updateFastForward();
406}
407
408void CoreController::forceFastForward(bool enable) {
409 m_fastForwardForced = enable;
410 updateFastForward();
411}
412
413void CoreController::loadState(int slot) {
414 if (slot > 0 && slot != m_stateSlot) {
415 m_stateSlot = slot;
416 m_backupSaveState.clear();
417 }
418 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
419 CoreController* controller = static_cast<CoreController*>(context->userData);
420 if (!controller->m_backupLoadState.isOpen()) {
421 controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
422 }
423 mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
424 if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
425 emit controller->frameAvailable();
426 emit controller->stateLoaded();
427 }
428 });
429}
430
431void CoreController::saveState(int slot) {
432 if (slot > 0) {
433 m_stateSlot = slot;
434 }
435 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
436 CoreController* controller = static_cast<CoreController*>(context->userData);
437 VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
438 if (vf) {
439 controller->m_backupSaveState.resize(vf->size(vf));
440 vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
441 vf->close(vf);
442 }
443 mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
444 });
445}
446
447void CoreController::loadBackupState() {
448 if (!m_backupLoadState.isOpen()) {
449 return;
450 }
451
452 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
453 CoreController* controller = static_cast<CoreController*>(context->userData);
454 controller->m_backupLoadState.seek(0);
455 if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
456 mLOG(STATUS, INFO, "Undid state load");
457 controller->frameAvailable();
458 controller->stateLoaded();
459 }
460 controller->m_backupLoadState.close();
461 });
462}
463
464void CoreController::saveBackupState() {
465 if (m_backupSaveState.isEmpty()) {
466 return;
467 }
468
469 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
470 CoreController* controller = static_cast<CoreController*>(context->userData);
471 VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
472 if (vf) {
473 vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
474 vf->close(vf);
475 mLOG(STATUS, INFO, "Undid state save");
476 }
477 controller->m_backupSaveState.clear();
478 });
479}
480
481void CoreController::loadSave(const QString& path, bool temporary) {
482 m_resetActions.append([this, path, temporary]() {
483 VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
484 if (!vf) {
485 LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
486 return;
487 }
488
489 if (temporary) {
490 m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
491 } else {
492 m_threadContext.core->loadSave(m_threadContext.core, vf);
493 }
494 });
495 reset();
496}
497
498void CoreController::loadPatch(const QString& patchPath) {
499 Interrupter interrupter(this);
500 VFile* patch = VFileDevice::open(patchPath, O_RDONLY);
501 if (patch) {
502 m_threadContext.core->loadPatch(m_threadContext.core, patch);
503 m_patched = true;
504 }
505 patch->close(patch);
506 if (mCoreThreadHasStarted(&m_threadContext)) {
507 reset();
508 }
509}
510
511void CoreController::replaceGame(const QString& path) {
512 QFileInfo info(path);
513 if (!info.isReadable()) {
514 LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
515 return;
516 }
517 QString fname = info.canonicalFilePath();
518 Interrupter interrupter(this);
519 mDirectorySetDetachBase(&m_threadContext.core->dirs);
520 mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData());
521}
522
523void CoreController::yankPak() {
524#ifdef M_CORE_GBA
525 if (platform() != PLATFORM_GBA) {
526 return;
527 }
528 Interrupter interrupter(this);
529 GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
530#endif
531}
532
533void CoreController::addKey(int key) {
534 m_activeKeys |= 1 << key;
535}
536
537void CoreController::clearKey(int key) {
538 m_activeKeys &= ~(1 << key);
539}
540
541void CoreController::setAutofire(int key, bool enable) {
542 if (key >= 32 || key < 0) {
543 return;
544 }
545
546 m_autofire[key] = enable;
547 m_autofireStatus[key] = 0;
548}
549
550#ifdef USE_PNG
551void CoreController::screenshot() {
552 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
553 mCoreTakeScreenshot(context->core);
554 });
555}
556#endif
557
558void CoreController::setRealTime() {
559 m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
560}
561
562void CoreController::setFixedTime(const QDateTime& time) {
563 m_threadContext.core->rtc.override = RTC_FIXED;
564 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
565}
566
567void CoreController::setFakeEpoch(const QDateTime& time) {
568 m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
569 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
570}
571
572void CoreController::importSharkport(const QString& path) {
573#ifdef M_CORE_GBA
574 if (platform() != PLATFORM_GBA) {
575 return;
576 }
577 VFile* vf = VFileDevice::open(path, O_RDONLY);
578 if (!vf) {
579 LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
580 return;
581 }
582 Interrupter interrupter(this);
583 GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
584 vf->close(vf);
585#endif
586}
587
588void CoreController::exportSharkport(const QString& path) {
589#ifdef M_CORE_GBA
590 if (platform() != PLATFORM_GBA) {
591 return;
592 }
593 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
594 if (!vf) {
595 LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
596 return;
597 }
598 Interrupter interrupter(this);
599 GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
600 vf->close(vf);
601#endif
602}
603
604void CoreController::attachPrinter() {
605#ifdef M_CORE_GB
606 if (platform() != PLATFORM_GB) {
607 return;
608 }
609 GB* gb = static_cast<GB*>(m_threadContext.core->board);
610 clearMultiplayerController();
611 GBPrinterCreate(&m_printer.d);
612 m_printer.parent = this;
613 m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
614 QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
615 QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
616 QVector<QRgb> colors;
617 colors.append(qRgb(0xF8, 0xF8, 0xF8));
618 colors.append(qRgb(0xA8, 0xA8, 0xA8));
619 colors.append(qRgb(0x50, 0x50, 0x50));
620 colors.append(qRgb(0x00, 0x00, 0x00));
621 image.setColorTable(colors);
622 for (int y = 0; y < height; ++y) {
623 for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
624 uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
625 image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
626 image.setPixel(x + 1, y, (byte & 0x30) >> 4);
627 image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
628 image.setPixel(x + 3, y, (byte & 0x03) >> 0);
629 }
630 }
631 QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
632 };
633 GBSIOSetDriver(&gb->sio, &m_printer.d.d);
634#endif
635}
636
637void CoreController::detachPrinter() {
638#ifdef M_CORE_GB
639 if (platform() != PLATFORM_GB) {
640 return;
641 }
642 GB* gb = static_cast<GB*>(m_threadContext.core->board);
643 GBPrinterDonePrinting(&m_printer.d);
644 GBSIOSetDriver(&gb->sio, nullptr);
645#endif
646}
647
648void CoreController::endPrint() {
649#ifdef M_CORE_GB
650 if (platform() != PLATFORM_GB) {
651 return;
652 }
653 GBPrinterDonePrinting(&m_printer.d);
654#endif
655}
656
657void CoreController::setAVStream(mAVStream* stream) {
658 Interrupter interrupter(this);
659 m_threadContext.core->setAVStream(m_threadContext.core, stream);
660}
661
662void CoreController::clearAVStream() {
663 Interrupter interrupter(this);
664 m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
665}
666
667void CoreController::clearOverride() {
668 m_override.reset();
669}
670
671void CoreController::startVideoLog(const QString& path) {
672 if (m_vl) {
673 return;
674 }
675
676 Interrupter interrupter(this);
677 m_vl = mVideoLogContextCreate(m_threadContext.core);
678 m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
679 mVideoLogContextSetOutput(m_vl, m_vlVf);
680 mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
681}
682
683void CoreController::endVideoLog() {
684 if (!m_vl) {
685 return;
686 }
687
688 Interrupter interrupter(this);
689 mVideoLogContextDestroy(m_threadContext.core, m_vl);
690 if (m_vlVf) {
691 m_vlVf->close(m_vlVf);
692 m_vlVf = nullptr;
693 }
694 m_vl = nullptr;
695}
696
697void CoreController::updateKeys() {
698 int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents();
699 m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
700}
701
702int CoreController::updateAutofire() {
703 int active = 0;
704 for (int k = 0; k < 32; ++k) {
705 if (!m_autofire[k]) {
706 continue;
707 }
708 ++m_autofireStatus[k];
709 if (m_autofireStatus[k]) {
710 m_autofireStatus[k] = 0;
711 active |= 1 << k;
712 }
713 }
714 return active;
715}
716
717void CoreController::finishFrame() {
718 QMutexLocker locker(&m_mutex);
719 m_completeBuffer = m_activeBuffer;
720
721 // TODO: Generalize this to triple buffering?
722 m_activeBuffer = &m_buffers[0];
723 if (m_activeBuffer == m_completeBuffer) {
724 m_activeBuffer = &m_buffers[1];
725 }
726 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), toPow2(screenDimensions().width()));
727
728 for (auto& action : m_frameActions) {
729 action();
730 }
731 m_frameActions.clear();
732 updateKeys();
733
734 QMetaObject::invokeMethod(this, "frameAvailable");
735}
736
737void CoreController::updateFastForward() {
738 if (m_fastForward || m_fastForwardForced) {
739 if (m_fastForwardRatio > 0) {
740 m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
741 } else {
742 setSync(false);
743 }
744 } else {
745 m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
746 setSync(true);
747 }
748}
749
750CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread)
751 : m_parent(parent)
752{
753 if (!m_parent->thread()->impl) {
754 return;
755 }
756 if (!fromThread) {
757 mCoreThreadInterrupt(m_parent->thread());
758 } else {
759 mCoreThreadInterruptFromThread(m_parent->thread());
760 }
761}
762
763CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent, bool fromThread)
764 : m_parent(parent.get())
765{
766 if (!m_parent->thread()->impl) {
767 return;
768 }
769 if (!fromThread) {
770 mCoreThreadInterrupt(m_parent->thread());
771 } else {
772 mCoreThreadInterruptFromThread(m_parent->thread());
773 }
774}
775
776CoreController::Interrupter::Interrupter(const Interrupter& other)
777 : m_parent(other.m_parent)
778{
779 if (!m_parent->thread()->impl) {
780 return;
781 }
782 mCoreThreadInterrupt(m_parent->thread());
783}
784
785CoreController::Interrupter::~Interrupter() {
786 if (!m_parent->thread()->impl) {
787 return;
788 }
789 mCoreThreadContinue(m_parent->thread());
790}