How to break `while` loop and `deliver partial result to `View`?

I make some small program to make dots. Many of them. I have a Generator which generates dots in a loop:

        //reprat until all dots in frame
        while !newDots.isEmpty {
            virginDots = []
            for newDot in newDots {
                autoreleasepool{
                    virginDots.append(
                        contentsOf: newDot.addDots(in: size,  allDots: &result, inSomeWay))
                }
                newDots = virginDots
            }
            counter += 1
            print ("\(result.count) dots  in \(counter) grnerations")
        }

Sometimes this loop needs hours/days to finish (depend of inSomeWay settings), so it would be very nice to send partial result to a View, and/or if result is not satisfying — break this loop and start over.

My understanding of Tasks and Concurrency became worse each time I try to understand it, maybe it's my age, maybe language barier. For now, Button with {Task {...}} action doesn't removed Rainbow Wheel from my screen. Killing an app is wrong because killing is wrong.

How to deal with it?

You don't show how the view uses the result. That should help.

I would do it with recursively calling iteration steps.

In the View, as an action for a button, call

simulation()

This function is defined as

    func simulation() {
        
        let numberOfStep = 100000 // any very large number        
        runSimulation(in: stopped.atStep..<numberOfStep) 
}

The trick is that in runSimulation(), you call calls it recursively

    func runSimulation<T: Sequence>(in sequence: T) where T.Element == Int {
    // execute one step of the loop
   runSimulation(in: sequence.dropFirst())     // call recursively
}

stopped is a state var

    @State var stopped   : (on: Bool, atStep: Int) = (on: false, atStep: 0)   

atStep is the value (loop index) at which you may have stopped with another button action in the view, which let's you resume where you stopped if you want with a resume button.

Note that in your case, you could do it by chunks of 100 steps instead of individual steps, so the number of steps would be 1000 instead of 100000 for instance.

Much more detailed explanation here: https://stackoverflow.com/questions/64202210/dispatchqueue-main-asyncafter-not-delaying

Hope that helps.

For now like this, in a simplest way

struct ContentView: View {
      @ObservedObject var manager = Manager()
       var body: some View {
       ...
          VStack {
                ScrollView([.horizontal, .vertical]) {
                    DotTestView(dots: $manager.dots)
                        .frame(width: manager.size.width,
                               height: manager.size.height)
                }
                HStack {
                    Button(action: {
                        Task {
                            await manager.updateDots(in: manager.size)
                        }}, 
                           label: {Text("start")})
                    Button(action: {
                        print ("stop")}, 
                           label: {Text("How to Stop")})
                }
            }
        ...
    }
}


Manager keeps all configuration data, has @Published var dots:[Dot]. Simple as that.

And a problem is that Rainbow Wheel does not allow me to touch any Button. I suspect:

@MainActor
public struct DotGenerator {....}

but I don't know how to make it different way in Swift6

It is difficult to guess the overall structure of your code.

Is DotGenerator calling ContentView ?

Did you look at the solution I proposed ?

The best way to tackle this depends on the nature of your dot generation code. There are two possibilities:

  • It can easily save its state and then pick up based on that saved state.

  • It has a complex internal state that’s hard to save.

Let me talk about each in turn.


In the first case, you could have the main actor start an async function to generate the next group of results based on the saved state. That function then returns both the group of results and an updated saved state. The main actor then applies the group of results and, if it so desires, calls the async function again to generate the next group of results.

So, the async function might look like this:

func nextGroupOfResults(inputs: Inputs, state: MyState?) async -> ([ResultGroup], MyState) {
    …
}

struct Inputs { }
struct ResultGroup { }
struct MyState { }

and you might call it like this:

while true {
    let inputs = Inputs()
    let (resultGroup, newState) = await nextGroupOfResults(inputs: inputs, state: self.state)
    … apply result group …
    self.state = newState
}

If the dot generation is unable to save its state easily, you can switch to a generation model. In that case you might have a routine like this:

func results(for inputs: Inputs) -> AsyncStream<ResultGroup> {
    …
}

struct Inputs { }
struct ResultGroup { }

and then your main actor task would iterate over the result groups like this:

let inputs = Inputs()
for await resultGroup in results(for: inputs) {
    … apply result group …
}

That raises the question of what to put in results(for:). Here’s how you might do that:

func results(for inputs: Inputs) -> AsyncStream<ResultGroup> {
    let (stream, continuation) = AsyncStream.makeStream(of: ResultGroup.self, bufferingPolicy: .unbounded)
    Task {
        await generateResultGroups(continuation: continuation)
    }
    return stream
}

func generateResultGroups(continuation: AsyncStream<ResultGroup>.Continuation) async {
    … call `continuation.yield(_:)` to return a result group …
    … or `continuation.finish()` to be done …
}

The tricky back of this is flow control. If the generateResultGroups(…) routine can create result groups faster than the main actor can display them, a lot of data can back up in the stream. It’s possible to solve that, but it’s definitely more challenging.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

How to break `while` loop and `deliver partial result to `View`?
 
 
Q