use core_foundation::base::kCFAllocatorDefault;
use core_foundation::runloop::{
CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopRemoveSource, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal,
CFRunLoopWakeUp, kCFRunLoopCommonModes,
};
use crossbeam::channel::{self, Receiver, Sender};
use objc::{class, msg_send, sel, sel_impl};
use std::os::raw::c_void;
use std::sync::Weak;
use super::{BackgroundThread, EventLoop, MainThreadExecutor};
use crate::nice_debug_assert_failure;
struct LoopSourceWrapper(CFRunLoopSourceRef);
unsafe impl Send for LoopSourceWrapper {}
unsafe impl Sync for LoopSourceWrapper {}
pub(crate) struct MacOSEventLoop<T, E> {
executor: Weak<E>,
background_thread: BackgroundThread<T, E>,
loop_source: LoopSourceWrapper,
main_thread_sender: Sender<T>,
_callback_data: Box<(Weak<E>, Receiver<T>)>,
}
impl<T, E> EventLoop<T, E> for MacOSEventLoop<T, E>
where
T: Send + 'static,
E: MainThreadExecutor<T> + 'static,
{
fn new_and_spawn(executor: Weak<E>) -> Self {
let (main_thread_sender, main_thread_receiver) =
channel::bounded::<T>(super::TASK_QUEUE_CAPACITY);
let callback_data = Box::new((executor.clone(), main_thread_receiver));
let loop_source;
unsafe {
let source_context = CFRunLoopSourceContext {
info: &*callback_data as *const _ as *mut c_void,
cancel: None,
copyDescription: None,
equal: None,
hash: None,
perform: loop_source_callback::<T, E>,
release: None,
retain: None,
schedule: None,
version: 0,
};
loop_source = CFRunLoopSourceCreate(
kCFAllocatorDefault,
1,
&source_context as *const _ as *mut CFRunLoopSourceContext,
);
CFRunLoopAddSource(CFRunLoopGetMain(), loop_source, kCFRunLoopCommonModes);
}
Self {
executor: executor.clone(),
background_thread: BackgroundThread::get_or_create(executor),
loop_source: LoopSourceWrapper(loop_source),
main_thread_sender,
_callback_data: callback_data,
}
}
fn schedule_gui(&self, task: T) -> bool {
if self.is_main_thread() {
match self.executor.upgrade() {
Some(executor) => executor.execute(task, true),
None => {
nice_debug_assert_failure!("GUI task posted after the executor was dropped")
}
}
true
} else {
let success = self.main_thread_sender.try_send(task).is_ok();
if success {
unsafe {
CFRunLoopSourceSignal(self.loop_source.0);
CFRunLoopWakeUp(CFRunLoopGetMain());
}
}
success
}
}
fn schedule_background(&self, task: T) -> bool {
self.background_thread.schedule(task)
}
fn is_main_thread(&self) -> bool {
unsafe { msg_send![class!(NSThread), isMainThread] }
}
}
impl<T, E> Drop for MacOSEventLoop<T, E> {
fn drop(&mut self) {
unsafe {
CFRunLoopRemoveSource(
CFRunLoopGetMain(),
self.loop_source.0,
kCFRunLoopCommonModes,
);
CFRunLoopSourceInvalidate(self.loop_source.0);
}
}
}
extern "C" fn loop_source_callback<T, E>(info: *const c_void)
where
T: Send + 'static,
E: MainThreadExecutor<T> + 'static,
{
let (executor, receiver) = unsafe { &*(info as *mut (Weak<E>, Receiver<T>)) };
let executor = match executor.upgrade() {
Some(executor) => executor,
None => {
nice_debug_assert_failure!("GUI task was posted after the executor was dropped");
return;
}
};
while let Ok(task) = receiver.try_recv() {
executor.execute(task, true);
}
}