//! Creating and attaching various runloop objects. use std::sync::Arc; use std::time::Duration; use std::{cell::Cell, ffi::c_void}; use objc2_core_foundation::{ kCFAllocatorDefault, kCFRunLoopCommonModes, CFAbsoluteTime, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, CFRunLoop, CFRunLoopActivity, CFRunLoopMode, CFRunLoopObserver, CFRunLoopObserverContext, CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopTimer, CFRunLoopTimerContext, CFTimeInterval, }; fn main() { let rl = CFRunLoop::main().unwrap(); // Add an observer. let observer = create_observer( CFRunLoopActivity::AllActivities, true, 0, |_observer, activity| match activity { CFRunLoopActivity::Entry => println!("-- observed: Entry"), CFRunLoopActivity::BeforeTimers => println!("-- observed: BeforeTimers"), CFRunLoopActivity::BeforeSources => println!("-- observed: BeforeSources"), CFRunLoopActivity::BeforeWaiting => println!("-- observed: BeforeWaiting"), CFRunLoopActivity::AfterWaiting => println!("-- observed: AfterWaiting"), CFRunLoopActivity::Exit => println!("-- observed: Exit"), _ => eprintln!("unknown observer activity"), }, ); rl.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes }); // Add a simple timer. let iterations = Cell::new(0); let callback = { let rl = rl.clone(); move |timer: &CFRunLoopTimer| { println!("Timer called: {}", iterations.get()); if 10 <= iterations.get() { // Remove the timer. rl.remove_timer(Some(timer), unsafe { kCFRunLoopCommonModes }); // Stop the run loop explicitly // (the main run-loop won't stop otherwise). println!("Stopping run loop"); rl.stop(); } iterations.set(iterations.get() + 1); } }; // SAFETY: The timer is added to a run loop on the same thread. let timer = unsafe { create_timer_unchecked(0.0, 0.1, 0, callback) }; rl.add_timer(Some(&timer), unsafe { kCFRunLoopCommonModes }); // Add a single-shot timer. let fire_date = CFAbsoluteTimeGetCurrent() + 0.5; let timer = create_timer(fire_date, 0.0, 0, |_| { println!("Fired one-shot timer after 0.5 seconds"); // Still runs on the main thread. assert_eq!(CFRunLoop::current().unwrap(), CFRunLoop::main().unwrap()); }); rl.add_timer(Some(&timer), unsafe { kCFRunLoopCommonModes }); // Add an external source. let source = create_source(0, |data| match data { SourceData::Schedule { mode, .. } => println!("source added to runloop mode {mode}"), SourceData::Perform => println!("performing source!"), SourceData::Cancel { mode, .. } => { println!("source removed from runloop mode {mode}") } }); rl.add_source(Some(&source), unsafe { kCFRunLoopCommonModes }); // Add a thread that signals the source. let source = { struct Wrapper(CFRetained); // SAFETY: Still under discussion, see: // https://github.com/madsmtm/objc2/issues/696 unsafe impl Send for Wrapper {} Wrapper(source) }; let handle = std::thread::spawn(move || { let source = source; std::thread::sleep(Duration::from_millis(200)); println!("signalling first time"); source.0.signal(); println!("signalling second time"); source.0.signal(); println!("signalling third time"); source.0.signal(); std::thread::sleep(Duration::from_millis(500)); println!("signalling fourth time"); source.0.signal(); source.0.invalidate(); }); println!("Starting run loop"); CFRunLoop::run(); handle.join().unwrap(); } // ------------------------------------------------------------------------- // Generic runloop helpers. // // These will probably become part of `objc2-core-foundation` one day, but // more work is needed on the following issue first: // https://github.com/madsmtm/objc2/issues/696 // ------------------------------------------------------------------------- /// Create a new `CFRunLoopObserver`. fn create_observer( activities: CFRunLoopActivity, repeats: bool, order: CFIndex, callback: F, ) -> CFRetained { // SAFETY: The callback is `Send + Sync`. unsafe { create_observer_unchecked(activities, repeats, order, callback) } } /// Create a new `CFRunLoopObserver`, without enforcing the callback to be /// thread-safe. /// /// # Safety /// /// The callback must be either `Send` + `Sync`, or the observer must only be /// added to a run loop that runs on the current thread. unsafe fn create_observer_unchecked( activities: CFRunLoopActivity, repeats: bool, order: CFIndex, callback: F, ) -> CFRetained { // We use an `Arc` here to make sure that the reference-counting of the // signal container is atomic (`Retained`/`CFRetained` would be valid // alternatives too). let callback = Arc::new(callback); unsafe extern "C-unwind" fn retain(info: *const c_void) -> *const c_void { // SAFETY: The pointer was passed to `CFRunLoopObserverContext.info` below. unsafe { Arc::increment_strong_count(info.cast::()) }; info } unsafe extern "C-unwind" fn release(info: *const c_void) { // SAFETY: The pointer was passed to `CFRunLoopObserverContext.info` below. unsafe { Arc::decrement_strong_count(info.cast::()) }; } unsafe extern "C-unwind" fn callout( observer: *mut CFRunLoopObserver, activity: CFRunLoopActivity, info: *mut c_void, ) { // SAFETY: The observer is valid for at least the duration of the callback. let observer = unsafe { &*observer }; // SAFETY: The pointer was passed to `CFRunLoopObserverContext.info` below. let callback = unsafe { &*info.cast::() }; // Call the provided closure. callback(observer, activity); } // This is marked `mut` to match the signature of `CFRunLoopObserver::new`, // but the information is copied, and not actually mutated. let mut context = CFRunLoopObserverContext { version: 0, // This pointer is retained by CF on creation. info: Arc::as_ptr(&callback) as *mut c_void, retain: Some(retain::), release: Some(release::), copyDescription: None, }; // SAFETY: The retain/release callbacks are thread-safe, and caller // upholds that the main callback is used in a thread-safe manner. // // `F: 'static`, so extending the lifetime of the closure is fine. unsafe { CFRunLoopObserver::new( kCFAllocatorDefault, activities.0, repeats, order, Some(callout::), &mut context, ) } .unwrap() } /// Create a new `CFRunLoopTimer`. fn create_timer( fire_date: CFAbsoluteTime, interval: CFTimeInterval, order: CFIndex, callback: F, ) -> CFRetained { // SAFETY: The callback is `Send + Sync`. unsafe { create_timer_unchecked(fire_date, interval, order, callback) } } /// Create a new `CFRunLoopTimer`, without enforcing the callback to be /// thread-safe. /// /// # Safety /// /// The callback must be either `Send` + `Sync`, or the timer must only be /// added to a run loop that runs on the current thread. unsafe fn create_timer_unchecked( fire_date: CFAbsoluteTime, interval: CFTimeInterval, order: CFIndex, callback: F, ) -> CFRetained { // We use an `Arc` here to make sure that the reference-counting of the // signal container is atomic (`Retained`/`CFRetained` would be valid // alternatives too). let callback = Arc::new(callback); unsafe extern "C-unwind" fn retain(info: *const c_void) -> *const c_void { // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below. unsafe { Arc::increment_strong_count(info.cast::()) }; info } unsafe extern "C-unwind" fn release(info: *const c_void) { // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below. unsafe { Arc::decrement_strong_count(info.cast::()) }; } unsafe extern "C-unwind" fn callout( timer: *mut CFRunLoopTimer, info: *mut c_void, ) { // SAFETY: The timer is valid for at least the duration of the callback. let timer = unsafe { &*timer }; // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below. let callback = unsafe { &*info.cast::() }; // Call the provided closure. callback(timer); } // This is marked `mut` to match the signature of `CFRunLoopTimer::new`, // but the information is copied, and not actually mutated. let mut context = CFRunLoopTimerContext { version: 0, // This pointer is retained by CF on creation. info: Arc::as_ptr(&callback) as *mut c_void, retain: Some(retain::), release: Some(release::), copyDescription: None, }; // SAFETY: The retain/release callbacks are thread-safe, and caller // upholds that the main callback is used in a thread-safe manner. // // `F: 'static`, so extending the lifetime of the closure is fine. unsafe { CFRunLoopTimer::new( kCFAllocatorDefault, fire_date, interval, 0, // Documentation says to pass 0 for future compat. order, Some(callout::), &mut context, ) } .unwrap() } /// Data received in source callbacks. #[derive(Debug)] #[allow(dead_code)] enum SourceData<'a> { Schedule { rl: &'a CFRunLoop, mode: &'a CFRunLoopMode, }, Perform, Cancel { rl: &'a CFRunLoop, mode: &'a CFRunLoopMode, }, } /// Create a new "version 0" `CFRunLoopSource`. fn create_source) + Send + Sync + 'static>( order: CFIndex, callback: F, ) -> CFRetained { // We use an `Arc` here to make sure that the reference-counting of the // signal container is atomic (`Retained`/`CFRetained` would be valid // alternatives too). let callback = Arc::new(callback); unsafe extern "C-unwind" fn retain(info: *const c_void) -> *const c_void { // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. unsafe { Arc::increment_strong_count(info.cast::()) }; info } unsafe extern "C-unwind" fn release(info: *const c_void) { // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. unsafe { Arc::decrement_strong_count(info.cast::()) }; } // Pointer equality / hashing for the Arc. #[allow(clippy::ptr_eq)] extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 { (info1 == info2) as u8 } extern "C-unwind" fn hash(info: *const c_void) -> usize { info as usize } unsafe extern "C-unwind" fn schedule)>( info: *mut c_void, rl: *mut CFRunLoop, mode: *const CFRunLoopMode, ) { // SAFETY: The data is valid for at least the duration of the callback. let rl = unsafe { &*rl }; let mode = unsafe { &*mode }; // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. let signaller = unsafe { &*info.cast::() }; (signaller)(SourceData::Schedule { rl, mode }); } unsafe extern "C-unwind" fn cancel)>( info: *mut c_void, rl: *mut CFRunLoop, mode: *const CFRunLoopMode, ) { // SAFETY: The data is valid for at least the duration of the callback. let rl = unsafe { &*rl }; let mode = unsafe { &*mode }; // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. let signaller = unsafe { &*info.cast::() }; (signaller)(SourceData::Cancel { rl, mode }); } unsafe extern "C-unwind" fn perform)>(info: *mut c_void) { // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. let signaller = unsafe { &*info.cast::() }; (signaller)(SourceData::Perform); } // This is marked `mut` to match the signature of `CFRunLoopSource::new`, // but the information is copied, and not actually mutated. let mut context = CFRunLoopSourceContext { version: 0, // Version 0 source // This pointer is retained by CF on creation. info: Arc::as_ptr(&callback) as *mut c_void, retain: Some(retain::), release: Some(release::), copyDescription: None, equal: Some(equal), hash: Some(hash), schedule: Some(schedule::), cancel: Some(cancel::), perform: Some(perform::), }; // SAFETY: The retain/release callbacks are thread-safe, and F is marked // with `Send + Sync`, so that is thread-safe too. // // `F: 'static`, so extending the lifetime of the closure is fine. unsafe { CFRunLoopSource::new(kCFAllocatorDefault, order, &mut context) }.unwrap() }