Skip to main content

sim_lib_web_bridge/
remote.rs

1//! Network transports (wasm, local server, remote server).
2//!
3//! These share the [`Transport`] contract with the fixture so they are
4//! interchangeable behind the session bridge. Each connects its runtime through
5//! `realize`/`EvalFabric` (HTTP bootstrap plus a WebSocket live channel for the
6//! server transports, the in-process fabric for wasm). Disconnected transports
7//! fail closed, so the session degrades to a visible state rather than a crash.
8
9use sim_kernel::{Cx, Error, Expr, Result, Symbol};
10use sim_lib_server::{FrameEnvelope, ServerFrame};
11use sim_lib_stream_core::{
12    BufferPolicy, ClockDomain, PushResult, StreamDirection, StreamEnvelope,
13    StreamInspectorSnapshot, StreamItem, StreamMedia, StreamMetadata, StreamStats,
14    TransportProfile, stream_inspector_route_local_symbol,
15};
16use sim_lib_stream_fabric::{StreamControl, stream_control_frame_from_control};
17use sim_lib_view::Operation;
18
19use crate::transport::{
20    BrowserStreamStatus, ChangeEvent, SessionStatus, StreamInspectorRecord, Transport,
21    TransportKind,
22};
23
24/// A network-backed transport that connects a runtime over `realize`.
25pub struct RemoteTransport {
26    kind: TransportKind,
27    status: SessionStatus,
28    endpoint: String,
29}
30
31impl RemoteTransport {
32    /// A wasm transport targeting an in-browser runtime.
33    pub fn wasm() -> Self {
34        Self::new(TransportKind::Wasm, "wasm:local")
35    }
36
37    /// A local-server transport (HTTP bootstrap + WebSocket live).
38    pub fn local_server(endpoint: impl Into<String>) -> Self {
39        Self::new(TransportKind::LocalServer, endpoint)
40    }
41
42    /// A remote-server transport (HTTP bootstrap + WebSocket live).
43    pub fn remote_server(endpoint: impl Into<String>) -> Self {
44        Self::new(TransportKind::RemoteServer, endpoint)
45    }
46
47    fn new(kind: TransportKind, endpoint: impl Into<String>) -> Self {
48        Self {
49            kind,
50            status: SessionStatus::Disconnected,
51            endpoint: endpoint.into(),
52        }
53    }
54
55    /// The configured endpoint.
56    pub fn endpoint(&self) -> &str {
57        &self.endpoint
58    }
59
60    /// Request a remote connection.
61    ///
62    /// The placeholder remote transports do not yet implement data read or
63    /// realize operations, so they remain unavailable rather than reporting a
64    /// live status that cannot carry traffic.
65    pub fn connect(&mut self) {
66        self.status = SessionStatus::Disconnected;
67    }
68
69    /// Mark the remote channel disconnected.
70    pub fn disconnect(&mut self) {
71        self.status = SessionStatus::Disconnected;
72    }
73
74    /// Mark the remote channel reconnecting.
75    pub fn begin_reconnect(&mut self) {
76        self.status = SessionStatus::Reconnecting;
77    }
78
79    /// Encode a stream-fabric control frame for server-backed transports.
80    pub fn stream_control_frame(
81        &self,
82        cx: &mut Cx,
83        codec: Symbol,
84        control: &StreamControl,
85    ) -> Result<ServerFrame> {
86        match self.kind {
87            TransportKind::LocalServer | TransportKind::RemoteServer => {
88                stream_control_frame_from_control(cx, codec, control, FrameEnvelope::default())
89            }
90            TransportKind::Fixture | TransportKind::Wasm | TransportKind::Fabric => {
91                Err(Error::HostError(format!(
92                    "{:?} transport does not use server stream-fabric frames",
93                    self.kind
94                )))
95            }
96        }
97    }
98
99    fn not_connected(&self) -> Error {
100        Error::HostError(format!(
101            "{:?} transport to {} is unavailable (data channel not implemented)",
102            self.kind, self.endpoint
103        ))
104    }
105}
106
107impl Transport for RemoteTransport {
108    fn kind(&self) -> TransportKind {
109        self.kind
110    }
111
112    fn status(&self) -> SessionStatus {
113        self.status
114    }
115
116    fn read(&self, _resource: &Symbol) -> Result<Expr> {
117        Err(self.not_connected())
118    }
119
120    fn realize_operation(&mut self, _resource: &Symbol, _operation: &Operation) -> Result<Expr> {
121        Err(self.not_connected())
122    }
123
124    fn drain_events(&mut self) -> Vec<ChangeEvent> {
125        Vec::new()
126    }
127
128    fn stream_subscribe(&mut self, stream_id: &Symbol) -> Result<StreamInspectorRecord> {
129        Err(Error::HostError(format!(
130            "cannot subscribe to stream {stream_id}: {}",
131            self.not_connected()
132        )))
133    }
134
135    fn stream_read(&mut self, stream_id: &Symbol, _limit: usize) -> Result<Vec<StreamItem>> {
136        Err(Error::HostError(format!(
137            "cannot read stream {stream_id}: {}",
138            self.not_connected()
139        )))
140    }
141
142    fn stream_push(&mut self, stream_id: &Symbol, _envelope: StreamEnvelope) -> Result<PushResult> {
143        Err(Error::HostError(format!(
144            "cannot push stream {stream_id}: {}",
145            self.not_connected()
146        )))
147    }
148
149    fn stream_cancel(&mut self, stream_id: &Symbol) -> Result<()> {
150        Err(Error::HostError(format!(
151            "cannot cancel stream {stream_id}: {}",
152            self.not_connected()
153        )))
154    }
155
156    fn stream_stats(&self, stream_id: &Symbol) -> Result<StreamStats> {
157        Err(Error::HostError(format!(
158            "cannot inspect stream stats {stream_id}: {}",
159            self.not_connected()
160        )))
161    }
162
163    fn stream_inspector(&self, stream_id: &Symbol) -> Result<StreamInspectorRecord> {
164        let status = match self.status {
165            SessionStatus::Disconnected => BrowserStreamStatus::Disconnected,
166            SessionStatus::Reconnecting => BrowserStreamStatus::Reconnecting,
167            SessionStatus::Closed => BrowserStreamStatus::Cancelled,
168            SessionStatus::Connected => BrowserStreamStatus::Disconnected,
169            SessionStatus::Connecting => BrowserStreamStatus::Disconnected,
170        };
171        Ok(StreamInspectorRecord {
172            stream_id: stream_id.clone(),
173            status,
174            buffered: 0,
175            stats: StreamStats::default(),
176            diagnostics: Vec::new(),
177            snapshot: StreamInspectorSnapshot::new(
178                &StreamMetadata::new(
179                    stream_id.clone(),
180                    StreamMedia::Data,
181                    StreamDirection::Source,
182                    ClockDomain::ServerFrame.symbol(),
183                    BufferPolicy::bounded(1)?,
184                ),
185                stream_inspector_route_local_symbol(),
186                TransportProfile::remote_stream_fabric().name().clone(),
187                status.inspector_status(),
188                0,
189                &StreamStats::default(),
190                None,
191                Vec::new(),
192            ),
193        })
194    }
195}