How to combine tasks

I’ve got an iced app whose update function looks roughly like this:

struct UI {
  pub fn update(&mut self, message: Message) -> Task<Message> {
    let task_a = function_a(message);
    let task_b = function_b(message);

    let task_c = match message {
      Message::Foo => {
        // do something
        Task::done(Message::Bar)
      }
      Message::Baz => {
        // do something else
        Task::none()
      }
    }

    // Chain the tasks together and return

The problem comes with chaining the tasks. From the source for version 0.13.2, the chain method looks like this:

    /// Chains a new [`Task`] to be performed once the current one finishes completely.
    pub fn chain(self, task: Self) -> Self
    where
        T: 'static,
    {
        match self.0 {
            None => task,
            Some(first) => match task.0 {
                None => Task::none(),
                Some(second) => Task(Some(boxed_stream(first.chain(second)))),
            },
        }
    }

which, if I’m reading this right, means that having Task::none().chain(Task::done(Message::Foo)) will return Task::done(Message::Foo), which makes sense, but Task::done(Message::Foo).chain(Task::none) will just return Task::none(), which is what I’m seeing.

What I need is a way to aggregate a set of tasks in such a way as to run the non-none() tasks in sequence, but skip over the Task::none()s in the set.

FWIW, I did try just returning Option<Task<Message>> from my functions, and then putting the tasks in a Vec, and then flattening the vec and turning it into a Stream to pass into Task::stream, but, since Tasks aren’t cloneable, I couldn’t find a way to get the Tasks out of the vec and into the stream.

Any help would be greatly appreciated!!

-Jack

Use Task.batch.

Oh, wait, no, you mentioned that you wanted to run them in sequence, not in parallel. In that case chain is what you want. Can’t you just rewrite your logic so that the chaining occurs inside the match arms and then you don’t chain Task::none() at all?

This has been fixed on the master branch: iced/runtime/src/task.rs at master · iced-rs/iced · GitHub

As for how you could chain them on 0.13… Well, by depending on tokio-stream, you can do the following:

let task_a = Task::done(Message::X);
let task_b = Task::none();
let task_c = Task::done(Message::X);

let task_d = Task::stream(tokio_stream::iter(
    [task_a, task_b, task_c]
        .into_iter()
        .filter(|t| t.units() > 0)
        .collect::<Vec<_>>(),
));

Edit: no need to create a Vec initially

Tried that, but there’s no way to match against Task::none(). It’d be nice if there was even an is_none() for it, but no such luck.

Thanks, though!

Yep, saw that. However, when I try to build from HEAD of master, it breaks a whole lot of things in my code. Tried hunting them down, but there were one or two which seemed like incompatibilities between that version of iced and iced_aw, so…kinda gave up.

That’s lovely, but, unfortunately I run into the same thing I had with @lufte’s post - there’s no way to identify a Task as being Task::none in 0.13 (That nifty t.units() is, I’m assuming, from the HEAD of master).

I dunno, this is kinda frustrating, because the reason I did this was to be able to refactor the code so that my update() function didn’t take up ~1000 lines (well, that’s an exaggeration, but not by much). It was getting to the point where it was getting pretty hard to reason about, and refactoring helped A LOT.

I think maybe I’m gonna have to wait until they cut a new version of iced, and hope that the API changes aren’t too painful.

Thanks, though, both of you, for the help! I really appreciate it.

-Jack

The code snippet I wrote, although working on master, was specifically a temporary fix for while you’re still using 0.13

As for the iced_aw incompatibilities, that happens. People depending on crates that themselves depend on iced or one of its subcrates either have to hope that it’s updated for the latest version, or do it themselves after forking said crate.

1 Like

Huh, I don’t see a units() function in the Task docs, and the compiler also says error[E0599]: no method named unitsfound for reference&Task in the current scope.

Is there maybe a trait I need to add to see it?

-Jack

I mean even before that. Something like this:

    let mut task = function_a(message).chain(function_b(message));

    match message {
      Message::Foo => {
        // do something
        task = task.chain(Task::done(Message::Bar));
      }
      Message::Baz => {}
    }

It seems to be the case that Task::units is also master only… As such, you have to either wait, or redesign your code so that Task::nones simply aren’t part of the chain