egui interaction with background thread - rust

My goal is to have a background thread counting up every second, which can be started and stopped by buttons on the UI. The gui should display the current time. I thought that the communication between the gui and the background thread could be done via channels for the commands, and via a mutex for the number being counted upwards. In this example, I have omitted the communication via channels, and just focus on the mutex.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
fullscreen: false,
default_theme: eframe::Theme::Light,
..Default::default()
};
eframe::run_native(
"My egui App",
options,
Box::new(|_cc| Box::new(Timer::default())),
);
}
struct Timer {
time: u32,
counter: Arc<Mutex<i32>>,
}
impl Default for Timer {
fn default() -> Self {
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
//spawn looping thread
let _ = thread::spawn(move || {
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
std::thread::sleep_ms(100);
}
});
Self {
time: 0,
counter: counter,
}
}
}
impl eframe::App for Timer {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
let mut num = self.counter.lock().unwrap();
ui.heading(format!("My egui Application {}", num));
});
}
}
This compiles and runs, but it is super laggy. Could you help me to understand what I can do better?

In your sleeping loop you hold the lock for the whole duration you're sleeping.
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
std::thread::sleep_ms(100);
} // counter_clone only gets released here.
You either want to add a manual call to drop after you increase the number:
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
drop(num); // counter_clone now gets released here before we wait.
std::thread::sleep_ms(100);
}
Or just lock it in place:
loop {
*counter_clone().lock.unwrap() += 1; // counter_clone gets unlocked at the end of this statement.
std::thread::sleep_ms(100);
}

Related

Polling a context inside another poll

I have a polling function that will forever poll and always do pending. Inside the polling function poll_event_loop I want to control timings when the context should be polled again and the function to be called again in x seconds depending on some conditions. I could do this using another thread which calls the waker.wake_by_ref function. But this feels like a cheat. How could I do this without other threads.
poll_fn(|cx| self.poll_event_loop(cx)).await
// function will never be Ready, always pending, polling sould be fast so not just sleeping x seconds inside
fn poll_event_loop(&mut self, cx: &mut Context) -> Poll<anyhow::Result<()>> {
while some_codeandfunc() { /*....*/ }
// guarantee another poll_fn in 1 sec
if condition {
context_callback(cx, 1000);
}
// guarantee another poll_fn in 2 sec
if condition {
context_callback(cx, 2000);
}
Poll::Pending
}
fn context_callback(context: &mut Context, millisec: u64) {
let mut future = Box::pin(tokio::time::sleep(Durationtk::from_millis(millisec)));
//let cb = future.as_mut().poll(context);
future.poll_unpin(context);
}
// ugly way to auto poll the function every x seconds
fn spawn_qeueu_thread(waker: &Waker, rx: &Receiver<String>) -> Option<JoinHandle<()>> {
debug!("doing spawning thread");
//self.thread_spawned = true;
let waker = waker.clone();
let rx2 = rx.clone();
let spawn = tokio::spawn(async move {
loop {
tokio::time::sleep(Durationtk::from_millis(WAKEUPINTERVAL)).await;
debug!("doing other thread wakebyref");
waker.wake_by_ref();
let try_result = rx2.try_recv();
match try_result {
Err(_) => {}
Ok(_msg) => break,
}
}
debug!("ending spawned thread");
});
return Some(spawn);
//self.threadhandle = Some(spawn);
}
After some experimenting, I found a working solution that lets you call the polling function on multiple desired times, working example:
use chrono::Utc;
use futures::FutureExt;
use futures::future::poll_fn;
use tokio::time::Sleep;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use tokio::time::Duration as Durationtk;
pub struct ControllerModule {
vec: Vec<Pin<Box<Sleep>>>,
i: i64,
}
impl ControllerModule {
fn new() -> Self {
let vec = vec![];
let i = 0;
Self { vec, i }
}
async fn start(&mut self) {
poll_fn(|cx| self.poll_event_loop(cx)).await;
print!("worked");
}
fn poll_event_loop(&mut self, context: &mut Context) -> Poll<anyhow::Result<()>> {
self.i += 1;
if self.i % 3 == 0 {
let mut sleep = Box::pin(tokio::time::sleep(Durationtk::from_millis(5000)));
sleep.poll_unpin(context);
self.vec.push(sleep);
} else if self.i % 3 == 1 {
let mut sleep = Box::pin(tokio::time::sleep(Durationtk::from_millis(4000)));
sleep.poll_unpin(context);
self.vec.push(sleep);
} else {
context.waker().wake_by_ref();
}
self.vec.retain(|e| !e.is_elapsed());
Poll::Pending
}
}
#[tokio::main]
async fn main() {
let mut i = ControllerModule::new();
i.start().await
}

How to let struct hold a thread and destroy thread as soon as it go out of scope

struct ThreadHolder{
state: ???
thread: ???
}
impl ThreadHolder {
fn launch(&mut self) {
self.thread = ???
// in thread change self.state
}
}
#[test]
fn test() {
let mut th = ThreadHolder{...};
th.launch();
// thread will be destroy as soon as th go out of scope
}
I think there is something to deal with lifetime, but I don't know how to write it.
What you want is so simple that you don't even need it to be mutable in any way, and then it becomes trivial to share it across threads, unless you want to reset it. You said you need to leave a thread, for one reason or another, therefore I'll assume that you don't care about this.
You instead can poll it every tick (most games run in ticks so I don't think there will be any issue implementing that).
I will provide example that uses sleep, so it's not most accurate thing, it is painfully obvious on the last subsecond duration, but I am not trying to do your work for you anyway, there's enough resources on internet that can help you deal with it.
Here it goes:
use std::{
sync::Arc,
thread::{self, Result},
time::{Duration, Instant},
};
struct Timer {
end: Instant,
}
impl Timer {
fn new(duration: Duration) -> Self {
// this code is valid for now, but might break in the future
// future so distant, that you really don't need to care unless
// you let your players draw for eternity
let end = Instant::now().checked_add(duration).unwrap();
Timer { end }
}
fn left(&self) -> Duration {
self.end.saturating_duration_since(Instant::now())
}
// more usable than above with fractional value being accounted for
fn secs_left(&self) -> u64 {
let span = self.left();
span.as_secs() + if span.subsec_millis() > 0 { 1 } else { 0 }
}
}
fn main() -> Result<()> {
let timer = Timer::new(Duration::from_secs(10));
let timer_main = Arc::new(timer);
let timer = timer_main.clone();
let t = thread::spawn(move || loop {
let seconds_left = timer.secs_left();
println!("[Worker] Seconds left: {}", seconds_left);
if seconds_left == 0 {
break;
}
thread::sleep(Duration::from_secs(1));
});
loop {
let seconds_left = timer_main.secs_left();
println!("[Main] Seconds left: {}", seconds_left);
if seconds_left == 5 {
println!("[Main] 5 seconds left, waiting for worker thread to finish work.");
break;
}
thread::sleep(Duration::from_secs(1));
}
t.join()?;
println!("[Main] worker thread finished work, shutting down!");
Ok(())
}
By the way, this kind of implementation wouldn't be any different in any other language, so please don't blame Rust for it. It's not the easiest language, but it provides more than enough tools to build anything you want from scratch as long as you put effort into it.
Goodluck :)
I think I got it work
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn, JoinHandle};
use std::time::Duration;
struct Timer {
pub(crate) time: Arc<Mutex<u32>>,
jh_ticker: Option<JoinHandle<()>>,
}
impl Timer {
fn new<T>(i: T, duration: Duration) -> Self
where
T: Iterator<Item = u32> + Send + 'static,
{
let time = Arc::new(Mutex::new(0));
let arc_time = time.clone();
let jh_ticker = Some(spawn(move || {
for item in i {
let mut mg = arc_time.lock().unwrap();
*mg = item;
drop(mg); // needed, otherwise this thread will always hold lock
sleep(duration);
}
}));
Timer { time, jh_ticker }
}
}
impl Drop for Timer {
fn drop(&mut self) {
self.jh_ticker.take().unwrap().join();
}
}
#[test]
fn test_timer() {
let t = Timer::new(0..=10, Duration::from_secs(1));
let a = t.time.clone();
for _ in 0..100 {
let b = *a.lock().unwrap();
println!("{}", b);
sleep(Duration::from_millis(100));
}
}

Implement a monitoring thread without lock?

I have a struct that sends messages to a channel as well as updating some of its own fields. How do I implement a monitoring thread that looks (read only) at its internal fields periodically?
I can write it using a Arc<Mutex<T>> wrapper, but I feel it is not that efficient as A::x could have been i32 which is stored and updated on the stack. Is there any better way to do it without the locks?
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Sender};
use std::{thread, time};
struct A {
x: Arc<Mutex<i32>>,
y: Sender<i32>,
}
impl A {
fn do_some_loop(&mut self) {
let sleep_time = time::Duration::from_millis(200);
// This is a long running thread.
for x in 1..1000000 {
*self.x.lock().unwrap() = x;
self.y.send(x);
thread::sleep(sleep_time);
}
}
}
fn test() {
let (sender, recever) = channel();
let x = Arc::new(Mutex::new(1));
let mut a = A { x: x.clone(), y: sender };
thread::spawn(move || {
// Monitor every 10 secs.
let sleep_time = time::Duration::from_millis(10000);
loop {
thread::sleep(sleep_time);
println!("{}", *x.lock().unwrap());
}
});
a.do_some_loop();
}

Condition variable not playing well with thread::sleep

I'm not sure I understand Rust's concurrency support with Mutexes and condition variables. In the following code, the main thread sets the poll_thread to be idle for two seconds, then to "read a register" for 2 seconds, and then return to "idle":
use std::thread;
use std::sync::{Arc, Mutex, Condvar};
use std::time;
#[derive(PartialEq, Debug)]
enum Command {
Idle,
ReadRegister(u32),
}
fn poll_thread(sync_pair: Arc<(Mutex<Command>, Condvar)>) {
let &(ref mutex, ref cvar) = &*sync_pair;
loop {
let mut flag = mutex.lock().unwrap();
while *flag == Command::Idle {
flag = cvar.wait(flag).unwrap();
}
match *flag {
Command::Idle => {
println!("WHAT IMPOSSIBLE!");
panic!();
}
Command::ReadRegister(i) => {
println!("You want me to read {}?", i);
thread::sleep(time::Duration::from_millis(450));
println!("Ok, here it is: {}", 42);
}
}
}
}
pub fn main() {
let pair = Arc::new((Mutex::new(Command::Idle), Condvar::new()));
let pclone = pair.clone();
let rx_thread = thread::spawn(|| poll_thread(pclone));
let &(ref mutex, ref cvar) = &*pair;
for i in 0..10 {
thread::sleep(time::Duration::from_millis(500));
if i == 4 {
println!("Setting ReadRegister");
let mut flag = mutex.lock().unwrap();
*flag = Command::ReadRegister(5);
println!("flag is = {:?}", *flag);
cvar.notify_one();
} else if i == 8 {
println!("Setting Idle");
let mut flag = mutex.lock().unwrap();
*flag = Command::Idle;
println!("flag is = {:?}", *flag);
cvar.notify_one();
}
}
println!("after notify_one()");
rx_thread.join();
}
This works as expected, but when the line to sleep for 450 milliseconds is uncommented, the code will often remain in the "read" state and not return to waiting on the condition variable cvar.wait(). Sometimes it will return to idle after, say, 15 seconds!
I would think that when poll_thread reaches the bottom of the loop, it would release the lock, allowing main to acquire and set flag = Command::Idle, and within roughly half a second, poll_thread would return to idle, but it appears that isn't happening when poll_thread sleeps. Why?

Skip part of loop while thread is sleeping

I have two parts of code that I want to run in a loop. Sometimes I need to make the loop 'sleep', making each iteration skip the second part. The loop should stop sleeping after a set amount of time (for example using a thread with a call to thread::sleep). How do I accomplish this?
use std::thread;
let mut sleeping = false;
let mut handle = thread::spawn(|| {});
loop {
part_1();
if sleeping {
continue;
}
part_2();
if some_condition {
sleeping = true;
handle = thread::spawn(|| thread::sleep_ms(100));
}
}
In this example, if the condition is met, the part_2 call would be skipped for some amount of iterations. My use case is continuing to run graphical updates in a game, while freezing the game's logic (such as counting down timers).
There is no need for the overhead of threads or even the need to sleep. Simply track the time that you should delay executing code until:
use std::time::{Duration, Instant};
fn part_1() {}
fn part_2() {}
fn some_condition() -> bool {
false
}
fn main() {
let mut sleep_until = None;
loop {
part_1();
if let Some(until) = sleep_until {
if until > Instant::now() {
continue;
}
}
part_2();
if some_condition() {
let now = Instant::now();
let until = now + Duration::from_millis(500);
sleep_until = Some(until);
}
}
}
Although I'd probably avoid the use of continue here, and instead embed the logic within:
use std::time::{Duration, Instant};
fn perform_physics_calculation() {}
fn perform_graphics_render() {}
fn main() {
let mut next_graphics_update = Instant::now();
let graphics_delay = Duration::from_millis(500);
loop {
let now = Instant::now();
perform_physics_calculation();
if next_graphics_update <= now {
perform_graphics_render();
next_graphics_update = now + graphics_delay;
}
}
}
Note in one case I use an Option<Instant> and in the other I just use an Instant; both cases can make sense.
Turn your sleeping variable into a reference-counted atomic boolean so that you can reset it on the sleeping thread.
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
fn part_1() {}
fn part_2() {}
fn some_condition() -> bool { false }
fn main() {
let sleeping = Arc::new(AtomicBool::new(false));
let mut handle = None;
loop {
part_1();
if sleeping.load(Ordering::Acquire) {
continue;
}
part_2();
if some_condition() {
sleeping.store(true, Ordering::Release);
let sleeping_clone = sleeping.clone();
handle = Some(thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
sleeping_clone.store(false, Ordering::Release);
}));
}
}
}

Resources