From 7c7fcc98872b3c35bd7767b5c6235a74bc105e06 Mon Sep 17 00:00:00 2001 From: Francesca Plebani Date: Wed, 12 Dec 2018 17:11:09 -0500 Subject: [PATCH] Very rough usage of CFRunLoop --- src/platform_impl/macos/app_delegate.rs | 101 ++++++++ src/platform_impl/macos/event_loop.rs | 201 ++++++++++++---- src/platform_impl/macos/mod.rs | 2 + src/platform_impl/macos/observer.rs | 264 +++++++++++++++++++++ src/platform_impl/macos/window_delegate.rs | 2 +- 5 files changed, 522 insertions(+), 48 deletions(-) create mode 100644 src/platform_impl/macos/app_delegate.rs create mode 100644 src/platform_impl/macos/observer.rs diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs new file mode 100644 index 0000000000..09b2945071 --- /dev/null +++ b/src/platform_impl/macos/app_delegate.rs @@ -0,0 +1,101 @@ +use cocoa::base::id; +use objc::{runtime::{Class, Object, Sel, BOOL, YES}, declare::ClassDecl}; + +use platform_impl::platform::event_loop::HANDLER; + +pub struct AppDelegateClass(pub *const Class); +unsafe impl Send for AppDelegateClass {} +unsafe impl Sync for AppDelegateClass {} + +lazy_static! { + pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); + + decl.add_method( + sel!(applicationDidFinishLaunching:), + did_finish_launching as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(applicationDidBecomeActive:), + did_become_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillResignActive:), + will_resign_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillEnterForeground:), + will_enter_foreground as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationDidEnterBackground:), + did_enter_background as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillTerminate:), + will_terminate as extern fn(&Object, Sel, id), + ); + + AppDelegateClass(decl.register()) + }; +} + +extern fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `didFinishLaunching`"); + HANDLER.lock().unwrap().launched(); + trace!("Completed `didFinishLaunching`"); + YES +} + +extern fn did_become_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `didBecomeActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(false)) + }*/ + trace!("Completed `didBecomeActive`"); +} + +extern fn will_resign_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `willResignActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(true)) + }*/ + trace!("Completed `willResignActive`"); +} + +extern fn will_enter_foreground(_: &Object, _: Sel, _: id) { + trace!("Triggered `willEnterForeground`"); + trace!("Completed `willEnterForeground`"); +} + +extern fn did_enter_background(_: &Object, _: Sel, _: id) { + trace!("Triggered `didEnterBackground`"); + trace!("Completed `didEnterBackground`"); +} + +extern fn will_terminate(_: &Object, _: Sel, _: id) { + trace!("Triggered `willTerminate`"); + /*unsafe { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break + } + let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)]; + if is_winit_window == YES { + events.push(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Destroyed, + }); + } + } + HANDLER.lock().unwrap().handle_nonuser_events(events); + HANDLER.lock().unwrap().terminated(); + }*/ + trace!("Completed `willTerminate`"); +} diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 3e7261495c..364de98366 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -1,6 +1,7 @@ use std::{ - collections::VecDeque, hint::unreachable_unchecked, marker::PhantomData, - mem, os::raw::*, process::exit, sync::{Arc, Mutex, Weak}, + collections::VecDeque, fmt::{self, Debug, Formatter}, + hint::unreachable_unchecked, marker::PhantomData, mem, os::raw::*, + process::exit, sync::{Arc, Mutex, Weak}, }; use cocoa::{ @@ -21,7 +22,9 @@ use { window, }; use platform_impl::platform::{ - DEVICE_ID, monitor::{self, MonitorHandle}, window::UnownedWindow, + app_delegate::APP_DELEGATE_CLASS, DEVICE_ID, monitor::{self, MonitorHandle}, + observer::{EventLoopWaker, setup_control_flow_observers}, + util::IdRef, window::UnownedWindow, }; #[derive(Default)] @@ -87,6 +90,118 @@ impl WindowList { } } +lazy_static! { + pub static ref HANDLER: Mutex = Default::default(); +} + +#[derive(Default)] +pub struct Handler { + control_flow: ControlFlow, + control_flow_prev: ControlFlow, + callback: Option>, + waker: EventLoopWaker, +} + +unsafe impl Send for Handler {} +unsafe impl Sync for Handler {} + +impl Handler { + pub fn launched(&mut self) { + self.waker.start(); + if let Some(ref mut callback) = self.callback { + callback.handle_nonuser_event(Event::NewEvents(StartCause::Init), &mut self.control_flow); + } + } + + pub fn wakeup(&mut self) { + self.control_flow_prev = self.control_flow; + let cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + /*ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(requested_resume) => { + if Instant::now() >= requested_resume { + StartCause::ResumeTimeReached { + start, + requested_resume, + } + } else { + StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + } + } + },*/ + ControlFlow::Exit => panic!("unexpected `ControlFlow::Exit`"), + _ => unimplemented!(), + }; + if let Some(ref mut callback) = self.callback { + callback.handle_nonuser_event(Event::NewEvents(cause), &mut self.control_flow); + } + } + + pub fn cleared(&mut self) { + if let Some(ref mut callback) = self.callback { + callback.handle_nonuser_event(Event::EventsCleared, &mut self.control_flow); + } + let old = self.control_flow_prev; + let new = self.control_flow; + match (old, new) { + (ControlFlow::Poll, ControlFlow::Poll) => (), + (ControlFlow::Wait, ControlFlow::Wait) => (), + (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => (), + (_, ControlFlow::Wait) => self.waker.stop(), + (_, ControlFlow::WaitUntil(new_instant)) => self.waker.start_at(new_instant), + (_, ControlFlow::Poll) => self.waker.start(), + (_, ControlFlow::Exit) => (), + } + } +} + +pub trait EventHandler: Debug { + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + //fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + callback: F, + event_loop: RootELW, +} + +impl Debug for EventLoopHandler { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.debug_struct("EventLoopHandler") + .field("event_loop", &self.event_loop) + .finish() + } +} + +impl EventHandler for EventLoopHandler +where + F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), + T: 'static, +{ + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + (self.callback)( + event.userify(), + &self.event_loop, + control_flow, + ); + } + + /*fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + for event in self.event_loop.inner.receiver.try_iter() { + (self.callback)( + Event::UserEvent(event), + &self.event_loop, + control_flow, + ); + } + }*/ +} + pub struct EventLoopWindowTarget { pub pending_events: Arc>, pub window_list: Arc>, @@ -105,19 +220,35 @@ impl Default for EventLoopWindowTarget { pub struct EventLoop { elw_target: RootELW, + delegate: IdRef, modifiers: Modifiers, } impl EventLoop { pub fn new() -> Self { - // Mark this thread as the main thread of the Cocoa event system. - // - // This must be done before any worker threads get a chance to call it - // (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be - // marked as the main thread. - unsafe { NSApp() }; + let delegate = unsafe { + if !msg_send![class!(NSThread), isMainThread] { + // This check should be in `new` instead + panic!("Events can only be polled from the main thread on macOS"); + } + + // Mark this thread as the main thread of the Cocoa event system. + // + // This must be done before any worker threads get a chance to call it + // (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be + // marked as the main thread. + let app = NSApp(); + + let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); + let pool = NSAutoreleasePool::new(nil); + let _: () = msg_send![app, setDelegate:*delegate]; + let _: () = msg_send![pool, drain]; + delegate + }; + setup_control_flow_observers(); EventLoop { elw_target: RootELW::new(Default::default()), + delegate, // is this necessary? modifiers: Default::default(), } } @@ -139,18 +270,8 @@ impl EventLoop { pub fn run(mut self, mut callback: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), { - unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Events can only be polled from the main thread on macOS"); - } - } - - let mut control_flow = Default::default(); - let mut cause = StartCause::Init; - + /* loop { - callback(Event::NewEvents(cause), self.window_target(), &mut control_flow); - { trace!("Locked pending events in `run`"); let mut pending = self.elw_target @@ -169,38 +290,24 @@ impl EventLoop { } } - let maybe_event = unsafe { - let pool = NSAutoreleasePool::new(nil); - - // Wait for the next event. Note that this function blocks during resize. - let ns_event = NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(), - NSDate::distantFuture(nil), - NSDefaultRunLoopMode, - YES, - ); - - let maybe_event = self.translate_event(ns_event); - - // Release the pool before calling the top callback in case the user calls either - // `run_forever` or `poll_events` within the callback. - let _: () = msg_send![pool, release]; - - maybe_event - }; - - if let Some(event) = maybe_event { - callback(event.userify(), self.window_target(), &mut control_flow); - } - - callback(Event::EventsCleared, self.window_target(), &mut control_flow); - if let ControlFlow::Exit = control_flow { callback(Event::LoopDestroyed, self.window_target(), &mut control_flow); exit(0); } + } + */ - cause = StartCause::Poll; + unsafe { + let _pool = NSAutoreleasePool::new(nil); + let app = NSApp(); + assert!(!app.is_null()); + HANDLER.lock().unwrap().callback = Some(Box::new(EventLoopHandler { + callback, + event_loop: self.elw_target, + })); + let _: () = msg_send![app, run]; + // This is probably wrong + unreachable_unchecked() } } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 90a5dc78d6..c76e743748 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,8 +1,10 @@ #![cfg(target_os = "macos")] +mod app_delegate; mod event_loop; mod ffi; mod monitor; +mod observer; mod util; mod view; mod window; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs new file mode 100644 index 0000000000..92479ad74f --- /dev/null +++ b/src/platform_impl/macos/observer.rs @@ -0,0 +1,264 @@ +use std::{self, ptr, os::raw::*, time::Instant}; + +use platform_impl::platform::event_loop::HANDLER; + +#[link(name = "CoreFoundation", kind = "framework")] +extern { + pub static kCFRunLoopDefaultMode: CFRunLoopMode; + pub static kCFRunLoopCommonModes: CFRunLoopMode; + + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer( + rl: CFRunLoopRef, + timer: CFRunLoopTimerRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopTimerSetNextFireDate( + timer: CFRunLoopTimerRef, + fireDate: CFAbsoluteTime, + ); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource( + rl: CFRunLoopRef, + source: CFRunLoopSourceRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); +} + +pub type Boolean = u8; +const FALSE: Boolean = 0; +const TRUE: Boolean = 1; + +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = extern "C" fn( + observer: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + info: *mut c_void, +); +pub type CFRunLoopTimerCallBack = extern "C" fn( + timer: CFRunLoopTimerRef, + info: *mut c_void +); + +pub enum CFRunLoopObserverContext {} +pub enum CFRunLoopTimerContext {} + +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: extern "C" fn(*const c_void) -> *const c_void, + pub release: extern "C" fn(*const c_void), + pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, + pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, + pub hash: extern "C" fn(*const c_void) -> CFHashCode, + pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub perform: extern "C" fn(*mut c_void), +} + +// begin is queued with the highest priority to ensure it is processed before other observers +extern fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => { + trace!("Triggered `CFRunLoopAfterWaiting`"); + HANDLER.lock().unwrap().wakeup(); + trace!("Completed `CFRunLoopAfterWaiting`"); + }, + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } +} + +// end is queued with the lowest priority to ensure it is processed after other observers +// without that, LoopDestroyed would get sent after EventsCleared +extern fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => { + trace!("Triggered `CFRunLoopBeforeWaiting`"); + HANDLER.lock().unwrap().cleared(); + trace!("Completed `CFRunLoopBeforeWaiting`"); + }, + kCFRunLoopExit => (),//unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } +} + +struct RunLoop(CFRunLoopRef); + +impl RunLoop { + unsafe fn get() -> Self { + RunLoop(CFRunLoopGetMain()) + } + + unsafe fn add_observer( + &self, + flags: CFOptionFlags, + priority: CFIndex, + handler: CFRunLoopObserverCallBack, + ) { + let observer = CFRunLoopObserverCreate( + ptr::null_mut(), + flags, + TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run + handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode); + } +} + +pub fn setup_control_flow_observers() { + unsafe { + let run_loop = RunLoop::get(); + run_loop.add_observer( + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + CFIndex::min_value(), + control_flow_begin_handler, + ); + run_loop.add_observer( + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + CFIndex::max_value(), + control_flow_end_handler, + ); + } +} + + +pub struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl Default for EventLoopWaker { + fn default() -> EventLoopWaker { + extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // create a timer with a 1microsec 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 immediatley in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + std::f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); + + EventLoopWaker { timer } + } + } +} + +impl EventLoopWaker { + pub fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } + + pub fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } + + pub fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + 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) + } + } + } +} diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 18811e0f24..af81a029aa 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -150,7 +150,7 @@ unsafe impl Sync for WindowDelegateClass {} lazy_static! { static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe { - let superclass = class!(NSObject); + let superclass = class!(NSResponder); let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); decl.add_method(