141 lines
2.9 KiB
Rust
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")
|
|
}
|
|
}
|