Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions winit-android/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ impl EventLoop {
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let event = event::WindowEvent::Focused(true);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let event = event::WindowEvent::Focused(false);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
},
MainEvent::ConfigChanged { .. } => {
let old_scale_factor = scale_factor(&self.android_app);
Expand All @@ -218,7 +218,7 @@ impl EventLoop {
scale_factor,
};

app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
}
},
MainEvent::LowMemory => {
Expand Down Expand Up @@ -295,14 +295,14 @@ impl EventLoop {
PhysicalSize::new(0, 0)
};
let event = event::WindowEvent::SurfaceResized(size);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
}

pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let event = event::WindowEvent::RedrawRequested;
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
}
}

Expand Down Expand Up @@ -369,7 +369,12 @@ impl EventLoop {
_ => event::PointerKind::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(
&self.window_target,
GLOBAL_WINDOW,
Instant::now(),
event,
);
let event = event::WindowEvent::PointerButton {
device_id,
primary,
Expand All @@ -384,7 +389,12 @@ impl EventLoop {
_ => event::ButtonSource::Unknown(0),
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(
&self.window_target,
GLOBAL_WINDOW,
Instant::now(),
event,
);
},
MotionAction::Move => {
let primary = self.primary_pointer == Some(finger_id);
Expand All @@ -401,7 +411,12 @@ impl EventLoop {
_ => event::PointerSource::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(
&self.window_target,
GLOBAL_WINDOW,
Instant::now(),
event,
);
},
MotionAction::Up | MotionAction::PointerUp | MotionAction::Cancel => {
let primary = action == MotionAction::Up
Expand All @@ -427,7 +442,12 @@ impl EventLoop {
_ => event::ButtonSource::Unknown(0),
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(
&self.window_target,
GLOBAL_WINDOW,
Instant::now(),
event,
);
}

let event = event::WindowEvent::PointerLeft {
Expand All @@ -443,7 +463,12 @@ impl EventLoop {
_ => event::PointerKind::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(
&self.window_target,
GLOBAL_WINDOW,
Instant::now(),
event,
);
},
_ => unreachable!(),
}
Expand Down Expand Up @@ -495,7 +520,7 @@ impl EventLoop {
is_synthetic: false,
};

app.window_event(&self.window_target, GLOBAL_WINDOW, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event);
},
}
},
Expand Down
7 changes: 4 additions & 3 deletions winit-appkit/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
}

fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let time = app_state.event_time(event);
let event_type = event.r#type();
#[allow(non_upper_case_globals)]
match event_type {
Expand All @@ -113,7 +114,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {

if delta_x != 0.0 || delta_y != 0.0 {
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),
});
});
Expand All @@ -122,7 +123,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = event.buttonNumber() as u32;
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,
state: ElementState::Pressed,
});
Expand All @@ -131,7 +132,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = event.buttonNumber() as u32;
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,
state: ElementState::Released,
});
Expand Down
63 changes: 59 additions & 4 deletions winit-appkit/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use std::time::{Duration, Instant};

use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication};
use objc2_foundation::NSNotification;
use tracing::warn;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
Expand All @@ -16,6 +17,7 @@ use winit_core::event_loop::ControlFlow;
use winit_core::window::WindowId;

use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
use super::ffi::CACurrentMediaTime;
use super::menu;
use super::observer::EventLoopWaker;

Expand Down Expand Up @@ -43,6 +45,10 @@ pub(super) struct AppState {
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>,
/// [`Instant`] / `CACurrentMediaTime` pair sampled at startup, used to translate
/// `NSEvent.timestamp` into [`Instant`].
startup_instant: Instant,
startup_timestamp: f64,
// 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.
}
Expand All @@ -63,6 +69,12 @@ impl AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));

// Prime the QuartzCore dylib cache so the subsequent read reflects steady-state cost,
// then sample back-to-back to bound the calibration skew between the two clocks.
let _ = unsafe { CACurrentMediaTime() };
let startup_instant = Instant::now();
let startup_timestamp = unsafe { CACurrentMediaTime() };

let this = Rc::new(Self {
mtm,
activation_policy,
Expand All @@ -83,6 +95,8 @@ impl AppState {
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
startup_instant,
startup_timestamp,
});

GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
Expand All @@ -96,6 +110,37 @@ impl AppState {
.clone()
}

/// Convert an `NSEvent`'s `mach_absolute_time` timestamp into an [`Instant`] using the
/// calibration captured at startup.
pub(crate) fn event_time(&self, event: &NSEvent) -> Instant {
let ts = event.timestamp();
if ts == 0.0 {
warn!("NSEvent has zero timestamp; falling back to Instant::now()");
return Instant::now();
}
let secs_since_startup = ts - self.startup_timestamp;
if !secs_since_startup.is_finite() {
return Instant::now();
}
let offset = Duration::from_secs_f64(secs_since_startup.abs());
if secs_since_startup < 0.0 {
self.startup_instant.checked_sub(offset).unwrap_or(self.startup_instant)
} else {
self.startup_instant + offset
}
}

/// Look up the time of the currently-dispatching `NSEvent`, if any; otherwise
/// [`Instant::now()`].
pub(crate) fn current_event_time(&self) -> Instant {
let app = NSApplication::sharedApplication(self.mtm);
if let Some(nsevent) = app.currentEvent() {
self.event_time(&nsevent)
} else {
Instant::now()
}
}

// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
Expand Down Expand Up @@ -245,7 +290,12 @@ impl AppState {
// -> Don't go back into the event handler when our callstack originates from there
if !self.event_handler.in_use() {
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
Expand Down Expand Up @@ -345,7 +395,12 @@ impl AppState {
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
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| {
Expand Down
12 changes: 12 additions & 0 deletions winit-appkit/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ unsafe extern "C" {
) -> i32;
}

// `NSEvent.timestamp()` is an `NSTimeInterval` on the `mach_absolute_time` base.
// `CACurrentMediaTime` reads from that same base and is the anchor used when
// converting event timestamps into `Instant`.
//
// `NSProcessInfo.processInfo().systemUptime()` reads the same clock but is a
// "required reason API" that mandates a privacy manifest for App Store
// distribution; `CACurrentMediaTime` is not.
#[link(name = "QuartzCore", kind = "framework")]
unsafe extern "C" {
pub fn CACurrentMediaTime() -> f64;
}

#[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void);

Expand Down
6 changes: 4 additions & 2 deletions winit-appkit/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,8 +684,9 @@ define_class!(

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| {
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 });
}
Expand Down Expand Up @@ -849,8 +850,9 @@ impl WinitView {

fn queue_event(&self, event: WindowEvent) {
let window_id = window_id(&self.window());
let time = self.ivars().app_state.current_event_time();
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);
});
}

Expand Down
3 changes: 2 additions & 1 deletion winit-appkit/src/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,8 +904,9 @@ impl WindowDelegate {

pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = window_id(self.window());
let time = self.ivars().app_state.current_event_time();
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);
});
}

Expand Down
Loading
Loading