Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use_callback should take a closure that accepts an argument #2371

Open
jkelleyrtp opened this issue Apr 26, 2024 · 1 comment
Open

use_callback should take a closure that accepts an argument #2371

jkelleyrtp opened this issue Apr 26, 2024 · 1 comment
Labels
hooks Changes to built-in hook package

Comments

@jkelleyrtp
Copy link
Member

Feature Request

Currently there's no way to pass in an argument to the closure that use_callback wraps. This should be easy enough to add. I think the only reason we didn't add it is to make the type signature of UseCallback a bit more compact, but that's not so important.

@jkelleyrtp jkelleyrtp added the hooks Changes to built-in hook package label Apr 26, 2024
@spookyvision
Copy link
Contributor

spookyvision commented May 2, 2024

since I needed this I've hacked something together (mostly copy-paste of dioxus code, plus one argument) that appears to work for me. Let me know if this looks plausible and if so I'll create a PR!

pub fn use_callback1<T, O>(f: impl FnMut(T) -> O + 'static) -> UseCallback1<O, T> {
    // Create a copyvalue with no contents
    // This copyvalue is generic over F so that it can be sized properly
    let mut inner = use_hook(|| CopyValue::new(None));

    // Every time this hook is called replace the inner callback with the new callback
    inner.set(Some(f));

    // And then wrap that callback in a boxed callback so we're blind to the size of the actual callback
    use_hook(|| {
        let cur_scope = current_scope_id().unwrap();
        let rt = Runtime::current().unwrap();

        UseCallback1 {
            inner: CopyValue::new(Box::new(move |arg1| {
                // run this callback in the context of the scope it was created in.
                let run_callback = || inner.with_mut(|f: &mut Option<_>| f.as_mut().unwrap()(arg1));
                rt.on_scope(cur_scope, run_callback)
            })),
        }
    })
}

/// This callback is not generic over a return type so you can hold a bunch of callbacks at once
///
/// If you need a callback that returns a value, you can simply wrap the closure you pass in that sets a value in its scope
#[derive(PartialEq)]
pub struct UseCallback1<O: 'static + ?Sized, T: 'static> {
    inner: CopyValue<Box<dyn FnMut(T) -> O>>,
}

impl<O: 'static + ?Sized, T> Clone for UseCallback1<O, T> {
    fn clone(&self) -> Self {
        Self { inner: self.inner }
    }
}
impl<O: 'static, T: 'static> Copy for UseCallback1<O, T> {}

impl<O, T> UseCallback1<O, T> {
    /// Call the callback
    pub fn call(&self, arg1: T) -> O {
        (self.inner.write_unchecked())(arg1)
    }
}

// This makes UseCallback callable like a normal function
impl<O, T> std::ops::Deref for UseCallback1<O, T> {
    type Target = dyn Fn(T) -> O;

    fn deref(&self) -> &Self::Target {
        // https://github.com/dtolnay/case-studies/tree/master/callable-types

        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
        let uninit_callable = MaybeUninit::<Self>::uninit();
        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
        let uninit_closure = move |arg1| Self::call(unsafe { &*uninit_callable.as_ptr() }, arg1);

        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
        let size_of_closure = std::mem::size_of_val(&uninit_closure);
        assert_eq!(size_of_closure, std::mem::size_of::<Self>());

        // Then cast the lifetime of the closure to the lifetime of &self.
        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
            b
        }
        let reference_to_closure = cast_lifetime(
            {
                // The real closure that we will never use.
                &uninit_closure
            },
            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
            unsafe { std::mem::transmute(self) },
        );

        // Cast the closure to a trait object.
        reference_to_closure as &_
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hooks Changes to built-in hook package
Projects
None yet
Development

No branches or pull requests

2 participants