~raph/crochet

de6d9ecbca8cbd1197ac250ab429f77f93620368 — Raph Levien 1 year, 7 months ago 8e0288c
Add data to use_future

Now it's not one-shot, but will resubmit when the data changes.
5 files changed, 63 insertions(+), 27 deletions(-)

M examples/async.rs
M src/any_widget.rs
M src/app_holder.rs
M src/cx.rs
M src/tree.rs
M examples/async.rs => examples/async.rs +14 -4
@@ 2,7 2,7 @@

use druid::{AppLauncher, PlatformError, Widget, WindowDesc};

use crochet::{AppHolder, Cx, DruidAppData, Label};
use crochet::{AppHolder, Button, Cx, DruidAppData, Label};

fn main() -> Result<(), PlatformError> {
    let main_window = WindowDesc::new(ui_builder);


@@ 13,14 13,24 @@ fn main() -> Result<(), PlatformError> {
}

#[derive(Default)]
struct MyAppLogic;
struct MyAppLogic {
    count: usize,
}

impl MyAppLogic {
    fn run(&mut self, cx: &mut Cx) {
        Label::new(format!("current count: {}", self.count)).build(cx);
        if Button::new("Increment").build(cx) {
            self.count += 1;
        }
        if self.count > 3 && self.count < 6 {
            Label::new("You did it!").build(cx);
        }
        cx.use_future(
            || async {
            self.count,
            |&val| async move {
                async_std::task::sleep(std::time::Duration::from_secs(1)).await;
                42
                val * 2
            },
            |cx, result| {
                let text = if let Some(val) = result {

M src/any_widget.rs => src/any_widget.rs +1 -1
@@ 140,7 140,7 @@ impl AnyWidget {
                widget.mutate_update(ctx, None, mut_iter);
                widget
            }
            Payload::State(_) | Payload::Future => {
            Payload::State(_) | Payload::Future(..) => {
                // Here we assume that the state node has exactly one
                // child. Not awesome but it simplifies prototyping.
                if let Some(MutIterItem::Insert(id, body, iter)) = mut_iter.next() {

M src/app_holder.rs => src/app_holder.rs +3 -3
@@ 9,7 9,7 @@ use crate::any_widget::{Action, AnyWidget, DruidAppData};
use crate::state::State;
use crate::{Cx, Id, MutationIter, Tree};

pub const ASYNC: Selector<SingleUse<(Id, Box<dyn State>)>> = Selector::new("crochet.async");
pub const ASYNC: Selector<SingleUse<(Id, Id, Box<dyn State>)>> = Selector::new("crochet.async");

/// A container for a user application.
///


@@ 71,8 71,8 @@ impl Widget<DruidAppData> for AppHolder {
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut DruidAppData, env: &Env) {
        if let Event::Command(cmd) = event {
            if let Some(payload) = cmd.get(ASYNC) {
                if let Some((id, val)) = payload.take() {
                    self.resolved_futures.insert(id, val);
                if let Some((id, f_id, val)) = payload.take() {
                    self.resolved_futures.insert(f_id, val);
                    data.queue_action(id, Action::FutureResolved);
                }
            }

M src/cx.rs => src/cx.rs +44 -18
@@ 125,42 125,68 @@ impl<'a> Cx<'a> {
        result
    }

    /// Spawn a future when first inserted.
    /// Spawn a future when the data changes.
    ///
    /// When this element is first inserted, call `future_cb` and spawn
    /// the returned future.
    /// When the data changes (including first insert), call `future_cb` and
    /// spawn the returned future.
    ///
    /// The value of the future is then made available to the main body
    /// callback.
    #[cfg(feature = "async-std")]
    #[track_caller]
    pub fn use_future<T: Send + 'static, U, F, FC>(
    pub fn use_future<T, U, V, F, FC>(
        &mut self,
        data: T,
        future_cb: FC,
        f: impl FnOnce(&mut Cx, Option<&T>) -> U,
    ) -> U
        f: impl FnOnce(&mut Cx, Option<&U>) -> V,
    ) -> V
    where
        FC: FnOnce() -> F,
        F: Future<Output = T> + Send + 'static,
        T: State + PartialEq + 'static,
        T: State + PartialEq + Clone + 'static,
        FC: FnOnce(&T) -> F,
        // Note: we can remove State bound
        U: Send + State + 'static,
        F: Future<Output = U> + Send + 'static,
    {
        let key = self.mut_cursor.key_from_loc(Location::caller());
        let (id, is_insert) = self.mut_cursor.begin_core(key, |id, old_body| {
            if let Some(Payload::Future) = old_body {
                (None, (id, false))
        // Note: ideally we want to remove this clone, but it's tricky.
        // After the `begin_core`, either there was no change, in which
        // case two potentially usable copies, the `data` argument and
        // the copy in the tree (which are equal), or there was a change,
        // in which case it was moved into the mutation.
        //
        // So I think it's possible, but will require very advanced
        // fighting with the borrow checker, possibly even unsafe. To
        // avoid dealing with that now, just clone.
        let data_clone = data.clone();
        let (id, f_id, changed) = self.mut_cursor.begin_core(key, |id, old_body| {
            if let Some(Payload::Future(f_id, old_data)) = old_body {
                if let Some(old_data) = old_data.as_any().downcast_ref::<T>() {
                    if old_data == &data {
                        (None, (id, *f_id, false))
                    } else {
                        // Types match, data not equal
                        let f_id = Id::new();
                        (Some(Payload::Future(f_id, Box::new(data))), (id, f_id, true))
                    }
                } else {
                    // Downcast failed; this shouldn't happen
                    let f_id = Id::new();
                    (Some(Payload::Future(f_id, Box::new(data))), (id, f_id, true))
                }
            } else {
                // Inserting a new future
                (Some(Payload::Future), (id, true))
                // Probably inserting new state
                let f_id = Id::new();
                (Some(Payload::Future(f_id, Box::new(data))), (id, f_id, true))
            }
        });
        if is_insert {
        if changed {
            // Spawn the future.
            let future = future_cb();
            let future = future_cb(&data_clone);
            let sink = self.event_sink.clone();
            let boxed_future = Box::pin(async move {
                let result = future.await;
                let boxed_result: Box<dyn State> = Box::new(result);
                let payload = (id, boxed_result);
                let payload = (id, f_id, boxed_result);
                if let Err(e) = sink.submit_command(ASYNC, SingleUse::new(payload), Target::Auto) {
                    println!("error {:?} submitting", e);
                }


@@ 171,7 197,7 @@ impl<'a> Cx<'a> {
        let _ = self.app_data.dequeue_action(id);
        let future_result = self
            .resolved_futures
            .get(&id)
            .get(&f_id)
            .and_then(|result| result.as_any().downcast_ref());
        let result = f(self, future_result);
        self.mut_cursor.end();

M src/tree.rs => src/tree.rs +1 -1
@@ 10,7 10,7 @@ use crate::view::View;
/// The payload of an item in the tree.
#[derive(Debug)]
pub enum Payload {
    Future,
    Future(Id, Box<dyn State>),
    State(Box<dyn State>),
    View(Box<dyn View>),
}