mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/sio-lockstep'
This commit is contained in:
commit
64b396aff9
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -51,6 +51,7 @@ set(SOURCE_FILES
|
|||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogView.cpp
|
||||
MultiplayerController.cpp
|
||||
OverrideView.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
|
|
|
@ -23,8 +23,11 @@ ConfigOption::ConfigOption(QObject* parent)
|
|||
{
|
||||
}
|
||||
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot) {
|
||||
m_slot = slot;
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> 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<void(const QVariant&)> slot;
|
||||
foreach(slot, m_slots.values()) {
|
||||
slot(value);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigController::ConfigController(QObject* parent)
|
||||
|
|
|
@ -32,11 +32,11 @@ Q_OBJECT
|
|||
public:
|
||||
ConfigOption(QObject* parent = nullptr);
|
||||
|
||||
void connect(std::function<void(const QVariant&)>);
|
||||
void connect(std::function<void(const QVariant&)>, 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<void(const QVariant&)> m_slot;
|
||||
QMap<QObject*, std::function<void(const QVariant&)>> m_slots;
|
||||
QList<QPair<QAction*, QVariant>> m_actions;
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "AudioProcessor.h"
|
||||
#include "InputController.h"
|
||||
#include "MultiplayerController.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
|
@ -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<MultiplayerController> 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;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<MultiplayerController> controller);
|
||||
std::shared_ptr<MultiplayerController> 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<MultiplayerController> m_multiplayer;
|
||||
|
||||
struct GameControllerLux : GBALuminanceSource {
|
||||
GameController* p;
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<GBASIOLockstepNode*>(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;
|
||||
}
|
|
@ -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
|
|
@ -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<MultiplayerController> multiplayer = m_controller->multiplayerController();
|
||||
if (!multiplayer) {
|
||||
multiplayer = std::make_shared<MultiplayerController>();
|
||||
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);
|
||||
|
|
|
@ -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<QString> m_mruFiles;
|
||||
QMenu* m_mruMenu;
|
||||
ShortcutController* m_shortcutController;
|
||||
int m_playerId;
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
VideoView* m_videoView;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue