diff --git a/CMakeLists.txt b/CMakeLists.txt index 02c930abc..c258f133d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,11 @@ file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) +file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) source_group("ARM core" FILES ${ARM_SRC}) -source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC}) +source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) @@ -296,6 +297,7 @@ set(CORE_SRC ${GBA_SV_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} + ${SIO_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC} diff --git a/src/gba/sio.h b/src/gba/sio.h index 030218cc8..61e979385 100644 --- a/src/gba/sio.h +++ b/src/gba/sio.h @@ -30,11 +30,11 @@ struct GBASIO; struct GBASIODriver { struct GBASIO* p; - int (*init)(struct GBASIODriver* driver); + bool (*init)(struct GBASIODriver* driver); void (*deinit)(struct GBASIODriver* driver); - int (*load)(struct GBASIODriver* driver); - int (*unload)(struct GBASIODriver* driver); - int (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); + bool (*load)(struct GBASIODriver* driver); + bool (*unload)(struct GBASIODriver* driver); + uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles); }; diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c new file mode 100644 index 000000000..842db4571 --- /dev/null +++ b/src/gba/sio/lockstep.c @@ -0,0 +1,193 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "lockstep.h" + +#include "gba/gba.h" +#include "gba/io.h" + +#define LOCKSTEP_INCREMENT 2048 + +static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); +static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver); +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles); + +void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { + lockstep->players[0] = 0; + lockstep->players[1] = 0; + lockstep->players[2] = 0; + lockstep->players[3] = 0; + lockstep->multiRecv[0] = 0xFFFF; + lockstep->multiRecv[1] = 0xFFFF; + lockstep->multiRecv[2] = 0xFFFF; + lockstep->multiRecv[3] = 0xFFFF; + lockstep->attached = 0; + lockstep->loaded = 0; + lockstep->transferActive = false; + lockstep->waiting = 0; + lockstep->nextEvent = LOCKSTEP_INCREMENT; + ConditionInit(&lockstep->barrier); + MutexInit(&lockstep->mutex); +} + +void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) { + ConditionDeinit(&lockstep->barrier); + MutexDeinit(&lockstep->mutex); +} + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) { + node->d.init = GBASIOLockstepNodeInit; + node->d.deinit = GBASIOLockstepNodeDeinit; + node->d.load = GBASIOLockstepNodeLoad; + node->d.unload = GBASIOLockstepNodeUnload; + node->d.writeRegister = GBASIOLockstepNodeWriteRegister; + node->d.processEvents = GBASIOLockstepNodeProcessEvents; +} + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == MAX_GBAS) { + return false; + } + lockstep->players[lockstep->attached] = node; + node->p = lockstep; + node->id = lockstep->attached; + ++lockstep->attached; + return true; +} + +void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == 0) { + return; + } + int i; + for (i = 0; i < lockstep->attached; ++i) { + if (lockstep->players[i] != node) { + continue; + } + for (++i; i < lockstep->attached; ++i) { + lockstep->players[i - 1] = lockstep->players[i]; + lockstep->players[i - 1]->id = i - 1; + } + --lockstep->attached; + break; + } +} + +bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent = LOCKSTEP_INCREMENT; + node->d.p->multiplayerControl.slave = node->id > 0; + return true; +} + +void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) { + UNUSED(driver); +} + +bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->state = LOCKSTEP_IDLE; + MutexLock(&node->p->mutex); + ++node->p->loaded; + node->d.p->rcnt |= 3; + if (node->id) { + node->d.p->rcnt |= 4; + node->d.p->multiplayerControl.slave = 1; + } + MutexUnlock(&node->p->mutex); + return true; +} + +bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + MutexLock(&node->p->mutex); + --node->p->loaded; + ConditionWake(&node->p->barrier); + MutexUnlock(&node->p->mutex); + return true; +} + +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + if (address == REG_SIOCNT) { + if (value & 0x0080) { + value &= ~0x0080; + if (!node->id) { + MutexLock(&node->p->mutex); + node->p->transferActive = true; + node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1]; + MutexUnlock(&node->p->mutex); + } + } + value &= 0xFF03; + value |= driver->p->siocnt & 0x007C; + } + return value; +} + +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent -= cycles; + while (node->nextEvent <= 0) { + MutexLock(&node->p->mutex); + ++node->p->waiting; + if (node->p->waiting < node->p->loaded) { + ConditionWait(&node->p->barrier, &node->p->mutex); + } else { + if (node->p->transferActive) { + node->p->transferCycles -= node->p->nextEvent; + if (node->p->transferCycles > 0) { + if (node->p->transferCycles < LOCKSTEP_INCREMENT) { + node->p->nextEvent = node->p->transferCycles; + } + } else { + node->p->nextEvent = LOCKSTEP_INCREMENT; + node->p->transferActive = false; + int i; + for (i = 0; i < node->p->attached; ++i) { + node->p->multiRecv[i] = node->p->players[i]->multiSend; + node->p->players[i]->state = LOCKSTEP_FINISHED; + } + for (; i < MAX_GBAS; ++i) { + node->p->multiRecv[i] = 0xFFFF; + } + } + } + node->p->waiting = 0; + ConditionWake(&node->p->barrier); + } + if (node->state == LOCKSTEP_FINISHED) { + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0]; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1]; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; + node->d.p->rcnt |= 1; + node->state = LOCKSTEP_IDLE; + if (node->d.p->multiplayerControl.irq) { + GBARaiseIRQ(node->d.p->p, IRQ_SIO); + } + node->d.p->multiplayerControl.id = node->id; + node->d.p->multiplayerControl.busy = 0; + } else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) { + node->state = LOCKSTEP_STARTED; + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; + node->d.p->rcnt &= ~1; + node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; + if (node->id) { + node->d.p->multiplayerControl.busy = 1; + } + } + node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached; + node->nextEvent += node->p->nextEvent; + MutexUnlock(&node->p->mutex); + } + return node->nextEvent; +} diff --git a/src/gba/sio/lockstep.h b/src/gba/sio/lockstep.h new file mode 100644 index 000000000..3b4c5979e --- /dev/null +++ b/src/gba/sio/lockstep.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef SIO_LOCKSTEP_H +#define SIO_LOCKSTEP_H + +#include "gba/sio.h" + +#include "util/threading.h" + +enum LockstepState { + LOCKSTEP_IDLE = 0, + LOCKSTEP_STARTED = 1, + LOCKSTEP_FINISHED = 2 +}; + +struct GBASIOLockstep { + struct GBASIOLockstepNode* players[MAX_GBAS]; + int attached; + int loaded; + + uint16_t multiRecv[MAX_GBAS]; + bool transferActive; + int32_t transferCycles; + int32_t nextEvent; + + int waiting; + Mutex mutex; + Condition barrier; +}; + +struct GBASIOLockstepNode { + struct GBASIODriver d; + struct GBASIOLockstep* p; + + int32_t nextEvent; + uint16_t multiSend; + enum LockstepState state; + int id; +}; + +void GBASIOLockstepInit(struct GBASIOLockstep*); +void GBASIOLockstepDeinit(struct GBASIOLockstep*); + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*); + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); +void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); + +#endif diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 4b0900be6..69cf68712 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -51,6 +51,7 @@ set(SOURCE_FILES KeyEditor.cpp LoadSaveState.cpp LogView.cpp + MultiplayerController.cpp OverrideView.cpp SavestateButton.cpp SensorView.cpp diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index c33190a65..ba21ff288 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -23,8 +23,11 @@ ConfigOption::ConfigOption(QObject* parent) { } -void ConfigOption::connect(std::function slot) { - m_slot = slot; +void ConfigOption::connect(std::function slot, QObject* parent) { + m_slots[parent] = slot; + QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() { + m_slots.remove(parent); + }); } QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) { @@ -33,6 +36,9 @@ QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMen QObject::connect(action, &QAction::triggered, [this, value]() { emit valueChanged(value); }); + QObject::connect(parent, &QAction::destroyed, [this, action, value]() { + m_actions.removeAll(qMakePair(action, value)); + }); parent->addAction(action); m_actions.append(qMakePair(action, value)); return action; @@ -48,6 +54,9 @@ QAction* ConfigOption::addBoolean(const QString& text, QMenu* parent) { QObject::connect(action, &QAction::triggered, [this, action]() { emit valueChanged(action->isChecked()); }); + QObject::connect(parent, &QAction::destroyed, [this, action]() { + m_actions.removeAll(qMakePair(action, 1)); + }); parent->addAction(action); m_actions.append(qMakePair(action, 1)); return action; @@ -76,7 +85,10 @@ void ConfigOption::setValue(const QVariant& value) { action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled); } - m_slot(value); + std::function slot; + foreach(slot, m_slots.values()) { + slot(value); + } } ConfigController::ConfigController(QObject* parent) diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index 1a0997057..14c83471f 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -32,11 +32,11 @@ Q_OBJECT public: ConfigOption(QObject* parent = nullptr); - void connect(std::function); + void connect(std::function, QObject* parent = nullptr); - QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = 0); - QAction* addValue(const QString& text, const char* value, QMenu* parent = 0); - QAction* addBoolean(const QString& text, QMenu* parent = 0); + QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr); + QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr); + QAction* addBoolean(const QString& text, QMenu* parent = nullptr); public slots: void setValue(bool value); @@ -49,7 +49,7 @@ signals: void valueChanged(const QVariant& value); private: - std::function m_slot; + QMap> m_slots; QList> m_actions; }; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 35b55085d..2c9ea7158 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -7,6 +7,7 @@ #include "AudioProcessor.h" #include "InputController.h" +#include "MultiplayerController.h" #include #include @@ -41,6 +42,7 @@ GameController::GameController(QObject* parent) , m_turbo(false) , m_turboForced(false) , m_inputController(nullptr) + , m_multiplayer(nullptr) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer); @@ -140,12 +142,30 @@ GameController::~GameController() { m_audioThread->quit(); m_audioThread->wait(); disconnect(); + clearMultiplayerController(); closeGame(); GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; } +void GameController::setMultiplayerController(std::shared_ptr controller) { + if (controller == m_multiplayer) { + return; + } + clearMultiplayerController(); + m_multiplayer = controller; + controller->attachGame(this); +} + +void GameController::clearMultiplayerController() { + if (!m_multiplayer) { + return; + } + m_multiplayer->detachGame(this); + m_multiplayer.reset(); +} + void GameController::setOverride(const GBACartridgeOverride& override) { m_threadContext.override = override; m_threadContext.hasOverride = true; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index f05337057..eb3a1580c 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -12,6 +12,8 @@ #include #include +#include + extern "C" { #include "gba/cheats.h" #include "gba/hardware.h" @@ -32,6 +34,7 @@ namespace QGBA { class AudioProcessor; class InputController; +class MultiplayerController; class GameController : public QObject { Q_OBJECT @@ -59,6 +62,10 @@ public: void setInputController(InputController* controller) { m_inputController = controller; } void setOverrides(Configuration* overrides) { m_threadContext.overrides = overrides; } + void setMultiplayerController(std::shared_ptr controller); + std::shared_ptr multiplayerController() { return m_multiplayer; } + void clearMultiplayerController(); + void setOverride(const GBACartridgeOverride& override); void clearOverride() { m_threadContext.hasOverride = false; } @@ -167,6 +174,7 @@ private: bool m_turboForced; InputController* m_inputController; + std::shared_ptr m_multiplayer; struct GameControllerLux : GBALuminanceSource { GameController* p; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 21986e48c..592761c08 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -19,8 +19,9 @@ extern "C" { using namespace QGBA; -InputController::InputController(QObject* parent) +InputController::InputController(int playerId, QObject* parent) : QObject(parent) + , m_playerId(playerId) , m_config(nullptr) , m_gamepadTimer(nullptr) { @@ -28,9 +29,8 @@ InputController::InputController(QObject* parent) #ifdef BUILD_SDL m_sdlEvents.bindings = &m_inputMap; - GBASDLInitEvents(&m_sdlEvents); + GBASDLInitEvents(&m_sdlEvents, playerId); GBASDLInitBindings(&m_inputMap); - SDL_JoystickEventState(SDL_QUERY); m_gamepadTimer = new QTimer(this); connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 14877e3f2..1ed729064 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -31,7 +31,7 @@ Q_OBJECT public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(QObject* parent = nullptr); + InputController(int playerId = 0, QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config); @@ -67,6 +67,7 @@ private: GBAInputMap m_inputMap; ConfigController* m_config; + int m_playerId; #ifdef BUILD_SDL GBASDLEvents m_sdlEvents; diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp new file mode 100644 index 000000000..827900c70 --- /dev/null +++ b/src/platform/qt/MultiplayerController.cpp @@ -0,0 +1,71 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MultiplayerController.h" + +#include "GameController.h" + +using namespace QGBA; + +MultiplayerController::MultiplayerController() { + GBASIOLockstepInit(&m_lockstep); +} + +MultiplayerController::~MultiplayerController() { + GBASIOLockstepDeinit(&m_lockstep); +} + +bool MultiplayerController::attachGame(GameController* controller) { + MutexLock(&m_lockstep.mutex); + if (m_lockstep.attached == MAX_GBAS) { + MutexUnlock(&m_lockstep.mutex); + return false; + } + GBASIOLockstepNode* node = new GBASIOLockstepNode; + GBASIOLockstepNodeCreate(node); + GBASIOLockstepAttachNode(&m_lockstep, node); + MutexUnlock(&m_lockstep.mutex); + + controller->threadInterrupt(); + GBAThread* thread = controller->thread(); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI); + } + thread->sioDrivers.multiplayer = &node->d; + controller->threadContinue(); + return true; +} + +void MultiplayerController::detachGame(GameController* controller) { + controller->threadInterrupt(); + MutexLock(&m_lockstep.mutex); + GBAThread* thread = nullptr; + for (int i = 0; i < m_lockstep.attached; ++i) { + thread = controller->thread(); + if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) { + break; + } + thread = nullptr; + } + if (thread) { + GBASIOLockstepNode* node = reinterpret_cast(thread->sioDrivers.multiplayer); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_MULTI); + } + thread->sioDrivers.multiplayer = nullptr; + GBASIOLockstepDetachNode(&m_lockstep, node); + delete node; + } + MutexUnlock(&m_lockstep.mutex); + controller->threadContinue(); +} + +int MultiplayerController::attached() { + int num; + MutexLock(&m_lockstep.mutex); + num = m_lockstep.attached; + MutexUnlock(&m_lockstep.mutex); + return num; +} diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h new file mode 100644 index 000000000..ac3514c1b --- /dev/null +++ b/src/platform/qt/MultiplayerController.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_MULTIPLAYER_CONTROLLER +#define QGBA_MULTIPLAYER_CONTROLLER + +extern "C" { +#include "gba/sio/lockstep.h" +} + +namespace QGBA { + +class GameController; + +class MultiplayerController { +public: + MultiplayerController(); + ~MultiplayerController(); + + bool attachGame(GameController*); + void detachGame(GameController*); + + int attached(); + +private: + GBASIOLockstep m_lockstep; +}; + +} +#endif diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 89cd0cdec..0fea4b3a2 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -23,6 +23,7 @@ #include "GIFView.h" #include "LoadSaveState.h" #include "LogView.h" +#include "MultiplayerController.h" #include "OverrideView.h" #include "SensorView.h" #include "SettingsView.h" @@ -36,13 +37,14 @@ extern "C" { using namespace QGBA; -Window::Window(ConfigController* config, QWidget* parent) +Window::Window(ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) , m_logView(new LogView()) , m_stateWindow(nullptr) , m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) + , m_inputController(playerId) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif @@ -54,6 +56,7 @@ Window::Window(ConfigController* config, QWidget* parent) #endif , m_mruMenu(nullptr) , m_shortcutController(new ShortcutController(this)) + , m_playerId(playerId) { setWindowTitle(PROJECT_NAME); setFocusPolicy(Qt::StrongFocus); @@ -525,8 +528,23 @@ void Window::setupMenu(QMenuBar* menubar) { quickSaveMenu->addAction(quickSave); } -#ifndef Q_OS_MAC fileMenu->addSeparator(); + QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); + connect(multiWindow, &QAction::triggered, [this]() { + std::shared_ptr multiplayer = m_controller->multiplayerController(); + if (!multiplayer) { + multiplayer = std::make_shared(); + m_controller->setMultiplayerController(multiplayer); + } + Window* w2 = new Window(m_config, multiplayer->attached()); + w2->setAttribute(Qt::WA_DeleteOnClose); + w2->loadConfig(); + w2->controller()->setMultiplayerController(multiplayer); + w2->show(); + }); + addControlledAction(fileMenu, multiWindow, "multiWindow"); + +#ifndef Q_OS_MAC addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif @@ -585,12 +603,16 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); - videoSync->connect([this](const QVariant& value) { m_controller->setVideoSync(value.toBool()); }); + videoSync->connect([this](const QVariant& value) { + m_controller->setVideoSync(value.toBool()); + }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); - audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); }); + audioSync->connect([this](const QVariant& value) { + m_controller->setAudioSync(value.toBool()); + }, this); m_config->updateOption("audioSync"); QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); @@ -609,17 +631,23 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); - lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); }); + lockAspectRatio->connect([this](const QVariant& value) { + m_display->lockAspectRatio(value.toBool()); + }, this); m_config->updateOption("lockAspectRatio"); ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Resample video"), avMenu); - resampleVideo->connect([this](const QVariant& value) { m_display->filter(value.toBool()); }); + resampleVideo->connect([this](const QVariant& value) { + m_display->filter(value.toBool()); + }, this); m_config->updateOption("resampleVideo"); QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip")); ConfigOption* skip = m_config->addOption("frameskip"); - skip->connect([this](const QVariant& value) { m_controller->setFrameskip(value.toInt()); }); + skip->connect([this](const QVariant& value) { + m_controller->setFrameskip(value.toInt()); + }, this); for (int i = 0; i <= 10; ++i) { skip->addValue(QString::number(i), i, skipMenu); } @@ -629,7 +657,9 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size")); ConfigOption* buffers = m_config->addOption("audioBuffers"); - buffers->connect([this](const QVariant& value) { emit audioBufferSamplesChanged(value.toInt()); }); + buffers->connect([this](const QVariant& value) { + emit audioBufferSamplesChanged(value.toInt()); + }, this); buffers->addValue(tr("512"), 512, buffersMenu); buffers->addValue(tr("768"), 768, buffersMenu); buffers->addValue(tr("1024"), 1024, buffersMenu); @@ -641,7 +671,9 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* target = avMenu->addMenu("FPS target"); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); - fpsTargetOption->connect([this](const QVariant& value) { emit fpsTargetChanged(value.toInt()); }); + fpsTargetOption->connect([this](const QVariant& value) { + emit fpsTargetChanged(value.toInt()); + }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target); fpsTargetOption->addValue(tr("45"), 45, target); @@ -733,19 +765,27 @@ void Window::setupMenu(QMenuBar* menubar) { #endif ConfigOption* skipBios = m_config->addOption("skipBios"); - skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); }); + skipBios->connect([this](const QVariant& value) { + m_controller->setSkipBIOS(value.toBool()); + }, this); ConfigOption* useBios = m_config->addOption("useBios"); useBios->connect([this](const QVariant& value) { m_controller->setUseBIOS(value.toBool()); }); ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); - rewindEnable->connect([this](const QVariant& value) { m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + rewindEnable->connect([this](const QVariant& value) { + m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, this); ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity"); - rewindBufferCapacity->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + rewindBufferCapacity->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, this); ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); - rewindBufferInterval->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); }); + rewindBufferInterval->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); + }, this); QMenu* other = new QMenu(tr("Other"), this); m_shortcutController->addMenu(other); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 68aac1697..9dce80b42 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -39,7 +39,7 @@ class Window : public QMainWindow { Q_OBJECT public: - Window(ConfigController* config, QWidget* parent = nullptr); + Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr); virtual ~Window(); GameController* controller() { return m_controller; } @@ -134,6 +134,7 @@ private: QList m_mruFiles; QMenu* m_mruMenu; ShortcutController* m_shortcutController; + int m_playerId; #ifdef USE_FFMPEG VideoView* m_videoView; diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 71911c29b..eb7a758c6 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -105,7 +105,7 @@ int main(int argc, char** argv) { renderer.events.bindings = &inputMap; GBASDLInitBindings(&inputMap); - GBASDLInitEvents(&renderer.events); + GBASDLInitEvents(&renderer.events, 0); GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config)); context.overrides = GBAConfigGetOverrides(&config); diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index e8d381b63..fb9a83e92 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -21,13 +21,13 @@ static int _openContexts = 0; -bool GBASDLInitEvents(struct GBASDLEvents* context) { +bool GBASDLInitEvents(struct GBASDLEvents* context, int playerId) { if (!_openContexts && SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { return false; } ++_openContexts; SDL_JoystickEventState(SDL_ENABLE); - context->joystick = SDL_JoystickOpen(0); + context->joystick = SDL_JoystickOpen(playerId); #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); #endif diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 997345e73..1e8e3a2a2 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -28,7 +28,7 @@ struct GBASDLEvents { #endif }; -bool GBASDLInitEvents(struct GBASDLEvents*); +bool GBASDLInitEvents(struct GBASDLEvents*, int playerId); void GBASDLDeinitEvents(struct GBASDLEvents*); void GBASDLInitBindings(struct GBAInputMap* inputMap);