ferroui_base/threading/
dispatcher_main_loop.rs1use std::cell::Cell;
2use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
3use std::rc::Rc;
4use std::sync::atomic::Ordering;
5use std::sync::Arc;
6
7use super::{CancellationToken, Dispatcher, DispatcherFrame, DispatcherPriority, FerroSynchronizationContext};
8use crate::reactive::{Disposable, IDisposable};
9
10impl Dispatcher {
11 pub(crate) fn exit_all_frames_requested(&self) -> bool {
12 self.exit_all_frames_requested.load(Ordering::SeqCst)
13 }
14
15 pub fn shutdown_started(&self, handler: impl Fn(&Arc<Dispatcher>) + 'static) -> Rc<dyn IDisposable> {
20 self.verify_access();
21 let Some(local) = self.try_local() else {
22 return Disposable::empty();
23 };
24 let token = local.shutdown_started.add(Rc::new(handler));
25 let local = Rc::downgrade(&local);
26 Disposable::create(move || {
27 if let Some(local) = local.upgrade() {
28 local.shutdown_started.remove(token);
29 }
30 })
31 }
32
33 pub fn shutdown_finished(&self, handler: impl Fn(&Arc<Dispatcher>) + 'static) -> Rc<dyn IDisposable> {
38 self.verify_access();
39 let Some(local) = self.try_local() else {
40 return Disposable::empty();
41 };
42 let token = local.shutdown_finished.add(Rc::new(handler));
43 let local = Rc::downgrade(&local);
44 Disposable::create(move || {
45 if let Some(local) = local.upgrade() {
46 local.shutdown_finished.remove(token);
47 }
48 })
49 }
50
51 pub fn push_frame(&self, frame: &Arc<DispatcherFrame>) {
59 self.verify_access();
60 let impl_ = {
61 let impl_ = self.platform_impl();
62 let st = self.lock();
63 if impl_.as_controlled().is_none() {
64 panic!("Operation is not supported on this platform: the dispatcher cannot run nested loops.");
65 }
66
67 if st.has_shutdown_finished {
68 panic!("Cannot perform requested operation because the Dispatcher shut down");
70 }
71
72 if st.disabled_processing_count > 0 {
73 panic!("Cannot perform this operation while dispatcher processing is suspended.");
74 }
75 impl_
76 };
77
78 self.lock().frames.push(frame.clone());
79 let result = catch_unwind(AssertUnwindSafe(|| {
80 if let Some(controlled) = impl_.as_controlled() {
81 let _restore = FerroSynchronizationContext::ensure_with_dispatcher(self, DispatcherPriority::NORMAL);
82 frame.run(controlled);
83 }
84 }));
85
86 let no_frames_left = {
88 let mut st = self.lock();
89 st.frames.pop();
90 st.frames.is_empty()
91 };
92 if no_frames_left {
93 if self.has_shutdown_started() {
94 self.shutdown_impl();
95 } else {
96 self.exit_all_frames_requested.store(false, Ordering::SeqCst);
97 }
98 }
99 if let Err(payload) = result {
100 resume_unwind(payload);
101 }
102 }
103
104 pub fn main_loop(&self, cancellation_token: &CancellationToken) {
110 if !self.supports_run_loops() {
111 panic!("Operation is not supported on this platform: the dispatcher cannot run loops.");
112 }
113 self.verify_access();
114 let frame = DispatcherFrame::with_dispatcher(&self.to_arc(), true);
115 let to_stop = frame.clone();
116 let registration = cancellation_token.register(move || to_stop.set_continue(false));
117 let result = catch_unwind(AssertUnwindSafe(|| self.push_frame(&frame)));
118 registration.dispose();
120 if let Err(payload) = result {
121 resume_unwind(payload);
122 }
123 }
124
125 pub fn exit_all_frames(&self) {
127 let frames = self.lock().frames.clone();
128 if frames.is_empty() {
129 return;
130 }
131 self.exit_all_frames_requested.store(true, Ordering::SeqCst);
132 for frame in frames.iter().rev() {
133 frame.maybe_exit_on_dispatcher_request();
134 }
135 }
136
137 pub fn begin_invoke_shutdown(&self, priority: DispatcherPriority) {
139 let dispatcher = self.to_arc();
140 self.post(move || dispatcher.start_shutdown_impl(), priority);
141 }
142
143 pub fn invoke_shutdown(&self) {
145 let dispatcher = self.to_arc();
146 let _ = self.invoke_with_priority(move || dispatcher.start_shutdown_impl(), DispatcherPriority::SEND);
149 }
150
151 fn start_shutdown_impl(&self) {
152 {
153 let mut st = self.lock();
154 if st.starting_shutdown {
155 return;
156 }
157 st.starting_shutdown = true;
160 }
161
162 if let Some(local) = self.try_local() {
166 let dispatcher = self.to_arc();
167 for (_, handler) in local.shutdown_started.snapshot().iter() {
168 handler(&dispatcher);
169 }
170 }
171
172 self.has_shutdown_started.store(true, Ordering::SeqCst);
173
174 let has_frames = !self.lock().frames.is_empty();
175 if has_frames {
176 self.exit_all_frames();
177 } else {
178 self.shutdown_impl();
179 }
180 }
181
182 pub(crate) fn shutdown_impl(&self) {
183 let local = self.try_local();
184 if let Some(local) = &local {
185 Self::detach_implementation(local);
186 }
187 loop {
188 let operation = {
189 let mut st = self.lock();
190 if st.queue.max_priority() != DispatcherPriority::INVALID {
191 st.queue.peek()
192 } else {
193 if let Some(local) = &local {
194 local.impl_.borrow().update_timer(None);
195 }
196 st.has_shutdown_finished = true;
197 None
198 }
199 };
200
201 match operation {
202 Some(operation) => {
203 operation.abort();
204 }
205 None => break,
206 }
207 }
208
209 self.cancel_tasks();
212
213 if let Some(local) = local {
214 let dispatcher = self.to_arc();
215 for (_, handler) in local.shutdown_finished.snapshot().iter() {
216 handler(&dispatcher);
217 }
218 }
219 }
220
221 pub fn disable_processing(&self) -> DispatcherProcessingDisabled {
228 self.verify_access();
229
230 self.lock().disabled_processing_count += 1;
232 DispatcherProcessingDisabled { dispatcher: Cell::new(Some(self.to_arc())) }
233 }
234}
235
236pub struct DispatcherProcessingDisabled {
239 dispatcher: Cell<Option<Arc<Dispatcher>>>,
240}
241
242impl IDisposable for DispatcherProcessingDisabled {
243 fn dispose(&self) {
244 let Some(dispatcher) = self.dispatcher.take() else {
245 return;
246 };
247 dispatcher.lock().disabled_processing_count -= 1;
248 }
249}