1use std::{
8 collections::HashMap,
9 panic::{self, AssertUnwindSafe},
10 rc::{Rc, Weak},
11 sync::{
12 Arc, Mutex, Weak as ArcWeak,
13 atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
14 mpsc,
15 },
16 time::Duration,
17};
18
19use incular_platform::WindowId;
20use incular_widgets::internal::ElementId;
21use tokio::{
22 runtime::{Builder, Handle, Runtime as TokioRuntime},
23 task::{AbortHandle, JoinHandle},
24};
25
26use crate::Runtime;
27
28const DEFAULT_UI_MESSAGE_BUDGET: usize = 128;
29const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100);
30
31pub trait RuntimeWake: Send + Sync + 'static {
34 fn wake(&self);
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum TaskFailure {
40 Cancelled,
41 Panicked,
42 RuntimeStopped,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum AsyncState<T, E> {
52 Idle,
53 Loading,
54 Ready(T),
55 Error(E),
56}
57
58impl<T, E> AsyncState<T, E> {
59 #[must_use]
60 pub const fn is_loading(&self) -> bool {
61 matches!(self, Self::Loading)
62 }
63
64 #[must_use]
65 pub const fn is_terminal(&self) -> bool {
66 matches!(self, Self::Ready(_) | Self::Error(_))
67 }
68
69 #[must_use]
70 pub const fn is_idle(&self) -> bool {
71 matches!(self, Self::Idle)
72 }
73
74 #[must_use]
75 pub const fn is_ready(&self) -> bool {
76 matches!(self, Self::Ready(_))
77 }
78
79 #[must_use]
80 pub const fn is_error(&self) -> bool {
81 matches!(self, Self::Error(_))
82 }
83
84 #[must_use]
86 pub fn map<U>(self, map: impl FnOnce(T) -> U) -> AsyncState<U, E> {
87 match self {
88 Self::Idle => AsyncState::Idle,
89 Self::Loading => AsyncState::Loading,
90 Self::Ready(value) => AsyncState::Ready(map(value)),
91 Self::Error(error) => AsyncState::Error(error),
92 }
93 }
94}
95
96pub type AsyncValue<T> = AsyncState<T, TaskFailure>;
100
101#[derive(Clone)]
105pub struct TaskHandle {
106 control: Arc<TaskControl>,
107}
108
109impl TaskHandle {
110 pub fn cancel(&self) {
113 self.control.cancel();
114 }
115
116 #[must_use]
117 pub fn is_cancelled(&self) -> bool {
118 self.control.is_cancelled()
119 }
120}
121
122pub struct Task<T> {
126 state: Arc<Mutex<AsyncValue<T>>>,
127 handle: TaskHandle,
128}
129
130impl<T: Clone> Task<T> {
131 #[must_use]
132 pub fn value(&self) -> AsyncValue<T> {
133 if self.handle.is_cancelled() {
134 AsyncValue::Error(TaskFailure::Cancelled)
135 } else {
136 self.state.lock().expect("task state mutex").clone()
137 }
138 }
139}
140
141impl<T> Task<T> {
142 pub fn cancel(&self) {
143 self.handle.cancel();
144 }
145
146 #[must_use]
147 pub fn handle(&self) -> TaskHandle {
148 self.handle.clone()
149 }
150}
151
152#[derive(Clone)]
156pub struct TaskScope {
157 state: Arc<ScopeState>,
158}
159
160struct ScopeState {
161 cancelled: AtomicBool,
162 owner: Mutex<Option<ElementId>>,
163 window: Mutex<Option<WindowId>>,
164 children: Mutex<HashMap<u64, ArcWeak<TaskControl>>>,
165 child_scopes: Mutex<Vec<ArcWeak<ScopeState>>>,
166 parent: ArcWeak<ScopeState>,
167}
168
169impl TaskScope {
170 fn new() -> Self {
171 Self {
172 state: Arc::new(ScopeState {
173 cancelled: AtomicBool::new(false),
174 owner: Mutex::new(None),
175 window: Mutex::new(None),
176 children: Mutex::new(HashMap::new()),
177 child_scopes: Mutex::new(Vec::new()),
178 parent: ArcWeak::new(),
179 }),
180 }
181 }
182
183 #[must_use]
186 pub(crate) fn child(&self) -> Self {
187 let child = Self {
188 state: Arc::new(ScopeState {
189 cancelled: AtomicBool::new(self.is_cancelled()),
190 owner: Mutex::new(None),
191 window: Mutex::new(self.window_id()),
192 children: Mutex::new(HashMap::new()),
193 child_scopes: Mutex::new(Vec::new()),
194 parent: Arc::downgrade(&self.state),
195 }),
196 };
197 self.state
198 .child_scopes
199 .lock()
200 .expect("task scope children mutex")
201 .push(Arc::downgrade(&child.state));
202 child
203 }
204
205 pub fn cancel(&self) {
206 if self.state.cancelled.swap(true, Ordering::AcqRel) {
207 return;
208 }
209 let children: Vec<_> = self
210 .state
211 .children
212 .lock()
213 .expect("task scope mutex")
214 .values()
215 .filter_map(ArcWeak::upgrade)
216 .collect();
217 for child in children {
218 child.cancel();
219 }
220 let child_scopes: Vec<_> = self
221 .state
222 .child_scopes
223 .lock()
224 .expect("task scope children mutex")
225 .drain(..)
226 .filter_map(|scope| scope.upgrade())
227 .collect();
228 for scope in child_scopes {
229 TaskScope { state: scope }.cancel();
230 }
231 }
232
233 #[must_use]
234 pub fn is_cancelled(&self) -> bool {
235 self.state.cancelled.load(Ordering::Acquire)
236 || self
237 .state
238 .parent
239 .upgrade()
240 .is_some_and(|parent| parent.cancelled.load(Ordering::Acquire))
241 }
242
243 pub(crate) fn bind_owner(&self, owner: ElementId) {
244 *self.state.owner.lock().expect("task scope owner mutex") = Some(owner);
245 }
246
247 pub(crate) fn bind_window(&self, window: WindowId) {
248 *self.state.window.lock().expect("task scope window mutex") = Some(window);
249 }
250
251 #[must_use]
252 pub(crate) fn window_id(&self) -> Option<WindowId> {
253 (*self.state.window.lock().expect("task scope window mutex")).or_else(|| {
254 self.state
255 .parent
256 .upgrade()
257 .and_then(|parent| *parent.window.lock().expect("task scope window mutex"))
258 })
259 }
260
261 fn attach(&self, control: &Arc<TaskControl>) {
262 self.state
263 .children
264 .lock()
265 .expect("task scope mutex")
266 .insert(control.id, Arc::downgrade(control));
267 }
268
269 fn detach(&self, id: u64) {
270 self.state
271 .children
272 .lock()
273 .expect("task scope mutex")
274 .remove(&id);
275 }
276}
277
278impl Drop for TaskScope {
279 fn drop(&mut self) {
280 if Arc::strong_count(&self.state) == 1 {
283 self.cancel();
284 }
285 }
286}
287
288pub(crate) struct TaskControl {
289 id: u64,
290 cancelled: AtomicBool,
291 scope: ArcWeak<ScopeState>,
292 abort: Mutex<Option<AbortHandle>>,
293}
294
295impl TaskControl {
296 fn cancel(&self) {
297 self.cancelled.store(true, Ordering::Release);
298 if let Some(abort) = self.abort.lock().expect("task abort mutex").as_ref() {
299 abort.abort();
300 }
301 }
302
303 pub(crate) fn is_cancelled(&self) -> bool {
304 self.cancelled.load(Ordering::Acquire)
305 || self
306 .scope
307 .upgrade()
308 .is_none_or(|scope| scope.cancelled.load(Ordering::Acquire))
309 }
310
311 pub(crate) fn owner_is_stale(&self, runtime: &Runtime) -> bool {
312 let Some(scope) = self.scope.upgrade() else {
313 return true;
314 };
315 scope
316 .owner
317 .lock()
318 .expect("task scope owner mutex")
319 .is_some_and(|owner| !runtime.tree().element_exists(owner))
320 }
321
322 pub(crate) fn window_id(&self) -> Option<WindowId> {
323 self.scope.upgrade().and_then(|scope| {
324 (*scope.window.lock().expect("task scope window mutex")).or_else(|| {
325 scope
326 .parent
327 .upgrade()
328 .and_then(|parent| *parent.window.lock().expect("task scope window mutex"))
329 })
330 })
331 }
332}
333
334pub type TokioHandle = Handle;
337
338#[derive(Clone)]
340pub struct RuntimeSpawner {
341 scheduler: Weak<std::cell::RefCell<TaskScheduler>>,
342 application_scope: TaskScope,
343 window_id: Option<WindowId>,
344}
345
346impl RuntimeSpawner {
347 pub(crate) const fn window_id(&self) -> Option<WindowId> {
348 self.window_id
349 }
350
351 pub fn spawn<F, T>(&self, future: F) -> Task<T>
354 where
355 F: std::future::Future<Output = T> + Send + 'static,
356 T: Send + 'static,
357 {
358 self.spawn_in(&self.application_scope, future)
359 }
360
361 pub fn spawn_in<F, T>(&self, scope: &TaskScope, future: F) -> Task<T>
362 where
363 F: std::future::Future<Output = T> + Send + 'static,
364 T: Send + 'static,
365 {
366 self.scheduler
367 .upgrade()
368 .expect("runtime spawner outlived runtime")
369 .borrow_mut()
370 .spawn_task(scope, future)
371 }
372
373 pub fn spawn_into<F, T>(
377 &self,
378 future: F,
379 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
380 ) -> TaskHandle
381 where
382 F: std::future::Future<Output = T> + Send + 'static,
383 T: Send + 'static,
384 {
385 self.spawn_into_in(&self.application_scope, future, complete)
386 }
387
388 pub fn spawn_into_in<F, T>(
389 &self,
390 scope: &TaskScope,
391 future: F,
392 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
393 ) -> TaskHandle
394 where
395 F: std::future::Future<Output = T> + Send + 'static,
396 T: Send + 'static,
397 {
398 self.scheduler
399 .upgrade()
400 .expect("runtime spawner outlived runtime")
401 .borrow_mut()
402 .spawn_into(scope, future, complete)
403 }
404
405 pub fn spawn_blocking<T>(
408 &self,
409 work: impl FnOnce() -> T + Send + 'static,
410 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
411 ) -> TaskHandle
412 where
413 T: Send + 'static,
414 {
415 self.spawn_blocking_in(&self.application_scope, work, complete)
416 }
417
418 pub fn spawn_blocking_in<T>(
419 &self,
420 scope: &TaskScope,
421 work: impl FnOnce() -> T + Send + 'static,
422 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
423 ) -> TaskHandle
424 where
425 T: Send + 'static,
426 {
427 self.scheduler
428 .upgrade()
429 .expect("runtime spawner outlived runtime")
430 .borrow_mut()
431 .spawn_blocking_in(scope, work, complete)
432 }
433
434 #[must_use]
435 pub fn scope(&self) -> TaskScope {
436 self.application_scope.child()
437 }
438
439 #[must_use]
440 pub fn tokio_handle(&self) -> TokioHandle {
441 self.scheduler
442 .upgrade()
443 .expect("runtime spawner outlived runtime")
444 .borrow()
445 .tokio_handle()
446 }
447
448 #[must_use]
449 pub fn dispatcher(&self) -> UiDispatcher {
450 self.scheduler
451 .upgrade()
452 .expect("runtime spawner outlived runtime")
453 .borrow()
454 .dispatcher_for(self.window_id)
455 }
456}
457
458#[derive(Clone)]
461pub struct UiDispatcher {
462 bridge: Arc<RuntimeBridge>,
463 window_id: Option<WindowId>,
464}
465
466impl UiDispatcher {
467 pub fn dispatch(&self, callback: impl FnOnce(&mut Runtime) + Send + 'static) {
468 self.bridge.enqueue(RuntimeMessage::Dispatch {
469 window_id: self.window_id,
470 callback: Box::new(callback),
471 });
472 }
473}
474
475pub(crate) type UiCallback = Box<dyn FnOnce(&mut Runtime) + Send>;
476pub(crate) type TaskFinish = Box<dyn FnOnce(&mut Runtime) -> Option<TaskFailure>>;
477pub(crate) type TaskDiscard = Box<dyn FnOnce()>;
478
479enum RuntimeMessage {
480 TaskCompleted(u64),
481 Dispatch {
482 window_id: Option<WindowId>,
483 callback: UiCallback,
484 },
485}
486
487struct RuntimeBridge {
488 sender: mpsc::Sender<RuntimeMessage>,
489 wake: Mutex<Option<Arc<dyn RuntimeWake>>>,
490 accepting: AtomicBool,
491 wake_pending: AtomicBool,
492 queued: AtomicUsize,
493 queue_peak: AtomicUsize,
494 messages_enqueued: AtomicU64,
495 wakes: AtomicU64,
496 coalesced_wakes: AtomicU64,
497}
498
499impl RuntimeBridge {
500 fn enqueue(&self, message: RuntimeMessage) {
501 if !self.accepting.load(Ordering::Acquire) {
502 return;
503 }
504 if self.sender.send(message).is_err() {
505 return;
506 }
507 self.messages_enqueued.fetch_add(1, Ordering::Relaxed);
508 let queued = self.queued.fetch_add(1, Ordering::AcqRel) + 1;
509 self.queue_peak.fetch_max(queued, Ordering::Relaxed);
510 self.request_wake();
511 }
512
513 fn request_wake(&self) {
514 if self.wake_pending.swap(true, Ordering::AcqRel) {
515 self.coalesced_wakes.fetch_add(1, Ordering::Relaxed);
516 return;
517 }
518 self.wakes.fetch_add(1, Ordering::Relaxed);
519 if let Some(wake) = self.wake.lock().expect("runtime wake mutex").as_ref() {
520 wake.wake();
521 }
522 }
523
524 fn set_wake(&self, wake: Arc<dyn RuntimeWake>) {
525 *self.wake.lock().expect("runtime wake mutex") = Some(wake.clone());
526 if self.queued.load(Ordering::Acquire) > 0 {
527 self.wakes.fetch_add(1, Ordering::Relaxed);
528 wake.wake();
529 }
530 }
531}
532
533pub(crate) struct PendingTask {
534 pub(crate) control: Arc<TaskControl>,
535 pub(crate) finish: TaskFinish,
536 pub(crate) discard: TaskDiscard,
537 pub(crate) blocking: bool,
538}
539
540#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
542pub struct RuntimeDiagnostics {
543 pub app_scoped_tasks_spawned: u64,
544 pub owner_scoped_tasks_spawned: u64,
545 pub active_tracked_tasks: usize,
546 pub tasks_completed: u64,
547 pub tasks_cancelled: u64,
548 pub task_join_failures: u64,
549 pub task_panics: u64,
550 pub blocking_jobs_spawned: u64,
551 pub blocking_jobs_completed: u64,
552 pub blocking_results_discarded: u64,
553 pub ui_messages_enqueued: u64,
554 pub ui_messages_processed: u64,
555 pub ui_message_queue_peak: usize,
556 pub winit_runtime_wakes: u64,
557 pub coalesced_wakes: u64,
558 pub stale_completion_rejections: u64,
559}
560
561pub(crate) enum UiWork {
562 Dispatch {
563 window_id: Option<WindowId>,
564 callback: UiCallback,
565 },
566 Task(PendingTask),
567}
568
569pub(crate) struct TaskScheduler {
573 bridge: Arc<RuntimeBridge>,
574 receiver: mpsc::Receiver<RuntimeMessage>,
575 pending: HashMap<u64, PendingTask>,
576 tokio: std::cell::OnceCell<TokioRuntime>,
579 stopped: Option<TokioHandle>,
582 application_scope: TaskScope,
583 next_task: u64,
584 diagnostics: RuntimeDiagnostics,
585}
586
587impl TaskScheduler {
588 pub(crate) fn new() -> Rc<std::cell::RefCell<Self>> {
589 let (sender, receiver) = mpsc::channel();
590 let bridge = Arc::new(RuntimeBridge {
591 sender,
592 wake: Mutex::new(None),
593 accepting: AtomicBool::new(true),
594 wake_pending: AtomicBool::new(false),
595 queued: AtomicUsize::new(0),
596 queue_peak: AtomicUsize::new(0),
597 messages_enqueued: AtomicU64::new(0),
598 wakes: AtomicU64::new(0),
599 coalesced_wakes: AtomicU64::new(0),
600 });
601 Rc::new(std::cell::RefCell::new(Self {
602 bridge,
603 receiver,
604 pending: HashMap::new(),
605 tokio: std::cell::OnceCell::new(),
606 stopped: None,
607 application_scope: TaskScope::new(),
608 next_task: 1,
609 diagnostics: RuntimeDiagnostics::default(),
610 }))
611 }
612
613 pub(crate) fn spawner(scheduler: &Rc<std::cell::RefCell<Self>>) -> RuntimeSpawner {
614 let borrowed = scheduler.borrow();
615 RuntimeSpawner {
616 scheduler: Rc::downgrade(scheduler),
617 application_scope: borrowed.application_scope.clone(),
618 window_id: None,
619 }
620 }
621
622 pub(crate) fn spawner_for(
623 scheduler: &Rc<std::cell::RefCell<Self>>,
624 window_id: WindowId,
625 ) -> RuntimeSpawner {
626 let mut spawner = Self::spawner(scheduler);
627 spawner.window_id = Some(window_id);
628 spawner
629 }
630
631 pub(crate) fn tokio_handle(&self) -> TokioHandle {
632 if let Some(stopped) = &self.stopped {
633 return stopped.clone();
634 }
635 self.tokio
636 .get_or_init(|| {
637 Builder::new_multi_thread()
638 .enable_time()
639 .enable_io()
640 .build()
641 .expect("Tokio runtime construction")
642 })
643 .handle()
644 .clone()
645 }
646
647 fn next_control(&mut self, scope: &TaskScope) -> Arc<TaskControl> {
648 let id = self.next_task;
649 self.next_task = self
650 .next_task
651 .checked_add(1)
652 .expect("runtime task identity space exhausted");
653 let control = Arc::new(TaskControl {
654 id,
655 cancelled: AtomicBool::new(
656 scope.is_cancelled() || !self.bridge.accepting.load(Ordering::Acquire),
657 ),
658 scope: Arc::downgrade(&scope.state),
659 abort: Mutex::new(None),
660 });
661 scope.attach(&control);
662 if !Arc::ptr_eq(&scope.state, &self.application_scope.state) {
666 self.diagnostics.owner_scoped_tasks_spawned += 1;
667 } else {
668 self.diagnostics.app_scoped_tasks_spawned += 1;
669 }
670 control
671 }
672
673 fn spawn_task<F, T>(&mut self, scope: &TaskScope, future: F) -> Task<T>
674 where
675 F: std::future::Future<Output = T> + Send + 'static,
676 T: Send + 'static,
677 {
678 let state = Arc::new(Mutex::new(AsyncValue::Loading));
679 let state_for_finish = state.clone();
680 let state_for_discard = state.clone();
681 let handle = self.spawn_impl(
682 scope,
683 future,
684 move |outcome, _| {
685 let failure = outcome.as_ref().err().cloned();
686 *state_for_finish.lock().expect("task state mutex") = match outcome {
687 Ok(value) => AsyncValue::Ready(value),
688 Err(failure) => AsyncValue::Error(failure),
689 };
690 failure
691 },
692 move || {
693 *state_for_discard.lock().expect("task state mutex") =
694 AsyncValue::Error(TaskFailure::Cancelled);
695 },
696 );
697 Task { state, handle }
698 }
699
700 fn spawn_into<F, T>(
701 &mut self,
702 scope: &TaskScope,
703 future: F,
704 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
705 ) -> TaskHandle
706 where
707 F: std::future::Future<Output = T> + Send + 'static,
708 T: Send + 'static,
709 {
710 self.spawn_impl(
711 scope,
712 future,
713 move |outcome, runtime| {
714 let failure = outcome.as_ref().err().cloned();
715 let callback = panic::catch_unwind(AssertUnwindSafe(|| complete(outcome, runtime)));
716 if callback.is_err() {
717 Some(TaskFailure::Panicked)
718 } else {
719 failure
720 }
721 },
722 || {},
723 )
724 }
725
726 pub(crate) fn spawn_blocking<T>(
727 &mut self,
728 work: impl FnOnce() -> T + Send + 'static,
729 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
730 ) -> TaskHandle
731 where
732 T: Send + 'static,
733 {
734 let scope = self.application_scope.clone();
735 self.spawn_blocking_in(&scope, work, complete)
736 }
737
738 fn spawn_blocking_in<T>(
739 &mut self,
740 scope: &TaskScope,
741 work: impl FnOnce() -> T + Send + 'static,
742 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
743 ) -> TaskHandle
744 where
745 T: Send + 'static,
746 {
747 self.diagnostics.blocking_jobs_spawned += 1;
748 let join = self.tokio_handle().spawn_blocking(work);
749 self.register_join(
750 scope,
751 true,
752 join,
753 move |outcome, runtime| {
754 let failure = outcome.as_ref().err().cloned();
755 let callback = panic::catch_unwind(AssertUnwindSafe(|| complete(outcome, runtime)));
756 if callback.is_err() {
757 Some(TaskFailure::Panicked)
758 } else {
759 failure
760 }
761 },
762 || {},
763 )
764 }
765
766 fn spawn_impl<F, T>(
767 &mut self,
768 scope: &TaskScope,
769 future: F,
770 finish: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) -> Option<TaskFailure> + 'static,
771 discard: impl FnOnce() + 'static,
772 ) -> TaskHandle
773 where
774 F: std::future::Future<Output = T> + Send + 'static,
775 T: Send + 'static,
776 {
777 let join = self.tokio_handle().spawn(future);
778 self.register_join(scope, false, join, finish, discard)
779 }
780
781 fn register_join<T>(
782 &mut self,
783 scope: &TaskScope,
784 blocking: bool,
785 join: JoinHandle<T>,
786 finish: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) -> Option<TaskFailure> + 'static,
787 discard: impl FnOnce() + 'static,
788 ) -> TaskHandle
789 where
790 T: Send + 'static,
791 {
792 let control = self.next_control(scope);
793 let task_handle = TaskHandle {
794 control: control.clone(),
795 };
796 if control.is_cancelled() {
797 join.abort();
798 discard();
799 self.diagnostics.tasks_cancelled += u64::from(!blocking);
800 self.diagnostics.blocking_results_discarded += u64::from(blocking);
801 scope.detach(control.id);
802 return task_handle;
803 }
804
805 let result = Arc::new(Mutex::new(None));
806 let result_for_completion = result.clone();
807 let complete_id = control.id;
808 let bridge = self.bridge.clone();
809 *control.abort.lock().expect("task abort mutex") = Some(join.abort_handle());
810 self.tokio_handle().spawn(async move {
811 let outcome = match join.await {
812 Ok(value) => Ok(value),
813 Err(error) if error.is_cancelled() => Err(TaskFailure::Cancelled),
814 Err(error) if error.is_panic() => Err(TaskFailure::Panicked),
815 Err(_) => Err(TaskFailure::RuntimeStopped),
816 };
817 *result_for_completion.lock().expect("task result mutex") = Some(outcome);
818 bridge.enqueue(RuntimeMessage::TaskCompleted(complete_id));
819 });
820 self.pending.insert(
821 control.id,
822 PendingTask {
823 control,
824 finish: Box::new(move |runtime| {
825 let outcome = result
826 .lock()
827 .expect("task result mutex")
828 .take()
829 .unwrap_or(Err(TaskFailure::RuntimeStopped));
830 finish(outcome, runtime)
831 }),
832 discard: Box::new(discard),
833 blocking,
834 },
835 );
836 task_handle
837 }
838
839 pub(crate) fn take_turn(&mut self) -> Vec<UiWork> {
840 self.bridge.wake_pending.store(false, Ordering::Release);
843 let mut work = Vec::new();
844 let mut processed = 0;
845 while processed < DEFAULT_UI_MESSAGE_BUDGET {
846 let message = match self.receiver.try_recv() {
847 Ok(message) => message,
848 Err(mpsc::TryRecvError::Empty | mpsc::TryRecvError::Disconnected) => break,
849 };
850 processed += 1;
851 self.bridge.queued.fetch_sub(1, Ordering::AcqRel);
852 self.diagnostics.ui_messages_processed += 1;
853 match message {
854 RuntimeMessage::Dispatch {
855 window_id,
856 callback,
857 } => work.push(UiWork::Dispatch {
858 window_id,
859 callback,
860 }),
861 RuntimeMessage::TaskCompleted(id) => {
862 if let Some(pending) = self.pending.remove(&id) {
863 work.push(UiWork::Task(pending));
864 }
865 }
866 }
867 }
868 if processed == DEFAULT_UI_MESSAGE_BUDGET && self.bridge.queued.load(Ordering::Acquire) > 0
869 {
870 self.bridge.request_wake();
871 }
872 work
873 }
874
875 pub(crate) fn accepting(&self) -> bool {
876 self.bridge.accepting.load(Ordering::Acquire)
877 }
878
879 pub(crate) fn detach_scope(&self, control: &TaskControl) {
880 if let Some(scope) = control.scope.upgrade() {
881 TaskScope { state: scope }.detach(control.id);
882 }
883 }
884
885 pub(crate) fn record_discard(&mut self, blocking: bool, stale_owner: bool) {
886 if blocking {
887 self.diagnostics.blocking_results_discarded += 1;
888 } else {
889 self.diagnostics.tasks_cancelled += 1;
890 }
891 if stale_owner {
892 self.diagnostics.stale_completion_rejections += 1;
893 }
894 }
895
896 pub(crate) fn record_completion(
897 &mut self,
898 blocking: bool,
899 failure: Option<TaskFailure>,
900 ) -> Option<TaskFailure> {
901 if blocking {
902 self.diagnostics.blocking_jobs_completed += 1;
903 } else {
904 self.diagnostics.tasks_completed += 1;
905 }
906 match failure {
907 Some(TaskFailure::Cancelled) => {
908 self.diagnostics.tasks_cancelled += 1;
909 None
910 }
911 Some(TaskFailure::Panicked) => {
912 self.diagnostics.task_join_failures += 1;
913 self.diagnostics.task_panics += 1;
914 Some(TaskFailure::Panicked)
915 }
916 Some(TaskFailure::RuntimeStopped) => {
917 self.diagnostics.task_join_failures += 1;
918 None
919 }
920 None => None,
921 }
922 }
923
924 pub(crate) fn dispatcher_for(&self, window_id: Option<WindowId>) -> UiDispatcher {
925 UiDispatcher {
926 bridge: self.bridge.clone(),
927 window_id,
928 }
929 }
930
931 pub(crate) fn set_wake(&self, wake: Arc<dyn RuntimeWake>) {
932 self.bridge.set_wake(wake);
933 }
934
935 pub(crate) fn shutdown(&mut self) {
936 if !self.bridge.accepting.swap(false, Ordering::AcqRel) {
937 return;
938 }
939 self.application_scope.cancel();
940 let pending = std::mem::take(&mut self.pending);
941 for (_, task) in pending {
942 task.control.cancel();
943 (task.discard)();
944 if task.blocking {
945 self.diagnostics.blocking_results_discarded += 1;
946 } else {
947 self.diagnostics.tasks_cancelled += 1;
948 }
949 }
950 while self.receiver.try_recv().is_ok() {
951 self.bridge.queued.fetch_sub(1, Ordering::AcqRel);
952 }
953 let tokio = self.tokio.take().unwrap_or_else(|| {
956 Builder::new_current_thread()
957 .build()
958 .expect("Tokio runtime construction")
959 });
960 self.stopped = Some(tokio.handle().clone());
961 tokio.shutdown_timeout(SHUTDOWN_TIMEOUT);
964 }
965
966 pub(crate) fn diagnostics(&self) -> RuntimeDiagnostics {
967 let mut diagnostics = self.diagnostics;
968 diagnostics.active_tracked_tasks = self.pending.len();
969 diagnostics.ui_messages_enqueued = self.bridge.messages_enqueued.load(Ordering::Relaxed);
970 diagnostics.ui_message_queue_peak = self.bridge.queue_peak.load(Ordering::Relaxed);
971 diagnostics.winit_runtime_wakes = self.bridge.wakes.load(Ordering::Relaxed);
972 diagnostics.coalesced_wakes = self.bridge.coalesced_wakes.load(Ordering::Relaxed);
973 diagnostics
974 }
975
976 pub(crate) fn active_ownership(&self) -> (usize, usize) {
977 self.pending.values().fold((0, 0), |(app, owner), task| {
978 let application_owned = task
979 .control
980 .scope
981 .upgrade()
982 .is_some_and(|scope| Arc::ptr_eq(&scope, &self.application_scope.state));
983 if application_owned {
984 (app + 1, owner)
985 } else {
986 (app, owner + 1)
987 }
988 })
989 }
990}
991
992impl Drop for TaskScheduler {
993 fn drop(&mut self) {
994 self.shutdown();
997 }
998}