Search code examples
multithreadingrustmutability

how do i share a mutable variable between threads?


i need to have a thread that recursively checks a variable while the main thread changes the variable. however, it appears that with move, the variable is not being changes by the lambda in register. (i checked and the function works, its just that move makes it so i cannot change the original variable)

the code i currently have created:

use std::io::stdin;
use std::thread;

use enigo::{self, Enigo, MouseControllable};
use enigo::MouseButton;

use livesplit_hotkey::*;


fn main() {
    let mut input = String::new();
    let mut started = false;

    println!("How many clicks per tick? (too many will lag)");

    stdin().read_line(&mut input).expect("Unable to read line");

    let input2 = input.trim().parse::<u16>().unwrap();

    for _ in 0 .. input2 {
        thread::spawn(move || {
            let mut enigo = Enigo::new();

            loop {
                if started {
                    println!("clicking"); // debug
                    enigo.mouse_click(MouseButton::Left);
                }
            }
        });
    }

    println!("Press f8 to toggle clicking");

    let hook = Hook::new().unwrap();

    hook.register(Hotkey { key_code: KeyCode::F8, modifiers: Modifiers::empty() },  move || {
        started = !started;
    }).expect("Unable to assign hotkey");

    loop {}
}

i know that there are things such as Arc and Mutex, but i'm unsure how to use them correctly.


Solution

  • Just use a static AtomicBool in place of started:

    use std::io::stdin;
    use std::thread;
    
    use enigo::{self, Enigo, MouseControllable};
    use enigo::MouseButton;
    
    use livesplit_hotkey::*;
    
    static STARTED: atomic::AtomicBool = atomic::AtomicBool::new(false);
    
    fn main() {
        let mut input = String::new();
    
        println!("How many clicks per tick? (too many will lag)");
    
        stdin().read_line(&mut input).expect("Unable to read line");
    
        let input2 = input.trim().parse::<u16>().unwrap();
    
        for _ in 0 .. input2 {
            thread::spawn(move || {
                let mut enigo = Enigo::new();
    
                loop {
                    if STARTED.load(atomic::Ordering::SeqCst) {
                        println!("clicking"); // debug
                        enigo.mouse_click(MouseButton::Left);
                    }
                }
            });
        }
    
        println!("Press f8 to toggle clicking");
    
        let hook = Hook::new().unwrap();
    
        hook.register(Hotkey { key_code: KeyCode::F8, modifiers: Modifiers::empty() },  move || {
            // use xor(true) to emulate "NOT" operation
            // true ^ true -> false
            // false ^ true -> true
            STARTED.fetch_xor(true, atomic::Ordering::SeqCst);
        }).expect("Unable to assign hotkey");
    
        loop {}
    }
    

    Note: Ordering::SeqCst may not be the ideal atomic ordering for this. You could use one of the less strict orderings, but I'll leave that to you.