mirror of https://github.com/mgba-emu/mgba.git
GB: First pass at lockstep multiplayer
This commit is contained in:
parent
4ac4733cfd
commit
ceea51b55e
|
@ -51,7 +51,8 @@ file(GLOB UTIL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/*.[cSs])
|
|||
file(GLOB UTIL_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/test/*.c)
|
||||
file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c)
|
||||
file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c)
|
||||
file(GLOB SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c)
|
||||
file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c)
|
||||
file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c)
|
||||
file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c)
|
||||
file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||
set(CLI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/commandline.c)
|
||||
|
@ -59,9 +60,9 @@ set(CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-mem.c)
|
|||
set(VFS_SRC)
|
||||
source_group("ARM core" FILES ${ARM_SRC})
|
||||
source_group("LR35902 core" FILES ${LR35902_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${GBA_RENDERER_SRC} ${SIO_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${GBA_RENDERER_SRC} ${GBA_SIO_SRC})
|
||||
source_group("GBA extra" FILES ${GBA_CHEATS_SRC} ${GBA_RR_SRC})
|
||||
source_group("GB board" FILES ${GB_SRC})
|
||||
source_group("GB board" FILES ${GB_SRC} ${GB_SIO_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC})
|
||||
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
|
@ -616,7 +617,11 @@ if(NOT MINIMAL_CORE)
|
|||
list(APPEND SRC
|
||||
${GBA_RR_SRC}
|
||||
${GBA_EXTRA_SRC}
|
||||
${SIO_SRC})
|
||||
${GBA_SIO_SRC})
|
||||
endif()
|
||||
if(M_CORE_GB)
|
||||
list(APPEND SRC
|
||||
${GB_SIO_SRC})
|
||||
endif()
|
||||
list(APPEND SRC
|
||||
${FEATURE_SRC}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* Copyright (c) 2013-2016 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"
|
||||
|
||||
void mLockstepInit(struct mLockstep* lockstep) {
|
||||
lockstep->attached = 0;
|
||||
lockstep->transferActive = 0;
|
||||
#ifndef NDEBUG
|
||||
lockstep->transferId = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO: Migrate nodes
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright (c) 2013-2016 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 "util/common.h"
|
||||
|
||||
enum mLockstepPhase {
|
||||
TRANSFER_IDLE = 0,
|
||||
TRANSFER_STARTING,
|
||||
TRANSFER_STARTED,
|
||||
TRANSFER_FINISHING,
|
||||
TRANSFER_FINISHED
|
||||
};
|
||||
|
||||
struct mLockstep {
|
||||
int attached;
|
||||
enum mLockstepPhase transferActive;
|
||||
int32_t transferCycles;
|
||||
|
||||
bool (*signal)(struct mLockstep*, unsigned mask);
|
||||
bool (*wait)(struct mLockstep*, unsigned mask);
|
||||
void (*addCycles)(struct mLockstep*, int id, int32_t cycles);
|
||||
int32_t (*useCycles)(struct mLockstep*, int id, int32_t cycles);
|
||||
void (*unload)(struct mLockstep*, int id);
|
||||
void* context;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
#endif
|
||||
};
|
||||
|
||||
void mLockstepInit(struct mLockstep*);
|
||||
|
||||
#endif
|
|
@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) {
|
|||
}
|
||||
|
||||
gb->cpuBlocked = false;
|
||||
gb->earlyExit = false;
|
||||
gb->doubleSpeed = 0;
|
||||
|
||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||
|
@ -548,6 +549,10 @@ void GBProcessEvents(struct LR35902Core* cpu) {
|
|||
} while (gb->cpuBlocked);
|
||||
cpu->nextEvent = nextEvent;
|
||||
|
||||
if (gb->earlyExit) {
|
||||
gb->earlyExit = false;
|
||||
break;
|
||||
}
|
||||
if (cpu->halted) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
if (!gb->memory.ie || !gb->memory.ime) {
|
||||
|
|
|
@ -79,6 +79,7 @@ struct GB {
|
|||
struct mAVStream* stream;
|
||||
|
||||
bool cpuBlocked;
|
||||
bool earlyExit;
|
||||
struct mTimingEvent eiPending;
|
||||
unsigned doubleSpeed;
|
||||
};
|
||||
|
|
|
@ -158,6 +158,9 @@ void GBIOReset(struct GB* gb) {
|
|||
|
||||
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
||||
switch (address) {
|
||||
case REG_SB:
|
||||
GBSIOWriteSB(&gb->sio, value);
|
||||
break;
|
||||
case REG_SC:
|
||||
GBSIOWriteSC(&gb->sio, value);
|
||||
break;
|
||||
|
@ -338,7 +341,6 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
|||
}
|
||||
break;
|
||||
case REG_JOYP:
|
||||
case REG_SB:
|
||||
case REG_TIMA:
|
||||
case REG_TMA:
|
||||
// Handled transparently by the registers
|
||||
|
|
47
src/gb/sio.c
47
src/gb/sio.c
|
@ -9,6 +9,13 @@
|
|||
#include "gb/io.h"
|
||||
#include "gb/serialize.h"
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB_SIO, "GB Serial I/O");
|
||||
|
||||
const int GBSIOCyclesPerTransfer[2] = {
|
||||
512,
|
||||
16
|
||||
};
|
||||
|
||||
void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
|
||||
|
||||
void GBSIOInit(struct GBSIO* sio) {
|
||||
|
@ -17,6 +24,8 @@ void GBSIOInit(struct GBSIO* sio) {
|
|||
sio->event.name = "GB SIO";
|
||||
sio->event.callback = _GBSIOProcessEvents;
|
||||
sio->event.priority = 0x30;
|
||||
|
||||
sio->driver = NULL;
|
||||
}
|
||||
|
||||
void GBSIOReset(struct GBSIO* sio) {
|
||||
|
@ -29,6 +38,26 @@ void GBSIODeinit(struct GBSIO* sio) {
|
|||
// Nothing to do yet
|
||||
}
|
||||
|
||||
void GBSIOSetDriver(struct GBSIO* sio, struct GBSIODriver* driver) {
|
||||
if (sio->driver) {
|
||||
if (sio->driver->deinit) {
|
||||
sio->driver->deinit(sio->driver);
|
||||
}
|
||||
}
|
||||
if (driver) {
|
||||
driver->p = sio;
|
||||
|
||||
if (driver->init) {
|
||||
if (!driver->init(driver)) {
|
||||
driver->deinit(driver);
|
||||
mLOG(GB_SIO, ERROR, "Could not initialize SIO driver");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
sio->driver = driver;
|
||||
}
|
||||
|
||||
void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(cyclesLate);
|
||||
struct GBSIO* sio = context;
|
||||
|
@ -38,19 +67,29 @@ void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesL
|
|||
if (!sio->remainingBits) {
|
||||
sio->p->memory.io[REG_IF] |= (1 << GB_IRQ_SIO);
|
||||
sio->p->memory.io[REG_SC] = GBRegisterSCClearEnable(sio->p->memory.io[REG_SC]);
|
||||
sio->pendingSB = 0xFF;
|
||||
GBUpdateIRQs(sio->p);
|
||||
} else {
|
||||
mTimingSchedule(timing, &sio->event, sio->period);
|
||||
}
|
||||
}
|
||||
|
||||
void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) {
|
||||
if (!sio->driver) {
|
||||
return;
|
||||
}
|
||||
sio->driver->writeSB(sio->driver, sb);
|
||||
}
|
||||
|
||||
void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) {
|
||||
sio->period = 0x1000; // TODO Shift Clock
|
||||
sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock
|
||||
if (GBRegisterSCIsEnable(sc)) {
|
||||
if (GBRegisterSCIsShiftClock(sc)) {
|
||||
mTimingSchedule(&sio->p->timing, &sio->event, sio->period);
|
||||
}
|
||||
mTimingDeschedule(&sio->p->timing, &sio->event);
|
||||
mTimingSchedule(&sio->p->timing, &sio->event, sio->period);
|
||||
sio->remainingBits = 8;
|
||||
}
|
||||
if (sio->driver) {
|
||||
sio->driver->writeSC(sio->driver, sc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
src/gb/sio.h
20
src/gb/sio.h
|
@ -8,13 +8,22 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/log.h"
|
||||
#include "core/timing.h"
|
||||
|
||||
#define MAX_GBS 2
|
||||
|
||||
extern const int GBSIOCyclesPerTransfer[2];
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GB_SIO);
|
||||
|
||||
struct GB;
|
||||
struct GBSIODriver;
|
||||
struct GBSIO {
|
||||
struct GB* p;
|
||||
|
||||
struct mTimingEvent event;
|
||||
struct GBSIODriver* driver;
|
||||
|
||||
int32_t nextEvent;
|
||||
int32_t period;
|
||||
|
@ -23,6 +32,15 @@ struct GBSIO {
|
|||
uint8_t pendingSB;
|
||||
};
|
||||
|
||||
struct GBSIODriver {
|
||||
struct GBSIO* p;
|
||||
|
||||
bool (*init)(struct GBSIODriver* driver);
|
||||
void (*deinit)(struct GBSIODriver* driver);
|
||||
void (*writeSB)(struct GBSIODriver* driver, uint8_t value);
|
||||
uint8_t (*writeSC)(struct GBSIODriver* driver, uint8_t value);
|
||||
};
|
||||
|
||||
DECL_BITFIELD(GBRegisterSC, uint8_t);
|
||||
DECL_BIT(GBRegisterSC, ShiftClock, 0);
|
||||
DECL_BIT(GBRegisterSC, ClockSpeed, 1);
|
||||
|
@ -31,6 +49,8 @@ DECL_BIT(GBRegisterSC, Enable, 7);
|
|||
void GBSIOInit(struct GBSIO* sio);
|
||||
void GBSIOReset(struct GBSIO* sio);
|
||||
void GBSIODeinit(struct GBSIO* sio);
|
||||
void GBSIOSetDriver(struct GBSIO* sio, struct GBSIODriver* driver);
|
||||
void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc);
|
||||
void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
/* Copyright (c) 2013-2016 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 "gb/gb.h"
|
||||
#include "gb/io.h"
|
||||
|
||||
#define LOCKSTEP_INCREMENT 500
|
||||
|
||||
static bool GBSIOLockstepNodeInit(struct GBSIODriver* driver);
|
||||
static void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver);
|
||||
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value);
|
||||
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value);
|
||||
static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate);
|
||||
|
||||
void GBSIOLockstepInit(struct GBSIOLockstep* lockstep) {
|
||||
mLockstepInit(&lockstep->d);
|
||||
lockstep->players[0] = NULL;
|
||||
lockstep->players[1] = NULL;
|
||||
lockstep->pendingSB[0] = 0xFF;
|
||||
lockstep->pendingSB[1] = 0xFF;
|
||||
}
|
||||
|
||||
void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode* node) {
|
||||
node->d.init = GBSIOLockstepNodeInit;
|
||||
node->d.deinit = GBSIOLockstepNodeDeinit;
|
||||
node->d.writeSB = GBSIOLockstepNodeWriteSB;
|
||||
node->d.writeSC = GBSIOLockstepNodeWriteSC;
|
||||
}
|
||||
|
||||
bool GBSIOLockstepAttachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
|
||||
if (lockstep->d.attached == MAX_GBS) {
|
||||
return false;
|
||||
}
|
||||
lockstep->players[lockstep->d.attached] = node;
|
||||
node->p = lockstep;
|
||||
node->id = lockstep->d.attached;
|
||||
++lockstep->d.attached;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBSIOLockstepDetachNode(struct GBSIOLockstep* lockstep, struct GBSIOLockstepNode* node) {
|
||||
if (lockstep->d.attached == 0) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lockstep->d.attached; ++i) {
|
||||
if (lockstep->players[i] != node) {
|
||||
continue;
|
||||
}
|
||||
for (++i; i < lockstep->d.attached; ++i) {
|
||||
lockstep->players[i - 1] = lockstep->players[i];
|
||||
lockstep->players[i - 1]->id = i - 1;
|
||||
}
|
||||
--lockstep->d.attached;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBSIOLockstepNodeInit(struct GBSIODriver* driver) {
|
||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||
mLOG(GB_SIO, DEBUG, "Lockstep %i: Node init", node->id);
|
||||
node->event.context = node;
|
||||
node->event.name = "GB SIO Lockstep";
|
||||
node->event.callback = _GBSIOLockstepNodeProcessEvents;
|
||||
node->event.priority = 0x80;
|
||||
|
||||
node->nextEvent = 0;
|
||||
node->eventDiff = 0;
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->d.transferActive;
|
||||
node->transferId = node->p->d.transferId;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBSIOLockstepNodeDeinit(struct GBSIODriver* driver) {
|
||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||
node->p->d.unload(&node->p->d, node->id);
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
}
|
||||
|
||||
static void _finishTransfer(struct GBSIOLockstepNode* node) {
|
||||
if (node->transferFinished) {
|
||||
return;
|
||||
}
|
||||
struct GBSIO* sio = node->d.p;
|
||||
sio->pendingSB = node->p->pendingSB[!node->id];
|
||||
node->transferFinished = true;
|
||||
#ifndef NDEBUG
|
||||
++node->transferId;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
||||
bool needsToWait = false;
|
||||
int i;
|
||||
switch (node->p->d.transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
// If the master hasn't initiated a transfer, it can keep going.
|
||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
// Start the transfer, but wait for the other GBs to catch up
|
||||
node->transferFinished = false;
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
|
||||
node->nextEvent += 128;
|
||||
break;
|
||||
case TRANSFER_STARTED:
|
||||
// All the other GBs have caught up and are sleeping, we can all continue now
|
||||
node->nextEvent += 128;
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
|
||||
break;
|
||||
case TRANSFER_FINISHING:
|
||||
// Finish the transfer
|
||||
// We need to make sure the other GBs catch up so they don't get behind
|
||||
node->nextEvent += LOCKSTEP_INCREMENT - 256; // Split the cycles to avoid waiting too long
|
||||
#ifndef NDEBUG
|
||||
ATOMIC_ADD(node->p->d.transferId, 1);
|
||||
#endif
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
|
||||
break;
|
||||
case TRANSFER_FINISHED:
|
||||
// Everything's settled. We're done.
|
||||
_finishTransfer(node);
|
||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
|
||||
break;
|
||||
}
|
||||
int mask = 0;
|
||||
for (i = 1; i < node->p->d.attached; ++i) {
|
||||
mask |= 1 << i;
|
||||
}
|
||||
if (mask) {
|
||||
if (needsToWait) {
|
||||
if (!node->p->d.wait(&node->p->d, mask)) {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
node->p->d.signal(&node->p->d, mask);
|
||||
}
|
||||
}
|
||||
// Tell the other GBs they can continue up to where we were
|
||||
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
if (needsToWait) {
|
||||
return 0;
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
||||
|
||||
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||
bool signal = false;
|
||||
switch (node->p->d.transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
case TRANSFER_FINISHING:
|
||||
break;
|
||||
case TRANSFER_STARTED:
|
||||
node->transferFinished = false;
|
||||
signal = true;
|
||||
break;
|
||||
case TRANSFER_FINISHED:
|
||||
_finishTransfer(node);
|
||||
signal = true;
|
||||
break;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
if (signal) {
|
||||
node->p->d.signal(&node->p->d, 1 << node->id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBSIOLockstepNode* node = user;
|
||||
if (node->p->d.attached < 2) {
|
||||
return;
|
||||
}
|
||||
int32_t cycles = 0;
|
||||
node->nextEvent -= cyclesLate;
|
||||
if (node->nextEvent <= 0) {
|
||||
if (!node->id) {
|
||||
cycles = _masterUpdate(node);
|
||||
} else {
|
||||
cycles = _slaveUpdate(node);
|
||||
cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
|
||||
}
|
||||
node->eventDiff = 0;
|
||||
} else {
|
||||
cycles = node->nextEvent;
|
||||
}
|
||||
if (cycles > 0) {
|
||||
node->nextEvent = 0;
|
||||
node->eventDiff += cycles;
|
||||
mTimingDeschedule(timing, &node->event);
|
||||
mTimingSchedule(timing, &node->event, cycles);
|
||||
} else {
|
||||
node->d.p->p->earlyExit = true;
|
||||
mTimingSchedule(timing, &node->event, cyclesLate + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||
node->p->pendingSB[node->id] = value;
|
||||
}
|
||||
|
||||
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
|
||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||
if (!node->id && (value & 0x81) == 0x81) {
|
||||
node->p->d.transferActive = TRANSFER_STARTING;
|
||||
node->p->d.transferCycles = GBSIOCyclesPerTransfer[(value >> 1) & 1];
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
}
|
||||
return value;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_SIO_LOCKSTEP_H
|
||||
#define GB_SIO_LOCKSTEP_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/lockstep.h"
|
||||
#include "core/timing.h"
|
||||
#include "gb/sio.h"
|
||||
|
||||
struct GBSIOLockstep {
|
||||
struct mLockstep d;
|
||||
struct GBSIOLockstepNode* players[MAX_GBS];
|
||||
|
||||
uint8_t pendingSB[MAX_GBS];
|
||||
};
|
||||
|
||||
struct GBSIOLockstepNode {
|
||||
struct GBSIODriver d;
|
||||
struct GBSIOLockstep* p;
|
||||
struct mTimingEvent event;
|
||||
|
||||
volatile int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
int id;
|
||||
bool transferFinished;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
enum mLockstepPhase phase;
|
||||
#endif
|
||||
};
|
||||
|
||||
void GBSIOLockstepInit(struct GBSIOLockstep*);
|
||||
|
||||
void GBSIOLockstepNodeCreate(struct GBSIOLockstepNode*);
|
||||
|
||||
bool GBSIOLockstepAttachNode(struct GBSIOLockstep*, struct GBSIOLockstepNode*);
|
||||
void GBSIOLockstepDetachNode(struct GBSIOLockstep*, struct GBSIOLockstepNode*);
|
||||
|
||||
#endif
|
|
@ -20,6 +20,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
|||
static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate);
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
||||
mLockstepInit(&lockstep->d);
|
||||
lockstep->players[0] = 0;
|
||||
lockstep->players[1] = 0;
|
||||
lockstep->players[2] = 0;
|
||||
|
@ -28,16 +29,7 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
|||
lockstep->multiRecv[1] = 0xFFFF;
|
||||
lockstep->multiRecv[2] = 0xFFFF;
|
||||
lockstep->multiRecv[3] = 0xFFFF;
|
||||
lockstep->attached = 0;
|
||||
lockstep->attachedMulti = 0;
|
||||
lockstep->transferActive = 0;
|
||||
#ifndef NDEBUG
|
||||
lockstep->transferId = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
|
||||
UNUSED(lockstep);
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
||||
|
@ -49,30 +41,30 @@ void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
|||
}
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == MAX_GBAS) {
|
||||
if (lockstep->d.attached == MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
lockstep->players[lockstep->attached] = node;
|
||||
lockstep->players[lockstep->d.attached] = node;
|
||||
node->p = lockstep;
|
||||
node->id = lockstep->attached;
|
||||
++lockstep->attached;
|
||||
node->id = lockstep->d.attached;
|
||||
++lockstep->d.attached;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == 0) {
|
||||
if (lockstep->d.attached == 0) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lockstep->attached; ++i) {
|
||||
for (i = 0; i < lockstep->d.attached; ++i) {
|
||||
if (lockstep->players[i] != node) {
|
||||
continue;
|
||||
}
|
||||
for (++i; i < lockstep->attached; ++i) {
|
||||
for (++i; i < lockstep->d.attached; ++i) {
|
||||
lockstep->players[i - 1] = lockstep->players[i];
|
||||
lockstep->players[i - 1]->id = i - 1;
|
||||
}
|
||||
--lockstep->attached;
|
||||
--lockstep->d.attached;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +95,7 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
|||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||
node->d.p->rcnt |= 3;
|
||||
++node->p->attachedMulti;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
|
@ -116,8 +108,8 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
|||
break;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
node->transferId = node->p->transferId;
|
||||
node->phase = node->p->d.transferActive;
|
||||
node->transferId = node->p->d.transferId;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
@ -132,7 +124,7 @@ bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
node->p->unload(node->p, node->id);
|
||||
node->p->d.unload(&node->p->d, node->id);
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
return true;
|
||||
}
|
||||
|
@ -141,11 +133,11 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
|||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||
if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) {
|
||||
if (value & 0x0080 && node->p->d.transferActive == TRANSFER_IDLE) {
|
||||
if (!node->id && node->d.p->multiplayerControl.ready) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
||||
node->p->transferActive = TRANSFER_STARTING;
|
||||
node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
|
||||
node->p->d.transferActive = TRANSFER_STARTING;
|
||||
node->p->d.transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1];
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
} else {
|
||||
|
@ -218,11 +210,11 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
|
|||
static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
||||
bool needsToWait = false;
|
||||
int i;
|
||||
switch (node->p->transferActive) {
|
||||
switch (node->p->d.transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
// If the master hasn't initiated a transfer, it can keep going.
|
||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached;
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
// Start the transfer, but wait for the other GBAs to catch up
|
||||
|
@ -232,51 +224,51 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
node->p->multiRecv[2] = 0xFFFF;
|
||||
node->p->multiRecv[3] = 0xFFFF;
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED);
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED);
|
||||
node->nextEvent += 512;
|
||||
break;
|
||||
case TRANSFER_STARTED:
|
||||
// All the other GBAs have caught up and are sleeping, we can all continue now
|
||||
node->p->multiRecv[0] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
node->nextEvent += 512;
|
||||
ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHING);
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING);
|
||||
break;
|
||||
case TRANSFER_FINISHING:
|
||||
// Finish the transfer
|
||||
// We need to make sure the other GBAs catch up so they don't get behind
|
||||
node->nextEvent += node->p->transferCycles - 1024; // Split the cycles to avoid waiting too long
|
||||
node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long
|
||||
#ifndef NDEBUG
|
||||
ATOMIC_ADD(node->p->transferId, 1);
|
||||
ATOMIC_ADD(node->p->d.transferId, 1);
|
||||
#endif
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED);
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED);
|
||||
break;
|
||||
case TRANSFER_FINISHED:
|
||||
// Everything's settled. We're done.
|
||||
_finishTransfer(node);
|
||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||
ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE);
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE);
|
||||
break;
|
||||
}
|
||||
int mask = 0;
|
||||
for (i = 1; i < node->p->attached; ++i) {
|
||||
for (i = 1; i < node->p->d.attached; ++i) {
|
||||
if (node->p->players[i]->mode == node->mode) {
|
||||
mask |= 1 << i;
|
||||
}
|
||||
}
|
||||
if (mask) {
|
||||
if (needsToWait) {
|
||||
if (!node->p->wait(node->p, mask)) {
|
||||
if (!node->p->d.wait(&node->p->d, mask)) {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
node->p->signal(node->p, mask);
|
||||
node->p->d.signal(&node->p->d, mask);
|
||||
}
|
||||
}
|
||||
// Tell the other GBAs they can continue up to where we were
|
||||
node->p->addCycles(node->p, 0, node->eventDiff);
|
||||
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
if (needsToWait) {
|
||||
return 0;
|
||||
|
@ -285,12 +277,12 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
}
|
||||
|
||||
static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached;
|
||||
bool signal = false;
|
||||
switch (node->p->transferActive) {
|
||||
switch (node->p->d.transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
if (!node->d.p->multiplayerControl.ready) {
|
||||
node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
|
||||
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
||||
}
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
|
@ -329,17 +321,17 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
|||
break;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
if (signal) {
|
||||
node->p->signal(node->p, 1 << node->id);
|
||||
node->p->d.signal(&node->p->d, 1 << node->id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBASIOLockstepNode* node = user;
|
||||
if (node->p->attached < 2) {
|
||||
if (node->p->d.attached < 2) {
|
||||
return;
|
||||
}
|
||||
int32_t cycles = 0;
|
||||
|
@ -349,7 +341,7 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
|||
cycles = _masterUpdate(node);
|
||||
} else {
|
||||
cycles = _slaveUpdate(node);
|
||||
cycles += node->p->useCycles(node->p, node->id, node->eventDiff);
|
||||
cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
|
||||
}
|
||||
node->eventDiff = 0;
|
||||
} else {
|
||||
|
@ -377,13 +369,13 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
|||
if (value & 0x0080 && !node->id) {
|
||||
// Internal shift clock
|
||||
if (value & 1) {
|
||||
node->p->transferActive = TRANSFER_STARTING;
|
||||
node->p->d.transferActive = TRANSFER_STARTING;
|
||||
}
|
||||
// Frequency
|
||||
if (value & 2) {
|
||||
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
|
||||
node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
|
||||
} else {
|
||||
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
|
||||
node->p->d.transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
|
||||
}
|
||||
}
|
||||
} else if (address == REG_SIODATA32_LO) {
|
||||
|
|
|
@ -3,40 +3,23 @@
|
|||
* 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
|
||||
#ifndef GBA_SIO_LOCKSTEP_H
|
||||
#define GBA_SIO_LOCKSTEP_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/lockstep.h"
|
||||
#include "core/timing.h"
|
||||
#include "gba/sio.h"
|
||||
|
||||
enum GBASIOLockstepPhase {
|
||||
TRANSFER_IDLE = 0,
|
||||
TRANSFER_STARTING,
|
||||
TRANSFER_STARTED,
|
||||
TRANSFER_FINISHING,
|
||||
TRANSFER_FINISHED
|
||||
};
|
||||
|
||||
struct GBASIOLockstep {
|
||||
struct mLockstep d;
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attached;
|
||||
int attachedMulti;
|
||||
int attachedNormal;
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
uint32_t normalRecv[MAX_GBAS];
|
||||
enum GBASIOLockstepPhase transferActive;
|
||||
int32_t transferCycles;
|
||||
|
||||
bool (*signal)(struct GBASIOLockstep*, unsigned mask);
|
||||
bool (*wait)(struct GBASIOLockstep*, unsigned mask);
|
||||
void (*addCycles)(struct GBASIOLockstep*, int id, int32_t cycles);
|
||||
int32_t (*useCycles)(struct GBASIOLockstep*, int id, int32_t cycles);
|
||||
void (*unload)(struct GBASIOLockstep*, int id);
|
||||
void* context;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct GBASIOLockstepNode {
|
||||
|
@ -52,12 +35,11 @@ struct GBASIOLockstepNode {
|
|||
bool transferFinished;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
enum GBASIOLockstepPhase phase;
|
||||
enum mLockstepPhase phase;
|
||||
#endif
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep*);
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ struct NoIntroDB;
|
|||
|
||||
extern "C" {
|
||||
#include "core/log.h"
|
||||
#include "gba/sio.h"
|
||||
}
|
||||
|
||||
mLOG_DECLARE_CATEGORY(QT);
|
||||
|
|
|
@ -11,15 +11,17 @@ extern "C" {
|
|||
#ifdef M_CORE_GBA
|
||||
#include "gba/gba.h"
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include "gb/gb.h"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
MultiplayerController::MultiplayerController() {
|
||||
GBASIOLockstepInit(&m_lockstep);
|
||||
mLockstepInit(&m_lockstep);
|
||||
m_lockstep.context = this;
|
||||
m_lockstep.signal = [](GBASIOLockstep* lockstep, unsigned mask) {
|
||||
m_lockstep.signal = [](mLockstep* lockstep, unsigned mask) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
Player* player = &controller->m_players[0];
|
||||
bool woke = false;
|
||||
|
@ -33,7 +35,7 @@ MultiplayerController::MultiplayerController() {
|
|||
controller->m_lock.unlock();
|
||||
return woke;
|
||||
};
|
||||
m_lockstep.wait = [](GBASIOLockstep* lockstep, unsigned mask) {
|
||||
m_lockstep.wait = [](mLockstep* lockstep, unsigned mask) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[0];
|
||||
|
@ -47,7 +49,7 @@ MultiplayerController::MultiplayerController() {
|
|||
controller->m_lock.unlock();
|
||||
return slept;
|
||||
};
|
||||
m_lockstep.addCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
|
||||
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||
if (cycles < 0) {
|
||||
abort();
|
||||
}
|
||||
|
@ -56,14 +58,27 @@ MultiplayerController::MultiplayerController() {
|
|||
if (!id) {
|
||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||
Player* player = &controller->m_players[i];
|
||||
if (player->node->d.p->mode != controller->m_players[0].node->d.p->mode) {
|
||||
if (player->controller->platform() == PLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) {
|
||||
player->controller->setSync(true);
|
||||
continue;
|
||||
}
|
||||
player->controller->setSync(false);
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
player->node->nextEvent += player->cyclesPosted;
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
player->gbNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
|
@ -74,7 +89,7 @@ MultiplayerController::MultiplayerController() {
|
|||
}
|
||||
controller->m_lock.unlock();
|
||||
};
|
||||
m_lockstep.useCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
|
||||
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[id];
|
||||
|
@ -87,7 +102,7 @@ MultiplayerController::MultiplayerController() {
|
|||
controller->m_lock.unlock();
|
||||
return cycles;
|
||||
};
|
||||
m_lockstep.unload = [](GBASIOLockstep* lockstep, int id) {
|
||||
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[id];
|
||||
|
@ -102,9 +117,35 @@ MultiplayerController::MultiplayerController() {
|
|||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||
Player* player = &controller->m_players[i];
|
||||
player->controller->setSync(true);
|
||||
player->cyclesPosted += lockstep->players[0]->eventDiff;
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
player->cyclesPosted += reinterpret_cast<GBASIOLockstep*>(lockstep)->players[0]->eventDiff;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
player->cyclesPosted += reinterpret_cast<GBSIOLockstep*>(lockstep)->players[0]->eventDiff;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (player->awake < 1) {
|
||||
player->node->nextEvent += player->cyclesPosted;
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
player->gbNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
|
@ -114,29 +155,44 @@ MultiplayerController::MultiplayerController() {
|
|||
};
|
||||
}
|
||||
|
||||
MultiplayerController::~MultiplayerController() {
|
||||
GBASIOLockstepDeinit(&m_lockstep);
|
||||
}
|
||||
|
||||
bool MultiplayerController::attachGame(GameController* controller) {
|
||||
if (m_lockstep.attached == MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_lockstep.attached == 0) {
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
GBASIOLockstepInit(&m_gbaLockstep);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
GBSIOLockstepInit(&m_gbLockstep);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mCoreThread* thread = controller->thread();
|
||||
if (!thread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
if (controller->platform() == PLATFORM_GBA) {
|
||||
case PLATFORM_GBA: {
|
||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||
|
||||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||
GBASIOLockstepNodeCreate(node);
|
||||
GBASIOLockstepAttachNode(&m_lockstep, node);
|
||||
GBASIOLockstepAttachNode(&m_gbaLockstep, node);
|
||||
m_players.append({
|
||||
controller,
|
||||
nullptr,
|
||||
node,
|
||||
1,
|
||||
0,
|
||||
|
@ -149,6 +205,31 @@ bool MultiplayerController::attachGame(GameController* controller) {
|
|||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB: {
|
||||
GB* gb = static_cast<GB*>(thread->core->board);
|
||||
|
||||
GBSIOLockstepNode* node = new GBSIOLockstepNode;
|
||||
GBSIOLockstepNodeCreate(node);
|
||||
GBSIOLockstepAttachNode(&m_gbLockstep, node);
|
||||
m_players.append({
|
||||
controller,
|
||||
node,
|
||||
nullptr,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
});
|
||||
|
||||
GBSIOSetDriver(&gb->sio, &node->d);
|
||||
|
||||
emit gameAttached();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -161,17 +242,34 @@ void MultiplayerController::detachGame(GameController* controller) {
|
|||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
m_players[i].controller->threadInterrupt();
|
||||
}
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
if (controller->platform() == PLATFORM_GBA) {
|
||||
case PLATFORM_GBA: {
|
||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, SIO_MULTI);
|
||||
if (node) {
|
||||
GBASIOLockstepDetachNode(&m_lockstep, node);
|
||||
GBASIOLockstepDetachNode(&m_gbaLockstep, node);
|
||||
delete node;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB: {
|
||||
GB* gb = static_cast<GB*>(thread->core->board);
|
||||
GBSIOLockstepNode* node = reinterpret_cast<GBSIOLockstepNode*>(gb->sio.driver);
|
||||
GBSIOSetDriver(&gb->sio, nullptr);
|
||||
if (node) {
|
||||
GBSIOLockstepDetachNode(&m_gbLockstep, node);
|
||||
delete node;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
controller->threadContinue();
|
||||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
|
|
|
@ -11,7 +11,13 @@
|
|||
#include <QObject>
|
||||
|
||||
extern "C" {
|
||||
#include "core/lockstep.h"
|
||||
#ifdef M_CORE_GBA
|
||||
#include "gba/sio/lockstep.h"
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include "gb/sio/lockstep.h"
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -23,7 +29,6 @@ Q_OBJECT
|
|||
|
||||
public:
|
||||
MultiplayerController();
|
||||
~MultiplayerController();
|
||||
|
||||
bool attachGame(GameController*);
|
||||
void detachGame(GameController*);
|
||||
|
@ -38,12 +43,21 @@ signals:
|
|||
private:
|
||||
struct Player {
|
||||
GameController* controller;
|
||||
GBASIOLockstepNode* node;
|
||||
GBSIOLockstepNode* gbNode;
|
||||
GBASIOLockstepNode* gbaNode;
|
||||
int awake;
|
||||
int32_t cyclesPosted;
|
||||
unsigned waitMask;
|
||||
};
|
||||
GBASIOLockstep m_lockstep;
|
||||
union {
|
||||
mLockstep m_lockstep;
|
||||
#ifdef M_CORE_GB
|
||||
GBSIOLockstep m_gbLockstep;
|
||||
#endif
|
||||
#ifdef M_CORE_GBA
|
||||
GBASIOLockstep m_gbaLockstep;
|
||||
#endif
|
||||
};
|
||||
QList<Player> m_players;
|
||||
QMutex m_lock;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue