use std::{ffi::c_void, ptr, sync::Arc};
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFRetained, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverContext,
kCFRunLoopCommonModes,
};
type OnPass = dyn Fn() + Send + Sync;
#[derive(Clone)]
pub(crate) struct MainThreadWake {
inner: Arc<Inner>,
}
struct Inner {
main_loop: CFRetained<CFRunLoop>,
observer: CFRetained<CFRunLoopObserver>,
}
unsafe impl Send for Inner {}
unsafe impl Sync for Inner {}
impl Drop for Inner {
fn drop(&mut self) {
self.observer.invalidate();
}
}
unsafe extern "C-unwind" fn on_before_sources(
_: *mut CFRunLoopObserver,
_: CFRunLoopActivity,
info: *mut c_void,
) {
let on_pass = unsafe { &*(info as *const Box<OnPass>) };
on_pass();
}
unsafe extern "C-unwind" fn release(info: *const c_void) {
drop(unsafe { Box::from_raw(info as *mut Box<OnPass>) });
}
impl MainThreadWake {
pub(crate) fn new(on_pass: impl Fn() + Send + Sync + 'static) -> Self {
debug_assert!(
MainThreadMarker::new().is_some(),
"MainThreadWake must be created on the main thread"
);
let on_pass: Box<OnPass> = Box::new(on_pass);
let mut context = CFRunLoopObserverContext {
version: 0,
info: Box::into_raw(Box::new(on_pass)) as *mut c_void,
retain: None,
release: Some(release),
copyDescription: None,
};
let observer = unsafe {
CFRunLoopObserver::new(
None,
CFRunLoopActivity::BeforeSources.0,
true,
0,
Some(on_before_sources),
ptr::addr_of_mut!(context),
)
}
.expect("failed to create the wake-up run-loop observer");
let main_loop = CFRunLoop::main().expect("no main run loop");
main_loop.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
Self {
inner: Arc::new(Inner {
main_loop,
observer,
}),
}
}
pub(crate) fn wake_up(&self) {
self.inner.main_loop.wake_up();
}
}