5 unstable releases

0.3.1 Apr 11, 2024
0.3.0 Apr 8, 2024
0.2.0 Apr 8, 2024
0.1.1 Mar 24, 2024
0.1.0 Mar 24, 2024

#330 in Game dev

MIT/Apache

69KB
1.5K SLoC

bevy_rtc

MIT/Apache 2.0 crates.io docs.rs

bevy_rtc is a simple, multi-platform WebRTC networking library for client<->server topologies using Bevy.

  • Simple: no knowledge of WebRTC is needed
  • Easy unreliable (UDP-like) and reliable (TCP-like) networking on web
  • Bevy system parameters for reading and writing packets
  • Derive macros for creating protocols
  • Support for unbounded and bounded buffers
  • Easily read instantaneous and smoothed latency

Quickstart

For your client:

cargo add bevy_rtc -F client

For your server:

cargo add bevy_rtc -F server

Run the demos and instructions.

Bevy version support

bevy bevy_rtc
0.13 0.1-0.3, main
< 0.13 unsupported

Cargo features

All features are opt-in.

  • server - Provides necessary networking for server applications
  • client - Provides necessary networking for client applications
  • binary - Sends networking packets as binary instead of JSON (the default)

[!IMPORTANT]

  • The client feature supports both WASM and native targets.
  • The server feature is native only.

The server is native only because it serves a signaling server (and is the first peer of itself). While a WASM server is possible by depending on an external WebRTC signaling server, WASM is (currently) single threaded like JavaScript. If you really want a WASM server, I would accept PRs, but you probably don't want one!

Demos

There are two demos provided, a simple ping/pong demo and a painting game. Run one demo server, and any number of respective clients.

  • Server (Native only)
cargo run -p painting-server
  • Client (Native)
cargo run -p painting-client
  • Client (Web)
cargo install wasm-server-runner
cd demos/painting-client
cargo run --target wasm32-unknown-unknown

Instructions

Protocols

Place your packet definitions in a shared location.

#[derive(Payload)]
pub enum MyPacket {
    Ping,
    Pong
}

Need help? See the demo protocol source or open an issue.

Server

  • Ensure your client has the server feature

    cargo add bevy_rtc -F server
    
  • Add the RtcServerPlugin to your app.

    .add_plugins(RtcServerPlugin { port: 3536 })
    
  • Register your protocols as bounded or unbounded.

    • Bounded protocols will only keep the most recent N payloads received to read.
    • Unbounded protocols will keep all payloads using a resizable buffer.

    Payloads are only flushed by a system when they get read!
    It is recommended to keep your protocols bounded on the server.

    // Only choose one!
    .add_bounded_protocol::<MyPacket>(5) // Only keep the most recent 5 payloads for reading
    .add_unbounded_protocol::<MyPacket>() // Keep all payloads until read
    
  • Add systems to read and send payloads.

    .add_systems(
        Update,
        |mut server: RtcServer<MyPacket>| {
            for (peer_id, packet) in server.read() {
                if let MyPacket::Ping = packet {
                    server.reliable_to_peer(peer_id, MyPacket::Pong);
                }
            }
        })
    

    Need help? See the ping-server or painting-server source or open an issue.

Client

  • Ensure your client has the client feature

    cargo add bevy_rtc -F client
    
  • Add the RtcClientPlugin to your app.

    .add_plugins(RtcClientPlugin)
    
  • Register your protocols as bounded or unbounded.

    • Bounded protocols will only keep the most recent N payloads received to read.
    • Unbounded protocols will keep all payloads using a resizable buffer.

    Payloads are only flushed by a system when they get read!
    It is recommended to keep your protocols unbounded on the client.

    // Only choose one!
    .add_bounded_protocol::<MyPacket>(5) // Only keep the most recent 5 payloads for reading
    .add_unbounded_protocol::<MyPacket>() // Keep all payloads until read
    
  • Add systems to read and send payloads.

    .add_systems(
        Update,
        {
            |mut client: RtcClient<PingPayload>| {
                client.reliable_to_host(PingPayload::Ping);
            }
        }
        .run_if(
            // Only send every second, and if we are connected.
            on_timer(Duration::from_secs(1)).and_then(
                state_exists_and_equals(RtcClientStatus::Connected),
            ),
        ),
    )
    .add_systems(Update, |mut client: RtcClient<PingPayload>| {
        for payload in client.read() {
            if let PingPayload::Pong = payload {
                info!("..Received pong!");
            }
        }
    })
    

    Need help? See the ping-client or ping-server source or open an issue.

Community

All Loopy projects and development happens in the Loopy Discord. The discord is open to the public.

Contributions are welcome by pull request. The Rust code of conduct applies.

License

Licensed under either of

at your option

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~21–69MB
~1M SLoC