]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
Split out TaskQueue into its own taskqueue.[ch] module, it doesn't depend on SDL2...
[xonotic/darkplaces.git] / sys_shared.c
1 #ifdef WIN32
2 # ifndef DONT_USE_SETDLLDIRECTORY
3 #  define _WIN32_WINNT 0x0502
4 # endif
5 #endif
6
7 #include "quakedef.h"
8 #include "taskqueue.h"
9 #include "thread.h"
10
11 #define SUPPORTDLL
12
13 #ifdef WIN32
14 # include <windows.h>
15 # include <mmsystem.h> // timeGetTime
16 # include <time.h> // localtime
17 #ifdef _MSC_VER
18 #pragma comment(lib, "winmm.lib")
19 #endif
20 #else
21 # include <unistd.h>
22 # include <fcntl.h>
23 # include <sys/time.h>
24 # include <time.h>
25 # ifdef SUPPORTDLL
26 #  include <dlfcn.h>
27 # endif
28 #endif
29
30 static char sys_timestring[128];
31 char *Sys_TimeString(const char *timeformat)
32 {
33         time_t mytime = time(NULL);
34 #if _MSC_VER >= 1400
35         struct tm mytm;
36         localtime_s(&mytm, &mytime);
37         strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
38 #else
39         strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
40 #endif
41         return sys_timestring;
42 }
43
44
45 extern qboolean host_shuttingdown;
46 void Sys_Quit (int returnvalue)
47 {
48         // Unlock mutexes because the quit command may jump directly here, causing a deadlock
49         Cbuf_UnlockThreadMutex();
50         SV_UnlockThreadMutex();
51         TaskQueue_Frame(true);
52
53         if (COM_CheckParm("-profilegameonly"))
54                 Sys_AllowProfiling(false);
55         host_shuttingdown = true;
56         Host_Shutdown();
57         exit(returnvalue);
58 }
59
60 #ifdef __cplusplus
61 extern "C"
62 #endif
63 void Sys_AllowProfiling(qboolean enable)
64 {
65 #ifdef __ANDROID__
66 #ifdef USE_PROFILER
67         extern void monstartup(const char *libname);
68         extern void moncleanup(void);
69         if (enable)
70                 monstartup("libmain.so");
71         else
72                 moncleanup();
73 #endif
74 #elif defined(__linux__) || defined(__FreeBSD__)
75         extern int moncontrol(int);
76         moncontrol(enable);
77 #endif
78 }
79
80
81 /*
82 ===============================================================================
83
84 DLL MANAGEMENT
85
86 ===============================================================================
87 */
88
89 static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next)
90 {
91         const dllfunction_t *func;
92         if(dllhandle)
93         {
94                 for (func = fcts; func && func->name != NULL; func++)
95                         if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
96                         {
97                                 if(complain)
98                                 {
99                                         Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
100                                         if(has_next)
101                                                 Con_DPrintf("\nContinuing with");
102                                 }
103                                 goto notfound;
104                         }
105                 return true;
106
107         notfound:
108                 for (func = fcts; func && func->name != NULL; func++)
109                         *func->funcvariable = NULL;
110         }
111         return false;
112 }
113
114 qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
115 {
116 #ifdef SUPPORTDLL
117         const dllfunction_t *func;
118         dllhandle_t dllhandle = 0;
119         unsigned int i;
120
121         if (handle == NULL)
122                 return false;
123
124 #ifndef WIN32
125 #ifdef PREFER_PRELOAD
126         dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
127         if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false))
128         {
129                 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
130                 *handle = dllhandle;
131                 return true;
132         }
133         else
134                 Sys_UnloadLibrary(&dllhandle);
135 notfound:
136 #endif
137 #endif
138
139         // Initializations
140         for (func = fcts; func && func->name != NULL; func++)
141                 *func->funcvariable = NULL;
142
143         // Try every possible name
144         Con_DPrintf ("Trying to load library...");
145         for (i = 0; dllnames[i] != NULL; i++)
146         {
147                 Con_DPrintf (" \"%s\"", dllnames[i]);
148 #ifdef WIN32
149 # ifndef DONT_USE_SETDLLDIRECTORY
150 #  ifdef _WIN64
151                 SetDllDirectory("bin64");
152 #  else
153                 SetDllDirectory("bin32");
154 #  endif
155 # endif
156                 dllhandle = LoadLibrary (dllnames[i]);
157                 // no need to unset this - we want ALL dlls to be loaded from there, anyway
158 #else
159                 dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL);
160 #endif
161                 if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(com_argv[0], '/'))))
162                         break;
163                 else
164                         Sys_UnloadLibrary (&dllhandle);
165         }
166
167         // see if the names can be loaded relative to the executable path
168         // (this is for Mac OSX which does not check next to the executable)
169         if (!dllhandle && strrchr(com_argv[0], '/'))
170         {
171                 char path[MAX_OSPATH];
172                 strlcpy(path, com_argv[0], sizeof(path));
173                 strrchr(path, '/')[1] = 0;
174                 for (i = 0; dllnames[i] != NULL; i++)
175                 {
176                         char temp[MAX_OSPATH];
177                         strlcpy(temp, path, sizeof(temp));
178                         strlcat(temp, dllnames[i], sizeof(temp));
179                         Con_DPrintf (" \"%s\"", temp);
180 #ifdef WIN32
181                         dllhandle = LoadLibrary (temp);
182 #else
183                         dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL);
184 #endif
185                         if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL))
186                                 break;
187                         else
188                                 Sys_UnloadLibrary (&dllhandle);
189                 }
190         }
191
192         // No DLL found
193         if (! dllhandle)
194         {
195                 Con_DPrintf(" - failed.\n");
196                 return false;
197         }
198
199         Con_DPrintf(" - loaded.\n");
200
201         *handle = dllhandle;
202         return true;
203 #else
204         return false;
205 #endif
206 }
207
208 void Sys_UnloadLibrary (dllhandle_t* handle)
209 {
210 #ifdef SUPPORTDLL
211         if (handle == NULL || *handle == NULL)
212                 return;
213
214 #ifdef WIN32
215         FreeLibrary (*handle);
216 #else
217         dlclose (*handle);
218 #endif
219
220         *handle = NULL;
221 #endif
222 }
223
224 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
225 {
226 #ifdef SUPPORTDLL
227 #ifdef WIN32
228         return (void *)GetProcAddress (handle, name);
229 #else
230         return (void *)dlsym (handle, name);
231 #endif
232 #else
233         return NULL;
234 #endif
235 }
236
237 #ifdef WIN32
238 # define HAVE_TIMEGETTIME 1
239 # define HAVE_QUERYPERFORMANCECOUNTER 1
240 # define HAVE_Sleep 1
241 #endif
242
243 #ifndef WIN32
244 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
245 # define HAVE_CLOCKGETTIME 1
246 #endif
247 // FIXME improve this check, manpage hints to DST_NONE
248 # define HAVE_GETTIMEOFDAY 1
249 #endif
250
251 #ifndef WIN32
252 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
253 // (so much for POSIX...)
254 # ifdef FD_SET
255 #  define HAVE_SELECT 1
256 # endif
257 #endif
258
259 #ifndef WIN32
260 // FIXME improve this check
261 # define HAVE_USLEEP 1
262 #endif
263
264 // this one is referenced elsewhere
265 cvar_t sys_usenoclockbutbenchmark = {CVAR_SAVE, "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."};
266
267 // these are not
268 static cvar_t sys_debugsleep = {0, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
269 static cvar_t sys_usesdlgetticks = {CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"};
270 static cvar_t sys_usesdldelay = {CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"};
271 #if HAVE_QUERYPERFORMANCECOUNTER
272 static cvar_t sys_usequeryperformancecounter = {CVAR_SAVE, "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)"};
273 #endif
274 #if HAVE_CLOCKGETTIME
275 static cvar_t sys_useclockgettime = {CVAR_SAVE, "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)"};
276 #endif
277
278 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
279
280 void Sys_Init_Commands (void)
281 {
282         Cvar_RegisterVariable(&sys_debugsleep);
283         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
284 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
285         if(sys_supportsdlgetticks)
286         {
287                 Cvar_RegisterVariable(&sys_usesdlgetticks);
288                 Cvar_RegisterVariable(&sys_usesdldelay);
289         }
290 #endif
291 #if HAVE_QUERYPERFORMANCECOUNTER
292         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
293 #endif
294 #if HAVE_CLOCKGETTIME
295         Cvar_RegisterVariable(&sys_useclockgettime);
296 #endif
297 }
298
299 double Sys_DirtyTime(void)
300 {
301         // first all the OPTIONAL timers
302
303         // benchmark timer (fake clock)
304         if(sys_usenoclockbutbenchmark.integer)
305         {
306                 double old_benchmark_time = benchmark_time;
307                 benchmark_time += 1;
308                 if(benchmark_time == old_benchmark_time)
309                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
310                 return benchmark_time * 0.000001;
311         }
312 #if HAVE_QUERYPERFORMANCECOUNTER
313         if (sys_usequeryperformancecounter.integer)
314         {
315                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
316                 // QueryPerformanceCounter
317                 // platform:
318                 // Windows 95/98/ME/NT/2000/XP
319                 // features:
320                 // very accurate (CPU cycles)
321                 // known issues:
322                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
323                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
324                 double timescale;
325                 LARGE_INTEGER PerformanceFreq;
326                 LARGE_INTEGER PerformanceCount;
327
328                 if (QueryPerformanceFrequency (&PerformanceFreq))
329                 {
330                         QueryPerformanceCounter (&PerformanceCount);
331         
332                         #ifdef __BORLANDC__
333                         timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0);
334                         return ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale;
335                         #else
336                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
337                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
338                         #endif
339                 }
340                 else
341                 {
342                         Con_Printf("No hardware timer available\n");
343                         // fall back to other clock sources
344                         Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
345                 }
346         }
347 #endif
348
349 #if HAVE_CLOCKGETTIME
350         if (sys_useclockgettime.integer)
351         {
352                 struct timespec ts;
353 #  ifdef CLOCK_MONOTONIC
354                 // linux
355                 clock_gettime(CLOCK_MONOTONIC, &ts);
356 #  else
357                 // sunos
358                 clock_gettime(CLOCK_HIGHRES, &ts);
359 #  endif
360                 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
361         }
362 #endif
363
364         // now all the FALLBACK timers
365         if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
366                 return (double) Sys_SDL_GetTicks() / 1000.0;
367 #if HAVE_GETTIMEOFDAY
368         {
369                 struct timeval tp;
370                 gettimeofday(&tp, NULL);
371                 return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
372         }
373 #elif HAVE_TIMEGETTIME
374         {
375                 static int firsttimegettime = true;
376                 // timeGetTime
377                 // platform:
378                 // Windows 95/98/ME/NT/2000/XP
379                 // features:
380                 // reasonable accuracy (millisecond)
381                 // issues:
382                 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
383
384                 // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
385                 if (firsttimegettime)
386                 {
387                         timeBeginPeriod(1);
388                         firsttimegettime = false;
389                 }
390
391                 return (double) timeGetTime() / 1000.0;
392         }
393 #else
394         // fallback for using the SDL timer if no other timer is available
395         // this calls Sys_Error() if not linking against SDL
396         return (double) Sys_SDL_GetTicks() / 1000.0;
397 #endif
398 }
399
400 void Sys_Sleep(int microseconds)
401 {
402         double t = 0;
403         if(sys_usenoclockbutbenchmark.integer)
404         {
405                 if(microseconds)
406                 {
407                         double old_benchmark_time = benchmark_time;
408                         benchmark_time += microseconds;
409                         if(benchmark_time == old_benchmark_time)
410                                 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
411                 }
412                 return;
413         }
414         if(sys_debugsleep.integer)
415         {
416                 t = Sys_DirtyTime();
417         }
418         if(sys_supportsdlgetticks && sys_usesdldelay.integer)
419         {
420                 Sys_SDL_Delay(microseconds / 1000);
421         }
422 #if HAVE_SELECT
423         else
424         {
425                 struct timeval tv;
426                 tv.tv_sec = microseconds / 1000000;
427                 tv.tv_usec = microseconds % 1000000;
428                 select(0, NULL, NULL, NULL, &tv);
429         }
430 #elif HAVE_USLEEP
431         else
432         {
433                 usleep(microseconds);
434         }
435 #elif HAVE_Sleep
436         else
437         {
438                 Sleep(microseconds / 1000);
439         }
440 #else
441         else
442         {
443                 Sys_SDL_Delay(microseconds / 1000);
444         }
445 #endif
446         if(sys_debugsleep.integer)
447         {
448                 t = Sys_DirtyTime() - t;
449                 Sys_PrintfToTerminal("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000));
450         }
451 }
452
453 void Sys_PrintfToTerminal(const char *fmt, ...)
454 {
455         va_list argptr;
456         char msg[MAX_INPUTLINE];
457
458         va_start(argptr,fmt);
459         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
460         va_end(argptr);
461
462         Sys_PrintToTerminal(msg);
463 }
464
465 #ifndef WIN32
466 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
467 {
468         const char *p = PATH;
469         const char *q;
470         if(p && name)
471         {
472                 while((q = strchr(p, ':')))
473                 {
474                         dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
475                         if(FS_SysFileExists(buf))
476                                 return buf;
477                         p = q + 1;
478                 }
479                 if(!q) // none found - try the last item
480                 {
481                         dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
482                         if(FS_SysFileExists(buf))
483                                 return buf;
484                 }
485         }
486         return name;
487 }
488 #endif
489
490 static const char *Sys_FindExecutableName(void)
491 {
492 #if defined(WIN32)
493         return com_argv[0];
494 #else
495         static char exenamebuf[MAX_OSPATH+1];
496         ssize_t n = -1;
497 #if defined(__FreeBSD__)
498         n = readlink("/proc/curproc/file", exenamebuf, sizeof(exenamebuf)-1);
499 #elif defined(__linux__)
500         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
501 #endif
502         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
503         {
504                 exenamebuf[n] = 0;
505                 return exenamebuf;
506         }
507         if(strchr(com_argv[0], '/'))
508                 return com_argv[0]; // possibly a relative path
509         else
510                 return Sys_FindInPATH(com_argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
511 #endif
512 }
513
514 void Sys_ProvideSelfFD(void)
515 {
516         if(com_selffd != -1)
517                 return;
518         com_selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
519 }
520
521 // for x86 cpus only...  (x64 has SSE2_PRESENT)
522 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
523 // code from SDL, shortened as we can expect CPUID to work
524 static int CPUID_Features(void)
525 {
526         int features = 0;
527 # if defined(__GNUC__) && defined(__i386__)
528         __asm__ (
529 "        movl    %%ebx,%%edi\n"
530 "        xorl    %%eax,%%eax                                           \n"
531 "        incl    %%eax                                                 \n"
532 "        cpuid                       # Get family/model/stepping/features\n"
533 "        movl    %%edx,%0                                              \n"
534 "        movl    %%edi,%%ebx\n"
535         : "=m" (features)
536         :
537         : "%eax", "%ecx", "%edx", "%edi"
538         );
539 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
540         __asm {
541         xor     eax, eax
542         inc     eax
543         cpuid                       ; Get family/model/stepping/features
544         mov     features, edx
545         }
546 # else
547 #  error SSE_POSSIBLE set but no CPUID implementation
548 # endif
549         return features;
550 }
551 #endif
552
553 #ifdef SSE_POSSIBLE
554 qboolean Sys_HaveSSE(void)
555 {
556         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
557         if(COM_CheckParm("-nosse"))
558                 return false;
559 #ifdef SSE_PRESENT
560         return true;
561 #else
562         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
563         if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2"))
564                 return true;
565         if(CPUID_Features() & (1 << 25))
566                 return true;
567         return false;
568 #endif
569 }
570
571 qboolean Sys_HaveSSE2(void)
572 {
573         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
574         if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2"))
575                 return false;
576 #ifdef SSE2_PRESENT
577         return true;
578 #else
579         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
580         if(COM_CheckParm("-forcesse2"))
581                 return true;
582         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
583                 return true;
584         return false;
585 #endif
586 }
587 #endif
588
589 /// called to set process priority for dedicated servers
590 #if defined(__linux__)
591 #include <sys/resource.h>
592 #include <errno.h>
593 static int nicelevel;
594 static qboolean nicepossible;
595 static qboolean isnice;
596 void Sys_InitProcessNice (void)
597 {
598         struct rlimit lim;
599         nicepossible = false;
600         if(COM_CheckParm("-nonice"))
601                 return;
602         errno = 0;
603         nicelevel = getpriority(PRIO_PROCESS, 0);
604         if(errno)
605         {
606                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
607                 return;
608         }
609         if(getrlimit(RLIMIT_NICE, &lim))
610         {
611                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
612                 return;
613         }
614         if(lim.rlim_cur != RLIM_INFINITY && nicelevel < (int) (20 - lim.rlim_cur))
615         {
616                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
617                 return;
618         }
619         nicepossible = true;
620         isnice = false;
621 }
622 void Sys_MakeProcessNice (void)
623 {
624         if(!nicepossible)
625                 return;
626         if(isnice)
627                 return;
628         Con_DPrintf("Process is becoming 'nice'...\n");
629         if(setpriority(PRIO_PROCESS, 0, 19))
630                 Con_Printf("Failed to raise nice level to %d\n", 19);
631         isnice = true;
632 }
633 void Sys_MakeProcessMean (void)
634 {
635         if(!nicepossible)
636                 return;
637         if(!isnice)
638                 return;
639         Con_DPrintf("Process is becoming 'mean'...\n");
640         if(setpriority(PRIO_PROCESS, 0, nicelevel))
641                 Con_Printf("Failed to lower nice level to %d\n", nicelevel);
642         isnice = false;
643 }
644 #else
645 void Sys_InitProcessNice (void)
646 {
647 }
648 void Sys_MakeProcessNice (void)
649 {
650 }
651 void Sys_MakeProcessMean (void)
652 {
653 }
654 #endif