mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/multiplayer-rewrite'
This commit is contained in:
commit
2b71e5c797
|
@ -47,7 +47,7 @@ static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadStat
|
|||
}
|
||||
|
||||
static void _waitOnInterrupt(struct mCoreThread* threadContext) {
|
||||
while (threadContext->state == THREAD_INTERRUPTED) {
|
||||
while (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
|
||||
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||
}
|
||||
}
|
||||
|
@ -80,11 +80,9 @@ static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThre
|
|||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
static void _pauseThread(struct mCoreThread* threadContext, bool onThread) {
|
||||
static void _pauseThread(struct mCoreThread* threadContext) {
|
||||
threadContext->state = THREAD_PAUSING;
|
||||
if (!onThread) {
|
||||
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||
}
|
||||
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||
}
|
||||
|
||||
static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||
|
@ -157,7 +155,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
|||
threadContext->state = THREAD_RUNNING;
|
||||
resetScheduled = 1;
|
||||
}
|
||||
while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
|
||||
while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_WAITING) {
|
||||
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +270,7 @@ void mCoreThreadEnd(struct mCoreThread* threadContext) {
|
|||
|
||||
void mCoreThreadReset(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_INTERRUPTED) {
|
||||
if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
|
||||
threadContext->savedState = THREAD_RESETING;
|
||||
} else {
|
||||
threadContext->state = THREAD_RESETING;
|
||||
|
@ -365,7 +363,7 @@ void mCoreThreadPause(struct mCoreThread* threadContext) {
|
|||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, false);
|
||||
_pauseThread(threadContext);
|
||||
threadContext->frameWasOn = frameOn;
|
||||
frameOn = false;
|
||||
}
|
||||
|
@ -391,7 +389,7 @@ void mCoreThreadUnpause(struct mCoreThread* threadContext) {
|
|||
bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
|
||||
bool isPaused;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_INTERRUPTED) {
|
||||
if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
|
||||
isPaused = threadContext->savedState == THREAD_PAUSED;
|
||||
} else {
|
||||
isPaused = threadContext->state == THREAD_PAUSED;
|
||||
|
@ -409,7 +407,7 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
|
|||
ConditionWake(&threadContext->stateCond);
|
||||
frameOn = threadContext->frameWasOn;
|
||||
} else if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, false);
|
||||
_pauseThread(threadContext);
|
||||
threadContext->frameWasOn = frameOn;
|
||||
frameOn = false;
|
||||
}
|
||||
|
@ -421,8 +419,8 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
|
|||
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
|
||||
bool frameOn = true;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, true);
|
||||
if (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING)) {
|
||||
threadContext->state = THREAD_PAUSING;
|
||||
frameOn = false;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
@ -442,6 +440,27 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding)
|
|||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_RUNNING) {
|
||||
threadContext->savedState = THREAD_WAITING;
|
||||
} else if (threadContext->state == THREAD_RUNNING) {
|
||||
threadContext->state = THREAD_WAITING;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_WAITING) {
|
||||
threadContext->savedState = THREAD_RUNNING;
|
||||
} else if (threadContext->state == THREAD_WAITING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
struct mCoreThread* mCoreThreadGet(void) {
|
||||
pthread_once(&_contextOnce, _createTLS);
|
||||
|
|
|
@ -28,6 +28,7 @@ enum mCoreThreadState {
|
|||
THREAD_PAUSED,
|
||||
THREAD_PAUSING,
|
||||
THREAD_RUN_ON,
|
||||
THREAD_WAITING,
|
||||
THREAD_RESETING,
|
||||
THREAD_EXITING,
|
||||
THREAD_SHUTDOWN,
|
||||
|
@ -87,6 +88,8 @@ void mCoreThreadUnpause(struct mCoreThread* threadContext);
|
|||
bool mCoreThreadIsPaused(struct mCoreThread* threadContext);
|
||||
void mCoreThreadTogglePause(struct mCoreThread* threadContext);
|
||||
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext);
|
||||
void mCoreThreadWaitFromThread(struct mCoreThread* threadContext);
|
||||
void mCoreThreadStopWaiting(struct mCoreThread* threadContext);
|
||||
|
||||
void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool);
|
||||
|
||||
|
|
|
@ -234,21 +234,33 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
|
|||
|
||||
testEvent = GBAVideoProcessEvents(&gba->video, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
if (testEvent == 0) {
|
||||
abort();
|
||||
}
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
testEvent = GBAAudioProcessEvents(&gba->audio, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
if (testEvent == 0) {
|
||||
abort();
|
||||
}
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
testEvent = GBATimersProcessEvents(gba, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
if (testEvent == 0) {
|
||||
abort();
|
||||
}
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
testEvent = GBAMemoryRunDMAs(gba, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
if (testEvent == 0) {
|
||||
abort();
|
||||
}
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
|
@ -263,6 +275,9 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
|
|||
if (cpu->halted) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
}
|
||||
if (cpu->nextEvent == 0) {
|
||||
break;
|
||||
}
|
||||
} while (cpu->cycles >= cpu->nextEvent);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,16 +32,17 @@ static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mo
|
|||
|
||||
static void _switchMode(struct GBASIO* sio) {
|
||||
unsigned mode = ((sio->rcnt & 0xC000) | (sio->siocnt & 0x3000)) >> 12;
|
||||
enum GBASIOMode oldMode = sio->mode;
|
||||
enum GBASIOMode newMode;
|
||||
if (mode < 8) {
|
||||
sio->mode = (enum GBASIOMode) (mode & 0x3);
|
||||
newMode = (enum GBASIOMode) (mode & 0x3);
|
||||
} else {
|
||||
sio->mode = (enum GBASIOMode) (mode & 0xC);
|
||||
newMode = (enum GBASIOMode) (mode & 0xC);
|
||||
}
|
||||
if (oldMode != sio->mode) {
|
||||
if (newMode != sio->mode) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
sio->mode = newMode;
|
||||
sio->activeDriver = _lookupDriver(sio, sio->mode);
|
||||
if (sio->activeDriver && sio->activeDriver->load) {
|
||||
sio->activeDriver->load(sio->activeDriver);
|
||||
|
@ -121,11 +122,11 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (sio->mode == mode) {
|
||||
sio->activeDriver = driver;
|
||||
if (driver->load) {
|
||||
driver->load(driver);
|
||||
}
|
||||
}
|
||||
if (sio->mode == mode) {
|
||||
sio->activeDriver = driver;
|
||||
if (driver && driver->load) {
|
||||
driver->load(driver);
|
||||
}
|
||||
}
|
||||
*driverLoc = driver;
|
||||
|
|
|
@ -7,17 +7,18 @@
|
|||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#define LOCKSTEP_INCREMENT 2048
|
||||
#define LOCKSTEP_INCREMENT 3000
|
||||
|
||||
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 GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeMultiProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeNormalProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
||||
lockstep->players[0] = 0;
|
||||
|
@ -29,18 +30,15 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
|||
lockstep->multiRecv[2] = 0xFFFF;
|
||||
lockstep->multiRecv[3] = 0xFFFF;
|
||||
lockstep->attached = 0;
|
||||
lockstep->loadedMulti = 0;
|
||||
lockstep->loadedNormal = 0;
|
||||
lockstep->transferActive = false;
|
||||
lockstep->waiting = 0;
|
||||
lockstep->nextEvent = LOCKSTEP_INCREMENT;
|
||||
ConditionInit(&lockstep->barrier);
|
||||
MutexInit(&lockstep->mutex);
|
||||
lockstep->attachedMulti = 0;
|
||||
lockstep->transferActive = 0;
|
||||
#ifndef NDEBUG
|
||||
lockstep->transferId = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
|
||||
ConditionDeinit(&lockstep->barrier);
|
||||
MutexDeinit(&lockstep->mutex);
|
||||
UNUSED(lockstep);
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
||||
|
@ -49,7 +47,7 @@ void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
|||
node->d.load = GBASIOLockstepNodeLoad;
|
||||
node->d.unload = GBASIOLockstepNodeUnload;
|
||||
node->d.writeRegister = 0;
|
||||
node->d.processEvents = 0;
|
||||
node->d.processEvents = GBASIOLockstepNodeProcessEvents;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
|
@ -83,7 +81,6 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
|
|||
|
||||
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->d.p->multiplayerControl.slave = node->id > 0;
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
|
||||
return true;
|
||||
|
@ -95,15 +92,15 @@ void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
|
|||
|
||||
bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
node->nextEvent = 0;
|
||||
node->eventDiff = 0;
|
||||
node->mode = driver->p->mode;
|
||||
MutexLock(&node->p->mutex);
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeMultiProcessEvents;
|
||||
++node->p->loadedMulti;
|
||||
node->d.p->rcnt |= 3;
|
||||
++node->p->attachedMulti;
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
|
@ -111,31 +108,28 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
|||
break;
|
||||
case SIO_NORMAL_32:
|
||||
node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeNormalProcessEvents;
|
||||
++node->p->loadedNormal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
node->transferId = node->p->transferId;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
MutexLock(&node->p->mutex);
|
||||
node->mode = driver->p->mode;
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
--node->p->loadedMulti;
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
--node->p->loadedNormal;
|
||||
--node->p->attachedMulti;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ConditionWake(&node->p->barrier);
|
||||
MutexUnlock(&node->p->mutex);
|
||||
node->p->unload(node->p, node->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -143,14 +137,12 @@ 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) {
|
||||
if (!node->id) {
|
||||
if (value & 0x0080 && node->p->transferActive == TRANSFER_IDLE) {
|
||||
if (!node->id && node->d.p->multiplayerControl.ready) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
||||
MutexLock(&node->p->mutex);
|
||||
node->p->transferActive = true;
|
||||
node->p->transferActive = TRANSFER_STARTING;
|
||||
node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
|
||||
node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
MutexUnlock(&node->p->mutex);
|
||||
node->nextEvent = 0;
|
||||
} else {
|
||||
value &= ~0x0080;
|
||||
}
|
||||
|
@ -163,67 +155,205 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
|||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeMultiProcessEvents(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->loadedMulti) {
|
||||
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);
|
||||
static void _finishTransfer(struct GBASIOLockstepNode* node) {
|
||||
if (node->transferFinished) {
|
||||
return;
|
||||
}
|
||||
struct GBASIO* sio = node->d.p;
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
sio->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
|
||||
sio->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
|
||||
sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
|
||||
sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
||||
sio->rcnt |= 1;
|
||||
sio->multiplayerControl.busy = 0;
|
||||
sio->multiplayerControl.id = node->id;
|
||||
if (sio->multiplayerControl.irq) {
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO);
|
||||
}
|
||||
if (node->state == LOCKSTEP_FINISHED) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Finishing transfer: %04x %04x %04x %04x", node->id, node->p->multiRecv[0], node->p->multiRecv[1], node->p->multiRecv[2], node->p->multiRecv[3]);
|
||||
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);
|
||||
break;
|
||||
case SIO_NORMAL_8:
|
||||
// TODO
|
||||
sio->normalControl.start = 0;
|
||||
if (node->id) {
|
||||
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
|
||||
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
|
||||
} else {
|
||||
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
|
||||
}
|
||||
if (sio->multiplayerControl.irq) {
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO);
|
||||
}
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
// TODO
|
||||
sio->normalControl.start = 0;
|
||||
if (node->id) {
|
||||
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
|
||||
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
|
||||
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
|
||||
} else {
|
||||
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
|
||||
}
|
||||
if (sio->multiplayerControl.irq) {
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
node->transferFinished = true;
|
||||
#ifndef NDEBUG
|
||||
++node->transferId;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
||||
bool needsToWait = false;
|
||||
int i;
|
||||
switch (node->p->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;
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
// Start the transfer, but wait for the other GBAs to catch up
|
||||
node->transferFinished = false;
|
||||
node->p->multiRecv[0] = 0xFFFF;
|
||||
node->p->multiRecv[1] = 0xFFFF;
|
||||
node->p->multiRecv[2] = 0xFFFF;
|
||||
node->p->multiRecv[3] = 0xFFFF;
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->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);
|
||||
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
|
||||
#ifndef NDEBUG
|
||||
ATOMIC_ADD(node->p->transferId, 1);
|
||||
#endif
|
||||
needsToWait = true;
|
||||
ATOMIC_STORE(node->p->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);
|
||||
break;
|
||||
}
|
||||
int mask = 0;
|
||||
for (i = 1; i < node->p->attached; ++i) {
|
||||
if (node->p->players[i]->mode == node->mode) {
|
||||
mask |= 1 << i;
|
||||
}
|
||||
}
|
||||
if (mask) {
|
||||
if (needsToWait) {
|
||||
if (!node->p->wait(node->p, mask)) {
|
||||
abort();
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
node->p->signal(node->p, mask);
|
||||
}
|
||||
}
|
||||
// Tell the other GBAs they can continue up to where we were
|
||||
node->p->addCycles(node->p, 0, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
#endif
|
||||
if (needsToWait) {
|
||||
return 0;
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
||||
|
||||
static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached;
|
||||
bool signal = false;
|
||||
switch (node->p->transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
if (!node->d.p->multiplayerControl.ready) {
|
||||
node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
|
||||
}
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
case TRANSFER_FINISHING:
|
||||
break;
|
||||
case TRANSFER_STARTED:
|
||||
node->transferFinished = false;
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
node->d.p->rcnt &= ~1;
|
||||
node->p->multiRecv[node->id] = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
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;
|
||||
if (node->id) {
|
||||
node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
node->d.p->multiplayerControl.busy = 1;
|
||||
}
|
||||
node->d.p->multiplayerControl.busy = 1;
|
||||
break;
|
||||
case SIO_NORMAL_8:
|
||||
node->p->multiRecv[node->id] = 0xFFFF;
|
||||
node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
node->p->multiRecv[node->id] = 0xFFFF;
|
||||
node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
|
||||
node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
|
||||
break;
|
||||
default:
|
||||
node->p->multiRecv[node->id] = 0xFFFF;
|
||||
break;
|
||||
}
|
||||
node->d.p->multiplayerControl.ready = node->p->loadedMulti == node->p->attached;
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
signal = true;
|
||||
break;
|
||||
case TRANSFER_FINISHED:
|
||||
_finishTransfer(node);
|
||||
signal = true;
|
||||
break;
|
||||
}
|
||||
return node->nextEvent;
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
#endif
|
||||
if (signal) {
|
||||
node->p->signal(node->p, 1 << node->id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (node->p->attached < 2) {
|
||||
return INT_MAX;
|
||||
}
|
||||
node->eventDiff += cycles;
|
||||
node->nextEvent -= cycles;
|
||||
if (node->nextEvent <= 0) {
|
||||
if (!node->id) {
|
||||
cycles = _masterUpdate(node);
|
||||
} else {
|
||||
cycles = _slaveUpdate(node);
|
||||
node->nextEvent += node->p->useCycles(node->p, node->id, node->eventDiff);
|
||||
}
|
||||
node->eventDiff = 0;
|
||||
} else {
|
||||
cycles = node->nextEvent;
|
||||
}
|
||||
if (cycles < 0) {
|
||||
return 0;
|
||||
}
|
||||
return cycles;
|
||||
}
|
||||
|
||||
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
|
@ -231,11 +361,13 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
|||
if (address == REG_SIOCNT) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||
value &= 0xFF8B;
|
||||
MutexLock(&node->p->mutex);
|
||||
if (value & 0x0080) {
|
||||
if (!node->id) {
|
||||
driver->p->normalControl.si = 1;
|
||||
}
|
||||
if (value & 0x0080 && !node->id) {
|
||||
// Internal shift clock
|
||||
if (value & 1) {
|
||||
node->p->transferActive = true;
|
||||
node->p->transferActive = TRANSFER_STARTING;
|
||||
}
|
||||
// Frequency
|
||||
if (value & 2) {
|
||||
|
@ -243,13 +375,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
|||
} else {
|
||||
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
|
||||
}
|
||||
node->normalSO = !!(value & 8);
|
||||
// Opponent's SO
|
||||
if (node->id) {
|
||||
value |= node->p->players[node->id - 1]->normalSO << 2;
|
||||
}
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
} else if (address == REG_SIODATA32_LO) {
|
||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
|
||||
} else if (address == REG_SIODATA32_HI) {
|
||||
|
@ -257,51 +383,3 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
|||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeNormalProcessEvents(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->loadedNormal) {
|
||||
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->players[i]->state = LOCKSTEP_FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
node->p->waiting = 0;
|
||||
ConditionWake(&node->p->barrier);
|
||||
}
|
||||
if (node->state == LOCKSTEP_FINISHED) {
|
||||
int i;
|
||||
for (i = 1; i < node->p->loadedNormal; ++i) {
|
||||
node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
|
||||
node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_HI >> 1];
|
||||
}
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
if (node->d.p->normalControl.irq) {
|
||||
GBARaiseIRQ(node->d.p->p, IRQ_SIO);
|
||||
}
|
||||
node->d.p->multiplayerControl.id = node->id;
|
||||
node->d.p->normalControl.start = 0;
|
||||
} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
|
||||
node->state = LOCKSTEP_STARTED;
|
||||
}
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
||||
|
|
|
@ -8,40 +8,50 @@
|
|||
|
||||
#include "gba/sio.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
enum LockstepState {
|
||||
LOCKSTEP_IDLE = 0,
|
||||
LOCKSTEP_STARTED = 1,
|
||||
LOCKSTEP_FINISHED = 2
|
||||
enum GBASIOLockstepPhase {
|
||||
TRANSFER_IDLE = 0,
|
||||
TRANSFER_STARTING,
|
||||
TRANSFER_STARTED,
|
||||
TRANSFER_FINISHING,
|
||||
TRANSFER_FINISHED
|
||||
};
|
||||
|
||||
struct GBASIOLockstep {
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attached;
|
||||
int loadedMulti;
|
||||
int loadedNormal;
|
||||
int attachedMulti;
|
||||
int attachedNormal;
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
bool transferActive;
|
||||
uint32_t normalRecv[MAX_GBAS];
|
||||
enum GBASIOLockstepPhase transferActive;
|
||||
int32_t transferCycles;
|
||||
int32_t nextEvent;
|
||||
|
||||
int waiting;
|
||||
Mutex mutex;
|
||||
Condition barrier;
|
||||
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 {
|
||||
struct GBASIODriver d;
|
||||
struct GBASIOLockstep* p;
|
||||
|
||||
int32_t nextEvent;
|
||||
uint16_t multiSend;
|
||||
volatile int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
bool normalSO;
|
||||
enum LockstepState state;
|
||||
int id;
|
||||
enum GBASIOMode mode;
|
||||
bool transferFinished;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
enum GBASIOLockstepPhase phase;
|
||||
#endif
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
|
|
|
@ -50,6 +50,7 @@ GameController::GameController(QObject* parent)
|
|||
, m_audioThread(new QThread(this))
|
||||
, m_audioProcessor(AudioProcessor::create())
|
||||
, m_pauseAfterFrame(false)
|
||||
, m_sync(true)
|
||||
, m_videoSync(VIDEO_SYNC)
|
||||
, m_audioSync(AUDIO_SYNC)
|
||||
, m_fpsTarget(-1)
|
||||
|
@ -130,6 +131,10 @@ GameController::GameController(QObject* parent)
|
|||
}
|
||||
|
||||
controller->m_gameOpen = true;
|
||||
if (controller->m_multiplayer) {
|
||||
controller->m_multiplayer->attachGame(controller);
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, controller->m_fname));
|
||||
QMetaObject::invokeMethod(controller, "startAudio");
|
||||
};
|
||||
|
@ -256,7 +261,12 @@ void GameController::setMultiplayerController(MultiplayerController* controller)
|
|||
}
|
||||
clearMultiplayerController();
|
||||
m_multiplayer = controller;
|
||||
controller->attachGame(this);
|
||||
if (isLoaded()) {
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
|
||||
GameController* controller = static_cast<GameController*>(thread->userData);
|
||||
controller->m_multiplayer->attachGame(controller);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::clearMultiplayerController() {
|
||||
|
@ -522,6 +532,9 @@ void GameController::closeGame() {
|
|||
if (!m_gameOpen) {
|
||||
return;
|
||||
}
|
||||
if (m_multiplayer) {
|
||||
m_multiplayer->detachGame(this);
|
||||
}
|
||||
|
||||
if (mCoreThreadIsPaused(&m_threadContext)) {
|
||||
mCoreThreadUnpause(&m_threadContext);
|
||||
|
@ -926,10 +939,13 @@ void GameController::setTurbo(bool set, bool forced) {
|
|||
if (m_turboForced && !forced) {
|
||||
return;
|
||||
}
|
||||
if (m_turbo == set && m_turboForced == forced) {
|
||||
if (m_turbo == set && m_turboForced == (set && forced)) {
|
||||
// Don't interrupt the thread if we don't need to
|
||||
return;
|
||||
}
|
||||
if (!m_sync) {
|
||||
return;
|
||||
}
|
||||
m_turbo = set;
|
||||
m_turboForced = set && forced;
|
||||
enableTurbo();
|
||||
|
@ -942,25 +958,41 @@ void GameController::setTurboSpeed(float ratio) {
|
|||
|
||||
void GameController::enableTurbo() {
|
||||
threadInterrupt();
|
||||
bool shouldRedoSamples = false;
|
||||
if (!m_turbo) {
|
||||
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
|
||||
m_threadContext.sync.fpsTarget = m_fpsTarget;
|
||||
m_threadContext.sync.audioWait = m_audioSync;
|
||||
m_threadContext.sync.videoFrameWait = m_videoSync;
|
||||
} else if (m_turboSpeed <= 0) {
|
||||
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
|
||||
m_threadContext.sync.fpsTarget = m_fpsTarget;
|
||||
m_threadContext.sync.audioWait = false;
|
||||
m_threadContext.sync.videoFrameWait = false;
|
||||
} else {
|
||||
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget * m_turboSpeed;
|
||||
m_threadContext.sync.fpsTarget = m_fpsTarget * m_turboSpeed;
|
||||
m_threadContext.sync.audioWait = true;
|
||||
m_threadContext.sync.videoFrameWait = false;
|
||||
}
|
||||
if (m_audioProcessor) {
|
||||
if (m_audioProcessor && shouldRedoSamples) {
|
||||
redoSamples(m_audioProcessor->getBufferSamples());
|
||||
}
|
||||
threadContinue();
|
||||
}
|
||||
|
||||
void GameController::setSync(bool enable) {
|
||||
m_turbo = false;
|
||||
m_turboForced = false;
|
||||
if (!enable) {
|
||||
m_threadContext.sync.audioWait = false;
|
||||
m_threadContext.sync.videoFrameWait = false;
|
||||
} else {
|
||||
m_threadContext.sync.audioWait = m_audioSync;
|
||||
m_threadContext.sync.videoFrameWait = m_videoSync;
|
||||
}
|
||||
m_sync = enable;
|
||||
}
|
||||
void GameController::setAVStream(mAVStream* stream) {
|
||||
threadInterrupt();
|
||||
m_stream = stream;
|
||||
|
|
|
@ -135,7 +135,8 @@ public slots:
|
|||
void loadBackupState();
|
||||
void saveBackupState();
|
||||
void setTurbo(bool, bool forced = true);
|
||||
void setTurboSpeed(float ratio = -1);
|
||||
void setTurboSpeed(float ratio);
|
||||
void setSync(bool);
|
||||
void setAVStream(mAVStream*);
|
||||
void clearAVStream();
|
||||
void reloadAudioDriver();
|
||||
|
@ -197,6 +198,7 @@ private:
|
|||
QAtomicInt m_pauseAfterFrame;
|
||||
QList<std::function<void ()>> m_resetActions;
|
||||
|
||||
bool m_sync;
|
||||
bool m_videoSync;
|
||||
bool m_audioSync;
|
||||
float m_fpsTarget;
|
||||
|
|
|
@ -7,10 +7,111 @@
|
|||
|
||||
#include "GameController.h"
|
||||
|
||||
extern "C" {
|
||||
#ifdef M_CORE_GBA
|
||||
#include "gba/gba.h"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
MultiplayerController::MultiplayerController() {
|
||||
GBASIOLockstepInit(&m_lockstep);
|
||||
m_lockstep.context = this;
|
||||
m_lockstep.signal = [](GBASIOLockstep* lockstep, unsigned mask) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
Player* player = &controller->m_players[0];
|
||||
bool woke = false;
|
||||
controller->m_lock.lock();
|
||||
player->waitMask &= ~mask;
|
||||
if (!player->waitMask && player->awake < 1) {
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
woke = true;
|
||||
}
|
||||
controller->m_lock.unlock();
|
||||
return woke;
|
||||
};
|
||||
m_lockstep.wait = [](GBASIOLockstep* lockstep, unsigned mask) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[0];
|
||||
bool slept = false;
|
||||
player->waitMask |= mask;
|
||||
if (player->awake > 0) {
|
||||
mCoreThreadWaitFromThread(player->controller->thread());
|
||||
player->awake = 0;
|
||||
slept = true;
|
||||
}
|
||||
controller->m_lock.unlock();
|
||||
return slept;
|
||||
};
|
||||
m_lockstep.addCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
|
||||
if (cycles < 0) {
|
||||
abort();
|
||||
}
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
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) {
|
||||
player->controller->setSync(true);
|
||||
continue;
|
||||
}
|
||||
player->controller->setSync(false);
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
player->node->nextEvent += player->cyclesPosted;
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
controller->m_players[id].controller->setSync(true);
|
||||
controller->m_players[id].cyclesPosted += cycles;
|
||||
}
|
||||
controller->m_lock.unlock();
|
||||
};
|
||||
m_lockstep.useCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[id];
|
||||
player->cyclesPosted -= cycles;
|
||||
if (player->cyclesPosted <= 0) {
|
||||
mCoreThreadWaitFromThread(player->controller->thread());
|
||||
player->awake = 0;
|
||||
}
|
||||
cycles = player->cyclesPosted;
|
||||
controller->m_lock.unlock();
|
||||
return cycles;
|
||||
};
|
||||
m_lockstep.unload = [](GBASIOLockstep* lockstep, int id) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
Player* player = &controller->m_players[id];
|
||||
if (id) {
|
||||
player->controller->setSync(true);
|
||||
player->waitMask &= ~(1 << id);
|
||||
if (!player->waitMask && player->awake < 1) {
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
if (player->awake < 1) {
|
||||
player->node->nextEvent += player->cyclesPosted;
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
controller->m_lock.unlock();
|
||||
};
|
||||
}
|
||||
|
||||
MultiplayerController::~MultiplayerController() {
|
||||
|
@ -18,74 +119,78 @@ MultiplayerController::~MultiplayerController() {
|
|||
}
|
||||
|
||||
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();
|
||||
mCoreThread* thread = controller->thread();
|
||||
/*if (controller->isLoaded()) {
|
||||
GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI);
|
||||
GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_NORMAL_32);
|
||||
if (!thread) {
|
||||
return false;
|
||||
}
|
||||
thread->sioDrivers.multiplayer = &node->d;
|
||||
thread->sioDrivers.normal = &node->d;*/
|
||||
controller->threadContinue();
|
||||
emit gameAttached();
|
||||
return true;
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
if (controller->platform() == PLATFORM_GBA) {
|
||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||
|
||||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||
GBASIOLockstepNodeCreate(node);
|
||||
GBASIOLockstepAttachNode(&m_lockstep, node);
|
||||
m_players.append({
|
||||
controller,
|
||||
node,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
});
|
||||
|
||||
GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI);
|
||||
GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32);
|
||||
|
||||
emit gameAttached();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiplayerController::detachGame(GameController* controller) {
|
||||
controller->threadInterrupt();
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
mCoreThread* thread = nullptr;
|
||||
/*for (int i = 0; i < m_lockstep.attached; ++i) {
|
||||
thread = controller->thread();
|
||||
if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) {
|
||||
mCoreThread* thread = controller->thread();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
#ifdef M_CORE_GBA
|
||||
if (controller->platform() == 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);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, SIO_NORMAL_32);
|
||||
if (node) {
|
||||
GBASIOLockstepDetachNode(&m_lockstep, node);
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
if (m_players[i].controller == controller) {
|
||||
m_players.removeAt(i);
|
||||
break;
|
||||
}
|
||||
thread = nullptr;
|
||||
}
|
||||
if (thread) {
|
||||
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(thread->sioDrivers.multiplayer);
|
||||
if (controller->isLoaded()) {
|
||||
GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_MULTI);
|
||||
GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_NORMAL_32);
|
||||
}
|
||||
thread->sioDrivers.multiplayer = nullptr;
|
||||
thread->sioDrivers.normal = nullptr;
|
||||
GBASIOLockstepDetachNode(&m_lockstep, node);
|
||||
delete node;
|
||||
}*/
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
controller->threadContinue();
|
||||
emit gameDetached();
|
||||
}
|
||||
|
||||
int MultiplayerController::playerId(GameController* controller) {
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
int id = -1;
|
||||
for (int i = 0; i < m_lockstep.attached; ++i) {
|
||||
mCoreThread* thread = controller->thread();
|
||||
/*if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) {
|
||||
id = i;
|
||||
break;
|
||||
}*/
|
||||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
if (m_players[i].controller == controller) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
return id;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MultiplayerController::attached() {
|
||||
int num;
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
num = m_lockstep.attached;
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
return num;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#ifndef QGBA_MULTIPLAYER_CONTROLLER
|
||||
#define QGBA_MULTIPLAYER_CONTROLLER
|
||||
|
||||
#include <QMutex>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
|
||||
extern "C" {
|
||||
|
@ -34,7 +36,16 @@ signals:
|
|||
void gameDetached();
|
||||
|
||||
private:
|
||||
struct Player {
|
||||
GameController* controller;
|
||||
GBASIOLockstepNode* node;
|
||||
int awake;
|
||||
int32_t cyclesPosted;
|
||||
unsigned waitMask;
|
||||
};
|
||||
GBASIOLockstep m_lockstep;
|
||||
QList<Player> m_players;
|
||||
QMutex m_lock;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -52,6 +52,23 @@ typedef intptr_t ssize_t;
|
|||
#define M_PI 3.141592654f
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE)
|
||||
#define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE)
|
||||
#define ATOMIC_ADD(DST, OP) __atomic_add_fetch(&DST, OP, __ATOMIC_RELEASE)
|
||||
#define ATOMIC_OR(DST, OP) __atomic_or_fetch(&DST, OP, __ATOMIC_RELEASE)
|
||||
#define ATOMIC_AND(DST, OP) __atomic_and_fetch(&DST, OP, __ATOMIC_RELEASE)
|
||||
#define ATOMIC_CMPXCHG(DST, EXPECTED, SRC) __atomic_compare_exchange_n(&DST, &EXPECTED, SRC, true,__ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)
|
||||
#else
|
||||
// TODO
|
||||
#define ATOMIC_STORE(DST, SRC) DST = SRC
|
||||
#define ATOMIC_LOAD(DST, SRC) DST = SRC
|
||||
#define ATOMIC_ADD(DST, OP) DST += OP
|
||||
#define ATOMIC_OR(DST, OP) DST |= OP
|
||||
#define ATOMIC_AND(DST, OP) DST &= OP
|
||||
#define ATOMIC_CMPXCHG(DST, EXPECTED, OP) ((DST == EXPECTED) ? ((DST = OP), true) : false)
|
||||
#endif
|
||||
|
||||
#if defined(__PPC__) || defined(__POWERPC__)
|
||||
#define LOAD_32LE(DEST, ADDR, ARR) { \
|
||||
uint32_t _addr = (ADDR); \
|
||||
|
|
|
@ -7,15 +7,6 @@
|
|||
|
||||
#include "util/memory.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE)
|
||||
#define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE)
|
||||
#else
|
||||
// TODO
|
||||
#define ATOMIC_STORE(DST, SRC) DST = SRC
|
||||
#define ATOMIC_LOAD(DST, SRC) DST = SRC
|
||||
#endif
|
||||
|
||||
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity) {
|
||||
buffer->data = anonymousMemoryMap(capacity);
|
||||
buffer->capacity = capacity;
|
||||
|
|
Loading…
Reference in New Issue