4 releases (breaking)

0.4.0 Sep 25, 2024
0.3.0 Jul 8, 2024
0.2.0 Jun 26, 2024
0.1.0 Jun 25, 2024

#1209 in Network programming

Download history 284/week @ 2024-06-24 118/week @ 2024-07-08 25/week @ 2024-07-29 18/week @ 2024-09-16 151/week @ 2024-09-23 22/week @ 2024-09-30

191 downloads per month
Used in warqueen_derive

MIT/Apache

58KB
738 lines

Warqueen - Hobby-scale networking crate

Message based, no async, no blocking. Uses the awesome Quinn networking crate.

As the client, just connect, send messages, and poll received messages whenever, in a non-blocking way, without async. As the server, same stuff, and poll new client connections whenever. Can do that in a few loops, very simple API, easy to understand and use.

Message types are constrainted to only one possible type for sending and one possible type for receiving, swapping these type roles for the server and clients. An enum for both is natural.

Hope you like :3

Usage

Let's start with a client.

First define the two message types for the communication with the server. The type that the client can send (to the server) and the type that the client can receive (from the server).

use serde::{Deserialize, Serialize};
use warqueen::{NetReceive, NetSend};

#[derive(Serialize, NetSend)]
enum MessageClientToServer {
	String(String),
}

#[derive(Deserialize, NetReceive)]
enum MessageServerToClient {
	String(String),
}

The NetSend and NetReceive derive macros implement traits of the same name that allow marked types to be sent and received respectively.

Then create the ClientNetworking that connects to a server, and use it in a loop.

// Full type is `ClientNetworking<MessageClientToServer, MessageServerToClient>`.
let mut client = ClientNetworking::new(server_address);

loop {
	// Handling received messages from the server, as well as connection events.
	while let Some(event) = client.poll_event_from_server() {
		match event {
			ClientEvent::Message(message) => match message {
				MessageServerToClient::String(content) => println!("The server says \"{content}\""),
			},
			ClientEvent::Connected => println!("Connected"),
			ClientEvent::Disconnected => println!("Disconnected"),
		}
	}

	// Sending a message to the server.
	let message = MessageClientToServer::String("hiii :3".to_string());
	client.send_message_to_server(message);*

	sleep(Duration::from_millis(10));
}

Note that the connection is not established immediately, it may take some time, and when it finally happens there would be a ClientEvent::Connected event polled to notify the client. If the connection is never established, then such event won't ever be polled; a timeout error may be relevant in such case.

When it is time to go, the connection can be closed properly as follows.

client.disconnect().wait_for_proper_disconnection();

Note that if the call to ClientNetworking::disconnect is not made in the main thread, then the DisconnectionHandle that it returns should be passed to the main thread (for example via a channel) and only then should DisconnectionHandle::wait_for_proper_disconnection be called. See the documentation for more.

A server is very similar, it should use the same message types as the client, except with the traits NetSend and NetReceive implemented the other way around (because the server sends whan the client receives, and vice versa) (just swap the derives, including the serde ones). If both the server and the client use the same type definitions then it is even better: both types can implement both traits (#[derive(Serialize, Deserialize, NetSend, NetReceived)]).

Then create the ServerListenerNetworking that listens for new clients that want to connect, and use it in a loop.

// Full type is `ServerListenerNetworking<MessageServerToClient, MessageClientToServer>`.
let server_listener = ServerListenerNetworking::new(desired_port);
let mut clients = vec![];

loop {
	// Handling new clients connections.
	while let Some(client) = server_listener.poll_client() {
		// Client type is `ClientOnServerNetworking<MessageServerToClient, MessageClientToServer>`.
		println!("Connected to a client at {}", client.client_address());
		clients.push(client);
	}

	// Handling clients...

	sleep(Duration::from_millis(10));
}

Clients polled from ServerListenerNetworking::poll_client are of type ClientOnServerNetworking and are basically the server-side equivalent of ClientNetworking. For each connected ClientNetworking in a client program, there is a corresponding ClientOnServerNetworking in a server program.

// Handling received messages as well as connection events from all clients.
for (index, client) in clients.iter().enumerate() {
	while let Some(event) = client.poll_event_from_client() {
		match event {
			ClientOnServerEvent::Message(message) => match message {
				MessageClientToServer::String(content) => {
					println!("Client {index} says \"{content}\"");
				},
			},
			ClientOnServerEvent::Disconnected => {
				println!("Client {index} disconnected");
			},
		}
	}
}

// Sending a message to a client.
let message = MessageServerToClient::String("nyaa~".to_string());
clients[some_index_whatever].send_message_to_client(message);

Note that the server-side equivalent of the ClientEvent::Connected event is the fact that ServerListenerNetworking::poll_client returns a connection to a client, there is no need for a ClientOnServerEvent::Connected because a ClientOnServerNetworking is already connected to the client when created.

Disconnecting is the same as for clients, it also comes with a DisconnectionHandle (for each disconnection) to take special care to carry to the main thread if the diconnection is not made on the main thread.

clients[some_index_whatever].disconnect().wait_for_proper_disconnection();

Scale

Hobby-scale! Just a small wrapper around Quinn (best Rust netwroking crate for game networking?) to get an API that I like. It may be suited for game networking, hopefully! At least I am using it in my game.

See the TODO list for potential future features. Also don't hesitate to contribute!

Note that any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions.

License

Copyright 2024 Jeanne DEMOUSSEL.

This project is licensed under either of

at your option.

Dependencies

~14–24MB
~437K SLoC