Man, I'm having a SUPER difficult time properly connecting to a websocket server from my Bevy app.
My gut feelings is that there should be a Resource for the socket, maybe a system to set up the connection, and then another system (function handler) when a message comes in.
I even tried using ai to help me, but the issue is that it "moves" all my stuff to a different thread so then I am unable to use the Commands to spawn anything or send the message anywhere else...
Sometimes it does run and compile but then just prints out "Receiver has been dropped" when I should be getting a message.
Does anyone have any simple working example code that I can reference?
So I went through this, and it's a bit of a hassle, but here's a workable minimal-ish example:
You can press space to establish a connection to an echo server.
It stores the connection in a Component that can be attached to any entity.
It uses non-blocking I/O so there should be no need for async or separate threads. But I'm also not super confident in this implementation at the moment.
Well, I can connect and receive websocket messages, at least.
When I paste the send_info function in my editor though it immediately underlines in red the variable "transforms" in line 11 when calling send...
fn send_info(
some_data: Query<(&Transform,)>,
mut entities_with_client: Query<(&mut WebSocketClient,)>,
) {
for (mut client,) in entities_with_client.iter_mut() {
let transforms = &some_data.iter().map(|x| x.0.clone()).collect::<Vec<>>();
info!("Sending data: {transforms:?}");
match client
.0
.0
.send(Message::Binary(bincode::serialize(transforms).unwrap()))
{
Ok() => info!("Data successfully sent!"),
Err(tungstenite::Error::Io(e)) if e.kind() == ErrorKind::WouldBlock => { /* ignore */ }
Err(e) => {
warn!("Could not send the message: {e:?}");
}
}
}
}
Here's the error I'm seeing:
the trait bound `bevy::prelude::Transform: other_player::_::_serde::Serialize` is not satisfied
for local types consider adding `#[derive(serde::Serialize)]` to your `bevy::prelude::Transform` type
for types from other crates check whether the crate offers a `serde` feature flag
the following other types implement trait
I'm using bevy { version = "0.14", features = ["wayland"] }, is that maybe why I'm seeing this error? Also, how would you invoke this "send_info" from other places in the app when you do want to send messages to the server?
invoke .send directly on the websocket client. Since the underlying TcpStream is non-blocking this shouldn't be too costly. However since it would need exclusive access only one system can have access to a websocket at a time, so this could limit parallelism.
In wasm you can't use rustls or tungsten, you need to use the WebSocket API provided by the browser. See e.g. WebSocket - Web APIs | MDN .
I'm basically recreating this example from wasm using the web-sys crate which provides bindings to the browser's APIs. I can then set callback functions that modify the state inside the wasm context - specifically when a message is received it appends the message content to the recv_queue.
The callbacks are stored in the Client so they are not dropped (It appears setting event listeners only creates weak references? I'm not 100% sure.)
I also added a timer so messages are only sent once a second, and updated the Cargo.toml to enable optimization for dependencies in the dev profile, since otherwise I'm only getting 20 fps in the browser. With optimizations of the dependencies it reaches my monitor's refresh rate.
Probably the implementation clarity could be significantly improved by fully abstracting away the client and putting each implementation into its own module.
Probably should make a crate out of this at some point...
error**:** could not compile bevy_websocket (bin "bevy_websocket") due to 1 previous error; 1 warning emitted
When I try to run the wasm version (is it this command?) trunk serve --no-default-features
I then get this error:
2024-10-22T17:53:45.620915Z INFO Starting trunk 0.21.1
2024-10-22T17:53:46.043381Z ERROR error getting the canonical path to the build target HTML file "/Users/jim/g/bevy_websocket_example/index.html"
2024-10-22T17:53:46.043418Z INFO 1: No such file or directory (os error 2)
Looks like I didn't test the native version again. You can run it with cargo run --target wasm32-unknown-unknown. It assumes you have wasm-server-runner installed (cargo install wasm-server-runner).
I think there might be a small bug in this recv_info function though:
fn recv_info(mut q: Query<(&mut WebSocketClient,)>) {
for (mut client,) in q.iter_mut() {
#[cfg(not(target_arch = "wasm32"))]
{
match client.0 .0.read() {
Ok(m) => info!("Received message {m:?}"),
Err(tungstenite::Error::Io(e)) if e.kind() == ErrorKind::WouldBlock => { /* ignore */
}
Err(e) => warn!("error receiving: {e}"),
}
}
#[cfg(target_arch = "wasm32")]
{
while let Some(m) = client.0.recv_queue.borrow_mut().pop_front() {
info!("Received message {m:?}")
}
}
}
}
When I run it natively I can see "Received message" printed. everything good
When I run it on wasm though I don't see "Received message"... I can see logs in the browser console that I am connected and sending the messages, and I can see "Got message" be printed from the Client impl code, but the message is for some reason not making it to making it to recv_info in the wasm case. Do you know why this could be?