GBA SIO: Incomplete draft of multiplayer rewrite

This commit is contained in:
Jeffrey Pfau 2016-07-20 01:45:21 -07:00
parent 6519c4e465
commit 2ddc08e8b4
11 changed files with 448 additions and 233 deletions

View File

@ -47,7 +47,7 @@ static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadStat
}
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 @@ static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThre
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 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
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 mCoreThreadEnd(struct mCoreThread* threadContext) {
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 @@ void mCoreThreadPause(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext, false);
_pauseThread(threadContext);
threadContext->frameWasOn = frameOn;
frameOn = false;
}
@ -391,7 +389,7 @@ void mCoreThreadUnpause(struct mCoreThread* threadContext) {
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 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
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 mCoreThreadTogglePause(struct mCoreThread* threadContext) {
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);
@ -442,6 +440,27 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding)
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);
}
#ifdef USE_PTHREADS
struct mCoreThread* mCoreThreadGet(void) {
pthread_once(&_contextOnce, _createTLS);

View File

@ -28,6 +28,7 @@ enum mCoreThreadState {
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);

View File

@ -234,21 +234,33 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
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;
}
@ -263,6 +275,9 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
if (cpu->halted) {
cpu->cycles = cpu->nextEvent;
}
if (cpu->nextEvent == 0) {
break;
}
} while (cpu->cycles >= cpu->nextEvent);
}

View File

@ -32,16 +32,17 @@ static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mo
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 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI
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;

View File

@ -7,17 +7,56 @@
#include "gba/gba.h"
#include "gba/io.h"
#include "gba/video.h"
#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;
}
#define LOCKSTEP_INCREMENT 2048
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 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
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 @@ void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
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 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
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 @@ void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
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 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
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
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 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
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;
}
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;
_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;
}
ATOMIC_LOAD(cycles, node->nextEvent);
#ifndef NDEBUG
if (cycles <= 0 && !node->needsToWait) {
abort();
mLOG(GBA_SIO, WARN, "Sleeping needlessly");
}
#endif
return cycles;
}
return cycles;
}
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
@ -231,11 +399,10 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
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 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
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 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
}
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;
}

View File

@ -8,28 +8,32 @@
#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;
int waiting;
Mutex mutex;
Condition barrier;
uint32_t waitMask;
uint32_t waiting[MAX_GBAS];
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 GBASIOLockstepNode {
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*);

View File

@ -130,6 +130,10 @@ GameController::GameController(QObject* parent)
}
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 @@ void GameController::setMultiplayerController(MultiplayerController* controller)
}
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() {
@ -522,6 +531,9 @@ void GameController::closeGame() {
if (!m_gameOpen) {
return;
}
if (m_multiplayer) {
m_multiplayer->detachGame(this);
}
if (mCoreThreadIsPaused(&m_threadContext)) {
mCoreThreadUnpause(&m_threadContext);

View File

@ -7,10 +7,41 @@
#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 @@ MultiplayerController::~MultiplayerController() {
}
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);
m_asleep.append(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) {
break;
}
thread = nullptr;
if (!m_players.contains(controller)) {
return;
}
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);
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->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;
}

View File

@ -6,6 +6,8 @@
#ifndef QGBA_MULTIPLAYER_CONTROLLER
#define QGBA_MULTIPLAYER_CONTROLLER
#include <QMutex>
#include <QList>
#include <QObject>
extern "C" {
@ -35,6 +37,9 @@ signals:
private:
GBASIOLockstep m_lockstep;
QList<GameController*> m_players;
QList<int> m_asleep;
QMutex m_lock;
};
}

View File

@ -52,6 +52,23 @@ typedef intptr_t ssize_t;
#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); \

View File

@ -7,15 +7,6 @@
#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;