{"id":28067420,"url":"https://github.com/Henauxg/bevy_quinnet","last_synced_at":"2025-05-12T16:01:52.990Z","repository":{"id":62034120,"uuid":"553170356","full_name":"Henauxg/bevy_quinnet","owner":"Henauxg","description":"A Client/Server game networking plugin using QUIC, for the Bevy game engine.","archived":false,"fork":false,"pushed_at":"2025-04-27T10:44:39.000Z","size":1278,"stargazers_count":302,"open_issues_count":3,"forks_count":19,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-27T11:39:11.570Z","etag":null,"topics":["bevy","bevy-plugin","game-development","networking","quic","quinn"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Henauxg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-17T20:36:30.000Z","updated_at":"2025-04-27T10:43:13.000Z","dependencies_parsed_at":"2024-01-24T12:02:50.422Z","dependency_job_id":"8a342603-0f1a-49ad-a1a7-e05d29455cbc","html_url":"https://github.com/Henauxg/bevy_quinnet","commit_stats":{"total_commits":354,"total_committers":7,"mean_commits":50.57142857142857,"dds":"0.14406779661016944","last_synced_commit":"55cea3af68acf75b005a995f3531337b98dcdf1c"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Henauxg%2Fbevy_quinnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Henauxg%2Fbevy_quinnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Henauxg%2Fbevy_quinnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Henauxg%2Fbevy_quinnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Henauxg","download_url":"https://codeload.github.com/Henauxg/bevy_quinnet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253773089,"owners_count":21962187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bevy","bevy-plugin","game-development","networking","quic","quinn"],"created_at":"2025-05-12T16:01:25.392Z","updated_at":"2025-05-12T16:01:52.971Z","avatar_url":"https://github.com/Henauxg.png","language":"Rust","funding_links":[],"categories":["Networking"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking)\n[![crates.io](https://img.shields.io/crates/v/bevy_quinnet)](https://crates.io/crates/bevy_quinnet)\n[![bevy_quinnet on doc.rs](https://docs.rs/bevy_quinnet/badge.svg)](https://docs.rs/bevy_quinnet)\n\n# Bevy Quinnet\n\nA Client/Server game networking plugin using [QUIC](https://www.chromium.org/quic/), for the [`Bevy`](https://github.com/bevyengine/bevy) engine.\n\n\u003c/div\u003e\n\n## QUIC as a game networking protocol\n\nQUIC was really attractive to me as a game networking protocol because most of the hard-work is done by the protocol specification and the implementation (here [Quinn](https://github.com/quinn-rs/quinn)). No need to reinvent the wheel once again on error-prones subjects such as a UDP reliability wrapper, encryption \u0026 authentication mechanisms or congestion-control.\n\nMost of the features proposed by the big networking libs are supported by default through QUIC. As an example, here is the list of features presented in [GameNetworkingSockets](https://github.com/ValveSoftware/GameNetworkingSockets):\n\n* *Connection-oriented API (like TCP)*: -\u003e by default\n* *... but message-oriented (like UDP), not stream-oriented*: -\u003e by default (*)\n* *Supports both reliable and unreliable message types*: -\u003eby default\n* *Messages can be larger than underlying MTU. The protocol performs fragmentation, reassembly, and retransmission for reliable messages*: -\u003e by default (frag \u0026 reassembly is not done by the protocol for unreliable packets)\n* *A reliability layer [...]. It is based on the \"ack vector\" model from DCCP (RFC 4340, section 11.4) and Google QUIC and discussed in the context of games by Glenn Fiedler [...]*: -\u003e by default.\n* *Encryption. [...] The details for shared key derivation and per-packet IV are based on the design used by Google's QUIC protocol*: -\u003e by default\n* *Tools for simulating packet latency/loss, and detailed stats measurement*: -\u003e Not by default\n* *Head-of-line blocking control and bandwidth sharing of multiple message streams on the same connection.*: -\u003e by default\n* *IPv6 support*: -\u003e by default\n* *Peer-to-peer networking (NAT traversal with ICE + signaling + symmetric connect mode)*: -\u003e Not by default\n* *Cross platform*: -\u003e by default, where UDP is available\n\n-\u003e Roughly 9 points out of 11 by default.\n\n(*) Kinda, when sharing a QUIC stream, reliable messages need to be framed.\n\n## Features\n\nQuinnet can be used as a transport layer. It currently features:\n\n- A Client plugin which can:\n    - Connect/disconnect to/from one or more server\n    - Send \u0026 receive unreliable and ordered/unordered reliable messages\n- A Server plugin which can:\n    - Accept client connections \u0026 disconnect them\n    - Send \u0026 receive unreliable and ordered/unordered reliable messages\n- Both client \u0026 server accept custom protocol structs/enums defined by the user as the message format (as well as raw bytes).\n- Communications are encrypted, and the client can [authenticate the server](#certificates-and-server-authentication).\n\nAlthough Quinn and parts of Quinnet are asynchronous, the APIs exposed by Quinnet for the client and server are synchronous. This makes the surface API easy to work with and adapted to a Bevy usage.\nThe implementation uses [tokio channels](https://tokio.rs/tokio/tutorial/channels) internally to communicate with the networking async tasks.\n\n## Quickstart\n\n### Client\n\n- Add the `QuinnetClientPlugin` to the bevy app:\n\n```rust\n App::new()\n        // ...\n        .add_plugins(QuinnetClientPlugin::default())\n        // ...\n        .run();\n```\n\n- You can then use the `Client` resource to connect, send \u0026 receive messages:\n\n```rust\nfn start_connection(client: ResMut\u003cQuinnetClient\u003e) {\n    client\n        .open_connection(\n            ClientEndpointConfiguration::from_ips(\n                Ipv6Addr::LOCALHOST,\n                6000,\n                Ipv6Addr::UNSPECIFIED,\n                0,\n            ),\n            CertificateVerificationMode::SkipVerification,\n            ChannelsConfiguration::default(),\n        );\n    \n    // When trully connected, you will receive a ConnectionEvent\n```\n\n- To process server messages, you can use a bevy system such as the one below. The function `receive_message` is generic, here `ServerMessage` is a user provided enum deriving `Serialize` and `Deserialize`.\n\n```rust\nfn handle_server_messages(\n    mut client: ResMut\u003cQuinnetClient\u003e,\n    /*...*/\n) {\n    while let Ok(Some((channel_id, message))) = client.connection_mut().receive_message::\u003cServerMessage\u003e() {\n        match message {\n            // Match on your own message types ...\n            ServerMessage::ClientConnected { client_id, username} =\u003e {/*...*/}\n            ServerMessage::ClientDisconnected { client_id } =\u003e {/*...*/}\n            ServerMessage::ChatMessage { client_id, message } =\u003e {/*...*/}\n        }\n    }\n}\n```\n\n### Server\n\n- Add the `QuinnetServerPlugin` to the bevy app:\n\n```rust\n App::new()\n        /*...*/\n        .add_plugins(QuinnetServerPlugin::default())\n        /*...*/\n        .run();\n```\n\n- You can then use the `Server` resource to start the listening server:\n\n```rust\nfn start_listening(mut server: ResMut\u003cQuinnetServer\u003e) {\n    server\n        .start_endpoint(\n            ServerEndpointConfiguration::from_ip(Ipv6Addr::UNSPECIFIED, 6000),\n            CertificateRetrievalMode::GenerateSelfSigned {\n                server_hostname: Ipv6Addr::LOCALHOST.to_string(),\n            },\n            ChannelsConfiguration::default(),\n        )\n        .unwrap();\n}\n```\n\n- To process client messages \u0026 send messages, you can use a bevy system such as the one below. The function `receive_message` is generic, here `ClientMessage` is a user provided enum deriving `Serialize` and `Deserialize`.\n\n```rust\nfn handle_client_messages(\n    mut server: ResMut\u003cQuinnetServer\u003e,\n    /*...*/\n) {\n    let mut endpoint = server.endpoint_mut();\n    for client_id in endpoint.clients() {\n        while let Some((channel_id, message)) = endpoint.try_receive_message_from::\u003cClientMessage\u003e(client_id) {\n            match message {\n                // Match on your own message types ...\n                ClientMessage::Join { username} =\u003e {\n                    // Send a messsage to 1 client\n                    endpoint.send_message(client_id, ServerMessage::InitClient {/*...*/}).unwrap();\n                    /*...*/\n                }\n                ClientMessage::Disconnect { } =\u003e {\n                    // Disconnect a client\n                    endpoint.disconnect_client(client_id);\n                    /*...*/\n                }\n                ClientMessage::ChatMessage { message } =\u003e {\n                    // Send a message to a group of clients\n                    endpoint.send_group_message(\n                            client_group, // Iterator of ClientId\n                            ServerMessage::ChatMessage {/*...*/}\n                        )\n                        .unwrap();\n                    /*...*/\n                }           \n            }\n        }\n    }\n}\n```\n\nYou can also use `endpoint.broadcast_message`, which will send a message to all connected clients. \"Connected\" here means connected to the server plugin, which happens before your own app handshakes/verifications if you have any. Use `send_group_message` if you want to control the recipients.\n\n## Channels\n\nThere are currently 3 types of channels available when you send a message:\n- `OrderedReliable`: ensure that messages sent are delivered, and are processed by the receiving end in the same order as they were sent (exemple usage: chat messages)\n- `UnorderedReliable`: ensure that messages sent are delivered, in any order (exemple usage: an animation trigger)\n- `Unreliable`: no guarantees on the delivery or the order of processing by the receiving end (exemple usage: an entity position sent every ticks)\n\nWhen you open a connection/endpoint, some channels are created directly according to the given `ChannelsConfiguration`.\n\n```rust\n// Default channels configuration contains only 1 channel of the OrderedReliable type,\n// akin to a TCP connection.\nlet channels_config = ChannelsConfiguration::default();\n// Creates 2 OrderedReliable channels, and 1 unreliable channel,\n// with channel ids being respectively 0, 1 and 2.\nlet channels_config = ChannelsConfiguration::from_types(vec![\n    ChannelKind::default(),\n    ChannelKind::default(),\n    ChannelKind::Unreliable]);\n```\n\nEach channel is identified by its own `ChannelId`. Among those, there is a `default` channel which will be used when you don't specify the channel. At startup, the first opened channel becomes the default channel.\n\n```rust\nlet connection = client.connection();\n// No channel specified, default channel is used\nconnection.send_message(message);\n// Specifying the channel id\nconnection.send_message_on(channel_id, message);\n// Changing the default channel\nconnection.set_default_channel(channel_id);\n```\n\nIn some cases, you may want to create more than one channel instance of the same type. As an example, using multiple `OrderedReliable` channels to avoid some [Head of line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking) issues. Although channels can be defined through a `ChannelsConfiguration`, they can also currently be opened \u0026 closed at any time. You may have up to 256 differents channels opened simultaneously.\n\n```rust\n// If you want to create more channels\nlet chat_channel = client.connection().open_channel(ChannelKind::default()).unwrap();\nclient.connection().send_message_on(chat_channel, chat_message);\n```\n\nOn the server, channels are created and closed at the endpoint level and exist for all current \u0026 future clients.\n```rust\nlet chat_channel = server.endpoint().open_channel(ChannelKind::default()).unwrap();\nserver.endpoint().send_message_on(client_id, chat_channel, chat_message);\n```\n\n## Certificates and server authentication\n\nBevy Quinnet (through Quinn \u0026 QUIC) uses TLS 1.3 for authentication, the server needs to provide the client with a certificate confirming its identity, and the client must be configured to trust the certificates it receives from the server.\n\nHere are the current options available to the server and client plugins for the server authentication:\n- Client : \n    - [x] Skip certificate verification (messages are still encrypted, but the server is not authentified)\n    - [x] Accept certificates issued by a Certificate Authority (implemented in [Quinn](https://github.com/quinn-rs/quinn), using [rustls](https://github.com/rustls/rustls))\n    - [x] [Trust on first use](https://en.wikipedia.org/wiki/Trust_on_first_use) certificates (implemented in Quinnet, using [rustls](https://github.com/rustls/rustls))\n- Server:\n    - [x] Generate and issue a self-signed certificate\n    - [x] Issue an already existing certificate (CA or self-signed)\n\nOn the client:\n\n```rust\n    // To accept any certificate\n    client.open_connection(/*...*/, CertificateVerificationMode::SkipVerification);\n    // To only accept certificates issued by a Certificate Authority\n    client.open_connection(/*...*/, CertificateVerificationMode::SignedByCertificateAuthority);\n    // To use the default configuration of the Trust on first use authentication scheme\n    client.open_connection(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig {\n            // You can configure TrustOnFirstUse through the TrustOnFirstUseConfig:\n            // Provide your own fingerprint store variable/file,\n            // or configure the actions to apply for each possible certificate verification status.\n            ..Default::default()\n        }),\n    );\n```\n\nOn the server:\n\n```rust\n    // To generate a new self-signed certificate on each startup \n    server.start_endpoint(/*...*/, CertificateRetrievalMode::GenerateSelfSigned { \n        server_hostname: Ipv6Addr::LOCALHOST.to_string(),\n    });\n    // To load a pre-existing one from files\n    server.start_endpoint(/*...*/, CertificateRetrievalMode::LoadFromFile {\n        cert_file: \"./certificates.pem\".into(),\n        key_file: \"./privkey.pem\".into(),\n    });\n    // To load one from files, or to generate a new self-signed one if the files do not exist.\n    server.start_endpoint(/*...*/, CertificateRetrievalMode::LoadFromFileOrGenerateSelfSigned {\n        cert_file: \"./certificates.pem\".into(),\n        key_file: \"./privkey.pem\".into(),\n        save_on_disk: true, // To persist on disk if generated\n        server_hostname: Ipv6Addr::LOCALHOST.to_string(),\n    });\n```\n\nSee more about certificates in the [certificates readme](docs/Certificates.md)\n\n## Examples\n\n\u003cdetails\u003e\n  \u003csummary\u003eChat example\u003c/summary\u003e\n\nThis demo comes with an headless [server](examples/chat/server.rs), a [terminal client](examples/chat/client.rs) and a shared [protocol](examples/chat/protocol.rs).\n\nStart the server with `cargo run --example chat-server` and as many clients as needed with `cargo run --example chat-client`. Type `quit` to disconnect with a client.\n\n![terminal_chat_demo](https://user-images.githubusercontent.com/19689618/197757086-0643e6e7-6c69-4760-9af6-cb323529dc52.gif)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eBreakout versus example\u003c/summary\u003e\n\nThis demo is a modification of the classic [Bevy breakout](https://bevyengine.org/examples/games/breakout/) example to turn it into a 2 players versus game.\n\nIt hosts a local server from inside a client, instead of a dedicated headless server as in the chat demo. You can find a [server module](examples/breakout/server.rs), a [client module](examples/breakout/client.rs), a shared [protocol](examples/breakout/protocol.rs) and the [bevy app schedule](examples/breakout/breakout.rs).\n\nIt also makes uses of [`Channels`](#channels). The server broadcasts the paddle position every tick via the `PaddleMoved` message on an `Unreliable` channel, the `BrickDestroyed` and `BallCollided` events are emitted on an `UnorderedReliable` channel, while the game setup and start are using the default `OrderedReliable` channel.\n\nStart two clients with `cargo run --example breakout`, \"Host\" on one and \"Join\" on the other.\n\n[breakout_versus_demo_short.mp4](https://user-images.githubusercontent.com/19689618/213700921-85967bd7-9a47-44ac-9471-77a33938569f.mp4)\n\u003c/details\u003e\n\nExamples can be found in the [examples](examples) directory.\n\n## Replicon integration\n\nBevy Quinnet can be used as a transport in [`bevy_replicon`](https://github.com/projectharmonia/bevy_replicon) with the provided [`bevy_replicon_quinnet`](https://github.com/Henauxg/bevy_replicon_quinnet).\n\n## Compatible Bevy versions\n\n| bevy_quinnet | bevy |\n| :----------- | :--- |\n| 0.17         | 0.16 |\n| 0.12-0.16    | 0.15 |\n| 0.9-0.11     | 0.14 |\n| 0.7-0.8      | 0.13 |\n| 0.6          | 0.12 |\n| 0.5          | 0.11 |\n| 0.4          | 0.10 |\n| 0.2-0.3      | 0.9  |\n| 0.1          | 0.8  |\n\n## Misc\n\n### Cargo features\n\n*Find the list and description in [cargo.toml](Cargo.toml)*\n\n- `shared-client-id` *[default]*: When a new client connects to the server, the server sends its `ClientId` to the client. The client will consider himself `Connected` once it receives this id. When not enabled, the client does not know its `ClientId` on the server.\n\n### Logs\n\nFor logs configuration, see the unoffical [bevy cheatbook](https://bevy-cheatbook.github.io/features/log.html).\n\n### Limitations\n\n* QUIC is not available directly in a Browser (it is used by Browsers but not exposed directly as an API). [WebTransport](https://web.dev/webtransport/) is starting to be available on Browsers, however this crate does not provide nor use a WebTransport implementation, and as such, does not support Browser target.\n\n## Credits\n\nThanks to the [Renet](https://github.com/lucaspoffo/renet) crate for the inspiration on the high level API.\n\n## License\n\nThis crate is free and open source. All code in this repository is dual-licensed under either:\n\n* MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))\n* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))\n\nat your option.\n\nUnless 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHenauxg%2Fbevy_quinnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHenauxg%2Fbevy_quinnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHenauxg%2Fbevy_quinnet/lists"}