aboutsummaryrefslogtreecommitdiff
path: root/examples/chat
diff options
context:
space:
mode:
Diffstat (limited to 'examples/chat')
-rw-r--r--examples/chat/client.rs152
-rw-r--r--examples/chat/protocol.rs32
-rw-r--r--examples/chat/server.rs132
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();
+}