From ac9e2a9fcd01654388449e6ca0873f91651cd812 Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Wed, 7 Jan 2026 21:09:38 -0500 Subject: [PATCH] Add network-layer multiplayer disconnect handling --- Cargo.lock | 8 ++--- rc_multiplayer/Cargo.toml | 8 ++--- rc_multiplayer/src/disconnect.rs | 29 +++++++++++++++++++ .../src/events/client_unregister.rs | 1 + rc_multiplayer/src/events/mod.rs | 6 +++- rc_multiplayer/src/handler.rs | 6 ++-- rc_multiplayer/src/main.rs | 3 +- rc_multiplayer/src/matches/generic.rs | 15 +++++++--- rc_multiplayer/src/matches/messages.rs | 1 + rc_multiplayer/src/traits.rs | 5 ++++ 10 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 rc_multiplayer/src/disconnect.rs diff --git a/Cargo.lock b/Cargo.lock index 442e3b0..2dff29e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2084,9 +2084,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "literustlib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5222104a5463f3fca3aa822be1c1357fb07115cb7ba9bff7dc45aff781675ddd" +version = "0.4.0" dependencies = [ "bytes", "num_enum 0.7.5", @@ -2094,9 +2092,7 @@ dependencies = [ [[package]] name = "literustlib_server" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a39663abd3744e73be2609acf1362d15fce68d386cc796ed17aedd302f872e" +version = "0.4.0" dependencies = [ "async-recursion", "async-trait", diff --git a/rc_multiplayer/Cargo.toml b/rc_multiplayer/Cargo.toml index edb8520..42a0893 100644 --- a/rc_multiplayer/Cargo.toml +++ b/rc_multiplayer/Cargo.toml @@ -21,10 +21,10 @@ atomic_float = "1.1" num-quaternion.workspace = true rand.workspace = true -#literustlib_server = { version = "0.3", path = "../../LiteRustLib/server" } -#literustlib = { version = "0.3", path = "../../LiteRustLib" } -literustlib_server = { version = "0.3" } -literustlib = { version = "0.3" } +literustlib_server = { version = "0.4", path = "../../LiteRustLib/server" } +literustlib = { version = "0.4", path = "../../LiteRustLib" } +#literustlib_server = { version = "0.4" } +#literustlib = { version = "0.4" } rlnl = { version = "0.1", path = "../../rlnl" } oj_serdes.workspace = true diff --git a/rc_multiplayer/src/disconnect.rs b/rc_multiplayer/src/disconnect.rs new file mode 100644 index 0000000..7aec423 --- /dev/null +++ b/rc_multiplayer/src/disconnect.rs @@ -0,0 +1,29 @@ +pub struct ClientDisconnecter { + msg_router: tokio::sync::mpsc::Sender, +} + +pub(super) fn handler(init_ctx: &crate::InitConfig) -> ClientDisconnecter { + ClientDisconnecter::new(init_ctx) +} + +impl ClientDisconnecter { + fn new(init_ctx: &crate::InitConfig) -> Self { + Self { + msg_router: init_ctx.matches_chann.clone(), + } + } +} + +#[async_trait::async_trait] +impl crate::DisconnectHandler for ClientDisconnecter { + async fn handle(&self, _peer: &std::sync::Arc>, user: &crate::UserData) { + if let Some(user_info) = user.user().await { + crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection { + user_id: user_info.user_id(), + is_unregister: false, + }).await); + } else { + log::error!("Failed to handle disconnect for unknown user"); + } + } +} diff --git a/rc_multiplayer/src/events/client_unregister.rs b/rc_multiplayer/src/events/client_unregister.rs index 3f88ebd..120d836 100644 --- a/rc_multiplayer/src/events/client_unregister.rs +++ b/rc_multiplayer/src/events/client_unregister.rs @@ -22,6 +22,7 @@ impl crate::handlers::DatalessEventCodeHandler for ClientUnregisterer { if let Some(user_info) = user.user().await { super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection { user_id: user_info.user_id(), + is_unregister: true, }).await); } else { log::error!("Failed to handle sync loading request for unknown user"); diff --git a/rc_multiplayer/src/events/mod.rs b/rc_multiplayer/src/events/mod.rs index be3be32..7ceaa77 100644 --- a/rc_multiplayer/src/events/mod.rs +++ b/rc_multiplayer/src/events/mod.rs @@ -19,7 +19,11 @@ mod player_leave; mod heal_assist_bonus; pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHandler { - crate::handler::LnlEventHandler::new(init_ctx.users.clone(), crate::vehicle_motion::handler(init_ctx)) + crate::handler::LnlEventHandler::new( + init_ctx.users.clone(), + crate::vehicle_motion::handler(init_ctx), + crate::disconnect::handler(init_ctx), + ) .add(validate_game_guid::handler(init_ctx)) .add(loading_progress::handler(init_ctx)) .add(all_loading_progress::handler(init_ctx)) diff --git a/rc_multiplayer/src/handler.rs b/rc_multiplayer/src/handler.rs index 2db2550..a68c9fb 100644 --- a/rc_multiplayer/src/handler.rs +++ b/rc_multiplayer/src/handler.rs @@ -1,14 +1,16 @@ pub struct LnlEventHandler { event_handlers: std::collections::HashMap>, motion_handler: Box, + disconnect_handler: Box, user_provider: std::sync::Arc } impl LnlEventHandler { - pub fn new(user_provider: std::sync::Arc, motion_handler: M) -> Self { + pub fn new(user_provider: std::sync::Arc, motion_handler: M, disconnect_handler: D) -> Self { Self { event_handlers: std::collections::HashMap::new(), motion_handler: Box::new(motion_handler), + disconnect_handler: Box::new(disconnect_handler), user_provider, } } @@ -75,12 +77,12 @@ impl literustlib_server::EventHandler for LnlEventHandler { } async fn on_disconnect(&self, peer: &std::sync::Arc>, user: &Self::UserData) { + self.disconnect_handler.handle(peer, user).await; if let Some(user_info) = user.user().await { log::info!("Disconnect from user {} ({})", user_info.user_id(), peer.id()); } else { log::debug!("Disconnect from connection {}", peer.id()); } - } } diff --git a/rc_multiplayer/src/main.rs b/rc_multiplayer/src/main.rs index a26029d..6d8cd38 100644 --- a/rc_multiplayer/src/main.rs +++ b/rc_multiplayer/src/main.rs @@ -1,13 +1,14 @@ mod cli; mod handler; mod traits; -pub use traits::{EventCodeHandler, UserData, PacketData, EventCode, RobotMotionHandler, Broadcastable}; +pub use traits::{EventCodeHandler, UserData, PacketData, EventCode, RobotMotionHandler, DisconnectHandler, Broadcastable}; mod data; mod events; mod handlers; mod user; mod matches; mod vehicle_motion; +mod disconnect; pub struct InitConfig { pub config: oj_rc_core::persist::config::ConfigImpl, diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index 9d13142..9569968 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -427,8 +427,8 @@ impl GenericGamemodeEngine { super::GameMessage::NewConnection { user, game_guid, connection, response, sender } => { self.on_new_connection(user, game_guid, connection, response, sender).await; }, - super::GameMessage::EndConnection { user_id } => { - is_engaged = self.on_end_connection(user_id).await; + super::GameMessage::EndConnection { user_id, is_unregister } => { + is_engaged = self.on_end_connection(user_id, is_unregister).await; }, super::GameMessage::RequestLeave { user_id } => { self.on_request_leave(user_id).await; @@ -569,7 +569,7 @@ impl GenericGamemodeEngine { } } - async fn on_end_connection(&self, user_id: i32) -> bool { + async fn on_end_connection(&self, user_id: i32, _is_unregister: bool) -> bool { if let Some(player_id) = self.user_key_by_user_id(user_id) { let conn_opt = self.users.write().await.remove(&player_id); if let Some(conn) = conn_opt { @@ -642,7 +642,14 @@ impl GenericGamemodeEngine { } } } - true + for user in self.descriptors.values() { + if user.descriptor.user_id.is_none() { continue; } // skip non-players + let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + if !matches!(mode, ConnectionMode::Disconnected) { + return true; + } + } + false } async fn on_request_leave(&self, user_id: i32) { diff --git a/rc_multiplayer/src/matches/messages.rs b/rc_multiplayer/src/matches/messages.rs index b07d969..066e2de 100644 --- a/rc_multiplayer/src/matches/messages.rs +++ b/rc_multiplayer/src/matches/messages.rs @@ -8,6 +8,7 @@ pub enum GameMessage { }, EndConnection { user_id: i32, + is_unregister: bool, }, RequestLeave { user_id: i32, diff --git a/rc_multiplayer/src/traits.rs b/rc_multiplayer/src/traits.rs index 24f1ba5..92bd57c 100644 --- a/rc_multiplayer/src/traits.rs +++ b/rc_multiplayer/src/traits.rs @@ -15,4 +15,9 @@ pub trait RobotMotionHandler: Send + Sync { async fn handle(&self, data: &bytes::Bytes, user: &UserData); } +#[async_trait::async_trait] +pub trait DisconnectHandler: Send + Sync { + async fn handle(&self, peer: &std::sync::Arc>, user: &UserData); +} + pub trait Broadcastable: byteserde::ser_heap::ByteSerializeHeap + core::any::Any + Send + Sync + 'static {}