/*:#localized(key: "CleaningUpOnTermination")
 # Cleaning up with onTermination

 A real sensor needs to be switched off when nobody's listening anymore; otherwise
 it keeps sampling (and draining the battery) forever. The continuation gives you a
 hook for exactly that: **`onTermination` runs once when the stream ends**, whether
 it finished normally or the consumer walked away.

 This version streams readings indefinitely, and powers the sensor down on
 termination.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

func rooftopReadings() -> AsyncStream<Double> {
    AsyncStream { continuation in
        let producer = Task {
            while !Task.isCancelled {
                try? await Task.sleep(for: .milliseconds(300))
                continuation.yield(Double.random(in: 18...26))
            }
        }

        continuation.onTermination = { reason in
            producer.cancel()   // stop sampling
            switch reason {
            case .cancelled:
                print("🧹 Consumer left: sensor powered down")
            case .finished:
                print("🧹 Stream finished: sensor powered down")
            @unknown default:
                break
            }
        }
    }
}

let consumerTask = Task {
    var count = 0
    for await celsius in rooftopReadings() {
        print("🌡️ \(celsius.rounded())°C")
        count += 1
        if count == 3 { break }   // consumer decides it's had enough
    }
    print("Loop exited after \(count) readings")
}

// Wait for the consumer to finish (and its cleanup to run), then stop the page.
Task {
    _ = await consumerTask.value
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "CleaningUpOnTermination-2")
 **Watch it run:** three readings print, then the loop `break`s. Leaving the loop
 tears down the stream, so `onTermination` fires with `.cancelled`, prints
 `Consumer left: sensor powered down`, and cancels the inner producer task. Without
 that hook, the `while` loop would sample forever in the background even though no
 one is reading it: a classic leak.

 `onTermination` is your cleanup drawer: cancel timers, close files, unregister
 observers. Set it up as soon as you have the continuation, so it's ready no matter
 how the stream ends.

 * callout(Aside): The `onTermination` closure must be `@Sendable`. It may run from a
 different task than the one that created the stream, so it can only touch things
 that are safe to share (like the `Task` handle we cancel here). Swift 6 will flag it
 if you reach for something that isn't.

 [Previous](@previous)  |  page 4 of 7  |  [Next: Cancellation Stops Consumption](@next)
 */
