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

Add first-class async authoring support #540

Open
lucasfernog opened this issue Feb 12, 2021 · 14 comments
Open

Add first-class async authoring support #540

lucasfernog opened this issue Feb 12, 2021 · 14 comments
Labels
enhancement New feature or request

Comments

@lucasfernog
Copy link

Hello! First of all thanks for this project :) I've been writing a Webview app with this crate and it works great!

I'm trying to write a IUriToStreamResolver implementation and after a few hours researching and attempting to do it, I found the interface API:

#[implement(windows::web::IUriToStreamResolver)]
struct MyResolver();

impl MyResolver {
    pub fn uri_to_stream_async<'a, T0__: Into<Param<'a, Uri>>>(
        &self,
        uri: T0__
    ) -> Result<IAsyncOperation<IInputStream>> {
    }
}

But now I have no idea how to get an IAsyncOperation<IInputStream> to return. All I could do was return a IAsyncOperation<IRandomAccessStream> with the FileRandomAccessStream struct. Can you help me? Do I need to implemement another interface?

Thanks :)

@nklayman
Copy link

Also note: the IRandomAccessStream type can be converted to IInputStream, but not if it's inside of a IAsyncOperation.

@kennykerr
Copy link
Collaborator

WinRT provides four "special" interfaces for modeling async operations. IAsyncOperation<T> is one of them. Calling such APIs is already supported (here's an example) but implementing them is not.

In theory, you could provide an implementation of IAsyncOperation as it is "just" a WinRT interface. In practice, it is extremely hard to do so correctly and very detrimental if done incorrectly. I haven't yet added support for doing this automatically in the windows crate but the plan is to provide canned implementations much like I did for C++ where you can just write a coroutine:

struct Resolver : implements<Resolver, IUriToStreamResolver>
{
    IAsyncOperation<IInputStream> UriToStreamAsync(Uri const& uri)
    {
        auto stream = co_await <something> // load stuff asynchronously...

        // apply any transformation...

        co_return stream;
    }
};

This depends on #81 (support for implementing generic interfaces is still outstanding) and will likely use async funtions like this:

async fn main_async() -> Result<()> {
let mut message = std::env::current_dir().unwrap();
message.push("message.png");
let file = StorageFile::get_file_from_path_async(message.to_str().unwrap())?.await?;
let stream = file.open_async(FileAccessMode::Read)?.await?;
let decode = BitmapDecoder::create_async(stream)?.await?;
let bitmap = decode.get_software_bitmap_async()?.await?;
let engine = OcrEngine::try_create_from_user_profile_languages()?;
let result = engine.recognize_async(bitmap)?.await?;
println!("{}", result.text()?);
Ok(())
}

I'll rename this issue to track the addition of first-class async support.

@kennykerr kennykerr changed the title Implementing windows::web::IUriToStreamResolver Add first-class async authoring support Feb 12, 2021
@kennykerr kennykerr added the enhancement New feature or request label Feb 12, 2021
@lucasfernog
Copy link
Author

So there's no way to implement the URI to stream resolver with the Rust API currently?

@kennykerr
Copy link
Collaborator

You can implement the IUriToStreamResolver interface, but not mint a new IAsyncOperation directly. You could however return an IAsyncOperation from some other API. For example, you could use cppwinrt to implement IAsyncOperation and call that from the Rust implementation of IUriToStreamResolver. Not ideal obviously.

@lucasfernog
Copy link
Author

Yeah we really considered using cpp to do it, and it might be the path we follow.

@lucasfernog
Copy link
Author

But Noah just did something crazy and it might work:

impl MyResolver {
    pub fn uri_to_stream_async<'a, T0__: Into<Param<'a, Uri>>>(
        &self,
        uri: T0__
    ) -> windows::Result<IAsyncOperation<IInputStream>> {
        let file = StorageFile::get_file_from_path_async("test.html").unwrap();
        loop {
            if file.status().unwrap() != AsyncStatus::Completed {
                continue
            }
            break Ok(file.get().unwrap().open_sequential_read_async().unwrap())
        }
    }
}

@kennykerr
Copy link
Collaborator

Ha 😉 - that's basically what I was suggesting - use another implementation. This specifically is cheating slightly, but hey if it works for you that's fine. I'd just tweak it slightly:

pub fn uri_to_stream_async(&self, _: &Option<Uri>) -> Result<IAsyncOperation<IInputStream>> {
    StorageFile::get_file_from_path_async("test.html")?
        .get()?
        .open_sequential_read_async()
}

The caveat is that obviously its not that async since get blocks the calling thread until the async result is available.

@lucasfernog
Copy link
Author

I believe we can use an synchronous approach until first-class support is available here :) we're just playing around with the libraries and just by having a URI resolver would be great :)

Thanks for your help!

@lucasfernog
Copy link
Author

StorageFile creates a IInputStream from a file path, but is there a way to create such stream from a Vec<u8>?

@nklayman
Copy link

Okay, I've implemented your suggestion like so:

#[implement(windows::web::IUriToStreamResolver)]
#[derive(Clone)]
struct CustomResolver();

impl CustomResolver {
    pub fn uri_to_stream_async<'a, T0__: Into<Param<'a, Uri>>>(
        &self,
        uri: T0__,
    ) -> windows::Result<IAsyncOperation<IInputStream>> {
        StorageFile::get_file_from_path_async("index.html")?
            .get()?
            .open_sequential_read_async()
    }
}

impl<'a> Into<windows::Param<'a,windows::web::IUriToStreamResolver >> for CustomResolver {
    fn into(self) -> windows::Param<'a,windows::web::IUriToStreamResolver > {
        ::windows::Param::Owned(::std::convert::Into::into(::std::clone::Clone::clone(&self)))
    }
}

However, when I use it:

let res = CustomResolver();
Ok(self.webview.navigate_to_local_stream_uri(Uri::create_uri(url)?, res)?)

It compiles, but I get this error at runtime:

Error: WinrtError(Error { code: 0x80070057, message: "The parameter is incorrect." })
error: process didn't exit successfully: `target\debug\wry.exe` (exit code: 1)

Any ideas? Thanks for your help!

@lucasfernog
Copy link
Author

lucasfernog commented Feb 13, 2021

Okay, I've implemented your suggestion like so:

#[implement(windows::web::IUriToStreamResolver)]
#[derive(Clone)]
struct CustomResolver();

impl CustomResolver {
    pub fn uri_to_stream_async<'a, T0__: Into<Param<'a, Uri>>>(
        &self,
        uri: T0__,
    ) -> windows::Result<IAsyncOperation<IInputStream>> {
        StorageFile::get_file_from_path_async("index.html")?
            .get()?
            .open_sequential_read_async()
    }
}

impl<'a> Into<windows::Param<'a,windows::web::IUriToStreamResolver >> for CustomResolver {
    fn into(self) -> windows::Param<'a,windows::web::IUriToStreamResolver > {
        ::windows::Param::Owned(::std::convert::Into::into(::std::clone::Clone::clone(&self)))
    }
}

However, when I use it:

let res = CustomResolver();
Ok(self.webview.navigate_to_local_stream_uri(Uri::create_uri(url)?, res)?)

It compiles, but I get this error at runtime:

Error: WinrtError(Error { code: 0x80070057, message: "The parameter is incorrect." })
error: process didn't exit successfully: `target\debug\wry.exe` (exit code: 1)

Any ideas? Thanks for your help!

You just created the Uri the wrong way :) your code is working. Now we just need to know how to use the resolver with a byte array instead of a file.

This is the code that works:

let res = CustomResolver();
self.webview.navigate_to_local_stream_uri(self.webview.build_local_stream_uri("scheme", "index.html")?, res)?

@nklayman
Copy link

It doesn't seem like there is a way to create a StorageFile from a byte array. Unfortunately, it also seems like there isn't a way to get an IAsyncOperation<IInputStream> any other way. All we can get from a byte array is IAsyncOperation<pretty much everything but IInputStream> and IInputStream. This won't work unless we can convert the type nested inside IAsyncOperation or wrap IInputStream in an IAsyncOperation. The latter seems easy to do in C, but I can't find a way to do it in Rust. Seems like we'll have to write a C function and call that.

@kennykerr
Copy link
Collaborator

I will just say again that there is no need for Param and Into in your examples. Those are used by the windows crate but you need not do the same. Indeed, that could cause problems. I assume you've done so in part because of what the docs illustrate. If so, that's something I'd like to improve in the docs.

@kennykerr kennykerr self-assigned this Feb 26, 2021
@kennykerr kennykerr removed their assignment Oct 14, 2022
@kennykerr
Copy link
Collaborator

Just leaving a comment here to share this good write-up by @jonwis:

https://jonwis.github.io/notes/winrt-design/async_is_hazardous.html

I think I'll provide basic async implementation support in Rust for compatibility with existing APIs for example:

let async : IAsyncAction = async(move || {
    // some work here
})?;

But it does not seem worthwhile to add more comprehensive support such as what I provided for C++/WinRT via coroutines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants