Merge pull request #1223 from DioxusLabs/jk/fix-use-future

Fix race condition in use_future by using use_state directly.
This commit is contained in:
Jonathan Kelley 2023-07-19 20:01:15 -07:00 committed by GitHub
commit 79909060cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,12 +1,8 @@
#![allow(missing_docs)]
use dioxus_core::{ScopeState, TaskId};
use std::{
any::Any,
cell::{Cell, RefCell},
future::{Future, IntoFuture},
rc::Rc,
sync::Arc,
};
use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
use crate::{use_state, UseState};
/// A future that resolves to a value.
///
@ -31,43 +27,30 @@ where
F: Future<Output = T> + 'static,
D: UseFutureDep,
{
let val = use_state(cx, || None);
let state = cx.use_hook(move || UseFuture {
update: cx.schedule_update(),
needs_regen: Cell::new(true),
values: Default::default(),
task: Cell::new(None),
state: val.clone(),
task: Default::default(),
dependencies: Vec::new(),
waker: Default::default(),
});
*state.waker.borrow_mut() = None;
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
// We don't need regen anymore
state.needs_regen.set(false);
// kill the old one, if it exists
if let Some(task) = state.task.take() {
cx.remove_future(task);
}
// Create the new future
let fut = future(dependencies.out());
// Clone in our cells
let values = state.values.clone();
let schedule_update = state.update.clone();
let waker = state.waker.clone();
// Cancel the current future
if let Some(current) = state.task.take() {
cx.remove_future(current);
}
let val = val.clone();
let task = state.task.clone();
state.task.set(Some(cx.push_future(async move {
let res = fut.await;
values.borrow_mut().push(Box::leak(Box::new(res)));
// if there's a waker, we dont re-render the component. Instead we just progress that future
match waker.borrow().as_ref() {
Some(waker) => waker.wake_by_ref(),
None => schedule_update(),
}
val.set(Some(fut.await));
task.take();
})));
}
@ -80,21 +63,12 @@ pub enum FutureState<'a, T> {
Regenerating(&'a T), // the old value
}
pub struct UseFuture<T> {
pub struct UseFuture<T: 'static> {
update: Arc<dyn Fn()>,
needs_regen: Cell<bool>,
task: Cell<Option<TaskId>>,
task: Rc<Cell<Option<TaskId>>>,
dependencies: Vec<Box<dyn Any>>,
waker: Rc<RefCell<Option<std::task::Waker>>>,
values: Rc<RefCell<Vec<*mut T>>>,
}
impl<T> Drop for UseFuture<T> {
fn drop(&mut self) {
for value in self.values.take().into_iter() {
drop(unsafe { Box::from_raw(value) })
}
}
state: UseState<Option<T>>,
}
pub enum UseFutureState<'a, T> {
@ -120,30 +94,16 @@ impl<T> UseFuture<T> {
}
}
// clears the value in the future slot without starting the future over
pub fn clear(&self) -> Option<T> {
todo!()
// (self.update)();
// self.slot.replace(None)
}
// Manually set the value in the future slot without starting the future over
pub fn set(&self, _new_value: T) {
// self.slot.set(Some(new_value));
// self.needs_regen.set(true);
// (self.update)();
todo!()
pub fn set(&self, new_value: T) {
self.state.set(Some(new_value));
}
/// Return any value, even old values if the future has not yet resolved.
///
/// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<&T> {
self.values
.borrow_mut()
.last()
.cloned()
.map(|x| unsafe { &*x })
self.state.current_val.as_ref().as_ref()
}
/// Get the ID of the future in Dioxus' internal scheduler
@ -169,35 +129,6 @@ impl<T> UseFuture<T> {
}
}
impl<'a, T> IntoFuture for &'a UseFuture<T> {
type Output = &'a T;
type IntoFuture = UseFutureAwait<'a, T>;
fn into_future(self) -> Self::IntoFuture {
UseFutureAwait { hook: self }
}
}
pub struct UseFutureAwait<'a, T> {
hook: &'a UseFuture<T>,
}
impl<'a, T> Future for UseFutureAwait<'a, T> {
type Output = &'a T;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.hook.values.borrow_mut().last().cloned() {
Some(value) => std::task::Poll::Ready(unsafe { &*value }),
None => {
self.hook.waker.replace(Some(cx.waker().clone()));
std::task::Poll::Pending
}
}
}
}
pub trait UseFutureDep: Sized + Clone {
type Out;
fn out(&self) -> Self::Out;
@ -343,10 +274,6 @@ mod tests {
let blah = "asd";
});
let g2 = a.await;
let g = fut.await;
todo!()
}
}