diff --git a/src/drivers/Qt/NetPlay.cpp b/src/drivers/Qt/NetPlay.cpp index 19848652e..fb0eff0d2 100644 --- a/src/drivers/Qt/NetPlay.cpp +++ b/src/drivers/Qt/NetPlay.cpp @@ -71,13 +71,14 @@ struct NetPlayFrameDataHist_t int getLast( NetPlayFrameData& out ) { int i; - if (bufHead == 0) + const int head = bufHead; + if (head == 0) { i = numFrames - 1; } else { - i = bufHead - 1; + i = head - 1; } out = data[i]; @@ -87,17 +88,18 @@ struct NetPlayFrameDataHist_t int find( uint32_t frame, NetPlayFrameData& out ) { int i, retval = -1; + const int head = bufHead; - if (bufHead == 0) + if (head == 0) { i = numFrames - 1; } else { - i = bufHead - 1; + i = head - 1; } - while (i != bufHead) + while (i != head) { if (data[i].frameNum == frame) { @@ -173,6 +175,10 @@ NetPlayServer::NetPlayServer(QObject *parent) connect(consoleWindow, SIGNAL(romLoaded(void)), this, SLOT(onRomLoad(void))); connect(consoleWindow, SIGNAL(nesResetOccurred(void)), this, SLOT(onNesReset(void))); + + FCEU_WRAPPER_LOCK(); + inputFrameCount = static_cast(currFrameCounter); + FCEU_WRAPPER_UNLOCK(); } @@ -417,6 +423,10 @@ void NetPlayServer::onRomLoad() { //printf("New ROM Loaded!\n"); FCEU_WRAPPER_LOCK(); + + inputClear(); + inputFrameCount = static_cast(currFrameCounter); + // New ROM has been loaded by server, signal clients to load and sync for (auto& client : clientList ) { @@ -430,6 +440,10 @@ void NetPlayServer::onNesReset() { //printf("New ROM Loaded!\n"); FCEU_WRAPPER_LOCK(); + + inputClear(); + inputFrameCount = static_cast(currFrameCounter); + // NES Reset has occurred on server, signal clients sync for (auto& client : clientList ) { @@ -663,7 +677,7 @@ void NetPlayServer::update(void) const uint32_t maxLead = maxLeadFrames; const uint32_t currFrame = static_cast(currFrameCounter); const uint32_t leadFrame = currFrame + maxLead; - const uint32_t lastFrame = inputFrameBack(); + //const uint32_t lastFrame = inputFrameBack(); uint32_t lagFrame = 0; uint8_t localGP[4] = { 0 }; uint8_t gpData[4] = { 0 }; @@ -735,7 +749,7 @@ void NetPlayServer::update(void) } } - hostRdyFrame = ( (currFrame > lastFrame) || (lastFrame == 0) ); + hostRdyFrame = (currFrame >= inputFrameCount); shouldRunFrame = (clientMinFrame != 0xFFFFFFFF) && (clientMinFrame >= lagFrame ) && @@ -752,6 +766,7 @@ void NetPlayServer::update(void) clientWaitCounter = 0; } + //printf("Host Frame: Run:%u Input:%u Last:%u\n", currFrame, inputFrameCount, lastFrame); //printf("Client Frame: Min:%u Max:%u\n", clientMinFrame, clientMaxFrame); if (shouldRunFrame) @@ -760,7 +775,7 @@ void NetPlayServer::update(void) NetPlayFrameInput inputFrame; netPlayRunFrameReq runFrameReq; - inputFrame.frameCounter = static_cast(currFrameCounter) + 1; + inputFrame.frameCounter = ++inputFrameCount; inputFrame.ctrl[0] = gpData[0]; inputFrame.ctrl[1] = gpData[1]; inputFrame.ctrl[2] = gpData[2]; @@ -772,6 +787,13 @@ void NetPlayServer::update(void) runFrameReq.ctrlState[2] = inputFrame.ctrl[2]; runFrameReq.ctrlState[3] = inputFrame.ctrl[3]; + uint32_t catchUpThreshold = maxLead; + if (catchUpThreshold < 3) + { + catchUpThreshold = 3; + } + runFrameReq.catchUpThreshold = catchUpThreshold; + pushBackInput( inputFrame ); runFrameReq.toNetworkByteOrder(); @@ -1109,15 +1131,21 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB { bool readReq; netPlayMsgHdr *hdr; - const int netPlayMsgHdrSize = sizeof(netPlayMsgHdr); + constexpr int netPlayMsgHdrSize = sizeof(netPlayMsgHdr); + FCEU::timeStampRecord ts; + + ts.readNew(); + int bytesAvailable = sock->bytesAvailable(); + readReq = bytesAvailable > 0; - readReq = sock->bytesAvailable() > 0; + //printf("Read Bytes Available: %lu %i\n", ts.toMilliSeconds(), bytesAvailable); while (readReq) { if (recvMsgBytesLeft > 0) { - readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); + bytesAvailable = sock->bytesAvailable(); + readReq = (bytesAvailable >= recvMsgBytesLeft); if (readReq) { @@ -1135,16 +1163,18 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB if (recvMsgBytesLeft > 0) { - readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); + bytesAvailable = sock->bytesAvailable(); + readReq = (bytesAvailable >= recvMsgBytesLeft); } else { msgCallback( userData, recvMsgBuf, recvMsgSize ); - readReq = (sock->bytesAvailable() > 0); + bytesAvailable = sock->bytesAvailable(); + readReq = (bytesAvailable > 0); } } } - else if (sock->bytesAvailable() >= netPlayMsgHdrSize) + else if (bytesAvailable >= netPlayMsgHdrSize) { sock->read( recvMsgBuf, netPlayMsgHdrSize ); @@ -1166,7 +1196,8 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB { msgCallback( userData, recvMsgBuf, recvMsgSize ); } - readReq = (sock->bytesAvailable() >= recvMsgSize); + bytesAvailable = sock->bytesAvailable(); + readReq = (bytesAvailable >= recvMsgSize); } else { @@ -1269,10 +1300,20 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) inputFrame.ctrl[2] = msg->ctrlState[2]; inputFrame.ctrl[3] = msg->ctrlState[3]; - if (inputFrame.frameCounter > inputFrameBack()) + catchUpThreshold = msg->catchUpThreshold; + + uint32_t lastInputFrame = inputFrameBack(); + uint32_t currFrame = static_cast(currFrameCounter); + + if (inputFrame.frameCounter > lastInputFrame) { pushBackInput( inputFrame ); } + else + { + printf("Drop Frame: LastRun:%u LastInput:%u NewInput:%u\n", currFrame, lastInputFrame, inputFrame.frameCounter); + } + //printf("Run Frame: LastRun:%u LastInput:%u NewInput:%u\n", currFrame, lastInputFrame, inputFrame.frameCounter); } break; case NETPLAY_PING_REQ: @@ -1302,6 +1343,8 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent) QVBoxLayout *mainLayout; QHBoxLayout *hbox; QGridLayout *grid; + QGroupBox *networkGroupBox; + QGroupBox *settingsGroupBox; QPushButton *cancelButton, *startButton; QLabel *lbl; @@ -1310,8 +1353,18 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent) setWindowTitle("NetPlay Host Game"); mainLayout = new QVBoxLayout(); + networkGroupBox = new QGroupBox(tr("Network Setup")); + settingsGroupBox = new QGroupBox(tr("Server Settings")); + hbox = new QHBoxLayout(); grid = new QGridLayout(); + mainLayout->addLayout(hbox); + hbox->addWidget(networkGroupBox); + hbox->addWidget(settingsGroupBox); + + // Network Settings + networkGroupBox->setLayout(grid); + lbl = new QLabel( tr("Server Name:") ); grid->addWidget( lbl, 0, 0 ); @@ -1350,7 +1403,22 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent) passwordEntry = new QLineEdit(); grid->addWidget( passwordEntry, 4, 1 ); - mainLayout->addLayout(grid); + // Misc Settings + grid = new QGridLayout(); + settingsGroupBox->setLayout(grid); + + lbl = new QLabel( tr("Max Frame Lead:") ); + frameLeadSpinBox = new QSpinBox(); + frameLeadSpinBox->setRange(5,60); + frameLeadSpinBox->setValue(30); + grid->addWidget( lbl, 0, 0, 1, 1 ); + grid->addWidget( frameLeadSpinBox, 0, 1, 1, 1 ); + + allowClientRomReqCBox = new QCheckBox(tr("Allow Client ROM Load Requests")); + grid->addWidget( allowClientRomReqCBox, 1, 0, 1, 2 ); + + allowClientStateReqCBox = new QCheckBox(tr("Allow Client State Load Requests")); + grid->addWidget( allowClientStateReqCBox, 2, 0, 1, 2 ); startButton = new QPushButton( tr("Start") ); startButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton)); @@ -1406,6 +1474,9 @@ void NetPlayHostDialog::onStartClicked(void) server->setRole( playerRoleBox->currentData().toInt() ); server->sessionName = sessionNameEntry->text(); server->sessionPasswd = passwordEntry->text(); + server->setMaxLeadFrames( frameLeadSpinBox->value() ); + server->setAllowClientRomLoadRequest( allowClientRomReqCBox->isChecked() ); + server->setAllowClientStateLoadRequest( allowClientStateReqCBox->isChecked() ); bool listenSucceeded = server->listen( QHostAddress::Any, netPort ); @@ -1503,6 +1574,7 @@ NetPlayJoinDialog::NetPlayJoinDialog(QWidget *parent) passwordEntry = new QLineEdit(); passwordEntry->setMaxLength(64); + passwordEntry->setEnabled(false); grid->addWidget( passwordEntry, 4, 1 ); mainLayout->addLayout(grid); @@ -1944,7 +2016,7 @@ bool NetPlaySkipWait(void) if (client) { - skip = client->inputAvailable() > 1; + skip = client->inputAvailableCount() > client->catchUpThreshold; } return skip; } diff --git a/src/drivers/Qt/NetPlay.h b/src/drivers/Qt/NetPlay.h index b96fce17e..75756c4cf 100644 --- a/src/drivers/Qt/NetPlay.h +++ b/src/drivers/Qt/NetPlay.h @@ -139,6 +139,8 @@ class NetPlayServer : public QTcpServer uint32_t getMaxLeadFrames(){ return maxLeadFrames; } void setMaxLeadFrames(uint32_t value){ maxLeadFrames = value; } + void setAllowClientRomLoadRequest(bool value){ allowClientRomLoadReq = value; } + void setAllowClientStateLoadRequest(bool value){ allowClientStateLoadReq = value; } void serverProcessMessage( NetPlayClient *client, void *msgBuf, size_t msgSize ); @@ -161,7 +163,9 @@ class NetPlayServer : public QTcpServer uint32_t cycleCounter = 0; uint32_t maxLeadFrames = 10u; uint32_t clientWaitCounter = 0; + uint32_t inputFrameCount = 0; bool allowClientRomLoadReq = true; + bool allowClientStateLoadReq = true; public: signals: @@ -203,12 +207,18 @@ class NetPlayClient : public QObject int readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData ); void clientProcessMessage( void *msgBuf, size_t msgSize ); - size_t inputAvailable(void) + bool inputAvailable(void) { FCEU::autoScopedLock alock(inputMtx); return !input.empty(); }; + size_t inputAvailableCount(void) + { + FCEU::autoScopedLock alock(inputMtx); + return input.size(); + }; + void pushBackInput( NetPlayFrameInput &in ) { FCEU::autoScopedLock alock(inputMtx); @@ -264,6 +274,8 @@ class NetPlayClient : public QObject bool syncOk = false; unsigned int currentFrame = 0; unsigned int readyFrame = 0; + unsigned int catchUpThreshold = 10; + unsigned int tailTarget = 3; uint8_t gpData[4]; private: @@ -319,6 +331,9 @@ class NetPlayHostDialog : public QDialog QComboBox *playerRoleBox; QLineEdit *passwordEntry; QCheckBox *passwordRequiredCBox; + QSpinBox *frameLeadSpinBox; + QCheckBox *allowClientRomReqCBox; + QCheckBox *allowClientStateReqCBox; static NetPlayHostDialog* instance; diff --git a/src/drivers/Qt/NetPlayMsgDef.h b/src/drivers/Qt/NetPlayMsgDef.h index 15dad0375..1d8d1393c 100644 --- a/src/drivers/Qt/NetPlayMsgDef.h +++ b/src/drivers/Qt/NetPlayMsgDef.h @@ -252,9 +252,10 @@ struct netPlayRunFrameReq uint32_t flags; uint32_t frameNum; uint8_t ctrlState[4]; + uint8_t catchUpThreshold; netPlayRunFrameReq(void) - : hdr(NETPLAY_RUN_FRAME_REQ, sizeof(netPlayRunFrameReq)), flags(0), frameNum(0) + : hdr(NETPLAY_RUN_FRAME_REQ, sizeof(netPlayRunFrameReq)), flags(0), frameNum(0), catchUpThreshold(10) { memset( ctrlState, 0, sizeof(ctrlState) ); } diff --git a/src/drivers/Qt/sdl-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index 1aacecd5c..d60107e54 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -65,6 +65,8 @@ bool useIntFrameRate = false; static double frmRateAdjRatio = 1.000000f; // Frame Rate Adjustment Ratio extern bool turbo; +int NetPlayThrottleControl(); + double getHighPrecTimeStamp(void) { double t; @@ -373,6 +375,7 @@ SpeedThrottle(void) // If Emulator is paused, don't waste CPU cycles spinning on nothing. if ( !isEmuPaused && ((g_fpsScale >= 32) || turboActive) ) { + //printf("Skip Wait\n"); return 0; /* Done waiting */ } FCEU::timeStampRecord cur_time, idleStart, time_left; @@ -502,6 +505,8 @@ SpeedThrottle(void) //printf("Frame Delta: %f us min:%f max:%f \n", frameDelta * 1e6, frameDeltaMin * 1e6, frameDeltaMax * 1e6 ); //printf("Frame Sleep Time: %f Target Error: %f us\n", time_left * 1e6, (cur_time - Nexttime) * 1e6 ); } + NetPlayThrottleControl(); + Lasttime = Nexttime; Nexttime = Lasttime + DesiredFrameTime; Latetime = Nexttime + HalfFrameTime; @@ -512,6 +517,7 @@ SpeedThrottle(void) Nexttime = Lasttime + DesiredFrameTime; Latetime = Nexttime + HalfFrameTime; } + return 0; /* Done waiting */ } @@ -570,6 +576,64 @@ int CustomEmulationSpeed(int spdPercent) return 0; } +int NetPlayThrottleControl() +{ + NetPlayClient *client = NetPlayClient::GetInstance(); + + if (client) + { + uint32_t inputAvailCount = client->inputAvailableCount(); + const uint32_t tailTarget = client->tailTarget; + + double targetDelta = static_cast( static_cast(inputAvailCount) - static_cast(tailTarget) ); + + // Simple linear FPS scaling adjustment based on target error. + constexpr double speedUpSlope = (0.05) / (10.0); + double newScale = 1.0 + (targetDelta * speedUpSlope); + + if (newScale != g_fpsScale) + { + double hz; + int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz + int32_t T; + + g_fpsScale = newScale; + + hz = ( ((double)fps) / 16777216.0 ); + + desired_frametime = 1.0 / ( hz * g_fpsScale ); + + if ( useIntFrameRate ) + { + hz = (double)( (int)(hz) ); + + frmRateAdjRatio = (1.0 / ( hz * g_fpsScale )) / desired_frametime; + + //printf("frameAdjRatio:%f \n", frmRateAdjRatio ); + } + else + { + frmRateAdjRatio = 1.000000f; + } + desired_frametime = 1.0 / ( hz * g_fpsScale ); + desired_frameRate = ( hz * g_fpsScale ); + baseframeRate = hz; + + T = (int32_t)( desired_frametime * 1000.0 ); + + if ( T < 0 ) T = 1; + + DesiredFrameTime.fromSeconds( desired_frametime ); + HalfFrameTime = DesiredFrameTime / 2; + QuarterFrameTime = DesiredFrameTime / 4; + DoubleFrameTime = DesiredFrameTime * 2; + } + //printf("NetPlayCPUThrottle: %f %f %f Target:%u InputAvail:%u\n", newScale, desired_frameRate, targetDelta, tailTarget, inputAvailCount); + } + + return 0; +} + /** * Set the emulation speed throttling to a specific value.