rust-galmon-web/src/utils/callbacks.rs

141 lines
2.9 KiB
Rust

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<T> {
link: CallbackLink<T>,
exec_link: CallbackExecLink<T>,
callback: yew::Callback<T>,
}
struct InnerCallbacks<T> {
cbhead: CallbackHead<T>,
}
impl<T> Drop for InnerCallbacks<T> {
fn drop(&mut self) {
unsafe {
while let Some(nodeptr) = self.cbhead.pop_front() {
// free entry
drop(Box::from_raw(nodeptr as *mut CallbackEntry<T>));
}
}
}
}
pub struct Callbacks<T> {
inner: Rc<InnerCallbacks<T>>,
}
impl<T: 'static> Callbacks<T> {
pub fn new() -> Self {
Self {
inner: Rc::new(InnerCallbacks {
cbhead: CallbackHead::new(),
}),
}
}
pub fn register(&self, callback: yew::Callback<T>) -> CallbackRegistration<T> {
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<T> 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<T> = &*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<T> = &*nodeptr;
entry.callback.emit(data.clone());
if entry.link.is_unlinked() {
// free entry
drop(Box::from_raw(nodeptr as *mut CallbackEntry<T>));
}
}
}
}
}
impl<T> fmt::Debug for Callbacks<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Callbacks")
}
}
pub struct CallbackRegistration<T> {
inner: Weak<InnerCallbacks<T>>,
entry: *const CallbackEntry<T>,
}
impl<T> Default for CallbackRegistration<T> {
fn default() -> Self {
Self {
inner: Weak::default(),
entry: std::ptr::null(),
}
}
}
impl<T> Drop for CallbackRegistration<T> {
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<T>));
}
}
}
}
}
impl<T> fmt::Debug for CallbackRegistration<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("CallbackRegistration")
}
}