mirror of https://github.com/mgba-emu/mgba.git
GBA SIO: Simplify lockstep design
This commit is contained in:
parent
c9ede88332
commit
000e965b74
|
@ -11,37 +11,27 @@
|
|||
|
||||
#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;
|
||||
}
|
||||
node->p->signal(node->p, 0);
|
||||
return true;
|
||||
}
|
||||
if (!eventTriggered) {
|
||||
ATOMIC_STORE(node->p->waitMask, mask);
|
||||
} else {
|
||||
ATOMIC_STORE(node->p->waitMask, 0);
|
||||
}
|
||||
return eventTriggered;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
|
||||
static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
|
||||
|
@ -63,7 +53,7 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
|||
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 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
|
|||
|
||||
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 @@ bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
|||
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 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
|
|||
#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 @@ static void _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
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 @@ static void _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
#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 @@ static void _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
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 @@ static void _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
}
|
||||
}
|
||||
if (mask) {
|
||||
if (!_nodeWait(node, mask)) {
|
||||
if (!_waitMaster(node, mask)) {
|
||||
#ifndef NDEBUG
|
||||
abort();
|
||||
#endif
|
||||
|
@ -284,24 +282,24 @@ static void _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
// 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 @@ static void _slaveUpdate(struct GBASIOLockstepNode* 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 @@ static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int3
|
|||
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 @@ struct GBASIOLockstep {
|
|||
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 @@ MultiplayerController::MultiplayerController() {
|
|||
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 @@ bool MultiplayerController::attachGame(GameController* controller) {
|
|||
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 @@ signals:
|
|||
private:
|
||||
GBASIOLockstep m_lockstep;
|
||||
QList<GameController*> m_players;
|
||||
QList<int> m_asleep;
|
||||
QList<int> m_awake;
|
||||
QMutex m_lock;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue