Update to objc2 v0.6 (#4092)
* Use available! macro * Use objc2-core-foundation and objc2-core-graphics * Use MainThreadBound instead of StaticMainThreadBound hack
This commit is contained in:
parent
f5dcd2aabe
commit
953d9b4268
25 changed files with 787 additions and 1103 deletions
|
|
@ -4,23 +4,19 @@ use std::cell::{OnceCell, RefCell, RefMut};
|
|||
use std::collections::HashSet;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::sel;
|
||||
use objc2_foundation::{
|
||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
|
||||
NSProcessInfo,
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer,
|
||||
CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerSetNextFireDate, CGRect, CGSize,
|
||||
};
|
||||
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
|
||||
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
|
||||
|
||||
use super::super::event_handler::EventHandler;
|
||||
use super::window::WinitUIWindow;
|
||||
|
|
@ -48,22 +44,10 @@ macro_rules! bug_assert {
|
|||
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
|
||||
/// is executing.
|
||||
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
|
||||
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
|
||||
struct StaticMainThreadBound<T>(T);
|
||||
|
||||
impl<T> StaticMainThreadBound<T> {
|
||||
const fn get(&self, _mtm: MainThreadMarker) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for StaticMainThreadBound<T> {}
|
||||
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
|
||||
|
||||
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
|
||||
// of the main thread.
|
||||
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
|
||||
StaticMainThreadBound(OnceCell::new());
|
||||
static GLOBAL: MainThreadBound<OnceCell<EventHandler>> =
|
||||
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
|
||||
|
||||
GLOBAL.get(mtm).get_or_init(EventHandler::new)
|
||||
}
|
||||
|
|
@ -131,7 +115,7 @@ impl AppState {
|
|||
#[inline(never)]
|
||||
#[cold]
|
||||
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
|
||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() });
|
||||
**guard = Some(AppState {
|
||||
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
|
||||
control_flow: ControlFlow::default(),
|
||||
|
|
@ -440,13 +424,7 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
|
|||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
if let Ok(window) = window.downcast::<WinitUIWindow>() {
|
||||
events.push(EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::Occluded(occluded),
|
||||
|
|
@ -510,13 +488,7 @@ pub(crate) fn terminated(application: &UIApplication) {
|
|||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
if let Ok(window) = window.downcast::<WinitUIWindow>() {
|
||||
events.push(EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::Destroyed,
|
||||
|
|
@ -569,46 +541,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
|
|||
}
|
||||
|
||||
struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
timer: CFRetained<CFRunLoopTimer>,
|
||||
}
|
||||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
CFRunLoopTimerInvalidate(&self.timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker {
|
||||
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||
// It is initially setup with a first fire time really far into the
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
Some(wakeup_main_loop),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes);
|
||||
|
||||
EventLoopWaker { timer }
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) }
|
||||
}
|
||||
|
||||
fn start_at(&mut self, instant: Instant) {
|
||||
|
|
@ -621,94 +593,8 @@ impl EventLoopWaker {
|
|||
let duration = instant - now;
|
||||
let fsecs =
|
||||
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
|
||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
||||
CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! os_capabilities {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$error_name:ident: $objc_call:literal,
|
||||
$name:ident: $major:literal-$minor:literal
|
||||
),*
|
||||
$(,)*
|
||||
) => {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OSCapabilities {
|
||||
$(
|
||||
pub $name: bool,
|
||||
)*
|
||||
|
||||
os_version: NSOperatingSystemVersion,
|
||||
}
|
||||
|
||||
impl OSCapabilities {
|
||||
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
|
||||
$(let $name = meets_requirements(os_version, $major, $minor);)*
|
||||
Self { $($name,)* os_version, }
|
||||
}
|
||||
}
|
||||
|
||||
impl OSCapabilities {$(
|
||||
$(#[$attr])*
|
||||
pub fn $error_name(&self, extra_msg: &str) {
|
||||
tracing::warn!(
|
||||
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
|
||||
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
|
||||
extra_msg
|
||||
)
|
||||
}
|
||||
)*}
|
||||
};
|
||||
}
|
||||
|
||||
os_capabilities! {
|
||||
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
safe_area_err_msg: "-[UIView safeAreaInsets]",
|
||||
safe_area: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
|
||||
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
|
||||
home_indicator_hidden: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
|
||||
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
|
||||
defer_system_gestures: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
|
||||
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
|
||||
maximum_frames_per_second: 10-3,
|
||||
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
force_touch_err_msg: "-[UITouch force]",
|
||||
force_touch: 9-0,
|
||||
}
|
||||
|
||||
fn meets_requirements(
|
||||
version: NSOperatingSystemVersion,
|
||||
required_major: NSInteger,
|
||||
required_minor: NSInteger,
|
||||
) -> bool {
|
||||
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
|
||||
}
|
||||
|
||||
fn get_version() -> NSOperatingSystemVersion {
|
||||
let process_info = NSProcessInfo::processInfo();
|
||||
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
|
||||
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
|
||||
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
|
||||
// not support debugging on devices with an iOS version of less than 8. Another example, in
|
||||
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
|
||||
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
||||
process_info.operatingSystemVersion()
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
// Cache the version lookup for efficiency
|
||||
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
|
||||
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue