]> de.git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Sys_Sleep overhaul
authorbones_was_here <bones_was_here@xonotic.au>
Sun, 26 Nov 2023 01:17:53 +0000 (11:17 +1000)
committerbones_was_here <bones_was_here@xonotic.au>
Sat, 9 Dec 2023 10:13:33 +0000 (20:13 +1000)
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 <bones_was_here@xonotic.au>
12 files changed:
cl_main.c
host.c
lhnet.c
lhnet.h
libcurl.c
libcurl.h
netconn.c
netconn.h
sv_main.c
sys.h
sys_shared.c
taskqueue.c

index 64e711c79ab349f52b6d6b7c4c0b0e93e352fd17..218f7b2451ca7cfca81b05c471d265d0ce52975d 100644 (file)
--- 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 924e655e200bb679da3afd9478df5ea69a21e3f2..3e2c5f99075ab8e15934295b65bf84bde41d5c90 100644 (file)
--- 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 c5759ad29275fc50dd1930f17daac01949e39684..05fd9802a971edc6daa1ad4840291ef3e0f279b9 100644 (file)
--- 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 <winsock2.h>
 # include <ws2tcpip.h>
 # 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 9fe1e532665d09ee08184e9c427203caca30ecf1..e7ac0e48294dc79878e01b17c5f2bb16b48552b7 100644 (file)
--- 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);
index 31e331a1170e1faadf75f915a4e3d1c3f084ce77..65adee3081b919d6bf814be533ac260d4c5af4e6 100644 (file)
--- 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;
 }
 
 /*
index edf4c4a10cf9f676d0b3732df4235826ace0740a..722981e2f7b914703df2baf536f6a03cd9c4dbbe 100644 (file)
--- 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);
 
index 30328127f1d2ad09fd38af9608756fdaf8c1ac92..cb2992ea06700ac32d23be131f006269bd0c289e 100644 (file)
--- 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)
 {
index 44669e2af809cbc58f4f6f801da25f5cd6f8ea86..d39843df0ab3b01da3f6057e3293729535704ab6 100755 (executable)
--- 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);
 
index 0f4fb8fe8cd0ac638bcc00a0b3b1de0b5ca12f55..4f6c2426f72275c75f157c14b8892bd3fd82c973 100644 (file)
--- 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 fc47cdba53266d5ea804d8d806041af25b647c85..41d595b927785c41770fc1a5f8a4b8d5cde037c5 100644 (file)
--- 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);
index 39c9bf8bf4e1507d37a0d59b831e078dae5c63a9..d1c4eddea00951805c54758347a1b5d21f42ee3a 100644 (file)
@@ -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, ...)
index e2445d43cb621dad52c831fd71576ad27656478e..175a28af7c21c74aea3562ea4423b99c4c701a24 100644 (file)
@@ -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;