Merge branch 'feature/multiplayer-rewrite'

This commit is contained in:
Jeffrey Pfau 2016-09-03 19:09:00 -07:00
commit 2b71e5c797
12 changed files with 525 additions and 241 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,18 @@
#include "gba/gba.h"
#include "gba/io.h"
#include "gba/video.h"
#define LOCKSTEP_INCREMENT 2048
#define LOCKSTEP_INCREMENT 3000
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 +30,15 @@ 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;
#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 +47,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 +81,6 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
node->nextEvent = LOCKSTEP_INCREMENT;
node->d.p->multiplayerControl.slave = node->id > 0;
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
return true;
@ -95,15 +92,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 +108,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);
node->p->unload(node->p, node->id);
return true;
}
@ -143,14 +137,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 +155,205 @@ 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:
// TODO
sio->normalControl.start = 0;
if (node->id) {
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
} else {
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
}
if (sio->multiplayerControl.irq) {
GBARaiseIRQ(sio->p, IRQ_SIO);
}
break;
case SIO_NORMAL_32:
// TODO
sio->normalControl.start = 0;
if (node->id) {
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
} else {
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
}
if (sio->multiplayerControl.irq) {
GBARaiseIRQ(sio->p, IRQ_SIO);
}
break;
default:
break;
}
node->transferFinished = true;
#ifndef NDEBUG
++node->transferId;
#endif
}
static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
bool 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;
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
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
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;
}
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 (needsToWait) {
if (!node->p->wait(node->p, mask)) {
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;
} else {
node->p->signal(node->p, mask);
}
}
// Tell the other GBAs they can continue up to where we were
node->p->addCycles(node->p, 0, node->eventDiff);
#ifndef NDEBUG
node->phase = node->p->transferActive;
#endif
if (needsToWait) {
return 0;
}
return node->nextEvent;
}
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->p->addCycles(node->p, node->id, LOCKSTEP_INCREMENT);
}
break;
case TRANSFER_STARTING:
case TRANSFER_FINISHING:
break;
case TRANSFER_STARTED:
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;
case SIO_NORMAL_8:
node->p->multiRecv[node->id] = 0xFFFF;
node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA8 >> 1] & 0xFF;
break;
case SIO_NORMAL_32:
node->p->multiRecv[node->id] = 0xFFFF;
node->p->normalRecv[node->id] = node->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
node->p->normalRecv[node->id] |= node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] << 16;
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;
#ifndef NDEBUG
node->phase = node->p->transferActive;
#endif
if (signal) {
node->p->signal(node->p, 1 << node->id);
}
return 0;
}
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;
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;
} else {
cycles = node->nextEvent;
}
if (cycles < 0) {
return 0;
}
return cycles;
}
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
@ -231,11 +361,13 @@ 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) {
if (!node->id) {
driver->p->normalControl.si = 1;
}
if (value & 0x0080 && !node->id) {
// Internal shift clock
if (value & 1) {
node->p->transferActive = true;
node->p->transferActive = TRANSFER_STARTING;
}
// Frequency
if (value & 2) {
@ -243,13 +375,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
} else {
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
}
node->normalSO = !!(value & 8);
// Opponent's SO
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 +383,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,40 +8,50 @@
#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;
int attachedNormal;
uint16_t multiRecv[MAX_GBAS];
bool transferActive;
uint32_t normalRecv[MAX_GBAS];
enum GBASIOLockstepPhase transferActive;
int32_t transferCycles;
int32_t nextEvent;
int waiting;
Mutex mutex;
Condition barrier;
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 (*unload)(struct GBASIOLockstep*, int id);
void* context;
#ifndef NDEBUG
int transferId;
#endif
};
struct GBASIOLockstepNode {
struct GBASIODriver d;
struct GBASIOLockstep* p;
int32_t nextEvent;
uint16_t multiSend;
volatile int32_t nextEvent;
int32_t eventDiff;
bool normalSO;
enum LockstepState state;
int id;
enum GBASIOMode mode;
bool transferFinished;
#ifndef NDEBUG
int transferId;
enum GBASIOLockstepPhase phase;
#endif
};
void GBASIOLockstepInit(struct GBASIOLockstep*);

View File

@ -50,6 +50,7 @@ GameController::GameController(QObject* parent)
, m_audioThread(new QThread(this))
, m_audioProcessor(AudioProcessor::create())
, m_pauseAfterFrame(false)
, m_sync(true)
, m_videoSync(VIDEO_SYNC)
, m_audioSync(AUDIO_SYNC)
, m_fpsTarget(-1)
@ -130,6 +131,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 +261,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 +532,9 @@ void GameController::closeGame() {
if (!m_gameOpen) {
return;
}
if (m_multiplayer) {
m_multiplayer->detachGame(this);
}
if (mCoreThreadIsPaused(&m_threadContext)) {
mCoreThreadUnpause(&m_threadContext);
@ -926,10 +939,13 @@ void GameController::setTurbo(bool set, bool forced) {
if (m_turboForced && !forced) {
return;
}
if (m_turbo == set && m_turboForced == forced) {
if (m_turbo == set && m_turboForced == (set && forced)) {
// Don't interrupt the thread if we don't need to
return;
}
if (!m_sync) {
return;
}
m_turbo = set;
m_turboForced = set && forced;
enableTurbo();
@ -942,25 +958,41 @@ void GameController::setTurboSpeed(float ratio) {
void GameController::enableTurbo() {
threadInterrupt();
bool shouldRedoSamples = false;
if (!m_turbo) {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
m_threadContext.sync.fpsTarget = m_fpsTarget;
m_threadContext.sync.audioWait = m_audioSync;
m_threadContext.sync.videoFrameWait = m_videoSync;
} else if (m_turboSpeed <= 0) {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
m_threadContext.sync.fpsTarget = m_fpsTarget;
m_threadContext.sync.audioWait = false;
m_threadContext.sync.videoFrameWait = false;
} else {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget * m_turboSpeed;
m_threadContext.sync.fpsTarget = m_fpsTarget * m_turboSpeed;
m_threadContext.sync.audioWait = true;
m_threadContext.sync.videoFrameWait = false;
}
if (m_audioProcessor) {
if (m_audioProcessor && shouldRedoSamples) {
redoSamples(m_audioProcessor->getBufferSamples());
}
threadContinue();
}
void GameController::setSync(bool enable) {
m_turbo = false;
m_turboForced = false;
if (!enable) {
m_threadContext.sync.audioWait = false;
m_threadContext.sync.videoFrameWait = false;
} else {
m_threadContext.sync.audioWait = m_audioSync;
m_threadContext.sync.videoFrameWait = m_videoSync;
}
m_sync = enable;
}
void GameController::setAVStream(mAVStream* stream) {
threadInterrupt();
m_stream = stream;

View File

@ -135,7 +135,8 @@ public slots:
void loadBackupState();
void saveBackupState();
void setTurbo(bool, bool forced = true);
void setTurboSpeed(float ratio = -1);
void setTurboSpeed(float ratio);
void setSync(bool);
void setAVStream(mAVStream*);
void clearAVStream();
void reloadAudioDriver();
@ -197,6 +198,7 @@ private:
QAtomicInt m_pauseAfterFrame;
QList<std::function<void ()>> m_resetActions;
bool m_sync;
bool m_videoSync;
bool m_audioSync;
float m_fpsTarget;

View File

@ -7,10 +7,111 @@
#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, unsigned mask) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
Player* player = &controller->m_players[0];
bool woke = false;
controller->m_lock.lock();
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->d.p->mode != controller->m_players[0].node->d.p->mode) {
player->controller->setSync(true);
continue;
}
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
player->node->nextEvent += player->cyclesPosted;
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
}
} else {
controller->m_players[id].controller->setSync(true);
controller->m_players[id].cyclesPosted += cycles;
}
controller->m_lock.unlock();
};
m_lockstep.useCycles = [](GBASIOLockstep* lockstep, int id, int32_t cycles) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
controller->m_lock.lock();
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;
};
m_lockstep.unload = [](GBASIOLockstep* lockstep, int id) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
controller->m_lock.lock();
Player* player = &controller->m_players[id];
if (id) {
player->controller->setSync(true);
player->waitMask &= ~(1 << id);
if (!player->waitMask && player->awake < 1) {
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
} else {
for (int i = 1; i < controller->m_players.count(); ++i) {
Player* player = &controller->m_players[i];
player->controller->setSync(true);
player->cyclesPosted += lockstep->players[0]->eventDiff;
if (player->awake < 1) {
player->node->nextEvent += player->cyclesPosted;
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
}
}
controller->m_lock.unlock();
};
}
MultiplayerController::~MultiplayerController() {
@ -18,74 +119,78 @@ 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,
node,
1,
0,
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) {
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;
}
}
#endif
for (int i = 0; i < m_players.count(); ++i) {
if (m_players[i].controller == controller) {
m_players.removeAt(i);
break;
}
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();
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;
}*/
for (int i = 0; i < m_players.count(); ++i) {
if (m_players[i].controller == controller) {
return i;
}
}
MutexUnlock(&m_lockstep.mutex);
return id;
return -1;
}
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" {
@ -34,7 +36,16 @@ signals:
void gameDetached();
private:
struct Player {
GameController* controller;
GBASIOLockstepNode* node;
int awake;
int32_t cyclesPosted;
unsigned waitMask;
};
GBASIOLockstep m_lockstep;
QList<Player> m_players;
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;