Add CallbackRef that takes ref in argument instead of value (#3419)

* Add CallbackRef that takes ref in argument instead of value

* Fix up commented reform/filter_reform

* Add reform_ref functions to create CallbackRef instead

* Use macro_rules to avoid code duplication

* Add tests and fix wrong types

* Update doc comments

* clippy

* more clippy

---------

Co-authored-by: KirillSemyonkin <burnytc@gmail.com>
This commit is contained in:
Cecile Tonglet 2023-09-29 13:58:24 +02:00 committed by GitHub
parent 9507270d06
commit 194a1e6059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 231 additions and 75 deletions

View File

@ -9,6 +9,165 @@ use std::rc::Rc;
use crate::html::ImplicitClone;
macro_rules! generate_callback_impls {
($callback:ident, $in_ty:ty, $out_var:ident => $out_val:expr) => {
impl<IN, OUT, F: Fn($in_ty) -> 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(clippy::vtable_address_comparisons)]
impl<IN, OUT> PartialEq for $callback<IN, OUT> {
fn eq(&self, other: &$callback<IN, OUT>) -> bool {
let ($callback { cb }, $callback { cb: rhs_cb }) = (self, other);
Rc::ptr_eq(cb, rhs_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> {
/// This method calls the callback's function.
pub fn emit(&self, value: $in_ty) -> OUT {
(*self.cb)(value)
}
}
impl<IN> $callback<IN> {
/// Creates a "no-op" callback which can be used when it is not suitable to use an
/// `Option<$callback>`.
pub fn noop() -> Self {
Self::from(|_: $in_ty| ())
}
}
impl<IN> Default for $callback<IN> {
fn default() -> Self {
Self::noop()
}
}
impl<IN: 'static, OUT: 'static> $callback<IN, OUT> {
/// Creates a new [`Callback`] from another callback and a function.
///
/// That when emitted will call that function and will emit the original callback
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: T| {
#[allow(unused_mut)]
let mut $out_var = func(input);
this.emit($out_val)
};
func.into()
}
/// Creates a new [`CallbackRef`] from another callback and a function.
///
/// That when emitted will call that function and will emit the original callback
pub fn reform_ref<F, T>(&self, func: F) -> CallbackRef<T, OUT>
where
F: Fn(&T) -> $in_ty + 'static,
{
let this = self.clone();
let func = move |input: &T| {
#[allow(unused_mut)]
let mut $out_var = func(input);
this.emit($out_val)
};
func.into()
}
/// Creates a new [`CallbackRefMut`] from another callback and a function.
///
/// That when emitted will call that function and will emit the original callback
pub fn reform_ref_mut<F, T>(&self, func: F) -> CallbackRefMut<T, OUT>
where
F: Fn(&mut T) -> $in_ty + 'static,
{
let this = self.clone();
let func = move |input: &mut T| {
#[allow(unused_mut)]
let mut $out_var = func(input);
this.emit($out_val)
};
func.into()
}
/// Creates a new [`Callback`] from another callback and a function.
///
/// When emitted will call the function and, only if it returns `Some(value)`, will emit
/// `value` to the original callback.
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: T| {
func(input).map(
#[allow(unused_mut)]
|mut $out_var| this.emit($out_val),
)
};
func.into()
}
/// Creates a new [`CallbackRef`] from another callback and a function.
///
/// When emitted will call the function and, only if it returns `Some(value)`, will emit
/// `value` to the original callback.
pub fn filter_reform_ref<F, T>(&self, func: F) -> CallbackRef<T, Option<OUT>>
where
F: Fn(&T) -> Option<$in_ty> + 'static,
{
let this = self.clone();
let func = move |input: &T| {
func(input).map(
#[allow(unused_mut)]
|mut $out_var| this.emit($out_val),
)
};
func.into()
}
/// Creates a new [`CallbackRefMut`] from another callback and a function.
///
/// When emitted will call the function and, only if it returns `Some(value)`, will emit
/// `value` to the original callback.
pub fn filter_reform_ref_mut<F, T>(&self, func: F) -> CallbackRefMut<T, Option<OUT>>
where
F: Fn(&mut T) -> Option<$in_ty> + 'static,
{
let this = self.clone();
let func = move |input: &mut T| {
func(input).map(
#[allow(unused_mut)]
|mut $out_var| this.emit($out_val),
)
};
func.into()
}
}
impl<IN, OUT> ImplicitClone for $callback<IN, OUT> {}
};
}
/// Universal callback wrapper.
///
/// An `Rc` wrapper is used to make it cloneable.
@ -17,84 +176,27 @@ pub struct Callback<IN, OUT = ()> {
pub(crate) 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) }
}
generate_callback_impls!(Callback, IN, output => output);
/// Universal callback wrapper with reference in argument.
///
/// An `Rc` wrapper is used to make it cloneable.
pub struct CallbackRef<IN, OUT = ()> {
/// A callback which can be called multiple times
pub(crate) cb: Rc<dyn Fn(&IN) -> OUT>,
}
impl<IN, OUT> Clone for Callback<IN, OUT> {
fn clone(&self) -> Self {
Self {
cb: self.cb.clone(),
}
}
generate_callback_impls!(CallbackRef, &IN, output => #[allow(clippy::needless_borrow)] &output);
/// Universal callback wrapper with mutable reference in argument.
///
/// An `Rc` wrapper is used to make it cloneable.
pub struct CallbackRefMut<IN, OUT = ()> {
/// A callback which can be called multiple times
pub(crate) cb: Rc<dyn Fn(&mut IN) -> OUT>,
}
#[allow(clippy::vtable_address_comparisons)]
impl<IN, OUT> PartialEq for Callback<IN, OUT> {
fn eq(&self, other: &Callback<IN, OUT>) -> bool {
let (Callback { cb }, Callback { cb: rhs_cb }) = (self, other);
Rc::ptr_eq(cb, rhs_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> {
/// This method calls the callback's function.
pub fn emit(&self, value: IN) -> OUT {
(*self.cb)(value)
}
}
impl<IN> Callback<IN> {
/// Creates a "no-op" callback which can be used when it is not suitable to use an
/// `Option<Callback>`.
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> {
/// Creates a new callback from another callback and a function
/// That when emitted will call that function and will emit the original callback
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)
}
/// Creates a new callback from another callback and a function.
/// When emitted will call the function and, only if it returns `Some(value)`, will emit
/// `value` to the original callback.
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)
}
}
impl<IN, OUT> ImplicitClone for Callback<IN, OUT> {}
generate_callback_impls!(CallbackRefMut, &mut IN, output => &mut output);
#[cfg(test)]
mod test {
@ -144,4 +246,58 @@ mod test {
vec![true, false]
);
}
#[test]
fn test_ref() {
let callback: CallbackRef<usize, usize> = CallbackRef::from(|x: &usize| *x);
assert_eq!(callback.emit(&42), 42);
}
#[test]
fn test_ref_mut() {
let callback: CallbackRefMut<usize, ()> = CallbackRefMut::from(|x: &mut usize| *x = 42);
let mut value: usize = 0;
callback.emit(&mut value);
assert_eq!(value, 42);
}
#[test]
fn test_reform_ref() {
let callback: Callback<usize, usize> = Callback::from(|x: usize| x + 1);
let reformed: CallbackRef<usize, usize> = callback.reform_ref(|x: &usize| *x + 2);
assert_eq!(reformed.emit(&42), 45);
}
#[test]
fn test_reform_ref_mut() {
let callback: CallbackRefMut<usize, ()> = CallbackRefMut::from(|x: &mut usize| *x += 1);
let reformed: CallbackRefMut<usize, ()> = callback.reform_ref_mut(|x: &mut usize| {
*x += 2;
x
});
let mut value: usize = 42;
reformed.emit(&mut value);
assert_eq!(value, 45);
}
#[test]
fn test_filter_reform_ref() {
let callback: Callback<usize, usize> = Callback::from(|x: usize| x + 1);
let reformed: CallbackRef<usize, Option<usize>> =
callback.filter_reform_ref(|x: &usize| Some(*x + 2));
assert_eq!(reformed.emit(&42), Some(45));
}
#[test]
fn test_filter_reform_ref_mut() {
let callback: CallbackRefMut<usize, ()> = CallbackRefMut::from(|x: &mut usize| *x += 1);
let reformed: CallbackRefMut<usize, Option<()>> =
callback.filter_reform_ref_mut(|x: &mut usize| {
*x += 2;
Some(x)
});
let mut value: usize = 42;
reformed.emit(&mut value).expect("is some");
assert_eq!(value, 45);
}
}

View File

@ -330,7 +330,7 @@ pub mod prelude {
#[cfg(feature = "csr")]
pub use crate::app_handle::AppHandle;
pub use crate::callback::Callback;
pub use crate::callback::{Callback, CallbackRef, CallbackRefMut};
pub use crate::context::{ContextHandle, ContextProvider};
pub use crate::events::*;
pub use crate::functional::*;