ferroui_base/threading/
ferro_synchronization_context.rs1use std::cell::{Cell, RefCell};
2use std::future::Future;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::Arc;
5
6use super::dispatcher_task::{wrap_local_future, wrap_send_future};
7use super::{Dispatcher, DispatcherPriority, DispatcherTask};
8use crate::reactive::IDisposable;
9
10static AUTO_INSTALL: AtomicBool = AtomicBool::new(true);
11
12crate::ferro_thread_local! {
13 static CURRENT: RefCell<Option<Arc<FerroSynchronizationContext>>> = const { RefCell::new(None) };
14}
15
16pub struct FerroSynchronizationContext {
31 dispatcher: Arc<Dispatcher>,
32 priority: DispatcherPriority,
33}
34
35impl FerroSynchronizationContext {
36 #[allow(clippy::new_ret_no_self)]
39 pub fn new() -> Arc<Self> {
40 Self::with_dispatcher(&Dispatcher::current_dispatcher(), DispatcherPriority::DEFAULT)
41 }
42
43 pub fn with_priority(priority: DispatcherPriority) -> Arc<Self> {
45 Self::with_dispatcher(&Dispatcher::current_dispatcher(), priority)
46 }
47
48 pub fn with_dispatcher(dispatcher: &Arc<Dispatcher>, priority: DispatcherPriority) -> Arc<Self> {
49 Arc::new(Self { dispatcher: dispatcher.clone(), priority })
50 }
51
52 pub fn priority(&self) -> DispatcherPriority {
54 self.priority
55 }
56
57 pub fn dispatcher(&self) -> &Arc<Dispatcher> {
58 &self.dispatcher
59 }
60
61 pub fn current() -> Option<Arc<FerroSynchronizationContext>> {
63 CURRENT.try_with(|current| current.borrow().clone()).ok().flatten()
64 }
65
66 pub fn set_current(context: Option<Arc<FerroSynchronizationContext>>) -> Option<Arc<FerroSynchronizationContext>> {
68 CURRENT.try_with(|current| current.replace(context)).ok().flatten()
69 }
70
71 pub fn auto_install() -> bool {
74 AUTO_INSTALL.load(Ordering::Relaxed)
75 }
76
77 pub fn set_auto_install(value: bool) {
78 AUTO_INSTALL.store(value, Ordering::Relaxed);
79 }
80
81 pub fn install_if_needed() {
85 if !Self::auto_install() || Self::current().is_some() {
86 return;
87 }
88
89 Self::set_current(Some(Dispatcher::current_dispatcher().get_context_with_priority(DispatcherPriority::NORMAL)));
90 }
91
92 pub fn post(&self, callback: impl FnOnce() + Send + 'static) {
94 self.dispatcher.post(callback, self.priority);
95 }
96
97 pub fn post_local(&self, callback: impl FnOnce() + 'static) {
102 self.dispatcher.post_local(callback, self.priority);
103 }
104
105 pub fn send(&self, callback: impl FnOnce() + Send + 'static) {
107 if self.dispatcher.check_access() {
108 self.dispatcher.send(callback, Some(DispatcherPriority::SEND));
110 } else {
111 self.dispatcher.send(callback, Some(self.priority));
112 }
113 }
114
115 pub fn send_local(&self, callback: impl FnOnce() + 'static) {
121 self.dispatcher.send_local(callback, Some(DispatcherPriority::SEND));
123 }
124
125 pub fn to_task_scheduler(self: &Arc<Self>) -> DispatcherTaskScheduler {
128 DispatcherTaskScheduler { context: self.clone() }
129 }
130
131 pub fn ensure(priority: DispatcherPriority) -> RestoreContext {
135 Self::ensure_with_dispatcher(&Dispatcher::current_dispatcher(), priority)
136 }
137
138 pub fn ensure_with_dispatcher(dispatcher: &Dispatcher, priority: DispatcherPriority) -> RestoreContext {
146 let unchanged = CURRENT
147 .try_with(|current| current.borrow().as_ref().is_some_and(|context| context.priority == priority))
148 .unwrap_or(true);
149 if unchanged {
150 return RestoreContext::default();
151 }
152 dispatcher.verify_access();
153 let old_context = Self::set_current(Some(dispatcher.get_context_with_priority(priority)));
154 RestoreContext { old_context: Cell::new(Some(old_context)) }
155 }
156}
157
158#[derive(Default)]
164pub struct RestoreContext {
165 old_context: Cell<Option<Option<Arc<FerroSynchronizationContext>>>>,
167}
168
169impl IDisposable for RestoreContext {
170 fn dispose(&self) {
171 if let Some(old_context) = self.old_context.take() {
172 FerroSynchronizationContext::set_current(old_context);
173 }
174 }
175}
176
177impl Drop for RestoreContext {
178 fn drop(&mut self) {
179 self.dispose();
180 }
181}
182
183#[derive(Clone)]
189pub struct DispatcherTaskScheduler {
190 context: Arc<FerroSynchronizationContext>,
191}
192
193impl DispatcherTaskScheduler {
194 pub fn dispatcher(&self) -> &Arc<Dispatcher> {
195 &self.context.dispatcher
196 }
197
198 pub fn priority(&self) -> DispatcherPriority {
199 self.context.priority
200 }
201
202 pub fn spawn<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
205 where
206 Fut: Future + Send + 'static,
207 Fut::Output: Send + 'static,
208 {
209 let dispatcher = self.context.dispatcher.clone();
210 let priority = self.context.priority;
211 let (task, completion, wrap) = wrap_send_future::<Fut>(&dispatcher);
212 let target = dispatcher.clone();
213 dispatcher.post(move || target.start_task(priority, wrap(future), completion, true), priority);
215 task
216 }
217
218 pub fn spawn_local<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
223 where
224 Fut: Future + 'static,
225 Fut::Output: 'static,
226 {
227 let dispatcher = &self.context.dispatcher;
228 dispatcher.verify_access();
229 let (task, completion, wrap) = wrap_local_future::<Fut>(dispatcher);
230 dispatcher.start_task(self.context.priority, wrap(future), completion, false);
231 task
232 }
233
234 pub fn start_local<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
244 where
245 Fut: Future + 'static,
246 Fut::Output: 'static,
247 {
248 let dispatcher = &self.context.dispatcher;
249 dispatcher.verify_access();
250 let (task, completion, wrap) = wrap_local_future::<Fut>(dispatcher);
251 dispatcher.start_task(self.context.priority, wrap(future), completion, true);
252 task
253 }
254}
255
256impl Dispatcher {
257 pub(crate) fn get_context_with_priority(&self, priority: DispatcherPriority) -> Arc<FerroSynchronizationContext> {
260 DispatcherPriority::validate(priority, "priority");
261 let Some(local) = self.try_local() else {
262 return FerroSynchronizationContext::with_dispatcher(&self.to_arc(), priority);
263 };
264 let index = (priority.value() - DispatcherPriority::MIN_VALUE.value()) as usize;
265 let mut contexts = local.priority_contexts.borrow_mut();
266 if contexts.is_empty() {
267 let count = (DispatcherPriority::MAX_VALUE.value() - DispatcherPriority::MIN_VALUE.value() + 1) as usize;
268 contexts.resize(count, None);
269 }
270 contexts[index]
271 .get_or_insert_with(|| FerroSynchronizationContext::with_dispatcher(&self.to_arc(), priority))
272 .clone()
273 }
274
275 pub fn to_task_scheduler(&self) -> DispatcherTaskScheduler {
279 match FerroSynchronizationContext::current() {
280 Some(context) => self.to_task_scheduler_with_priority(context.priority()),
281 None => self.to_task_scheduler_with_priority(DispatcherPriority::DEFAULT),
282 }
283 }
284
285 pub fn to_task_scheduler_with_priority(&self, priority: DispatcherPriority) -> DispatcherTaskScheduler {
288 self.get_context_with_priority(priority).to_task_scheduler()
289 }
290}