365 lines
12 KiB
Rust
365 lines
12 KiB
Rust
use std::{
|
|
collections::{hash_map::Entry, HashMap},
|
|
ffi::{c_void, CStr},
|
|
hash::{self, Hash},
|
|
ptr::{self, NonNull},
|
|
sync::{Mutex, OnceLock},
|
|
};
|
|
|
|
// Helper function for adding the entry `(key, (observer, action))` to `map`.
|
|
fn register<K: Eq + Hash>(
|
|
map: &mut HashMap<K, Vec<(Option<Sendable>, Action)>>,
|
|
key: K,
|
|
observer: Option<Sendable>,
|
|
action: Action,
|
|
) {
|
|
match map.entry(key) {
|
|
Entry::Occupied(mut o) => {
|
|
o.get_mut().push((observer, action));
|
|
}
|
|
Entry::Vacant(v) => {
|
|
v.insert(vec![(observer, action)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Lightweight message from object to another. When a notification is sent,
|
|
/// registered [`Action`]s are called.
|
|
///
|
|
/// Use [`ffi::WMAddNotificationObserver`] or [`NotificationCenter::register`]
|
|
/// to request notifications.
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// `Notification` encapsulates two data pointers. The Rust implementation
|
|
/// explicitly supports notifications across threads. To uphold Rust's safety
|
|
/// rules, `Notification`s must only be created with pointers that can be sent
|
|
/// across threads. The [`Sendable`] struct is provided to guarantee this.
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// This was originally a reference-counted structure, but it is so lightweight
|
|
/// (consisting of three pointers) that the Rust version is `Copy`. This
|
|
/// simplifies things --- each Rust recipient of a `Notification` owns it, and
|
|
/// there is no need to coordinate how it is cleaned up.
|
|
///
|
|
/// In unported C code, a notificaton's `name` may be compared against a string
|
|
/// constant using pointer equality rather than string equality. This has
|
|
/// negative implications for sending notifications across the Rust/C
|
|
/// boundary. For the time being, notifications are generated and received by
|
|
/// code written in C, but care should be taken that notification names are
|
|
/// checked properly when porting notification registration in the future. (We
|
|
/// will ideally move to a different data type for identifying notifications,
|
|
/// too.)
|
|
#[derive(Clone, Copy)]
|
|
pub struct Notification {
|
|
name: &'static CStr,
|
|
/// The object that generated the notification. This may be `None` for
|
|
/// notifications that are about global state.
|
|
source: Option<Sendable>,
|
|
/// Optional side-channel data provided to notification listeners.
|
|
client_data: Option<Sendable>,
|
|
}
|
|
|
|
/// Callback that notifies `observer` (which may be null) of `notification` (which won't be).
|
|
pub type Action = unsafe extern "C" fn(observer: *mut c_void, notification: *const Notification);
|
|
|
|
/// Wraps a type-erased pointer (which it does not own) and marks it as `Send`.
|
|
///
|
|
/// The `Send`-ability of the wrapped pointer must be guaranteed by code that
|
|
/// instantiates a `Sendable`.
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub struct Sendable {
|
|
ptr: NonNull<c_void>,
|
|
}
|
|
|
|
impl Sendable {
|
|
/// Creates a `Sendable` wrapping `ptr`.
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// `ptr` must be safe to send across threads.
|
|
pub unsafe fn new(ptr: NonNull<c_void>) -> Self {
|
|
Sendable { ptr }
|
|
}
|
|
}
|
|
|
|
impl hash::Hash for Sendable {
|
|
fn hash<H: hash::Hasher>(&self, h: &mut H) {
|
|
h.write_usize(self.ptr.as_ptr().addr());
|
|
}
|
|
}
|
|
|
|
// Guaranteed by `Sendable::new`.
|
|
unsafe impl Send for Sendable {}
|
|
|
|
#[derive(Default)]
|
|
pub struct NotificationCenter {
|
|
/// Notification subscriptions that match on name and source.
|
|
exact: HashMap<(&'static CStr, Sendable), Vec<(Option<Sendable>, Action)>>,
|
|
/// Notification subscriptions that match on name.
|
|
by_name: HashMap<&'static CStr, Vec<(Option<Sendable>, Action)>>,
|
|
/// Notification subscriptions that match on source.
|
|
by_source: HashMap<Sendable, Vec<(Option<Sendable>, Action)>>,
|
|
/// Notification subscriptions that match all notifications.
|
|
universal: Vec<(Option<Sendable>, Action)>,
|
|
}
|
|
|
|
// It is safe to send NotificationCenter across threads as long as the contract
|
|
// on Sendable is respected.
|
|
unsafe impl Send for NotificationCenter {}
|
|
|
|
impl NotificationCenter {
|
|
/// Creates a new `NotificationCenter`.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Provides access to the default, process-wide notification center. The
|
|
/// FFI C API uses this notification center. This is protected behind a
|
|
/// mutex that is held while `f` is run, so panicking inside of `f` should
|
|
/// be avoided.
|
|
pub fn with_global_default<R>(f: impl FnOnce(&mut Self) -> R) -> R {
|
|
static INSTANCE: OnceLock<Mutex<NotificationCenter>> = OnceLock::new();
|
|
f(&mut INSTANCE
|
|
.get_or_init(|| Mutex::new(NotificationCenter::new()))
|
|
.try_lock()
|
|
.unwrap())
|
|
}
|
|
|
|
/// Registers `action` to be invoked and invoked on `observer` when
|
|
/// notifications named `name` are fired from `source`.
|
|
pub fn register_exact(
|
|
&mut self,
|
|
name: &'static CStr,
|
|
source: Sendable,
|
|
observer: Option<Sendable>,
|
|
action: Action,
|
|
) {
|
|
register(&mut self.exact, (name, source), observer, action);
|
|
}
|
|
|
|
/// Registers `action` to be invoked on `observer` when notifications are
|
|
/// fired by `source` (regardless of the notification name).
|
|
pub fn register_by_source(
|
|
&mut self,
|
|
source: Sendable,
|
|
observer: Option<Sendable>,
|
|
action: Action,
|
|
) {
|
|
register(&mut self.by_source, source, observer, action);
|
|
}
|
|
|
|
/// Registers `action` to be invoked on `observer` for all notifications
|
|
/// named `name`.
|
|
pub fn register_by_name(
|
|
&mut self,
|
|
name: &'static CStr,
|
|
observer: Option<Sendable>,
|
|
action: Action,
|
|
) {
|
|
register(&mut self.by_name, name, observer, action);
|
|
}
|
|
|
|
/// Registers `action` to be invoked on `observer` for all notifications,
|
|
/// regardless of the notification's name or source.
|
|
pub fn register_universal(&mut self, observer: Option<Sendable>, action: Action) {
|
|
self.universal.push((observer, action));
|
|
}
|
|
|
|
/// Dispatches `notification` with registered actions.
|
|
pub fn dispatch(&mut self, notification: Notification) {
|
|
if let Some(observers) = self.by_name.get_mut(notification.name) {
|
|
for (observer, action) in observers {
|
|
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
|
|
unsafe {
|
|
(action)(observer, ¬ification);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(source) = notification.source {
|
|
if let Some(observers) = self.exact.get_mut(&(notification.name, source)) {
|
|
for (observer, action) in observers {
|
|
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
|
|
unsafe {
|
|
(action)(observer, ¬ification);
|
|
}
|
|
}
|
|
}
|
|
if let Some(observers) = self.by_source.get_mut(&source) {
|
|
for (observer, action) in observers {
|
|
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
|
|
unsafe {
|
|
(action)(observer, ¬ification);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (observer, action) in &mut self.universal {
|
|
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
|
|
unsafe {
|
|
(action)(observer, ¬ification);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Removes all notification subscriptions that would notify `observer` if they fired.
|
|
pub fn remove_observer(&mut self, observer: Sendable) {
|
|
self.exact.retain(|_, values| {
|
|
values.retain(|(o, _)| *o != Some(observer));
|
|
!values.is_empty()
|
|
});
|
|
self.by_name.retain(|_, values| {
|
|
values.retain(|(o, _)| *o != Some(observer));
|
|
!values.is_empty()
|
|
});
|
|
self.by_source.retain(|_, values| {
|
|
values.retain(|(o, _)| *o != Some(observer));
|
|
!values.is_empty()
|
|
});
|
|
self.universal.retain(|(o, _)| *o != Some(observer));
|
|
}
|
|
|
|
/// Clears all registered notification listeners and resets `self` to its
|
|
/// default state.
|
|
pub fn clear(&mut self) {
|
|
*self = Self::default();
|
|
}
|
|
}
|
|
|
|
pub mod ffi {
|
|
use super::{Action, Notification, NotificationCenter, Sendable};
|
|
|
|
use std::{
|
|
ffi::{c_char, c_void, CStr},
|
|
ptr::{self, NonNull},
|
|
};
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetNotificationClientData(
|
|
notification: *mut Notification,
|
|
) -> *mut c_void {
|
|
if notification.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
(*notification)
|
|
.client_data
|
|
.map(|x| x.ptr.as_ptr())
|
|
.unwrap_or(ptr::null_mut())
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetNotificationObject(
|
|
notification: *mut Notification,
|
|
) -> *mut c_void {
|
|
if notification.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
(*notification)
|
|
.source
|
|
.map(|x| x.ptr.as_ptr())
|
|
.unwrap_or(ptr::null_mut())
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetNotificationName(
|
|
notification: *mut Notification,
|
|
) -> *const c_char {
|
|
if notification.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe { (*notification).name.as_ptr() }
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMAddNotificationObserver(
|
|
action: Option<Action>,
|
|
observer: *mut c_void,
|
|
name: *const c_char,
|
|
object: *mut c_void,
|
|
) {
|
|
let Some(action) = action else {
|
|
return;
|
|
};
|
|
let observer = NonNull::new(observer).map(|x| unsafe { Sendable::new(x) });
|
|
let source = NonNull::new(object);
|
|
NotificationCenter::with_global_default(|c| {
|
|
if name.is_null() {
|
|
match source {
|
|
Some(source) => {
|
|
c.register_by_source(unsafe { Sendable::new(source) }, observer, action);
|
|
}
|
|
None => c.register_universal(observer, action),
|
|
}
|
|
} else {
|
|
let name = unsafe { CStr::from_ptr(name) };
|
|
match source {
|
|
Some(source) => {
|
|
c.register_exact(name, unsafe { Sendable::new(source) }, observer, action);
|
|
}
|
|
None => c.register_by_name(name, observer, action),
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMRemoveNotificationObserver(observer: *mut c_void) {
|
|
let Some(observer) = NonNull::new(observer) else {
|
|
return;
|
|
};
|
|
|
|
NotificationCenter::with_global_default(|c| {
|
|
c.remove_observer(unsafe { Sendable::new(observer) })
|
|
});
|
|
}
|
|
|
|
/// Posts a notification from `object` with the given `name` and `client_data`.
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// `name` must be a non-null string constant or some other pointer with a
|
|
/// static lifetime.
|
|
///
|
|
/// `object` and `client_data` must be safe to send across threads (per the
|
|
/// contract of [`Sendable`]).
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// This originally took a heap-allocated `*mut Notification`, but now the
|
|
/// constructed `Notification` parameters are passed directly.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMPostNotificationName(
|
|
name: *const c_char,
|
|
object: *mut c_void,
|
|
client_data: *mut c_void,
|
|
) {
|
|
if name.is_null() {
|
|
return;
|
|
}
|
|
let name = unsafe { CStr::from_ptr(name) };
|
|
let source = NonNull::new(object).map(|x| unsafe { Sendable::new(x) });
|
|
let client_data = NonNull::new(client_data).map(|x| unsafe { Sendable::new(x) });
|
|
NotificationCenter::with_global_default(|c| {
|
|
c.dispatch(Notification {
|
|
name,
|
|
source,
|
|
client_data,
|
|
})
|
|
});
|
|
}
|
|
|
|
/// Resets all notifications for the global notification center. Used by
|
|
/// `WApplication` teardown code. This is a private, WINGs-internal API.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn W_ClearNotificationCenter() {
|
|
NotificationCenter::with_global_default(|c| c.clear());
|
|
}
|
|
}
|