-
Notifications
You must be signed in to change notification settings - Fork 599
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
Spawn future in the slint event loop (Was: Async callbacks in Rust) #747
Comments
Hi, It really depends on what the callback function are going to be doing. Starting a thread with tokio::spawn should work. Regarding the fact that the model can't be Send, this is because all the UI state needs to be consistent in the rendering thread, so you must do all your updates from that thread. But the easier way to do it is to call We are also looking for ways to make this API nicer. We considered adding some data to the sixtyfps::Weak so that more things could be then send to another thread and then accessed from the eventloop again. But we are still thinking about what our options are. Yet another way to spawn an async function would be not to use thread at all, and run the future on the UI event loop. It should be possible to implement something similar with the public API only. Something like (untested) use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::Wake;
fn run_async(fut: Pin<Box<dyn Future<Output = ()>>>) {
struct FutureRunner {
fut: Mutex<Option<Pin<Box<dyn Future<Output = ()>>>>>,
}
/// Safety: the future is only going to be run in the UI thread
unsafe impl Send for FutureRunner {}
unsafe impl Sync for FutureRunner {}
impl Wake for FutureRunner {
fn wake(self: Arc<Self>) {
sixtyfps::invoke_from_event_loop(move || {
let waker = self.clone().into();
let mut cx = std::task::Context::from_waker(&waker);
let mut fut_opt = self.fut.lock().unwrap();
if let Some(fut) = &mut *fut_opt {
match fut.as_mut().poll(&mut cx) {
std::task::Poll::Ready(_) => *fut_opt = None,
std::task::Poll::Pending => {}
}
}
})
}
}
Arc::new(FutureRunner { fut: Mutex::new(Some(fut)) }).wake()
} |
Hi @ogoffart thanks for your fast and extensive answer! I got it working with a combination of |
@secana can you reopen this issue to keep discussion going of how to improve the API? |
I think this would be the nicest to use option. It would be great to not have to write boilerplate for message passing or data synchronization to integrate async Rust with the SixtyFPS GUI. I am a bit unclear how the API @ogoffart proposed above with main_window.on_some_callback(run_async(async {
let data = some_expensive_operation().await();
main_window.set_some_property(data);
})); If Rust manages to implement async overloading in the future it would be great if that could be used for the autogenerated |
@secana, thank you for the question. Did you manage to succeed? |
@DaMilyutin I got it to work with the following code (simplified) use sixtyfps::Model;
sixtyfps::include_modules!();
#[tokio::main]
async fn main() {
// ... code ...
let model = Rc::new(sixtyfps::VecModel::<Item>::from(items.clone()));
let ui = Ui::new();
ui.set_model(sixtyfps::ModelHandle::new(model));
let handle_weak = ui.as_weak();
ui.on_scan({
move || {
// Spawn thread to be able to call async functions.
tokio::spawn(async move {
// Call async function
let item = my_async_func().await;
// Update UI model state
update_model(
handle_weak.clone(),
item,
);
}
});
}
});
ui.run();
}
fn update_model(handle: sixtyfps::Weak<Ui>, fi: Item) {
handle.upgrade_in_event_loop(move |handle| {
let fm = handle.get_model();
fm.set_row_data((fi.id - 1) as usize, fi)
});
} |
First: Thank you for creating sixtyfps, it's the best GUI framework I've worked with for Rust!
I started my first project with sixtyfps in Rust and hit a problem. I need to use async code in the
on_...
callback functions.The callback expects an synchronous lambda, which I can workaround by starting a a thread with
tokio::spawn
, but I'm still not able to work with the model data from the UI, as the model data isRc<VecModel<...>>
andRc
cannot be send between threads. I'm sure there is a way around that with some wrappers, I guess there is an easier option.What is the recommended way of using async code with sixtyfps?
The text was updated successfully, but these errors were encountered: