1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_TIMER_UNIX
24
25#include <stdio.h>
26#include <sys/time.h>
27#include <unistd.h>
28#include <errno.h>
29
30#include "../SDL_timer_c.h"
31
32#ifdef SDL_PLATFORM_EMSCRIPTEN
33#include <emscripten.h>
34#endif
35
36/* The clock_gettime provides monotonous time, so we should use it if
37 it's available. The clock_gettime function is behind ifdef
38 for __USE_POSIX199309
39 Tommi Kyntola (tommi.kyntola@ray.fi) 27/09/2005
40*/
41/* Reworked monotonic clock to not assume the current system has one
42 as not all linux kernels provide a monotonic clock (yeah recent ones
43 probably do)
44 Also added macOS Monotonic clock support
45 Based on work in https://github.com/ThomasHabets/monotonic_clock
46 */
47#if defined(HAVE_NANOSLEEP) || defined(HAVE_CLOCK_GETTIME)
48#include <time.h>
49#endif
50#ifdef SDL_PLATFORM_APPLE
51#include <mach/mach_time.h>
52#endif
53
54// Use CLOCK_MONOTONIC_RAW, if available, which is not subject to adjustment by NTP
55#ifdef HAVE_CLOCK_GETTIME
56#ifdef CLOCK_MONOTONIC_RAW
57#define SDL_MONOTONIC_CLOCK CLOCK_MONOTONIC_RAW
58#else
59#define SDL_MONOTONIC_CLOCK CLOCK_MONOTONIC
60#endif
61#endif
62
63// The first ticks value of the application
64#if !defined(HAVE_CLOCK_GETTIME) && defined(SDL_PLATFORM_APPLE)
65mach_timebase_info_data_t mach_base_info;
66#endif
67static bool checked_monotonic_time = false;
68static bool has_monotonic_time = false;
69
70static void CheckMonotonicTime(void)
71{
72#ifdef HAVE_CLOCK_GETTIME
73 struct timespec value;
74 if (clock_gettime(SDL_MONOTONIC_CLOCK, &value) == 0) {
75 has_monotonic_time = true;
76 }
77#elif defined(SDL_PLATFORM_APPLE)
78 if (mach_timebase_info(&mach_base_info) == 0) {
79 has_monotonic_time = true;
80 }
81#endif
82 checked_monotonic_time = true;
83}
84
85Uint64 SDL_GetPerformanceCounter(void)
86{
87 Uint64 ticks;
88
89 if (!checked_monotonic_time) {
90 CheckMonotonicTime();
91 }
92
93 if (has_monotonic_time) {
94#ifdef HAVE_CLOCK_GETTIME
95 struct timespec now;
96
97 clock_gettime(SDL_MONOTONIC_CLOCK, &now);
98 ticks = now.tv_sec;
99 ticks *= SDL_NS_PER_SECOND;
100 ticks += now.tv_nsec;
101#elif defined(SDL_PLATFORM_APPLE)
102 ticks = mach_absolute_time();
103#else
104 SDL_assert(false);
105 ticks = 0;
106#endif
107 } else {
108 struct timeval now;
109
110 gettimeofday(&now, NULL);
111 ticks = now.tv_sec;
112 ticks *= SDL_US_PER_SECOND;
113 ticks += now.tv_usec;
114 }
115 return ticks;
116}
117
118Uint64 SDL_GetPerformanceFrequency(void)
119{
120 if (!checked_monotonic_time) {
121 CheckMonotonicTime();
122 }
123
124 if (has_monotonic_time) {
125#ifdef HAVE_CLOCK_GETTIME
126 return SDL_NS_PER_SECOND;
127#elif defined(SDL_PLATFORM_APPLE)
128 Uint64 freq = mach_base_info.denom;
129 freq *= SDL_NS_PER_SECOND;
130 freq /= mach_base_info.numer;
131 return freq;
132#endif
133 }
134
135 return SDL_US_PER_SECOND;
136}
137
138void SDL_SYS_DelayNS(Uint64 ns)
139{
140 int was_error;
141
142#ifdef HAVE_NANOSLEEP
143 struct timespec tv, remaining;
144#else
145 struct timeval tv;
146 Uint64 then, now, elapsed;
147#endif
148
149#ifdef SDL_PLATFORM_EMSCRIPTEN
150 if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) {
151 // pseudo-synchronous pause, used directly or through e.g. SDL_WaitEvent
152 emscripten_sleep(ns / SDL_NS_PER_MS);
153 return;
154 }
155#endif
156
157 // Set the timeout interval
158#ifdef HAVE_NANOSLEEP
159 remaining.tv_sec = (time_t)(ns / SDL_NS_PER_SECOND);
160 remaining.tv_nsec = (long)(ns % SDL_NS_PER_SECOND);
161#else
162 then = SDL_GetTicksNS();
163#endif
164 do {
165 errno = 0;
166
167#ifdef HAVE_NANOSLEEP
168 tv.tv_sec = remaining.tv_sec;
169 tv.tv_nsec = remaining.tv_nsec;
170 was_error = nanosleep(&tv, &remaining);
171#else
172 // Calculate the time interval left (in case of interrupt)
173 now = SDL_GetTicksNS();
174 elapsed = (now - then);
175 then = now;
176 if (elapsed >= ns) {
177 break;
178 }
179 ns -= elapsed;
180 tv.tv_sec = (ns / SDL_NS_PER_SECOND);
181 tv.tv_usec = SDL_NS_TO_US(ns % SDL_NS_PER_SECOND);
182
183 was_error = select(0, NULL, NULL, NULL, &tv);
184#endif // HAVE_NANOSLEEP
185 } while (was_error && (errno == EINTR));
186}
187
188#endif // SDL_TIMER_UNIX
189