use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(not(test))]
use objc::declare::ClassDecl;
#[cfg(not(test))]
use objc::runtime::{Class, Object, Sel};
#[cfg(not(test))]
#[allow(unused_imports)]
use objc::{msg_send, sel, sel_impl};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CallbackId(pub usize);
impl CallbackId {
pub fn new(id: usize) -> Self {
Self(id)
}
pub fn as_usize(&self) -> usize {
self.0
}
}
impl From<usize> for CallbackId {
fn from(id: usize) -> Self {
Self(id)
}
}
impl From<CallbackId> for usize {
fn from(id: CallbackId) -> usize {
id.0
}
}
type Callback = Box<dyn Fn() + Send>;
type ValueCallback = Box<dyn Fn(String) + Send>;
type BoolCallback = Box<dyn Fn(bool) + Send>;
type SizeCallback = Box<dyn Fn(f64, f64) + Send>;
type CallbackRegistry = HashMap<usize, Callback>;
type ValueCallbackRegistry = HashMap<usize, ValueCallback>;
type BoolCallbackRegistry = HashMap<usize, BoolCallback>;
type SizeCallbackRegistry = HashMap<usize, SizeCallback>;
static CALLBACKS: Mutex<Option<CallbackRegistry>> = Mutex::new(None);
static VALUE_CALLBACKS: Mutex<Option<ValueCallbackRegistry>> = Mutex::new(None);
static BOOL_CALLBACKS: Mutex<Option<BoolCallbackRegistry>> = Mutex::new(None);
static SIZE_CALLBACKS: Mutex<Option<SizeCallbackRegistry>> = Mutex::new(None);
static TAG_MAP: Mutex<Option<HashMap<isize, usize>>> = Mutex::new(None);
static NEXT_ID: AtomicUsize = AtomicUsize::new(1_000_000);
pub fn next_id() -> usize {
NEXT_ID.fetch_add(1, Ordering::SeqCst)
}
pub fn init() {
let mut cbs = CALLBACKS.lock().unwrap();
if cbs.is_none() {
*cbs = Some(HashMap::new());
}
let mut vcbs = VALUE_CALLBACKS.lock().unwrap();
if vcbs.is_none() {
*vcbs = Some(HashMap::new());
}
let mut bcbs = BOOL_CALLBACKS.lock().unwrap();
if bcbs.is_none() {
*bcbs = Some(HashMap::new());
}
let mut scbs = SIZE_CALLBACKS.lock().unwrap();
if scbs.is_none() {
*scbs = Some(HashMap::new());
}
let mut tags = TAG_MAP.lock().unwrap();
if tags.is_none() {
*tags = Some(HashMap::new());
}
}
pub fn register(callback_id: usize, f: impl Fn() + Send + 'static) {
init();
let mut cbs = CALLBACKS.lock().unwrap();
if let Some(map) = cbs.as_mut() {
map.insert(callback_id, Box::new(f));
}
}
pub fn register_with_auto_id(f: impl Fn() + Send + 'static) -> usize {
let id = next_id();
register(id, f);
id
}
pub fn register_value(callback_id: usize, f: impl Fn(String) + Send + 'static) {
init();
let mut vcbs = VALUE_CALLBACKS.lock().unwrap();
if let Some(map) = vcbs.as_mut() {
map.insert(callback_id, Box::new(f));
}
}
pub fn register_value_with_auto_id(f: impl Fn(String) + Send + 'static) -> usize {
let id = next_id();
register_value(id, f);
id
}
pub fn register_bool(callback_id: usize, f: impl Fn(bool) + Send + 'static) {
init();
let mut bcbs = BOOL_CALLBACKS.lock().unwrap();
if let Some(map) = bcbs.as_mut() {
map.insert(callback_id, Box::new(f));
}
}
pub fn register_bool_with_auto_id(f: impl Fn(bool) + Send + 'static) -> usize {
let id = next_id();
register_bool(id, f);
id
}
pub fn register_size(callback_id: usize, f: impl Fn(f64, f64) + Send + 'static) {
init();
let mut scbs = SIZE_CALLBACKS.lock().unwrap();
if let Some(map) = scbs.as_mut() {
map.insert(callback_id, Box::new(f));
}
}
pub fn register_size_with_auto_id(f: impl Fn(f64, f64) + Send + 'static) -> usize {
let id = next_id();
register_size(id, f);
id
}
pub fn map_tag(tag: isize, callback_id: usize) {
init();
let mut tags = TAG_MAP.lock().unwrap();
if let Some(map) = tags.as_mut() {
map.insert(tag, callback_id);
}
}
pub fn dispatch_by_tag(tag: isize) {
let callback_id = {
let tags = TAG_MAP.lock().unwrap();
tags.as_ref().and_then(|m| m.get(&tag).copied())
};
if let Some(id) = callback_id {
dispatch(id);
}
}
pub fn dispatch(callback_id: usize) {
let cbs = CALLBACKS.lock().unwrap();
if let Some(map) = cbs.as_ref()
&& let Some(f) = map.get(&callback_id)
{
f();
}
}
pub fn dispatch_value(callback_id: usize, value: String) {
let vcbs = VALUE_CALLBACKS.lock().unwrap();
if let Some(map) = vcbs.as_ref()
&& let Some(f) = map.get(&callback_id)
{
f(value);
}
}
pub fn dispatch_bool(callback_id: usize, value: bool) {
let bcbs = BOOL_CALLBACKS.lock().unwrap();
if let Some(map) = bcbs.as_ref()
&& let Some(f) = map.get(&callback_id)
{
f(value);
}
}
pub fn dispatch_size(callback_id: usize, width: f64, height: f64) {
let scbs = SIZE_CALLBACKS.lock().unwrap();
if let Some(map) = scbs.as_ref()
&& let Some(f) = map.get(&callback_id)
{
f(width, height);
}
}
#[cfg(not(test))]
pub fn register_action_class() -> &'static Class {
use std::sync::Once;
static REGISTER: Once = Once::new();
static mut CLASS: Option<&'static Class> = None;
REGISTER.call_once(|| {
let superclass = Class::get("NSObject").unwrap();
let mut decl = ClassDecl::new("CocoanutActionHandler", superclass).unwrap();
extern "C" fn handle_action(_this: &Object, _cmd: Sel, sender: *mut Object) {
unsafe {
let tag: isize = objc::msg_send![sender, tag];
dispatch_by_tag(tag);
}
}
unsafe {
decl.add_method(
objc::sel!(handleAction:),
handle_action as extern "C" fn(&Object, Sel, *mut Object),
);
CLASS = Some(decl.register());
}
});
unsafe { CLASS.unwrap() }
}
#[cfg(not(test))]
pub fn action_handler() -> *mut Object {
use std::sync::Once;
static INIT: Once = Once::new();
static mut HANDLER: *mut Object = std::ptr::null_mut();
INIT.call_once(|| {
let cls = register_action_class();
unsafe {
let obj: *mut Object = objc::msg_send![cls, new];
HANDLER = obj;
}
});
unsafe { HANDLER }
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_register_and_dispatch() {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();
register(100, move || {
c.fetch_add(1, Ordering::SeqCst);
});
dispatch(100);
assert_eq!(counter.load(Ordering::SeqCst), 1);
dispatch(100);
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
#[test]
fn test_tag_mapping() {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();
register(200, move || {
c.fetch_add(1, Ordering::SeqCst);
});
map_tag(42, 200);
dispatch_by_tag(42);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_dispatch_nonexistent() {
dispatch(99999); dispatch_by_tag(99999); }
#[test]
fn callback_id_conversions() {
let c = CallbackId::new(5);
assert_eq!(c.as_usize(), 5);
let u: usize = c.into();
assert_eq!(u, 5);
let c2: CallbackId = 7.into();
assert_eq!(c2.as_usize(), 7);
}
#[test]
fn register_with_auto_id_increments() {
let id1 = register_with_auto_id(|| {});
let id2 = register_with_auto_id(|| {});
assert_ne!(id1, id2);
dispatch(id1);
dispatch(id2);
}
#[test]
fn register_value_dispatch_value() {
let seen = Arc::new(Mutex::new(String::new()));
let s = seen.clone();
register_value(3001, move |t| {
*s.lock().unwrap() = t;
});
dispatch_value(3001, "payload".into());
assert_eq!(*seen.lock().unwrap(), "payload");
}
#[test]
fn register_bool_dispatch_bool() {
let seen = Arc::new(Mutex::new(None::<bool>));
let s = seen.clone();
register_bool(3002, move |b| {
*s.lock().unwrap() = Some(b);
});
dispatch_bool(3002, true);
assert_eq!(*seen.lock().unwrap(), Some(true));
}
#[test]
fn register_size_dispatch_size() {
let seen = Arc::new(Mutex::new((0.0_f64, 0.0_f64)));
let s = seen.clone();
register_size(3003, move |w, h| {
*s.lock().unwrap() = (w, h);
});
dispatch_size(3003, 640.0, 480.0);
assert_eq!(*seen.lock().unwrap(), (640.0, 480.0));
}
#[test]
fn test_register_value_with_auto_id() {
let id = register_value_with_auto_id(|_| {});
dispatch_value(id, "x".into());
}
}