use std::fmt;
use std::rc::Rc;
pub struct Callback<IN, OUT = ()> {
cb: Rc<dyn Fn(IN) -> OUT>,
}
impl<IN, OUT, F: Fn(IN) -> OUT + 'static> From<F> for Callback<IN, OUT> {
fn from(func: F) -> Self {
Callback { cb: Rc::new(func) }
}
}
impl<IN, OUT> Clone for Callback<IN, OUT> {
fn clone(&self) -> Self {
Self {
cb: self.cb.clone(),
}
}
}
#[allow(ambiguous_wide_pointer_comparisons)]
impl<IN, OUT> PartialEq for Callback<IN, OUT> {
fn eq(&self, other: &Callback<IN, OUT>) -> bool {
Rc::ptr_eq(&self.cb, &other.cb)
}
}
impl<IN, OUT> fmt::Debug for Callback<IN, OUT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Callback<_>")
}
}
impl<IN, OUT> Callback<IN, OUT> {
pub fn emit(&self, value: IN) -> OUT {
(*self.cb)(value)
}
}
impl<IN> Callback<IN> {
pub fn noop() -> Self {
Self::from(|_| ())
}
}
impl<IN> Default for Callback<IN> {
fn default() -> Self {
Self::noop()
}
}
impl<IN: 'static, OUT: 'static> Callback<IN, OUT> {
pub fn reform<F, T>(&self, func: F) -> Callback<T, OUT>
where
F: Fn(T) -> IN + 'static,
{
let this = self.clone();
let func = move |input| {
let output = func(input);
this.emit(output)
};
Callback::from(func)
}
pub fn filter_reform<F, T>(&self, func: F) -> Callback<T, Option<OUT>>
where
F: Fn(T) -> Option<IN> + 'static,
{
let this = self.clone();
let func = move |input| func(input).map(|output| this.emit(output));
Callback::from(func)
}
}
#[cfg(test)]
mod test {
use std::sync::Mutex;
use super::*;
fn emit<T, I, R: 'static + Clone, F, OUT>(values: I, f: F) -> Vec<R>
where
I: IntoIterator<Item = T>,
F: FnOnce(Callback<R, ()>) -> Callback<T, OUT>,
{
let result = Rc::new(Mutex::new(Vec::new()));
let cb_result = result.clone();
let cb = f(Callback::<R, ()>::from(move |v| {
cb_result.lock().unwrap().push(v);
}));
for value in values {
cb.emit(value);
}
let x = result.lock().unwrap().clone();
x
}
#[test]
fn test_callback() {
assert_eq!(*emit([true, false], |cb| cb), vec![true, false]);
}
#[test]
fn test_reform() {
assert_eq!(
*emit([true, false], |cb| cb.reform(|v: bool| !v)),
vec![false, true]
);
}
#[test]
fn test_filter_reform() {
assert_eq!(
*emit([1, 2, 3], |cb| cb.filter_reform(|v| match v {
1 => Some(true),
2 => Some(false),
_ => None,
})),
vec![true, false]
);
}
}