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