From 103444e57478b2f702c26a6dfb9a62b34a60e5fe Mon Sep 17 00:00:00 2001 From: core Date: Wed, 2 Jul 2025 23:30:27 -0400 Subject: [PATCH] chore(wasm)!: fix compilations on webassembly BREAKING CHANGE: server cli now has two bind options, they must be different --- .cargo/config.toml | 3 + Cargo.lock | 106 ++++++++++++++++++++++++++- crates/unified/Cargo.toml | 14 +++- crates/unified/src/client/mod.rs | 69 ++++++++++++----- crates/unified/src/client_plugins.rs | 3 + crates/unified/src/main.rs | 15 +++- crates/unified/src/server/mod.rs | 50 +++++++++---- crates/unified/src/server_plugins.rs | 6 +- 8 files changed, 220 insertions(+), 46 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index ea50f5a119af0f931169ca158bcf3ed297889ad1..7177eae7609cff888615190eee33763f7500df3a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,6 @@ xtask = "run --release --package xtask --" [target.x86_64-unknown-linux-gnu] linker = "clang" rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4e3c7a4e5cefecf7a2cf62c0dd5b70f780a99873..0ff269ab8ea937eebbc8838aaba7b53444c2340b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" [[package]] name = "accesskit" @@ -5112,6 +5112,17 @@ dependencies = [ "mach2", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -7255,15 +7266,31 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee5d017932669cb57862c8c24ad5294d4fff877ace85487bc771b36ab958a054" dependencies = [ + "anyhow", + "async-channel", "bevy_ecs 0.16.1", "bytes", + "crossbeam", + "futures", + "futures-channel", + "futures-util", "hmac-sha256", + "http", + "js-sys", "log", "octets", "renet2", "renetcode2", "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tungstenite 0.26.2", "url", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] @@ -7664,6 +7691,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + [[package]] name = "simba" version = "0.8.1" @@ -7794,6 +7830,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socks" version = "0.3.4" @@ -7858,10 +7904,13 @@ dependencies = [ "bevy_replicon", "bevy_replicon_renet2", "clap", + "getrandom 0.3.3", "log", "rand 0.9.1", "serde", + "tokio", "tracing-subscriber", + "url", ] [[package]] @@ -8346,6 +8395,49 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + [[package]] name = "toml" version = "0.7.8" @@ -8782,6 +8874,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "usvg" version = "0.45.1" diff --git a/crates/unified/Cargo.toml b/crates/unified/Cargo.toml index bd1f1d728fd41b45c64f5b32acd29a742a18e827..0b116a9baae8fbfa8116469b9c17c342d7523e6d 100644 --- a/crates/unified/Cargo.toml +++ b/crates/unified/Cargo.toml @@ -5,14 +5,15 @@ edition = "2024" version = "0.1.0" [dependencies] -bevy = { version = "0.16", features = ["serialize", "file_watcher", "tonemapping_luts"] } +bevy = { version = "0.16", features = ["serialize", "tonemapping_luts"] } bevy_rapier2d = { version = "0.30", features = ["serde-serialize", "simd-stable"] } bevy_common_assets = { version = "0.13", features = ["toml"] } bevy_replicon = "0.34" -bevy_replicon_renet2 = { version = "0.10", features = ["native_transport"] } +bevy_replicon_renet2 = { version = "0.10" } clap = { version = "4", features = ["derive", "cargo"] } +url = "2" tracing-subscriber = "0.3" log = { version = "*", features = ["max_level_debug", "release_max_level_warn"] } @@ -20,3 +21,12 @@ log = { version = "*", features = ["max_level_debug", "release_max_level_warn"] serde = { version = "1", features = ["derive"] } rand = "0.9" +getrandom = { version = "0.3", features = [] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = ["rt-multi-thread"] } + +[features] +default = [] +native = ["bevy/file_watcher", "bevy_replicon_renet2/native_transport", "bevy_replicon_renet2/ws_server_transport"] +wasm = ["getrandom/wasm_js", "bevy_replicon_renet2/ws_client_transport"] \ No newline at end of file diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index b06d5aac9a2d762aa11839b5dce9834033639a11..92abe5da54db354da1480e5a747cdff1391d3ea8 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -3,7 +3,7 @@ mod incoming_parts; mod key_input; mod starfield; -use std::net::{SocketAddr, UdpSocket}; +use std::net::{IpAddr, SocketAddr, UdpSocket}; use std::time::SystemTime; use bevy::core_pipeline::bloom::Bloom; use bevy::core_pipeline::fxaa::Fxaa; @@ -13,7 +13,11 @@ use bevy::window::PrimaryWindow; use bevy_rapier2d::prelude::RigidBody; use bevy_replicon::prelude::{ConnectedClient, RepliconChannels}; use bevy_replicon::shared::server_entity_map::ServerEntityMap; -use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport, NativeSocket}; +use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport}; +#[cfg(not(target_arch = "wasm32"))] +use bevy_replicon_renet2::netcode::NativeSocket; +#[cfg(target_arch = "wasm32")] +use bevy_replicon_renet2::netcode::{WebSocketClientConfig, WebSocketClient, ClientSocket}; use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient}; use bevy_replicon_renet2::RenetChannelsExt; use crate::client::incoming_parts::incoming_parts_plugin; @@ -23,6 +27,9 @@ use crate::client::starfield::starfield_plugin; use crate::ecs::{Ball, CursorWorldCoordinates, Ground, MainCamera, Player, SendBallHere}; pub struct ClientPlugin { + #[cfg(target_arch = "wasm32")] + pub server: url::Url, + #[cfg(not(target_arch = "wasm32"))] pub server: SocketAddr } impl Plugin for ClientPlugin { @@ -31,26 +38,50 @@ impl Plugin for ClientPlugin { app .insert_resource(CursorWorldCoordinates(None)) .add_systems(Startup, move |mut commands: Commands, channels: Res| { - let client = RenetClient::new( - ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs()), - false - ); let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let client_id = current_time.as_millis() as u64; - let socket = UdpSocket::bind(SocketAddr::new("::1".parse().unwrap(), 0)).unwrap(); - let authentication = ClientAuthentication::Unsecure { - client_id, - protocol_id: 0, - socket_id: 0, - server_addr: server, - user_data: None - }; - let transport = NetcodeClientTransport::new(current_time, authentication, NativeSocket::new(socket).unwrap()).unwrap(); - commands.insert_resource(client); - commands.insert_resource(transport); - - info!(?client_id, "connected!"); + #[cfg(target_arch = "wasm32")] { + let socket_config = WebSocketClientConfig { + server_url: server.clone(), + }; + let socket = WebSocketClient::new(socket_config).unwrap(); + let client = RenetClient::new( + ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs()), + socket.is_reliable() + ); + let authentication = ClientAuthentication::Unsecure { + socket_id: 1, + server_addr: socket.server_address(), + client_id: current_time.as_millis() as u64, + user_data: None, + protocol_id: 0 + }; + let mut transport = NetcodeClientTransport::new(current_time, authentication, socket).unwrap(); + commands.insert_resource(client); + commands.insert_resource(transport); + } + #[cfg(not(target_arch = "wasm32"))] { + let bind = match server.ip() { + IpAddr::V4(_) => "127.0.0.1:0", + IpAddr::V6(_) => "[::1]:0", + }; + let client_socket = NativeSocket::new(UdpSocket::bind(bind).unwrap()).unwrap(); + let authentication = ClientAuthentication::Unsecure { + socket_id: 0, + server_addr: server, + client_id: current_time.as_millis() as u64, + user_data: None, + protocol_id: 0 + }; + let client = RenetClient::new( + ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs()), + false + ); + let mut transport = NetcodeClientTransport::new(current_time, authentication, client_socket).unwrap(); + commands.insert_resource(client); + commands.insert_resource(transport); + } }) .add_systems(Startup, setup_graphics) .add_systems(Update, update_cursor_position) diff --git a/crates/unified/src/client_plugins.rs b/crates/unified/src/client_plugins.rs index 7057850682b24e9dfe321404ed31d8b477f0ebe6..252bc169c9cf12341ea4faf5f21c460a124133f1 100644 --- a/crates/unified/src/client_plugins.rs +++ b/crates/unified/src/client_plugins.rs @@ -8,6 +8,9 @@ use bevy_replicon_renet2::RepliconRenetClientPlugin; use crate::client::ClientPlugin; pub struct ClientPluginGroup { + #[cfg(target_arch = "wasm32")] + pub server: url::Url, + #[cfg(not(target_arch = "wasm32"))] pub server: SocketAddr } impl PluginGroup for ClientPluginGroup { diff --git a/crates/unified/src/main.rs b/crates/unified/src/main.rs index 73eb9060f7ec56833646242d33877eec951f1843..33af9ef86b97ed524c22a59b47f924071dea1c1e 100644 --- a/crates/unified/src/main.rs +++ b/crates/unified/src/main.rs @@ -22,12 +22,18 @@ use crate::shared_plugins::SharedPluginGroup; #[command(version, about)] enum Cli { Client { + #[cfg(target_arch = "wasm32")] + #[arg(short, long)] + server: url::Url, + #[cfg(not(target_arch = "wasm32"))] #[arg(short, long)] server: SocketAddr }, Server { - #[arg(short = 'b', long)] - bind: SocketAddr, + #[arg(short = 'w', long)] + bind_ws: SocketAddr, + #[arg(short = 'u', long)] + bind_native: SocketAddr, #[arg(short = 'r', long)] tick_rate: f64, #[arg(short = 'C', long)] @@ -60,14 +66,15 @@ fn main() -> AppExit { server }); }, - Cli::Server { bind, tick_rate, max_clients } => { + Cli::Server { bind_ws, bind_native, tick_rate, max_clients } => { if cfg!(target_family = "wasm") { eprintln!("the server cannot run on webassembly"); exit(1); } app.add_plugins(ServerPluginGroup { - bind, + bind_ws, + bind_native, tick_rate, max_clients }); diff --git a/crates/unified/src/server/mod.rs b/crates/unified/src/server/mod.rs index d3d851e092454d2508247a052db7706542c92961..6445f5033ab2b1f68210fb0fd1b2e48620dec113 100644 --- a/crates/unified/src/server/mod.rs +++ b/crates/unified/src/server/mod.rs @@ -8,7 +8,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; use bevy::prelude::*; use bevy_rapier2d::prelude::{Collider, Restitution, RigidBody, Velocity}; use bevy_replicon::prelude::{FromClient, Replicated, RepliconChannels}; -use bevy_replicon_renet2::netcode::{NativeSocket, NetcodeServerTransport, ServerAuthentication, ServerSetupConfig}; +use bevy_replicon_renet2::netcode::{NetcodeServerTransport, ServerAuthentication, ServerSetupConfig, BoxedSocket}; + +#[cfg(not(target_arch = "wasm32"))] +use bevy_replicon_renet2::netcode::{NativeSocket, WebSocketAcceptor, WebSocketServerConfig, WebSocketServer}; + use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetServer}; use bevy_replicon_renet2::RenetChannelsExt; use crate::ecs::{Ball, Ground, SendBallHere}; @@ -18,37 +22,53 @@ use crate::server::player::player_management_plugin; use crate::server::world_config::world_config_plugin; pub struct ServerPlugin { - pub bind: SocketAddr, + pub bind_ws: SocketAddr, + pub bind_native: SocketAddr, pub max_clients: usize, } impl Plugin for ServerPlugin { fn build(&self, app: &mut App) { - let bind = self.bind.clone(); + let bind_ws = self.bind_ws.clone(); + let bind_native = self.bind_native.clone(); let max_clients = self.max_clients.clone(); app .add_systems(FixedPreUpdate, bevy_replicon::server::increment_tick) // !!important!! do not remove or move .add_systems(Startup, move |mut commands: Commands, channels: Res| { + let server = RenetServer::new(ConnectionConfig::from_channels( channels.server_configs(), channels.client_configs() )); - let server_config = ServerSetupConfig { - current_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), - max_clients: max_clients, - protocol_id: 0, - authentication: ServerAuthentication::Unsecure, - socket_addresses: vec![vec![bind]] - }; - let socket = UdpSocket::bind(bind).unwrap(); + #[cfg(not(target_arch = "wasm32"))] { + let server_config = ServerSetupConfig { + current_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), + max_clients: max_clients, + protocol_id: 0, + authentication: ServerAuthentication::Unsecure, + socket_addresses: vec![vec![bind_native], vec![bind_ws]] + }; + + let rt = tokio::runtime::Runtime::new().unwrap(); + + let ws_config = WebSocketServerConfig { + acceptor: WebSocketAcceptor::Plain { has_tls_proxy: true }, + listen: bind_ws, + max_clients + }; + let ws_server = WebSocketServer::new(ws_config, rt.handle().clone()).unwrap(); + + let native_socket = NativeSocket::new(UdpSocket::bind(bind_native).unwrap()).unwrap(); + + let transport = NetcodeServerTransport::new_with_sockets(server_config, vec![BoxedSocket::new(native_socket), BoxedSocket::new(ws_server)]).unwrap(); - let transport = NetcodeServerTransport::new(server_config, NativeSocket::new(socket).unwrap()).unwrap(); + commands.insert_resource(server); + commands.insert_resource(transport); - commands.insert_resource(server); - commands.insert_resource(transport); + info!("websocket/native server listening"); - info!("websocket server listening"); + } }) .add_plugins(planets_plugin) .add_plugins(world_config_plugin) diff --git a/crates/unified/src/server_plugins.rs b/crates/unified/src/server_plugins.rs index a54fd4882258c756b067fe4c988d80757e4dd641..8219309e8dc478bc1c4d54383ff4d93c3f4e3e9a 100644 --- a/crates/unified/src/server_plugins.rs +++ b/crates/unified/src/server_plugins.rs @@ -11,7 +11,8 @@ use crate::config::planet::{Planet, PlanetConfigCollection}; use crate::config::world::{GlobalWorldConfig, WorldConfig}; pub struct ServerPluginGroup { - pub bind: SocketAddr, + pub bind_ws: SocketAddr, + pub bind_native: SocketAddr, pub tick_rate: f64, pub max_clients: usize } @@ -35,7 +36,8 @@ impl PluginGroup for ServerPluginGroup { .add(TomlAssetPlugin::::new(&["wc.toml"])) .add(TomlAssetPlugin::::new(&["pc.toml"])) .add(crate::server::ServerPlugin { - bind: self.bind, + bind_ws: self.bind_ws, + bind_native: self.bind_native, max_clients: self.max_clients, }) }