Skip to content

Commit

Permalink
Add CallbackRef that takes ref in argument instead of value (#3419)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
cecton and kirillsemyonkin authored Sep 29, 2023
1 parent 9507270 commit 194a1e6
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 72 deletions.
298 changes: 227 additions & 71 deletions packages/yew/src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,92 +9,194 @@ use std::rc::Rc;

use crate::html::ImplicitClone;

/// Universal callback wrapper.
///
/// An `Rc` wrapper is used to make it cloneable.
pub struct Callback<IN, OUT = ()> {
/// A callback which can be called multiple times
pub(crate) cb: Rc<dyn Fn(IN) -> OUT>,
}
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, 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(),
}
}
}

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)
}
}
}
}

#[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> 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, 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(|_: $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> {}
};
}

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(|_| ())
}
/// Universal callback wrapper.
///
/// An `Rc` wrapper is used to make it cloneable.
pub struct Callback<IN, OUT = ()> {
/// A callback which can be called multiple times
pub(crate) cb: Rc<dyn Fn(IN) -> OUT>,
}

impl<IN> Default for Callback<IN> {
fn default() -> Self {
Self::noop()
}
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: '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)
}
generate_callback_impls!(CallbackRef, &IN, output => #[allow(clippy::needless_borrow)] &output);

/// 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)
}
/// 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>,
}

impl<IN, OUT> ImplicitClone for Callback<IN, OUT> {}
generate_callback_impls!(CallbackRefMut, &mut IN, output => &mut output);

#[cfg(test)]
mod test {
Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion packages/yew/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down

0 comments on commit 194a1e6

Please sign in to comment.