Skip to content

Commit

Permalink
Merge pull request #416 from cBournhonesque/cb/add-disconnection-error
Browse files Browse the repository at this point in the history
Return DisconnectReason in the DisconnectEvent for clients
  • Loading branch information
cBournhonesque authored May 29, 2024
2 parents 6c56e4d + 2326bb6 commit 90a7a6d
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 45 deletions.
2 changes: 1 addition & 1 deletion examples/common/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ pub(crate) fn build_client_netcode_config(

/// Parse the settings into a `NetConfig` that is used to configure how the lightyear client
/// connects to the server
pub(crate) fn get_client_net_config(settings: &Settings, client_id: u64) -> client::NetConfig {
pub fn get_client_net_config(settings: &Settings, client_id: u64) -> client::NetConfig {
let server_addr = SocketAddr::new(
settings.client.server_addr.into(),
settings.client.server_port,
Expand Down
3 changes: 2 additions & 1 deletion examples/lobby/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl Plugin for ExampleClientPlugin {
app.init_resource::<lobby::LobbyTable>();
app.init_resource::<Lobbies>();
app.init_state::<AppState>();
app.add_systems(Startup, on_disconnect);
app.add_systems(PreUpdate, handle_connection.after(MainSet::Receive));
app.add_systems(
FixedPreUpdate,
Expand Down Expand Up @@ -371,7 +372,7 @@ mod lobby {
AppState::Game => {}
};
match state.get() {
NetworkingState::Disconnected => {
NetworkingState::Disconnected | NetworkingState::None => {
if ui.button("Join lobby list").clicked() {
// TODO: before connecting, we want to adjust all clients ConnectionConfig to respect the new host
// - the new host must run in host-server
Expand Down
16 changes: 14 additions & 2 deletions examples/simple_box/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ pub struct ExampleClientPlugin;
impl Plugin for ExampleClientPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_connect_button);
app.add_systems(PreUpdate, handle_connection.after(MainSet::Receive));
app.add_systems(
PreUpdate,
(handle_connection, handle_disconnection).after(MainSet::Receive),
);
// Inputs have to be buffered in the FixedPreUpdate schedule
app.add_systems(
FixedPreUpdate,
Expand Down Expand Up @@ -75,6 +78,15 @@ pub(crate) fn handle_connection(
}
}

/// Listen for events to know when the client is disconnected, and print out the reason
/// of the disconnection
pub(crate) fn handle_disconnection(mut events: EventReader<DisconnectEvent>) {
for event in events.read() {
let reason = &event.reason;
error!("Disconnected from server: {:?}", reason);
}
}

/// System that reads from peripherals and adds inputs to the buffer
/// This system must be run in the `InputSystemSet::BufferInputs` set in the `FixedPreUpdate` schedule
/// to work correctly.
Expand Down Expand Up @@ -262,7 +274,7 @@ fn button_system(
for (entity, children, mut on_click) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match state.get() {
NetworkingState::Disconnected => {
NetworkingState::Disconnected | NetworkingState::None => {
text.sections[0].value = "Connect".to_string();
*on_click = On::<Pointer<Click>>::run(|mut commands: Commands| {
commands.connect_client();
Expand Down
5 changes: 4 additions & 1 deletion lightyear/src/client/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use bevy::app::{App, Plugin, PreUpdate};
use bevy::prelude::{Component, Event, Events, IntoSystemConfigs};

use crate::client::connection::ConnectionManager;
use crate::connection::client::DisconnectReason;
use crate::prelude::ClientId;
use crate::shared::events::plugin::EventsPlugin;
use crate::shared::events::systems::push_component_events;
Expand Down Expand Up @@ -66,7 +67,9 @@ impl ConnectEvent {

/// Bevy [`Event`] emitted on the client on the frame where the connection is disconnected
#[derive(Event, Default)]
pub struct DisconnectEvent;
pub struct DisconnectEvent {
pub reason: Option<DisconnectReason>,
}

/// Bevy [`Event`] emitted on the client to indicate the user input for the tick
pub type InputEvent<I> = crate::shared::events::components::InputEvent<I, ()>;
Expand Down
41 changes: 23 additions & 18 deletions lightyear/src/client/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ use crate::client::interpolation::Interpolated;
use crate::client::io::ClientIoEvent;
use crate::client::prediction::Predicted;
use crate::client::sync::SyncSet;
use crate::connection::client::{ClientConnection, NetClient, NetConfig};
use crate::connection::client::{
ClientConnection, ConnectionState, DisconnectReason, NetClient, NetConfig,
};
use crate::connection::server::{IoConfig, ServerConnections};
use crate::prelude::{
is_host_server, ChannelRegistry, MainSet, MessageRegistry, SharedConfig, TickManager,
Expand Down Expand Up @@ -127,14 +129,6 @@ impl Plugin for ClientNetworkingPlugin {

pub(crate) fn receive(world: &mut World) {
trace!("Receive server packets");
// TODO: here we can control time elapsed from the client's perspective?

// TODO: THE CLIENT COULD DO PHYSICS UPDATES INSIDE FIXED-UPDATE SYSTEMS
// WE SHOULD BE CALLING UPDATE INSIDE THOSE AS WELL SO THAT WE CAN SEND UPDATES
// IN THE MIDDLE OF THE FIXED UPDATE LOOPS
// WE JUST KEEP AN INTERNAL TIMER TO KNOW IF WE REACHED OUR TICK AND SHOULD RECEIVE/SEND OUT PACKETS?
// FIXED-UPDATE.expend() updates the clock zR the fixed update interval
// THE NETWORK TICK INTERVAL COULD BE IN BETWEEN FIXED UPDATE INTERVALS
world.resource_scope(
|world: &mut World, mut connection: Mut<ConnectionManager>| {
world.resource_scope(
Expand All @@ -152,15 +146,15 @@ pub(crate) fn receive(world: &mut World) {
time_manager.update(delta);
trace!(time = ?time_manager.current_time(), tick = ?tick_manager.tick(), "receive");

if netclient.state() != NetworkingState::Disconnected {
if !matches!(netclient.state(), ConnectionState::Disconnected {..}){
let _ = netclient
.try_update(delta.as_secs_f64())
.map_err(|e| {
error!("Error updating netcode: {}", e);
});
}

if netclient.state() == NetworkingState::Connected {
if matches!(netclient.state(), ConnectionState::Connected) {
// we just connected, do a state transition
if state.get() != &NetworkingState::Connected {
debug!("Setting the networking state to connected");
Expand All @@ -174,7 +168,8 @@ pub(crate) fn receive(world: &mut World) {
tick_manager.as_ref(),
);
}
if netclient.state() == NetworkingState::Disconnected {
if let ConnectionState::Disconnected{reason} = netclient.state() {
netclient.disconnect_reason = reason;
// we just disconnected, do a state transition
if state.get() != &NetworkingState::Disconnected {
next_state.set(NetworkingState::Disconnected);
Expand Down Expand Up @@ -273,8 +268,10 @@ pub(crate) fn sync_update(
/// Bevy [`State`] representing the networking state of the client.
#[derive(States, Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NetworkingState {
/// The client is disconnected from the server. The receive/send packets systems do not run.
/// Starting state (to avoid running the OnDisconnect schedule when starting the app)
#[default]
None,
/// The client is disconnected from the server. The receive/send packets systems do not run.
Disconnected,
/// The client is trying to connect to the server
Connecting,
Expand All @@ -298,13 +295,17 @@ fn listen_io_state(
Ok(ClientIoEvent::Disconnected(e)) => {
error!("Error from io: {}", e);
io.state = IoState::Disconnected;
netclient.disconnect_reason = Some(DisconnectReason::Transport(e));
disconnect = true;
}
Err(TryRecvError::Empty) => {
trace!("we are still connecting the io, and there is no error yet");
}
Err(TryRecvError::Closed) => {
error!("Io status channel has been closed when it shouldn't be");
netclient.disconnect_reason = Some(DisconnectReason::Transport(
std::io::Error::other("Io status channel has been closed").into(),
));
disconnect = true;
}
}
Expand All @@ -313,6 +314,7 @@ fn listen_io_state(
if disconnect {
debug!("Going to NetworkingState::Disconnected because of io error.");
next_state.set(NetworkingState::Disconnected);
// TODO: do we need to disconnect here? we disconnect in the OnEnter(Disconnected) system anyway
let _ = netclient
.disconnect()
.inspect_err(|e| debug!("error disconnecting netclient: {e:?}"));
Expand Down Expand Up @@ -357,7 +359,7 @@ fn on_connect_host_server(
fn on_disconnect(
mut connection_manager: ResMut<ConnectionManager>,
mut disconnect_event_writer: EventWriter<DisconnectEvent>,
mut netcode: ResMut<ClientConnection>,
mut netclient: ResMut<ClientConnection>,
mut commands: Commands,
received_entities: Query<Entity, Or<(With<Replicated>, With<Predicted>, With<Interpolated>)>>,
) {
Expand All @@ -371,11 +373,12 @@ fn on_disconnect(
connection_manager.sync_manager.synced = false;

// try to disconnect again to close io tasks (in case the disconnection is from the io)
let _ = netcode.disconnect();
let _ = netclient.disconnect();

// no need to update the io state, because we will recreate a new `ClientConnection`
// for the next connection attempt
disconnect_event_writer.send(DisconnectEvent);
let reason = std::mem::take(&mut netclient.disconnect_reason);
disconnect_event_writer.send(DisconnectEvent { reason });
// TODO: remove ClientConnection and ConnectionManager resources?
}

Expand Down Expand Up @@ -455,8 +458,10 @@ fn connect(world: &mut World) {
});
let config = world.resource::<ClientConfig>();

if world.resource::<ClientConnection>().state() == NetworkingState::Connected
&& config.shared.mode == Mode::HostServer
if matches!(
world.resource::<ClientConnection>().state(),
ConnectionState::Connected
) && config.shared.mode == Mode::HostServer
{
// TODO: also check if the connection is of type local?
// in host server mode, there is no connecting phase, we directly become connected
Expand Down
26 changes: 23 additions & 3 deletions lightyear/src/connection/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ use crate::prelude::client::ClientTransport;
use crate::prelude::{generate_key, Key, LinkConditionerConfig};
use crate::transport::config::SharedIoConfig;

#[derive(Debug)]
pub enum ConnectionState {
Disconnected { reason: Option<DisconnectReason> },
Connecting,
Connected,
}

// TODO: add diagnostics methods?
#[enum_dispatch]
pub trait NetClient: Send + Sync {
Expand All @@ -32,8 +39,8 @@ pub trait NetClient: Send + Sync {
/// Disconnect from the server
fn disconnect(&mut self) -> Result<()>;

/// Returns the [`NetworkingState`] of the client
fn state(&self) -> NetworkingState;
/// Returns the [`ConnectionState`] of the client
fn state(&self) -> ConnectionState;

/// Update the connection state + internal bookkeeping (keep-alives, etc.)
fn try_update(&mut self, delta_ms: f64) -> Result<()>;
Expand Down Expand Up @@ -70,6 +77,16 @@ pub enum NetClientDispatch {
#[derive(Resource)]
pub struct ClientConnection {
pub client: NetClientDispatch,
pub(crate) disconnect_reason: Option<DisconnectReason>,
}

/// Enumerates the possible reasons for a client to disconnect from the server
#[derive(Debug)]
pub enum DisconnectReason {
Transport(crate::transport::error::Error),
Netcode(super::netcode::ClientState),
#[cfg(all(feature = "steam", not(target_family = "wasm")))]
Steam(steamworks::networking_types::NetConnectionEnd),
}

pub type IoConfig = SharedIoConfig<ClientTransport>;
Expand Down Expand Up @@ -131,6 +148,7 @@ impl NetConfig {
};
ClientConnection {
client: NetClientDispatch::Netcode(client),
disconnect_reason: None,
}
}
#[cfg(all(feature = "steam", not(target_family = "wasm")))]
Expand All @@ -150,12 +168,14 @@ impl NetConfig {
.expect("could not create steam client");
ClientConnection {
client: NetClientDispatch::Steam(client),
disconnect_reason: None,
}
}
NetConfig::Local { id } => {
let client = super::local::client::Client::new(id);
ClientConnection {
client: NetClientDispatch::Local(client),
disconnect_reason: None,
}
}
}
Expand All @@ -171,7 +191,7 @@ impl NetClient for ClientConnection {
self.client.disconnect()
}

fn state(&self) -> NetworkingState {
fn state(&self) -> ConnectionState {
self.client.state()
}

Expand Down
8 changes: 4 additions & 4 deletions lightyear/src/connection/local/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::client::io::Io;
use crate::client::networking::NetworkingState;
use crate::connection::client::NetClient;
use crate::connection::client::{ConnectionState, NetClient};
use crate::packet::packet::Packet;
use crate::prelude::ClientId;
use crate::transport::LOCAL_SOCKET;
Expand Down Expand Up @@ -33,11 +33,11 @@ impl NetClient for Client {
Ok(())
}

fn state(&self) -> NetworkingState {
fn state(&self) -> ConnectionState {
if self.is_connected {
NetworkingState::Connected
ConnectionState::Connected
} else {
NetworkingState::Disconnected
ConnectionState::Disconnected { reason: None }
}
}

Expand Down
12 changes: 7 additions & 5 deletions lightyear/src/connection/netcode/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bevy::prelude::Resource;
use tracing::{debug, error, info, trace, warn};

use crate::client::io::Io;
use crate::connection::client::{IoConfig, NetClient};
use crate::connection::client::{ConnectionState, DisconnectReason, IoConfig, NetClient};
use crate::connection::id;
use crate::prelude::client::NetworkingState;
use crate::serialize::bitcode::reader::BufferPool;
Expand Down Expand Up @@ -679,13 +679,15 @@ impl<Ctx: Send + Sync> NetClient for Client<Ctx> {
Ok(())
}

fn state(&self) -> NetworkingState {
fn state(&self) -> ConnectionState {
match self.client.state() {
ClientState::SendingConnectionRequest | ClientState::SendingChallengeResponse => {
NetworkingState::Connecting
ConnectionState::Connecting
}
ClientState::Connected => NetworkingState::Connected,
_ => NetworkingState::Disconnected,
ClientState::Connected => ConnectionState::Connected,
_ => ConnectionState::Disconnected {
reason: Some(DisconnectReason::Netcode(self.client.state)),
},
}
}

Expand Down
17 changes: 12 additions & 5 deletions lightyear/src/connection/steam/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::client::networking::NetworkingState;
use crate::connection::client::NetClient;
use crate::connection::client::{ConnectionState, DisconnectReason, NetClient};
use crate::connection::id::ClientId;
use crate::packet::packet::Packet;
use crate::prelude::client::Io;
Expand Down Expand Up @@ -153,16 +153,23 @@ impl NetClient for Client {
Ok(())
}

fn state(&self) -> NetworkingState {
fn state(&self) -> ConnectionState {
match self
.connection_state()
.unwrap_or(NetworkingConnectionState::None)
{
NetworkingConnectionState::Connecting | NetworkingConnectionState::FindingRoute => {
NetworkingState::Connecting
ConnectionState::Connecting
}
NetworkingConnectionState::Connected => ConnectionState::Connected,
_ => {
let reason = self
.connection_info()
.map_or(None, |info| info.ok().map(|i| i.end_reason()))
.flatten()
.map(|r| DisconnectReason::Steam(r));
ConnectionState::Disconnected { reason }
}
NetworkingConnectionState::Connected => NetworkingState::Connected,
_ => NetworkingState::Disconnected,
}
}

Expand Down
Loading

0 comments on commit 90a7a6d

Please sign in to comment.