use std::collections::BTreeMap;
use std::sync::Arc;
use sim_kernel::{
Consistency, Cx, DefaultFactory, EagerPolicy, Error, EvalFabricRef, EvalMode, EvalRequest,
Expr, Result, Symbol,
};
use sim_lib_stream_core::{PushResult, StreamEnvelope, StreamItem, StreamStats};
use crate::transport::{
ChangeEvent, SessionStatus, StreamInspectorRecord, Transport, TransportKind,
};
pub struct FabricTransport {
fabric: EvalFabricRef,
cx: Cx,
store: BTreeMap<Symbol, Expr>,
events: Vec<ChangeEvent>,
status: SessionStatus,
}
impl FabricTransport {
pub fn new(fabric: EvalFabricRef) -> Self {
Self {
fabric,
cx: Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory)),
store: BTreeMap::new(),
events: Vec::new(),
status: SessionStatus::Connected,
}
}
pub fn with(mut self, resource: Symbol, value: Expr) -> Self {
self.store.insert(resource, value);
self
}
pub fn set(&mut self, resource: Symbol, value: Expr) {
self.store.insert(resource, value);
}
fn no_streams(&self) -> Error {
Error::HostError("fabric transport does not provide streams".to_owned())
}
}
impl Transport for FabricTransport {
fn kind(&self) -> TransportKind {
TransportKind::Fabric
}
fn status(&self) -> SessionStatus {
self.status
}
fn read(&self, resource: &Symbol) -> Result<Expr> {
self.store
.get(resource)
.cloned()
.ok_or_else(|| Error::UnknownSymbol {
symbol: resource.clone(),
})
}
fn realize(&mut self, resource: &Symbol, operation: &Expr) -> Result<Expr> {
let request = operation_to_request(operation);
let reply = self.fabric.realize(&mut self.cx, request)?;
let new_value = reply.value.object().as_expr(&mut self.cx)?;
self.store.insert(resource.clone(), new_value.clone());
self.events.push(ChangeEvent {
resource: resource.clone(),
});
Ok(new_value)
}
fn drain_events(&mut self) -> Vec<ChangeEvent> {
std::mem::take(&mut self.events)
}
fn stream_subscribe(&mut self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
Err(self.no_streams())
}
fn stream_read(&mut self, _stream_id: &Symbol, _limit: usize) -> Result<Vec<StreamItem>> {
Err(self.no_streams())
}
fn stream_push(
&mut self,
_stream_id: &Symbol,
_envelope: StreamEnvelope,
) -> Result<PushResult> {
Err(self.no_streams())
}
fn stream_cancel(&mut self, _stream_id: &Symbol) -> Result<()> {
Err(self.no_streams())
}
fn stream_stats(&self, _stream_id: &Symbol) -> Result<StreamStats> {
Err(self.no_streams())
}
fn stream_inspector(&self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
Err(self.no_streams())
}
}
pub fn operation_to_request(operation: &Expr) -> EvalRequest {
EvalRequest {
expr: operation.clone(),
result_shape: None,
required_capabilities: Vec::new(),
deadline: None,
consistency: Consistency::LocalFirst,
mode: EvalMode::Eval,
answer_limit: None,
stream_buffer: None,
stream: false,
trace: false,
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use sim_kernel::{Cx, Error, EvalFabric, EvalReply, EvalRequest, Expr, NumberLiteral, Result};
use sim_lib_intent::{Origin, intent};
use sim_lib_view::{
LensRegistry, UNIVERSAL_EDITOR_ID, UNIVERSAL_VIEW_ID, register_universal_default,
};
use super::FabricTransport;
use crate::session::Session;
use crate::transport::{Transport, TransportKind};
struct SetValueFabric;
impl EvalFabric for SetValueFabric {
fn realize(&self, cx: &mut Cx, request: EvalRequest) -> Result<EvalReply> {
let Expr::Map(entries) = &request.expr else {
return Err(Error::HostError("operation is not a map".to_owned()));
};
let value_expr = entries
.iter()
.find_map(|(key, value)| {
matches!(key, Expr::Symbol(symbol) if &*symbol.name == "value").then_some(value)
})
.ok_or_else(|| {
Error::HostError("set-value operation is missing a 'value'".to_owned())
})?;
Ok(EvalReply {
value: cx.factory().expr(value_expr.clone())?,
diagnostics: Vec::new(),
trace: None,
})
}
}
use sim_kernel::testing::eager_cx as cx;
fn registry() -> LensRegistry {
let mut registry = LensRegistry::new();
register_universal_default(&mut registry, false);
registry
}
use sim_value::build::keyword as sym;
fn number(value: &str) -> Expr {
Expr::Number(NumberLiteral {
domain: sym("i64"),
canonical: value.to_owned(),
})
}
fn doc() -> Expr {
Expr::Map(vec![
(Expr::Symbol(sym("a")), number("1")),
(Expr::Symbol(sym("b")), number("2")),
])
}
fn edit_a_to_9() -> Expr {
intent(
"edit-field",
Origin::human(1),
vec![
("target", doc()),
(
"path",
Expr::List(vec![Expr::Vector(vec![
Expr::Symbol(sym("k")),
Expr::Symbol(sym("a")),
])]),
),
("value", number("9")),
],
)
}
fn set_value_op(value: Expr) -> Expr {
Expr::Map(vec![
(Expr::Symbol(sym("op")), Expr::Symbol(sym("set-value"))),
(Expr::Symbol(sym("value")), value),
])
}
fn field<'a>(value: &'a Expr, name: &str) -> Option<&'a Expr> {
let Expr::Map(entries) = value else {
return None;
};
entries
.iter()
.find(|(key, _)| matches!(key, Expr::Symbol(symbol) if &*symbol.name == name))
.map(|(_, value)| value)
}
#[test]
fn session_commits_an_edit_through_the_fabric_and_the_scene_diff_reconstructs() {
let mut cx = cx();
let registry = registry();
let transport = FabricTransport::new(Arc::new(SetValueFabric)).with(sym("doc"), doc());
let mut session = Session::new(transport);
let initial = session
.open(
&mut cx,
®istry,
sym("pane-1"),
sym("doc"),
sym(UNIVERSAL_VIEW_ID),
sym(UNIVERSAL_EDITOR_ID),
)
.unwrap();
sim_lib_scene::validate_scene(&initial).expect("initial scene is valid");
session
.submit_intent(&mut cx, ®istry, &sym("pane-1"), &edit_a_to_9())
.unwrap();
let value = session.transport_mut().read(&sym("doc")).unwrap();
assert_eq!(field(&value, "a"), Some(&number("9")));
let updates = session.pump(&mut cx, ®istry).unwrap();
assert_eq!(updates.len(), 1, "exactly the subscribed pane updates");
let update = &updates[0];
assert_eq!(update.pane, sym("pane-1"));
assert_ne!(update.scene, initial, "the Scene changed");
let rebuilt = sim_lib_scene::apply(&initial, &update.diff).unwrap();
assert_eq!(rebuilt, update.scene, "the diff reconstructs the new Scene");
}
#[test]
fn direct_realize_returns_the_new_value_and_records_one_event() {
let mut transport = FabricTransport::new(Arc::new(SetValueFabric));
assert_eq!(transport.kind(), TransportKind::Fabric);
let new_value = transport
.realize(&sym("x"), &set_value_op(number("42")))
.unwrap();
assert_eq!(new_value, number("42"));
assert_eq!(transport.read(&sym("x")).unwrap(), number("42"));
let events = transport.drain_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].resource, sym("x"));
assert!(transport.drain_events().is_empty());
}
}