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

async run() #2793

Open
frehberg opened this issue May 30, 2023 · 16 comments
Open

async run() #2793

frehberg opened this issue May 30, 2023 · 16 comments
Labels
a:language-rust Rust API and codegen (mO,mS) enhancement New feature or request

Comments

@frehberg
Copy link

instead of blocking call into slint event-loop

HelloWorld::new().unwrap().run().unwrap();

there should be an option to invoke an async run function, so the underlying event loop (embassy or tokio) might have a chance to process other pending operations asynchronously.

@tronical
Copy link
Member

I think as a generic solution that works with winit and Qt backends, that's going to be very hard.

I'd prefer having public API for the winit backend and then folks could use this together with winit-async for example - if that's somehow feasible.

For Tokio, we used a thread in cargo-ui: The Tokio runtime is run in a thread and a channel is used to exchange messages between Slint and async functions: https://github.com/slint-ui/cargo-ui/blob/master/src/main.rs

For embassy and MCU: I think for the time being that may be possible of you run your own superloop instead of using the run() function Slint generates. Then you control the loop entirely.

@ogoffart
Copy link
Member

I believe winit need to take control of the event loop, so there can't be an underlying event loop like Tokio.

What we should support however is the ability to run any future in the Slint's event loop #747

@frehberg
Copy link
Author

Hmmm, I don't see how the winit event loop might share CPU time with another async-event loop such as embassy.

Right now I am evaluating best solution to manage a HMI on stm32f4 and handling async events from USB and buttons.

Instead of using kind of RTOS/C++ to handle different preempt tasks, I would prefer using Rust and its async-feature to handle the IO events from USB, hardware-buttons, HMI-touch events etc. in a single event loop.

Here is a comparison
https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown

AFAICS, an embassy "task" starting a slint Window event loop would block and would starve all other embassy tasks.

https://docs.rs/embassy-executor/latest/embassy_executor/attr.task.html

So far I dont see how embassy and slint could be combined on stm32f4 :(

@tronical
Copy link
Member

I think the way you'd combine Slint into am embassy task is to write the loop like here in the task and use a timer like Timer::after(duration).await; and combine that with a future that waits on a channel for incoming user input events.

I wonder if there's a way to create an example of this that runs on desktop (for testing purposes).

@ogoffart
Copy link
Member

ogoffart commented Jun 5, 2023

I wonder how much this also relates to #2763

@frehberg
Copy link
Author

Yes, #2763 is related (just its use case is C++). My use case is using Rust "embassy" xor "tokio" event-engine based on the Rust async feature.

@ogoffart
Copy link
Member

It would be usefull to know the exact use-case and what we are trying to solve.
If you are using embassy, you are probably using your own slint::Platform, so you are not using winit and you can have the full control of the event loop.

If you never call slint::run_event_loop, you can just control on when you draw by calling the slint API to handle event and draw from something managed by embassy or tokio.

I am not super faimilar with these framework, but i guess the loop from https://docs.rs/slint/latest/slint/docs/mcu/index.html#the-event-loop can be converted to an async loop. Something like that

async fn my_event_loop() {
    //...  initialize the window and stuff
    loop {


       select!{
            event = the_touch_driver.query_event() => { 
                  slint::platform::update_timers_and_animations();
                  window.dispatch_event(event);
             }
             _ = timer_wait(slint::platform::duration_until_next_timer_update()) => {
                 slint::platform::update_timers_and_animations();
             }
       }

       // Draw the scene if something needs to be drawn.
       window.draw_if_needed(|renderer| {
                todo!("render the scene")
       });
}

Or something similar.

We may want to add helpers or examples to use Slint with known runtime.

@ogoffart ogoffart added the a:language-rust Rust API and codegen (mO,mS) label Jul 13, 2023
@ierturk
Copy link

ierturk commented Dec 1, 2023

This is the async loop that runs on the STM32F429-DK for your reference. There is no select in no_std. The lilos asnc rtos was employed here as task scheduler. Is there any possible improvement out there. Thank you.

https://github.com/ierturk/rust-on-stm32/blob/b8af1ad27e8adf37a9666fd8c9d315ac08ff4c32/src/drivers/bsp.rs#L496

@tronical
Copy link
Member

tronical commented Dec 1, 2023

Perhaps you can use futures::select! ?

@ierturk
Copy link

ierturk commented Dec 1, 2023

I'll chek it.

@ierturk
Copy link

ierturk commented Dec 1, 2023

@tronical
Copy link
Member

tronical commented Dec 1, 2023

Indeed, but select_biased seems enabled for no_std.

ierturk pushed a commit to ierturk/rust-on-stm32 that referenced this issue Dec 3, 2023
@ierturk
Copy link

ierturk commented Dec 3, 2023

The proposed solution was implemented by using the macro select_biased!, however in the current implementation the method slint::platform::duration_until_next_timer_update always returns None. I don't know what, but it didn't work.

My solution is based on the lilos sugestion. 5.2. Giving other tasks an opportunity to run if ready
https://github.com/cbiffle/lilos/blob/main/doc/intro.adoc#52-giving-other-tasks-an-opportunity-to-run-if-ready

It works by combining following methods
lilos::exec::run_tasks_with_preemption
https://github.com/ierturk/rust-on-stm32/blob/79e84297026e4c71a8bcc54eb0e3aeee25e97b93/src/main.rs#L42

and
lilos::exec::yield_cpu().await
https://github.com/ierturk/rust-on-stm32/blob/79e84297026e4c71a8bcc54eb0e3aeee25e97b93/src/drivers/bsp.rs#L567

@ierturk
Copy link

ierturk commented Dec 4, 2023

Indeed, but select_biased seems enabled for no_std.

There is an open PR here
rust-lang/futures-rs#1912

@ogoffart ogoffart added the enhancement New feature or request label Jan 16, 2024
@doxxx
Copy link

doxxx commented Feb 13, 2024

I'll add my 2 cents here:

I am primarily a C# developer with an interest in Rust. I would consider myself an intermediate Rust programmer, but novice Tokio user. I recently discovered Slint and since I was also looking into gRPC at the same time, I thought I'd try to combine them in a small Slint GUI desktop (Windows) app that makes a gRPC call to a server to fetch some data that is displayed in the UI. Shouldn't be too hard, right?

Alas, my poor brain imploded when I contemplated how to integrate Slint's synchronous Rust API with a Tokio async-based gRPC client like tonic. Needing to implement a message channel between the UI code and the async gRPC code seems a little too yo-dawg for my liking. Is this the only way?

@doxxx
Copy link

doxxx commented Feb 14, 2024

Well, I got it working without a message channel. It's not terrible..

I'm using the Hello World tutorial from tonic as the basis for the gRPC part.

use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
use tokio::task::spawn_blocking;

pub mod hello_world {
    tonic::include_proto!("helloworld");
}

slint::include_modules!();

fn main() {
    // Create a multi-threaded Tokio Runtime
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();

    // The Tokio Runtime main task just launches a blocking thread for the UI and waits for it to finish.
    // This could do other things as well, like starting background initialization tasks, etc.
    runtime.block_on(async {
        spawn_blocking(run_ui)
            .await
            .unwrap()
            .unwrap()
    });
}

fn run_ui() -> Result<(), slint::PlatformError> {
    // Initialize UI
    let ui = AppWindow::new()?;

    // Get a UI weakref for use in the callback
    let ui_handle = ui.as_weak();

    // Handle the button click
    ui.on_send_hello(move || {
        // Get the name that the user entered in the text field
        let ui = ui_handle.unwrap();
        let name = ui.get_name().to_string();

        // Another UI weakref for updating it with the response from the gRPC server
        let ui_handle = ui.as_weak();

        // Spawn a Tokio task to talk to the gRPC server
        [runtime](tokio::runtime::Handle::current()).spawn(async move {
            // send the request and await the result
            let result = send_hello(&name).await.unwrap();
            // update the UI with the server response on the UI thread
            ui_handle.upgrade_in_event_loop(|ui| {
                ui.set_hello_response(result.into());
            }).unwrap();
        });
    });

    ui.run()
}

async fn send_hello(name: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut client = GreeterClient::connect("http://[::1]:50051").await?;
    let request = tonic::Request::new(HelloRequest { name: name.into() });
    let response = client.say_hello(request).await?;
    let message = response.into_inner().message;
    println!("RESPONSE: {:?}", message);
    Ok(message)
}
import { Button, VerticalBox, HorizontalBox, LineEdit } from "std-widgets.slint";

export component AppWindow inherits Window {
    in-out property <string> name: "";
    in-out property <string> hello-response: "";

    callback send-hello();

    VerticalBox {
        HorizontalBox {
            Text {
                vertical-alignment: TextVerticalAlignment.center;
                text: "Name:";
            }
            le-name := LineEdit {
                text: root.name;
                edited(text) => {
                    root.name = text;
                }
                accepted => {
                    root.send-hello();
                }
            }
        }
        Button {
            text: "Send Hello";
            clicked => {
                root.send-hello();
            }
        }
        Text {
            text: "Server says \"\{root.hello-response}\"";
        }
    }

    init => {
        le-name.focus();
    }
}

EDIT: Simplified to not require passing Tokio runtime handle through UI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:language-rust Rust API and codegen (mO,mS) enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants