initial event loop implementation for macOS
This commit is contained in:
parent
ab9adaf13e
commit
9fd88ffd48
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2611,6 +2611,7 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"clap",
|
"clap",
|
||||||
"clap-sys",
|
"clap-sys",
|
||||||
|
"core-foundation",
|
||||||
"cpal",
|
"cpal",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"jack",
|
"jack",
|
||||||
|
@ -2619,6 +2620,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"midi-consts",
|
"midi-consts",
|
||||||
"nih_plug_derive",
|
"nih_plug_derive",
|
||||||
|
"objc",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
"raw-window-handle 0.4.3",
|
"raw-window-handle 0.4.3",
|
||||||
"rtrb",
|
"rtrb",
|
||||||
|
|
|
@ -140,6 +140,10 @@ features = [
|
||||||
"Win32_System_Performance",
|
"Win32_System_Performance",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[target.'cfg(macos)'.dependencies]
|
||||||
|
objc = "0.2.7"
|
||||||
|
core-foundation = "0.9.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
|
@ -6,9 +6,8 @@ mod background_thread;
|
||||||
|
|
||||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||||
mod linux;
|
mod linux;
|
||||||
// For now, also use the Linux event loop on macOS so it at least compiles
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod linux;
|
mod macos;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
|
@ -16,9 +15,8 @@ pub(crate) use self::background_thread::BackgroundThread;
|
||||||
|
|
||||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||||
pub(crate) use self::linux::LinuxEventLoop as OsEventLoop;
|
pub(crate) use self::linux::LinuxEventLoop as OsEventLoop;
|
||||||
// For now, also use the Linux event loop on macOS so it at least compiles
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub(crate) use self::linux::LinuxEventLoop as OsEventLoop;
|
pub(crate) use self::macos::MacOSEventLoop as OsEventLoop;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) use self::windows::WindowsEventLoop as OsEventLoop;
|
pub(crate) use self::windows::WindowsEventLoop as OsEventLoop;
|
||||||
|
|
||||||
|
|
139
src/event_loop/macos.rs
Normal file
139
src/event_loop/macos.rs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
//! An event loop implementation for Linux. APIs on Linux are generally thread safe, so the context
|
||||||
|
//! of a main thread does not exist there. Because of that, this mostly just serves as a way to
|
||||||
|
//! delegate expensive processing to another thread.
|
||||||
|
|
||||||
|
use core_foundation::base::kCFAllocatorDefault;
|
||||||
|
use core_foundation::runloop::{
|
||||||
|
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopRemoveSource,
|
||||||
|
CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
||||||
|
CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||||
|
};
|
||||||
|
use crossbeam::channel::{self, Receiver, Sender};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{BackgroundThread, EventLoop, MainThreadExecutor};
|
||||||
|
|
||||||
|
/// Wrapping the `CFRunLoopSourceRef` type is required to be able to annotate it as thread-safe.
|
||||||
|
struct LoopSourceWrapper(CFRunLoopSourceRef);
|
||||||
|
|
||||||
|
unsafe impl Send for LoopSourceWrapper {}
|
||||||
|
unsafe impl Sync for LoopSourceWrapper {}
|
||||||
|
|
||||||
|
/// See [`EventLoop`][super::EventLoop].
|
||||||
|
pub(crate) struct MacOSEventLoop<T, E> {
|
||||||
|
/// The thing that ends up executing these tasks. The tasks are usually executed from the worker
|
||||||
|
/// thread, but if the current thread is the main thread then the task cna also be executed
|
||||||
|
/// directly.
|
||||||
|
executor: Arc<E>,
|
||||||
|
|
||||||
|
/// A background thread for running tasks independently from the host's GUI thread. Useful for
|
||||||
|
/// longer, blocking tasks.
|
||||||
|
background_thread: BackgroundThread<T>,
|
||||||
|
|
||||||
|
/// The reference to the run-loop source so that it can be torn down when this struct is dropped.
|
||||||
|
loop_source: LoopSourceWrapper,
|
||||||
|
|
||||||
|
/// The sender for passing messages to the main thread.
|
||||||
|
main_thread_sender: Sender<T>,
|
||||||
|
|
||||||
|
/// Is it necessary to store the information that is sent to the callback function here?
|
||||||
|
_info: Arc<(Arc<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: Arc<E>) -> Self {
|
||||||
|
// What's a good size for the queue?
|
||||||
|
let (main_thread_sender, main_thread_receiver) = channel::bounded::<T>(32);
|
||||||
|
|
||||||
|
let info = Arc::new((executor.clone(), main_thread_receiver));
|
||||||
|
|
||||||
|
let loop_source;
|
||||||
|
unsafe {
|
||||||
|
let mut source_context = CFRunLoopSourceContext {
|
||||||
|
info: Arc::as_ptr(&info) 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,
|
||||||
|
&mut source_context as *mut _ as *mut CFRunLoopSourceContext,
|
||||||
|
);
|
||||||
|
CFRunLoopAddSource(CFRunLoopGetMain(), loop_source, kCFRunLoopCommonModes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
executor: executor.clone(),
|
||||||
|
background_thread: BackgroundThread::new_and_spawn(executor),
|
||||||
|
loop_source: LoopSourceWrapper(loop_source),
|
||||||
|
main_thread_sender,
|
||||||
|
_info: info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_gui(&self, task: T) -> bool {
|
||||||
|
if self.is_main_thread() {
|
||||||
|
self.executor.execute(task, true);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// Only signal the main thread callback to be called if the task was added to the queue.
|
||||||
|
if self.main_thread_sender.try_send(task).is_ok() {
|
||||||
|
unsafe {
|
||||||
|
CFRunLoopSourceSignal(self.loop_source.0);
|
||||||
|
CFRunLoopWakeUp(CFRunLoopGetMain());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let (executor, receiver) = &*(info as *mut (Arc<E>, Receiver<T>));
|
||||||
|
while let Ok(task) = receiver.try_recv() {
|
||||||
|
executor.execute(task, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue