General purpose client/server networking library written in Rust, built on top of the QUIC protocol which is implemented by quinn

Overview

Overview

"This library stinks!" ... "Unless you like durian"

durian is a client-server networking library built on top of the QUIC protocol which is implemented in Rust by quinn.

It provides a thin abstraction layer above the lower-level details of connection management, byte management, framing, and more, to make writing netcode easier and allow the user to focus on the messaging contents instead. Serialization and deserialization are built into the APIs so you can send and receive exact Packets as structs for ease of development.

durian is a general purpose library, but was made primarily for me to dabble in game development. It has been tested and working with the Bevy game engine.

Full documentation can be found at https://docs.rs/durian/latest/durian/

Crates.io: https://crates.io/crates/durian/

Disclaimer

This library is in very early (but very active!) development, meaning a LOT of it will change rapidly.
In its current state, it's usable to create a quick multiplayer demo. I use it myself to learn game development.

This is not production ready, and is missing a lot of features to make it production ready (see Features list below).

However, if you are trying to build something and want to avoid a lot of the headache of lower-level netcode details, and don't need the "production" features, such as a multiplayer game demo, LAN sandbox applications, etc., then feel free to try it out!

durian's goal is to make it as simple as possible to setup netcode (See the examples below).

Features

  • Simultaneous basic Client/Server connection management and operations
  • Both async and sync APIs for different caller contexts
  • Multiplexing without head of line blocking (QUIC feature)
    • Dedicated stream for each Packet type and multi-threaded
  • Reliable packets: guaranteed delivery of all messages
  • Ordered packets: packets are received in the same order they are sent on each stream
  • Packet Fragmentation and re-assembly automatically for you
  • Macros to ease creation of Packets
  • Send and receive packets simultaneously
  • Various Client/Server configurations
    • keep-alive-intervals
    • idle-timeout
    • ALPN Protocol

Not yet done

  • Certificate authentication between client-server
  • More complex connection configurations such as:
    • Pluggable cryptography
  • Handshake protocol
  • Connection/streams re-establishment
  • Reusing an Endpoint across multiple PacketManagers (for client connected to multiple servers, or having different Packet contracts)
  • Better Error handling/messaging
  • Unreliable packets
  • Unordered packets
  • Probably lots more

Cargo.toml Dependency

Add durian to your Cargo.toml via cargo add durian or manually:

[dependencies]
durian = "0.2.x"

Packet/PacketBuilder

There are 2 steps needed to create a Packet to be used with durian:

  1. durian allows for structuring Packets as simple structs. The structs must implement Trait Packet, which has a single function Packet::as_bytes() which will be called for serializing the Packet into bytes to be sent over the wire between client and server.

  2. There also needs to be a struct that implements PacketBuilder which is used to deserialize from bytes back into your Packet struct via the PacketBuilder::read() function.

For your convenience, durian is bundled with durian_macros which contains a few macros that help autogenerate Impl blocks for a struct for both Packet and PacketBuilder. The only requirement is the struct must be de/serializable, meaning all nested fields also need to be de/serializable.

#[bincode_packet] will de/serialize your Packet using bincode and applies necessary derive macros automatically for you.

use durian::bincode_packet;

// Automatically implements Packet, and generates a PositionPacketBuilder that implements 
// PacketBuilder.  You can also add other macros such as derive macros so long as they don't
// conflict with what #[bincode_packet] adds (See bincode_packet documentation).
#[bincode_packet]
#[derive(Debug)]
struct Position {
    x: i32,
    y: i32
}

// Works for Unit (empty) structs as well
#[bincode_packet]
struct Ack;

You can also use the derive macros (BinPacket and UnitPacket) manually:

use durian::serde::{Deserialize, Serialize};
use durian::{BinPacket, UnitPacket};

#[derive(Serialize, Deserialize, BinPacket)]
#[serde(crate = "durian::serde")]
struct Position { x: i32, y: i32 }

#[derive(UnitPacket)]
struct Ack;

PacketManager

PacketManager is what you will use to initiate connections between clients/servers, and send/receive Packets.

A PacketManager would be created on each client to connect to a single server, and one created on the server to connect to multiple clients. It contains both synchronous and asynchronous APIs, so you can call the functions both from a synchronous context, or within an async runtime (Note: the synchronous path will create a separate isolated async runtime context per PacketManager instance.)

There are 4 basic steps to using the PacketManager, which would be done on both the client and server side:

  1. Create a PacketManager via new() or, if calling from an async context, new_for_async()

  2. Register the Packets and PacketBuilders that the PacketManager will receive and send using register_receive_packet() and register_send_packet().
    The ordering of Packet registration matters for the receive channel and send channel each - the client and server must register the same packets in the same order, for the opposite channels.

    • In other words, the client must register receive packets in the same order the server registers the same as send packets, and vice versa, the client must register send packets in the same order the server registers the same as receive packets. This helps to ensure the client and servers are in sync on what Packets to send/receive, almost like ensuring they are on the same "version" so to speak, and is used to properly identify Packets.
  3. Initiate connection(s) with init_client() (or the async variant async_init_client() if on the client side, else use init_server() (or the async variant async_init_server) if on the server side.

  4. Send packets using any of broadcast(), send(), send_to() or the respective async variants if calling from an async context already. Receive packets using any of received_all() , received(), or the respective async variants.

Putting these together:

use durian::PacketManager;
use durian_macros::bincode_packet;

#[bincode_packet]
struct Position { x: i32, y: i32 }
#[bincode_packet]
struct ServerAck;
#[bincode_packet]
struct ClientAck;
#[bincode_packet]
struct InputMovement { direction: String }

fn packet_manager_example() {
    // Create PacketManager
    let mut manager = PacketManager::new();

    // Register send and receive packets
    manager.register_receive_packet::<Position>(PositionPacketBuilder).unwrap();
    manager.register_receive_packet::<ServerAck>(ServerAckPacketBuilder).unwrap();
    manager.register_send_packet::<ClientAck>().unwrap();
    manager.register_send_packet::<InputMovement>().unwrap();

    // Initialize connection to an address
    manager.init_connections(true, 2, 2, "127.0.0.1:5000", Some("127.0.0.1:5001"), 0, None).unwrap();

    // Send and receive packets
    manager.broadcast(InputMovement { direction: "North".to_string() }).unwrap();
    manager.received_all::<Position, PositionPacketBuilder>(false).unwrap();

    // The above PacketManager is for the client.  Server side is similar except the packets
    // are swapped between receive vs send channels.

    // Create PacketManager
    let mut server_manager = PacketManager::new();

    // Register send and receive packets
    server_manager.register_receive_packet::<ClientAck>(ClientAckPacketBuilder).unwrap();
    server_manager.register_receive_packet::<InputMovement>(InputMovementPacketBuilder).unwrap();
    server_manager.register_send_packet::<Position>().unwrap();
    server_manager.register_send_packet::<ServerAck>().unwrap();

    // Initialize a client
    let client_config = ClientConfig::new("127.0.0.1:5001", "127.0.0.1:5000", 2, 2);
    server_manager.init_client(client_config).unwrap();

    // Send and receive packets
    server_manager.broadcast(Position { x: 1, y: 3 }).unwrap();
    server_manager.received_all::<InputMovement, InputMovementPacketBuilder>(false).unwrap();
}

Examples

For beginners, creating packets to be sent between clients/server should be extremely straight-forward and the above examples should cover most of what you'd need. For more complex scenarios, such as serializing/deserializing packets in a custom way, can be done by implementing the various Traits yourself, or through extra configurations in the PacketManager.

For a comprehensive minimal example, see the example crate.

I also use this library myself for simple game development. See the multisnakegame repo.

Debugging

durian uses the log API with debug and trace logs. Enable debug logging to see update logs from durian, and enable trace logging to see packet byte transmissions.

You might also like...
A 2d top-down shooter written in Bevy, capable of handling 100K enemies
A 2d top-down shooter written in Bevy, capable of handling 100K enemies

Bevy 2D Shooter This is a 2d top-down shooter written in Rust using the Bevy game engine. It's capable of handling 100K enemies and uses a kd-tree to

An alternative ggez implementation on top of miniquad.
An alternative ggez implementation on top of miniquad.

Good Web Game good-web-game is a wasm32-unknown-unknown implementation of a ggez subset on top of miniquad. Originally built to run Zemeroth on the we

Victorem - easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust.

Victorem Easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust. Example Cargo.toml [dependencies] vict

Solana Game Server is a decentralized game server running on Solana, designed for game developers

Solana Game Server* is the first decentralized Game Server (aka web3 game server) designed for game devs. (Think web3 SDK for game developers as a ser

Minecraft Protocol Library

Protocol Minecraft Protocol Library This branch is dedicated to the Minecraft Bedrock Edition Protocol. If you have any concerns or questions you can

This is the core library with the full abstraction and implementation of the Minecraft protocol and logic. (Currently WIP)

MineRust (WIP) This is the core library with the full abstraction and implementation of the Minecraft protocol and logic. This project is currently WI

Game examples implemented in rust console applications primarily for educational purposes.

rust-console-games A collection of game examples implemented as rust console applications primarily for providing education and inspiration. :) Game *

Conway's Game of Life implemented for Game Boy Advance in Rust

game-of-life An implementation of a Conway's Game of Life environment on GBA. The ultimate game should have two modes: Edit and Run mode which can be

Multi-channel signed distance field (MSDF) generator for fonts implemented in pure Rust.

msdfont WIP - school started so less updates from now on :(( Multi-channel signed distance field (MSDF) generator for fonts implemented in pure Rust.

Comments
Releases(0.2.0)
  • 0.2.0(Dec 10, 2022)

    Full Changelog: https://github.com/spoorn/durian/compare/0.1.0...0.2.0

    Docs: https://docs.rs/durian/0.2.0/durian/ Crates.io: https://crates.io/crates/durian/0.2.0

    Major Changes

    • Full documentation done
    • Separated client vs server initialization via a new ClientConfig and ServerConfig, and PacketManager::init_client() vs PacketManager::init_server()
    • Added configuration for keep-alive-intervals and idle-timeouts
    • send() packet variants now properly return a SendError on error
    • durian_macros can no longer be opted out from, and always come bundled with durian

    Minor Changes

    • Added benchmarking package for later
    • Minor bug fixes to flesh out the APIs
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Dec 10, 2022)

Owner
Michael
Michael
A feature-rich, production-ready, general purpose 2D/3D game engine written in Rust with a scene editor.

A feature-rich, production-ready, general purpose 2D/3D game engine written in Rust with a scene editor.

rg3d engine 5.4k Jan 4, 2023
A general-purpose transfom interpolation plugin for fixed timesteps for the Bevy game engine.

bevy_transform_interpolation A general-purpose Transform interpolation plugin for fixed timesteps for the Bevy game engine. What Is This For? A lot of

Joona Aalto 22 Oct 22, 2024
Simple event-based client-server networking library for Bevy

Bevy Client Server Events Simple event-based client-server networking library for Bevy. Easily send bevy events to/from a client/server without worryi

Edouard Poitras 5 Sep 20, 2023
A simple authoritative server networking library for Bevy.

Bevy Networking Plugin This is a simple networking plugin for the Bevy game engine. This plugin provides the building blocks which game developers can

Matthew Fisher 35 Jan 1, 2023
Scion is a tiny 2D game library built on top of wgpu, winit and legion.

Scion is a 2D game library made in rust. Please note that this project is in its first milestones and is subject to change according to convience need

Jérémy Thulliez 143 Dec 25, 2022
3d transformation gizmo built on top of the egui library.

egui-gizmo 3d transformation gizmo built on top of the egui library. Try it out in a web demo Usage let gizmo = Gizmo::new("My gizmo") .view_matri

Urho Laukkarinen 69 Dec 10, 2022
A novel path-based approach to UI built on top of existing Bevy features

Bevy Lunex A novel path-based approach to UI built on top of existing Bevy features. A recreation of Cyberpunk 2077 menu in Bevy using Lunex Table of

Bytestring 4 Jun 29, 2023
A cross platform (wasm included) networking library!

bootleg_networking A cross platform (wasm included) networking library! A networking plugin for the Bevy game engine that wraps around bevy_networking

William Batista 51 Jan 1, 2023
🎮 A Realtime Multiplayer Server/Client Game example built entirely with Rust 🦀

Example of a ?? Realtime Multiplayer Web Game Server/Client built entirely using Rust ??

Nick Baker 5 Dec 17, 2022
wecs (wckd-ecs) is a simple ECS library suitable for general use.

wecs wecs (wckd-ecs) is a simple ECS library heavily based on bevy_ecs. Motivation As part of my "Road to Rust GameDev" journey, I wanted to learn how

João Victor 9 Jul 2, 2023