Add timestamps to events

This commit is contained in:
Mads Marquart
2026-03-18 17:31:57 +01:00
parent 4d81f4aa62
commit 99f7bb6b44
8 changed files with 109 additions and 20 deletions

View File

@@ -60,6 +60,7 @@ objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { version = "0.3.2", default-features = false } objc2-core-graphics = { version = "0.3.2", default-features = false }
objc2-core-video = { version = "0.3.2", default-features = false } objc2-core-video = { version = "0.3.2", default-features = false }
objc2-foundation = { version = "0.3.2", default-features = false } objc2-foundation = { version = "0.3.2", default-features = false }
objc2-quartz-core = { version = "0.3.2", default-features = false }
objc2-ui-kit = { version = "0.3.2", default-features = false } objc2-ui-kit = { version = "0.3.2", default-features = false }
# Windows dependencies. # Windows dependencies.

View File

@@ -107,6 +107,7 @@ objc2-foundation = { workspace = true, features = [
"NSThread", "NSThread",
"NSValue", "NSValue",
] } ] }
objc2-quartz-core = { workspace = true, features = ["std", "CABase"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] } winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
[dev-dependencies] [dev-dependencies]

View File

@@ -98,6 +98,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
} }
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) { fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let time = app_state.event_time(event);
let event_type = event.r#type(); let event_type = event.r#type();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match event_type { match event_type {
@@ -110,7 +111,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
if delta_x != 0.0 || delta_y != 0.0 { if delta_x != 0.0 || delta_y != 0.0 {
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::PointerMotion { app.device_event(event_loop, None, time, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y), delta: (delta_x, delta_y),
}); });
}); });
@@ -119,7 +120,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = event.buttonNumber() as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, time, DeviceEvent::Button {
button, button,
state: ElementState::Pressed, state: ElementState::Pressed,
}); });
@@ -128,7 +129,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = event.buttonNumber() as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, time, DeviceEvent::Button {
button, button,
state: ElementState::Released, state: ElementState::Released,
}); });

View File

@@ -2,12 +2,14 @@ use std::cell::{Cell, OnceCell, RefCell};
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::{Duration, Instant};
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication};
use objc2_foundation::NSNotification; use objc2_foundation::{NSNotification, NSTimeInterval};
use objc2_quartz_core::CACurrentMediaTime;
use tracing::warn;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop}; use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler; use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler; use winit_core::application::ApplicationHandler;
@@ -43,6 +45,8 @@ pub(super) struct AppState {
start_time: Cell<Option<Instant>>, start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>, wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>, pending_redraw: RefCell<Vec<WindowId>>,
startup_instant: Instant,
startup_timestamp: NSTimeInterval,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and // NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those. // as such should be careful to not add fields that, in turn, strongly reference those.
} }
@@ -63,6 +67,17 @@ impl AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
})); }));
// Prime dylib caches etc.
let _ = CACurrentMediaTime();
// Find the current Rust timestamp.
let startup_instant = Instant::now();
// Find the timestamp
//
// `NSProcessInfo::processInfo().systemUptime()` needs the required reason manifest,
// `CACurrentMediaTime` (currently) doesn't, so we use that instead.
let startup_timestamp = CACurrentMediaTime();
let this = Rc::new(Self { let this = Rc::new(Self {
mtm, mtm,
activation_policy, activation_policy,
@@ -83,6 +98,8 @@ impl AppState {
start_time: Cell::new(None), start_time: Cell::new(None),
wait_timeout: Cell::new(None), wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]), pending_redraw: RefCell::new(vec![]),
startup_instant,
startup_timestamp,
}); });
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this)) GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
@@ -96,6 +113,16 @@ impl AppState {
.clone() .clone()
} }
pub(crate) fn event_time(&self, event: &NSEvent) -> Instant {
if event.timestamp() == 0.0 {
warn!(?event, "got zero timestamp");
return Instant::now();
}
let duration_since_startup = event.timestamp() - self.startup_timestamp;
let duration_since_startup = Duration::from_secs_f64(duration_since_startup as f64);
self.startup_instant + duration_since_startup
}
// NOTE: This notification will, globally, only be emitted once, // NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates. // no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) { pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
@@ -247,7 +274,12 @@ impl AppState {
// -> Don't go back into the event handler when our callstack originates from there // -> Don't go back into the event handler when our callstack originates from there
if !self.event_handler.in_use() { if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
}); });
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -347,7 +379,12 @@ impl AppState {
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw { for window_id in redraw {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
}); });
} }
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {

View File

@@ -2,11 +2,12 @@
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant;
use dpi::{LogicalPosition, LogicalSize}; use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send}; use objc2::{AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSWindow, NSTrackingAreaOptions, NSView, NSWindow,
@@ -16,6 +17,7 @@ use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
}; };
use tracing::warn;
use winit_core::event::{ use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent, PointerKind, PointerSource, TouchPhase, WindowEvent,
@@ -680,8 +682,9 @@ define_class!(
self.update_modifiers(event, false); self.update_modifiers(event, false);
let time = self.ivars().app_state.event_time(event);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) app.device_event(event_loop, None, time, DeviceEvent::MouseWheel { delta })
}); });
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase }); self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
} }
@@ -844,9 +847,16 @@ impl WinitView {
} }
fn queue_event(&self, event: WindowEvent) { fn queue_event(&self, event: WindowEvent) {
let app = NSApplication::sharedApplication(self.mtm());
let window_id = window_id(&self.window()); let window_id = window_id(&self.window());
let time = if let Some(nsevent) = app.currentEvent() {
self.ivars().app_state.event_time(&nsevent)
} else {
warn!("queued event with wrong timestamp, no active NSEvent found");
Instant::now()
};
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event); app.window_event(event_loop, window_id, time, event);
}); });
} }

View File

@@ -5,6 +5,7 @@ use std::ffi::c_void;
use std::ptr; use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant;
use dpi::{ use dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
@@ -903,9 +904,16 @@ impl WindowDelegate {
} }
pub(crate) fn queue_event(&self, event: WindowEvent) { pub(crate) fn queue_event(&self, event: WindowEvent) {
let app = NSApplication::sharedApplication(self.mtm());
let window_id = window_id(self.window()); let window_id = window_id(self.window());
let time = if let Some(nsevent) = app.currentEvent() {
self.ivars().app_state.event_time(&nsevent)
} else {
warn!("queued event with wrong timestamp, no active NSEvent found");
Instant::now()
};
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event); app.window_event(event_loop, window_id, time, event);
}); });
} }

View File

@@ -1,4 +1,9 @@
//! End user application handling. //! End user application handling.
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
#[cfg(target_family = "wasm")]
use web_time::Instant;
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
@@ -192,10 +197,14 @@ pub trait ApplicationHandler {
} }
/// Emitted when the OS sends an event to a winit window. /// Emitted when the OS sends an event to a winit window.
///
/// Contains the ID of the window, the event, and the time the event was received. Note that
/// since events are queued, the time will differ from [`Instant::now()`].
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
); );
@@ -206,9 +215,10 @@ pub trait ApplicationHandler {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
let _ = (event_loop, device_id, event); let _ = (event_loop, device_id, timestamp, event);
} }
/// Emitted when the event loop is about to block and wait for new events. /// Emitted when the event loop is about to block and wait for new events.
@@ -372,9 +382,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
) { ) {
(**self).window_event(event_loop, window_id, event); (**self).window_event(event_loop, window_id, timestamp, event);
} }
#[inline] #[inline]
@@ -382,9 +393,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, timestamp, event);
} }
#[inline] #[inline]
@@ -440,9 +452,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
) { ) {
(**self).window_event(event_loop, window_id, event); (**self).window_event(event_loop, window_id, timestamp, event);
} }
#[inline] #[inline]
@@ -450,9 +463,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, timestamp, event);
} }
#[inline] #[inline]

View File

@@ -1,6 +1,7 @@
//! Simple winit window example. //! Simple winit window example.
use std::error::Error; use std::error::Error;
use std::time::Instant;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
@@ -36,8 +37,14 @@ impl ApplicationHandler for App {
} }
} }
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { fn window_event(
println!("{event:?}"); &mut self,
event_loop: &dyn ActiveEventLoop,
_: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
println!("Close was requested; stopping"); println!("Close was requested; stopping");
@@ -62,11 +69,21 @@ impl ApplicationHandler for App {
fill::fill_window(window.as_ref()); fill::fill_window(window.as_ref());
// For contiguous redraw loop you can request a redraw from here. // For contiguous redraw loop you can request a redraw from here.
// window.request_redraw(); window.request_redraw();
}, },
_ => (), _ => (),
} }
} }
fn device_event(
&mut self,
_event_loop: &dyn ActiveEventLoop,
_device_id: Option<winit::event::DeviceId>,
timestamp: Instant,
event: winit::event::DeviceEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
}
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {