//! Native callback and legacy asynchronous-delegate adapters. use std::any::Any; use std::fmt; use std::sync::{Arc, Condvar, Mutex}; use libremetaverse_types::compat::{DictionaryEntry, Object}; use crate::{DictionaryEventAction, Error}; /// Completion object returned by mapped delegate `BeginInvoke` methods. #[derive(Clone)] pub struct AsyncInvocation { state: Arc, async_state: Object, } struct AsyncInvocationState { result: Mutex>>, completed: Condvar, } impl fmt::Debug for AsyncInvocation { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("AsyncInvocation") .field("async_state", &self.async_state) .field("completed", &self.is_completed()) .finish() } } impl AsyncInvocation { pub(crate) fn pending(async_state: Object) -> Self { Self { state: Arc::new(AsyncInvocationState { result: Mutex::new(None), completed: Condvar::new(), }), async_state, } } pub(crate) fn complete(&self, result: Result<(), Error>) { *self .state .result .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result); self.state.completed.notify_all(); } #[must_use] pub fn is_completed(&self) -> bool { self.state .result .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .is_some() } #[must_use] pub fn async_state(&self) -> &Object { &self.async_state } pub(crate) fn wait(&self) -> Result<(), Error> { let result = self .state .result .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let result = self .state .completed .wait_while(result, |result| result.is_none()) .unwrap_or_else(std::sync::PoisonError::into_inner); result.expect("completion predicate guarantees a result") } } type DictionaryHandler = Arc; /// Opaque target accepted by the metadata-compatible delegate constructor. pub struct DictionaryCallbackTarget(pub DictionaryHandler); impl fmt::Debug for DictionaryCallbackTarget { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("DictionaryCallbackTarget(..)") } } #[derive(Clone)] pub struct DictionaryChangeCallback(DictionaryHandler); impl DictionaryChangeCallback { pub fn from_callback( callback: impl Fn(DictionaryEventAction, DictionaryEntry) + Send + Sync + 'static, ) -> Self { Self(Arc::new(callback)) } pub fn new(object: Object, method: isize) -> Result { if method != 0 { return Err(Error::Argument); } object .downcast_arc::() .map(|target| Self(Arc::clone(&target.0))) .ok_or(Error::Argument) } pub fn begin_invoke( &self, action: DictionaryEventAction, entry: DictionaryEntry, callback: Box, object: Object, ) -> Result, Error> { let invocation = AsyncInvocation::pending(object); let worker_invocation = invocation.clone(); let handler = Arc::clone(&self.0); std::thread::Builder::new() .name("dictionary-callback".to_owned()) .spawn(move || { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { handler(action, entry); })) .map_err(|_| Error::InvalidOperation); worker_invocation.complete(result); let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { callback(&worker_invocation); })); }) .map_err(|_| Error::InvalidOperation)?; Ok(Box::new(invocation)) } pub fn end_invoke(&self, result: Box) -> Result<(), Error> { result .downcast::() .map_err(|_| Error::Argument)? .wait() } pub fn invoke( &self, action: DictionaryEventAction, entry: DictionaryEntry, ) -> Result<(), Error> { (self.0)(action, entry); Ok(()) } } #[cfg(test)] mod tests { use super::*; use std::sync::atomic::{AtomicUsize, Ordering}; #[test] fn asynchronous_delegate_can_be_observed_and_ended() { let calls = Arc::new(AtomicUsize::new(0)); let observed = Arc::clone(&calls); let callback = DictionaryChangeCallback::from_callback(move |_, _| { observed.fetch_add(1, Ordering::Relaxed); }); let result = callback .begin_invoke( DictionaryEventAction::Add, DictionaryEntry(Object::Integer(1), Object::Integer(2)), Box::new(|_| {}), Object::Undefined, ) .unwrap(); callback.end_invoke(result).unwrap(); assert_eq!(calls.load(Ordering::Relaxed), 1); } }