-
-
Notifications
You must be signed in to change notification settings - Fork 4
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
How do you communicate with the outer world from Rid ? #57
Comments
Update! Ok my bad regarding |
Well maybe I panicked for nothing 😅 Seems like I can use |
Ok everything finally worked, here are a few notes if you need async grpc remote calls:
|
Oh well I'm re-browsing the docs and indeed I owe another apology: enum are indeed supported. |
Hi @Roms1383, would you be willing to post an example of how you're setting up and managing your |
Oh sure @SecondFlight right now it's still pretty rudimentary but here it is: impl RidStore<Msg> for Store {
// ...
fn update(&mut self, req_id: u64, msg: Msg) {
match msg {
Msg::Sync => {
// TODO: how to know if tokio runtime is recreated on each calls ?
// investigate further ...
rid::post(Reply::Syncing(req_id));
println!("hello Tokio");
let runtime = tokio::runtime::Builder::new_multi_thread()
// this is required to make remote grpc calls with tonic client (also don't forget ios entitlements in settings)
.enable_io()
.build()
.expect("runtime");
runtime.block_on(async {
println!("inside block on");
let uri = "https://127.0.0.1:50051".parse::<Uri>().expect("valid uri");
// just a helper method to connect grpc client
match protocol::service::connect(uri).await {
Ok(client) => {
// here it's a stream because I use grpc server-side streaming
let mut stream = client.sync().await.expect("stream");
loop {
match stream.message().await {
// type received over the wire: generated by tonic-build
Ok(Some(protocol::domain::MyTypeProtobuf {
items,
})) => {
eprintln!("items = {:?}", items);
// local type that allows for sending over FFI
// (identical to type generated by tonic-build, but with FFI-compliant typed fields and a TryFrom impl, see below)
let mut mapped: Vec<MyTypeFFI> = vec![];
for item in items {
mapped.push(MyTypeFFI::try_from(item).expect("invalid data"));
}
eprintln!("mapped = {:?}", mapped);
self.items.append(&mut mapped);
}
Ok(None) => {
eprintln!("finished");
break;
}
Err(e) => {
eprintln!("{:#?} stream failure", e);
rid::post(Reply::SyncFailure(req_id));
break;
}
}
}
eprintln!("synced");
rid::post(Reply::Synced(req_id));
}
Err(e) => {
eprintln!("{:#?} connection error", e);
rid::post(Reply::SyncFailure(req_id));
}
};
})
}
}
}
} |
As you can see I'm not yet managing the runtime at all. I'm still stuck on another minor thing: I try to use the rid plugin crate in different context and make |
Thanks so much! I'll put a pin in this for later. |
@SecondFlight take what follows with a bit of salt, I might be wrong 😅 let runtime = tokio::runtime::Handle::try_current();
let runtime = if runtime.is_ok() { runtime.unwrap() } else {
tokio::runtime::Builder::new_multi_thread()
// this is required to make remote grpc calls with tonic client (also don't forget ios entitlements in settings)
.enable_io()
.build()
.expect("runtime")
}; |
Yeah trying right now and I'm wrong because they return different types |
Well just tested some more but runtime gets recreated on each call anyway. |
Ok, no worries! I probably won't get to this by then, but if I do I'll let you know what I find. |
Looks like you both already went pretty deep here. Please LMK how I can help and yes you're right simple enums are supported (i.e. ones with just scalar values like the below). #[rid::model]
pub enum Color {
Green,
Blue
} They need to be identified where used via #[rid::model]
#[rid::enums(Color)]
struct MyStruct {
color: Color
} I need to improve/update the docs, unless one of you wants to fill in some examples for now in this section. Given this issue is closed maybe open another one if you run into more problems/need help. |
Speaking about which, a cool and quick experiment that could be worth checking: start either a tokio, an async_std or a smol runtime on |
I'm sure that would work. With my limited understanding, I feel it should be possible to do this without baking it into Rid though... If it's relatively easy to do this, then it might be better to add documentation for how to do it instead. That way the library consumer can choose which one they want to use, and Rid won't need to support anything extra. |
You should create a prototype of an app with those features @Roms1383 . I also do think adding a second event loop (given Flutter provides one already) is somewhat of a special case. |
Ok I have quite a lot of things in the pipeline at the moment, but I'll try to do it before Christmas. Only thing I haven't look at yet is: how do you start The problem doing as in #57 (comment), is that I have to build a runtime on each call to use tokio::runtime::Handle;
// Inside an async block or function.
let handle = Handle::current(); // or Handle::try_current(); in fallible situations
handle.spawn(async {
println!("do some task asynchronously");
});
Well to allow users to be able to use
Not so special actually: library consumers who create a Rid plugin might also want to make remote calls (REST/gRPC/etc) or any kind of asynchronous tasks from Rust directly, instead of having to rely on Dart for these.This is even better in terms of decoupling since you can delegate all your client-side business logic to Rust.
This is exactly what In other term, library consumers who have no need for a runtime have nothing to do e.g. |
Would you mind activating the Discussions feature from your Github repository @thlorenz ? |
Hi @Roms1383 I like the idea of discussions, but as you can see I'm having a hard time keeping up with responding to issues/PRs as it is. Therefore what I'd suggest you do is the following. When you're ready to work/discuss a new feature/idea please open an issue or PR (whichever works best for the case). Then provide a detailed summary of what you're suggesting/attempting to implement and we will keep it open as long as we discuss the idea in there. Thanks! |
PR in draft mode indeed looks great, I didn't know about this feature ! |
As a quick follow-up, this evening I was browsing Is my understanding correct @thlorenz that import Flutter
import UIKit
public class RidPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
// ...
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result(nil)
}
public func dummyMethodToEnforceBundling() {
// dummy calls to prevent tree shaking
// ...
}
} I would assume that one can probably use some initialization function like e.g.: #[no_mangle]
pub extern "C" fn initialize_runtime(...) -> ? {
// build tokio runtime ...
} And if that's the case I currently wonder how to keep the runtime around (not dropped) between FFI calls and I still have no clue yet. |
After a bit more thinking I believe @thlorenz is right, in that a separate async runtime in Rust is unnecessary for most use-cases. Flutter already provides an async runtime. Additionally, the way Rid is designed makes it safe to block in Rust code as doing so will not block the UI (with one caveat, see below). See the Reddit ticker app for an example. Note that the example uses It should also be fairly straightforward to use a two-way channel like a socket. Based on this snippet from the Reddit ticker app, I believe it's safe to call
@thlorenz I have some questions about how Rid handles messages:
Caveat to the first paragraph: if my assumptions are correct, a blocked message would prevent additional messages from being processed, which could have detrimental effects on usability. |
Well I actually think it's really a viable case @SecondFlight in order to delegate all the business logic to Rust / |
I definitely won't argue that. What works best really depends on your architecture, use-cases, preferences, etc. My big a-ha moment in the last week is just that the whole stack is fundamentally async-capable without adding tokio. |
Yes, utterly agree on this point too. |
Okay, I've made a few progress in the meantime, and it would be very long to detail everything but basically :
rid
cannot be used ifdiesel
orrocket
exists in the dependencies (I personally had to do A LOT of conditional compile and feature flags, since my workspace aims at multiple facets)enum
cannot be used inrid
just yetOption<T>
is not supported inrid
just yetOnce I overcame all these previous issues I now have some script that I call inside
update
, like so:And as you probably already guessed, I'm hitting a wall here since I can't find how to start a tokio runtime in Rid context, nor can I store it in
Store
itself since it results in:Cannot convert unknown rust type to dart type
.I went to search for a macro attribute like e.g.
#[rid::skip]
or#[rid::ignore]
but couldn't find one.My guess is that I could find a trick to create the runtime inside update and use some boolean flag to avoid recreating it every time, but I haven't tried yet and it feels super hacky.
Which brings another related question:
provided I manage to run tokio runtime and call grpc just fine, then how do I even store the results locally ?
I might have missed something but the fact that having
diesel
in the dependencies makes the build fails doesn't give me much faith that I can use a local storage of any kind on the device (I naively thought that I could go with SQLite).So what are the options left ?
Do I need to go through calling Dart and leave it to Dart to save the results with e.g. Hive for example ?
IMHO that would just defeat the purpose of using Rust in the first place 😐 or maybe I didn't understood Rid use-cases ? 🤔
The text was updated successfully, but these errors were encountered: