Added netplay client frame throttling functionality to keep it in step with server.

This commit is contained in:
harry 2024-03-04 06:47:09 -05:00
parent 798c5a1d9c
commit 72d1a8edf2
4 changed files with 172 additions and 20 deletions

View File

@ -71,13 +71,14 @@ struct NetPlayFrameDataHist_t
int getLast( NetPlayFrameData& out ) int getLast( NetPlayFrameData& out )
{ {
int i; int i;
if (bufHead == 0) const int head = bufHead;
if (head == 0)
{ {
i = numFrames - 1; i = numFrames - 1;
} }
else else
{ {
i = bufHead - 1; i = head - 1;
} }
out = data[i]; out = data[i];
@ -87,17 +88,18 @@ struct NetPlayFrameDataHist_t
int find( uint32_t frame, NetPlayFrameData& out ) int find( uint32_t frame, NetPlayFrameData& out )
{ {
int i, retval = -1; int i, retval = -1;
const int head = bufHead;
if (bufHead == 0) if (head == 0)
{ {
i = numFrames - 1; i = numFrames - 1;
} }
else else
{ {
i = bufHead - 1; i = head - 1;
} }
while (i != bufHead) while (i != head)
{ {
if (data[i].frameNum == frame) 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(romLoaded(void)), this, SLOT(onRomLoad(void)));
connect(consoleWindow, SIGNAL(nesResetOccurred(void)), this, SLOT(onNesReset(void))); connect(consoleWindow, SIGNAL(nesResetOccurred(void)), this, SLOT(onNesReset(void)));
FCEU_WRAPPER_LOCK();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
FCEU_WRAPPER_UNLOCK();
} }
@ -417,6 +423,10 @@ void NetPlayServer::onRomLoad()
{ {
//printf("New ROM Loaded!\n"); //printf("New ROM Loaded!\n");
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
// New ROM has been loaded by server, signal clients to load and sync // New ROM has been loaded by server, signal clients to load and sync
for (auto& client : clientList ) for (auto& client : clientList )
{ {
@ -430,6 +440,10 @@ void NetPlayServer::onNesReset()
{ {
//printf("New ROM Loaded!\n"); //printf("New ROM Loaded!\n");
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
// NES Reset has occurred on server, signal clients sync // NES Reset has occurred on server, signal clients sync
for (auto& client : clientList ) for (auto& client : clientList )
{ {
@ -663,7 +677,7 @@ void NetPlayServer::update(void)
const uint32_t maxLead = maxLeadFrames; const uint32_t maxLead = maxLeadFrames;
const uint32_t currFrame = static_cast<uint32_t>(currFrameCounter); const uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
const uint32_t leadFrame = currFrame + maxLead; const uint32_t leadFrame = currFrame + maxLead;
const uint32_t lastFrame = inputFrameBack(); //const uint32_t lastFrame = inputFrameBack();
uint32_t lagFrame = 0; uint32_t lagFrame = 0;
uint8_t localGP[4] = { 0 }; uint8_t localGP[4] = { 0 };
uint8_t gpData[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) && shouldRunFrame = (clientMinFrame != 0xFFFFFFFF) &&
(clientMinFrame >= lagFrame ) && (clientMinFrame >= lagFrame ) &&
@ -752,6 +766,7 @@ void NetPlayServer::update(void)
clientWaitCounter = 0; 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); //printf("Client Frame: Min:%u Max:%u\n", clientMinFrame, clientMaxFrame);
if (shouldRunFrame) if (shouldRunFrame)
@ -760,7 +775,7 @@ void NetPlayServer::update(void)
NetPlayFrameInput inputFrame; NetPlayFrameInput inputFrame;
netPlayRunFrameReq runFrameReq; netPlayRunFrameReq runFrameReq;
inputFrame.frameCounter = static_cast<uint32_t>(currFrameCounter) + 1; inputFrame.frameCounter = ++inputFrameCount;
inputFrame.ctrl[0] = gpData[0]; inputFrame.ctrl[0] = gpData[0];
inputFrame.ctrl[1] = gpData[1]; inputFrame.ctrl[1] = gpData[1];
inputFrame.ctrl[2] = gpData[2]; inputFrame.ctrl[2] = gpData[2];
@ -772,6 +787,13 @@ void NetPlayServer::update(void)
runFrameReq.ctrlState[2] = inputFrame.ctrl[2]; runFrameReq.ctrlState[2] = inputFrame.ctrl[2];
runFrameReq.ctrlState[3] = inputFrame.ctrl[3]; runFrameReq.ctrlState[3] = inputFrame.ctrl[3];
uint32_t catchUpThreshold = maxLead;
if (catchUpThreshold < 3)
{
catchUpThreshold = 3;
}
runFrameReq.catchUpThreshold = catchUpThreshold;
pushBackInput( inputFrame ); pushBackInput( inputFrame );
runFrameReq.toNetworkByteOrder(); runFrameReq.toNetworkByteOrder();
@ -1109,15 +1131,21 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB
{ {
bool readReq; bool readReq;
netPlayMsgHdr *hdr; netPlayMsgHdr *hdr;
const int netPlayMsgHdrSize = sizeof(netPlayMsgHdr); constexpr int netPlayMsgHdrSize = sizeof(netPlayMsgHdr);
FCEU::timeStampRecord ts;
readReq = sock->bytesAvailable() > 0; ts.readNew();
int bytesAvailable = sock->bytesAvailable();
readReq = bytesAvailable > 0;
//printf("Read Bytes Available: %lu %i\n", ts.toMilliSeconds(), bytesAvailable);
while (readReq) while (readReq)
{ {
if (recvMsgBytesLeft > 0) if (recvMsgBytesLeft > 0)
{ {
readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable >= recvMsgBytesLeft);
if (readReq) if (readReq)
{ {
@ -1135,16 +1163,18 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB
if (recvMsgBytesLeft > 0) if (recvMsgBytesLeft > 0)
{ {
readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable >= recvMsgBytesLeft);
} }
else else
{ {
msgCallback( userData, recvMsgBuf, recvMsgSize ); 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 ); sock->read( recvMsgBuf, netPlayMsgHdrSize );
@ -1166,7 +1196,8 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB
{ {
msgCallback( userData, recvMsgBuf, recvMsgSize ); msgCallback( userData, recvMsgBuf, recvMsgSize );
} }
readReq = (sock->bytesAvailable() >= recvMsgSize); bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable >= recvMsgSize);
} }
else else
{ {
@ -1269,10 +1300,20 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
inputFrame.ctrl[2] = msg->ctrlState[2]; inputFrame.ctrl[2] = msg->ctrlState[2];
inputFrame.ctrl[3] = msg->ctrlState[3]; inputFrame.ctrl[3] = msg->ctrlState[3];
if (inputFrame.frameCounter > inputFrameBack()) catchUpThreshold = msg->catchUpThreshold;
uint32_t lastInputFrame = inputFrameBack();
uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
if (inputFrame.frameCounter > lastInputFrame)
{ {
pushBackInput( inputFrame ); 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; break;
case NETPLAY_PING_REQ: case NETPLAY_PING_REQ:
@ -1302,6 +1343,8 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent)
QVBoxLayout *mainLayout; QVBoxLayout *mainLayout;
QHBoxLayout *hbox; QHBoxLayout *hbox;
QGridLayout *grid; QGridLayout *grid;
QGroupBox *networkGroupBox;
QGroupBox *settingsGroupBox;
QPushButton *cancelButton, *startButton; QPushButton *cancelButton, *startButton;
QLabel *lbl; QLabel *lbl;
@ -1310,8 +1353,18 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent)
setWindowTitle("NetPlay Host Game"); setWindowTitle("NetPlay Host Game");
mainLayout = new QVBoxLayout(); mainLayout = new QVBoxLayout();
networkGroupBox = new QGroupBox(tr("Network Setup"));
settingsGroupBox = new QGroupBox(tr("Server Settings"));
hbox = new QHBoxLayout();
grid = new QGridLayout(); grid = new QGridLayout();
mainLayout->addLayout(hbox);
hbox->addWidget(networkGroupBox);
hbox->addWidget(settingsGroupBox);
// Network Settings
networkGroupBox->setLayout(grid);
lbl = new QLabel( tr("Server Name:") ); lbl = new QLabel( tr("Server Name:") );
grid->addWidget( lbl, 0, 0 ); grid->addWidget( lbl, 0, 0 );
@ -1350,7 +1403,22 @@ NetPlayHostDialog::NetPlayHostDialog(QWidget *parent)
passwordEntry = new QLineEdit(); passwordEntry = new QLineEdit();
grid->addWidget( passwordEntry, 4, 1 ); 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 = new QPushButton( tr("Start") );
startButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton)); startButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton));
@ -1406,6 +1474,9 @@ void NetPlayHostDialog::onStartClicked(void)
server->setRole( playerRoleBox->currentData().toInt() ); server->setRole( playerRoleBox->currentData().toInt() );
server->sessionName = sessionNameEntry->text(); server->sessionName = sessionNameEntry->text();
server->sessionPasswd = passwordEntry->text(); server->sessionPasswd = passwordEntry->text();
server->setMaxLeadFrames( frameLeadSpinBox->value() );
server->setAllowClientRomLoadRequest( allowClientRomReqCBox->isChecked() );
server->setAllowClientStateLoadRequest( allowClientStateReqCBox->isChecked() );
bool listenSucceeded = server->listen( QHostAddress::Any, netPort ); bool listenSucceeded = server->listen( QHostAddress::Any, netPort );
@ -1503,6 +1574,7 @@ NetPlayJoinDialog::NetPlayJoinDialog(QWidget *parent)
passwordEntry = new QLineEdit(); passwordEntry = new QLineEdit();
passwordEntry->setMaxLength(64); passwordEntry->setMaxLength(64);
passwordEntry->setEnabled(false);
grid->addWidget( passwordEntry, 4, 1 ); grid->addWidget( passwordEntry, 4, 1 );
mainLayout->addLayout(grid); mainLayout->addLayout(grid);
@ -1944,7 +2016,7 @@ bool NetPlaySkipWait(void)
if (client) if (client)
{ {
skip = client->inputAvailable() > 1; skip = client->inputAvailableCount() > client->catchUpThreshold;
} }
return skip; return skip;
} }

View File

@ -139,6 +139,8 @@ class NetPlayServer : public QTcpServer
uint32_t getMaxLeadFrames(){ return maxLeadFrames; } uint32_t getMaxLeadFrames(){ return maxLeadFrames; }
void setMaxLeadFrames(uint32_t value){ maxLeadFrames = value; } 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 ); void serverProcessMessage( NetPlayClient *client, void *msgBuf, size_t msgSize );
@ -161,7 +163,9 @@ class NetPlayServer : public QTcpServer
uint32_t cycleCounter = 0; uint32_t cycleCounter = 0;
uint32_t maxLeadFrames = 10u; uint32_t maxLeadFrames = 10u;
uint32_t clientWaitCounter = 0; uint32_t clientWaitCounter = 0;
uint32_t inputFrameCount = 0;
bool allowClientRomLoadReq = true; bool allowClientRomLoadReq = true;
bool allowClientStateLoadReq = true;
public: public:
signals: signals:
@ -203,12 +207,18 @@ class NetPlayClient : public QObject
int readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData ); int readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData );
void clientProcessMessage( void *msgBuf, size_t msgSize ); void clientProcessMessage( void *msgBuf, size_t msgSize );
size_t inputAvailable(void) bool inputAvailable(void)
{ {
FCEU::autoScopedLock alock(inputMtx); FCEU::autoScopedLock alock(inputMtx);
return !input.empty(); return !input.empty();
}; };
size_t inputAvailableCount(void)
{
FCEU::autoScopedLock alock(inputMtx);
return input.size();
};
void pushBackInput( NetPlayFrameInput &in ) void pushBackInput( NetPlayFrameInput &in )
{ {
FCEU::autoScopedLock alock(inputMtx); FCEU::autoScopedLock alock(inputMtx);
@ -264,6 +274,8 @@ class NetPlayClient : public QObject
bool syncOk = false; bool syncOk = false;
unsigned int currentFrame = 0; unsigned int currentFrame = 0;
unsigned int readyFrame = 0; unsigned int readyFrame = 0;
unsigned int catchUpThreshold = 10;
unsigned int tailTarget = 3;
uint8_t gpData[4]; uint8_t gpData[4];
private: private:
@ -319,6 +331,9 @@ protected:
QComboBox *playerRoleBox; QComboBox *playerRoleBox;
QLineEdit *passwordEntry; QLineEdit *passwordEntry;
QCheckBox *passwordRequiredCBox; QCheckBox *passwordRequiredCBox;
QSpinBox *frameLeadSpinBox;
QCheckBox *allowClientRomReqCBox;
QCheckBox *allowClientStateReqCBox;
static NetPlayHostDialog* instance; static NetPlayHostDialog* instance;

View File

@ -252,9 +252,10 @@ struct netPlayRunFrameReq
uint32_t flags; uint32_t flags;
uint32_t frameNum; uint32_t frameNum;
uint8_t ctrlState[4]; uint8_t ctrlState[4];
uint8_t catchUpThreshold;
netPlayRunFrameReq(void) 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) ); memset( ctrlState, 0, sizeof(ctrlState) );
} }

View File

@ -65,6 +65,8 @@ bool useIntFrameRate = false;
static double frmRateAdjRatio = 1.000000f; // Frame Rate Adjustment Ratio static double frmRateAdjRatio = 1.000000f; // Frame Rate Adjustment Ratio
extern bool turbo; extern bool turbo;
int NetPlayThrottleControl();
double getHighPrecTimeStamp(void) double getHighPrecTimeStamp(void)
{ {
double t; double t;
@ -373,6 +375,7 @@ SpeedThrottle(void)
// If Emulator is paused, don't waste CPU cycles spinning on nothing. // If Emulator is paused, don't waste CPU cycles spinning on nothing.
if ( !isEmuPaused && ((g_fpsScale >= 32) || turboActive) ) if ( !isEmuPaused && ((g_fpsScale >= 32) || turboActive) )
{ {
//printf("Skip Wait\n");
return 0; /* Done waiting */ return 0; /* Done waiting */
} }
FCEU::timeStampRecord cur_time, idleStart, time_left; 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 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 ); //printf("Frame Sleep Time: %f Target Error: %f us\n", time_left * 1e6, (cur_time - Nexttime) * 1e6 );
} }
NetPlayThrottleControl();
Lasttime = Nexttime; Lasttime = Nexttime;
Nexttime = Lasttime + DesiredFrameTime; Nexttime = Lasttime + DesiredFrameTime;
Latetime = Nexttime + HalfFrameTime; Latetime = Nexttime + HalfFrameTime;
@ -512,6 +517,7 @@ SpeedThrottle(void)
Nexttime = Lasttime + DesiredFrameTime; Nexttime = Lasttime + DesiredFrameTime;
Latetime = Nexttime + HalfFrameTime; Latetime = Nexttime + HalfFrameTime;
} }
return 0; /* Done waiting */ return 0; /* Done waiting */
} }
@ -570,6 +576,64 @@ int CustomEmulationSpeed(int spdPercent)
return 0; 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<double>( static_cast<intptr_t>(inputAvailCount) - static_cast<intptr_t>(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. * Set the emulation speed throttling to a specific value.