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>
// 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
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)
{
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;
// 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
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
#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;
llist_t list;
}
lhnetsocket_t;
+extern lhnetsocket_t lhnet_socketlist;
void LHNET_Init(void);
void LHNET_Shutdown(void);
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);
{"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},
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;
}
/*
// 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);
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)
{
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);
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)
{
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();
}
// 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;
}
extern sys_t sys;
-extern struct cvar_s sys_usenoclockbutbenchmark;
//
// DLL management
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);
#include "quakedef.h"
#include "taskqueue.h"
#include "thread.h"
+#include "libcurl.h"
#define SUPPORTDLL
# 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
#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"
#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, ...)
break;
sleepcounter++;
if (sleepcounter >= THREADSLEEPCOUNT)
- Sys_Sleep(1000);
+ Sys_Sleep(0.001);
sleepcounter = 0;
}
return 0;