]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sys_shared.c
CONTRIBUTING: Fix typos
[xonotic/darkplaces.git] / sys_shared.c
index 7ce69c3132be521c6173760e6df715c5bc2c6d4e..4222c42be414c88836cdfe48a87c73993482b554 100644 (file)
@@ -268,42 +268,36 @@ void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
 #endif
 }
 
+
+
 #ifdef WIN32
 # define HAVE_TIMEGETTIME 1
 # define HAVE_QUERYPERFORMANCECOUNTER 1
+# define HAVE_WIN32_USLEEP 1
 # define HAVE_Sleep 1
-#endif
-
-#ifndef WIN32
-#if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
-# define HAVE_CLOCKGETTIME 1
-#endif
-// FIXME improve this check, manpage hints to DST_NONE
-# define HAVE_GETTIMEOFDAY 1
+#else
+# if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
+#  define HAVE_CLOCKGETTIME 1
+# endif
+# if _POSIX_VERSION >= 200112L
+#  define HAVE_CLOCK_NANOSLEEP 1
+# endif
 #endif
 
 #ifdef FD_SET
 # define HAVE_SELECT 1
 #endif
 
-#ifndef WIN32
-// FIXME improve this check
-# define HAVE_USLEEP 1
-#endif
-
 // these are referenced elsewhere
 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_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)"};
+static cvar_t sys_usesdlgetticks = {CF_SHARED, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (low precision, for debugging)"};
+static cvar_t sys_usesdldelay = {CF_SHARED, "sys_usesdldelay", "0", "use SDL_Delay() (low precision, for debugging)"};
 #if HAVE_QUERYPERFORMANCECOUNTER
-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_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)"};
+static cvar_t sys_usequeryperformancecounter = {CF_SHARED | CF_ARCHIVE, "sys_usequeryperformancecounter", "1", "use windows QueryPerformanceCounter timer (which has issues on systems lacking constant-rate TSCs synchronised across all cores, such as ancient PCs or VMs) for timing rather than timeGetTime function (which is low precision and had issues on some old motherboards)"};
 #endif
 
 static cvar_t sys_stdout = {CF_SHARED, "sys_stdout", "1", "0: nothing is written to stdout (-nostdout cmdline option sets this), 1: normal messages are written to stdout, 2: normal messages are written to stderr (-stderr cmdline option sets this)"};
@@ -352,7 +346,7 @@ void Sys_Init_Commands (void)
        Cvar_RegisterVariable(&sys_debugsleep);
        Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
        Cvar_RegisterVariable(&sys_libdir);
-#if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
+#if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME
        if(sys_supportsdlgetticks)
        {
                Cvar_RegisterVariable(&sys_usesdlgetticks);
@@ -362,9 +356,7 @@ void Sys_Init_Commands (void)
 #if HAVE_QUERYPERFORMANCECOUNTER
        Cvar_RegisterVariable(&sys_usequeryperformancecounter);
 #endif
-#if HAVE_CLOCKGETTIME
-       Cvar_RegisterVariable(&sys_useclockgettime);
-#endif
+
        Cvar_RegisterVariable(&sys_stdout);
        Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
 #ifndef WIN32
@@ -372,6 +364,47 @@ void Sys_Init_Commands (void)
 #endif
 }
 
+#ifdef WIN32
+static LARGE_INTEGER PerformanceFreq;
+/// Windows default timer resolution is only 15.625ms,
+/// this affects (at least) timeGetTime() and all forms of sleeping.
+static void Sys_SetTimerResolution(void)
+{
+       NTSTATUS(NTAPI *qNtQueryTimerResolution)(OUT PULONG MinRes, OUT PULONG MaxRes, OUT PULONG CurrentRes);
+       NTSTATUS(NTAPI *qNtSetTimerResolution)(IN ULONG DesiredRes, IN BOOLEAN SetRes, OUT PULONG ActualRes);
+       const char* ntdll_names [] =
+       {
+               "ntdll.dll",
+               NULL
+       };
+       dllfunction_t ntdll_funcs[] =
+       {
+               {"NtQueryTimerResolution", (void **) &qNtQueryTimerResolution},
+               {"NtSetTimerResolution",   (void **) &qNtSetTimerResolution},
+               {NULL, NULL}
+       };
+       dllhandle_t ntdll;
+       unsigned long WorstRes, BestRes, CurrentRes;
+
+       timeBeginPeriod(1); // 1ms, documented
+
+       // the best Windows can manage (typically 0.5ms)
+       // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
+       if (Sys_LoadDependency(ntdll_names, &ntdll, ntdll_funcs))
+       {
+               qNtQueryTimerResolution(&WorstRes, &BestRes, &CurrentRes); // no pointers may be NULL
+               if (CurrentRes > BestRes)
+                       qNtSetTimerResolution(BestRes, true, &CurrentRes);
+
+               Sys_FreeLibrary(&ntdll);
+       }
+
+       // Microsoft says the freq is fixed at boot and consistent across all processors
+       // and that it need only be queried once and cached.
+       QueryPerformanceFrequency (&PerformanceFreq);
+}
+#endif // WIN32
+
 double Sys_DirtyTime(void)
 {
        // first all the OPTIONAL timers
@@ -382,31 +415,32 @@ double Sys_DirtyTime(void)
                double old_benchmark_time = benchmark_time;
                benchmark_time += 1;
                if(benchmark_time == old_benchmark_time)
-                       Sys_Abort("sys_usenoclockbutbenchmark cannot run any longer, sorry");
+                       Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
                return benchmark_time * 0.000001;
        }
+
+       if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
+               return (double) Sys_SDL_GetTicks() / 1000.0;
+
 #if HAVE_QUERYPERFORMANCECOUNTER
        if (sys_usequeryperformancecounter.integer)
        {
-               // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
                // QueryPerformanceCounter
                // platform:
                // Windows 95/98/ME/NT/2000/XP
                // features:
-               // very accurate (CPU cycles)
+               // + very accurate (constant-rate TSCs on modern systems)
                // known issues:
-               // does not necessarily match realtime too well (tends to get faster and faster in win98)
-               // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
-               double timescale;
-               LARGE_INTEGER PerformanceFreq;
+               // does not necessarily match realtime too well (tends to get faster and faster in win98)
+               // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
+               // - higher access latency on Vista
+               // Microsoft says on Win 7 or later, latency and overhead are very low, synchronisation is excellent.
                LARGE_INTEGER PerformanceCount;
 
-               if (QueryPerformanceFrequency (&PerformanceFreq))
+               if (PerformanceFreq.QuadPart)
                {
                        QueryPerformanceCounter (&PerformanceCount);
-       
-                       timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
-                       return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
+                       return (double)PerformanceCount.QuadPart * (1.0 / (double)PerformanceFreq.QuadPart);
                }
                else
                {
@@ -418,11 +452,13 @@ double Sys_DirtyTime(void)
 #endif
 
 #if HAVE_CLOCKGETTIME
-       if (sys_useclockgettime.integer)
        {
                struct timespec ts;
-#  ifdef CLOCK_MONOTONIC
-               // linux
+#  ifdef CLOCK_MONOTONIC_RAW
+               // Linux-specific, SDL_GetPerformanceCounter() uses it
+               clock_gettime(CLOCK_MONOTONIC, &ts);
+#  elif defined(CLOCK_MONOTONIC)
+               // POSIX
                clock_gettime(CLOCK_MONOTONIC, &ts);
 #  else
                // sunos
@@ -433,17 +469,8 @@ double Sys_DirtyTime(void)
 #endif
 
        // now all the FALLBACK timers
-       if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
-               return (double) Sys_SDL_GetTicks() / 1000.0;
-#if HAVE_GETTIMEOFDAY
+#if HAVE_TIMEGETTIME
        {
-               struct timeval tp;
-               gettimeofday(&tp, NULL);
-               return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
-       }
-#elif HAVE_TIMEGETTIME
-       {
-               static int firsttimegettime = true;
                // timeGetTime
                // platform:
                // Windows 95/98/ME/NT/2000/XP
@@ -451,64 +478,51 @@ double Sys_DirtyTime(void)
                // reasonable accuracy (millisecond)
                // issues:
                // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
-
-               // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
-               if (firsttimegettime)
-               {
-                       timeBeginPeriod(1);
-                       firsttimegettime = false;
-               }
-
+               // requires Sys_SetTimerResolution()
                return (double) timeGetTime() / 1000.0;
        }
 #else
        // fallback for using the SDL timer if no other timer is available
-       // this calls Sys_Abort() if not linking against SDL
+       // this calls Sys_Error() if not linking against SDL
        return (double) Sys_SDL_GetTicks() / 1000.0;
 #endif
 }
 
-extern cvar_t host_maxwait;
 double Sys_Sleep(double time)
 {
        double dt;
-       uint32_t microseconds;
+       uint32_t msec, usec, nsec;
 
-       // 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)
+       if (time < 1.0/1000000.0 || host.restless)
                return 0; // not sleeping this frame
-
-       microseconds = time; // post-validation to prevent overflow
+       if (time >= 1)
+               time = 0.999999; // ensure passed values are in range
+       msec = time * 1000;
+       usec = time * 1000000;
+       nsec = time * 1000000000;
 
        if(sys_usenoclockbutbenchmark.integer)
        {
                double old_benchmark_time = benchmark_time;
-               benchmark_time += microseconds;
+               benchmark_time += usec;
                if(benchmark_time == old_benchmark_time)
-                       Sys_Abort("sys_usenoclockbutbenchmark cannot run any longer, sorry");
+                       Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
                return 0;
        }
 
        if(sys_debugsleep.integer)
-               Con_Printf("sys_debugsleep: requesting %u ", microseconds);
+               Con_Printf("sys_debugsleep: requesting %u ", usec);
        dt = Sys_DirtyTime();
 
        // less important on newer libcurl so no need to disturb dedicated servers
-       if (cls.state != ca_dedicated && Curl_Select(microseconds))
+       if (cls.state != ca_dedicated && Curl_Select(msec))
        {
                // a transfer is ready or we finished sleeping
        }
        else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
-               Sys_SDL_Delay(microseconds / 1000);
+               Sys_SDL_Delay(msec);
 #if HAVE_SELECT
-       else
+       else if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
        {
                struct timeval tv;
                lhnetsocket_t *s;
@@ -516,43 +530,58 @@ double Sys_Sleep(double time)
                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)
                {
-                       List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
+                       if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
                        {
-                               if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
-                               {
-                                       if (lastfd < s->inetsocket)
-                                               lastfd = s->inetsocket;
+                               if (lastfd < s->inetsocket)
+                                       lastfd = s->inetsocket;
        #if defined(WIN32) && !defined(_MSC_VER)
-                                       FD_SET((int)s->inetsocket, &fdreadset);
+                               FD_SET((int)s->inetsocket, &fdreadset);
        #else
-                                       FD_SET((unsigned int)s->inetsocket, &fdreadset);
+                               FD_SET((unsigned int)s->inetsocket, &fdreadset);
        #endif
-                               }
                        }
                }
-               tv.tv_sec = microseconds / 1000000;
-               tv.tv_usec = microseconds % 1000000;
+               tv.tv_sec = 0;
+               tv.tv_usec = usec;
                // 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)
+               // (so much for POSIX...), not with an empty fd_set either.
                select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
        }
-#elif HAVE_USLEEP
+#endif
+#if HAVE_CLOCK_NANOSLEEP
+       else
+       {
+               struct timespec ts;
+               ts.tv_sec = 0;
+               ts.tv_nsec = nsec;
+               clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
+       }
+#elif HAVE_WIN32_USLEEP // Windows XP/2003 minimum
        else
-               usleep(microseconds);
+       {
+               HANDLE timer;
+               LARGE_INTEGER sleeptime;
+
+               // takes 100ns units, negative indicates relative time
+               sleeptime.QuadPart = -((int64_t)nsec / 100);
+               timer = CreateWaitableTimer(NULL, true, NULL);
+               SetWaitableTimer(timer, &sleeptime, 0, NULL, NULL, 0);
+               WaitForSingleObject(timer, INFINITE);
+               CloseHandle(timer);
+       }
 #elif HAVE_Sleep
        else
-               Sleep(microseconds / 1000);
+               Sleep(msec);
 #else
        else
-               Sys_SDL_Delay(microseconds / 1000);
+               Sys_SDL_Delay(msec);
 #endif
 
        dt = Sys_DirtyTime() - dt;
        if(sys_debugsleep.integer)
-               Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - microseconds);
+               Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - usec);
        return (dt < 0 || dt >= 1800) ? 0 : dt;
 }
 
@@ -674,12 +703,15 @@ Startup and Shutdown
 ===============================================================================
 */
 
-void Sys_Abort (const char *error, ...)
+void Sys_Error (const char *error, ...)
 {
        va_list argptr;
        char string[MAX_INPUTLINE];
        int i;
 
+       // Disable Sys_HandleSignal() but not Sys_HandleCrash()
+       host.state = host_shutdown;
+
        // set output to blocking stderr
        sys.outfd = fileno(stderr);
 #ifndef WIN32
@@ -690,7 +722,7 @@ void Sys_Abort (const char *error, ...)
        dpvsnprintf (string, sizeof (string), error, argptr);
        va_end (argptr);
 
-       Con_Printf(CON_ERROR "Engine Abort: %s\n^9%s\n", string, engineversion);
+       Con_Printf(CON_ERROR "Engine Aborted: %s\n^9%s\n", string, engineversion);
 
        dp_strlcat(string, "\n\n", sizeof(string));
        dp_strlcat(string, engineversion, sizeof(string));
@@ -706,17 +738,16 @@ void Sys_Abort (const char *error, ...)
                sv.active = false; // make SV_DropClient() skip the QC stuff to avoid recursive errors
                for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
                        if (host_client->active)
-                               SV_DropClient(false, "Server abort!"); // closes demo file
+                               SV_DropClient(false, "Server aborted!"); // closes demo file
        }
        // don't want a dead window left blocking the OS UI or the abort dialog
        VID_Shutdown();
        S_StopAllSounds();
 
-       host.state = host_failed; // make Sys_HandleSignal() call exit()
-       Sys_SDL_Dialog("Engine Abort", string);
+       host.state = host_failed; // make Sys_HandleSignal() call _Exit()
+       Sys_SDL_Dialog("Engine Aborted", string);
 
        fflush(stderr);
-
        exit (1);
 }
 
@@ -952,7 +983,14 @@ static void Sys_HandleCrash(int sig)
        char **btstrings;
 #endif
        char dialogtext[3072];
-       const char *sigdesc = Sys_SigDesc(sig);
+       const char *sigdesc;
+
+       // Break any loop and disable Sys_HandleSignal()
+       if (host.state == host_failing || host.state == host_failed)
+               return;
+       host.state = host_failing;
+
+       sigdesc = Sys_SigDesc(sig);
 
        // set output to blocking stderr and print header, backtrace, version
        sys.outfd = fileno(stderr); // not async-signal-safe :(
@@ -976,7 +1014,7 @@ static void Sys_HandleCrash(int sig)
        Sys_Print("\n", 1);
 #endif
 
-       // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Abort() and Host_Error()
+       // DP8 TODO: send a disconnect message indicating we crashed, see Sys_Error() and Host_Error()
 
        // don't want a dead window left blocking the OS UI or the crash dialog
        VID_Shutdown();
@@ -1002,18 +1040,28 @@ static void Sys_HandleCrash(int sig)
        Sys_SDL_Dialog("Engine Crash", dialogtext);
 
        fflush(stderr); // not async-signal-safe :(
-       _Exit(sig);
+
+       // Continue execution with default signal handling.
+       // A real crash will be re-triggered so the platform can handle it,
+       // a fake crash (kill -SEGV) will cause a graceful shutdown.
+       signal(sig, SIG_DFL);
 }
 
 static void Sys_HandleSignal(int sig)
 {
-       const char *sigdesc = Sys_SigDesc(sig);
+       const char *sigdesc;
+
+       // Break any loop, eg if each Sys_Print triggers a SIGPIPE
+       if (host.state == host_shutdown || host.state == host_failing)
+               return;
+
+       sigdesc = Sys_SigDesc(sig);
        Sys_Print("\nReceived ", 10);
        Sys_Print(sigdesc, strlen(sigdesc));
        Sys_Print(" signal, exiting...\n", 20);
        if (host.state == host_failed)
        {
-               // user is trying to kill the process while the dialog is open
+               // user is trying to kill the process while the SDL dialog is open
                fflush(stderr); // not async-signal-safe :(
                _Exit(sig);
        }
@@ -1039,7 +1087,11 @@ static void Sys_InitSignals(void)
 #endif
 }
 
-int main (int argc, char **argv)
+/** main() but renamed so we can wrap it in sys_sdl.c and sys_null.c
+ * to avoid needing to include SDL.h in this file (would make the dedicated server require SDL).
+ * SDL builds need SDL.h in the file where main() is defined because SDL renames and wraps main().
+ */
+int Sys_Main(int argc, char *argv[])
 {
        sys.argc = argc;
        sys.argv = (const char **)argv;
@@ -1070,6 +1122,10 @@ int main (int argc, char **argv)
 
        Sys_InitSignals();
 
+#ifdef WIN32
+       Sys_SetTimerResolution();
+#endif
+
        Host_Main();
 
 #ifdef __ANDROID__