1mod action;
2mod state;
3
4use std::{
5 sync::Arc,
6 thread::sleep,
7 time::{Duration, Instant},
8};
9
10use captur::capture;
11use display::Tui;
12use parking_lot::Mutex;
13use runtime::{Installer, RuntimeError, Threadable};
14
15pub(crate) use self::action::ViewAction;
16pub use self::state::State;
17use crate::View;
18
19pub const MAIN_THREAD_NAME: &str = "view_main";
21pub const REFRESH_THREAD_NAME: &str = "view_refresh";
23
24const MINIMUM_TICK_RATE: Duration = Duration::from_millis(20); const PAUSE_TIME: Duration = Duration::from_millis(230); #[derive(Debug)]
29pub struct Thread<ViewTui: Tui + Send + 'static> {
30 state: State,
31 view: Arc<Mutex<View<ViewTui>>>,
32}
33
34impl<ViewTui: Tui + Send + 'static> Threadable for Thread<ViewTui> {
35 #[inline]
36 fn install(&self, installer: &Installer) {
37 self.install_message_thread(installer);
38 self.install_refresh_thread(installer);
39 }
40
41 #[inline]
42 fn pause(&self) {
43 self.state.stop();
44 }
45
46 #[inline]
47 fn resume(&self) {
48 self.state.start();
49 }
50
51 #[inline]
52 fn end(&self) {
53 self.state.end();
54 }
55}
56
57impl<ViewTui: Tui + Send + 'static> Thread<ViewTui> {
58 #[inline]
60 pub fn new(view: View<ViewTui>) -> Self {
61 Self {
62 state: State::new(),
63 view: Arc::new(Mutex::new(view)),
64 }
65 }
66
67 #[inline]
69 #[must_use]
70 pub fn state(&self) -> State {
71 self.state.clone()
72 }
73
74 fn install_message_thread(&self, installer: &Installer) {
75 let view = Arc::clone(&self.view);
76 let state = self.state();
77
78 installer.spawn(MAIN_THREAD_NAME, |notifier| {
79 move || {
80 capture!(notifier, state);
81 notifier.busy();
82 state.start();
83 notifier.wait();
84
85 let render_slice = state.render_slice();
86 let update_receiver = state.update_receiver();
87 let mut last_render_time = Instant::now();
88 let mut should_render = true;
89 let mut is_started = false;
90
91 for msg in update_receiver {
92 notifier.busy();
93 match msg {
94 ViewAction::Render => should_render = true,
95 ViewAction::Start => {
96 is_started = true;
97 if let Err(err) = view.lock().start() {
98 notifier.error(RuntimeError::ThreadError(err.to_string()));
99 break;
100 }
101 },
102 ViewAction::Stop => {
103 is_started = false;
104 if let Err(err) = view.lock().end() {
105 notifier.error(RuntimeError::ThreadError(err.to_string()));
106 break;
107 }
108 },
109 ViewAction::Refresh => {},
110 ViewAction::End => break,
111 }
112
113 if is_started && should_render && Instant::now() >= last_render_time {
114 last_render_time += MINIMUM_TICK_RATE;
115 should_render = false;
116 let render_slice_mutex = render_slice.lock();
117 if let Err(err) = view.lock().render(&render_slice_mutex) {
118 notifier.error(RuntimeError::ThreadError(err.to_string()));
119 break;
120 }
121 }
122 notifier.wait();
123 }
124
125 notifier.request_end();
126 notifier.end();
127 }
128 });
129 }
130
131 fn install_refresh_thread(&self, installer: &Installer) {
132 let state = self.state();
133
134 installer.spawn(REFRESH_THREAD_NAME, |notifier| {
135 move || {
136 capture!(notifier, state);
137 notifier.wait();
138 let sleep_time = MINIMUM_TICK_RATE / 2;
139 let mut time = Instant::now();
140 while !state.is_ended() {
141 notifier.busy();
142 state.refresh();
143 notifier.wait();
144 loop {
145 sleep(time.saturating_duration_since(Instant::now()));
146 time += sleep_time;
147 if !state.is_paused() || state.is_ended() {
148 break;
149 }
150 time += PAUSE_TIME;
151 }
152 }
153
154 notifier.request_end();
155 notifier.end();
156 }
157 });
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use std::borrow::BorrowMut;
164
165 use claim::assert_ok;
166 use config::Theme;
167 use display::{
168 testutil::{create_unexpected_error, CrossTerm, MockableTui},
169 Display,
170 DisplayError,
171 };
172 use runtime::{testutils::ThreadableTester, Status};
173
174 use super::*;
175 use crate::ViewData;
176
177 const READ_MESSAGE_TIMEOUT: Duration = Duration::from_secs(1);
178
179 fn with_view<C, CT: MockableTui>(tui: CT, callback: C)
180 where C: FnOnce(View<CT>) {
181 let theme = Theme::new();
182 let display = Display::new(tui, &theme);
183 callback(View::new(display, "~", "?"));
184 }
185
186 #[test]
187 fn set_pause_resume() {
188 with_view(CrossTerm::new(), |view| {
189 let thread = Thread::new(view);
190 let state = thread.state();
191 thread.pause();
192 assert!(state.is_paused());
193 thread.resume();
194 assert!(!state.is_paused());
195 });
196 }
197
198 #[test]
199 fn set_end() {
200 with_view(CrossTerm::new(), |view| {
201 let thread = Thread::new(view);
202 let state = thread.state();
203 thread.end();
204 assert!(state.is_ended());
205 });
206 }
207
208 #[test]
209 fn main_thread_end() {
210 with_view(CrossTerm::new(), |view| {
211 let thread = Thread::new(view);
212 let state = thread.state();
213
214 let tester = ThreadableTester::new();
215 tester.start_threadable(&thread, MAIN_THREAD_NAME);
216
217 tester.wait_for_status(&Status::Waiting);
218 state.end();
219 tester.wait_for_status(&Status::Ended);
220 });
221 }
222
223 #[test]
224 fn main_thread_start() {
225 with_view(CrossTerm::new(), |view| {
226 let thread = Thread::new(view);
227 let state = thread.state();
228
229 let tester = ThreadableTester::new();
230 tester.start_threadable(&thread, MAIN_THREAD_NAME);
231 tester.wait_for_status(&Status::Waiting);
232 state.end();
233 tester.wait_for_status(&Status::Ended);
234 });
235 }
236
237 #[test]
238 fn main_thread_start_error() {
239 struct TestCrossTerm;
240
241 impl MockableTui for TestCrossTerm {
242 fn start(&mut self) -> Result<(), DisplayError> {
243 Err(create_unexpected_error())
244 }
245 }
246
247 with_view(TestCrossTerm {}, |view| {
248 let thread = Thread::new(view);
249
250 let tester = ThreadableTester::new();
251 tester.start_threadable(&thread, MAIN_THREAD_NAME);
252 tester.wait_for_error_status();
253 });
254 }
255
256 #[test]
257 fn main_thread_stop() {
258 with_view(CrossTerm::new(), |view| {
259 let thread = Thread::new(view);
260 let state = thread.state();
261
262 let tester = ThreadableTester::new();
263 tester.start_threadable(&thread, MAIN_THREAD_NAME);
264 tester.wait_for_status(&Status::Waiting);
265 state.stop();
266 tester.wait_for_status(&Status::Waiting);
267 state.end();
268 tester.wait_for_status(&Status::Ended);
269 });
270 }
271
272 #[test]
273 fn main_thread_stop_error() {
274 struct TestCrossTerm;
275
276 impl MockableTui for TestCrossTerm {
277 fn end(&mut self) -> Result<(), DisplayError> {
278 Err(create_unexpected_error())
279 }
280 }
281
282 with_view(TestCrossTerm {}, |view| {
283 let thread = Thread::new(view);
284 let state = thread.state();
285
286 let tester = ThreadableTester::new();
287 tester.start_threadable(&thread, MAIN_THREAD_NAME);
288 tester.wait_for_status(&Status::Waiting);
289 state.stop();
290 tester.wait_for_error_status();
291 });
292 }
293
294 #[test]
295 fn main_thread_render_with_should_render() {
296 struct TestCrossTerm {
297 lines: Arc<Mutex<Vec<String>>>,
298 }
299
300 impl TestCrossTerm {
301 fn new() -> Self {
302 Self {
303 lines: Arc::new(Mutex::new(vec![])),
304 }
305 }
306 }
307
308 impl MockableTui for TestCrossTerm {
309 fn print(&mut self, s: &str) -> Result<(), DisplayError> {
310 self.lines.lock().push(String::from(s));
311 Ok(())
312 }
313 }
314
315 let crossterm = TestCrossTerm::new();
316 let lines = Arc::clone(&crossterm.lines);
317
318 with_view(crossterm, |view| {
319 let thread = Thread::new(view);
320 let state = thread.state();
321 state.resize(100, 1);
322 let view_data = ViewData::new(|updater| {
323 updater.push_lines("foo");
324 });
325
326 let tester = ThreadableTester::new();
327 tester.start_threadable(&thread, MAIN_THREAD_NAME);
328 state.render(&view_data);
329 tester.wait_for_status(&Status::Waiting);
330 state.end();
331 tester.wait_for_status(&Status::Ended);
332 for _ in 0..10 {
333 let lines_lock = lines.lock();
334 let line = lines_lock.first().unwrap();
335 if line != "~" {
336 assert_eq!(line, "foo");
337 break;
338 }
339 }
340 });
341 }
342
343 #[test]
344 fn main_thread_render_with_should_render_error() {
345 struct TestCrossTerm;
346
347 impl MockableTui for TestCrossTerm {
348 fn reset(&mut self) -> Result<(), DisplayError> {
349 Err(create_unexpected_error())
350 }
351 }
352
353 with_view(TestCrossTerm {}, |view| {
354 let thread = Thread::new(view);
355 let tester = ThreadableTester::new();
356 tester.start_threadable(&thread, MAIN_THREAD_NAME);
357 tester.wait_for_error_status();
358 });
359 }
360
361 #[test]
362 fn main_thread_render_with_refresh() {
363 struct TestCrossTerm {
364 lines: Arc<Mutex<Vec<String>>>,
365 }
366
367 impl TestCrossTerm {
368 fn new() -> Self {
369 Self {
370 lines: Arc::new(Mutex::new(vec![])),
371 }
372 }
373 }
374
375 impl MockableTui for TestCrossTerm {
376 fn print(&mut self, s: &str) -> Result<(), DisplayError> {
377 self.lines.lock().push(String::from(s));
378 Ok(())
379 }
380 }
381
382 let crossterm = TestCrossTerm::new();
383 let lines = Arc::clone(&crossterm.lines);
384
385 with_view(crossterm, |view| {
386 let thread = Thread::new(view);
387 let state = thread.state();
388 state.resize(100, 1);
389 let view_data = ViewData::new(|updater| {
390 updater.push_lines("foo");
391 });
392 let render_slice = state.render_slice();
393 render_slice.lock().borrow_mut().sync_view_data(&view_data);
394
395 let tester = ThreadableTester::new();
396 tester.start_threadable(&thread, MAIN_THREAD_NAME);
397 tester.wait_for_status(&Status::Waiting);
398 sleep(MINIMUM_TICK_RATE); state.refresh();
400 tester.wait_for_status(&Status::Waiting);
401 state.end();
402 tester.wait_for_status(&Status::Ended);
403 for _ in 0..10 {
404 let lines_lock = lines.lock();
405 let line = lines_lock.first().unwrap();
406 if line != "~" {
407 break;
408 }
409 }
410 assert_eq!(*lines.lock().first().unwrap(), "foo");
411 });
412 }
413
414 #[test]
415 fn refresh_thread_receive_and_end() {
416 with_view(CrossTerm::new(), |view| {
417 let thread = Thread::new(view);
418 let state = thread.state();
419 let receiver = state.update_receiver();
420
421 let tester = ThreadableTester::new();
422 tester.start_threadable(&thread, REFRESH_THREAD_NAME);
423
424 assert!(matches!(
425 receiver.recv_timeout(READ_MESSAGE_TIMEOUT).unwrap(),
426 ViewAction::Refresh
427 ));
428
429 state.end();
430 tester.wait_for_status(&Status::Ended);
431 });
432 }
433
434 #[test]
435 fn refresh_thread_stop_resume() {
436 with_view(CrossTerm::new(), |view| {
437 let thread = Thread::new(view);
438 let state = thread.state();
439 let receiver = state.update_receiver();
440
441 let tester = ThreadableTester::new();
442 tester.start_threadable(&thread, REFRESH_THREAD_NAME);
443 _ = receiver.recv_timeout(READ_MESSAGE_TIMEOUT).unwrap();
444 state.stop();
445 tester.wait_for_status(&Status::Waiting);
446 while receiver.recv_timeout(READ_MESSAGE_TIMEOUT).is_ok() {}
447 _ = receiver.recv_timeout(READ_MESSAGE_TIMEOUT).unwrap_err();
448 state.start();
449 assert_ok!(receiver.recv_timeout(READ_MESSAGE_TIMEOUT));
450 state.end();
451 tester.wait_for_status(&Status::Ended);
452 });
453 }
454
455 #[test]
456 fn refresh_thread_stop_end() {
457 with_view(CrossTerm::new(), |view| {
458 let thread = Thread::new(view);
459 let state = thread.state();
460 let receiver = state.update_receiver();
461
462 let tester = ThreadableTester::new();
463 tester.start_threadable(&thread, REFRESH_THREAD_NAME);
464 _ = receiver.recv_timeout(READ_MESSAGE_TIMEOUT).unwrap();
465 state.stop();
466 while receiver.recv_timeout(READ_MESSAGE_TIMEOUT).is_ok() {}
467 state.end();
468 tester.wait_for_status(&Status::Ended);
469 });
470 }
471}