src/platform/qt/GameController.cpp (view raw)
1#include "GameController.h"
2
3#include "AudioProcessor.h"
4
5#include <QThread>
6
7extern "C" {
8#include "gba.h"
9#include "gba-audio.h"
10#include "gba-serialize.h"
11#include "renderers/video-software.h"
12#include "util/vfs.h"
13}
14
15using namespace QGBA;
16
17GameController::GameController(QObject* parent)
18 : QObject(parent)
19 , m_drawContext(new uint32_t[256 * 256])
20 , m_threadContext()
21 , m_activeKeys(0)
22 , m_gameOpen(false)
23 , m_audioThread(new QThread(this))
24 , m_audioProcessor(new AudioProcessor)
25{
26 m_renderer = new GBAVideoSoftwareRenderer;
27 GBAVideoSoftwareRendererCreate(m_renderer);
28 m_renderer->outputBuffer = (color_t*) m_drawContext;
29 m_renderer->outputBufferStride = 256;
30 m_threadContext.state = THREAD_INITIALIZED;
31 m_threadContext.debugger = 0;
32 m_threadContext.frameskip = 0;
33 m_threadContext.bios = 0;
34 m_threadContext.renderer = &m_renderer->d;
35 m_threadContext.userData = this;
36 m_threadContext.rewindBufferCapacity = 0;
37 m_threadContext.logLevel = -1;
38
39 GBAInputMapInit(&m_threadContext.inputMap);
40
41#ifdef BUILD_SDL
42 SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
43 m_sdlEvents.bindings = &m_threadContext.inputMap;
44 GBASDLInitEvents(&m_sdlEvents);
45 SDL_JoystickEventState(SDL_QUERY);
46#endif
47
48 m_threadContext.startCallback = [] (GBAThread* context) {
49 GameController* controller = static_cast<GameController*>(context->userData);
50 controller->m_audioProcessor->setInput(context);
51 controller->gameStarted(context);
52 };
53
54 m_threadContext.cleanCallback = [] (GBAThread* context) {
55 GameController* controller = static_cast<GameController*>(context->userData);
56 controller->gameStopped(context);
57 };
58
59 m_threadContext.frameCallback = [] (GBAThread* context) {
60 GameController* controller = static_cast<GameController*>(context->userData);
61 controller->m_pauseMutex.lock();
62 if (controller->m_pauseAfterFrame) {
63 GBAThreadPauseFromThread(context);
64 controller->m_pauseAfterFrame = false;
65 controller->gamePaused(&controller->m_threadContext);
66 }
67 controller->m_pauseMutex.unlock();
68 controller->frameAvailable(controller->m_drawContext);
69 };
70
71 m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
72 GameController* controller = static_cast<GameController*>(context->userData);
73 controller->postLog(level, QString().vsprintf(format, args));
74 };
75
76 m_audioThread->start(QThread::TimeCriticalPriority);
77 m_audioProcessor->moveToThread(m_audioThread);
78 connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
79 connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
80 connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause()));
81 connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start()));
82
83#ifdef BUILD_SDL
84 connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(testSDLEvents()));
85#endif
86}
87
88GameController::~GameController() {
89 m_audioThread->quit();
90 m_audioThread->wait();
91 if (GBAThreadIsPaused(&m_threadContext)) {
92 GBAThreadUnpause(&m_threadContext);
93 }
94 disconnect();
95 closeGame();
96 delete m_renderer;
97}
98
99ARMDebugger* GameController::debugger() {
100 return m_threadContext.debugger;
101}
102
103void GameController::setDebugger(ARMDebugger* debugger) {
104 bool wasPaused = isPaused();
105 setPaused(true);
106 if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
107 GBADetachDebugger(m_threadContext.gba);
108 }
109 m_threadContext.debugger = debugger;
110 if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
111 GBAAttachDebugger(m_threadContext.gba, m_threadContext.debugger);
112 }
113 setPaused(wasPaused);
114}
115
116void GameController::loadGame(const QString& path, bool dirmode) {
117 closeGame();
118 m_threadContext.sync.videoFrameWait = 0;
119 m_threadContext.sync.audioWait = 1;
120 if (!dirmode) {
121 QFile file(path);
122 if (!file.open(QIODevice::ReadOnly)) {
123 return;
124 }
125 file.close();
126 }
127 m_gameOpen = true;
128
129 m_pauseAfterFrame = false;
130
131 m_threadContext.fname = strdup(path.toLocal8Bit().constData());
132 if (dirmode) {
133 m_threadContext.gameDir = VDirOpen(m_threadContext.fname);
134 m_threadContext.stateDir = m_threadContext.gameDir;
135 } else {
136 m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
137#if ENABLE_LIBZIP
138 m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
139#endif
140 }
141
142 GBAThreadStart(&m_threadContext);
143}
144
145void GameController::closeGame() {
146 if (!m_gameOpen) {
147 return;
148 }
149 GBAThreadEnd(&m_threadContext);
150 GBAThreadJoin(&m_threadContext);
151 if (m_threadContext.fname) {
152 free(const_cast<char*>(m_threadContext.fname));
153 m_threadContext.fname = nullptr;
154 }
155
156 m_gameOpen = false;
157 emit gameStopped(&m_threadContext);
158}
159
160bool GameController::isPaused() {
161 return GBAThreadIsPaused(&m_threadContext);
162}
163
164void GameController::setPaused(bool paused) {
165 if (paused == GBAThreadIsPaused(&m_threadContext)) {
166 return;
167 }
168 if (paused) {
169 GBAThreadPause(&m_threadContext);
170 emit gamePaused(&m_threadContext);
171 } else {
172 GBAThreadUnpause(&m_threadContext);
173 emit gameUnpaused(&m_threadContext);
174 }
175}
176
177void GameController::reset() {
178 GBAThreadReset(&m_threadContext);
179}
180
181void GameController::frameAdvance() {
182 m_pauseMutex.lock();
183 m_pauseAfterFrame = true;
184 setPaused(false);
185 m_pauseMutex.unlock();
186}
187
188void GameController::keyPressed(int key) {
189 int mappedKey = 1 << key;
190 m_activeKeys |= mappedKey;
191 updateKeys();
192}
193
194void GameController::keyReleased(int key) {
195 int mappedKey = 1 << key;
196 m_activeKeys &= ~mappedKey;
197 updateKeys();
198}
199
200void GameController::setAudioBufferSamples(int samples) {
201 GBAThreadInterrupt(&m_threadContext);
202 m_threadContext.audioBuffers = samples;
203 GBAAudioResizeBuffer(&m_threadContext.gba->audio, samples);
204 GBAThreadContinue(&m_threadContext);
205 QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
206}
207
208void GameController::setFPSTarget(float fps) {
209 GBAThreadInterrupt(&m_threadContext);
210 m_threadContext.fpsTarget = fps;
211 GBAThreadContinue(&m_threadContext);
212 QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged");
213}
214
215void GameController::loadState(int slot) {
216 GBAThreadInterrupt(&m_threadContext);
217 GBALoadState(m_threadContext.gba, m_threadContext.stateDir, slot);
218 ConditionWake(&m_threadContext.sync.videoFrameAvailableCond); // Hack: wake up the drawing thread
219 GBAThreadContinue(&m_threadContext);
220 emit stateLoaded(&m_threadContext);
221 emit frameAvailable(m_drawContext);
222}
223
224void GameController::saveState(int slot) {
225 GBAThreadInterrupt(&m_threadContext);
226 GBASaveState(m_threadContext.gba, m_threadContext.stateDir, slot, true);
227 GBAThreadContinue(&m_threadContext);
228}
229
230void GameController::updateKeys() {
231 int activeKeys = m_activeKeys;
232#ifdef BUILD_SDL
233 activeKeys |= m_activeButtons;
234#endif
235 m_threadContext.activeKeys = activeKeys;
236}
237
238#ifdef BUILD_SDL
239void GameController::testSDLEvents() {
240 SDL_Joystick* joystick = m_sdlEvents.joystick;
241 SDL_JoystickUpdate();
242 int numButtons = SDL_JoystickNumButtons(joystick);
243 m_activeButtons = 0;
244 int i;
245 for (i = 0; i < numButtons; ++i) {
246 GBAKey key = GBAInputMapKey(&m_threadContext.inputMap, SDL_BINDING_BUTTON, i);
247 if (key == GBA_KEY_NONE) {
248 continue;
249 }
250 if (SDL_JoystickGetButton(joystick, i)) {
251 m_activeButtons |= 1 << key;
252 }
253 }
254 int numHats = SDL_JoystickNumHats(joystick);
255 for (i = 0; i < numHats; ++i) {
256 int hat = SDL_JoystickGetHat(joystick, i);
257 if (hat & SDL_HAT_UP) {
258 m_activeButtons |= 1 << GBA_KEY_UP;
259 }
260 if (hat & SDL_HAT_LEFT) {
261 m_activeButtons |= 1 << GBA_KEY_LEFT;
262 }
263 if (hat & SDL_HAT_DOWN) {
264 m_activeButtons |= 1 << GBA_KEY_DOWN;
265 }
266 if (hat & SDL_HAT_RIGHT) {
267 m_activeButtons |= 1 << GBA_KEY_RIGHT;
268 }
269 }
270 updateKeys();
271}
272#endif