Skip to content

Move AnyAsyncSequenceWorker into WorkflowConcurrency as AsyncSequenceWorker #353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions WorkflowConcurrency/Sources/AsyncSequenceWorker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Foundation
import Workflow

Check warning on line 2 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 18.1]

add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Workflow'

Check warning on line 2 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 16.4]

add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Workflow'

Check warning on line 2 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 17.5]

add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Workflow'

/// Workers define a unit of asynchronous work.
///
/// During a render pass, a workflow can ask the context to await the result of a worker.
///
/// When this occurs, the context checks to see if there is already a running worker of the same type.
/// If there is, and if the workers are 'equivalent', the context leaves the existing worker running.
///
/// If there is not an existing worker of this type, the context will kick off the new worker (via `run`).
public protocol AsyncSequenceWorker<Output>: AnyWorkflowConvertible where Rendering == Void {
/// The type of output events returned by this worker.
associatedtype Output

// In iOS 18+ we can do:
// func run() -> any AsyncSequence<Output, Never>
// And then remove the casting in the side effect

/// Returns an `AsyncSequence` to execute the work represented by this worker.
func run() -> any AsyncSequence
/// Returns `true` if the other worker should be considered equivalent to `self`. Equivalence should take into
/// account whatever data is meaningful to the task. For example, a worker that loads a user account from a server
/// would not be equivalent to another worker with a different user ID.
func isEquivalent(to otherWorker: Self) -> Bool
}

extension AsyncSequenceWorker {
public func asAnyWorkflow() -> AnyWorkflow<Void, Output> {
AsyncSequenceWorkerWorkflow(worker: self).asAnyWorkflow()
}
}

struct AsyncSequenceWorkerWorkflow<WorkerType: AsyncSequenceWorker>: Workflow {
let worker: WorkerType

typealias Output = WorkerType.Output
typealias Rendering = Void
typealias State = UUID

func makeInitialState() -> State { UUID() }

func workflowDidChange(from previousWorkflow: AsyncSequenceWorkerWorkflow<WorkerType>, state: inout UUID) {
if !worker.isEquivalent(to: previousWorkflow.worker) {
state = UUID()
}
}

func render(state: State, context: RenderContext<AsyncSequenceWorkerWorkflow>) -> Rendering {
let sink = context.makeSink(of: AnyWorkflowAction.self)
context.runSideEffect(key: state) { lifetime in
let task = Task {
for try await output in worker.run() {
// Not necessary in iOS 18+ once we can use AsyncSequence<Output, Never>
guard let output = output as? Output else {
fatalError("Unexpected output type \(type(of: output)) from worker \(worker)")
}
await MainActor.run {
sink.send(AnyWorkflowAction(sendingOutput: output))

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 18.1]

capture of 'output' with non-sendable type 'AsyncSequenceWorkerWorkflow<WorkerType>.Output' (aka 'WorkerType.Output') in a `@Sendable` closure; this is an error in the Swift 6 language mode

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 18.1]

capture of 'sink' with non-sendable type 'Sink<AnyWorkflowAction<AsyncSequenceWorkerWorkflow<WorkerType>>>' in a `@Sendable` closure; this is an error in the Swift 6 language mode

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 16.4]

capture of 'output' with non-sendable type 'AsyncSequenceWorkerWorkflow<WorkerType>.Output' (aka 'WorkerType.Output') in a `@Sendable` closure; this is an error in the Swift 6 language mode

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 16.4]

capture of 'sink' with non-sendable type 'Sink<AnyWorkflowAction<AsyncSequenceWorkerWorkflow<WorkerType>>>' in a `@Sendable` closure; this is an error in the Swift 6 language mode

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 17.5]

capture of 'output' with non-sendable type 'AsyncSequenceWorkerWorkflow<WorkerType>.Output' (aka 'WorkerType.Output') in a `@Sendable` closure; this is an error in the Swift 6 language mode

Check warning on line 59 in WorkflowConcurrency/Sources/AsyncSequenceWorker.swift

View workflow job for this annotation

GitHub Actions / development-tests [iOS 17.5]

capture of 'sink' with non-sendable type 'Sink<AnyWorkflowAction<AsyncSequenceWorkerWorkflow<WorkerType>>>' in a `@Sendable` closure; this is an error in the Swift 6 language mode
}
}
}

lifetime.onEnded {
task.cancel()
}
}
}
}
Loading
Loading