1use std::collections::BTreeMap;
16use std::sync::Arc;
17
18use sim_kernel::{
19 Consistency, Cx, DefaultFactory, EagerPolicy, Error, EvalFabricRef, EvalMode, EvalRequest,
20 Expr, Result, Symbol,
21};
22use sim_lib_stream_core::{PushResult, StreamEnvelope, StreamItem, StreamStats};
23
24use crate::transport::{
25 ChangeEvent, SessionStatus, StreamInspectorRecord, Transport, TransportKind,
26};
27
28pub struct FabricTransport {
35 fabric: EvalFabricRef,
36 cx: Cx,
37 store: BTreeMap<Symbol, Expr>,
38 events: Vec<ChangeEvent>,
39 status: SessionStatus,
40}
41
42impl FabricTransport {
43 pub fn new(fabric: EvalFabricRef) -> Self {
45 Self {
46 fabric,
47 cx: Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory)),
48 store: BTreeMap::new(),
49 events: Vec::new(),
50 status: SessionStatus::Connected,
51 }
52 }
53
54 pub fn with(mut self, resource: Symbol, value: Expr) -> Self {
56 self.store.insert(resource, value);
57 self
58 }
59
60 pub fn set(&mut self, resource: Symbol, value: Expr) {
62 self.store.insert(resource, value);
63 }
64
65 fn no_streams(&self) -> Error {
66 Error::HostError("fabric transport does not provide streams".to_owned())
67 }
68}
69
70impl Transport for FabricTransport {
71 fn kind(&self) -> TransportKind {
72 TransportKind::Fabric
73 }
74
75 fn status(&self) -> SessionStatus {
76 self.status
77 }
78
79 fn read(&self, resource: &Symbol) -> Result<Expr> {
80 self.store
81 .get(resource)
82 .cloned()
83 .ok_or_else(|| Error::UnknownSymbol {
84 symbol: resource.clone(),
85 })
86 }
87
88 fn realize(&mut self, resource: &Symbol, operation: &Expr) -> Result<Expr> {
89 let request = operation_to_request(operation);
90 let reply = self.fabric.realize(&mut self.cx, request)?;
91 let new_value = reply.value.object().as_expr(&mut self.cx)?;
92 self.store.insert(resource.clone(), new_value.clone());
93 self.events.push(ChangeEvent {
94 resource: resource.clone(),
95 });
96 Ok(new_value)
97 }
98
99 fn drain_events(&mut self) -> Vec<ChangeEvent> {
100 std::mem::take(&mut self.events)
101 }
102
103 fn stream_subscribe(&mut self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
104 Err(self.no_streams())
105 }
106
107 fn stream_read(&mut self, _stream_id: &Symbol, _limit: usize) -> Result<Vec<StreamItem>> {
108 Err(self.no_streams())
109 }
110
111 fn stream_push(
112 &mut self,
113 _stream_id: &Symbol,
114 _envelope: StreamEnvelope,
115 ) -> Result<PushResult> {
116 Err(self.no_streams())
117 }
118
119 fn stream_cancel(&mut self, _stream_id: &Symbol) -> Result<()> {
120 Err(self.no_streams())
121 }
122
123 fn stream_stats(&self, _stream_id: &Symbol) -> Result<StreamStats> {
124 Err(self.no_streams())
125 }
126
127 fn stream_inspector(&self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
128 Err(self.no_streams())
129 }
130}
131
132pub fn operation_to_request(operation: &Expr) -> EvalRequest {
138 EvalRequest {
139 expr: operation.clone(),
140 result_shape: None,
141 required_capabilities: Vec::new(),
142 deadline: None,
143 consistency: Consistency::LocalFirst,
144 mode: EvalMode::Eval,
145 answer_limit: None,
146 stream_buffer: None,
147 stream: false,
148 trace: false,
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::sync::Arc;
155
156 use sim_kernel::{Cx, Error, EvalFabric, EvalReply, EvalRequest, Expr, NumberLiteral, Result};
157 use sim_lib_intent::{Origin, intent};
158 use sim_lib_view::{
159 LensRegistry, UNIVERSAL_EDITOR_ID, UNIVERSAL_VIEW_ID, register_universal_default,
160 };
161
162 use super::FabricTransport;
163 use crate::session::Session;
164 use crate::transport::{Transport, TransportKind};
165
166 struct SetValueFabric;
169
170 impl EvalFabric for SetValueFabric {
171 fn realize(&self, cx: &mut Cx, request: EvalRequest) -> Result<EvalReply> {
172 let Expr::Map(entries) = &request.expr else {
173 return Err(Error::HostError("operation is not a map".to_owned()));
174 };
175 let value_expr = entries
176 .iter()
177 .find_map(|(key, value)| {
178 matches!(key, Expr::Symbol(symbol) if &*symbol.name == "value").then_some(value)
179 })
180 .ok_or_else(|| {
181 Error::HostError("set-value operation is missing a 'value'".to_owned())
182 })?;
183 Ok(EvalReply {
184 value: cx.factory().expr(value_expr.clone())?,
185 diagnostics: Vec::new(),
186 trace: None,
187 })
188 }
189 }
190
191 use sim_kernel::testing::eager_cx as cx;
192
193 fn registry() -> LensRegistry {
194 let mut registry = LensRegistry::new();
195 register_universal_default(&mut registry, false);
196 registry
197 }
198
199 use sim_value::build::keyword as sym;
200
201 fn number(value: &str) -> Expr {
202 Expr::Number(NumberLiteral {
203 domain: sym("i64"),
204 canonical: value.to_owned(),
205 })
206 }
207
208 fn doc() -> Expr {
209 Expr::Map(vec![
210 (Expr::Symbol(sym("a")), number("1")),
211 (Expr::Symbol(sym("b")), number("2")),
212 ])
213 }
214
215 fn edit_a_to_9() -> Expr {
216 intent(
217 "edit-field",
218 Origin::human(1),
219 vec![
220 ("target", doc()),
221 (
222 "path",
223 Expr::List(vec![Expr::Vector(vec![
224 Expr::Symbol(sym("k")),
225 Expr::Symbol(sym("a")),
226 ])]),
227 ),
228 ("value", number("9")),
229 ],
230 )
231 }
232
233 fn set_value_op(value: Expr) -> Expr {
234 Expr::Map(vec![
235 (Expr::Symbol(sym("op")), Expr::Symbol(sym("set-value"))),
236 (Expr::Symbol(sym("value")), value),
237 ])
238 }
239
240 fn field<'a>(value: &'a Expr, name: &str) -> Option<&'a Expr> {
241 let Expr::Map(entries) = value else {
242 return None;
243 };
244 entries
245 .iter()
246 .find(|(key, _)| matches!(key, Expr::Symbol(symbol) if &*symbol.name == name))
247 .map(|(_, value)| value)
248 }
249
250 #[test]
251 fn session_commits_an_edit_through_the_fabric_and_the_scene_diff_reconstructs() {
252 let mut cx = cx();
253 let registry = registry();
254 let transport = FabricTransport::new(Arc::new(SetValueFabric)).with(sym("doc"), doc());
255 let mut session = Session::new(transport);
256
257 let initial = session
258 .open(
259 &mut cx,
260 ®istry,
261 sym("pane-1"),
262 sym("doc"),
263 sym(UNIVERSAL_VIEW_ID),
264 sym(UNIVERSAL_EDITOR_ID),
265 )
266 .unwrap();
267 sim_lib_scene::validate_scene(&initial).expect("initial scene is valid");
268
269 session
271 .submit_intent(&mut cx, ®istry, &sym("pane-1"), &edit_a_to_9())
272 .unwrap();
273
274 let value = session.transport_mut().read(&sym("doc")).unwrap();
276 assert_eq!(field(&value, "a"), Some(&number("9")));
277
278 let updates = session.pump(&mut cx, ®istry).unwrap();
280 assert_eq!(updates.len(), 1, "exactly the subscribed pane updates");
281 let update = &updates[0];
282 assert_eq!(update.pane, sym("pane-1"));
283 assert_ne!(update.scene, initial, "the Scene changed");
284 let rebuilt = sim_lib_scene::apply(&initial, &update.diff).unwrap();
285 assert_eq!(rebuilt, update.scene, "the diff reconstructs the new Scene");
286 }
287
288 #[test]
289 fn direct_realize_returns_the_new_value_and_records_one_event() {
290 let mut transport = FabricTransport::new(Arc::new(SetValueFabric));
291 assert_eq!(transport.kind(), TransportKind::Fabric);
292
293 let new_value = transport
294 .realize(&sym("x"), &set_value_op(number("42")))
295 .unwrap();
296 assert_eq!(new_value, number("42"));
297 assert_eq!(transport.read(&sym("x")).unwrap(), number("42"));
298
299 let events = transport.drain_events();
300 assert_eq!(events.len(), 1);
301 assert_eq!(events[0].resource, sym("x"));
302 assert!(transport.drain_events().is_empty());
303 }
304}