From 72b24a93488a7eac8ce73bc6d03e67084a4ac8c2 Mon Sep 17 00:00:00 2001 From: mtak- Date: Wed, 25 Jul 2018 11:49:46 -0700 Subject: [PATCH] iOS Abstract Out the UIView type from winit (#609) * remove opengl code from winit * iOS: restrict EventsLoop to be created on the main thread iOS: Window can only be made once, make Drop on Window thread safe iOS: make DelegateState owned by Window, cleanup iOS: fixes from merge (class! macro) * update the changelog * Fixed nitpicks --- CHANGELOG.md | 2 + src/os/ios.rs | 18 +++- src/platform/ios/ffi.rs | 11 +-- src/platform/ios/mod.rs | 211 ++++++++++++++++++++-------------------- 4 files changed, 128 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28ed76be..399df9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - On MacOS, the key state for modifiers key events is now properly set. - On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again. - Added NetBSD support. +- **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK). +- On iOS, the `UIApplication` is not started until `Window::new` is called. # Version 0.16.2 (2018-07-07) diff --git a/src/os/ios.rs b/src/os/ios.rs index 5090272c..62c2c247 100644 --- a/src/os/ios.rs +++ b/src/os/ios.rs @@ -2,7 +2,7 @@ use std::os::raw::c_void; -use {MonitorId, Window}; +use {MonitorId, Window, WindowBuilder}; /// Additional methods on `Window` that are specific to iOS. pub trait WindowExt { @@ -29,6 +29,22 @@ impl WindowExt for Window { } } +/// Additional methods on `WindowBuilder` that are specific to iOS. +pub trait WindowBuilderExt { + /// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided. + /// + /// The class will be initialized by calling `[root_view initWithFrame:CGRect]` + fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; +} + +impl WindowBuilderExt for WindowBuilder { + #[inline] + fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder { + self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) }; + self + } +} + /// Additional methods on `MonitorId` that are specific to iOS. pub trait MonitorIdExt { /// Returns a pointer to the `UIScreen` that is used by this monitor. diff --git a/src/platform/ios/ffi.rs b/src/platform/ios/ffi.rs index bde3ee49..3e69d128 100644 --- a/src/platform/ios/ffi.rs +++ b/src/platform/ios/ffi.rs @@ -1,7 +1,6 @@ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] use std::ffi::CString; -use std::mem; use std::os::raw::*; use objc::runtime::Object; @@ -15,19 +14,11 @@ pub type Boolean = u32; pub const kCFRunLoopRunHandledSource: i32 = 4; -pub const UIViewAutoresizingFlexibleWidth: NSUInteger = 1 << 1; -pub const UIViewAutoresizingFlexibleHeight: NSUInteger = 1 << 4; - #[cfg(target_pointer_width = "32")] pub type CGFloat = f32; #[cfg(target_pointer_width = "64")] pub type CGFloat = f64; -#[cfg(target_pointer_width = "32")] -pub type NSUInteger = u32; -#[cfg(target_pointer_width = "64")] -pub type NSUInteger = u64; - #[repr(C)] #[derive(Debug, Clone)] pub struct CGPoint { @@ -76,6 +67,8 @@ extern { pub fn longjmp(env: *mut c_void, val: c_int); } +pub type JmpBuf = [c_int; 27]; + pub trait NSString: Sized { unsafe fn alloc(_: Self) -> id { msg_send![class!(NSString), alloc] diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 8be759c0..b18bf226 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -61,8 +61,10 @@ #![cfg(target_os = "ios")] use std::{fmt, mem, ptr}; +use std::cell::RefCell; use std::collections::VecDeque; use std::os::raw::*; +use std::sync::Arc; use objc::declare::ClassDecl; use objc::runtime::{BOOL, Class, Object, Sel, YES}; @@ -90,6 +92,7 @@ use self::ffi::{ CGPoint, CGRect, id, + JmpBuf, kCFRunLoopDefaultMode, kCFRunLoopRunHandledSource, longjmp, @@ -97,14 +100,13 @@ use self::ffi::{ NSString, setjmp, UIApplicationMain, - UIViewAutoresizingFlexibleWidth, - UIViewAutoresizingFlexibleHeight, }; -static mut JMPBUF: [c_int; 27] = [0; 27]; +static mut JMPBUF: Option> = None; pub struct Window { - delegate_state: *mut DelegateState, + _events_queue: Arc>>, + delegate_state: Box, } unsafe impl Send for Window {} @@ -112,7 +114,6 @@ unsafe impl Sync for Window {} #[derive(Debug)] struct DelegateState { - events_queue: VecDeque, window: id, controller: id, view: id, @@ -123,7 +124,6 @@ struct DelegateState { impl DelegateState { fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState { DelegateState { - events_queue: VecDeque::new(), window, controller, view, @@ -199,7 +199,7 @@ impl MonitorId { } pub struct EventsLoop { - delegate_state: *mut DelegateState, + events_queue: Arc>>, } #[derive(Clone)] @@ -208,21 +208,11 @@ pub struct EventsLoopProxy; impl EventsLoop { pub fn new() -> EventsLoop { unsafe { - if setjmp(mem::transmute(&mut JMPBUF)) != 0 { - let app_class = class!(UIApplication); - let app: id = msg_send![app_class, sharedApplication]; - let delegate: id = msg_send![app, delegate]; - let state: *mut c_void = *(&*delegate).get_ivar("winitState"); - let delegate_state = state as *mut DelegateState; - return EventsLoop { delegate_state }; + if !msg_send![class!(NSThread), isMainThread] { + panic!("`Window` can only be created on the main thread on iOS"); } } - - create_view_class(); - create_delegate_class(); - start_app(); - - panic!("Couldn't create `UIApplication`!") + EventsLoop { events_queue: Default::default() } } #[inline] @@ -240,29 +230,30 @@ impl EventsLoop { pub fn poll_events(&mut self, mut callback: F) where F: FnMut(::Event) { + if let Some(event) = self.events_queue.borrow_mut().pop_front() { + callback(event); + return; + } + unsafe { - let state = &mut *self.delegate_state; - - if let Some(event) = state.events_queue.pop_front() { - callback(event); - return; - } - // jump hack, so we won't quit on willTerminate event before processing it - if setjmp(mem::transmute(&mut JMPBUF)) != 0 { - if let Some(event) = state.events_queue.pop_front() { + assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS"); + if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 { + if let Some(event) = self.events_queue.borrow_mut().pop_front() { callback(event); return; } } + } + unsafe { // run runloop let seconds: CFTimeInterval = 0.000002; while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {} + } - if let Some(event) = state.events_queue.pop_front() { - callback(event) - } + if let Some(event) = self.events_queue.borrow_mut().pop_front() { + callback(event) } } @@ -301,8 +292,18 @@ pub struct WindowId; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes; +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub root_view_class: &'static Class, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + PlatformSpecificWindowBuilderAttributes { + root_view_class: class!(UIView), + } + } +} // TODO: AFAIK transparency is enabled by default on iOS, // so to be consistent with other platforms we have to change that. @@ -310,19 +311,60 @@ impl Window { pub fn new( ev: &EventsLoop, _attributes: WindowAttributes, - _pl_alltributes: PlatformSpecificWindowBuilderAttributes, + pl_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { - Ok(Window { delegate_state: ev.delegate_state }) + unsafe { + debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::>()); + assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; 27]))).is_none(), "Only one `Window` is supported on iOS"); + } + + unsafe { + if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 { + let app_class = class!(UIApplication); + let app: id = msg_send![app_class, sharedApplication]; + let delegate: id = msg_send![app, delegate]; + let state: *mut c_void = *(&*delegate).get_ivar("winitState"); + let mut delegate_state = Box::from_raw(state as *mut DelegateState); + let events_queue = &*ev.events_queue; + (&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue)); + + // easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view + let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds]; + + let uiview_class = class!(UIView); + let root_view_class = pl_attributes.root_view_class; + let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class]; + assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`"); + + delegate_state.view = msg_send![root_view_class, alloc]; + assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance"); + delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect]; + assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance"); + + let _: () = msg_send![delegate_state.controller, setView:delegate_state.view]; + let _: () = msg_send![delegate_state.window, makeKeyAndVisible]; + + return Ok(Window { + _events_queue: ev.events_queue.clone(), + delegate_state, + }); + } + } + + create_delegate_class(); + start_app(); + + panic!("Couldn't create `UIApplication`!") } #[inline] pub fn get_uiwindow(&self) -> id { - unsafe { (*self.delegate_state).window } + self.delegate_state.window } #[inline] pub fn get_uiview(&self) -> id { - unsafe { (*self.delegate_state).view } + self.delegate_state.view } #[inline] @@ -359,7 +401,7 @@ impl Window { #[inline] pub fn get_inner_size(&self) -> Option { - unsafe { Some((&*self.delegate_state).size) } + Some(self.delegate_state.size) } #[inline] @@ -404,7 +446,7 @@ impl Window { #[inline] pub fn get_hidpi_factor(&self) -> f64 { - unsafe { (&*self.delegate_state) }.scale + self.delegate_state.scale } #[inline] @@ -471,8 +513,7 @@ fn create_delegate_class() { extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL { let screen_class = class!(UIScreen); let window_class = class!(UIWindow); - let controller_class = class!(MainViewController); - let view_class = class!(MainView); + let controller_class = class!(UIViewController); unsafe { let main_screen: id = msg_send![screen_class, mainScreen]; let bounds: CGRect = msg_send![main_screen, bounds]; @@ -486,32 +527,28 @@ fn create_delegate_class() { let view_controller: id = msg_send![controller_class, alloc]; let view_controller: id = msg_send![view_controller, init]; - let view: id = msg_send![view_class, alloc]; - let view: id = msg_send![view, initForGl:&bounds]; - - let _: () = msg_send![view_controller, setView:view]; - let _: () = msg_send![window, setRootViewController:view_controller]; - let _: () = msg_send![window, makeKeyAndVisible]; - let state = Box::new(DelegateState::new(window, view_controller, view, size, scale as f64)); + let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64)); let state_ptr: *mut DelegateState = mem::transmute(state); this.set_ivar("winitState", state_ptr as *mut c_void); + // The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here. + // This makes it easier to configure the specific `UIView` type. let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0]; } YES } extern fn post_launch(_: &Object, _: Sel, _: id) { - unsafe { longjmp(mem::transmute(&mut JMPBUF),1); } + unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); } } extern fn did_become_active(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::WindowEvent { + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); + events_queue.borrow_mut().push_back(Event::WindowEvent { window_id: RootEventId(WindowId), event: WindowEvent::Focused(true), }); @@ -520,9 +557,9 @@ fn create_delegate_class() { extern fn will_resign_active(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::WindowEvent { + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); + events_queue.borrow_mut().push_back(Event::WindowEvent { window_id: RootEventId(WindowId), event: WindowEvent::Focused(false), }); @@ -531,38 +568,38 @@ fn create_delegate_class() { extern fn will_enter_foreground(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Suspended(false)); + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); + events_queue.borrow_mut().push_back(Event::Suspended(false)); } } extern fn did_enter_background(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Suspended(true)); + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); + events_queue.borrow_mut().push_back(Event::Suspended(true)); } } extern fn will_terminate(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); // push event to the front to garantee that we'll process it // immidiatly after jump - state.events_queue.push_front(Event::WindowEvent { + events_queue.borrow_mut().push_front(Event::WindowEvent { window_id: RootEventId(WindowId), event: WindowEvent::Destroyed, }); - longjmp(mem::transmute(&mut JMPBUF),1); + longjmp(mem::transmute_copy(&mut JMPBUF), 1); } } extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) { unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); + let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); + let events_queue = &*(events_queue as *const RefCell>); let touches_enum: id = msg_send![touches, objectEnumerator]; @@ -575,7 +612,7 @@ fn create_delegate_class() { let touch_id = touch as u64; let phase: i32 = msg_send![touch, phase]; - state.events_queue.push_back(Event::WindowEvent { + events_queue.borrow_mut().push_back(Event::WindowEvent { window_id: RootEventId(WindowId), event: WindowEvent::Touch(Touch { device_id: DEVICE_ID, @@ -635,46 +672,12 @@ fn create_delegate_class() { post_launch as extern fn(&Object, Sel, id)); decl.add_ivar::<*mut c_void>("winitState"); + decl.add_ivar::<*mut c_void>("eventsQueue"); decl.register(); } } -// TODO: winit shouldn't contain GL-specfiic code -pub fn create_view_class() { - let superclass = class!(UIViewController); - let decl = ClassDecl::new("MainViewController", superclass).expect("Failed to declare class `MainViewController`"); - decl.register(); - - extern fn init_for_gl(this: &Object, _: Sel, frame: *const c_void) -> id { - unsafe { - let bounds = frame as *const CGRect; - let view: id = msg_send![this, initWithFrame:(*bounds).clone()]; - - let mask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - let _: () = msg_send![view, setAutoresizingMask:mask]; - let _: () = msg_send![view, setAutoresizesSubviews:YES]; - - let layer: id = msg_send![view, layer]; - let _ : () = msg_send![layer, setOpaque:YES]; - - view - } - } - - extern fn layer_class(_: &Class, _: Sel) -> *const Class { - class!(CAEAGLLayer) - } - - let superclass = class!(GLKView); - let mut decl = ClassDecl::new("MainView", superclass).expect("Failed to declare class `MainView`"); - unsafe { - decl.add_method(sel!(initForGl:), init_for_gl as extern fn(&Object, Sel, *const c_void) -> id); - decl.add_class_method(sel!(layerClass), layer_class as extern fn(&Class, Sel) -> *const Class); - decl.register(); - } -} - #[inline] fn start_app() { unsafe {