mirror of https://github.com/mgba-emu/mgba.git
GBA SIO: Fix freezing issues
This commit is contained in:
parent
b0a9a595ef
commit
08b78cb468
|
@ -11,27 +11,6 @@
|
|||
|
||||
#define LOCKSTEP_INCREMENT 3000
|
||||
|
||||
static bool _waitMaster(struct GBASIOLockstepNode* node, uint32_t mask) {
|
||||
uint32_t oldMask = 0;
|
||||
if (ATOMIC_CMPXCHG(node->p->masterWaiting, oldMask, mask | 0x10)) {
|
||||
node->p->wait(node->p, 0);
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
|
||||
static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
|
||||
|
@ -53,7 +32,6 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
|||
lockstep->attached = 0;
|
||||
lockstep->attachedMulti = 0;
|
||||
lockstep->transferActive = 0;
|
||||
lockstep->masterWaiting = 0;
|
||||
#ifndef NDEBUG
|
||||
lockstep->transferId = 0;
|
||||
#endif
|
||||
|
@ -152,14 +130,9 @@ bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
|||
break;
|
||||
}
|
||||
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);
|
||||
node->p->signal(node->p, 1 << node->id);
|
||||
} else {
|
||||
node->p->addCycles(node->p, 0, node->eventDiff);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -284,26 +257,23 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
ATOMIC_STORE(node->p->transferActive, TRANSFER_IDLE);
|
||||
break;
|
||||
}
|
||||
if (needsToWait) {
|
||||
int mask = 0;
|
||||
for (i = 1; i < node->p->attached; ++i) {
|
||||
if (node->p->players[i]->mode == node->mode) {
|
||||
mask |= 1 << i;
|
||||
}
|
||||
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 (!_waitMaster(node, mask)) {
|
||||
#ifndef NDEBUG
|
||||
}
|
||||
if (mask) {
|
||||
if (needsToWait) {
|
||||
if (!node->p->wait(node->p, mask)) {
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
node->p->signal(node->p, 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);
|
||||
node->p->signal(node->p, i);
|
||||
}
|
||||
node->p->addCycles(node->p, 0, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->transferActive;
|
||||
#endif
|
||||
|
@ -319,7 +289,7 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
|||
switch (node->p->transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
if (!node->d.p->multiplayerControl.ready) {
|
||||
return ATOMIC_ADD(node->nextEvent, LOCKSTEP_INCREMENT);
|
||||
node->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
|
||||
}
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
|
@ -357,19 +327,11 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
|||
signal = true;
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
_signalMaster(node, 1 << node->id);
|
||||
if (cycles > 0) {
|
||||
return cycles;
|
||||
}
|
||||
node->p->signal(node->p, 1 << node->id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -380,17 +342,20 @@ static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int3
|
|||
return INT_MAX;
|
||||
}
|
||||
node->eventDiff += cycles;
|
||||
cycles = ATOMIC_ADD(node->nextEvent, -cycles);
|
||||
if (cycles <= 0) {
|
||||
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;
|
||||
if (cycles <= 0) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
cycles = node->nextEvent;
|
||||
}
|
||||
if (cycles < 0) {
|
||||
return 0;
|
||||
}
|
||||
return cycles;
|
||||
}
|
||||
|
|
|
@ -27,10 +27,10 @@ struct GBASIOLockstep {
|
|||
enum GBASIOLockstepPhase transferActive;
|
||||
int32_t transferCycles;
|
||||
|
||||
uint32_t masterWaiting;
|
||||
|
||||
void (*signal)(struct GBASIOLockstep*, int id);
|
||||
void (*wait)(struct GBASIOLockstep*, int id);
|
||||
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* context;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
|
|
|
@ -19,21 +19,70 @@ using namespace QGBA;
|
|||
MultiplayerController::MultiplayerController() {
|
||||
GBASIOLockstepInit(&m_lockstep);
|
||||
m_lockstep.context = this;
|
||||
m_lockstep.signal = [](GBASIOLockstep* lockstep, int id) {
|
||||
m_lockstep.signal = [](GBASIOLockstep* lockstep, unsigned mask) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
GameController* game = controller->m_players[id];
|
||||
Player* player = &controller->m_players[0];
|
||||
bool woke = false;
|
||||
controller->m_lock.lock();
|
||||
++controller->m_awake[id];
|
||||
mCoreThreadStopWaiting(game->thread());
|
||||
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->mode != controller->m_players[0].node->mode) {
|
||||
continue;
|
||||
}
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
player->node->nextEvent += player->cyclesPosted;
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
controller->m_players[id].cyclesPosted += cycles;
|
||||
}
|
||||
controller->m_lock.unlock();
|
||||
};
|
||||
m_lockstep.wait = [](GBASIOLockstep* lockstep, int id) {
|
||||
m_lockstep.useCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
controller->m_lock.lock();
|
||||
controller->m_awake[id] = 0;
|
||||
GameController* game = controller->m_players[id];
|
||||
mCoreThreadWaitFromThread(game->thread());
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -58,8 +107,13 @@ bool MultiplayerController::attachGame(GameController* controller) {
|
|||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||
GBASIOLockstepNodeCreate(node);
|
||||
GBASIOLockstepAttachNode(&m_lockstep, node);
|
||||
m_players.append(controller);
|
||||
m_awake.append(1);
|
||||
m_players.append({
|
||||
controller,
|
||||
node,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
});
|
||||
|
||||
GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI);
|
||||
GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32);
|
||||
|
@ -73,9 +127,6 @@ bool MultiplayerController::attachGame(GameController* controller) {
|
|||
}
|
||||
|
||||
void MultiplayerController::detachGame(GameController* controller) {
|
||||
if (!m_players.contains(controller)) {
|
||||
return;
|
||||
}
|
||||
mCoreThread* thread = controller->thread();
|
||||
if (!thread) {
|
||||
return;
|
||||
|
@ -92,14 +143,22 @@ void MultiplayerController::detachGame(GameController* controller) {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
int i = m_players.indexOf(controller);
|
||||
m_players.removeAt(i);
|
||||
m_players.removeAt(i);
|
||||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
if (m_players[i].controller == controller) {
|
||||
m_players.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit gameDetached();
|
||||
}
|
||||
|
||||
int MultiplayerController::playerId(GameController* controller) {
|
||||
return m_players.indexOf(controller);
|
||||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
if (m_players[i].controller == controller) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MultiplayerController::attached() {
|
||||
|
|
|
@ -36,9 +36,15 @@ signals:
|
|||
void gameDetached();
|
||||
|
||||
private:
|
||||
struct Player {
|
||||
GameController* controller;
|
||||
GBASIOLockstepNode* node;
|
||||
int awake;
|
||||
int32_t cyclesPosted;
|
||||
unsigned waitMask;
|
||||
};
|
||||
GBASIOLockstep m_lockstep;
|
||||
QList<GameController*> m_players;
|
||||
QList<int> m_awake;
|
||||
QList<Player> m_players;
|
||||
QMutex m_lock;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue