summaryrefslogtreecommitdiffstats
path: root/src/action.rs
blob: bb8958fd6fc4477a484d6e5ac92e66e999af3038 (plain) (blame)
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
#[derive(Debug)]
pub enum Action {
    Render,
    ForceRedraw,
    Run(String),
    UpdateFocus(crate::state::Focus),
    UpdateScene(crate::state::Scene),
    CheckUpdateScene,
    Resize((u16, u16)),
    Quit,
}

pub struct Debouncer {
    pending: async_std::sync::Mutex<Pending>,
    cvar: async_std::sync::Condvar,
}

impl Debouncer {
    pub async fn recv(&self) -> Option<Action> {
        let mut pending = self
            .cvar
            .wait_until(self.pending.lock().await, |pending| {
                pending.has_event()
            })
            .await;
        pending.get_event()
    }

    async fn send(&self, action: Option<Action>) {
        let mut pending = self.pending.lock().await;
        pending.new_event(&action);
        self.cvar.notify_one();
    }
}

#[derive(Default)]
struct Pending {
    render: Option<()>,
    force_redraw: Option<()>,
    run: std::collections::VecDeque<String>,
    focus: Option<crate::state::Focus>,
    scene: Option<crate::state::Scene>,
    check_scene: Option<()>,
    size: Option<(u16, u16)>,
    done: bool,
}

impl Pending {
    fn new() -> Self {
        Self::default()
    }

    fn has_event(&self) -> bool {
        self.done
            || self.render.is_some()
            || self.force_redraw.is_some()
            || !self.run.is_empty()
            || self.focus.is_some()
            || self.scene.is_some()
            || self.check_scene.is_some()
            || self.size.is_some()
    }

    fn get_event(&mut self) -> Option<Action> {
        if self.size.is_some() {
            return Some(Action::Resize(self.size.take().unwrap()));
        }
        if !self.run.is_empty() {
            return Some(Action::Run(self.run.pop_front().unwrap()));
        }
        if self.focus.is_some() {
            return Some(Action::UpdateFocus(self.focus.take().unwrap()));
        }
        if self.scene.is_some() {
            return Some(Action::UpdateScene(self.scene.take().unwrap()));
        }
        if self.check_scene.take().is_some() {
            return Some(Action::CheckUpdateScene);
        }
        if self.force_redraw.take().is_some() {
            self.render.take();
            return Some(Action::ForceRedraw);
        }
        if self.render.take().is_some() {
            return Some(Action::Render);
        }
        if self.done {
            return None;
        }
        unreachable!()
    }

    fn new_event(&mut self, action: &Option<Action>) {
        match action {
            Some(Action::Render) => self.render = Some(()),
            Some(Action::ForceRedraw) => self.force_redraw = Some(()),
            Some(Action::Run(cmd)) => self.run.push_back(cmd.to_string()),
            Some(Action::UpdateFocus(focus)) => self.focus = Some(*focus),
            Some(Action::UpdateScene(scene)) => self.scene = Some(*scene),
            Some(Action::CheckUpdateScene) => self.check_scene = Some(()),
            Some(Action::Resize(size)) => self.size = Some(*size),
            Some(Action::Quit) | None => self.done = true,
        }
    }
}

pub fn debounce(
    input: async_std::channel::Receiver<Action>,
) -> async_std::sync::Arc<Debouncer> {
    let debouncer = std::sync::Arc::new(Debouncer {
        pending: async_std::sync::Mutex::new(Pending::new()),
        cvar: async_std::sync::Condvar::new(),
    });
    {
        let debouncer = std::sync::Arc::clone(&debouncer);
        async_std::task::spawn(async move {
            while let Ok(action) = input.recv().await {
                debouncer.send(Some(action)).await;
            }
            debouncer.send(None).await;
        });
    }
    debouncer
}