ferroui_base/threading/
dispatcher_frame.rs1use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
4
5use super::{CancellationTokenSource, Dispatcher, IControlledDispatcherImpl};
6
7#[derive(Default)]
8struct FrameState {
9 is_running: bool,
10 cancellation_token_source: Option<CancellationTokenSource>,
11}
12
13pub struct DispatcherFrame {
18 dispatcher: Arc<Dispatcher>,
19 exit_when_requested: bool,
20 continue_: AtomicBool,
21 state: Mutex<FrameState>,
22}
23
24impl DispatcherFrame {
25 pub fn new() -> Arc<DispatcherFrame> {
28 Self::with_exit_when_requested(true)
29 }
30
31 pub fn with_exit_when_requested(exit_when_requested: bool) -> Arc<DispatcherFrame> {
44 let frame = Self::with_dispatcher(&Dispatcher::current_dispatcher(), exit_when_requested);
45 frame.dispatcher.verify_access();
46 frame
47 }
48
49 pub(crate) fn with_dispatcher(dispatcher: &Arc<Dispatcher>, exit_when_requested: bool) -> Arc<DispatcherFrame> {
50 Arc::new(DispatcherFrame {
51 dispatcher: dispatcher.clone(),
52 exit_when_requested,
53 continue_: AtomicBool::new(true),
54 state: Mutex::new(FrameState::default()),
55 })
56 }
57
58 pub fn dispatcher(&self) -> &Arc<Dispatcher> {
60 &self.dispatcher
61 }
62
63 fn lock(&self) -> MutexGuard<'_, FrameState> {
64 self.state.lock().unwrap_or_else(PoisonError::into_inner)
65 }
66
67 pub fn continue_(&self) -> bool {
69 let mut should_continue = self.continue_.load(Ordering::SeqCst);
73 if should_continue {
74 if self.exit_when_requested {
77 let dispatcher = &self.dispatcher;
78
79 if dispatcher.exit_all_frames_requested() || dispatcher.has_shutdown_started() {
82 should_continue = false;
83 }
84 }
85 }
86
87 should_continue
88 }
89
90 pub fn set_continue(&self, value: bool) {
93 let to_cancel = {
95 let state = self.lock();
96 self.continue_.store(value, Ordering::SeqCst);
97 if !value {
98 state.cancellation_token_source.clone()
99 } else {
100 None
101 }
102 };
103 if let Some(source) = to_cancel {
105 source.cancel();
106 }
107 }
108
109 pub(crate) fn run(&self, impl_: &dyn IControlledDispatcherImpl) {
110 self.dispatcher.verify_access();
111
112 loop {
115 let token = {
116 let mut state = self.lock();
117 if !self.continue_() {
118 return;
119 }
120
121 if state.is_running {
122 panic!("This frame is already running");
123 }
124
125 let source = CancellationTokenSource::new();
126 let token = source.token();
127 state.cancellation_token_source = Some(source);
128 state.is_running = true;
129 token
130 };
131
132 let result = catch_unwind(AssertUnwindSafe(|| {
133 self.dispatcher.request_processing();
135 impl_.run_loop(token);
136 }));
137
138 let source = {
140 let mut state = self.lock();
141 state.is_running = false;
142 state.cancellation_token_source.take()
143 };
144 if let Some(source) = source {
145 source.cancel();
146 }
147 if let Err(payload) = result {
148 resume_unwind(payload);
149 }
150 }
151 }
152
153 pub(crate) fn maybe_exit_on_dispatcher_request(&self) {
154 if self.exit_when_requested {
155 let source = self.lock().cancellation_token_source.clone();
156 if let Some(source) = source {
157 source.cancel();
158 }
159 }
160 }
161}