GBA SIO: Simplify lockstep design
@@ -11,36 +11,26 @@ #include "gba/video.h"
#define LOCKSTEP_INCREMENT 3000 -static bool _nodeWait(struct GBASIOLockstepNode* node, uint32_t mask) { +static bool _waitMaster(struct GBASIOLockstepNode* node, uint32_t mask) { uint32_t oldMask = 0; - if (ATOMIC_CMPXCHG(node->p->waiting[node->id], oldMask, mask | 0x10)) { - node->p->wait(node->p, node->id); + if (ATOMIC_CMPXCHG(node->p->masterWaiting, oldMask, mask | 0x10)) { + node->p->wait(node->p, 0); } return true; } -static bool _nodeSignal(struct GBASIOLockstepNode* node, uint32_t mask) { - bool eventTriggered = false; - int i; - for (i = 0; i < node->p->attached; ++i) { - uint32_t waiting = ATOMIC_AND(node->p->waiting[i], ~mask); - if (waiting == 0x10) { - if (!ATOMIC_CMPXCHG(node->p->waiting[i], waiting, 0)) { - return false; - } - node->p->signal(node->p, i); - eventTriggered = true; +static bool _signalMaster(struct GBASIOLockstepNode* node, uint32_t mask) { + uint32_t waiting = ATOMIC_AND(node->p->masterWaiting, ~mask); + if (waiting == 0x10) { + if (!ATOMIC_CMPXCHG(node->p->masterWaiting, waiting, 0)) { + return false; } - } - if (!eventTriggered) { - ATOMIC_STORE(node->p->waitMask, mask); - } else { - ATOMIC_STORE(node->p->waitMask, 0); + node->p->signal(node->p, 0); + return true; } - return eventTriggered; + return false; } - static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);@@ -63,7 +53,7 @@ lockstep->multiRecv[3] = 0xFFFF;
lockstep->attached = 0; lockstep->attachedMulti = 0; lockstep->transferActive = 0; - memset(lockstep->waiting, 0, sizeof(lockstep->waiting)); + lockstep->masterWaiting = 0; #ifndef NDEBUG lockstep->transferId = 0; #endif@@ -113,7 +103,6 @@ }
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->needsToWait = false; node->d.p->multiplayerControl.slave = node->id > 0; mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); return true;@@ -162,7 +151,16 @@ break;
default: break; } - _nodeSignal(node, (1 << node->id) ^ 0xF); + if (node->id) { + _signalMaster(node, 1 << node->id); + } + int i; + for (i = 0; i < node->p->attached; ++i) { + if (i == node->id) { + continue; + } + node->p->signal(node->p, i); + } return true; }@@ -223,8 +221,8 @@ ++node->transferId;
#endif } -static void _masterUpdate(struct GBASIOLockstepNode* node) { - ATOMIC_STORE(node->needsToWait, false); +static int32_t _masterUpdate(struct GBASIOLockstepNode* node) { + bool needsToWait = false; int i; switch (node->p->transferActive) { case TRANSFER_IDLE:@@ -239,7 +237,7 @@ 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); + needsToWait = true; ATOMIC_STORE(node->p->transferActive, TRANSFER_STARTED); node->nextEvent += 512; break;@@ -256,7 +254,7 @@ 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); + needsToWait = true; ATOMIC_STORE(node->p->transferActive, TRANSFER_FINISHED); break; case TRANSFER_FINISHED:@@ -266,7 +264,7 @@ node->nextEvent += LOCKSTEP_INCREMENT;
ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE); break; } - if (node->needsToWait) { + if (needsToWait) { int mask = 0; for (i = 1; i < node->p->attached; ++i) { if (node->p->players[i]->mode == node->mode) {@@ -274,7 +272,7 @@ mask |= 1 << i;
} } if (mask) { - if (!_nodeWait(node, mask)) { + if (!_waitMaster(node, mask)) { #ifndef NDEBUG abort(); #endif@@ -284,24 +282,24 @@ }
// 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); + node->p->signal(node->p, i); } #ifndef NDEBUG node->phase = node->p->transferActive; #endif - _nodeSignal(node, 1); + if (needsToWait) { + return 0; + } + return node->nextEvent; } -static void _slaveUpdate(struct GBASIOLockstepNode* node) { - ATOMIC_STORE(node->needsToWait, true); +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->nextEvent += LOCKSTEP_INCREMENT; - ATOMIC_STORE(node->needsToWait, false); - return; + return ATOMIC_ADD(node->nextEvent, LOCKSTEP_INCREMENT); } break; case TRANSFER_STARTING:@@ -330,13 +328,21 @@ _finishTransfer(node);
signal = true; break; } - _nodeWait(node, 1); + int32_t cycles; + ATOMIC_LOAD(cycles, node->nextEvent); + if (cycles <= 0) { + node->p->wait(node->p, node->id); + } #ifndef NDEBUG node->phase = node->p->transferActive; #endif if (signal) { - _nodeSignal(node, 1 << node->id); + _signalMaster(node, 1 << node->id); + if (cycles > 0) { + return cycles; + } } + return 0; } static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {@@ -348,31 +354,14 @@ node->eventDiff += cycles;
cycles = ATOMIC_ADD(node->nextEvent, -cycles); if (cycles <= 0) { if (!node->id) { - _masterUpdate(node); + cycles = _masterUpdate(node); } else { - _slaveUpdate(node); + cycles = _slaveUpdate(node); } node->eventDiff = 0; - bool needsToWait; - ATOMIC_LOAD(needsToWait, node->needsToWait); - if (needsToWait) { + if (cycles <= 0) { return 0; } - ATOMIC_LOAD(cycles, node->nextEvent); - if (cycles <= 0 && !needsToWait) { - int i; - // XXX: If we're multithreaded, the atomics may not be sufficient, so wait a few usecs - for (i = 0; i < 0x40; ++i) { - usleep(10); - ATOMIC_LOAD(cycles, node->nextEvent); - if (cycles > 0) { - return cycles; - } - } - abort(); - mLOG(GBA_SIO, WARN, "Sleeping needlessly"); - } - return cycles; } return cycles; }
@@ -25,8 +25,7 @@ uint16_t multiRecv[MAX_GBAS];
enum GBASIOLockstepPhase transferActive; int32_t transferCycles; - uint32_t waitMask; - uint32_t waiting[MAX_GBAS]; + uint32_t masterWaiting; void (*signal)(struct GBASIOLockstep*, int id); void (*wait)(struct GBASIOLockstep*, int id);@@ -40,10 +39,9 @@ struct GBASIOLockstepNode {
struct GBASIODriver d; struct GBASIOLockstep* p; - int32_t nextEvent; + volatile int32_t nextEvent; int32_t eventDiff; bool normalSO; - bool needsToWait; int id; enum GBASIOMode mode; bool transferFinished;
@@ -23,23 +23,16 @@ 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_awake[id]; + 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(); + controller->m_awake[id] = 0; 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(); - } + mCoreThreadWaitFromThread(game->thread()); controller->m_lock.unlock(); }; }@@ -66,7 +59,7 @@ GBASIOLockstepNode* node = new GBASIOLockstepNode;
GBASIOLockstepNodeCreate(node); GBASIOLockstepAttachNode(&m_lockstep, node); m_players.append(controller); - m_asleep.append(0); + m_awake.append(1); GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI); GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32);
@@ -38,7 +38,7 @@
private: GBASIOLockstep m_lockstep; QList<GameController*> m_players; - QList<int> m_asleep; + QList<int> m_awake; QMutex m_lock; };