M .cargo/config.toml => .cargo/config.toml +3 -0
@@ 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
M Cargo.lock => Cargo.lock +102 -4
@@ 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"
@@ 5113,6 5113,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 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]]
@@ 7665,6 7692,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 7795,6 7831,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 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]]
@@ 8347,6 8396,49 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 8783,6 8875,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +12 -2
@@ 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
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +50 -19
@@ 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<RepliconChannels>| {
- 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)
M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +3 -0
@@ 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 {
M crates/unified/src/main.rs => crates/unified/src/main.rs +11 -4
@@ 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
});
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +35 -15
@@ 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<RepliconChannels>| {
+
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)
M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +4 -2
@@ 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::<GlobalWorldConfig>::new(&["wc.toml"]))
.add(TomlAssetPlugin::<PlanetConfigCollection>::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,
})
}