How to use static lifetime variable in combination with egui & WASM

Hi,

im new to Rust and hope to find some help here.

I try to write a small app which runs in the browser via egui, web_sys, wasm_bindgen.

The goal is to use the (unstable) Bluetooth API to connect to a bluetooth device, fetch & parse data and create some plots of this data.

use eframe::egui;

use js_sys::Array;
use std::str;
use wasm_bindgen::JsValue;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use wasm_bindgen::closure::Closure;
use web_sys::console;
use web_sys::{Bluetooth, Navigator};

pub struct TemplateApp {
    gatt_service_characteristics: Option<web_sys::BluetoothRemoteGattCharacteristic>,
}

impl Default for TemplateApp {
    fn default() -> Self {
        Self {
            gatt_service_characteristics: None::<web_sys::BluetoothRemoteGattCharacteristic>,
        }
    }
}
fn js_array(values: &[&str]) -> JsValue {
    return JsValue::from( 
        values
            .into_iter()
            .map(|x| JsValue::from_str(x))
            .collect::<Array>(),
    );
}
impl eframe::App for TemplateApp {
    /// Called each time the UI needs repainting, which may be many times per second.
    /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        let Self { gatt_service_characteristics  } = self;

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("example App");


            if ui.button("Connect BLE").clicked() {
                
                let window = web_sys::window().expect("Missing Window");
                let navigator = window.navigator();

                let blue = navigator.bluetooth().unwrap();

                let mut opt = web_sys::RequestDeviceOptions::new();
                opt.accept_all_devices(true);
                opt.optional_services(&js_array(&["0000ffe0-0000-1000-8000-00805f9b34fb"]));

                let res_promise = blue.request_device(&opt);
                let res_future = wasm_bindgen_futures::JsFuture::from(res_promise);

                spawn_local(async  move{
                    match res_future.await {
                        Ok(message) => {
                            let device = web_sys::BluetoothDevice::from(message);
                            let gatt_server = device.gatt().unwrap();
                            let connect_promise = gatt_server.connect();

                            let connect_future =
                                wasm_bindgen_futures::JsFuture::from(connect_promise);

                            let gatt_server = web_sys::BluetoothRemoteGattServer::from(
                                connect_future.await.unwrap(),
                            );

                            let gatt_service_promise = gatt_server.get_primary_service_with_str(
                                "0000ffe0-0000-1000-8000-00805f9b34fb",
                            );
                            let gatt_service_future =
                                wasm_bindgen_futures::JsFuture::from(gatt_service_promise);
                            let remote_gatt_service = web_sys::BluetoothRemoteGattService::from(
                                gatt_service_future.await.unwrap(),
                            );

                            console::log_2(
                                &"remote_gatt_service : %s".into(),
                                &remote_gatt_service.uuid().into(),
                            );

                            let gatt_service_characteristics_promise = remote_gatt_service
                                .get_characteristic_with_str(
                                    "0000ffe1-0000-1000-8000-00805f9b34fb",
                                );
                            let gatt_service_characteristics_future =
                                wasm_bindgen_futures::JsFuture::from(
                                    gatt_service_characteristics_promise,
                                );
              
                                
                            *gatt_service_characteristics = Some(
                                web_sys::BluetoothRemoteGattCharacteristic::from(
                                    gatt_service_characteristics_future.await.unwrap(),
                                ));
                            console::log_2(
                                &"remote_gatt_characteristics : %s".into(),
                                &gatt_service_characteristics.unwrap().uuid().into(),
                            );                    
                        }
                        Err(e) => {}
                    }
                });

            }

        });
    }
}

Actually the compiler complains about the lifetime:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src\app.rs:35:20
   |
34 |     fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
   |               --------- this data with an anonymous lifetime `'_`...
35 |         let Self { gatt_service_characteristics  } = self;
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...is used here...
...
55 |                 spawn_local(async  move{
   |                 ----------- ...and is required to live as long as `'static` here
   |
note: `'static` lifetime requirement introduced by this bound
  --> cargo\registry\src\github.com-1ecc6299db9ec823\wasm-bindgen-futures-0.4.30\src\lib.rs:79:30
   |
79 |     F: Future<Output = ()> + 'static,
   |                              ^^^^^^^
  • How can i set the correct lifetime for gatt_service_characteristics ? And is this even the right approach to use it elsewhere after connection succeeded?

Any other suggestion on dealing with promises/futures (as this feels a bit clumsy) or about a better option for "spawn_local" are highly welcome. :slight_smile:

Thanks

Is there a reason the gatt_service_characteristics field is within the TemplateApp? You don’t seem to be using it at the moment. Assuming that in the real code you use it in the rendering process, then you will probably want to use some kind of shared state. Here’s one possible solution:

pub struct TemplateApp {
    gatt_service_characteristics: Option<web_sys::BluetoothRemoteGattCharacteristic>,
    loading_gatt_service_characteristics: Option<Task<Option<web_sys::BluetoothRemoteGattCharacteristic>>>,
}

impl TemplateApp {
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        // Check if we spawned a task before that has now completed
        let updated_gatt_service_characteristics = self.loading_gatt_service_characteristics
            .as_ref()
            .and_then(|task| task.take_output())
            // propagate panics and ignore `None`s from the inner task;
            // you should choose what to do here yourself
            .and_then(|res| res.unwrap());
        if let Some(updated) = updated_gatt_service_characteristics {
            self.gatt_service_characteristics = Some(updated);
        }

        // ...
            // Start the async loading of the characteristic; if this field
            // is already `Some` at this point you may want to throw an error
            // to prevent multiple concurrent loading tasks from running
            self.loading_gatt_service_characteristic = Some(Task::spawn(async move {
                // perform the async loading of the value, returning an
                // Option<BluetoothRemoteGattCharacteristic>, or maybe
                // a Result if you want
            }));
    }
}

/// A handle to a spawned task that resolves to a value of type `T`.
pub struct Task<T>(Rc<Cell<Option<thread::Result<T>>>>);

impl<T> Task<T> {
    pub fn spawn<F: 'static + Future<Output = T>>(future: F) -> Self {
        let sender = Rc::new(Cell::new(None));
        let receiver = sender.clone();
        spawn_local(async move {
            let future = panic::AssertUnwindSafe(future).catch_unwind();
            sender.set(Some(future.await));
        });
        Self(receiver)
    }
    pub fn take_output(&self) -> Option<thread::Result<T>> {
        self.0.take()
    }
}
1 Like

Hello Sabrina,

thanks for your help.

Is there a reason the gatt_service_characteristics field is within the TemplateApp ? You don’t seem to be using it at the moment.

Yes you are right, my intention was to us gatt_service_characteristics later on other parts in rendering. I will try to test your solution, i might need some time as im still on the steep side of the learning path.

error[E0599]: no method named `catch_unwind` found for struct `AssertUnwindSafe<F>` in the current scope
   --> src\app.rs:248:63
    |
248 |             let future = std::panic::AssertUnwindSafe(future).catch_unwind();
    |                                                               ^^^^^^^^^^^^ method not found in `AssertUnwindSafe<F>` 

Hmm, it seems there is not catch_unwind(). Im on rustc 1.60.0 (7737e0b5c 2022-04-04) but not sure if this plays a role here.

It looks like that's using the extension method FutureExt::catch_unwind(), which can be imported with use futures::FutureExt; or use futures::prelude::*;.

2 Likes

Thanks to all for your help