generated from tipragot/rust
Save
This commit is contained in:
parent
466265d451
commit
ca2433a40f
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1278,8 +1278,10 @@ dependencies = [
|
||||||
name = "border-wars"
|
name = "border-wars"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bevnet",
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy_egui",
|
"bevy_egui",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -23,7 +23,9 @@ use serde::Serialize;
|
||||||
#[derive(Component, Resource)]
|
#[derive(Component, Resource)]
|
||||||
pub struct Connection {
|
pub struct Connection {
|
||||||
/// The underlying [TcpStream] used for the connection.
|
/// The underlying [TcpStream] used for the connection.
|
||||||
stream: TcpStream,
|
///
|
||||||
|
/// If `None`, the connection is self connected.
|
||||||
|
stream: Option<TcpStream>,
|
||||||
|
|
||||||
/// Contains the buffers that are not yet being sent.
|
/// Contains the buffers that are not yet being sent.
|
||||||
send_buffers: Mutex<LinkedList<(usize, Vec<u8>)>>,
|
send_buffers: Mutex<LinkedList<(usize, Vec<u8>)>>,
|
||||||
|
@ -57,7 +59,7 @@ impl Connection {
|
||||||
fn new(stream: TcpStream, secret_key: &Key<Aes128Gcm>) -> io::Result<Self> {
|
fn new(stream: TcpStream, secret_key: &Key<Aes128Gcm>) -> io::Result<Self> {
|
||||||
stream.set_nonblocking(true)?;
|
stream.set_nonblocking(true)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
stream,
|
stream: Some(stream),
|
||||||
send_buffers: Mutex::new(LinkedList::new()),
|
send_buffers: Mutex::new(LinkedList::new()),
|
||||||
received_messages: Mutex::new(HashMap::new()),
|
received_messages: Mutex::new(HashMap::new()),
|
||||||
receive_message_len: None,
|
receive_message_len: None,
|
||||||
|
@ -68,6 +70,20 @@ impl Connection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [Connection] that connects to the current game.
|
||||||
|
fn new_self() -> Self {
|
||||||
|
Self {
|
||||||
|
stream: None,
|
||||||
|
send_buffers: Mutex::new(LinkedList::new()),
|
||||||
|
received_messages: Mutex::new(HashMap::new()),
|
||||||
|
receive_message_len: None,
|
||||||
|
receive_message_nonce: None,
|
||||||
|
receive_filled_len: 0,
|
||||||
|
receive_buffer: Vec::new(),
|
||||||
|
cipher: Aes128Gcm::new(&Key::<Aes128Gcm>::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [Connection] that connects to the given connection string.
|
/// Creates a new [Connection] that connects to the given connection string.
|
||||||
///
|
///
|
||||||
/// This function is blocking.
|
/// This function is blocking.
|
||||||
|
@ -99,7 +115,7 @@ impl Connection {
|
||||||
/// Sends a message and its id over the connection.
|
/// Sends a message and its id over the connection.
|
||||||
///
|
///
|
||||||
/// This function is not blocking.
|
/// This function is not blocking.
|
||||||
pub fn send(&self, message: &[u8], id: u16) -> io::Result<()> {
|
fn send(&self, message: &[u8], id: u16) -> io::Result<()> {
|
||||||
// Add the id to the message.
|
// Add the id to the message.
|
||||||
let mut message_with_id = Vec::with_capacity(message.len() + 2);
|
let mut message_with_id = Vec::with_capacity(message.len() + 2);
|
||||||
message_with_id.extend_from_slice(message);
|
message_with_id.extend_from_slice(message);
|
||||||
|
@ -150,7 +166,12 @@ impl Connection {
|
||||||
/// receives any pending messages that have not been received yet.
|
/// receives any pending messages that have not been received yet.
|
||||||
///
|
///
|
||||||
/// This function is not blocking.
|
/// This function is not blocking.
|
||||||
pub fn update(&mut self) -> io::Result<()> {
|
fn update(&mut self) -> io::Result<()> {
|
||||||
|
// Quit if the connection is self connected.
|
||||||
|
let Some(stream) = self.stream.as_mut() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
// Lock the send buffers.
|
// Lock the send buffers.
|
||||||
let send_buffers = self
|
let send_buffers = self
|
||||||
.send_buffers
|
.send_buffers
|
||||||
|
@ -160,7 +181,7 @@ impl Connection {
|
||||||
// Looping over the send buffers to send the messages.
|
// Looping over the send buffers to send the messages.
|
||||||
while let Some((offset, buffer)) = send_buffers.front_mut() {
|
while let Some((offset, buffer)) = send_buffers.front_mut() {
|
||||||
// Writing the buffer to the stream.
|
// Writing the buffer to the stream.
|
||||||
match self.stream.write(&buffer[*offset..]) {
|
match stream.write(&buffer[*offset..]) {
|
||||||
Ok(n) => *offset += n,
|
Ok(n) => *offset += n,
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => break,
|
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => break,
|
||||||
|
@ -211,6 +232,11 @@ impl Connection {
|
||||||
///
|
///
|
||||||
/// This function is not blocking.
|
/// This function is not blocking.
|
||||||
fn receive_partial(&mut self, len: u16) -> io::Result<bool> {
|
fn receive_partial(&mut self, len: u16) -> io::Result<bool> {
|
||||||
|
// Quit if the connection is self connected.
|
||||||
|
let Some(stream) = self.stream.as_mut() else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
let len = len as usize;
|
let len = len as usize;
|
||||||
|
|
||||||
// Resizing the buffer if it is not large enough.
|
// Resizing the buffer if it is not large enough.
|
||||||
|
@ -227,7 +253,7 @@ impl Connection {
|
||||||
// Reading from the stream.
|
// Reading from the stream.
|
||||||
let start_index = self.receive_filled_len;
|
let start_index = self.receive_filled_len;
|
||||||
let receive_buffer = &mut self.receive_buffer[start_index..start_index + len];
|
let receive_buffer = &mut self.receive_buffer[start_index..start_index + len];
|
||||||
let received_len = self.stream.read(receive_buffer);
|
let received_len = stream.read(receive_buffer);
|
||||||
self.receive_filled_len += match received_len {
|
self.receive_filled_len += match received_len {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
|
@ -322,7 +348,7 @@ impl Connection {
|
||||||
/// Receives all the messages with the given id from the connection.
|
/// Receives all the messages with the given id from the connection.
|
||||||
///
|
///
|
||||||
/// This function is not blocking.
|
/// This function is not blocking.
|
||||||
pub fn receive(&self, id: u16) -> io::Result<LinkedList<Vec<u8>>> {
|
fn receive(&self, id: u16) -> io::Result<LinkedList<Vec<u8>>> {
|
||||||
// Locking the received messages.
|
// Locking the received messages.
|
||||||
let mut received_messages = self.received_messages.lock().map_err(|e| {
|
let mut received_messages = self.received_messages.lock().map_err(|e| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
|
@ -374,7 +400,7 @@ impl Listener {
|
||||||
/// Accepts a new [Connection].
|
/// Accepts a new [Connection].
|
||||||
///
|
///
|
||||||
/// This function is not blocking.
|
/// This function is not blocking.
|
||||||
pub fn accept(&self) -> io::Result<Option<Connection>> {
|
fn accept(&self) -> io::Result<Option<Connection>> {
|
||||||
match self.0.accept() {
|
match self.0.accept() {
|
||||||
Ok((stream, _)) => Connection::new(stream, &self.3).map(Some),
|
Ok((stream, _)) => Connection::new(stream, &self.3).map(Some),
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
|
||||||
|
@ -402,33 +428,16 @@ impl Drop for Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [Component] that marks an entity as containing a self [Connection].
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SelfConnection;
|
||||||
|
|
||||||
/// A resource that store the last event id used to register an [Event].
|
/// A resource that store the last event id used to register an [Event].
|
||||||
///
|
///
|
||||||
/// This is used to give an unique id to each event.
|
/// This is used to give an unique id to each event.
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
struct LastEventId(u16);
|
struct LastEventId(u16);
|
||||||
|
|
||||||
/// An utility function that sends an [Event] to a [Connection].
|
|
||||||
fn send_event<T: Event + Serialize + DeserializeOwned>(
|
|
||||||
connection: &Connection,
|
|
||||||
event_id: u16,
|
|
||||||
event: &T,
|
|
||||||
) {
|
|
||||||
// Serializing the event.
|
|
||||||
let message = match bincode::serialize(event) {
|
|
||||||
Ok(message) => message,
|
|
||||||
Err(e) => {
|
|
||||||
error!("failed to serialize event: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sending the event.
|
|
||||||
if let Err(e) = connection.send(&message, event_id) {
|
|
||||||
info!("failed to send event: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An [Event] received from the server on the client.
|
/// An [Event] received from the server on the client.
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct ServerEvent<T: Event + Serialize + DeserializeOwned>(pub T);
|
pub struct ServerEvent<T: Event + Serialize + DeserializeOwned>(pub T);
|
||||||
|
@ -511,11 +520,42 @@ pub struct SendAll<T: Event + Serialize + DeserializeOwned>(pub T);
|
||||||
impl<T: Event + Serialize + DeserializeOwned> SendAll<T> {
|
impl<T: Event + Serialize + DeserializeOwned> SendAll<T> {
|
||||||
/// Returns a system that sends the events to all the connected clients on
|
/// Returns a system that sends the events to all the connected clients on
|
||||||
/// the server.
|
/// the server.
|
||||||
pub fn send(event_id: u16) -> impl Fn(EventReader<Self>, Query<&Connection>) {
|
pub fn send(
|
||||||
move |mut events: EventReader<Self>, connections: Query<&Connection>| {
|
event_id: u16,
|
||||||
|
) -> impl Fn(EventReader<Self>, EventWriter<ServerEvent<T>>, Query<&Connection>) {
|
||||||
|
move |mut events: EventReader<Self>,
|
||||||
|
mut writer: EventWriter<ServerEvent<T>>,
|
||||||
|
connections: Query<&Connection>| {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
|
// Serializing the event.
|
||||||
|
let message = match bincode::serialize(&event.0) {
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to serialize event: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for connection in connections.iter() {
|
for connection in connections.iter() {
|
||||||
send_event(connection, event_id, &event.0);
|
// If the connection is self connected.
|
||||||
|
if connection.stream.is_none() {
|
||||||
|
// Deserializing the message.
|
||||||
|
let event: T = match bincode::deserialize(&message) {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to deserialize event: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sending the event.
|
||||||
|
writer.send(ServerEvent(event));
|
||||||
|
} else {
|
||||||
|
// Sending the event through the connection.
|
||||||
|
if let Err(e) = connection.send(&message, event_id) {
|
||||||
|
error!("failed to send event: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -529,11 +569,44 @@ pub struct SendTo<T: Event + Serialize + DeserializeOwned>(pub Entity, pub T);
|
||||||
impl<T: Event + Serialize + DeserializeOwned> SendTo<T> {
|
impl<T: Event + Serialize + DeserializeOwned> SendTo<T> {
|
||||||
/// Returns a system that sends the events to a specific client on the
|
/// Returns a system that sends the events to a specific client on the
|
||||||
/// server.
|
/// server.
|
||||||
pub fn send(event_id: u16) -> impl Fn(EventReader<Self>, Query<&Connection>) {
|
pub fn send(
|
||||||
move |mut events: EventReader<Self>, connections: Query<&Connection>| {
|
event_id: u16,
|
||||||
|
) -> impl Fn(EventReader<Self>, EventWriter<ServerEvent<T>>, Query<&Connection>) {
|
||||||
|
move |mut events: EventReader<Self>,
|
||||||
|
mut writer: EventWriter<ServerEvent<T>>,
|
||||||
|
connections: Query<&Connection>| {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
|
// Serializing the event.
|
||||||
|
let message = match bincode::serialize(&event.0) {
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to serialize event: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match connections.get(event.0) {
|
match connections.get(event.0) {
|
||||||
Ok(connection) => send_event(connection, event_id, &event.1),
|
Ok(connection) => {
|
||||||
|
// If the connection is self connected.
|
||||||
|
if connection.stream.is_none() {
|
||||||
|
// Deserializing the message.
|
||||||
|
let event: T = match bincode::deserialize(&message) {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to deserialize event: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sending the event.
|
||||||
|
writer.send(ServerEvent(event));
|
||||||
|
} else {
|
||||||
|
// Sending the event through the connection.
|
||||||
|
if let Err(e) = connection.send(&message, event_id) {
|
||||||
|
error!("failed to send event: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => error!("tried to send event to non-existent connection: {e}"),
|
Err(e) => error!("tried to send event to non-existent connection: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -548,12 +621,38 @@ pub struct SendToServer<T: Event + Serialize + DeserializeOwned>(pub T);
|
||||||
impl<T: Event + Serialize + DeserializeOwned> SendToServer<T> {
|
impl<T: Event + Serialize + DeserializeOwned> SendToServer<T> {
|
||||||
/// Returns a system that sends the events from a client to the server.
|
/// Returns a system that sends the events from a client to the server.
|
||||||
pub fn send(event_id: u16) -> SystemConfigs {
|
pub fn send(event_id: u16) -> SystemConfigs {
|
||||||
(move |mut events: EventReader<Self>, connection: Option<Res<Connection>>| {
|
(move |mut events: EventReader<Self>,
|
||||||
|
mut writer: EventWriter<ClientEvent<T>>,
|
||||||
|
connection: Res<Connection>,
|
||||||
|
self_connections: Query<Entity, (With<Connection>, With<SelfConnection>)>| {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
if let Some(connection) = connection.as_ref() {
|
// Serializing the event.
|
||||||
send_event(connection, event_id, &event.0);
|
let message = match bincode::serialize(&event.0) {
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to serialize event: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the connection is self connected.
|
||||||
|
if connection.stream.is_none() {
|
||||||
|
// Deserializing the message.
|
||||||
|
let event: T = match bincode::deserialize(&message) {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to deserialize event: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sending the event to the self connection.
|
||||||
|
writer.send(ClientEvent(self_connections.single(), event));
|
||||||
} else {
|
} else {
|
||||||
error!("tried to send event to non-existent connection");
|
// Sending the event through the connection.
|
||||||
|
if let Err(e) = connection.send(&message, event_id) {
|
||||||
|
error!("failed to send event: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -585,6 +684,32 @@ impl NetworkPlugin {
|
||||||
commands.remove_resource::<Connection>();
|
commands.remove_resource::<Connection>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A system that automatically create a new self [Connection] when a
|
||||||
|
/// [Listener] is created.
|
||||||
|
fn create_self_connection(mut commands: Commands, listener: Res<Listener>) {
|
||||||
|
if listener.is_changed() {
|
||||||
|
commands.insert_resource(Connection::new_self());
|
||||||
|
commands.spawn((Connection::new_self(), SelfConnection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A system that accepts new [Connection] from the [Listener] of the
|
||||||
|
/// server.
|
||||||
|
fn accept_new_connections(mut commands: Commands, listener: Res<Listener>) {
|
||||||
|
// Accept new connections.
|
||||||
|
let connection = match listener.accept() {
|
||||||
|
Ok(Some(connection)) => connection,
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to accept new connection: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the new connection into the world.
|
||||||
|
commands.spawn(connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for NetworkPlugin {
|
impl Plugin for NetworkPlugin {
|
||||||
|
@ -592,8 +717,10 @@ impl Plugin for NetworkPlugin {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Last,
|
Last,
|
||||||
(
|
(
|
||||||
|
Self::accept_new_connections.run_if(resource_exists::<Listener>()),
|
||||||
Self::update_client_connections,
|
Self::update_client_connections,
|
||||||
Self::update_server_connection.run_if(resource_exists::<Connection>()),
|
Self::update_server_connection.run_if(resource_exists::<Connection>()),
|
||||||
|
Self::create_self_connection.run_if(resource_exists::<Listener>()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,3 +12,5 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = "0.12.1"
|
bevy = "0.12.1"
|
||||||
bevy_egui = "0.24.0"
|
bevy_egui = "0.24.0"
|
||||||
|
bevnet = { path = "../bevnet" }
|
||||||
|
serde = "1.0.196"
|
||||||
|
|
|
@ -1,13 +1,155 @@
|
||||||
//! The main entry point of the game.
|
//! The main entry point of the game.
|
||||||
|
|
||||||
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
|
use std::collections::LinkedList;
|
||||||
|
|
||||||
|
use bevnet::{
|
||||||
|
ClientEvent, Connection, Listener, NetworkAppExt, NetworkPlugin, SelfConnection, SendAll,
|
||||||
|
SendToServer, ServerEvent,
|
||||||
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy_egui::egui::{self, Key};
|
||||||
|
use bevy_egui::EguiContexts;
|
||||||
use border_wars::menu::MenuPlugin;
|
use border_wars::menu::MenuPlugin;
|
||||||
use border_wars::GameState;
|
use border_wars::GameState;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A message that can be sent between clients.
|
||||||
|
#[derive(Event, Serialize, Deserialize, Clone)]
|
||||||
|
struct Message {
|
||||||
|
/// The username of the sender.
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
/// The content of the message.
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A system that display a chat window.
|
||||||
|
fn chat_ui(
|
||||||
|
mut ctx: EguiContexts,
|
||||||
|
mut username: Local<String>,
|
||||||
|
mut message: Local<String>,
|
||||||
|
mut messages: Local<LinkedList<Message>>,
|
||||||
|
mut reader: EventReader<ServerEvent<Message>>,
|
||||||
|
mut writer: EventWriter<SendToServer<Message>>,
|
||||||
|
listener: Option<Res<Listener>>,
|
||||||
|
) {
|
||||||
|
// Read all new messages from the server.
|
||||||
|
for event in reader.read() {
|
||||||
|
messages.push_back(event.0.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the chat window.
|
||||||
|
egui::Window::new("Chat").show(ctx.ctx_mut(), |ui| {
|
||||||
|
if let Some(listener) = listener.as_ref() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Game ID: ");
|
||||||
|
});
|
||||||
|
ui.text_edit_singleline(&mut listener.connection_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Username:");
|
||||||
|
ui.text_edit_singleline(&mut *username);
|
||||||
|
});
|
||||||
|
|
||||||
|
for message in messages.iter() {
|
||||||
|
ui.label(format!("{}: {}", message.username, message.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Message:");
|
||||||
|
let text = ui.text_edit_singleline(&mut *message);
|
||||||
|
|
||||||
|
if (text.lost_focus() && text.ctx.input(|i| i.key_pressed(Key::Enter)))
|
||||||
|
|| ui.button("Send").clicked()
|
||||||
|
{
|
||||||
|
let username = username.trim();
|
||||||
|
if username.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let trimmed_message = message.trim();
|
||||||
|
if trimmed_message.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer.send(SendToServer(Message {
|
||||||
|
username: username.to_owned(),
|
||||||
|
content: trimmed_message.to_owned(),
|
||||||
|
}));
|
||||||
|
message.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A system that handles chat events from the clients.
|
||||||
|
fn handle_chat_events(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut reader: EventReader<ClientEvent<Message>>,
|
||||||
|
mut writer: EventWriter<SendAll<Message>>,
|
||||||
|
) {
|
||||||
|
for event in reader.read() {
|
||||||
|
info!("{} sent: {}", event.1.username, event.1.content);
|
||||||
|
commands
|
||||||
|
.entity(event.0)
|
||||||
|
.insert(LastUsername(event.1.username.clone()));
|
||||||
|
writer.send(SendAll(event.1.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Component] that stores the last username used by a client.
|
||||||
|
#[derive(Component)]
|
||||||
|
struct LastUsername(String);
|
||||||
|
|
||||||
|
/// A system that display the admin window for the server.
|
||||||
|
fn admin_ui(
|
||||||
|
mut ctx: EguiContexts,
|
||||||
|
mut commands: Commands,
|
||||||
|
connections: Query<
|
||||||
|
(Entity, Option<&LastUsername>),
|
||||||
|
(With<Connection>, Without<SelfConnection>),
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
egui::Window::new("Admin").show(ctx.ctx_mut(), |ui| {
|
||||||
|
ui.label("Connected clients:");
|
||||||
|
for (entity, last_username) in connections.iter() {
|
||||||
|
let name = last_username
|
||||||
|
.map(|u| u.0.clone())
|
||||||
|
.unwrap_or(format!("{entity:?}"));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(name);
|
||||||
|
if ui
|
||||||
|
.button("Kick")
|
||||||
|
.on_hover_text("Kicks the client")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A system to return to the menu if we got disconnected.
|
||||||
|
fn return_to_menu(
|
||||||
|
mut next_state: ResMut<NextState<GameState>>,
|
||||||
|
connection: Option<Res<Connection>>,
|
||||||
|
) {
|
||||||
|
if connection.is_none() {
|
||||||
|
next_state.set(GameState::Menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_state::<GameState>()
|
.add_state::<GameState>()
|
||||||
.add_plugins(MenuPlugin)
|
.add_plugins(MenuPlugin)
|
||||||
|
.add_plugins(NetworkPlugin)
|
||||||
|
.add_network_event::<Message>()
|
||||||
|
.add_systems(Update, chat_ui.run_if(resource_exists::<Connection>()))
|
||||||
|
.add_systems(Update, (handle_chat_events, return_to_menu))
|
||||||
|
.add_systems(Update, admin_ui.run_if(resource_exists::<Listener>()))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! The main menu of the game.
|
//! The main menu of the game.
|
||||||
|
|
||||||
|
use bevnet::{Connection, Listener};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_egui::{egui, EguiContexts, EguiPlugin};
|
use bevy_egui::{egui, EguiContexts, EguiPlugin};
|
||||||
|
|
||||||
|
@ -18,8 +19,11 @@ impl Plugin for MenuPlugin {
|
||||||
}
|
}
|
||||||
/// Display the UI of the menu to host a game or join one.
|
/// Display the UI of the menu to host a game or join one.
|
||||||
fn menu_ui(
|
fn menu_ui(
|
||||||
|
mut commands: Commands,
|
||||||
mut ctx: EguiContexts,
|
mut ctx: EguiContexts,
|
||||||
mut connection_string: Local<String>,
|
mut connection_string: Local<String>,
|
||||||
|
mut join_error_message: Local<Option<String>>,
|
||||||
|
mut host_error_message: Local<Option<String>>,
|
||||||
mut next_state: ResMut<NextState<GameState>>,
|
mut next_state: ResMut<NextState<GameState>>,
|
||||||
) {
|
) {
|
||||||
egui::CentralPanel::default().show(ctx.ctx_mut(), |ui| {
|
egui::CentralPanel::default().show(ctx.ctx_mut(), |ui| {
|
||||||
|
@ -33,16 +37,37 @@ fn menu_ui(
|
||||||
ui.text_edit_singleline(&mut *connection_string);
|
ui.text_edit_singleline(&mut *connection_string);
|
||||||
|
|
||||||
if ui.button("Join").clicked() {
|
if ui.button("Join").clicked() {
|
||||||
|
match Connection::connect(&connection_string) {
|
||||||
|
Ok(connection) => {
|
||||||
|
commands.insert_resource(connection);
|
||||||
next_state.set(GameState::Game);
|
next_state.set(GameState::Game);
|
||||||
// TODO: connect to the game
|
}
|
||||||
|
Err(e) => {
|
||||||
|
*join_error_message = Some(format!("Failed to connect: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if let Some(message) = join_error_message.as_ref() {
|
||||||
|
ui.label(message);
|
||||||
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
if ui.button("Create new game").clicked() {
|
if ui.button("Create new game").clicked() {
|
||||||
|
match Listener::new() {
|
||||||
|
Ok(listener) => {
|
||||||
|
info!("Game ID: {}", listener.connection_string());
|
||||||
|
commands.insert_resource(listener);
|
||||||
next_state.set(GameState::Lobby);
|
next_state.set(GameState::Lobby);
|
||||||
// TODO: create a new game
|
}
|
||||||
|
Err(e) => {
|
||||||
|
*host_error_message = Some(format!("Failed to start server: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(message) = host_error_message.as_ref() {
|
||||||
|
ui.label(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue