From 231beb2c234914f942166eb152c44e55b5b01f86 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Sun, 26 Nov 2023 11:17:53 +1000 Subject: [PATCH] Sys_Sleep overhaul One function to rule them all, and in the darkplaces sleep threads. Makes sub-millisecond accuracy available on Windows. Increases the number of sockets we can monitor on Windows. Improves Curl_Select() a little. Updates flags of some timing-related cvars. Cleans up disorganised and duplicate code. Signed-off-by: bones_was_here --- cl_main.c | 2 +- host.c | 42 +------------------- lhnet.c | 43 ++++---------------- lhnet.h | 1 + libcurl.c | 25 ++++++++---- libcurl.h | 2 +- netconn.c | 5 --- netconn.h | 1 - sv_main.c | 17 +------- sys.h | 3 +- sys_shared.c | 108 +++++++++++++++++++++++++++++++++------------------ taskqueue.c | 2 +- 12 files changed, 104 insertions(+), 147 deletions(-) diff --git a/cl_main.c b/cl_main.c index 64e711c7..218f7b24 100644 --- a/cl_main.c +++ b/cl_main.c @@ -2858,7 +2858,7 @@ double CL_Frame (double time) // on some legacy systems, we need to sleep to keep input responsive if (cl_maxfps_alwayssleep.value > 0 && !cls.timedemo) - Sys_Sleep((int)bound(1, cl_maxfps_alwayssleep.value * 1000, 50000)); + Sys_Sleep(min(cl_maxfps_alwayssleep.value / 1000, 0.05)); } // apply slowmo scaling diff --git a/host.c b/host.c index 924e655e..3e2c5f99 100644 --- a/host.c +++ b/host.c @@ -664,43 +664,6 @@ static double Host_Frame(double time) return min(cl_wait, sv_wait); // listen server or singleplayer } -static inline double Host_Sleep(double time) -{ - double delta, time0; - - // convert to microseconds - time *= 1000000.0; - - if (time < 1 || host.restless) - return 0; // not sleeping this frame - - if(host_maxwait.value <= 0) - time = min(time, 1000000.0); - else - time = min(time, host_maxwait.value * 1000.0); - - time0 = Sys_DirtyTime(); - if (sv_checkforpacketsduringsleep.integer && !sys_usenoclockbutbenchmark.integer && !svs.threaded) { - NetConn_SleepMicroseconds((int)time); - if (cls.state != ca_dedicated) - NetConn_ClientFrame(); // helps server browser get good ping values - // TODO can we do the same for ServerFrame? Probably not. - } - else - { - if (cls.state != ca_dedicated) - Curl_Select(&time); - Sys_Sleep((int)time); - } - - delta = Sys_DirtyTime() - time0; - if (delta < 0 || delta >= 1800) - delta = 0; - -// R_TimeReport("sleep"); - return delta; -} - // Cloudwalk: Most overpowered function declaration... static inline double Host_UpdateTime (double newtime, double oldtime) { @@ -743,13 +706,12 @@ void Host_Main(void) host.dirtytime = Sys_DirtyTime(); host.realtime += time = Host_UpdateTime(host.dirtytime, oldtime); + oldtime = host.dirtytime; sleeptime = Host_Frame(time); - oldtime = host.dirtytime; ++host.framecount; - sleeptime -= Sys_DirtyTime() - host.dirtytime; // execution time - host.sleeptime = Host_Sleep(sleeptime); + host.sleeptime = Sys_Sleep(sleeptime); } return; diff --git a/lhnet.c b/lhnet.c index c5759ad2..05fd9802 100644 --- a/lhnet.c +++ b/lhnet.c @@ -2,13 +2,16 @@ // Written by Ashley Rose Hale (LadyHavoc) 2003-06-15 and placed into public domain. #ifdef WIN32 -#ifdef _MSC_VER -#pragma comment(lib, "ws2_32.lib") -#endif +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# endif # ifndef NOSUPPORTIPV6 // Windows XP or higher is required for getaddrinfo, but the inclusion of wspiapi provides fallbacks for older versions -# define _WIN32_WINNT 0x0501 +# define _WIN32_WINNT 0x0501 # endif +// To increase FD_SETSIZE (defaults to 64 on Windows) +// it must be defined before the first inclusion of winsock2.h +# define FD_SETSIZE 1024 // Matches Linux and BSD defaults # include # include # ifdef USE_WSPIAPI_H @@ -716,7 +719,7 @@ typedef struct lhnetpacket_s lhnetpacket_t; static int lhnet_active; -static lhnetsocket_t lhnet_socketlist; +lhnetsocket_t lhnet_socketlist; static lhnetpacket_t lhnet_packetlist; static int lhnet_default_dscp = 0; #ifdef WIN32 @@ -831,36 +834,6 @@ static const char *LHNETPRIVATE_StrError(void) #endif } -void LHNET_SleepUntilPacket_Microseconds(int microseconds) -{ -#ifdef FD_SET - fd_set fdreadset; - struct timeval tv; - int lastfd; - lhnetsocket_t *s; - FD_ZERO(&fdreadset); - lastfd = 0; - List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list) - { - if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6) - { - if (lastfd < s->inetsocket) - lastfd = s->inetsocket; -#if defined(WIN32) && !defined(_MSC_VER) - FD_SET((int)s->inetsocket, &fdreadset); -#else - FD_SET((unsigned int)s->inetsocket, &fdreadset); -#endif - } - } - tv.tv_sec = microseconds / 1000000; - tv.tv_usec = microseconds % 1000000; - select(lastfd + 1, &fdreadset, NULL, NULL, &tv); -#else - Sys_Sleep(microseconds); -#endif -} - lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address) { lhnetsocket_t *lhnetsocket, *s; diff --git a/lhnet.h b/lhnet.h index 9fe1e532..e7ac0e48 100644 --- a/lhnet.h +++ b/lhnet.h @@ -46,6 +46,7 @@ typedef struct lhnetsocket_s llist_t list; } lhnetsocket_t; +extern lhnetsocket_t lhnet_socketlist; void LHNET_Init(void); void LHNET_Shutdown(void); diff --git a/libcurl.c b/libcurl.c index 31e331a1..65adee30 100644 --- a/libcurl.c +++ b/libcurl.c @@ -160,7 +160,7 @@ static const char * (*qcurl_easy_strerror) (CURLcode); static CURLM * (*qcurl_multi_init) (void); static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles); -static CURLMcode (*qcurl_multi_poll) (CURLM *multi_handle, void*, unsigned int extra_nfds, int timeout_ms, int *ret); +static CURLMcode (*qcurl_multi_wait) (CURLM *multi_handle, void*, unsigned int extra_nfds, int timeout_ms, int *ret); static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle); static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle); static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue); @@ -180,7 +180,7 @@ static dllfunction_t curlfuncs[] = {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo}, {"curl_multi_init", (void **) &qcurl_multi_init}, {"curl_multi_perform", (void **) &qcurl_multi_perform}, - {"curl_multi_poll", (void **) &qcurl_multi_poll}, + {"curl_multi_wait", (void **) &qcurl_multi_wait}, {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle}, {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle}, {"curl_multi_info_read", (void **) &qcurl_multi_info_read}, @@ -1254,16 +1254,25 @@ Curl_Select Sleeps until there's some transfer progress or a timeout is reached, unfortunately the timeout is only in milliseconds. This allows good throughput even at very low FPS. +Less important on newer libcurl versions but still helps. + +Returns 0 immediately if there's no transfers to wait for, +or > 0 if a transfer is ready or the timeout was reached. ==================== */ -void Curl_Select(double *microseconds) +int Curl_Select(uint32_t microseconds) { + CURLMcode err; + int numfds; + if (List_Is_Empty(&downloads)) - return; - if (qcurl_multi_poll(curlm, NULL, 0, *microseconds / 1000, NULL) == CURLM_OK) - *microseconds = 0; // either we finished waiting or a transfer progressed - else - Con_Print("There's an emergency going on!\nIt's still going on!\nMaybe you need to upgrade libcurl?\n"); + return 0; + + err = qcurl_multi_wait(curlm, NULL, 0, microseconds / 1000, &numfds); + if (err == CURLM_OK) + return numfds; + Con_Printf("curl_multi_wait() failed, code %d\n", err); + return 0; } /* diff --git a/libcurl.h b/libcurl.h index edf4c4a1..722981e2 100644 --- a/libcurl.h +++ b/libcurl.h @@ -14,7 +14,7 @@ typedef void (*curl_callback_t) (int status, size_t length_received, unsigned ch // code is one of the CURLCBSTATUS constants, or the HTTP error code (when > 0). void Curl_Frame(void); -void Curl_Select(double *microseconds); +int Curl_Select(uint32_t microseconds); qbool Curl_Running(void); qbool Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qbool forthismap); diff --git a/netconn.c b/netconn.c index 30328127..cb2992ea 100644 --- a/netconn.c +++ b/netconn.c @@ -3833,11 +3833,6 @@ void NetConn_ServerFrame(void) NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress); } -void NetConn_SleepMicroseconds(int microseconds) -{ - LHNET_SleepUntilPacket_Microseconds(microseconds); -} - #ifdef CONFIG_MENU void NetConn_QueryMasters(qbool querydp, qbool queryqw) { diff --git a/netconn.h b/netconn.h index 44669e2a..d39843df 100755 --- a/netconn.h +++ b/netconn.h @@ -446,7 +446,6 @@ int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnet int NetConn_IsLocalGame(void); void NetConn_ClientFrame(void); void NetConn_ServerFrame(void); -void NetConn_SleepMicroseconds(int microseconds); void NetConn_Heartbeat(int priority); void Net_Stats_f(struct cmd_state_s *cmd); diff --git a/sv_main.c b/sv_main.c index 0f4fb8fe..4f6c2426 100644 --- a/sv_main.c +++ b/sv_main.c @@ -2519,7 +2519,6 @@ const char *SV_TimingReport(char *buf, size_t buflen) return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", sv.perf_cpuload * 100, sv.perf_lost * 100, sv.perf_offset_avg * 1000, sv.perf_offset_max * 1000, sv.perf_offset_sdev * 1000); } -extern cvar_t host_maxwait; extern cvar_t host_framerate; double SV_Frame(double time) { @@ -2712,7 +2711,6 @@ static int SV_ThreadFunc(void *voiddata) qbool playing = false; double sv_timer = 0; double sv_deltarealtime, sv_oldrealtime, sv_realtime; - double wait; int i; char vabuf[1024]; sv_realtime = Sys_DirtyTime(); @@ -2771,21 +2769,10 @@ static int SV_ThreadFunc(void *voiddata) } // if the accumulators haven't become positive yet, wait a while - wait = sv_timer * -1000000.0; - if (wait >= 1) + if (sv_timer < 0) { - double time0, delta; SV_UnlockThreadMutex(); // don't keep mutex locked while sleeping - if (host_maxwait.value <= 0) - wait = min(wait, 1000000.0); - else - wait = min(wait, host_maxwait.value * 1000.0); - if(wait < 1) - wait = 1; // because we cast to int - time0 = Sys_DirtyTime(); - Sys_Sleep((int)wait); - delta = Sys_DirtyTime() - time0;if (delta < 0 || delta >= 1800) delta = 0; - sv.perf_acc_sleeptime += delta; + sv.perf_acc_sleeptime += Sys_Sleep(-sv_timer); continue; } diff --git a/sys.h b/sys.h index fc47cdba..41d595b9 100644 --- a/sys.h +++ b/sys.h @@ -150,7 +150,6 @@ typedef struct sys_s extern sys_t sys; -extern struct cvar_s sys_usenoclockbutbenchmark; // // DLL management @@ -240,7 +239,7 @@ void Sys_ProvideSelfFD (void); char *Sys_ConsoleInput (void); /// called to yield for a little bit so as not to hog cpu when paused or debugging -void Sys_Sleep(int microseconds); +double Sys_Sleep(double time); /// Perform Key_Event () callbacks until the input que is empty void Sys_SDL_HandleEvents(void); diff --git a/sys_shared.c b/sys_shared.c index 39c9bf8b..d1c4edde 100644 --- a/sys_shared.c +++ b/sys_shared.c @@ -7,6 +7,7 @@ #include "quakedef.h" #include "taskqueue.h" #include "thread.h" +#include "libcurl.h" #define SUPPORTDLL @@ -286,12 +287,8 @@ void* Sys_GetProcAddress (dllhandle_t handle, const char* name) # define HAVE_GETTIMEOFDAY 1 #endif -#ifndef WIN32 -// on Win32, select() cannot be used with all three FD list args being NULL according to MSDN -// (so much for POSIX...) -# ifdef FD_SET -# define HAVE_SELECT 1 -# endif +#ifdef FD_SET +# define HAVE_SELECT 1 #endif #ifndef WIN32 @@ -300,18 +297,18 @@ void* Sys_GetProcAddress (dllhandle_t handle, const char* name) #endif // these are referenced elsewhere -cvar_t sys_usenoclockbutbenchmark = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."}; -cvar_t sys_libdir = {CF_READONLY | CF_CLIENT | CF_SERVER, "sys_libdir", "", "Default engine library directory"}; +cvar_t sys_usenoclockbutbenchmark = {CF_SHARED, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."}; +cvar_t sys_libdir = {CF_READONLY | CF_SHARED, "sys_libdir", "", "Default engine library directory"}; // these are not -static cvar_t sys_debugsleep = {CF_CLIENT | CF_SERVER, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"}; -static cvar_t sys_usesdlgetticks = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"}; -static cvar_t sys_usesdldelay = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"}; +static cvar_t sys_debugsleep = {CF_SHARED, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"}; +static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"}; +static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"}; #if HAVE_QUERYPERFORMANCECOUNTER -static cvar_t sys_usequeryperformancecounter = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"}; +static cvar_t sys_usequeryperformancecounter = {CF_SHARED | CF_ARCHIVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"}; #endif #if HAVE_CLOCKGETTIME -static cvar_t sys_useclockgettime = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sys_useclockgettime", "1", "use POSIX clock_gettime function (not adjusted by NTP on some older Linux kernels) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"}; +static cvar_t sys_useclockgettime = {CF_SHARED | CF_ARCHIVE, "sys_useclockgettime", "1", "use POSIX clock_gettime function (not adjusted by NTP on some older Linux kernels) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"}; #endif static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow" @@ -455,57 +452,92 @@ double Sys_DirtyTime(void) #endif } -void Sys_Sleep(int microseconds) +extern cvar_t host_maxwait; +double Sys_Sleep(double time) { - double t = 0; + double dt; + uint32_t microseconds; + + // convert to microseconds + time *= 1000000.0; + + if(host_maxwait.value <= 0) + time = min(time, 1000000.0); + else + time = min(time, host_maxwait.value * 1000.0); + + if (time < 1 || host.restless) + return 0; // not sleeping this frame + + microseconds = time; // post-validation to prevent overflow + if(sys_usenoclockbutbenchmark.integer) { - if(microseconds) - { - double old_benchmark_time = benchmark_time; - benchmark_time += microseconds; - if(benchmark_time == old_benchmark_time) - Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); - } - return; + double old_benchmark_time = benchmark_time; + benchmark_time += microseconds; + if(benchmark_time == old_benchmark_time) + Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); + return 0; } + if(sys_debugsleep.integer) + Sys_Printf("sys_debugsleep: requesting %u ", microseconds); + dt = Sys_DirtyTime(); + + // less important on newer libcurl so no need to disturb dedicated servers + if (cls.state != ca_dedicated && Curl_Select(microseconds)) { - t = Sys_DirtyTime(); + // a transfer is ready or we finished sleeping } - if(sys_supportsdlgetticks && sys_usesdldelay.integer) - { + else if(sys_supportsdlgetticks && sys_usesdldelay.integer) Sys_SDL_Delay(microseconds / 1000); - } #if HAVE_SELECT else { struct timeval tv; + lhnetsocket_t *s; + fd_set fdreadset; + int lastfd = -1; + + FD_ZERO(&fdreadset); + if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer) + { + List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list) + { + if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6) + { + if (lastfd < s->inetsocket) + lastfd = s->inetsocket; + #if defined(WIN32) && !defined(_MSC_VER) + FD_SET((int)s->inetsocket, &fdreadset); + #else + FD_SET((unsigned int)s->inetsocket, &fdreadset); + #endif + } + } + } tv.tv_sec = microseconds / 1000000; tv.tv_usec = microseconds % 1000000; - select(0, NULL, NULL, NULL, &tv); + // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN + // (so much for POSIX...) + // bones_was_here: but a zeroed fd_set seems to be tolerated (tested on Win 7) + select(lastfd + 1, &fdreadset, NULL, NULL, &tv); } #elif HAVE_USLEEP else - { usleep(microseconds); - } #elif HAVE_Sleep else - { Sleep(microseconds / 1000); - } #else else - { Sys_SDL_Delay(microseconds / 1000); - } #endif + + dt = Sys_DirtyTime() - dt; if(sys_debugsleep.integer) - { - t = Sys_DirtyTime() - t; - Sys_Printf("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000)); - } + Sys_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - microseconds); + return (dt < 0 || dt >= 1800) ? 0 : dt; } void Sys_Printf(const char *fmt, ...) diff --git a/taskqueue.c b/taskqueue.c index e2445d43..175a28af 100644 --- a/taskqueue.c +++ b/taskqueue.c @@ -96,7 +96,7 @@ static int TaskQueue_ThreadFunc(void *d) break; sleepcounter++; if (sleepcounter >= THREADSLEEPCOUNT) - Sys_Sleep(1000); + Sys_Sleep(0.001); sleepcounter = 0; } return 0; -- 2.39.2