Skip to content

core: Add timestamps to window and device events#4562

Draft
chaynabors wants to merge 2 commits intorust-windowing:masterfrom
chaynabors:master
Draft

core: Add timestamps to window and device events#4562
chaynabors wants to merge 2 commits intorust-windowing:masterfrom
chaynabors:master

Conversation

@chaynabors
Copy link
Copy Markdown

@chaynabors chaynabors commented Apr 25, 2026

Fixes #1194. Reimplements and expands on #4529 to cover all four major desktop backends.

Before reviewing: The API shape here adds real and synthesized timestamps to the events. Let me know if that isn't desired. It would be possible to make timestamps optional in cases where events might be real but that adds boilerplate without any significant gain? Open to interpretations. Possible to sacrifice some honesty for ergonomics and vice-versa.


ApplicationHandler::window_event and ApplicationHandler::device_event take a new timestamp: Instant argument. The value is sourced from the OS where the platform exposes one, and from Instant::now() at winit's receipt point otherwise. Using it in place of Instant::now() inside the handler removes the polling-delay latency that applications pick up when their event loop is slow to poll, which matters for game input, audio scheduling, and similar latency-sensitive workloads.

Instant is re-exported as winit::Instant so cross-platform users don't need cfg-gated imports. winit substitutes web_time::Instant on wasm32-unknown-unknown/wasm32-unknown-none, where std::time::Instant::now() still panics.

Per backend:

  • AppKit: NSEvent.timestamp on the mach_absolute_time base, calibrated against Instant at event-loop construction via CACurrentMediaTime from QuartzCore. Events whose timestamps slightly predate the calibration sample project backward via checked_sub rather than falling back to Instant::now().
  • X11: xev.time lazily anchored on first observation. The anchor slides forward with each event so wrap-detection stays valid past the 49.7 day u32 wrap point and across multi-week application uptimes.
  • Wayland: the time field on wl_keyboard::key, wl_pointer::{motion, button, axis}, and wl_touch::{down, up, motion}, plus the utime_hi/utime_lo pair on zwp_relative_pointer_v1. Tracked via two separate sliding anchors, one u32 ms and one u64 us, so the ms wrap doesn't mis-anchor the us clock. EventSink gains push_*_event_at alongside the existing Instant::now(), stamping helpers so call sites without a native timestamp stay concise.
  • Win32: Instant::now() sampled inside WindowProc before queueing, within microseconds(?) of the OS dispatch.

Future work:

  • Android, iOS, Web, and Orbital stamp Instant::now() at every dispatch site for now.
  • Native AInputEvent_getEventTime, UIEvent.timestamp, and Event.timeStamp are follow-up work.
  • Win32 QueryPerformanceCounter calibration against MSG.time could be added but adds a jitter floor in the order of 0-16ms? Needs more testing.
  • Tested on all platforms changed
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality

@chaynabors
Copy link
Copy Markdown
Author

chaynabors commented Apr 25, 2026

Though the PR is effectively ready, it still needs additional testing. I'm also implementing my use case end to end behind the scenes to prove everything works as expected. I realize the diff is pretty sizable. It would be possible to split the breaking API changes from the implementation through stubs if desired, though I'd prefer to get the desktop implementations in all at once if possible.

I can test on win32 and x11 but would need help testing the other implementations.


I'm working on a scheduling IR + executor with sub millisecond latency requirements and I have a very strict budget. For the longest time I've simply given winit exclusive access to main and spun it as fast as possible to tag events with a timestamp, but I figured I'd give a shot to upstreaming my fork since I'm sure the Bevy folks would appreciate it among others.

I had the option to pull in another dependency on x11 instead of adding the one Quartz function to ffi. I didn't check if it was a transient dep already. Alternatively, MacOS supports a libc call for timing, the name of which is eluding me right now. It's the same timer under the hood as was implemented, and still an unsafe call. None of these decisions seemed to have any meaningful tradeoffs so I stuck with the 5 line change in ffi.

@chaynabors
Copy link
Copy Markdown
Author

Just some other ideas I had: macOS provides better granularity timestamps but that requires entitlements. Windows could probably get better granularity timestamps with the addition of a dedicated thread but that feels wasteful.

Improvements are incremental, what matters to me is if this API shape is sufficient for maintainers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Input event timestamps

1 participant