gaaclarke / agents

Dart package that conveniently wraps an isolate, ports and state for easy isolates.
BSD 3-Clause "New" or "Revised" License
79 stars 7 forks source link

add async Stream<E> getStream() method and switch command enum test to switch/case #8

Open willp opened 5 months ago

willp commented 5 months ago

This adds a new method, getStream() that returns a Stream<E> that will iterate on values as they are produced by a closure running on the background agent/isolate. If the stream throws an exception in the background agent, the exception is captured and set in the agent.error's e and stacktrace variables, and the iteration terminates.

Multiple async streams can be iterated on simultaneously, and the streams can produce any type <E>, not only the state's <T> type.

I think I covered the basics in the new tests, including one test that confirms values are being produced "live" using a stopwatch to measure the delay between values from a Stream<_>.periodic(100 milliseconds) to ensure it's actually generating values live from the agent back to the parent, not just collecting everything at once. :grin:

I'm new to Dart (and flutter) and I'm exploring the different packages for managing long-lived isolates for my first app. Thanks for this package, it's nicely done. I hope this PR is useful. Let me know if you'd like any changes in this unsolicited PR..

willp commented 5 months ago

Thanks for taking a look. I see what you mean about getStream not using the state at all... What would you think about a signature like: Stream<E> getStream<E>(Stream<E> Function(T) func) async* where the implementation invokes the function by passing in the state, like Stream<dynamic> stream = func(state); ?

This would allow for an Actor that stores state of type T, and then to emit a Stream<E> where E is derived from T state by code implemented in the actor, like prime factorizations, geometric sequences, taylor expansions, power series, newton's method iterative approximations, etc.

I'll understand if this isn't a direction you want to take, and appreciate your time in responding. I've already updated the diff (locally) to implement this, but won't submit it without hearing your thoughts first. I'm still learning how Dart Streams and Isolates work, so it's been fun to test my knowledge within your package. I appreciate your time, (and I apologize if this is a fruitless direction.)

Thanks again!

gaaclarke commented 5 months ago

What would you think about a signature like: Stream<E> getStream<E>(Stream<E> Function(T) func) async* where the implementation invokes the function by passing in the state, like Stream<dynamic> stream = func(state); ?

Someone could easily do that themselves today with Dart async generators. There's no need to edit the Agents for that =)

Stream<int> streamInc(Agent<int> agent) async* {
  while (true) {
    agent.update((x) => x + 1);
    yield await agent.read();
  }
}
petri-lipponen-movesense commented 1 month ago

I think this PR is close to what I was looking for:

I like the agents style of abstracting away the nastines of Isolates. However, I was wondering how to pass progress messages from the "single run isolate func" back to caller, and this stream would probably fit the bill nicely. In my case the single run isolate func is a long running file format conversion code. I'm not sure the code below would fit the bill, since the progress would have to be "sent" from within the long running update()

Stream<int> streamInc(Agent<int> agent) async* {
  while (true) {
    agent.update((x) => x + 1);
    yield await agent.read();
  }
}

Since I'm quite a newbie when it comes to flutter async streams with isolates, I'm not 100% sure this PR fits the bill either, so any advice?

gaaclarke commented 1 month ago

In my case the single run isolate func is a long running file format conversion code. I'm not sure the code below would fit the bill, since the progress would have to be "sent" from within the long running update()

You could have the state for the agent be the number representing the progress. In the UI you can have an animation that is polling that state. I think that would be ideal. Otherwise, I think you might need to send the agent a stream to communicate back to the main isolate.