]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
host: Return to main() if the loop breaks and Sys_Quit from there
[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 # ifdef __FreeBSD__
22 #  include <sys/sysctl.h>
23 # endif
24 # include <unistd.h>
25 # include <fcntl.h>
26 # include <sys/time.h>
27 # include <time.h>
28 # ifdef SUPPORTDLL
29 #  include <dlfcn.h>
30 # endif
31 #endif
32
33 static char sys_timestring[128];
34 char *Sys_TimeString(const char *timeformat)
35 {
36         time_t mytime = time(NULL);
37 #if _MSC_VER >= 1400
38         struct tm mytm;
39         localtime_s(&mytm, &mytime);
40         strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
41 #else
42         strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
43 #endif
44         return sys_timestring;
45 }
46
47
48 void Sys_Quit (int returnvalue)
49 {
50         // Unlock mutexes because the quit command may jump directly here, causing a deadlock
51         if (cmd_client.text_mutex)
52                 Cbuf_Unlock(&cmd_client);
53         if (cmd_server.text_mutex)
54                 Cbuf_Unlock(&cmd_server);
55         SV_UnlockThreadMutex();
56         TaskQueue_Frame(true);
57
58         if (COM_CheckParm("-profilegameonly"))
59                 Sys_AllowProfiling(false);
60         host.state = host_shutdown;
61         Host_Shutdown();
62         exit(returnvalue);
63 }
64
65 #ifdef __cplusplus
66 extern "C"
67 #endif
68 void Sys_AllowProfiling(qboolean enable)
69 {
70 #ifdef __ANDROID__
71 #ifdef USE_PROFILER
72         extern void monstartup(const char *libname);
73         extern void moncleanup(void);
74         if (enable)
75                 monstartup("libmain.so");
76         else
77                 moncleanup();
78 #endif
79 #elif (defined(__linux__) && (defined(__GLIBC__) || defined(__GNU_LIBRARY__))) || defined(__FreeBSD__)
80         extern int moncontrol(int);
81         moncontrol(enable);
82 #endif
83 }
84
85
86 /*
87 ===============================================================================
88
89 DLL MANAGEMENT
90
91 ===============================================================================
92 */
93
94 static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next)
95 {
96         const dllfunction_t *func;
97         if(dllhandle)
98         {
99                 for (func = fcts; func && func->name != NULL; func++)
100                         if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
101                         {
102                                 if(complain)
103                                 {
104                                         Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
105                                         if(has_next)
106                                                 Con_DPrintf("\nContinuing with");
107                                 }
108                                 goto notfound;
109                         }
110                 return true;
111
112         notfound:
113                 for (func = fcts; func && func->name != NULL; func++)
114                         *func->funcvariable = NULL;
115         }
116         return false;
117 }
118
119 qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
120 {
121 #ifdef SUPPORTDLL
122         const dllfunction_t *func;
123         dllhandle_t dllhandle = 0;
124         unsigned int i;
125
126         if (handle == NULL)
127                 return false;
128
129 #ifndef WIN32
130 #ifdef PREFER_PRELOAD
131         dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
132         if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false))
133         {
134                 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
135                 *handle = dllhandle;
136                 return true;
137         }
138         else
139                 Sys_UnloadLibrary(&dllhandle);
140 notfound:
141 #endif
142 #endif
143
144         // Initializations
145         for (func = fcts; func && func->name != NULL; func++)
146                 *func->funcvariable = NULL;
147
148         // Try every possible name
149         Con_DPrintf ("Trying to load library...");
150         for (i = 0; dllnames[i] != NULL; i++)
151         {
152                 Con_DPrintf (" \"%s\"", dllnames[i]);
153 #ifdef WIN32
154 # ifndef DONT_USE_SETDLLDIRECTORY
155 #  ifdef _WIN64
156                 SetDllDirectory("bin64");
157 #  else
158                 SetDllDirectory("bin32");
159 #  endif
160 # endif
161                 dllhandle = LoadLibrary (dllnames[i]);
162                 // no need to unset this - we want ALL dlls to be loaded from there, anyway
163 #else
164                 dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL);
165 #endif
166                 if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
167                         break;
168                 else
169                         Sys_UnloadLibrary (&dllhandle);
170         }
171
172         // see if the names can be loaded relative to the executable path
173         // (this is for Mac OSX which does not check next to the executable)
174         if (!dllhandle && strrchr(sys.argv[0], '/'))
175         {
176                 char path[MAX_OSPATH];
177                 strlcpy(path, sys.argv[0], sizeof(path));
178                 strrchr(path, '/')[1] = 0;
179                 for (i = 0; dllnames[i] != NULL; i++)
180                 {
181                         char temp[MAX_OSPATH];
182                         strlcpy(temp, path, sizeof(temp));
183                         strlcat(temp, dllnames[i], sizeof(temp));
184                         Con_DPrintf (" \"%s\"", temp);
185 #ifdef WIN32
186                         dllhandle = LoadLibrary (temp);
187 #else
188                         dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL);
189 #endif
190                         if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL))
191                                 break;
192                         else
193                                 Sys_UnloadLibrary (&dllhandle);
194                 }
195         }
196
197         // No DLL found
198         if (! dllhandle)
199         {
200                 Con_DPrintf(" - failed.\n");
201                 return false;
202         }
203
204         Con_DPrintf(" - loaded.\n");
205
206         *handle = dllhandle;
207         return true;
208 #else
209         return false;
210 #endif
211 }
212
213 void Sys_UnloadLibrary (dllhandle_t* handle)
214 {
215 #ifdef SUPPORTDLL
216         if (handle == NULL || *handle == NULL)
217                 return;
218
219 #ifdef WIN32
220         FreeLibrary (*handle);
221 #else
222         dlclose (*handle);
223 #endif
224
225         *handle = NULL;
226 #endif
227 }
228
229 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
230 {
231 #ifdef SUPPORTDLL
232 #ifdef WIN32
233         return (void *)GetProcAddress (handle, name);
234 #else
235         return (void *)dlsym (handle, name);
236 #endif
237 #else
238         return NULL;
239 #endif
240 }
241
242 #ifdef WIN32
243 # define HAVE_TIMEGETTIME 1
244 # define HAVE_QUERYPERFORMANCECOUNTER 1
245 # define HAVE_Sleep 1
246 #endif
247
248 #ifndef WIN32
249 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
250 # define HAVE_CLOCKGETTIME 1
251 #endif
252 // FIXME improve this check, manpage hints to DST_NONE
253 # define HAVE_GETTIMEOFDAY 1
254 #endif
255
256 #ifndef WIN32
257 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
258 // (so much for POSIX...)
259 # ifdef FD_SET
260 #  define HAVE_SELECT 1
261 # endif
262 #endif
263
264 #ifndef WIN32
265 // FIXME improve this check
266 # define HAVE_USLEEP 1
267 #endif
268
269 // this one is referenced elsewhere
270 cvar_t sys_usenoclockbutbenchmark = {CVAR_CLIENT | CVAR_SERVER | 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."};
271
272 // these are not
273 static cvar_t sys_debugsleep = {CVAR_CLIENT | CVAR_SERVER, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
274 static cvar_t sys_usesdlgetticks = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"};
275 static cvar_t sys_usesdldelay = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"};
276 #if HAVE_QUERYPERFORMANCECOUNTER
277 static cvar_t sys_usequeryperformancecounter = {CVAR_CLIENT | CVAR_SERVER | 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)"};
278 #endif
279 #if HAVE_CLOCKGETTIME
280 static cvar_t sys_useclockgettime = {CVAR_CLIENT | CVAR_SERVER | 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)"};
281 #endif
282
283 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
284
285 void Sys_Init_Commands (void)
286 {
287         Cvar_RegisterVariable(&sys_debugsleep);
288         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
289 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
290         if(sys_supportsdlgetticks)
291         {
292                 Cvar_RegisterVariable(&sys_usesdlgetticks);
293                 Cvar_RegisterVariable(&sys_usesdldelay);
294         }
295 #endif
296 #if HAVE_QUERYPERFORMANCECOUNTER
297         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
298 #endif
299 #if HAVE_CLOCKGETTIME
300         Cvar_RegisterVariable(&sys_useclockgettime);
301 #endif
302 }
303
304 double Sys_DirtyTime(void)
305 {
306         // first all the OPTIONAL timers
307
308         // benchmark timer (fake clock)
309         if(sys_usenoclockbutbenchmark.integer)
310         {
311                 double old_benchmark_time = benchmark_time;
312                 benchmark_time += 1;
313                 if(benchmark_time == old_benchmark_time)
314                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
315                 return benchmark_time * 0.000001;
316         }
317 #if HAVE_QUERYPERFORMANCECOUNTER
318         if (sys_usequeryperformancecounter.integer)
319         {
320                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
321                 // QueryPerformanceCounter
322                 // platform:
323                 // Windows 95/98/ME/NT/2000/XP
324                 // features:
325                 // very accurate (CPU cycles)
326                 // known issues:
327                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
328                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
329                 double timescale;
330                 LARGE_INTEGER PerformanceFreq;
331                 LARGE_INTEGER PerformanceCount;
332
333                 if (QueryPerformanceFrequency (&PerformanceFreq))
334                 {
335                         QueryPerformanceCounter (&PerformanceCount);
336         
337                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
338                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
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 sys.argv[0];
494 #else
495         static char exenamebuf[MAX_OSPATH+1];
496         ssize_t n = -1;
497 #if defined(__FreeBSD__)
498         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
499         size_t exenamebuflen = sizeof(exenamebuf)-1;
500         if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
501         {
502                 n = exenamebuflen;
503         }
504 #elif defined(__linux__)
505         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
506 #endif
507         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
508         {
509                 exenamebuf[n] = 0;
510                 return exenamebuf;
511         }
512         if(strchr(sys.argv[0], '/'))
513                 return sys.argv[0]; // possibly a relative path
514         else
515                 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
516 #endif
517 }
518
519 void Sys_ProvideSelfFD(void)
520 {
521         if(sys.selffd != -1)
522                 return;
523         sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
524 }
525
526 // for x86 cpus only...  (x64 has SSE2_PRESENT)
527 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
528 // code from SDL, shortened as we can expect CPUID to work
529 static int CPUID_Features(void)
530 {
531         int features = 0;
532 # if defined((__GNUC__) || (__clang__) || (__TINYC__)) && defined(__i386__)
533         __asm__ (
534 "        movl    %%ebx,%%edi\n"
535 "        xorl    %%eax,%%eax                                           \n"
536 "        incl    %%eax                                                 \n"
537 "        cpuid                       # Get family/model/stepping/features\n"
538 "        movl    %%edx,%0                                              \n"
539 "        movl    %%edi,%%ebx\n"
540         : "=m" (features)
541         :
542         : "%eax", "%ecx", "%edx", "%edi"
543         );
544 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
545         __asm {
546         xor     eax, eax
547         inc     eax
548         cpuid                       ; Get family/model/stepping/features
549         mov     features, edx
550         }
551 # else
552 #  error SSE_POSSIBLE set but no CPUID implementation
553 # endif
554         return features;
555 }
556 #endif
557
558 #ifdef SSE_POSSIBLE
559 qboolean Sys_HaveSSE(void)
560 {
561         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
562         if(COM_CheckParm("-nosse"))
563                 return false;
564 #ifdef SSE_PRESENT
565         return true;
566 #else
567         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
568         if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2"))
569                 return true;
570         if(CPUID_Features() & (1 << 25))
571                 return true;
572         return false;
573 #endif
574 }
575
576 qboolean Sys_HaveSSE2(void)
577 {
578         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
579         if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2"))
580                 return false;
581 #ifdef SSE2_PRESENT
582         return true;
583 #else
584         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
585         if(COM_CheckParm("-forcesse2"))
586                 return true;
587         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
588                 return true;
589         return false;
590 #endif
591 }
592 #endif
593
594 /// called to set process priority for dedicated servers
595 #if defined(__linux__)
596 #include <sys/resource.h>
597 #include <errno.h>
598
599 void Sys_InitProcessNice (void)
600 {
601         struct rlimit lim;
602         sys.nicepossible = false;
603         if(COM_CheckParm("-nonice"))
604                 return;
605         errno = 0;
606         sys.nicelevel = getpriority(PRIO_PROCESS, 0);
607         if(errno)
608         {
609                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
610                 return;
611         }
612         if(getrlimit(RLIMIT_NICE, &lim))
613         {
614                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
615                 return;
616         }
617         if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
618         {
619                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
620                 return;
621         }
622         sys.nicepossible = true;
623         sys.isnice = false;
624 }
625 void Sys_MakeProcessNice (void)
626 {
627         if(!sys.nicepossible)
628                 return;
629         if(sys.isnice)
630                 return;
631         Con_DPrintf("Process is becoming 'nice'...\n");
632         if(setpriority(PRIO_PROCESS, 0, 19))
633                 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
634         sys.isnice = true;
635 }
636 void Sys_MakeProcessMean (void)
637 {
638         if(!sys.nicepossible)
639                 return;
640         if(!sys.isnice)
641                 return;
642         Con_DPrintf("Process is becoming 'mean'...\n");
643         if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
644                 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
645         sys.isnice = false;
646 }
647 #else
648 void Sys_InitProcessNice (void)
649 {
650 }
651 void Sys_MakeProcessNice (void)
652 {
653 }
654 void Sys_MakeProcessMean (void)
655 {
656 }
657 #endif