1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* Copyright (C) 2021 Purism SPC
 * SPDX-License-Identifier: GPL-3.0+
 */
 
/*! This drives the loop from the `loop` module.
 * 
 * The tracker loop needs to be driven somehow,
 * and connected to the external world,
 * both on the side of receiving and sending events.
 * 
 * That's going to be implementation-dependent,
 * connecting to some external mechanisms
 * for time, messages, and threading/callbacks.
 * 
 * This is the "imperative shell" part of the software,
 * and no longer unit-testable.
 */

use crate::event_loop;
use crate::logging;
use glib;
use std::sync::mpsc;
use std::thread;
use std::time::Instant;
use super::{ActorState, Outcome};

// Traits
use crate::logging::Warn;
use super::Event;


type UISender<S> = glib::Sender<
    <
        <S as ActorState>::Outcome as Outcome
    >::Commands
>;

/// This loop driver spawns a new thread which updates the state in a loop,
/// in response to incoming events.
/// It sends outcomes to the glib main loop using a channel.
/// The outcomes are applied by the UI end of the channel in the `main` module.
// This could still be reasonably tested,
/// by creating a glib::Sender and checking what messages it receives.
// This can/should be abstracted over Event and Commands,
// so that the C call-ins can be thrown away from here and defined near events.
#[derive(Clone)]
pub struct Threaded<S>
where
    S: ActorState + Send,
    S::Event: Send,
    <S::Outcome as Outcome>::Commands: Send,
{
    /// Waits for external events
    thread: mpsc::Sender<S::Event>,
}

impl<S> Threaded<S>
where
    // Not sure why this needs 'static. It's already owned.
    S: ActorState + Send + 'static,
    S::Event: Send,
    <S::Outcome as Outcome>::Commands: Send,
{
    pub fn new(
        ui: UISender<S>,
        initial_state: S,
    ) -> Self {
        let (sender, receiver) = mpsc::channel();
        let saved_sender = sender.clone();
        thread::spawn(move || {
            let mut state = event_loop::State::new(initial_state, Instant::now());
            loop {
                match receiver.recv() {
                    Ok(event) => {
                        state = Self::handle_loop_event(&sender, state, event, &ui);
                    },
                    Err(e) => {
                        logging::print(logging::Level::Bug, &format!("Senders hung up, aborting: {}", e));
                        return;
                    },
                };
            }
        });

        Self {
            thread: saved_sender,
        }
    }
    
    pub fn send(&self, event: S::Event) -> Result<(), mpsc::SendError<S::Event>> {
        self.thread.send(event)
    }
    
    fn handle_loop_event(
        loop_sender: &mpsc::Sender<S::Event>,
        state: event_loop::State<S>,
        event: S::Event, 
        ui: &UISender<S>,
    ) -> event_loop::State<S> {
        let now = Instant::now();

        let (new_state, commands) = event_loop::handle_event(state.clone(), event, now);

        ui.send(commands)
            .or_warn(&mut logging::Print, logging::Problem::Bug, "Can't send to UI");

        if new_state.scheduled_wakeup != state.scheduled_wakeup {
            if let Some(when) = new_state.scheduled_wakeup {
                Self::schedule_timeout_wake(loop_sender, when);
            }
        }
        
        new_state
    }

    fn schedule_timeout_wake(
        loop_sender: &mpsc::Sender<S::Event>,
        when: Instant,
    ) {
        let sender = loop_sender.clone();
        thread::spawn(move || {
            let now = Instant::now();
            thread::sleep(when - now);
            sender.send(S::Event::new_timeout_reached(when))
                .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake manager");
        });
    }
}