view/thread/
mod.rs

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
19/// The name of the main view thread.
20pub const MAIN_THREAD_NAME: &str = "view_main";
21/// The name of the view refresh thread.
22pub const REFRESH_THREAD_NAME: &str = "view_refresh";
23
24const MINIMUM_TICK_RATE: Duration = Duration::from_millis(20); // ~50 Hz update
25const PAUSE_TIME: Duration = Duration::from_millis(230); // 250 ms total pause
26
27/// A thread that updates the rendered view.
28#[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	/// Creates a new thread.
59	#[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	/// Returns a cloned copy of the state of the thread.
68	#[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); // give the refresh a chance to occur
399			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}