Merge branch 'feature/multiplayer-rewrite'
jump to
@@ -47,7 +47,7 @@ MutexUnlock(&threadContext->stateMutex);
} 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 @@ threadContext->sync.videoFrameWait = videoFrameWait;
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 @@ if (threadContext->state == THREAD_RESETING) {
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 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 @@ bool frameOn = threadContext->sync.videoFrameOn;
MutexLock(&threadContext->stateMutex); _waitOnInterrupt(threadContext); if (threadContext->state == THREAD_RUNNING) { - _pauseThread(threadContext, false); + _pauseThread(threadContext); threadContext->frameWasOn = frameOn; frameOn = false; }@@ -391,7 +389,7 @@
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 @@ threadContext->state = THREAD_RUNNING;
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 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);@@ -438,6 +436,27 @@ threadContext->state = THREAD_REWINDING;
} if (!rewinding && threadContext->state == THREAD_REWINDING) { threadContext->state = THREAD_RUNNING; + } + 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); }
@@ -28,6 +28,7 @@ THREAD_INTERRUPTING,
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 @@ #endif
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; }@@ -262,6 +274,9 @@ cpu->nextEvent = nextEvent;
if (cpu->halted) { cpu->cycles = cpu->nextEvent; + } + if (cpu->nextEvent == 0) { + break; } } while (cpu->cycles >= cpu->nextEvent); }
@@ -32,16 +32,17 @@ }
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 @@ mLOG(GBA_SIO, ERROR, "Could not initialize SIO driver");
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 "lockstep.h"
#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 @@ lockstep->multiRecv[1] = 0xFFFF;
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 @@ node->d.deinit = GBASIOLockstepNodeDeinit;
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 @@ }
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 @@ }
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 @@ }
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, uint32_t address, uint16_t value) {
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 @@ }
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); +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); + } + 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 { - 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); + node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; } - 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); + 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 @@ struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
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 @@ node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
} 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 @@ mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
} 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 @@ #define SIO_LOCKSTEP_H
#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 @@ , m_useBios(false)
, 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 @@ mCoreDeleteState(context->core, 0);
} 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 @@ return;
} 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() {@@ -521,6 +531,9 @@
void GameController::closeGame() { if (!m_gameOpen) { return; + } + if (m_multiplayer) { + m_multiplayer->detachGame(this); } if (mCoreThreadIsPaused(&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::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 @@ void saveState(int slot = 0);
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 @@
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 "MultiplayerController.h"
#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 @@ 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(); 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 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_MULTIPLAYER_CONTROLLER #define QGBA_MULTIPLAYER_CONTROLLER +#include <QMutex> +#include <QList> #include <QObject> extern "C" {@@ -34,7 +36,16 @@ void gameAttached();
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 @@ #ifndef M_PI
#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 "ring-fifo.h"
#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;