mainframe/app/
app.rs

1use crossterm::event::{Event, EventStream, KeyCode};
2use futures::StreamExt;
3
4use systemstat::Duration;
5
6use std::error::Error;
7use std::sync::{Arc, Mutex};
8
9use crate::display::state::UIState;
10use crate::display::ui::{draw, init_ui, shutdown_ui};
11
12use crate::monitoring::polling::{SystemPollResult, SystemPoller, SystemPollerTarget};
13use crate::monitoring::system::SystemData;
14use crate::ringbuffer::RingBuffer;
15
16enum MFAMessage {
17    Exit,
18}
19
20pub struct MainFrameApp {
21    refresh_rate: f32,
22    poll_rate: f32,
23}
24
25impl Default for MainFrameApp {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl MainFrameApp {
32    /// Set the ui refresh interval for the app instance.
33    ///
34    /// Interval is taken in hz - refreshes per second.
35    /// An app with a 60 fps refresh rate would supply interval=60
36    pub fn with_refresh_rate(mut self, hz: f32) -> Self {
37        self.refresh_rate = hz;
38
39        self
40    }
41
42    /// Set the data poll interval for the app instance.
43    ///
44    /// Interval is taken in hz - refreshes per second.
45    /// An app with a 60 fps refresh rate would supply interval=60
46    pub fn with_poll_rate(mut self, hz: f32) -> Self {
47        self.poll_rate = hz;
48
49        self
50    }
51
52    /// Intstantiate a new app instance.
53    ///
54    /// A new instance of mainframe app has not acquired any resources, nor
55    /// taken control of the terminal yet. New app instances should be modified
56    /// before the call to `run()`, at which point event and render resources
57    /// are acquired.
58    pub fn new() -> Self {
59        MainFrameApp {
60            refresh_rate: 20.0,
61            poll_rate: 1.0,
62        }
63    }
64
65    pub async fn run(self) -> Result<(), Box<dyn Error>> {
66        // Setup
67        let mut terminal = init_ui()?;
68
69        defer! {
70            // Teardown
71            // NOTE: UI shutdown must happen after every other event and
72            // terminal call.
73            shutdown_ui().unwrap();
74        }
75
76        // --- Init sync primitives --- //
77        // ui state
78        let app_state = UIState::new_shared();
79        let app_data = Arc::new(Mutex::new(SystemData::new_from_poll()));
80
81        let poll_results = Arc::new(Mutex::new(RingBuffer::<SystemPollResult>::new(1)));
82
83        let mut system_poller = SystemPoller::new().with_poll_targets(vec![
84            SystemPollerTarget::CpuUsage,
85            SystemPollerTarget::CpuTemperature,
86            SystemPollerTarget::Gpu,
87            SystemPollerTarget::Memory,
88        ]);
89
90        poll_results.lock().unwrap().add(system_poller.poll());
91
92        let _app_state_handle = app_state.clone();
93        let _app_data_handle = app_data.clone();
94        let _poll_result_handle_poll_thread = poll_results.clone();
95        let _poll_result_handle_draw_thread = poll_results.clone();
96
97        // Message channel for the draw thread
98        let (ui_tx, mut ui_rx) = tokio::sync::mpsc::unbounded_channel::<MFAMessage>();
99
100        let mut redraw_interval =
101            tokio::time::interval(Duration::from_secs_f32(1.0 / self.refresh_rate));
102
103        let mut polling_interval =
104            tokio::time::interval(Duration::from_secs_f32(1.0 / self.poll_rate));
105
106        // Launch ui thread
107        let ui_thread = tokio::spawn(async move {
108            loop {
109                // Suspend until next redraw timer
110                redraw_interval.tick().await;
111
112                // Consume messages from host thread
113                let msg = match ui_rx.try_recv() {
114                    Ok(x) => Some(x),
115                    Err(tokio::sync::mpsc::error::TryRecvError::Empty) => None,
116                    Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
117                        return;
118                    }
119                };
120
121                match msg {
122                    Some(MFAMessage::Exit) => {
123                        return;
124                    }
125                    _ => (),
126                };
127
128                {
129                    let mut s = _app_state_handle.lock().unwrap();
130                    let d = _app_data_handle.lock().unwrap();
131                    let r = _poll_result_handle_draw_thread.lock().unwrap();
132
133                    // Draw ui elements
134                    terminal.draw(|f| draw(&mut s, &d, &r, f)).unwrap();
135                }
136            }
137        });
138
139        // Launch polling thread
140        let polling_thread = tokio::spawn(async move {
141            loop {
142                polling_interval.tick().await;
143
144                let poll_result = system_poller.poll();
145
146                {
147                    let mut p = _poll_result_handle_poll_thread.lock().unwrap();
148
149                    p.add(poll_result);
150                }
151            }
152        });
153
154        let mut events = EventStream::new();
155
156        // Run main processing loop
157        'mainloop: loop {
158            // Consume pending events
159            match events.next().await {
160                Some(Ok(Event::Key(evnt))) => match evnt.code {
161                    // Quit key
162                    KeyCode::Char('q') => {
163                        ui_tx.send(MFAMessage::Exit).unwrap();
164                        break 'mainloop;
165                    }
166                    // Tab selection keys
167                    // KeyCode::Char('h') => {
168                    //     app_state.lock().unwrap().current_tab = 0;
169                    // }
170                    // KeyCode::Char('u') => {
171                    //     app_state.lock().unwrap().current_tab = 1;
172                    // }
173                    // KeyCode::Char('d') => {
174                    //     app_state.lock().unwrap().current_tab = 2;
175                    // }
176                    _ => (),
177                },
178                Some(Err(e)) => return Err(Box::new(e)),
179                None => break 'mainloop,
180                _ => (),
181            };
182        }
183
184        ui_thread.abort();
185        polling_thread.abort();
186
187        Ok(())
188    }
189}