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
This commit is contained in:
Mads Marquart 2023-08-27 17:04:39 +02:00 committed by GitHub
parent d9f04780cc
commit 86baa1c99a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 457 additions and 445 deletions

View file

@ -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`.

View file

@ -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]

View file

@ -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<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>,
@ -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(),
@ -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<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,
@ -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,
@ -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),
@ -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();
@ -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<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 } => {
@ -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,
@ -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() {
@ -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<EventWrapper> = 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<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 {

View file

@ -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<Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
pub(super) mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap())
monitor::uiscreens(self.mtm)
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
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<T: 'static> EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget<T>,
}
@ -80,7 +64,8 @@ impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, 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<T: 'static> EventLoop<T> {
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<T: 'static> EventLoop<T> {
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &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<T: 'static> EventLoop<T> {
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<T: 'static> EventLoop<T> {
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
@ -158,9 +147,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
UIDevice::current(self.mtm).userInterfaceIdiom().into()
}
}
@ -238,14 +225,13 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
@ -263,15 +249,14 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C" fn control_flow_end_handler(
@ -279,15 +264,14 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
let main_loop = CFRunLoopGetMain();
@ -336,6 +320,7 @@ pub trait EventHandler: Debug {
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>,
}
@ -357,7 +342,7 @@ impl<T: 'static> EventHandler for EventLoopHandler<T> {
}
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);
}
}

View file

@ -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;

View file

@ -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<UIScreenMode>);
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(
self.0
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
}
}
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&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<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
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<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
#[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<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
fn new(
uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
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<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
inner: Inner,
ui_screen: MainThreadBound<Id<UIScreen>>,
}
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<H: hash::Hasher>(&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<std::cmp::Ordering> {
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<UIScreen>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
// Holding `Id<UIScreen>` 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<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap());
if self.uiscreen == main {
self.ui_screen.get_on_main(|ui_screen, mtm| {
let main = UIScreen::main(mtm);
if *ui_screen == main {
Some("Primary".to_string())
} else if self.uiscreen == main.mirroredScreen() {
} else if *ui_screen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(MainThreadMarker::new().unwrap())
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &*self.uiscreen)
.position(|rhs| rhs == &**ui_screen)
.map(|idx| idx.to_string())
}
})
}
pub fn size(&self) -> PhysicalSize<u32> {
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<i32> {
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<u32> {
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<Item = VideoMode> {
self.ui_screen.get_on_main(|ui_screen, mtm| {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
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<UIScreen> {
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<UIScreen> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()

View file

@ -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,17 +116,19 @@ 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 {
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window,
scale_factor,
suggested_size: size,
}))
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
@ -131,7 +137,6 @@ declare_class!(
))),
);
}
}
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
@ -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);
}
}
);

View file

@ -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,8 +52,8 @@ impl Inner {
}
pub fn request_redraw(&self) {
unsafe {
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.
@ -63,12 +62,11 @@ impl Inner {
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window.clone());
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
} else {
self.view.setNeedsDisplay();
}
}
}
pub fn pre_present_notify(&self) {}
@ -219,14 +217,17 @@ impl Inner {
}
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
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<Fullscreen> {
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<T>(
_event_loop: &EventLoopWindowTarget<T>,
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
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,17 +438,19 @@ 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 {
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window: window.clone(),
scale_factor,
suggested_size: size,
}))
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
@ -455,7 +459,6 @@ impl Window {
))),
);
}
}
let inner = Inner {
window,

View file

@ -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);
}

View file

@ -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<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>,
struct EventLoopHandler<T: 'static> {
callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
}
impl<T> EventLoopHandler<T> {
@ -103,7 +99,7 @@ impl<T> EventHandler for EventLoopHandler<T> {
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<T> EventHandler for EventLoopHandler<T> {
}
}
#[derive(Debug)]
enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged {
window: Id<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Default)]
struct Handler {
stop_app_on_launch: AtomicBool,
@ -313,15 +319,10 @@ impl Handler {
self.callback.lock().unwrap().is_some()
}
fn handle_nonuser_event(&self, wrapper: EventWrapper) {
fn handle_nonuser_event(&self, event: Event<Never>) {
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),
}
}
}
fn handle_user_events(&self) {
@ -332,12 +333,12 @@ impl Handler {
fn handle_scale_factor_changed_event(
&self,
callback: &mut Box<dyn EventHandler + 'static>,
window: &WinitWindow,
suggested_size: LogicalSize<f64>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
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 {
@ -354,20 +355,6 @@ impl Handler {
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
match proxy {
EventProxy::DpiChangedProxy {
window,
suggested_size,
scale_factor,
} => self.handle_scale_factor_changed_event(
callback,
&window,
suggested_size,
scale_factor,
),
}
}
}
@ -387,10 +374,12 @@ impl AppState {
pub unsafe fn set_callback<T>(
callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
) {
*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<Never>) {
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<WinitWindow>,
suggested_size: PhysicalSize<u32>,
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() {
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() {

View file

@ -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<Never>),
EventProxy(EventProxy),
}
#[derive(Debug)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,

View file

@ -65,9 +65,10 @@ impl PanicInfo {
}
}
#[derive(Debug)]
pub struct EventLoopWindowTarget<T: 'static> {
pub receiver: mpsc::Receiver<T>,
mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
@ -107,14 +108,18 @@ impl<T> EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience.
app: Id<WinitApplication>,
/// The delegate is only weakly referenced by NSApplication, so we keep
/// it around here as well.
_delegate: Id<ApplicationDelegate>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>,
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<T> EventLoop<T> {
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<T> EventLoop<T> {
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<T> EventLoop<T> {
// # 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<T> EventLoop<T> {
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<T> EventLoop<T> {
// 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

View file

@ -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 {

View file

@ -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 crate::{
dpi::{LogicalPosition, LogicalSize},
event::{Event, WindowEvent},
platform_impl::platform::{
use super::{
app_state::AppState,
event::{EventProxy, EventWrapper},
util,
window::{get_ns_theme, WinitWindow},
Fullscreen,
},
};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{Event, WindowEvent},
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) {