From 86baa1c99ae2a2e3a0182d3cd144a27b74dc2bb5 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 27 Aug 2023 17:04:39 +0200 Subject: [PATCH] Make iOS fully thread safe (#3045) * macOS & iOS: Refactor EventWrapper * macOS & iOS: Make EventLoopWindowTarget independent of the user event * iOS: Use MainThreadMarker instead of marking functions unsafe * Make iOS thread safe --- CHANGELOG.md | 1 + src/platform/ios.rs | 5 +- src/platform_impl/ios/app_state.rs | 160 +++++++--------- src/platform_impl/ios/event_loop.rs | 93 ++++----- src/platform_impl/ios/mod.rs | 11 -- src/platform_impl/ios/monitor.rs | 207 ++++++++++++--------- src/platform_impl/ios/view.rs | 105 ++++++----- src/platform_impl/ios/window.rs | 87 ++++----- src/platform_impl/macos/app.rs | 4 +- src/platform_impl/macos/app_state.rs | 144 +++++++------- src/platform_impl/macos/event.rs | 21 +-- src/platform_impl/macos/event_loop.rs | 33 +++- src/platform_impl/macos/view.rs | 6 +- src/platform_impl/macos/window_delegate.rs | 25 ++- 14 files changed, 457 insertions(+), 445 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f7dec6..ce4a2f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- Make iOS `MonitorHandle` and `VideoMode` usable from other threads. - Fix window size sometimes being invalid when resizing on macOS. - On Web, `ControlFlow::Poll` and `ControlFlow::WaitUntil` are now using the Prioritized Task Scheduling API. `setTimeout()` with a trick to circumvent throttling to 4ms is used as a fallback. - On Web, never return a `MonitorHandle`. diff --git a/src/platform/ios.rs b/src/platform/ios.rs index e02708f1..e1a6fa86 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -1,5 +1,6 @@ use std::os::raw::c_void; +use icrate::Foundation::MainThreadMarker; use objc2::rc::Id; use crate::{ @@ -210,7 +211,9 @@ pub trait MonitorHandleExtIOS { impl MonitorHandleExtIOS for MonitorHandle { #[inline] fn ui_screen(&self) -> *mut c_void { - Id::as_ptr(self.inner.ui_screen()) as *mut c_void + // SAFETY: The marker is only used to get the pointer of the screen + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void } #[inline] diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 9bb898d3..df371ecb 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -16,19 +16,21 @@ use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, }; -use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo}; +use icrate::Foundation::{ + CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo, +}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{msg_send, sel}; use once_cell::sync::Lazy; +use super::event_loop::{EventHandler, Never}; use super::uikit::UIView; use super::view::WinitUIWindow; use crate::{ - dpi::LogicalSize, + dpi::PhysicalSize, event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never}, window::WindowId as RootWindowId, }; @@ -44,6 +46,19 @@ macro_rules! bug_assert { }; } +#[derive(Debug)] +pub enum EventWrapper { + StaticEvent(Event), + ScaleFactorChanged(ScaleFactorChanged), +} + +#[derive(Debug)] +pub struct ScaleFactorChanged { + pub(super) window: Id, + pub(super) suggested_size: PhysicalSize, + pub(super) scale_factor: f64, +} + enum UserCallbackTransitionResult<'a> { Success { event_handler: Box, @@ -114,24 +129,18 @@ struct AppState { } impl AppState { - // requires main thread - unsafe fn get_mut() -> RefMut<'static, AppState> { + fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> { // basically everything in UIKit requires the main thread, so it's pointless to use the // std::sync APIs. // must be mut because plain `static` requires `Sync` static mut APP_STATE: RefCell> = RefCell::new(None); - if cfg!(debug_assertions) { - assert_main_thread!( - "bug in winit: `AppState::get_mut()` can only be called on the main thread" - ); - } - let mut guard = APP_STATE.borrow_mut(); + let mut guard = unsafe { APP_STATE.borrow_mut() }; if guard.is_none() { #[inline(never)] #[cold] - unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { - let waker = EventLoopWaker::new(CFRunLoopGetMain()); + fn init_guard(guard: &mut RefMut<'static, Option>) { + let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); **guard = Some(AppState { app_state: Some(AppStateImpl::NotLaunched { queued_windows: Vec::new(), @@ -142,7 +151,7 @@ impl AppState { waker, }); } - init_guard(&mut guard) + init_guard(&mut guard); } RefMut::map(guard, |state| state.as_mut().unwrap()) } @@ -451,10 +460,8 @@ impl AppState { } } -// requires main thread and window is a UIWindow -// retains window -pub(crate) unsafe fn set_key_window(window: &Id) { - let mut this = AppState::get_mut(); +pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id) { + let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, @@ -474,10 +481,8 @@ pub(crate) unsafe fn set_key_window(window: &Id) { window.makeKeyAndVisible(); } -// requires main thread and window is a UIWindow -// retains window -pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id) { - let mut this = AppState::get_mut(); +pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id) { + let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, @@ -506,14 +511,12 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id) { } } -// requires main thread -pub unsafe fn will_launch(queued_event_handler: Box) { - AppState::get_mut().will_launch_transition(queued_event_handler) +pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box) { + AppState::get_mut(mtm).will_launch_transition(queued_event_handler) } -// requires main thread -pub unsafe fn did_finish_launching() { - let mut this = AppState::get_mut(); +pub fn did_finish_launching(mtm: MainThreadMarker) { + let mut this = AppState::get_mut(mtm); let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), @@ -541,7 +544,7 @@ pub unsafe fn did_finish_launching() { // completed. This may result in incorrect visual appearance. // ``` let screen = window.screen(); - let _: () = msg_send![&window, setScreen: ptr::null::()]; + let _: () = unsafe { msg_send![&window, setScreen: ptr::null::()] }; window.setScreen(&screen); let controller = window.rootViewController(); @@ -551,13 +554,13 @@ pub unsafe fn did_finish_launching() { window.makeKeyAndVisible(); } - let (windows, events) = AppState::get_mut().did_finish_launching_transition(); + let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( StartCause::Init, ))) .chain(events); - handle_nonuser_events(events); + handle_nonuser_events(mtm, events); // the above window dance hack, could possibly trigger new windows to be created. // we can just set those windows up normally, as they were created after didFinishLaunching @@ -566,27 +569,27 @@ pub unsafe fn did_finish_launching() { } } -// requires main thread // AppState::did_finish_launching handles the special transition `Init` -pub unsafe fn handle_wakeup_transition() { - let mut this = AppState::get_mut(); +pub fn handle_wakeup_transition(mtm: MainThreadMarker) { + let mut this = AppState::get_mut(mtm); let wakeup_event = match this.wakeup_transition() { None => return, Some(wakeup_event) => wakeup_event, }; drop(this); - handle_nonuser_event(wakeup_event) + handle_nonuser_event(mtm, wakeup_event) } -// requires main thread -pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) { - handle_nonuser_events(std::iter::once(event)) +pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { + handle_nonuser_events(mtm, std::iter::once(event)) } -// requires main thread -pub(crate) unsafe fn handle_nonuser_events>(events: I) { - let mut this = AppState::get_mut(); +pub(crate) fn handle_nonuser_events>( + mtm: MainThreadMarker, + events: I, +) { + let mut this = AppState::get_mut(mtm); let (mut event_handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { @@ -615,14 +618,14 @@ pub(crate) unsafe fn handle_nonuser_events> } event_handler.handle_nonuser_event(event, &mut control_flow) } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) + EventWrapper::ScaleFactorChanged(event) => { + handle_hidpi_proxy(&mut event_handler, control_flow, event) } } } loop { - let mut this = AppState::get_mut(); + let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, @@ -672,17 +675,16 @@ pub(crate) unsafe fn handle_nonuser_events> } event_handler.handle_nonuser_event(event, &mut control_flow) } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) + EventWrapper::ScaleFactorChanged(event) => { + handle_hidpi_proxy(&mut event_handler, control_flow, event) } } } } } -// requires main thread -unsafe fn handle_user_events() { - let mut this = AppState::get_mut(); +fn handle_user_events(mtm: MainThreadMarker) { + let mut this = AppState::get_mut(mtm); let mut control_flow = this.control_flow; let (mut event_handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { @@ -703,7 +705,7 @@ unsafe fn handle_user_events() { event_handler.handle_user_events(&mut control_flow); loop { - let mut this = AppState::get_mut(); + let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, @@ -734,8 +736,8 @@ unsafe fn handle_user_events() { EventWrapper::StaticEvent(event) => { event_handler.handle_nonuser_event(event, &mut control_flow) } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) + EventWrapper::ScaleFactorChanged(event) => { + handle_hidpi_proxy(&mut event_handler, control_flow, event) } } } @@ -743,9 +745,8 @@ unsafe fn handle_user_events() { } } -// requires main thread -pub unsafe fn handle_main_events_cleared() { - let mut this = AppState::get_mut(); +pub fn handle_main_events_cleared(mtm: MainThreadMarker) { + let mut this = AppState::get_mut(mtm); if !this.has_launched() { return; } @@ -755,9 +756,9 @@ pub unsafe fn handle_main_events_cleared() { }; drop(this); - handle_user_events(); + handle_user_events(mtm); - let mut this = AppState::get_mut(); + let mut this = AppState::get_mut(mtm); let redraw_events: Vec = this .main_events_cleared_transition() .into_iter() @@ -770,18 +771,16 @@ pub unsafe fn handle_main_events_cleared() { .collect(); drop(this); - handle_nonuser_events(redraw_events); - handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait)); + handle_nonuser_events(mtm, redraw_events); + handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); } -// requires main thread -pub unsafe fn handle_events_cleared() { - AppState::get_mut().events_cleared_transition(); +pub fn handle_events_cleared(mtm: MainThreadMarker) { + AppState::get_mut(mtm).events_cleared_transition(); } -// requires main thread -pub unsafe fn terminated() { - let mut this = AppState::get_mut(); +pub fn terminated(mtm: MainThreadMarker) { + let mut this = AppState::get_mut(mtm); let mut event_handler = this.terminated_transition(); let mut control_flow = this.control_flow; drop(this); @@ -789,34 +788,17 @@ pub unsafe fn terminated() { event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow) } -fn handle_event_proxy( - event_handler: &mut Box, - control_flow: ControlFlow, - proxy: EventProxy, -) { - match proxy { - EventProxy::DpiChangedProxy { - suggested_size, - scale_factor, - window, - } => handle_hidpi_proxy( - event_handler, - control_flow, - suggested_size, - scale_factor, - window, - ), - } -} - fn handle_hidpi_proxy( event_handler: &mut Box, mut control_flow: ControlFlow, - suggested_size: LogicalSize, - scale_factor: f64, - window: Id, + event: ScaleFactorChanged, ) { - let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor))); + let ScaleFactorChanged { + suggested_size, + scale_factor, + window, + } = event; + let new_inner_size = Arc::new(Mutex::new(suggested_size)); let event = Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::ScaleFactorChanged { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 88a08182..e3ad63b5 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -15,12 +15,10 @@ use core_foundation::runloop::{ CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use icrate::Foundation::{MainThreadMarker, NSString}; -use objc2::rc::Id; use objc2::ClassType; use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle}; use crate::{ - dpi::LogicalSize, error::EventLoopError, event::Event, event_loop::{ @@ -30,38 +28,21 @@ use crate::{ }; use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen}; -use super::view::WinitUIWindow; use super::{app_state, monitor, view, MonitorHandle}; #[derive(Debug)] -pub(crate) enum EventWrapper { - StaticEvent(Event), - EventProxy(EventProxy), -} - -#[derive(Debug, PartialEq)] -pub(crate) enum EventProxy { - DpiChangedProxy { - window: Id, - suggested_size: LogicalSize, - scale_factor: f64, - }, -} - pub struct EventLoopWindowTarget { - receiver: Receiver, - sender_to_clone: Sender, + pub(super) mtm: MainThreadMarker, + p: PhantomData, } impl EventLoopWindowTarget { pub fn available_monitors(&self) -> VecDeque { - monitor::uiscreens(MainThreadMarker::new().unwrap()) + monitor::uiscreens(self.mtm) } pub fn primary_monitor(&self) -> Option { - Some(MonitorHandle::new(UIScreen::main( - MainThreadMarker::new().unwrap(), - ))) + Some(MonitorHandle::new(UIScreen::main(self.mtm))) } pub fn raw_display_handle(&self) -> RawDisplayHandle { @@ -70,6 +51,9 @@ impl EventLoopWindowTarget { } pub struct EventLoop { + mtm: MainThreadMarker, + sender: Sender, + receiver: Receiver, window_target: RootEventLoopWindowTarget, } @@ -80,7 +64,8 @@ impl EventLoop { pub(crate) fn new( _: &PlatformSpecificEventLoopAttributes, ) -> Result, EventLoopError> { - assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); + let mtm = MainThreadMarker::new() + .expect("On iOS, `EventLoop` must be created on the main thread"); static mut SINGLETON_INIT: bool = false; unsafe { @@ -92,16 +77,19 @@ impl EventLoop { SINGLETON_INIT = true; } - let (sender_to_clone, receiver) = mpsc::channel(); + let (sender, receiver) = mpsc::channel(); // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); Ok(EventLoop { + mtm, + sender, + receiver, window_target: RootEventLoopWindowTarget { p: EventLoopWindowTarget { - receiver, - sender_to_clone, + mtm, + p: PhantomData, }, _marker: PhantomData, }, @@ -113,7 +101,7 @@ impl EventLoop { F: FnMut(Event, &RootEventLoopWindowTarget, &mut ControlFlow), { unsafe { - let application = UIApplication::shared(MainThreadMarker::new().unwrap()); + let application = UIApplication::shared(self.mtm); assert!( application.is_none(), "\ @@ -128,10 +116,11 @@ impl EventLoop { let handler = EventLoopHandler { f: event_handler, + receiver: self.receiver, event_loop: self.window_target, }; - app_state::will_launch(Box::new(handler)); + app_state::will_launch(self.mtm, Box::new(handler)); // Ensure application delegate is initialized view::WinitApplicationDelegate::class(); @@ -147,7 +136,7 @@ impl EventLoop { } pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) + EventLoopProxy::new(self.sender.clone()) } pub fn window_target(&self) -> &RootEventLoopWindowTarget { @@ -158,9 +147,7 @@ impl EventLoop { // EventLoopExtIOS impl EventLoop { pub fn idiom(&self) -> Idiom { - UIDevice::current(MainThreadMarker::new().unwrap()) - .userInterfaceIdiom() - .into() + UIDevice::current(self.mtm).userInterfaceIdiom().into() } } @@ -238,12 +225,11 @@ fn setup_control_flow_observers() { activity: CFRunLoopActivity, _: *mut c_void, ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), - _ => unreachable!(), - } + let mtm = MainThreadMarker::new().unwrap(); + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), + _ => unreachable!(), } } @@ -263,13 +249,12 @@ fn setup_control_flow_observers() { activity: CFRunLoopActivity, _: *mut c_void, ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), - kCFRunLoopExit => unimplemented!(), // not expected to ever happen - _ => unreachable!(), - } + let mtm = MainThreadMarker::new().unwrap(); + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), + kCFRunLoopExit => unimplemented!(), // not expected to ever happen + _ => unreachable!(), } } @@ -279,13 +264,12 @@ fn setup_control_flow_observers() { activity: CFRunLoopActivity, _: *mut c_void, ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), - kCFRunLoopExit => unimplemented!(), // not expected to ever happen - _ => unreachable!(), - } + let mtm = MainThreadMarker::new().unwrap(); + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), + kCFRunLoopExit => unimplemented!(), // not expected to ever happen + _ => unreachable!(), } } @@ -336,6 +320,7 @@ pub trait EventHandler: Debug { struct EventLoopHandler { f: Box>, + receiver: Receiver, event_loop: RootEventLoopWindowTarget, } @@ -357,7 +342,7 @@ impl EventHandler for EventLoopHandler { } fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { - for event in self.event_loop.p.receiver.try_iter() { + for event in self.receiver.try_iter() { (self.f)(Event::UserEvent(event), &self.event_loop, control_flow); } } diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index c7b788c0..dec71cde 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -58,17 +58,6 @@ #![cfg(ios_platform)] #![allow(clippy::let_unit_value)] -// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be -// worked around in the future by using GCD (grand central dispatch) and/or caching of values like -// window size/position. -macro_rules! assert_main_thread { - ($($t:tt)*) => { - if !::icrate::Foundation::is_main_thread() { - panic!($($t)*); - } - }; -} - mod app_state; mod event_loop; mod ffi; diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index baa1e3be..90ed8504 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -2,11 +2,11 @@ use std::{ collections::{BTreeSet, VecDeque}, - fmt, - ops::{Deref, DerefMut}, + fmt, hash, ptr, }; -use icrate::Foundation::{MainThreadMarker, NSInteger}; +use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger}; +use objc2::mutability::IsRetainable; use objc2::rc::Id; use super::uikit::{UIScreen, UIScreenMode}; @@ -16,32 +16,59 @@ use crate::{ platform_impl::platform::app_state, }; -// TODO(madsmtm): Remove or refactor this -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) struct ScreenModeSendSync(pub(crate) Id); +// Workaround for `MainThreadBound` implementing almost no traits +#[derive(Debug)] +struct MainThreadBoundDelegateImpls(MainThreadBound>); -unsafe impl Send for ScreenModeSendSync {} -unsafe impl Sync for ScreenModeSendSync {} +impl Clone for MainThreadBoundDelegateImpls { + fn clone(&self) -> Self { + Self( + self.0 + .get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)), + ) + } +} + +impl hash::Hash for MainThreadBoundDelegateImpls { + fn hash(&self, state: &mut H) { + // SAFETY: Marker only used to get the pointer + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Id::as_ptr(self.0.get(mtm)).hash(state); + } +} + +impl PartialEq for MainThreadBoundDelegateImpls { + fn eq(&self, other: &Self) -> bool { + // SAFETY: Marker only used to get the pointer + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm)) + } +} + +impl Eq for MainThreadBoundDelegateImpls {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, - pub(crate) screen_mode: ScreenModeSendSync, + screen_mode: MainThreadBoundDelegateImpls, pub(crate) monitor: MonitorHandle, } impl VideoMode { - fn new(uiscreen: Id, screen_mode: Id) -> VideoMode { - assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); + fn new( + uiscreen: Id, + screen_mode: Id, + mtm: MainThreadMarker, + ) -> VideoMode { let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let size = screen_mode.size(); VideoMode { size: (size.width as u32, size.height as u32), bit_depth: 32, refresh_rate_millihertz, - screen_mode: ScreenModeSendSync(screen_mode), + screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)), monitor: MonitorHandle::new(uiscreen), } } @@ -61,18 +88,40 @@ impl VideoMode { pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } + + pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id { + self.screen_mode.0.get(mtm) + } } -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Inner { - uiscreen: Id, -} - -#[derive(Clone, PartialEq, Eq, Hash)] pub struct MonitorHandle { - inner: Inner, + ui_screen: MainThreadBound>, } +impl Clone for MonitorHandle { + fn clone(&self) -> Self { + Self { + ui_screen: self + .ui_screen + .get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)), + } + } +} + +impl hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + (self as *const Self).hash(state); + } +} + +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + ptr::eq(self, other) + } +} + +impl Eq for MonitorHandle {} + impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -86,31 +135,6 @@ impl Ord for MonitorHandle { } } -impl Deref for MonitorHandle { - type Target = Inner; - - fn deref(&self) -> &Inner { - assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); - &self.inner - } -} - -impl DerefMut for MonitorHandle { - fn deref_mut(&mut self) -> &mut Inner { - assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); - &mut self.inner - } -} - -unsafe impl Send for MonitorHandle {} -unsafe impl Sync for MonitorHandle {} - -impl Drop for MonitorHandle { - fn drop(&mut self) { - assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS"); - } -} - impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // TODO: Do this using the proper fmt API @@ -135,59 +159,80 @@ impl fmt::Debug for MonitorHandle { } impl MonitorHandle { - pub(crate) fn new(uiscreen: Id) -> Self { - assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS"); + pub(crate) fn new(ui_screen: Id) -> Self { + // Holding `Id` implies we're on the main thread. + let mtm = MainThreadMarker::new().unwrap(); Self { - inner: Inner { uiscreen }, + ui_screen: MainThreadBound::new(ui_screen, mtm), } } -} -impl Inner { pub fn name(&self) -> Option { - let main = UIScreen::main(MainThreadMarker::new().unwrap()); - if self.uiscreen == main { - Some("Primary".to_string()) - } else if self.uiscreen == main.mirroredScreen() { - Some("Mirrored".to_string()) - } else { - UIScreen::screens(MainThreadMarker::new().unwrap()) - .iter() - .position(|rhs| rhs == &*self.uiscreen) - .map(|idx| idx.to_string()) - } + self.ui_screen.get_on_main(|ui_screen, mtm| { + let main = UIScreen::main(mtm); + if *ui_screen == main { + Some("Primary".to_string()) + } else if *ui_screen == main.mirroredScreen() { + Some("Mirrored".to_string()) + } else { + UIScreen::screens(mtm) + .iter() + .position(|rhs| rhs == &**ui_screen) + .map(|idx| idx.to_string()) + } + }) } pub fn size(&self) -> PhysicalSize { - let bounds = self.uiscreen.nativeBounds(); + let bounds = self + .ui_screen + .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } pub fn position(&self) -> PhysicalPosition { - let bounds = self.uiscreen.nativeBounds(); + let bounds = self + .ui_screen + .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); (bounds.origin.x as f64, bounds.origin.y as f64).into() } pub fn scale_factor(&self) -> f64 { - self.uiscreen.nativeScale() as f64 + self.ui_screen + .get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64 } pub fn refresh_rate_millihertz(&self) -> Option { - Some(refresh_rate_millihertz(&self.uiscreen)) + Some( + self.ui_screen + .get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)), + ) } pub fn video_modes(&self) -> impl Iterator { - // Use Ord impl of RootVideoMode - let modes: BTreeSet<_> = self - .uiscreen - .availableModes() - .into_iter() - .map(|mode| RootVideoMode { - video_mode: VideoMode::new(self.uiscreen.clone(), mode), - }) - .collect(); + self.ui_screen.get_on_main(|ui_screen, mtm| { + // Use Ord impl of RootVideoMode - modes.into_iter().map(|mode| mode.video_mode) + let modes: BTreeSet<_> = ui_screen + .availableModes() + .into_iter() + .map(|mode| RootVideoMode { + video_mode: VideoMode::new(ui_screen.clone(), mode, mtm), + }) + .collect(); + + modes.into_iter().map(|mode| mode.video_mode) + }) + } + + pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id { + self.ui_screen.get(mtm) + } + + pub fn preferred_video_mode(&self) -> VideoMode { + self.ui_screen.get_on_main(|ui_screen, mtm| { + VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm) + }) } } @@ -215,20 +260,6 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 { refresh_rate_millihertz as u32 * 1000 } -// MonitorHandleExtIOS -impl Inner { - pub(crate) fn ui_screen(&self) -> &Id { - &self.uiscreen - } - - pub fn preferred_video_mode(&self) -> VideoMode { - VideoMode::new( - self.uiscreen.clone(), - self.uiscreen.preferredMode().unwrap(), - ) - } -} - pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque { UIScreen::screens(mtm) .into_iter() diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index f97fbeee..f84efd3f 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -8,6 +8,7 @@ use objc2::rc::Id; use objc2::runtime::AnyClass; use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType}; +use super::app_state::{self, EventWrapper}; use super::uikit::{ UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask, UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController, @@ -19,8 +20,6 @@ use crate::{ event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::ValidOrientations, platform_impl::platform::{ - app_state, - event_loop::{EventProxy, EventWrapper}, ffi::{UIRectEdge, UIUserInterfaceIdiom}, window::PlatformSpecificWindowBuilderAttributes, DeviceId, Fullscreen, @@ -41,18 +40,21 @@ declare_class!( unsafe impl WinitView { #[method(drawRect:)] fn draw_rect(&self, rect: CGRect) { + let mtm = MainThreadMarker::new().unwrap(); let window = self.window().unwrap(); - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, - })); - } + }), + ); let _: () = unsafe { msg_send![super(self), drawRect: rect] }; } #[method(layoutSubviews)] fn layout_subviews(&self) { + let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let window = self.window().unwrap(); @@ -75,16 +77,18 @@ declare_class!( self.setFrame(window_bounds); } - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Resized(size), - })); - } + }), + ); } #[method(setContentScaleFactor:)] fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { + let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] }; @@ -112,25 +116,26 @@ declare_class!( let screen_space = screen.coordinateSpace(); let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space); let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); - unsafe { - app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + app_state::handle_nonuser_events( + mtm, + std::iter::once(EventWrapper::ScaleFactorChanged( + app_state::ScaleFactorChanged { window, scale_factor, - suggested_size: size, - })) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::WindowEvent { - window_id, - event: WindowEvent::Resized(size.to_physical(scale_factor)), - }, - ))), - ); - } + suggested_size: size.to_physical(scale_factor), + }, + )) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id, + event: WindowEvent::Resized(size.to_physical(scale_factor)), + }, + ))), + ); } #[method(touchesBegan:withEvent:)] @@ -255,9 +260,8 @@ impl WinitView { }), })); } - unsafe { - app_state::handle_nonuser_events(touch_events); - } + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_events(mtm, touch_events); } } @@ -431,23 +435,27 @@ declare_class!( unsafe impl WinitUIWindow { #[method(becomeKeyWindow)] fn become_key_window(&self) { - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(true), - })); - } + }), + ); let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } #[method(resignKeyWindow)] fn resign_key_window(&self) { - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(false), - })); - } + }), + ); let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; } } @@ -455,7 +463,7 @@ declare_class!( impl WinitUIWindow { pub(crate) fn new( - _mtm: MainThreadMarker, + mtm: MainThreadMarker, window_attributes: &WindowAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, @@ -468,12 +476,12 @@ impl WinitUIWindow { match window_attributes.fullscreen.clone().map(Into::into) { Some(Fullscreen::Exclusive(ref video_mode)) => { let monitor = video_mode.monitor(); - let screen = monitor.ui_screen(); - screen.setCurrentMode(Some(&video_mode.screen_mode.0)); + let screen = monitor.ui_screen(mtm); + screen.setCurrentMode(Some(video_mode.screen_mode(mtm))); this.setScreen(screen); } Some(Fullscreen::Borderless(Some(ref monitor))) => { - let screen = monitor.ui_screen(); + let screen = monitor.ui_screen(mtm); this.setScreen(screen); } _ => (), @@ -500,20 +508,20 @@ declare_class!( unsafe impl WinitApplicationDelegate { #[method(application:didFinishLaunchingWithOptions:)] fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { - unsafe { - app_state::did_finish_launching(); - } + app_state::did_finish_launching(MainThreadMarker::new().unwrap()); true } #[method(applicationDidBecomeActive:)] fn did_become_active(&self, _application: &UIApplication) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) } #[method(applicationWillResignActive:)] fn will_resign_active(&self, _application: &UIApplication) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) } #[method(applicationWillEnterForeground:)] @@ -539,10 +547,9 @@ declare_class!( })); } } - unsafe { - app_state::handle_nonuser_events(events); - app_state::terminated(); - } + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_events(mtm, events); + app_state::terminated(mtm); } } ); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 44ac9ce9..336d0191 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -8,6 +8,7 @@ use objc2::runtime::AnyObject; use objc2::{class, msg_send}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle}; +use super::app_state::EventWrapper; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::view::{WinitUIWindow, WinitView, WinitViewController}; use crate::{ @@ -17,9 +18,7 @@ use crate::{ icon::Icon, platform::ios::{ScreenEdge, ValidOrientations}, platform_impl::platform::{ - app_state, - event_loop::{EventProxy, EventWrapper}, - monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, + app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -53,20 +52,19 @@ impl Inner { } pub fn request_redraw(&self) { - unsafe { - if self.gl_or_metal_backed { - // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. - // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using - // raw or gl/metal for drawing this work is completely avoided. - // - // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via - // testing. - // - // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc - app_state::queue_gl_or_metal_redraw(self.window.clone()); - } else { - self.view.setNeedsDisplay(); - } + if self.gl_or_metal_backed { + let mtm = MainThreadMarker::new().unwrap(); + // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. + // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using + // raw or gl/metal for drawing this work is completely avoided. + // + // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via + // testing. + // + // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc + app_state::queue_gl_or_metal_redraw(mtm, self.window.clone()); + } else { + self.view.setNeedsDisplay(); } } @@ -219,14 +217,17 @@ impl Inner { } pub(crate) fn set_fullscreen(&self, monitor: Option) { + let mtm = MainThreadMarker::new().unwrap(); let uiscreen = match &monitor { Some(Fullscreen::Exclusive(video_mode)) => { - let uiscreen = video_mode.monitor.ui_screen(); - uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0)); + let uiscreen = video_mode.monitor.ui_screen(mtm); + uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm))); uiscreen.clone() } - Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(), - Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(), + Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(), + Some(Fullscreen::Borderless(None)) => { + self.current_monitor_inner().ui_screen(mtm).clone() + } None => { warn!("`Window::set_fullscreen(None)` ignored on iOS"); return; @@ -249,8 +250,9 @@ impl Inner { } pub(crate) fn fullscreen(&self) -> Option { + let mtm = MainThreadMarker::new().unwrap(); let monitor = self.current_monitor_inner(); - let uiscreen = monitor.ui_screen(); + let uiscreen = monitor.ui_screen(mtm); let screen_space_bounds = self.screen_frame(); let screen_bounds = uiscreen.bounds(); @@ -365,11 +367,11 @@ pub struct Window { impl Window { pub(crate) fn new( - _event_loop: &EventLoopWindowTarget, + event_loop: &EventLoopWindowTarget, window_attributes: WindowAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { - let mtm = MainThreadMarker::new().unwrap(); + let mtm = event_loop.mtm; if window_attributes.min_inner_size.is_some() { warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); @@ -383,8 +385,8 @@ impl Window { let main_screen = UIScreen::main(mtm); let fullscreen = window_attributes.fullscreen.clone().map(Into::into); let screen = match fullscreen { - Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(), - Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(), + Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm), + Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm), Some(Fullscreen::Borderless(None)) | None => &main_screen, }; @@ -424,7 +426,7 @@ impl Window { &view_controller, ); - unsafe { app_state::set_key_window(&window) }; + app_state::set_key_window(mtm, &window); // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 @@ -436,25 +438,26 @@ impl Window { let screen_space = screen.coordinateSpace(); let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space); let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); - unsafe { - app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + app_state::handle_nonuser_events( + mtm, + std::iter::once(EventWrapper::ScaleFactorChanged( + app_state::ScaleFactorChanged { window: window.clone(), scale_factor, - suggested_size: size, - })) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::WindowEvent { - window_id, - event: WindowEvent::Resized(size.to_physical(scale_factor)), - }, - ))), - ); - } + suggested_size: size.to_physical(scale_factor), + }, + )) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id, + event: WindowEvent::Resized(size.to_physical(scale_factor)), + }, + ))), + ); } let inner = Inner { diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 8a56b77b..326805aa 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -4,7 +4,7 @@ use icrate::Foundation::NSObject; use objc2::{declare_class, msg_send, mutability, ClassType}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; -use super::{app_state::AppState, event::EventWrapper, DEVICE_ID}; +use super::{app_state::AppState, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; declare_class!( @@ -96,5 +96,5 @@ fn queue_device_event(event: DeviceEvent) { device_id: DEVICE_ID, event, }; - AppState::queue_event(EventWrapper::StaticEvent(event)); + AppState::queue_event(event); } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 6699f508..8dc354fc 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -6,29 +6,24 @@ use std::{ rc::{Rc, Weak}, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, MutexGuard, + mpsc, Arc, Mutex, MutexGuard, }, time::Instant, }; use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp}; use icrate::Foundation::{is_main_thread, NSSize}; -use objc2::rc::autoreleasepool; +use objc2::rc::{autoreleasepool, Id}; use once_cell::sync::Lazy; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent}; +use super::{ + event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow, +}; use crate::{ - dpi::LogicalSize, + dpi::PhysicalSize, event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, - platform_impl::platform::{ - event::{EventProxy, EventWrapper}, - event_loop::PanicInfo, - menu, - observer::EventLoopWaker, - util::Never, - window::WinitWindow, - }, window::WindowId, }; @@ -54,6 +49,7 @@ pub(crate) type Callback = RefCell, &RootWindowTarget, struct EventLoopHandler { callback: Weak>, window_target: Rc>, + receiver: Rc>, } impl EventLoopHandler { @@ -103,7 +99,7 @@ impl EventHandler for EventLoopHandler { fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { self.with_callback(|this, mut callback| { - for event in this.window_target.p.receiver.try_iter() { + for event in this.receiver.try_iter() { if let ControlFlow::ExitWithCode(code) = *control_flow { // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? let dummy = &mut ControlFlow::ExitWithCode(code); @@ -116,6 +112,16 @@ impl EventHandler for EventLoopHandler { } } +#[derive(Debug)] +enum EventWrapper { + StaticEvent(Event), + ScaleFactorChanged { + window: Id, + suggested_size: PhysicalSize, + scale_factor: f64, + }, +} + #[derive(Default)] struct Handler { stop_app_on_launch: AtomicBool, @@ -313,14 +319,9 @@ impl Handler { self.callback.lock().unwrap().is_some() } - fn handle_nonuser_event(&self, wrapper: EventWrapper) { + fn handle_nonuser_event(&self, event: Event) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { - match wrapper { - EventWrapper::StaticEvent(event) => { - callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()) - } - EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback), - } + callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()) } } @@ -332,41 +333,27 @@ impl Handler { fn handle_scale_factor_changed_event( &self, - callback: &mut Box, window: &WinitWindow, - suggested_size: LogicalSize, + suggested_size: PhysicalSize, scale_factor: f64, ) { - let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor))); - let event = Event::WindowEvent { - window_id: WindowId(window.id()), - event: WindowEvent::ScaleFactorChanged { - scale_factor, - inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), - }, - }; + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + let new_inner_size = Arc::new(Mutex::new(suggested_size)); + let event = Event::WindowEvent { + window_id: WindowId(window.id()), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), + }, + }; - callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()); + callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()); - let physical_size = *new_inner_size.lock().unwrap(); - drop(new_inner_size); - let logical_size = physical_size.to_logical(scale_factor); - let size = NSSize::new(logical_size.width, logical_size.height); - window.setContentSize(size); - } - - fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { - match proxy { - EventProxy::DpiChangedProxy { - window, - suggested_size, - scale_factor, - } => self.handle_scale_factor_changed_event( - callback, - &window, - suggested_size, - scale_factor, - ), + let physical_size = *new_inner_size.lock().unwrap(); + drop(new_inner_size); + let logical_size = physical_size.to_logical(scale_factor); + let size = NSSize::new(logical_size.width, logical_size.height); + window.setContentSize(size); } } } @@ -387,10 +374,12 @@ impl AppState { pub unsafe fn set_callback( callback: Weak>, window_target: Rc>, + receiver: Rc>, ) { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { callback, window_target, + receiver, })); } @@ -435,7 +424,7 @@ impl AppState { pub fn exit() -> i32 { HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopExiting)); + HANDLER.handle_nonuser_event(Event::LoopExiting); HANDLER.set_in_callback(false); HANDLER.exit(); Self::clear_callback(); @@ -448,12 +437,10 @@ impl AppState { pub fn dispatch_init_events() { HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( - StartCause::Init, - ))); + HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); // NB: For consistency all platforms must emit a 'resumed' event even though macOS // applications don't themselves have a formal suspend/resume lifecycle. - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); + HANDLER.handle_nonuser_event(Event::Resumed); HANDLER.set_in_callback(false); } @@ -544,7 +531,7 @@ impl AppState { ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), }; HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); + HANDLER.handle_nonuser_event(Event::NewEvents(cause)); HANDLER.set_in_callback(false); } @@ -564,10 +551,10 @@ impl AppState { // Redraw request might come out of order from the OS. // -> Don't go back into the callback when our callstack originates from there if !HANDLER.in_callback.swap(true, Ordering::AcqRel) { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + HANDLER.handle_nonuser_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, - })); + }); HANDLER.set_in_callback(false); // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events @@ -578,11 +565,25 @@ impl AppState { } } - pub fn queue_event(wrapper: EventWrapper) { + pub fn queue_event(event: Event) { if !is_main_thread() { - panic!("Event queued from different thread: {wrapper:#?}"); + panic!("Event queued from different thread: {event:#?}"); } - HANDLER.events().push_back(wrapper); + HANDLER.events().push_back(EventWrapper::StaticEvent(event)); + } + + pub fn queue_static_scale_factor_changed_event( + window: Id, + suggested_size: PhysicalSize, + scale_factor: f64, + ) { + HANDLER + .events() + .push_back(EventWrapper::ScaleFactorChanged { + window, + suggested_size, + scale_factor, + }); } pub fn stop() { @@ -614,17 +615,32 @@ impl AppState { HANDLER.set_in_callback(true); HANDLER.handle_user_events(); for event in HANDLER.take_events() { - HANDLER.handle_nonuser_event(event); + match event { + EventWrapper::StaticEvent(event) => { + HANDLER.handle_nonuser_event(event); + } + EventWrapper::ScaleFactorChanged { + window, + suggested_size, + scale_factor, + } => { + HANDLER.handle_scale_factor_changed_event( + &window, + suggested_size, + scale_factor, + ); + } + } } for window_id in HANDLER.should_redraw() { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + HANDLER.handle_nonuser_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, - })); + }); } - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait)); + HANDLER.handle_nonuser_event(Event::AboutToWait); HANDLER.set_in_callback(false); if HANDLER.should_exit() { diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 44df7ceb..9f13787b 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -5,15 +5,11 @@ use core_foundation::{ data::{CFDataGetBytePtr, CFDataRef}, }; use icrate::Foundation::MainThreadMarker; -use objc2::rc::Id; use smol_str::SmolStr; use super::appkit::{NSEvent, NSEventModifierFlags}; -use super::util::Never; -use super::window::WinitWindow; use crate::{ - dpi::LogicalSize, - event::{ElementState, Event, KeyEvent, Modifiers}, + event::{ElementState, KeyEvent, Modifiers}, keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode, }, @@ -21,21 +17,6 @@ use crate::{ platform_impl::platform::ffi, }; -#[derive(Debug)] -pub(crate) enum EventWrapper { - StaticEvent(Event), - EventProxy(EventProxy), -} - -#[derive(Debug)] -pub(crate) enum EventProxy { - DpiChangedProxy { - window: Id, - suggested_size: LogicalSize, - scale_factor: f64, - }, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifiers: Option, diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 06c80df0..5a25789d 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -65,9 +65,10 @@ impl PanicInfo { } } +#[derive(Debug)] pub struct EventLoopWindowTarget { - pub receiver: mpsc::Receiver, mtm: MainThreadMarker, + p: PhantomData, } impl EventLoopWindowTarget { @@ -107,14 +108,18 @@ impl EventLoopWindowTarget { } pub struct EventLoop { + /// Store a reference to the application for convenience. + app: Id, /// The delegate is only weakly referenced by NSApplication, so we keep /// it around here as well. _delegate: Id, + // Event sender and receiver, used for EventLoopProxy. sender: mpsc::Sender, + receiver: Rc>, + window_target: Rc>, panic_info: Rc, - mtm: MainThreadMarker, /// We make sure that the callback closure is dropped during a panic /// by making the event loop own it. @@ -177,13 +182,17 @@ impl EventLoop { let (sender, receiver) = mpsc::channel(); Ok(EventLoop { + app, _delegate: delegate, sender, + receiver: Rc::new(receiver), window_target: Rc::new(RootWindowTarget { - p: EventLoopWindowTarget { receiver, mtm }, + p: EventLoopWindowTarget { + mtm, + p: PhantomData, + }, _marker: PhantomData, }), - mtm, panic_info, _callback: None, }) @@ -231,8 +240,6 @@ impl EventLoop { self._callback = Some(Rc::clone(&callback)); let exit_code = autoreleasepool(|_| { - let app = NSApplication::shared(self.mtm); - // A bit of juggling with the callback references to make sure // that `self.callback` is the only owner of the callback. let weak_cb: Weak<_> = Rc::downgrade(&callback); @@ -241,7 +248,11 @@ impl EventLoop { // # Safety // We make sure to call `AppState::clear_callback` before returning unsafe { - AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + AppState::set_callback( + weak_cb, + Rc::clone(&self.window_target), + Rc::clone(&self.receiver), + ); } // catch panics to make sure we can't unwind without clearing the set callback @@ -257,7 +268,7 @@ impl EventLoop { debug_assert!(!AppState::is_running()); AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` } - unsafe { app.run() }; + unsafe { self.app.run() }; // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which @@ -326,7 +337,11 @@ impl EventLoop { // to ensure that we don't hold on to the callback beyond its (erased) // lifetime unsafe { - AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + AppState::set_callback( + weak_cb, + Rc::clone(&self.window_target), + Rc::clone(&self.receiver), + ); } // catch panics to make sure we can't unwind without clearing the set callback diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index a4bf0da8..f1fac52f 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -31,7 +31,7 @@ use crate::{ platform::scancode::KeyCodeExtScancode, platform_impl::platform::{ app_state::AppState, - event::{create_key_event, event_mods, EventWrapper}, + event::{create_key_event, event_mods}, util, window::WinitWindow, DEVICE_ID, @@ -826,7 +826,7 @@ impl WinitView { window_id: self.window_id(), event, }; - AppState::queue_event(EventWrapper::StaticEvent(event)); + AppState::queue_event(event); } fn queue_device_event(&self, event: DeviceEvent) { @@ -834,7 +834,7 @@ impl WinitView { device_id: DEVICE_ID, event, }; - AppState::queue_event(EventWrapper::StaticEvent(event)); + AppState::queue_event(event); } fn scale_factor(&self) -> f64 { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 528e3a0b..fb6df1b4 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -11,16 +11,15 @@ use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassT use super::appkit::{ NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, }; +use super::{ + app_state::AppState, + util, + window::{get_ns_theme, WinitWindow}, + Fullscreen, +}; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, WindowEvent}, - platform_impl::platform::{ - app_state::AppState, - event::{EventProxy, EventWrapper}, - util, - window::{get_ns_theme, WinitWindow}, - Fullscreen, - }, window::WindowId, }; @@ -447,7 +446,7 @@ impl WinitWindowDelegate { window_id: WindowId(self.window.id()), event, }; - AppState::queue_event(EventWrapper::StaticEvent(event)); + AppState::queue_event(event); } fn queue_static_scale_factor_changed_event(&self) { @@ -457,12 +456,12 @@ impl WinitWindowDelegate { }; self.state.previous_scale_factor.set(scale_factor); - let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - window: self.window.clone(), - suggested_size: self.view_size(), + let suggested_size = self.view_size(); + AppState::queue_static_scale_factor_changed_event( + self.window.clone(), + suggested_size.to_physical(scale_factor), scale_factor, - }); - AppState::queue_event(wrapper); + ); } fn emit_move_event(&self) {