fix(simulation): use non-blocking TCP to prevent game loop stall
read_framed() called read_exact() which blocked the entire bevy Update schedule waiting for client input — no systems ran until a keystroke arrived. Switch TcpStream to non-blocking mode so receive_inputs() returns Ok(vec![]) on WouldBlock instead of blocking. Toggle to blocking for snapshot writes (reliable delivery). Add 50ms frame throttle (~20 ticks/sec) since the non-blocking loop would otherwise spin. Downgrade input receive logging to trace, add error logging for missing bridge resource and failed observer queries. Fixes bug #1 (server never sends snapshots) and #2 (camera doesn't center until first keystroke). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+26
-11
@@ -79,7 +79,18 @@ fn input_roundtrip_over_tcp() {
|
||||
let server_handle = thread::spawn(move || {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("failed to accept");
|
||||
|
||||
let inputs = bridge.receive_inputs().expect("failed to receive inputs");
|
||||
// Non-blocking socket: retry until data arrives or timeout.
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
|
||||
let inputs = loop {
|
||||
match bridge.receive_inputs() {
|
||||
Ok(inputs) if !inputs.is_empty() => break inputs,
|
||||
Ok(_) => {
|
||||
assert!(std::time::Instant::now() < deadline, "timed out waiting for inputs");
|
||||
thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
Err(e) => panic!("failed to receive inputs: {}", e),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(inputs.len(), 2);
|
||||
assert_eq!(inputs[0].tick, 10);
|
||||
@@ -132,16 +143,20 @@ fn tcp_bridge_eof_returns_error() {
|
||||
let server_handle = thread::spawn(move || {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("failed to accept");
|
||||
|
||||
let result = bridge.receive_inputs();
|
||||
|
||||
assert!(result.is_err(), "expected error on EOF");
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(settled_reach_server::bridge::BridgeError::Disconnected)
|
||||
),
|
||||
"expected Disconnected error"
|
||||
);
|
||||
// Non-blocking socket: retry until we get Disconnected or timeout.
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
|
||||
loop {
|
||||
match bridge.receive_inputs() {
|
||||
Ok(inputs) if inputs.is_empty() => {
|
||||
// WouldBlock — client hasn't disconnected yet, retry
|
||||
assert!(std::time::Instant::now() < deadline, "timed out waiting for EOF");
|
||||
thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
Ok(inputs) => panic!("expected Disconnected error, got {} inputs", inputs.len()),
|
||||
Err(settled_reach_server::bridge::BridgeError::Disconnected) => break,
|
||||
Err(e) => panic!("expected Disconnected error, got: {}", e),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Client: connect and immediately disconnect without sending data
|
||||
|
||||
Reference in New Issue
Block a user