use std::fmt; use std::rc::{Rc, Weak}; use std::mem::ManuallyDrop; local_dl_list! { mod cblist { link CallbackLink; head CallbackHead; member link of CallbackEntry; } } local_dl_list! { mod cbexeclist { link CallbackExecLink; head CallbackExecHead; member exec_link of CallbackEntry; } } struct CallbackEntry { link: CallbackLink, exec_link: CallbackExecLink, callback: yew::Callback, } struct InnerCallbacks { cbhead: CallbackHead, } impl Drop for InnerCallbacks { fn drop(&mut self) { unsafe { while let Some(nodeptr) = self.cbhead.pop_front() { // free entry drop(Box::from_raw(nodeptr as *mut CallbackEntry)); } } } } pub struct Callbacks { inner: Rc>, } impl Callbacks { pub fn new() -> Self { Self { inner: Rc::new(InnerCallbacks { cbhead: CallbackHead::new(), }), } } pub fn register(&self, callback: yew::Callback) -> CallbackRegistration { let entry = ManuallyDrop::new(Box::new(CallbackEntry { link: CallbackLink::new(), exec_link: CallbackExecLink::new(), callback, })); unsafe { self.inner.cbhead.append(&entry); } CallbackRegistration { inner: Rc::downgrade(&self.inner), entry: &entry as &CallbackEntry as *const _, } } pub fn emit(&self, data: T) where T: Clone, { unsafe { // copy list first so we don't run into problems when callbacks modify it let cbexec = CallbackExecHead::new(); if let Some(mut nodeptr) = self.inner.cbhead.front() { loop { let entry: &CallbackEntry = &*nodeptr; entry.exec_link.unlink(); cbexec.append(&entry); match self.inner.cbhead.next(entry) { Some(nextptr) => nodeptr = nextptr, None => break, } } } while let Some(nodeptr) = cbexec.pop_front() { let entry: &CallbackEntry = &*nodeptr; entry.callback.emit(data.clone()); if entry.link.is_unlinked() { // free entry drop(Box::from_raw(nodeptr as *mut CallbackEntry)); } } } } } impl fmt::Debug for Callbacks { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Callbacks") } } pub struct CallbackRegistration { inner: Weak>, entry: *const CallbackEntry, } impl Default for CallbackRegistration { fn default() -> Self { Self { inner: Weak::default(), entry: std::ptr::null(), } } } impl Drop for CallbackRegistration { fn drop(&mut self) { if let Some(_inner) = self.inner.upgrade() { // if inner isn't alive anymore the callback has already been freed. unsafe { let entry = &*self.entry; entry.link.unlink(); if entry.exec_link.is_unlinked() { // not in exec list, free now drop(Box::from_raw(self.entry as *mut CallbackEntry)); } } } } } impl fmt::Debug for CallbackRegistration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("CallbackRegistration") } }