Skip to content

Commit

Permalink
Make the Async Loop more precise
Browse files Browse the repository at this point in the history
by using Sys_MillisecondsPrecise() and Sys_SleepUntilPrecise()
and by adding com_preciseFrameLengthMS (as float instead of int).

Renamed com_gameFrameTime to com_gameFrameLengthMS, so it's less
confusing with com_frameTime (which is the timestamp of the current
frame, not its length)
  • Loading branch information
DanielGibson committed Jul 2, 2024
1 parent f02245d commit f8fa12f
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 23 deletions.
54 changes: 34 additions & 20 deletions neo/framework/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,19 @@ idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SY
idCVar com_gameHz( "com_gameHz", "60", CVAR_INTEGER | CVAR_ARCHIVE | CVAR_SYSTEM, COM_GAMEHZ_DESCR, 10, 480 ); // TODO: make it float? make it default to 62.5?
// the next three values will be set based on com_gameHz
int com_gameHzVal = 60;
int com_gameFrameTime = 16; // length of one frame in msec, 1000 / com_gameHz
int com_gameFrameLengthMS = 16; // length of one frame in msec, 1000 / com_gameHz
float com_preciseFrameLengthMS = 16.6667f; // 1000.0f / gameHzVal
float com_gameTicScale = 1.0f; // com_gameHzVal/60.0f, multiply stuff assuming one tic is 16ms with this

double com_preciseFrameTimeMS = 0; // like com_frameTime but as double: time (since start) for the current frame in milliseconds

// com_speeds times
int time_gameFrame;
int time_gameDraw;
int time_frontend; // renderSystem frontend time
int time_backend; // renderSystem backend time

int com_frameTime; // time (since start) for the current frame in milliseconds - TODO: DG: make it double?
int com_frameTime; // time (since start) for the current frame in milliseconds
int com_frameNumber; // variable frame number
volatile int com_ticNumber; // 60 hz tics
int com_editors; // currently opened editor(s)
Expand Down Expand Up @@ -270,7 +273,8 @@ void Com_UpdateFrameTime() {
int ticNum = com_ticNumber;
int ticDiff = ticNum - lastTicNum;
assert(ticDiff >= 0);
com_frameTime += ticDiff * USERCMD_MSEC;
com_preciseFrameTimeMS += ticDiff * com_preciseFrameLengthMS;
com_frameTime = idMath::Rint( com_preciseFrameTimeMS );
lastTicNum = ticNum;
}

Expand Down Expand Up @@ -2558,8 +2562,8 @@ typedef struct {

static const int MAX_ASYNC_STATS = 1024;
asyncStats_t com_asyncStats[MAX_ASYNC_STATS]; // indexed by com_ticNumber
static int lastTicMsec;
static int nextTicTargetMsec; // when (according to Sys_Milliseconds()) the next async tic should start
static double lastTicMsec = 0.0;
static double nextTicTargetMsec = 0.0; // when (according to Sys_Milliseconds()) the next async tic should start

void idCommonLocal::SingleAsyncTic( void ) {
// main thread code can prevent this from happening while modifying
Expand Down Expand Up @@ -2602,19 +2606,19 @@ idCommonLocal::Async
=================
*/
void idCommonLocal::Async( void ) {
int msec = Sys_Milliseconds(); // TODO: make double?
double msec = Sys_MillisecondsPrecise();
if ( !lastTicMsec ) {
lastTicMsec = msec - USERCMD_MSEC;
lastTicMsec = msec - com_preciseFrameLengthMS;
}

if ( !com_preciseTic.GetBool() ) {
// just run a single tic, even if the exact msec isn't precise
SingleAsyncTic();
nextTicTargetMsec = msec + USERCMD_MSEC;
nextTicTargetMsec = msec + com_preciseFrameLengthMS;
return;
}

int ticMsec = USERCMD_MSEC; // TODO: make float?
float ticMsec = com_preciseFrameLengthMS;

// the number of msec per tic can be varies with the timescale cvar
float timescale = com_timescale.GetFloat();
Expand All @@ -2627,8 +2631,8 @@ void idCommonLocal::Async( void ) {

// don't skip too many
if ( timescale == 1.0f ) {
if ( lastTicMsec + 10 * USERCMD_MSEC < msec ) {
lastTicMsec = msec - 10*USERCMD_MSEC;
if ( lastTicMsec + 10 * com_preciseFrameLengthMS < msec ) {
lastTicMsec = msec - 10.0*com_preciseFrameLengthMS;
}
}

Expand Down Expand Up @@ -2760,7 +2764,7 @@ void idCommonLocal::LoadGameDLL( void ) {

// initialize the game object
if ( game != NULL ) {
game->SetGameHz( com_gameHzVal, com_gameFrameTime, com_gameTicScale ); // DG: make sure it knows the ticrate
game->SetGameHz( com_gameHzVal, com_gameFrameLengthMS, com_gameTicScale ); // DG: make sure it knows the ticrate
game->Init();
}
}
Expand Down Expand Up @@ -2832,14 +2836,22 @@ int idCommonLocal::AsyncThread(void* arg)

while ( self->runAsyncThread ) {

// The idea is to make this run super-exact, but round *down* com_gameFrameTime (USERCMD_MSEC).
// Then (I think..) when the game thread actually runs (0.x ms later than it might expect)
// all the things that waited for USERCMD_MSEC will run because they're (slightly) overdue
// => they'll be exactly on time
// TODO: .. well, unless maybe if they waited for so many frametimes that we're a frame early..
// But does it even matter for such long waits?
// (and also, so far USERCMD_MSEC *has* been rounded down from 16.6667 to 16,
// and with VSync enabled there was more or less correct timing)

self->Async();

// idSessionLocal::Frame() waits for TRIGGER_EVENT_ONE
// => this syncs the main thread with the async thread
Sys_TriggerEvent(TRIGGER_EVENT_ONE);

// TODO: -1 is so we don't sleep too long - would -2 be better, or can we have a more precise sleep?
// IIRC especially on Windows sleeping is imprecise by at least on MS
int sleepTime = Max( 0, nextTicTargetMsec - (int)Sys_Milliseconds() - 1 );
Sys_Sleep( sleepTime );
Sys_SleepUntilPrecise( nextTicTargetMsec );
}
return 0;
}
Expand Down Expand Up @@ -3359,15 +3371,17 @@ void idCommonLocal::UpdateGameHz()
{
com_gameHz.ClearModified();
com_gameHzVal = com_gameHz.GetInteger();
com_preciseFrameLengthMS = 1000.0f / com_gameHzVal;
// only rounding up the frame time a little bit, so for 144hz (6.94ms) it becomes 7ms,
// but for 60Hz (16.6667ms) it remains 16ms, like before
com_gameFrameTime = ( 1000.0f / com_gameHzVal ) + 0.1f; // TODO: idMath::Rint ?
com_gameTicScale = com_gameHzVal / 60.0f; // TODO: or / 62.5 ?
com_gameFrameLengthMS = com_preciseFrameLengthMS + 0.1f; // TODO: idMath::Rint ? or always round down?

com_gameTicScale = com_gameHzVal / 60.0f; // TODO: or / 62.5?

Printf( "Running the game at com_gameHz = %dHz, frametime %dms\n", com_gameHzVal, com_gameFrameTime );
Printf( "Running the game at com_gameHz = %dHz, frametime %dms\n", com_gameHzVal, com_gameFrameLengthMS );

if ( game != NULL ) {
game->SetGameHz( com_gameHzVal, com_gameFrameTime, com_gameTicScale );
game->SetGameHz( com_gameHzVal, com_gameFrameLengthMS, com_gameTicScale );
}
}

Expand Down
6 changes: 4 additions & 2 deletions neo/framework/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ extern idCVar com_dbgServerAdr;
// DG: the next block is for configurable framerate
extern idCVar com_gameHz;
extern int com_gameHzVal;
extern int com_gameFrameTime; // round(1000.0f / gameHzVal), I guess
extern float com_gameTicScale; // com_gameHzVal/60.0f, multiply stuff assuming one tic is 16ms with this
extern int com_gameFrameLengthMS; // 1000.0f / gameHzVal, I guess
extern float com_preciseFrameLengthMS; // 1000.0f / gameHzVal
extern float com_gameTicScale; // com_gameHzVal/60.0f, multiply stuff assuming one tic is 16ms with this
extern double com_preciseFrameTimeMS; // like com_frameTime but as double: time (since start) for the current frame in milliseconds

extern int time_gameFrame; // game logic time
extern int time_gameDraw; // game present time
Expand Down
2 changes: 1 addition & 1 deletion neo/framework/UsercmdGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ If you have questions concerning this license or the applicable additional terms
#define USERCMD_MSEC gameLocal.gameMsec
#else
#define USERCMD_HZ com_gameHzVal
#define USERCMD_MSEC com_gameFrameTime
#define USERCMD_MSEC com_gameFrameLengthMS
#endif

// usercmd_t->button bits
Expand Down

0 comments on commit f8fa12f

Please sign in to comment.