renet

repository·master·Indexed 21 days ago

https://github.com/lucaspoffo/renet

A high-performance Rust network library for Server/Client games, optimized for fast-paced genres like FPS. It features connection management, authentication, and flexible message delivery guarantees via channels (ReliableOrdered, ReliableUnordered, and Unreliable). The library supports multiple transport layers, including Netcode and Steam, and provides a Bevy plugin (bevy_renet v5.0.0) for integration into the Bevy game engine.

Tokens
30.1K
Snippets
95
Records
120
Agent score
75%

What's inside renet

  1. What is Renetcode?

    master

    Renetcode is a connection-based client/server protocol designed primarily for games using UDP, though it is compatible with other transport methods. It implements the Netcode 1.02 standard.

    Key features include:

    • Encrypted and signed packets for security.
    • Secure client connections using connect tokens.
    • Connection-based protocol architecture.

    It provides protection against several common network attacks:

    • Zombie clients
    • Man-in-the-middle (MITM)
    • DDoS amplification
    • Packet replay attacks
  2. What is Renet?

    master
    Renet is a network library for Server/Client games written in Rust, specifically optimized for fast-paced competitive games like FPS. It provides connection management, message-based communication via channels with varying delivery guarantees, packet fragmentation/reassembly, and support for authentication and encryption through transport layers like renet_netcode or renet_steam.
  3. How Renet channels and delivery guarantees work

    master

    Renet uses unidirectional channels for communication. Channels are configured via ConnectionConfig.client_channels_config (for client-to-server) and ConnectionConfig.server_channels_config (for server-to-client).

    Each channel is defined by a ChannelConfig which includes a unique channel_id, max_memory_usage_bytes, and a send_type that determines the delivery guarantee:

    • SendType::ReliableOrdered: Guarantees both message delivery and the order of arrival. Requires a resend_time (Duration) to specify how long to wait before resending a lost message.
    • SendType::ReliableUnordered: Guarantees message delivery but does not guarantee the order of arrival. Also requires a resend_time.
    • SendType::Unreliable: No guarantee of delivery or order.
    // No guarantee of message delivery or order
    let send_type = SendType::Unreliable;
    
    // guarantee of message delivery and order
    let send_type = SendType::ReliableOrdered {
        // If a message is lost, it will be resent after this duration
        resend_time: Duration::from_millis(300)
    };
    
    // Guarantee of message delivery but not order
    let send_type = SendType::ReliableUnordered {
        resend_time: Duration::from_millis(300)
    };
    
    let channel_config = ChannelConfig {
        // The id for the channel, must be unique within its own list,
        // but it can be repeated between the server and client lists.
        channel_id: 0,
        // Maximum number of bytes that the channel may hold without acknowledgement of messages before becoming full.
        max_memory_usage_bytes: 5 * 1024 * 1024, // 5 megabytes
        send_type
    };
  4. Customizing Renet execution schedules

    master
    If you require fine-grained control over when Renet updates occur, you can bypass the RenetServerPlugin and RenetClientPlugin. Instead, manually call the public update functions implemented by these plugins within your own Bevy systems. You must still add the appropriate transport layer plugins to your App.
  5. Visualize Renet Server and Client metrics with RenetServerVisualizer

    master

    To monitor server-side metrics and individual connected client metrics, use RenetServerVisualizer.

    1. Initialization: Create the visualizer with a capacity and style: RenetServerVisualizer::<200>::new(RenetVisualizerStyle::default()).
    2. Client Management: Listen for ServerEvents. When a ServerEvent::ClientConnected(client_id, ...) occurs, call visualizer.add_client(client_id). When a ServerEvent::ClientDisconnected(client_id) occurs, call visualizer.remove_client(client_id).
    3. Data Update: Call visualizer.update(&server) to collect metrics from all currently tracked clients.
    4. Rendering: Call visualizer.show_window(egui_ctx) to draw the metrics window.
    let mut visualizer = RenetServerVisualizer::<200>::new(RenetVisualizerStyle::default());
    // ..
    
    loop {
        // Update Renet Server
        server.update(delta).unwrap();
    
        // Add/Remove clients from the visualizer
        while let Some(event) = server.get_event() {
            match event {
                ServerEvent::ClientConnected(client_id, user_data) => {
                    visualizer.add_client(client_id);
                    // ...
                }
                ServerEvent::ClientDisconnected(client_id) => {
                    visualizer.remove_client(client_id);
                    // ...
                }
            }
        }
    
        // Add all clients metrics to the visualizer
        visualizer.update(&server);
    
        // Draws a new egui window with all clients metrics
        visualizer.show_window(egui_ctx);
    
        // ..
    }
  6. Visualize Renet Client metrics with RenetClientVisualizer

    master

    To monitor client-side network metrics using egui, use RenetClientVisualizer. You must initialize it with a capacity (e.g., RenetClientVisualizer::<200>::new) and a RenetVisualizerStyle. In your main loop, you need to pass the client's network information to the visualizer using add_network_info and then call show_window with your egui context to render the metrics window.

    let mut visualizer = RenetClientVisualizer::<200>::new(RenetVisualizerStyle::default());
    // ..
    
    loop {
        // Update Renet Client
        client.update(delta).unwrap();
        // Add metrics to the visualizer
        visualizer.add_network_info(client.network_info());
    
        // Draws a new egui window with the metrics
        visualizer.show_window(egui_ctx);
    
        // ..
    }
  7. Implement a Renet Client loop

    master

    To run a client, initialize a RenetClient with a ConnectionConfig. You must set up a transport layer (e.g., NetcodeClientTransport) with authentication details (like ClientAuthentication::Unsecure or secure variants).

    In your gameplay loop:

    1. Call client.update(delta_time) to process internal state.
    2. Call transport.update(delta_time, &mut client) to bridge the network and the client.
    3. If client.is_connected() is true, use client.receive_message(channel) to read messages from the server.
    4. Use client.send_message(channel, message) to send data to the server.
    5. Call transport.send_packets(&mut client) to flush outgoing packets.
    let mut client = RenetClient::new(ConnectionConfig::default());
    
    // Setup transport layer using renet_netcode
    const server_addr: SocketAddr = "127.0.0.1:5000".parse().unwrap();
    let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
    let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
    let authentication = ClientAuthentication::Unsecure {
        server_addr,
        client_id: 0,
        user_data: None,
        protocol_id: 0,
    };
    
    let mut transport = NetcodeClientTransport::new(current_time, authentication, socket).unwrap();
    
    // Your gameplay loop
    loop {
        let delta_time = Duration::from_millis(16);
        // Receive new messages and update client
        client.update(delta_time);
        transport.update(delta_time, &mut client).unwrap();
        
        if client.is_connected() {
            // Receive message from server
            while let Some(message) = client.receive_message(DefaultChannel::ReliableOrdered) {
                // Handle received message
            }
            
            // Send message
            client.send_message(DefaultChannel::ReliableOrdered, "client text");
        }
     
        // Send packets to server using the transport layer
        transport.send_packets(&mut client)?;
        
        std::thread::sleep(delta_time);
    }
  8. Run the Demo Chat application

    master

    The demo_chat application is a chat client/server built with egui that demonstrates how to use renet. It showcases error handling, connection state management (Loading, Connected, Disconnected), and client self-hosting. To test the application, you must run two separate instances of the app.

    # Navigate to the demo_chat directory and run the application
    cd demo_chat
    cargo run
  9. Run the Bevy Demo with Netcode transport

    master

    To run the Bevy demo using the netcode transport, use the following commands in your terminal. This setup is suitable for standard network connections.

    Server:

    cargo run --bin server --features netcode

    Client:

    cargo run --bin client --features netcode
    # Server
    cargo run --bin server --features netcode
    
    # Client
    cargo run --bin client --features netcode
  10. Set up a Renet Server in Bevy

    master

    To implement a server, you must add the RenetServerPlugin and a transport plugin (like NetcodeServerPlugin) to your Bevy App. You then need to initialize a RenetServer and a transport instance (e.g., NetcodeServerTransport), inserting both as resources.

    Key steps:

    1. Add RenetServerPlugin.
    2. Insert RenetServer as a resource.
    3. Add the transport plugin (e.g., NetcodeServerPlugin).
    4. Initialize and insert the transport (e.g., NetcodeServerTransport) as a resource.
    5. Use systems to interact with the server via ResMut<RenetServer> and MessageReader<ServerEvent> for connection events.
    fn main() {
        let mut app = App::new();
        app.add_plugin(RenetServerPlugin);
    
        let server = RenetServer::new(ConnectionConfig::default());
        app.insert_resource(server);
    
        // Transport layer setup
        app.add_plugin(NetcodeServerPlugin);
        let server_addr = "127.0.0.1:5000".parse().unwrap();
        let socket = UdpSocket::bind(server_addr).unwrap();
        let server_config = ServerConfig {
            current_time: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(),
            max_clients: 64,
            protocol_id: 0,
            public_addresses: vec![server_addr],
            authentication: ServerAuthentication::Unsecure
        };
        let transport = NetcodeServerTransport::new(server_config, socket).unwrap();
        app.insert_resource(transport);
    
        app.add_system(send_message_system);
        app.add_system(receive_message_system);
        app.add_system(handle_events_system);
    }
  11. Run the Bevy Demo with Steam transport

    master

    To run the Bevy demo using the steam transport, use the following commands. Note that the client requires a Steam Host ID, which is printed to the console when the server starts.

    Server:

    cargo run --bin server --features steam

    Client:

    cargo run --bin client --features steam -- [HOST_STEAM_ID]

    Replace [HOST_STEAM_ID] with the ID provided by the running server.

    # Server
    cargo run --bin server --features steam
    
    # Client
    cargo run --bin client --features steam -- [HOST_STEAM_ID]