Skip to content
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

[pigeon]: Support of modern asynchronous api for Swift and Kotlin #8341

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

feduke-nukem
Copy link

@feduke-nukem feduke-nukem commented Dec 23, 2024

  • Added support of generating modern asynchronous api for Swift (async) and Kotlin (suspend). New annotation @modernAsync
  • Added ModernAsync annotation with SwiftModernAsynchronousOptions to specify if method throws

Resolves #123867, resolves #147283

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@feduke-nukem feduke-nukem changed the title [pigeon]: added support of modern asynchronous api for Swift and Kotlin [pigeon]: Support of modern asynchronous api for Swift and Kotlin Dec 23, 2024
@@ -25,6 +25,16 @@ private class PigeonApiImplementation: ExampleHostApi {
}
completion(.success(true))
}

func sendMessageModernAsync(message: MessageData) async throws -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just overload the function name func sendMessage(...)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that I understood.

It's generated Host API code. We could not overload functions in Dart even if we do in Swift

@@ -25,6 +25,16 @@ private class PigeonApiImplementation: ExampleHostApi {
}
completion(.success(true))
}

func sendMessageModernAsync(message: MessageData) async throws -> Bool {
try? await Task.sleep(nanoseconds: 2_000_000_000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used for unit test? we shouldn't wait for so long as it slows down the tests

Copy link
Author

@feduke-nukem feduke-nukem Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used in example app for demonstration purposes

I am not sure if it is used in any tests. I could remove this code if it is necessary

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed Task.sleep

Task {
do {
let result = try await api.sendMessageModernAsync(message: messageArg)
DispatchQueue.main.async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can do Task { @MainActor } here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can do Task { @MainActor } here.

Do you mean:

            sendMessageModernAsyncChannel.setMessageHandler { message, reply in
                let args = message as! [Any?]
                let messageArg = args[0] as! MessageData
                Task {
                    do { @MainActor
                        let result = try await api.sendMessageModernAsync(message: messageArg)
                        reply(wrapResult(result))

                    } catch {
                        reply(wrapError(error))
                    }
                }
            }

Will it lead to executing api.sendMessageModernAsync on main thread that could block it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, i meant you can replace DispatchQueue.main.async {} with Task { @MainActor}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, i meant you can replace DispatchQueue.main.async {} with Task { @MainActor}

What's the point/profit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of swift concurrency is to replace GCD. So since you are using swift concurrency here, there's no reason to use GCD at all.

Copy link
Author

@feduke-nukem feduke-nukem Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCD

Okay I get it.

I am not very familiar with swift concurrency actually. Is that what you meant?

sendMessageModernAsyncChannel.setMessageHandler { message, reply in
    let args = message as! [Any?]
    let messageArg = args[0] as! MessageData
    Task {
        do {
            let result = try await api.sendMessageModernAsync(message: messageArg)

            await Task { @MainActor in
                reply(wrapResult(result))
            }

        } catch {
            await Task { @MainActor in
                reply(wrapError(error))
            }
        }
    }
}

Maybe we could use MainActor.run instead? I think it is more readable.

sendMessageModernAsyncChannel.setMessageHandler { message, reply in
    let args = message as! [Any?]
    let messageArg = args[0] as! MessageData
    Task {
        do {
            let result = try await api.sendMessageModernAsync(message: messageArg)

            await MainActor.run(){
                reply(wrapResult(result))
            }

        } catch {
            await MainActor.run(){
                reply(wrapError(error))
            }
        }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your first chunk of code, don't put await in front of Task.

For this particular case, you can use MainActor.run { ... }, since reply doesn't require an async context.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -222,6 +223,30 @@ class ExampleHostApiSetup {
} else {
sendMessageChannel.setMessageHandler(nil)
}
let sendMessageModernAsyncChannel = FlutterBasicMessageChannel(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just reuse the channel?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you point me to where this is used? im wondering if you can just use the original channel

Copy link
Author

@feduke-nukem feduke-nukem Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "original channel"? Each method of Host API got its own generated FlutterBasicMessageChannel.

image

This is default Pigeon generated code flow.

Maybe I've misunderstood you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe i misunderstood how pigeon is used. Just a strawman - can we share the same channel for both old and new ways of doing things?

Copy link
Author

@feduke-nukem feduke-nukem Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we use channels will not differ at all either "new way" or "old". We just can not use two handlers for one channel at the same time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have old api to be the wrapper of the new api?

Copy link
Author

@feduke-nukem feduke-nukem Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide code sample of what you mean?

@feduke-nukem feduke-nukem force-pushed the issue-123867-async-await branch from a8df692 to 583d6d7 Compare December 23, 2024 22:00
@feduke-nukem
Copy link
Author

@hellohuanlin @LouiseHsu @tarrinneal CI check fails:

Changed packages: pigeon
[0:00] Running for packages/pigeon...
  No version change.
  Found NEXT; validating next version in the CHANGELOG.
No version change found, but the change to this package could not be verified to be exempt
from version changes according to repository policy.
If this is a false positive, please comment in the PR to explain why the PR
is exempt, and add (or ask your reviewer to add) the "override: no versioning needed" label.

Do I need to bump version in CHANGELOG.md? Currently I append related to pr changes below NEXT label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[pigeon] Support Swift Concurrency style api [pigeon] Implement Swift methods using async & await
2 participants