feat(simulation): demux inbound bridge stream — receive() + atlas routing (#969, D-225)
Replaces the fixed-type SimBridge::receive_inputs() with a tagged receive() -> Option<Inbound>, where Inbound is Inputs(Vec<PlayerInput>) or AtlasRequest(AtlasLayerRequest). A shared decode_inbound() demuxes a frame by shape (msgpack array = inputs, map = atlas request) — additive, no wire change to existing input/snapshot frames. Adds send_atlas_response() to the trait (both TcpBridge + LocalBridge impls). receive_bridge_inputs routes inputs to the InputQueue as before; atlas requests to a new AtlasRequestBuffer (drained by the serve system next). Integration tests (bridge_tcp/bridge_ipc) updated to the tagged receive(); a demux unit test covers all three branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-24
@@ -3,7 +3,8 @@
|
||||
// Deterministic client-server communication via TCP sockets
|
||||
// Used for Godot client which lacks Unix socket support
|
||||
|
||||
use super::{BridgeError, ObserverSnapshot, PlayerInput, SimBridge};
|
||||
use super::{decode_inbound, BridgeError, Inbound, ObserverSnapshot, SimBridge};
|
||||
use crate::atlas::layer_proxy::AtlasLayerResponse;
|
||||
use crate::bridge::framing::{read_framed, write_framed};
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
@@ -197,38 +198,33 @@ impl SimBridge for TcpBridge {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_inputs(&self) -> Result<Vec<PlayerInput>, BridgeError> {
|
||||
fn receive(&self) -> Result<Option<Inbound>, BridgeError> {
|
||||
let mut reader = self
|
||||
.reader
|
||||
.lock()
|
||||
.map_err(|e| BridgeError::MutexPoisoned(format!("reader: {}", e)))?;
|
||||
|
||||
match read_framed(reader.get_mut()) {
|
||||
Ok(Some(payload)) => match rmp_serde::from_slice::<Vec<PlayerInput>>(&payload) {
|
||||
Ok(inputs) => {
|
||||
tracing::trace!("received {} inputs", inputs.len());
|
||||
Ok(inputs)
|
||||
}
|
||||
Err(e) => {
|
||||
let dump_len = payload.len().min(256);
|
||||
tracing::error!(
|
||||
"deserialization failed: {}. Raw bytes ({} of {} total): {:02x?}",
|
||||
e,
|
||||
dump_len,
|
||||
payload.len(),
|
||||
&payload[..dump_len]
|
||||
);
|
||||
Err(BridgeError::DeserializationWithDump(format!(
|
||||
"{} (payload {} bytes)",
|
||||
e,
|
||||
payload.len()
|
||||
)))
|
||||
}
|
||||
},
|
||||
Ok(Some(payload)) => decode_inbound(&payload).map(Some),
|
||||
Ok(None) => Err(BridgeError::Disconnected),
|
||||
// Non-blocking socket: no data available this tick — not an error.
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(vec![]),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
|
||||
Err(e) => Err(BridgeError::Io(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_atlas_response(&self, resp: &AtlasLayerResponse) -> Result<(), BridgeError> {
|
||||
let payload = rmp_serde::to_vec_named(resp)?;
|
||||
let mut writer = self
|
||||
.writer
|
||||
.lock()
|
||||
.map_err(|e| BridgeError::MutexPoisoned(format!("writer: {}", e)))?;
|
||||
// Toggle to blocking for reliable write delivery (same as send_snapshot).
|
||||
let stream = writer.get_mut();
|
||||
stream.set_nonblocking(false).map_err(BridgeError::Io)?;
|
||||
let result = write_framed(stream, &payload);
|
||||
stream.set_nonblocking(true).map_err(BridgeError::Io)?;
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user