Skip to content

Commit

Permalink
Make iOS fully thread safe (#3045)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
madsmtm authored and kchibisov committed Oct 21, 2023
1 parent 05130cb commit 865afd2
Show file tree
Hide file tree
Showing 14 changed files with 457 additions and 445 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased

- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
- 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`.
Expand Down
5 changes: 4 additions & 1 deletion src/platform/ios.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::os::raw::c_void;

use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;

use crate::{
Expand Down Expand Up @@ -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]
Expand Down
160 changes: 71 additions & 89 deletions src/platform_impl/ios/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -44,6 +46,19 @@ macro_rules! bug_assert {
};
}

#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged(ScaleFactorChanged),
}

#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}

enum UserCallbackTransitionResult<'a> {
Success {
event_handler: Box<dyn EventHandler>,
Expand Down Expand Up @@ -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<Option<AppState>> = 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<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain());
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
Expand All @@ -142,7 +151,7 @@ impl AppState {
waker,
});
}
init_guard(&mut guard)
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
Expand Down Expand Up @@ -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<WinitUIWindow>) {
let mut this = AppState::get_mut();
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
Expand All @@ -474,10 +481,8 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
window.makeKeyAndVisible();
}

// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_gpu_redraws,
Expand Down Expand Up @@ -506,14 +511,12 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
}
}

// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut().will_launch_transition(queued_event_handler)
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
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),
Expand Down Expand Up @@ -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::<AnyObject>()];
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen);

let controller = window.rootViewController();
Expand All @@ -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
Expand All @@ -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<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut();
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
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 } => {
Expand Down Expand Up @@ -615,14 +618,14 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
}
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,
Expand Down Expand Up @@ -672,17 +675,16 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
}
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() {
Expand All @@ -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,
Expand Down Expand Up @@ -734,18 +736,17 @@ 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)
}
}
}
event_handler.handle_user_events(&mut control_flow);
}
}

// 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;
}
Expand All @@ -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<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
Expand All @@ -770,53 +771,34 @@ 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);

event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
}

fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
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<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow>,
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 {
Expand Down
Loading

0 comments on commit 865afd2

Please sign in to comment.