replicate / replicate-swift

Swift client for Replicate

Home Page:https://replicate.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot call mutating async function 'wait(with:priority:)' or ' cancel(with:)' on actor-isolated property 'prediction'

ConradoMateu opened this issue · comments

I have a problem using prediction.cancel and prediction.wait in a View that contains EnvironmentObjects:

struct PromptView: View {
    
    @EnvironmentObject private var controller: ImageController
    @EnvironmentObject private var generator: ImageProducer
    @EnvironmentObject private var focusCon: GenerateImageFocusController
    @EnvironmentObject private var store: ProjectStore
    @State var prediction: Difussion.Prediction? = nil
    let client: Replicate.Client = Replicate.Client(token: "exampleToken")

    
    func generate() async throws {
        prediction = try await Difussion.predict(with: client,
                                                       input: .init(prompt: "Example Prompt"))
        try await prediction?.wait(with: client)
    }
    
    func cancel() async throws {
        try await prediction?.cancel(with: client)


    }


    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text("Example")
            
        }
    }
}

Here is the error:

Screenshot 2023-07-04 at 20 42 41

But if I delete the EnvironmentObjects The view seems to work:

Screenshot 2023-07-04 at 20 42 57

Do you know what should I do to fix this issue? Since I need these environment objects in the view.
Thanks in advance.

You can reproduce it with this example easier, with the code that you have in the example repo:

import SwiftUI
import Replicate

// Get your API token at https://replicate.com/account
private let client = Replicate.Client(token: "12345")

// https://replicate.com/stability-ai/stable-diffusion
enum StableDiffusion: Predictable {
    static var modelID = "stability-ai/stable-diffusion"
    static let versionID = "db21e45d3f7023abc2a46ee38a23973f6dce16bb082a930b0c49861f96d1e5bf"

    struct Input: Codable {
        let prompt: String
    }

    typealias Output = [URL]
}

class Controller: ObservableObject {

}

struct ContentView: View {
    @EnvironmentObject private var controller: Controller
    @State private var prompt = ""
    @State private var prediction: StableDiffusion.Prediction? = nil
    
    var body: some View {
        Form {
            Section {
                TextField(text: $prompt,
                          prompt: Text("Enter a prompt to display an image"),
                          axis: .vertical,
                          label: {})
                    .disabled(prediction?.status.terminated == false)
                    .submitLabel(.go)
                    .onSubmit(of: .text) {
                        Task {
                            try! await generate()
                        }
                    }
            }
            
            if let prediction {
                ZStack {
                    Color.clear
                        .aspectRatio(1.0, contentMode: .fit)
                    
                    switch prediction.status {
                    case .starting, .processing:
                        VStack{
                            ProgressView("Generating...")
                                .padding(32)
                            
                            Button("Cancel") {
                                Task { try await cancel() }
                            }
                        }
                    case .failed:
                        Text(prediction.error?.localizedDescription ?? "Unknown error")
                            .foregroundColor(.red)
                    case .succeeded:
                        if let url = prediction.output?.first {
                            VStack {
                                AsyncImage(url: url, scale: 2.0, content: { phase in
                                    phase.image?
                                        .resizable()
                                        .aspectRatio(contentMode: .fit)
                                        .cornerRadius(32)
                                })
                                
                                ShareLink("Export", item: url)
                                    .padding(32)

                            }
                        }
                    case .canceled:
                        Text("The prediction was canceled")
                            .foregroundColor(.secondary)
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                .padding()
                .listRowBackground(Color.clear)
                .listRowInsets(.init())
            }
        }
        
        Spacer()
    }
    
    func generate() async throws {
        prediction = try await StableDiffusion.predict(with: client,
                                                       input: .init(prompt: prompt))
        try await prediction?.wait(with: client)
    }
    
    func cancel() async throws {
        try await prediction?.cancel(with: client)
    }
}
commented

Hi @ConradoMateu. This is more of a question about SwiftUI than anything specific to Replicate's Swift client. The issue has to do with calling the mutating method prediction.wait(with:).

I just opened #38 to make Prediction.wait(for:with:priority:retrier:) publicly available as a convenience. My understanding is that re-assigning the prediction property should work as expected.

func generate() async throws {
    prediction = try await StableDiffusion.predict(with: client,
                                                   input: .init(prompt: prompt))

    var retrier = client.retryPolicy.makeIterator()
    prediction = try await Prediction.wait(for: prediction, with: client, priority: nil, retrier: &retrier)
}

Thanks for creating #38 🙏
Also, I have read your posts on NSHipster many times, they were really helpful 🚀