Condition variable

What we want to do is to make the thread sleep when it has nothing to do. We also want to be able to wake the thread up from the main thread. This is exactly what condition variables are for. So, let's add one to our engine. We'll start by adding a condition_variable field to the EventLoop:

struct EventLoop {
    condition_variable: Arc<(Mutex<bool>, Condvar)>,
    queue: Arc<SegQueue<Action>>,
    playing: Arc<Mutex<bool>>,
}

A condition variable is usually used together with a Boolean value (wrapped in a Mutex). We need to rewrite the constructor of EventLoop to initialize this new field:

impl EventLoop {
    fn new() -> Self {
        EventLoop {
            condition_variable: Arc::new((Mutex::new(false), Condvar::new())),
            queue: Arc::new(SegQueue::new()),
            playing: Arc::new(Mutex::new(false)),
        }
    }
}

Next, we need to block the thread when it has nothing to do. Here's the start of the new code of the thread in Player::new():

{
    let app_state = app_state.clone();
    let event_loop = event_loop.clone();
    let condition_variable = event_loop.condition_variable.clone();
    thread::spawn(move || {
        let block = || {
            let (ref lock, ref condition_variable) = 
*condition_variable; let mut started = lock.lock().unwrap(); *started = false; while !*started { started =
condition_variable.wait(started).unwrap(); } };

We create a copy of the condition variable and we move this copy into the thread. Then, in the beginning of the closure, we lock the Boolean value associated with the condition variable to set it to false. Afterward, we loop: while this value is false, we block the current thread. We created a closure instead of a normal function because normal functions cannot capture values. The following code is the same as before:

        let mut buffer = [[0; 2]; BUFFER_SIZE];
        let mut playback = Playback::new("MP3", "MP3 Playback", None, 
DEFAULT_RATE); let mut source = None; loop { if let Some(action) = event_loop.queue.try_pop() { match action { Load(path) => { let file = File::open(path).unwrap(); source =
Some(Mp3Decoder::new(BufReader::new(file)).unwrap()); let rate = source.as_ref().map(|source|
source.samples_rate()).unwrap_or(DEFAULT_RATE); playback = Playback::new("MP3", "MP3 Playback",
None, rate); app_state.lock().unwrap().stopped = false; }, Stop => { source = None; }, } } else if *event_loop.playing.lock().unwrap() { let mut written = false; if let Some(ref mut source) = source { let size = iter_to_buffer(source, &mut buffer); if size > 0 { app_state.lock().unwrap().current_time =
source.current_time(); playback.write(&buffer[..size]); written = true; } }

But the rest of the closure is a bit different:

                if !written {
                    app_state.lock().unwrap().stopped = true;
                    *event_loop.playing.lock().unwrap() = false;
                    source = None;
                    block();
                }
            } else {
                block();
            }
        }
    });
}

If the player was unable to play song (that is, the song came into an end), we call the closure to block the thread. We also block the thread if the player is paused. With the condition variable, the software stopped using 100% CPU.