-
Notifications
You must be signed in to change notification settings - Fork 203
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
WASM support for client #388
Conversation
Hey, thanks for your contribution! I think I don't want to maintain a timer in-tree. Are you willing to maintain the forked wasm timer? If so, I encourage you to publish it as a crate and add an optional dependency on it in tarpc. |
@tikue Yes I like to maintain a one. I forked it just now. I will update this PR once I finalized the project. |
Any updates on this PR? I also just bumped into the |
@allsey87 I am still working on the new timer crate. I hope to release it in this week after implemented few more unit tests for |
Not to add anything here, but I was able to very successfully use this PR to bind my WASM frontend crate to my backend crate over a quick (possibly incorrect 😄, so use at your own caution) Websocket transport that I chucked together: https://gist.github.com/tekacs/b2cb7d19e8ec9a7c7838251b2e427d01 No issues so far, but will chime in if I notice anything -- happy to write this up as a proper end-to-end example once this is merged if I'm not doing things wrong or it's not likely to be gotten to. Thanks so much for this @whizsid! |
Would love to see this PR merged (I use it regularly, it works well albeit with a fork to wasmtimer-rs that I might ask @whizsid to upstream) -- I'm happy to resolve conflicts on this branch if there's appetite from the maintainers to have this one merged? |
Hi, thanks for your interest! I think I just need a bit more information to make a decision on this:
I think maybe a good next step is to talk to the tokio folks to see what's going on with wasm support? |
It looks like tokio support for wasm is planned: tokio-rs/tokio#4827. Given that, I think I would prefer to not merge this and just wait for that support. Sorry about that! But please let me know if tokio's support does not pan out for some reason. |
Looking at this PR more closely, I see that it also would have split the client and server into separate features to minimize size for clients that don't need the server. I think that would be a great, uncontroversial, change and would be happy to merge it in a new PR! |
So @tikue that issue that you're linking to is (as far as I can tell) for WASI support for Tokio rather than general WASM support -- in particular this PR supports usage of TARPC in WASM in the browser, which that proposal wouldn't lead to support for. Would that be reason enough to still consider this proposal at all? It's not essential to my use to see this merged (I can maintain my own fork), but I just wanted to call out that Tokio/WASI isn't the full picture (and @whizsid's work with timers in particular helps especially in browsers). |
@tekacs I opened this PR to use tarpc in a cross-platform (web and desktop) application. But now I am not using the tarpc. My personal idea on a wasm tarpc is, someone can make a new runtime agnostic framework by handing all the time related functionalities to users. Then this project can support both web and desktop. As an alternative to tarpc, I am using below struct. I am posting it here to save someone who fell into the same well. pub struct Client<
I: Serialize + DeserializeOwned + Clone + Debug + Sized,
O: Serialize + DeserializeOwned + Clone + Debug + Sized,
E: Debug,
T: Stream<Item = Vec<u8>> + Sink<Vec<u8>, Error = E> + Unpin,
> {
internal: T,
pending: Vec<I>,
_out: PhantomData<O>,
}
impl<
I: Serialize + DeserializeOwned + Clone + Debug + Sized,
O: Serialize + DeserializeOwned + Clone + Debug + Sized,
E: Debug,
T: Stream<Item = Vec<u8>> + Sink<Vec<u8>, Error = E> + Unpin,
> Client<I, O, E, T>
{
pub fn new(internal: T) -> Client<I, O, E, T> {
Client {
internal,
pending: Vec::new(),
_out: PhantomData,
}
}
/// Send a request and waiting for a response
pub async fn send_and_receive<OT: Into<O>, IT: TryFrom<I, Error = ()>>(
&mut self,
message: OT,
) -> Result<IT, SendAndReceiveError<E>> {
self.send(message).await?;
let response = self.receive::<IT>().await?;
Ok(response)
}
pub async fn receive<IT: TryFrom<I, Error = ()>>(&mut self) -> Result<IT, ReceiveError> {
// Immediately returning if it in pending list
for (i, pending_message) in self.pending.iter().enumerate() {
if let Ok(converted_message) = IT::try_from(pending_message.clone()) {
self.pending.remove(i);
return Ok(converted_message);
}
}
loop {
let response_message_opt = self.internal.next().await;
if let Some(bin_message) = response_message_opt {
match from_bin::<I>(&bin_message) {
Ok(response_message) => {
if let Ok(converted_message) = IT::try_from(response_message.clone()) {
return Ok(converted_message);
} else {
// Inserting it to pending list to return it in next time.
self.pending.push(response_message);
}
}
Err(e) => {
return Err(ReceiveError::Deserialize(*e));
}
}
}
}
}
/// Send a message without caring about a response
pub async fn send<OT: Into<O>>(&mut self, message: OT) -> Result<(), SendError<E>> {
let mut pin_internal: Pin<&mut T> = Pin::new(&mut self.internal);
match to_bin(&message.into()) {
Ok(serialized) => pin_internal
.send(serialized)
.await
.map_err(|e| SendError::Send(e)),
Err(e) => Err(SendError::Serialize(*e)),
}
}
}
#[derive(Debug)]
pub enum SendError<E: Debug> {
Serialize(BincodeError),
Send(E),
}
#[derive(Debug)]
pub enum ReceiveError {
Deserialize(BincodeError),
}
#[derive(Debug)]
pub enum SendAndReceiveError<E: Debug> {
Send(SendError<E>),
Receive(ReceiveError),
}
impl<E: Debug> From<SendError<E>> for SendAndReceiveError<E> {
fn from(value: SendError<E>) -> Self {
SendAndReceiveError::Send(value)
}
}
impl<E: Debug> From<ReceiveError> for SendAndReceiveError<E> {
fn from(value: ReceiveError) -> Self {
SendAndReceiveError::Receive(value)
}
} This can be improved. But now I can use it like this. // on web frontend
let client:Client<ServerMessage, ApplicationMessage, WebSocketError, WebSocket> = Client::new(websocket_transport);
// on standalone application UI thread
// I created this bi_channel by combining two futures channels
let client:Client<ServerMessage, ApplicationMessage, BiChannelError, BiChannel> = Client::new(bi_channel_1);
// on server
let client:Client<ApplicationMessage, ServerMessage, WebSocketError, WebSocket> = Client::new(web_socket);
// on standalone application main thread
let client:Client<ApplicationMessage, ServerMessage, BiChannelError, BiChannel> = Client::new(bi_channel_2); Then I can send and receive messages on any platform like:- client.send(MyMessage::new()).await;
let other_message = client.receive::<OtherMessage>().await; This is how I achieved it:- https://github.com/whizsid/openxd |
Separated client and server code by introducing new features
client
,server
. Because we have to minify the generated WASM binary.I used
wasm-timer
as a replacement fortokio::time
. Butwasm-timer
seems like dead today. I had to copy lot of code fromtokio_util::time
to implement thedelay_queue
for WASM. tomaka/wasm-timer#21 . Stole this example project from #331 and improved it.