Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return DisconnectReason in the DisconnectEvent for clients #416

Merged
merged 2 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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