diff options
Diffstat (limited to 'examples/chat')
-rw-r--r-- | examples/chat/client.rs | 152 | ||||
-rw-r--r-- | examples/chat/protocol.rs | 32 | ||||
-rw-r--r-- | examples/chat/server.rs | 132 |
3 files changed, 316 insertions, 0 deletions
diff --git a/examples/chat/client.rs b/examples/chat/client.rs new file mode 100644 index 0000000..b0f330d --- /dev/null +++ b/examples/chat/client.rs @@ -0,0 +1,152 @@ +use std::{ + collections::HashMap, + thread::{self, sleep}, + time::Duration, +}; + +use bevy::{ + app::{AppExit, ScheduleRunnerPlugin}, + log::LogPlugin, + prelude::{info, warn, App, Commands, CoreStage, EventReader, EventWriter, Res, ResMut}, +}; +use bevy_quinnet::{ + client::{ + CertificateVerificationMode, Client, ClientConfigurationData, ConnectionEvent, + QuinnetClientPlugin, + }, + ClientId, +}; +use rand::{distributions::Alphanumeric, Rng}; +use tokio::sync::mpsc; + +use protocol::{ClientMessage, ServerMessage}; + +mod protocol; + +#[derive(Debug, Clone, Default)] +struct Users { + self_id: ClientId, + names: HashMap<ClientId, String>, +} + +pub fn on_app_exit(app_exit_events: EventReader<AppExit>, client: Res<Client>) { + if !app_exit_events.is_empty() { + client.send_message(ClientMessage::Disconnect {}).unwrap(); + // TODO Clean: event to let the async client send his last messages. + sleep(Duration::from_secs_f32(0.1)); + } +} + +fn handle_server_messages(mut client: ResMut<Client>, mut users: ResMut<Users>) { + while let Ok(Some(message)) = client.receive_message::<ServerMessage>() { + match message { + ServerMessage::ClientConnected { + client_id, + username, + } => { + info!("{} joined", username); + users.names.insert(client_id, username); + } + ServerMessage::ClientDisconnected { client_id } => { + if let Some(username) = users.names.remove(&client_id) { + println!("{} left", username); + } else { + warn!("ClientDisconnected for an unknown client_id: {}", client_id) + } + } + ServerMessage::ChatMessage { client_id, message } => { + if let Some(username) = users.names.get(&client_id) { + if client_id != users.self_id { + println!("{}: {}", username, message); + } + } else { + warn!("Chat message from an unknown client_id: {}", client_id) + } + } + ServerMessage::InitClient { + client_id, + usernames, + } => { + users.self_id = client_id; + users.names = usernames; + } + } + } +} + +fn handle_terminal_messages( + client: ResMut<Client>, + mut terminal_messages: ResMut<mpsc::Receiver<String>>, + mut app_exit_events: EventWriter<AppExit>, +) { + while let Ok(message) = terminal_messages.try_recv() { + if message == "quit" { + app_exit_events.send(AppExit); + } else { + client + .send_message(ClientMessage::ChatMessage { message: message }) + .expect("Failed to send chat message"); + } + } +} + +fn start_terminal_listener(mut commands: Commands) { + let (from_terminal_sender, from_terminal_receiver) = mpsc::channel::<String>(100); + + thread::spawn(move || loop { + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer).unwrap(); + from_terminal_sender + .try_send(buffer.trim_end().to_string()) + .unwrap(); + }); + + commands.insert_resource(from_terminal_receiver); +} + +fn start_connection(client: ResMut<Client>) { + client + .connect( + ClientConfigurationData::new("127.0.0.1".to_string(), 6000, "0.0.0.0".to_string(), 0), + CertificateVerificationMode::SkipVerification, + ) + .unwrap(); + + // You can already send message(s) even before being connected, they will be buffered. In this example we will wait for a ConnectionEvent. We could also check client.is_connected() +} + +fn handle_client_events(connection_events: EventReader<ConnectionEvent>, client: ResMut<Client>) { + if !connection_events.is_empty() { + // We are connected + let username: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(7) + .map(char::from) + .collect(); + + println!("--- Joining with name: {}", username); + println!("--- Type 'quit' to disconnect"); + + client + .send_message(ClientMessage::Join { name: username }) + .unwrap(); + + connection_events.clear(); + } +} + +fn main() { + App::new() + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(LogPlugin::default()) + .add_plugin(QuinnetClientPlugin::default()) + .insert_resource(Users::default()) + .add_startup_system(start_terminal_listener) + .add_startup_system(start_connection) + .add_system(handle_terminal_messages) + .add_system(handle_server_messages) + .add_system(handle_client_events) + // CoreStage::PostUpdate so that AppExit events generated in the previous stage are available + .add_system_to_stage(CoreStage::PostUpdate, on_app_exit) + .run(); +} diff --git a/examples/chat/protocol.rs b/examples/chat/protocol.rs new file mode 100644 index 0000000..6a51e50 --- /dev/null +++ b/examples/chat/protocol.rs @@ -0,0 +1,32 @@ +use std::collections::HashMap; + +use bevy_quinnet::ClientId; +use serde::{Deserialize, Serialize}; + +// Messages from clients +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ClientMessage { + Join { name: String }, + Disconnect {}, + ChatMessage { message: String }, +} + +// Messages from the server +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerMessage { + ClientConnected { + client_id: ClientId, + username: String, + }, + ClientDisconnected { + client_id: ClientId, + }, + ChatMessage { + client_id: ClientId, + message: String, + }, + InitClient { + client_id: ClientId, + usernames: HashMap<ClientId, String>, + }, +} diff --git a/examples/chat/server.rs b/examples/chat/server.rs new file mode 100644 index 0000000..26fa75e --- /dev/null +++ b/examples/chat/server.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; + +use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, prelude::*}; +use bevy_quinnet::{ + server::{ + CertificateRetrievalMode, ConnectionLostEvent, QuinnetServerPlugin, Server, + ServerConfigurationData, + }, + ClientId, +}; + +use protocol::{ClientMessage, ServerMessage}; + +mod protocol; + +#[derive(Debug, Clone, Default)] +struct Users { + names: HashMap<ClientId, String>, +} + +fn handle_client_messages(mut server: ResMut<Server>, mut users: ResMut<Users>) { + while let Ok(Some((message, client_id))) = server.receive_message::<ClientMessage>() { + match message { + ClientMessage::Join { name } => { + if users.names.contains_key(&client_id) { + warn!( + "Received a Join from an already connected client: {}", + client_id + ) + } else { + info!("{} connected", name); + users.names.insert(client_id, name.clone()); + // Initialize this client with existing state + server + .send_message( + client_id, + ServerMessage::InitClient { + client_id: client_id, + usernames: users.names.clone(), + }, + ) + .unwrap(); + // Broadcast the connection event + server + .send_group_message( + users.names.keys().into_iter(), + ServerMessage::ClientConnected { + client_id: client_id, + username: name, + }, + ) + .unwrap(); + } + } + ClientMessage::Disconnect {} => { + // We tell the server to disconnect this user + server.disconnect_client(client_id); + handle_disconnect(&mut server, &mut users, client_id); + } + ClientMessage::ChatMessage { message } => { + info!( + "Chat message | {:?}: {}", + users.names.get(&client_id), + message + ); + server + .send_group_message( + users.names.keys().into_iter(), + ServerMessage::ChatMessage { + client_id: client_id, + message: message, + }, + ) + .unwrap(); + } + } + } +} + +fn handle_server_events( + mut connection_lost_events: EventReader<ConnectionLostEvent>, + mut server: ResMut<Server>, + mut users: ResMut<Users>, +) { + // The server signals us about users that lost connection + for client in connection_lost_events.iter() { + handle_disconnect(&mut server, &mut users, client.id); + } +} + +/// Shared disconnection behaviour, whether the client lost connection or asked to disconnect +fn handle_disconnect(server: &mut ResMut<Server>, users: &mut ResMut<Users>, client_id: ClientId) { + // Remove this user + if let Some(username) = users.names.remove(&client_id) { + // Broadcast its deconnection + server + .send_group_message( + users.names.keys().into_iter(), + ServerMessage::ClientDisconnected { + client_id: client_id, + }, + ) + .unwrap(); + info!("{} disconnected", username); + } else { + warn!( + "Received a Disconnect from an unknown or disconnected client: {}", + client_id + ) + } +} + +fn start_listening(mut server: ResMut<Server>) { + server + .start( + ServerConfigurationData::new("127.0.0.1".to_string(), 6000, "0.0.0.0".to_string()), + CertificateRetrievalMode::GenerateSelfSigned, + ) + .unwrap(); +} + +fn main() { + App::new() + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(LogPlugin::default()) + .add_plugin(QuinnetServerPlugin::default()) + .insert_resource(Users::default()) + .add_startup_system(start_listening) + .add_system(handle_client_messages) + .add_system(handle_server_events) + .run(); +} |