Search code examples
rustegui

What is the best way to update app fields from another thread in egui in rust


I'm trying to run a function in another thread that updates app fields every second and window accordingly. But I don't know how to do that. What is the best way to do that? I tried to use Arc.

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release

use std::{
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

use eframe::egui::{self};

fn main() {
    let options = eframe::NativeOptions::default();
    let app = MyApp::default();
    let arc = Arc::new(Mutex::new(app));
    thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        arc.lock().unwrap().field += 1;
    });
    eframe::run_native(
        "Test",
        options,
        Box::new(move |_cc| Box::new(arc.lock().unwrap())),
    );
}

struct MyApp {
    field: i128,
}

impl Default for MyApp {
    fn default() -> Self {
        Self { field: 0 }
    }
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| ui.label(format!("{}", self.field)));
    }
}

But I got this error:

error[E0277]: the trait bound `MutexGuard<'_, MyApp>: App` is not satisfied
  --> src/main.rs:22:29
   |
22 |         Box::new(move |_cc| Box::new(arc.lock().unwrap())),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `App` is not implemented for `MutexGuard<'_, MyApp>`
   |
   = note: required for the cast to the object type `dyn App`

For more information about this error, try `rustc --explain E0277`.

I think that I do it the wrong way.


Solution

  • I'm new to egui, but I think you're not able to wrap MyApp in an Arc<Mutex<T>> because the closure in Box::new(move |_cc| Box::new(arc.lock().unwrap())), expects your App in a Box<T> but when you lock the Mutex, it will give you a MutexGuard. Even if this worked, since run_native takes your locked mutex, no one else will be able to unlock it so no thread can modify that data.

    To fix this, you could either update the state in update or wrap your data (not app) in an Arc<Mutex<T>>. Below is one way to fix it. You actually have to move your mouse or click or hit a key to update the view.

    #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
    
    use std::{
        sync::{Arc, Mutex},
        thread,
        time::Duration,
    };
    
    use eframe::egui;
    
    fn main() {
        let options = eframe::NativeOptions::default();
        let app = MyApp::default();
        let field = app.field.clone();
        thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                *field.lock().unwrap() += 1;
            }
        });
        eframe::run_native(
            "Test",
            options,
            Box::new(move |_cc| Box::new(app)),
        );
    }
     
    struct MyApp {
        field: Arc<Mutex<i128>>,
    }
     
    impl Default for MyApp {
        fn default() -> Self {
            Self { field: Arc::new(Mutex::new(0)) }
        }
    }
     
    impl eframe::App for MyApp {
        fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
            egui::CentralPanel::default().show(ctx, |ui| ui.label(format!("{}", self.field.lock().unwrap())));
        }
    }