handpose-facemesh-demos
🎥🤟 8 minimalistic templates for tfjs mediapipe handpose and facemesh
A Client/Server game networking plugin using QUIC, for the Bevy game engine.
git clone https://github.com/Henauxg/bevy_quinnet.gitHenauxg/bevy_quinnetA Client/Server game networking plugin using QUIC, for the Bevy engine.
QUIC seems really attractive as a game networking protocol because most of the hard-work is done by the protocol specification and the implementation (here Quinn). No need to reinvent the wheel once again on error-prones subjects such as a UDP reliability wrapper, encryption & authentication mechanisms or congestion-control.
Most 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:
-> Roughly 9 points out of 11 by default.
(*) Almost, when sharing a QUIC stream, reliable messages need to be framed.
Quinnet can be used as a transport layer. It currently features:
Although 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. The implementation uses tokio channels internally to communicate with the networking async tasks.
Message helpers such as send_message and try_receive_message require the bincode-messages feature:
bevy_quinnet = { version = "0.21", features = ["bincode-messages"] }
Receiving payloads uses the default recv_channels feature (receive_payload, etc.).
QuinnetClientPlugin to the bevy app: App::new()
// ...
.add_plugins(QuinnetClientPlugin::default())
// ...
.run();
QuinnetClient resource to connect, send & receive messages:fn start_connection(mut client: ResMut<QuinnetClient>) {
client
.open_connection(ClientConnectionConfiguration {
addr_config: ClientAddrConfiguration::from_ips(
SERVER_HOST,
SERVER_PORT,
LOCAL_BIND_IP,
0,
),
cert_mode: CertificateVerificationMode::SkipVerification,
defaultables: Default::default(),
})
.unwrap();
// Once connected, you will receive a ConnectionEvent
receive_message is generic, here ServerMessage is a user provided enum deriving Serialize and Deserialize.fn handle_server_messages(
mut client: ResMut<QuinnetClient>,
/*...*/
) {
while let Some(message) = client.connection_mut().try_receive_message() {
match message {
// Match on your own message types ...
ServerMessage::ClientConnected { client_id, username} => {/*...*/}
ServerMessage::ClientDisconnected { client_id } => {/*...*/}
ServerMessage::ChatMessage { client_id, message } => {/*...*/}
}
}
}
QuinnetServerPlugin to the bevy app: App::new()
/*...*/
.add_plugins(QuinnetServerPlugin::default())
/*...*/
.run();
QuinnetServer resource to start the listening server:fn start_listening(mut server: ResMut<QuinnetServer>) {
server
.start_endpoint(ServerEndpointConfiguration {
addr_config: EndpointAddrConfiguration::from_ip(Ipv6Addr::UNSPECIFIED, 6000),
cert_mode: CertificateRetrievalMode::GenerateSelfSigned {
server_hostname: Ipv6Addr::LOCALHOST.to_string(),
},
defaultables: Default::default(),
})
.unwrap();
}
receive_message is generic, here ClientMessage is a user provided enum deriving Serialize and Deserialize.fn handle_client_messages(
mut server: ResMut<QuinnetServer>,
/*...*/
) {
let mut endpoint = server.endpoint_mut();
for client_id in endpoint.clients() {
while let Some(message) = endpoint.try_receive_message(client_id) {
match message {
// Match on your own message types ...
ClientMessage::Join { username} => {
// Send a messsage to 1 client
endpoint.try_send_message(client_id, ServerMessage::InitClient {/*...*/});
/*...*/
}
ClientMessage::Disconnect { } => {
// Disconnect a client
endpoint.disconnect_client(client_id);
/*...*/
}
ClientMessage::ChatMessage { message } => {
// Send a message to a group of clients
endpoint.try_send_group_message(
client_group, // Iterator of ClientId
ServerMessage::ChatMessage {/*...*/}
);
/*...*/
}
}
}
}
}
You 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.
There are currently 3 types of channels available when you send a message:
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)UnorderedReliable: ensure that messages sent are delivered, in any order (exemple usage: an animation trigger)Unreliable: no guarantees on the delivery or the order of processing by the receiving end (exemple usage: an entity position sent every ticks)When you open a connection/endpoint, some channels are created directly according to the given SendChannelsConfiguration.
// Default channels configuration contains only 1 channel of the OrderedReliable type,
// akin to a TCP connection.
let channels_config = SendChannelsConfiguration::default();
// Creates 2 OrderedReliable channels, and 1 unreliable channel,
// with channel ids being respectively 0, 1 and 2.
let channels_config = SendChannelsConfiguration::from_configs(vec![
ChannelConfig::default_ordered_reliable(),
ChannelConfig::default_ordered_reliable(),
ChannelConfig::default_unreliable(),
]);
Each 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.
let connection = client.connection(); // No channel specified, default channel is used connection.send_message(message); // Specifying the channel id connection.send_message_on(channel_id, message); // Changing the default channel connection.set_default_channel(channel_id);
In 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 issues. Although channels can be defined through a SendChannelsConfiguration, they can also currently be opened & closed at any time. You may have up to 256 differents channels opened simultaneously.
// If you want to create more channels let chat_channel = client.connection().open_channel(ChannelConfig::default()).unwrap(); client.connection().send_message_on(chat_channel, chat_message);
On the server, channels are created and closed at the endpoint level and exist for all current & future clients.
let chat_channel = server.endpoint().open_channel(ChannelConfig::default()).unwrap(); server.endpoint().send_message_on(client_id, chat_channel, chat_message);
Bevy Quinnet (through Quinn & 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.
Here are the current options available to the server and client plugins for the server authentication:
On the client (cert_mode field of ClientConnectionConfiguration):
// To accept any certificate
cert_mode: CertificateVerificationMode::SkipVerification,
// To only accept certificates issued by a Certificate Authority
cert_mode: CertificateVerificationMode::SignedByCertificateAuthority,
// To use the default configuration of the Trust on first use authentication scheme
cert_mode: CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig {
// Provide your own fingerprint store variable/file,
// or configure the actions to apply for each possible certificate verification status.
..Default::default()
}),
On the server (cert_mode field of ServerEndpointConfiguration):
// To generate a new self-signed certificate on each startup
cert_mode: CertificateRetrievalMode::GenerateSelfSigned {
server_hostname: Ipv6Addr::LOCALHOST.to_string(),
},
// To load a pre-existing one from files
cert_mode: CertificateRetrievalMode::LoadFromFile {
cert_file: "./certificates.pem".into(),
key_file: "./privkey.pem".into(),
},
// To load one from files, or to generate a new self-signed one if the files do not exist.
cert_mode: CertificateRetrievalMode::LoadFromFileOrGenerateSelfSigned {
cert_file: "./certificates.pem".into(),
key_file: "./privkey.pem".into(),
save_on_disk: true, // To persist on disk if generated
server_hostname: Ipv6Addr::LOCALHOST.to_string(),
},
See more about certificates in the certificates readme
Examples live in the workspace under examples/ and require cloning this repository (they are not included in the crates.io package).
This demo comes with a headless server, a terminal client, and a shared protocol in examples/chat/.
Start the server with
cargo run -p bevy-quinnet-chat --bin chat-server
and as many clients as needed with
cargo run -p bevy-quinnet-chat --bin chat-client
Type quit to disconnect with a client.
This demo is a modification of the classic Bevy breakout example to turn it into a 2 players versus game.
It hosts a local server from inside a client, instead of a dedicated headless server as in the chat demo. Sources are in examples/breakout/.
It also makes uses of Channels. The server broadcasts the paddle position every tick via the PaddleMoved message on an Unreliable channel, while the BrickDestroyed, BallCollided and the game setup and start are using OrderedReliable channels.
Start two clients with:
cargo run -p bevy-quinnet-breakout
"Host" on one and "Join" on the other.
Bevy Quinnet can be used as a transport in bevy_replicon with the provided bevy_replicon_quinnet.
| bevy_quinnet | bevy |
|---|---|
| 0.21 | 0.19 |
| 0.20 | 0.18 |
| 0.19 | 0.17 |
| 0.17-0.18 | 0.16 |
| 0.12-0.16 | 0.15 |
| 0.9-0.11 | 0.14 |
| 0.7-0.8 | 0.13 |
| 0.6 | 0.12 |
| 0.5 | 0.11 |
| 0.4 | 0.10 |
| 0.2-0.3 | 0.9 |
| 0.1 | 0.8 |
With just installed (cargo install just):
just check # fmt, clippy, check, test, doc, machete
just chat 3 # chat server + 3 clients
just breakout # two breakout windows (Host + Join)
Cargo.lock is committed. After changing dependencies, run cargo check and commit the updated lockfile. just check runs with --locked and will fail otherwise.
See Cargo.toml for the full list.
For logs configuration, see the unoffical bevy cheatbook.
Thanks to the Renet crate for the initial inspiration on the high level API.
This crate is free and open source. All code in this repository is dual-licensed under either:
at your option.
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.
more like this
🎥🤟 8 minimalistic templates for tfjs mediapipe handpose and facemesh
Pathfinding library for calculating all node pairs' shortest paths in an unweighted undirected graph.
search projects, people, and tags