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

Add callback interfaces and foreign traits to WASM! #216

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

jhugman
Copy link
Owner

@jhugman jhugman commented Feb 7, 2025

This PR generates Rust that accepts callback interfacevtables from Typescript. I have comment some generated code below as an illustration how the whole thing hangs together.

Things that must happen for Futures:

  • currently the CELL stores a JS vtable. This means that with this scheme we can't support standalone callback functions.

Things I don't like:

  • we have quite a lot of machinery to store a global variable (q.v. the thread_local! macro).
// This is called directly from Typescript at library initialization.
#[wasm_bindgen]
pub unsafe fn ubrn_uniffi_callbacks_fn_init_callback_vtable_callanswerer(
    vtable: v_table_callback_interface_call_answerer::VTableJs,
) {
    uniffi_callbacks_fn_init_callback_vtable_callanswerer(
        std::ptr::NonNull::<
            v_table_callback_interface_call_answerer::VTableRs,
        >::into_rust(vtable),
    );
}

mod v_table_callback_interface_call_answerer {
    use super::*;

    // VTableJs is "declared" in this block here. 
    // The `f::` prefix is "foreign", read: js.
    #[wasm_bindgen]
    extern "C" {
        pub type VTableJs;
        #[wasm_bindgen(method, js_name = answer)]
        pub fn answer(
            this_: &VTableJs,
            uniffi_handle: f::UInt64,
        ) -> f::UniffiResultForeignBytes;
        #[wasm_bindgen(method, js_name = uniffiFree)]
        pub fn uniffi_free(this_: &VTableJs, handle: f::UInt64);
    }
    // The Rust implementation of the VTable gets passed to Rust.
    #[repr(C)]
    pub(super) struct VTableRs {
        answer: callback_interface_call_answerer_method0::FnSig,
        uniffi_free: v_table_callback_interface_call_answerer__free::FnSig,
    }
    impl From<&VTableJs> for VTableRs {
        fn from(_v: &VTableJs) -> Self {
            Self {
                answer: callback_interface_call_answerer_method0::implementation,
                uniffi_free: v_table_callback_interface_call_answerer__free::implementation,
            }
        }
    }
    // The CELL is where we'll store the JS vtable for whenever it's needed.
    thread_local! {
        pub (super) static CELL : f::ForeignCell < VTableJs > = f::ForeignCell::new();
    }
    impl GeneratedIntoRust<VTableJs> for NonNull<VTableRs> {
        fn into_rust(vtable: VTableJs) -> Self {
            let vtable_rs = (&vtable).into();
            CELL.with(|cell| cell.set(vtable));
            let ptr = Box::into_raw(Box::new(vtable_rs));
            unsafe { NonNull::new_unchecked(ptr) }
        }
    }
}
// This module handles one method. In this case it's the "answer" method.
mod callback_interface_call_answerer_method0 {
    use super::*;
    pub(super) type FnSig = extern "C" fn(
        uniffi_handle: u64,
        rs_return_: &mut u::RustBuffer,
        rs_call_status_: &mut u::RustCallStatus,
    );
    // The implementation is where the transformation from Rust types to JS types happens.
    pub(super) extern "C" fn implementation(
        uniffi_handle: u64,
        rs_return_: &mut u::RustBuffer,
        rs_call_status_: &mut u::RustCallStatus,
    ) {
        let uniffi_result_ = v_table_callback_interface_call_answerer::CELL
            .with(|cell_| {
                cell_.with_value(|vtable_| vtable_.answer(uniffi_handle.into_js()))
            });
        uniffi_result_.copy_into_status(rs_call_status_);
        uniffi_result_.copy_into_return(rs_return_);
    }
}
// This module handles one method. In this case it's the special uniffiFree method.
mod v_table_callback_interface_call_answerer__free {
    use super::*;
    pub(super) type FnSig = extern "C" fn(handle: u64);
    pub(super) extern "C" fn implementation(handle: u64) {
        v_table_callback_interface_call_answerer::CELL
            .with(|cell_| {
                cell_.with_value(|vtable_| vtable_.uniffi_free(handle.into_js()))
            });
    }
}

@jhugman jhugman force-pushed the jhugman/wasm/callback-interfaces branch from 3eb5998 to 0f4ac5a Compare February 10, 2025 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant