GBA SIO: Incomplete draft of multiplayer rewrite
@@ -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,56 @@ #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 _nodeWait(struct GBASIOLockstepNode* node, uint32_t mask) { + uint32_t oldMask = 0; + if (ATOMIC_CMPXCHG(node->p->waiting[node->id], oldMask, mask)) { + node->p->wait(node->p, node->id); + } +#ifndef NDEBUG + else if (oldMask != mask) { + abort(); + } +#endif + else if ((node->p->waiting[node->id] & oldMask) == node->p->waiting[node->id]) { + ATOMIC_AND(node->p->waitMask, ~mask); + return false; + } + + return true; +} + +static bool _nodeSignal(struct GBASIOLockstepNode* node, uint32_t mask) { + mask = ATOMIC_OR(node->p->waitMask, mask); + bool eventTriggered = false; + int i; + for (i = 0; i < node->p->attached; ++i) { + uint32_t waiting = node->p->waiting[i]; + if (waiting && (waiting & mask) == waiting && ATOMIC_CMPXCHG(node->p->waiting[i], waiting, 0)) { + node->p->signal(node->p, i); + eventTriggered = true; + } + } + if (eventTriggered) { + ATOMIC_STORE(node->p->waitMask, 0); + } else { + mLOG(GBA_SIO, WARN, "Nothing woke with mask %X", mask); + } + return eventTriggered; +} + 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 +68,17 @@ 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; + memset(lockstep->waiting, 0, sizeof(lockstep->waiting)); + lockstep->waitMask = 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 +87,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 +121,7 @@ }
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->nextEvent = LOCKSTEP_INCREMENT; + node->needsToWait = false; node->d.p->multiplayerControl.slave = node->id > 0; mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); return true;@@ -95,15 +133,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 +149,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); + _nodeSignal(node, (1 << node->id) ^ 0xF); return true; }@@ -143,14 +178,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 +196,202 @@ }
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: + case SIO_NORMAL_32: + // TODO + sio->normalControl.start = 0; + if (sio->multiplayerControl.irq) { + GBARaiseIRQ(sio->p, IRQ_SIO); + } + break; + default: + break; + } + node->transferFinished = true; +#ifndef NDEBUG + ++node->transferId; +#endif +} + +static void _masterUpdate(struct GBASIOLockstepNode* node) { + ATOMIC_STORE(node->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; + ATOMIC_STORE(node->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 +#ifndef NDEBUG + /*for (i = 1; i < node->p->attached; ++i) { + enum GBASIOLockstepPhase phase; + ATOMIC_LOAD(phase, node->p->players[i]->phase); + if (node->p->players[i]->mode == node->mode && phase != TRANSFER_STARTED) { + 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; + }*/ +#endif + 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 + ATOMIC_STORE(node->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; + } + if (node->needsToWait) { + int mask = 0; + for (i = 1; i < node->p->attached; ++i) { + if (node->p->players[i]->mode == node->mode) { + mask |= 1 << i; + } + } + if (mask) { + _nodeWait(node, mask); + } + } + // Tell the other GBAs they can continue up to where we were + for (i = 1; i < node->p->attached; ++i) { + ATOMIC_ADD(node->p->players[i]->nextEvent, node->eventDiff); + ATOMIC_STORE(node->p->players[i]->needsToWait, false); + } +#ifndef NDEBUG + node->phase = node->p->transferActive; +#endif + _nodeSignal(node, 1); +} + +static void _slaveUpdate(struct GBASIOLockstepNode* node) { + ATOMIC_STORE(node->needsToWait, true); + node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->attached; +#ifndef NDEBUG + if (node->phase >= TRANSFER_STARTED && node->phase != TRANSFER_FINISHED && node->phase != node->p->transferActive && node->p->transferActive < TRANSFER_FINISHING) { + //abort(); + } + if (node->phase < TRANSFER_FINISHED && node->phase != TRANSFER_IDLE && node->p->transferActive == TRANSFER_IDLE) { + //abort(); + } +#endif + bool signal = false; + switch (node->p->transferActive) { + case TRANSFER_IDLE: + if (!node->d.p->multiplayerControl.ready) { + node->nextEvent += LOCKSTEP_INCREMENT; + ATOMIC_STORE(node->needsToWait, false); + return; + } + break; + case TRANSFER_STARTING: + case TRANSFER_FINISHING: + break; + case TRANSFER_STARTED: +#ifndef NDEBUG + if (node->transferId != node->p->transferId) { + //abort(); + } +#endif + 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; + default: + node->p->multiRecv[node->id]= 0xFFFF; + break; + } + signal = true; + break; + case TRANSFER_FINISHED: + _finishTransfer(node); + signal = true; + break; + } + _nodeWait(node, 1); +#ifndef NDEBUG + node->phase = node->p->transferActive; +#endif + if (signal) { + _nodeSignal(node, 1 << node->id); + } +} + +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; + cycles = ATOMIC_ADD(node->nextEvent, -cycles); + if (cycles <= 0) { + if (!node->id) { + _masterUpdate(node); + } else { + _slaveUpdate(node); + } + node->eventDiff = 0; + if (node->needsToWait) { + return 0; } - node->d.p->multiplayerControl.ready = node->p->loadedMulti == node->p->attached; - node->nextEvent += node->p->nextEvent; - MutexUnlock(&node->p->mutex); + ATOMIC_LOAD(cycles, node->nextEvent); +#ifndef NDEBUG + if (cycles <= 0 && !node->needsToWait) { + abort(); + mLOG(GBA_SIO, WARN, "Sleeping needlessly"); + } +#endif + return cycles; } - return node->nextEvent; + return cycles; } static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {@@ -231,11 +399,10 @@ 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) { // Internal shift clock if (value & 1) { - node->p->transferActive = true; + node->p->transferActive = TRANSFER_STARTING; } // Frequency if (value & 2) {@@ -249,7 +416,6 @@ 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 +423,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,28 +8,32 @@ #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; uint16_t multiRecv[MAX_GBAS]; - bool transferActive; + enum GBASIOLockstepPhase transferActive; int32_t transferCycles; - int32_t nextEvent; + + uint32_t waitMask; + uint32_t waiting[MAX_GBAS]; - int waiting; - Mutex mutex; - Condition barrier; + void (*signal)(struct GBASIOLockstep*, int id); + void (*wait)(struct GBASIOLockstep*, int id); + void* context; +#ifndef NDEBUG + int transferId; +#endif }; struct GBASIOLockstepNode {@@ -37,11 +41,16 @@ struct GBASIODriver d;
struct GBASIOLockstep* p; int32_t nextEvent; - uint16_t multiSend; + int32_t eventDiff; bool normalSO; - enum LockstepState state; + bool needsToWait; int id; enum GBASIOMode mode; + bool transferFinished; +#ifndef NDEBUG + int transferId; + enum GBASIOLockstepPhase phase; +#endif }; void GBASIOLockstepInit(struct GBASIOLockstep*);
@@ -130,6 +130,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 +260,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 +530,9 @@
void GameController::closeGame() { if (!m_gameOpen) { return; + } + if (m_multiplayer) { + m_multiplayer->detachGame(this); } if (mCoreThreadIsPaused(&m_threadContext)) {
@@ -7,10 +7,41 @@ #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, int id) { + MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); + GameController* game = controller->m_players[id]; + controller->m_lock.lock(); + if (--controller->m_asleep[id] == 0) { + mCoreThreadStopWaiting(game->thread()); + } + controller->m_lock.unlock(); + }; + m_lockstep.wait = [](GBASIOLockstep* lockstep, int id) { + MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context); + controller->m_lock.lock(); + GameController* game = controller->m_players[id]; + if (++controller->m_asleep[id] == 1) { + mCoreThreadWaitFromThread(game->thread()); + } else if (controller->m_asleep[id] == 0) { + mCoreThreadStopWaiting(game->thread()); + } + if (controller->m_asleep[id] > 1) { + //abort(); + } + controller->m_lock.unlock(); + }; } MultiplayerController::~MultiplayerController() {@@ -18,74 +49,68 @@ 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; + } + +#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); + m_asleep.append(0); + + GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI); + GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32); + + emit gameAttached(); + return true; } - thread->sioDrivers.multiplayer = &node->d; - thread->sioDrivers.normal = &node->d;*/ - controller->threadContinue(); - 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) { - break; + if (!m_players.contains(controller)) { + return; + } + 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; } - 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(); +#endif + int i = m_players.indexOf(controller); + m_players.removeAt(i); + m_players.removeAt(i); 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; - }*/ - } - MutexUnlock(&m_lockstep.mutex); - return id; + return m_players.indexOf(controller); } 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" {@@ -35,6 +37,9 @@ void gameDetached();
private: GBASIOLockstep m_lockstep; + QList<GameController*> m_players; + QList<int> m_asleep; + 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;