Comment only. Whether the monotonic clock counts time spent asleep differs per
platform, and the names invite exactly the wrong assumption: Apple's
CLOCK_MONOTONIC behaves like Linux's CLOCK_BOOTTIME, not like Linux's
CLOCK_MONOTONIC. We now skip suspended time on Linux/Android and Mac/iOS, but
not on Windows, where QPC is documented to include standby and hibernate.
Writing down why that's deliberate, so the Apple branch doesn't get "fixed"
back to CLOCK_MONOTONIC by someone who reads it as the portable spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
No behavior change - recording what was measured so the next person doesn't
have to rediscover it. Darwin's usleep overshoots by ~25% of the requested
interval, mach_wait_until doesn't improve on it, and the fix that does work
costs CPU every frame.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
CLOCK_MONOTONIC is the expensive clock on Apple - it keeps counting while the
system is asleep, so it can't be a plain counter read. CLOCK_UPTIME_RAW is the
raw counter (the clock_gettime man page notes it's identical to
mach_absolute_time() after the timebase conversion), and the _nsec_np variant
returns nanoseconds directly instead of filling in a timespec we then have to
recombine - which is exactly what time_now_raw() wants.
Measured on an M-series Mac, per time_now_d():
clock_gettime(CLOCK_MONOTONIC) 23-31 ns
clock_gettime_nsec_np(UPTIME_RAW) 14-16 ns
mach_absolute_time + double mult 10.3 ns
mach_absolute_time is a little faster still, but needs mach headers, a cached
timebase and a second time origin; this is a one-function change that keeps
time_now_raw()'s nanosecond contract. Available since macOS 10.12 / iOS 10, and
our deployment targets are 10.13 and 11.0.
Timing risk: this clock stops while the system is asleep, where CLOCK_MONOTONIC
kept running. Deltas across a sleep/wake will now be small rather than huge,
which is the better behavior for frame pacing. time_now_unix_utc() still uses
CLOCK_REALTIME, so wall-clock time is unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
It stored a split seconds/nanoseconds pair on POSIX and hand-rolled the borrow
in ElapsedNanos, duplicating what time_now_raw() already does. Just store the
nanosecond value, so there's one clock read per platform to keep correct.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
time_now_d() lazily initialized g_startTime on its first call, which is a data
race between threads, and left from_time_raw() subtracting zero (returning
seconds since boot) if it happened to run before any time_now_d(). Set it in
TimeInit() instead, matching what the Windows path already does with
frequencyMult.
That only works if TimeInit() is actually called, and iOS was the one entry
point that never did - Windows, UWP, SDL, Qt, Android, libretro, headless and
the unit tests all do it as the first thing in main(). Added it there too.
Timing risk: anything calling time_now_d() before TimeInit() now gets seconds
since boot rather than a value near zero. All entry points call TimeInit()
first, so this only bites code running from a static initializer; deltas
between two timestamps are unaffected either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
The fallback path (anything that isn't Windows/Linux/Mac/iOS/Android - so
Switch, OpenBSD, FreeBSD) was thoroughly broken:
- time_now_raw() computed a double of *seconds* and returned it as a uint64_t,
where every caller expects nanoseconds. from_time_raw() then scaled it by
1/1e9, so time came out roughly 1e9 times too small.
- Instant() seeded itself from gettimeofday (realtime epoch) while
ElapsedNanos() read CLOCK_MONOTONIC (since boot), so every elapsed span was
the difference between two unrelated clocks - decades, in practice.
- On top of that it mixed units, assigning tv_usec to nsecs_ and subtracting it
from ts.tv_nsec.
Since that code already called clock_gettime(CLOCK_MONOTONIC) itself, any
platform reaching it necessarily has POSIX clocks, so just fold those platforms
into the branch that works instead of fixing three bugs in a duplicate
implementation. Also drops the now-unused "micros" constant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
It's declared in TimeUtil.h and defined for Windows and for the generic
fallback path, but not in the branch that Linux, Mac, iOS and Android actually
compile - so the first caller on any of those platforms would have failed to
link. Nothing calls it today, which is why nobody noticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
sleep_ms() should generally be avoided when possible. This can be used to try
to track down unnecessary sleeps by adding some logging.
This commit on its own doesn't actually add any logging.
* Add yield() function to tell the CPU that we're busy-waiting (rare)
Use it only for the busy-wait in lag sync, which only happens in
Windows.
* Buildfix attempt