Skip to content

Commit bff06e7

Browse files
committed
refactored disconnection logic to simplify and ensure cleanup on connection drop
1 parent b239f2a commit bff06e7

File tree

3 files changed

+58
-28
lines changed

3 files changed

+58
-28
lines changed

src/bin/src/systems/connection_killer.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
use crate::systems::system_messages;
22
use bevy_ecs::prelude::{Commands, Entity, Query, Res};
33
use ferrumc_core::identity::player_identity::PlayerIdentity;
4-
use ferrumc_net::connection::{DisconnectHandle, StreamWriter};
4+
use ferrumc_net::connection::StreamWriter;
55
use ferrumc_state::GlobalStateResource;
66
use ferrumc_text::TextComponent;
77
use tracing::{info, trace, warn};
88

99
pub fn connection_killer(
10-
mut query: Query<(
11-
Entity,
12-
&StreamWriter,
13-
&PlayerIdentity,
14-
&mut DisconnectHandle,
15-
)>,
10+
query: Query<(Entity, &StreamWriter, &PlayerIdentity)>,
1611
mut cmd: Commands,
1712
state: Res<GlobalStateResource>,
1813
) {
1914
while let Some((disconnecting_entity, reason)) = state.0.players.disconnection_queue.pop() {
2015
let disconnecting_player_identity = query
2116
.get(disconnecting_entity)
2217
.ok()
23-
.map(|(_, _, identity, _)| identity.clone());
18+
.map(|(_, _, identity)| identity.clone());
2419

2520
if disconnecting_player_identity.is_none() {
2621
warn!("Player's entity has already been removed");
@@ -29,7 +24,7 @@ pub fn connection_killer(
2924

3025
let disconnecting_player_identity = disconnecting_player_identity.unwrap();
3126

32-
for (entity, conn, player_identity, mut disconnect_handle) in query.iter_mut() {
27+
for (entity, conn, player_identity) in query.iter() {
3328
if disconnecting_entity == entity {
3429
info!(
3530
"Player {} ({}) disconnected: {}",
@@ -54,11 +49,6 @@ pub fn connection_killer(
5449
player_identity.username, e
5550
);
5651
}
57-
if let Some(sender) = disconnect_handle.sender.take() {
58-
if sender.send(()).is_err() {
59-
trace!("Failed to send disconnect signal (receiver already dropped)");
60-
}
61-
}
6252
} else {
6353
trace!(
6454
"Connection for player {} is not running, skipping disconnect packet",

src/bin/src/systems/keep_alive_system.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ pub fn keep_alive_system(
1919
.running
2020
.load(std::sync::atomic::Ordering::Relaxed)
2121
{
22-
if state.0.players.is_connected(entity) {
23-
state
24-
.0
25-
.players
26-
.disconnect(entity, Some("Player disconnected.".to_string()));
27-
};
2822
continue;
2923
}
3024

src/lib/net/src/connection.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use ferrumc_net_codec::encode::NetEncode;
1414
use ferrumc_net_codec::encode::NetEncodeOpts;
1515
use ferrumc_state::ServerState;
1616
use std::sync::atomic::{AtomicBool, Ordering};
17-
use std::sync::Arc;
17+
use std::sync::{Arc, Mutex};
1818
use std::time::Duration;
1919
use tokio::io::AsyncWriteExt;
2020
use tokio::net::tcp::OwnedWriteHalf;
@@ -41,6 +41,8 @@ pub struct StreamWriter {
4141
sender: UnboundedSender<Vec<u8>>,
4242
pub running: Arc<AtomicBool>,
4343
pub compress: Arc<AtomicBool>,
44+
pub state: Arc<ServerState>,
45+
pub entity: Arc<Mutex<Option<Entity>>>,
4446
}
4547

4648
impl Drop for StreamWriter {
@@ -55,11 +57,18 @@ impl StreamWriter {
5557
///
5658
/// Spawns a background task that continuously reads from the channel
5759
/// and writes bytes to the network socket.
58-
pub async fn new(mut writer: OwnedWriteHalf, running: Arc<AtomicBool>) -> Self {
60+
pub async fn new(
61+
mut writer: OwnedWriteHalf,
62+
running: Arc<AtomicBool>,
63+
state: Arc<ServerState>,
64+
entity: Arc<Mutex<Option<Entity>>>,
65+
) -> Self {
5966
let compress = Arc::new(AtomicBool::new(false)); // Default: no compression
6067
let (sender, mut receiver): (UnboundedSender<Vec<u8>>, UnboundedReceiver<Vec<u8>>) =
6168
tokio::sync::mpsc::unbounded_channel();
6269
let running_clone = running.clone();
70+
let entity_clone = entity.clone();
71+
let state_clone = state.clone();
6372

6473
// Task: forward packets from channel to socket
6574
tokio::spawn(async move {
@@ -68,21 +77,42 @@ impl StreamWriter {
6877
break;
6978
};
7079

80+
// This handles ONLY if there was a writing error to the client.
7181
if let Err(e) = writer.write_all(&bytes).await {
7282
error!("Failed to write to client: {:?}", e);
7383
running_clone.store(false, Ordering::Relaxed);
84+
if let Some(entity_id) = *entity_clone.lock().unwrap() {
85+
state_clone.players.disconnect(entity_id, None);
86+
}
7487
break;
7588
}
7689
}
90+
91+
// This handles cases where the channel closes without a write error
92+
if let Some(entity_id) = *entity_clone.lock().unwrap() {
93+
trace!(
94+
"Write task ending for entity {:?}, ensuring disconnect",
95+
entity_id
96+
);
97+
state_clone.players.disconnect(entity_id, None);
98+
}
7799
});
78100

79101
Self {
80102
sender,
81103
running,
82104
compress,
105+
state,
106+
entity,
83107
}
84108
}
85109

110+
/// Sets the entity ID for this stream writer.
111+
/// This should be called after the entity is created in the ECS.
112+
pub fn set_entity(&self, entity: Entity) {
113+
*self.entity.lock().unwrap() = Some(entity);
114+
}
115+
86116
/// Sends a packet to the client using the default `WithLength` encoding.
87117
pub fn send_packet(&self, packet: impl NetEncode + Send) -> Result<(), NetError> {
88118
self.send_packet_with_opts(&packet, &NetEncodeOpts::WithLength)
@@ -177,9 +207,16 @@ pub async fn handle_connection(
177207

178208
let running = Arc::new(AtomicBool::new(true));
179209

180-
let stream = StreamWriter::new(tcp_writer, running.clone()).await;
210+
let entity_holder: Arc<Mutex<Option<Entity>>> = Arc::new(Mutex::new(None));
211+
212+
let stream = StreamWriter::new(
213+
tcp_writer,
214+
running.clone(),
215+
state.clone(),
216+
entity_holder.clone(),
217+
)
218+
.await;
181219

182-
// Perform handshake with timeout guard
183220
let handshake_result = timeout(
184221
MAX_HANDSHAKE_TIMEOUT,
185222
handle_handshake(&mut tcp_reader, &stream, state.clone()),
@@ -256,6 +293,11 @@ pub async fn handle_connection(
256293
}
257294
};
258295

296+
// Sets the entity for the stream writer.
297+
*entity_holder.lock().unwrap() = Some(entity);
298+
299+
trace!("Entity {:?} assigned to connection", entity);
300+
259301
// ---- Packet receive loop ----
260302
'recv: loop {
261303
if !running.load(Ordering::Relaxed) {
@@ -279,17 +321,20 @@ pub async fn handle_connection(
279321
if let NetError::ConnectionDropped = err {
280322
trace!("Connection dropped for entity {:?}", entity);
281323
running.store(false, Ordering::Relaxed);
282-
break 'recv;
324+
state.players.disconnect(entity, None);
325+
} else {
326+
error!("Failed to read packet skeleton: {:?} for {:?}", err, entity);
327+
running.store(false, Ordering::Relaxed);
328+
state.players.disconnect(entity, None);
283329
}
284-
error!("Failed to read packet skeleton: {:?} for {:?}", err, entity);
285-
running.store(false, Ordering::Relaxed);
286330
break 'recv;
287331
}
288332
}
289333
}
290334

291335
_ = &mut disconnect_receiver => {
292-
debug!("Received disconnect signal");
336+
debug!("Received disconnect signal for entity {:?}", entity);
337+
running.store(false, Ordering::Relaxed);
293338
break 'recv;
294339
}
295340
}
@@ -322,6 +367,7 @@ pub async fn handle_connection(
322367
_ => {
323368
warn!("Error handling packet for {:?}: {:?}", entity, err);
324369
running.store(false, Ordering::Relaxed);
370+
state.players.disconnect(entity, None);
325371
break 'recv;
326372
}
327373
},

0 commit comments

Comments
 (0)