fix(bridge): add Disconnected error variant, fix test race condition
Replace string-matching disconnect detection with explicit BridgeError::Disconnected variant. Add TcpBridge::accept_on(listener) that takes a pre-bound TcpListener, eliminating the 100ms sleep hack in TCP tests. Send errors now also trigger ServerRunning=false. Add trace logging to generate_snapshot for entity count visibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,7 +99,7 @@ impl SimBridge for LocalBridge {
|
||||
tracing::trace!("received {} inputs", inputs.len());
|
||||
Ok(inputs)
|
||||
}
|
||||
None => Err(BridgeError::Transport("client disconnected (EOF)".into())),
|
||||
None => Err(BridgeError::Disconnected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ pub enum BridgeError {
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("transport error: {0}")]
|
||||
Transport(String),
|
||||
#[error("client disconnected")]
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
/// Abstracts transport layer (D-020)
|
||||
@@ -86,6 +88,11 @@ pub fn generate_snapshot(
|
||||
kind,
|
||||
});
|
||||
}
|
||||
tracing::trace!(
|
||||
"generate_snapshot: tick={}, entities={}",
|
||||
time.tick,
|
||||
visible.len()
|
||||
);
|
||||
buffer.snapshot = Some(ObserverSnapshot {
|
||||
tick: time.tick,
|
||||
entities: visible,
|
||||
@@ -105,7 +112,7 @@ pub fn receive_bridge_inputs(
|
||||
input_queue.push(input);
|
||||
}
|
||||
}
|
||||
Err(BridgeError::Transport(ref msg)) if msg.contains("disconnected") => {
|
||||
Err(BridgeError::Disconnected) => {
|
||||
tracing::info!("Client disconnected, shutting down");
|
||||
running.0 = false;
|
||||
}
|
||||
@@ -119,11 +126,13 @@ pub fn receive_bridge_inputs(
|
||||
pub fn send_bridge_snapshot(
|
||||
bridge: Option<Res<BridgeResource>>,
|
||||
mut buffer: ResMut<SnapshotBuffer>,
|
||||
mut running: ResMut<ServerRunning>,
|
||||
) {
|
||||
let Some(bridge) = bridge else { return };
|
||||
if let Some(snapshot) = buffer.snapshot.take() {
|
||||
if let Err(e) = bridge.send_snapshot(&snapshot) {
|
||||
tracing::error!("Bridge send error: {}", e);
|
||||
running.0 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +159,9 @@ impl Plugin for BridgePlugin {
|
||||
Update,
|
||||
(
|
||||
receive_bridge_inputs.before(crate::simulation::input::process_player_input),
|
||||
generate_snapshot.after(crate::simulation::movement::validate_movement),
|
||||
generate_snapshot
|
||||
.after(crate::simulation::movement::validate_movement)
|
||||
.before(crate::simulation::time::advance_tick),
|
||||
send_bridge_snapshot.after(generate_snapshot),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -56,6 +56,36 @@ impl TcpBridge {
|
||||
})
|
||||
}
|
||||
|
||||
/// Server-side: accept one connection on an existing TcpListener.
|
||||
/// Avoids race conditions in tests by separating bind from accept.
|
||||
pub fn accept_on(listener: TcpListener) -> Result<Self, BridgeError> {
|
||||
let local_addr = listener
|
||||
.local_addr()
|
||||
.map_err(|e| BridgeError::Transport(format!("failed to get local address: {}", e)))?;
|
||||
|
||||
tracing::info!("TcpBridge accepting on {}", local_addr);
|
||||
|
||||
let (stream, peer_addr) = listener
|
||||
.accept()
|
||||
.map_err(|e| BridgeError::Transport(format!("failed to accept connection: {}", e)))?;
|
||||
|
||||
tracing::info!(
|
||||
"TcpBridge accepted connection from {} on {}",
|
||||
peer_addr,
|
||||
local_addr
|
||||
);
|
||||
|
||||
let reader_stream = stream.try_clone().map_err(|e| {
|
||||
BridgeError::Transport(format!("failed to clone stream for reader: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
reader: Mutex::new(BufReader::new(reader_stream)),
|
||||
writer: Mutex::new(BufWriter::new(stream)),
|
||||
local_addr,
|
||||
})
|
||||
}
|
||||
|
||||
/// Client-side: connect to TCP address (for tests).
|
||||
pub fn connect(addr: &str) -> Result<Self, BridgeError> {
|
||||
tracing::info!("TcpBridge connecting to {}", addr);
|
||||
@@ -108,7 +138,7 @@ impl SimBridge for TcpBridge {
|
||||
tracing::trace!("received {} inputs", inputs.len());
|
||||
Ok(inputs)
|
||||
}
|
||||
None => Err(BridgeError::Transport("client disconnected (EOF)".into())),
|
||||
None => Err(BridgeError::Disconnected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user