zng_task/ui.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
//! UI-thread bound tasks.
use std::{
fmt,
future::{Future, IntoFuture},
mem,
pin::Pin,
task::{Poll, Waker},
};
enum UiTaskState<R> {
Pending {
future: Pin<Box<dyn Future<Output = R> + Send>>,
event_loop_waker: Waker,
#[cfg(debug_assertions)]
last_update: Option<zng_var::VarUpdateId>,
},
Ready(R),
Cancelled,
}
impl<R: fmt::Debug> fmt::Debug for UiTaskState<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pending { .. } => write!(f, "Pending"),
Self::Ready(arg0) => f.debug_tuple("Ready").field(arg0).finish(),
Self::Cancelled => unreachable!(),
}
}
}
/// Represents a [`Future`] running in sync with the UI.
///
/// The future [`Waker`], wakes the app event loop and causes an update, in an update handler
/// of the task owner [`update`] is called, if this task waked the app the future is polled once.
///
/// [`Waker`]: std::task::Waker
/// [`update`]: UiTask::update
#[derive(Debug)]
pub struct UiTask<R>(UiTaskState<R>);
impl<R> UiTask<R> {
/// New task with already build event-loop waker.
///
/// App crate provides an integrated `UiTaskWidget::new` that creates the waker for widgets.
pub fn new_raw<F>(event_loop_waker: Waker, task: impl IntoFuture<IntoFuture = F>) -> Self
where
F: Future<Output = R> + Send + 'static,
{
UiTask(UiTaskState::Pending {
future: Box::pin(task.into_future()),
event_loop_waker,
#[cfg(debug_assertions)]
last_update: None,
})
}
/// Polls the future if needed, returns a reference to the result if the task is done.
///
/// This does not poll the future if the task is done.
///
/// # App Update
///
/// This method must be called only once per app update, if it is called more than once it will cause **execution bugs**,
/// futures like [`task::yield_now`] will not work correctly, variables will have old values when a new one
/// is expected and any other number of hard to debug issues will crop-up.
///
/// In debug builds this is validated and an error message is logged if incorrect updates are detected.
///
/// [`task::yield_now`]: crate::yield_now
pub fn update(&mut self) -> Option<&R> {
if let UiTaskState::Pending {
future,
event_loop_waker,
#[cfg(debug_assertions)]
last_update,
..
} = &mut self.0
{
#[cfg(debug_assertions)]
{
let update = Some(zng_var::VARS.update_id());
if *last_update == update {
tracing::error!("UiTask::update called twice in the same update");
}
*last_update = update;
}
if let Poll::Ready(r) = future.as_mut().poll(&mut std::task::Context::from_waker(event_loop_waker)) {
self.0 = UiTaskState::Ready(r);
}
}
if let UiTaskState::Ready(r) = &self.0 {
Some(r)
} else {
None
}
}
/// Returns `true` if the task is done.
///
/// This does not poll the future.
pub fn is_ready(&self) -> bool {
matches!(&self.0, UiTaskState::Ready(_))
}
/// Returns the result if the task is completed.
///
/// This does not poll the future, you must call [`update`] to poll until a result is available,
/// then call this method to take ownership of the result.
///
/// [`update`]: Self::update
pub fn into_result(mut self) -> Result<R, Self> {
match mem::replace(&mut self.0, UiTaskState::Cancelled) {
UiTaskState::Ready(r) => Ok(r),
p @ UiTaskState::Pending { .. } => Err(Self(p)),
UiTaskState::Cancelled => unreachable!(),
}
}
/// Drop the task without logging a warning if it is pending.
pub fn cancel(mut self) {
self.0 = UiTaskState::Cancelled;
}
}
impl<R> Drop for UiTask<R> {
fn drop(&mut self) {
if let UiTaskState::Pending { .. } = &self.0 {
#[cfg(debug_assertions)]
{
tracing::warn!("pending UiTask<{}> dropped", std::any::type_name::<R>());
}
#[cfg(not(debug_assertions))]
{
tracing::warn!("pending UiTask dropped");
}
}
}
}