1use std::collections::BTreeMap;
24use std::sync::Arc;
25
26use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Error, Expr, Result, Symbol};
27use sim_lib_view::codec::reduce_for_caps;
28use sim_lib_view::{
29 LensRegistry, SurfaceCaps, UNIVERSAL_EDITOR_ID, UNIVERSAL_VIEW_ID, register_universal_default,
30};
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum SurfaceRole {
35 Main,
37 Peer,
39}
40
41#[derive(Clone, Debug)]
46pub struct Broadcast {
47 pub surface: Symbol,
49 pub pane: Symbol,
51 pub scene: Expr,
53 pub diff: Expr,
55}
56
57#[derive(Clone, Debug)]
62pub struct EditRow {
63 pub resource: Symbol,
65 pub operator: Symbol,
67 pub tick: u64,
69 pub operation: Expr,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct SurfaceBinding {
76 pub surface: Symbol,
78 pub pane: Symbol,
80 pub resource: Symbol,
82}
83
84struct Binding {
87 surface: Symbol,
88 pane: Symbol,
89 resource: Symbol,
90 last_scene: Expr,
91}
92
93impl Binding {
94 fn snapshot(&self) -> SurfaceBinding {
95 SurfaceBinding {
96 surface: self.surface.clone(),
97 pane: self.pane.clone(),
98 resource: self.resource.clone(),
99 }
100 }
101}
102
103pub struct SurfaceHub {
110 canonical: BTreeMap<Symbol, Expr>,
111 registry: LensRegistry,
112 cx: Cx,
113 surfaces: BTreeMap<Symbol, SurfaceCaps>,
114 roles: BTreeMap<Symbol, SurfaceRole>,
115 bindings: Vec<Binding>,
116 ledger: Vec<EditRow>,
117}
118
119impl SurfaceHub {
120 pub fn new(handle_seed: sim_kernel::HandleSeed) -> Self {
124 let mut registry = LensRegistry::new();
125 register_universal_default(&mut registry, false);
126 Self {
127 canonical: BTreeMap::new(),
128 registry,
129 cx: Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory), handle_seed),
130 surfaces: BTreeMap::new(),
131 roles: BTreeMap::new(),
132 bindings: Vec::new(),
133 ledger: Vec::new(),
134 }
135 }
136
137 pub fn seed(&mut self, resource: Symbol, value: Expr) {
139 self.canonical.insert(resource, value);
140 }
141
142 pub fn register_surface(&mut self, surface: Symbol, caps: SurfaceCaps) {
145 self.register_surface_with_role(surface, caps, SurfaceRole::Main);
146 }
147
148 pub fn register_surface_with_role(
150 &mut self,
151 surface: Symbol,
152 caps: SurfaceCaps,
153 role: SurfaceRole,
154 ) {
155 self.roles.insert(surface.clone(), role);
156 self.surfaces.insert(surface, caps);
157 }
158
159 pub fn surface_role(&self, surface: &Symbol) -> Option<SurfaceRole> {
161 self.roles.get(surface).copied()
162 }
163
164 pub fn open(&mut self, surface: &Symbol, pane: Symbol, resource: Symbol) -> Result<Expr> {
171 let caps = self.caps_of(surface)?;
172 let value = self.value_of(&resource)?;
173 let scene = render_for_surface(&mut self.cx, &self.registry, &caps, &value)?;
174 self.bindings
175 .retain(|binding| !(binding.surface == *surface && binding.pane == pane));
176 self.bindings.push(Binding {
177 surface: surface.clone(),
178 pane,
179 resource,
180 last_scene: scene.clone(),
181 });
182 Ok(scene)
183 }
184
185 pub fn submit(
199 &mut self,
200 surface: &Symbol,
201 pane: &Symbol,
202 intent: &Expr,
203 ) -> Result<Vec<Broadcast>> {
204 let caps = self.caps_of(surface)?;
205 require_surface_input(&caps, intent)?;
206 let resource = self
207 .bindings
208 .iter()
209 .find(|binding| binding.surface == *surface && binding.pane == *pane)
210 .map(|binding| binding.resource.clone())
211 .ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
212 let value = self.value_of(&resource)?;
213
214 let editor = Symbol::new(UNIVERSAL_EDITOR_ID);
215 let draft = self
216 .registry
217 .propose(&mut self.cx, &editor, &value, intent)?;
218 let operation = self.registry.commit(&mut self.cx, &editor, &draft)?;
219 let new_value = apply_set_value(&operation.form)?;
220 self.commit_change(surface, pane, intent, new_value, operation.form)
221 }
222
223 pub fn commit_value_from(
231 &mut self,
232 surface: &Symbol,
233 pane: &Symbol,
234 intent: &Expr,
235 new_value: Expr,
236 ) -> Result<Vec<Broadcast>> {
237 sim_lib_intent::validate_intent(intent)
238 .map_err(|error| Error::HostError(format!("invalid intent: {error}")))?;
239 let caps = self.caps_of(surface)?;
240 require_surface_input(&caps, intent)?;
241 let resource = self
242 .bindings
243 .iter()
244 .find(|binding| binding.surface == *surface && binding.pane == *pane)
245 .map(|binding| binding.resource.clone())
246 .ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
247 self.commit_resource_change(resource, intent, new_value)
248 }
249
250 pub fn detach_surface(&mut self, surface: &Symbol) -> Vec<SurfaceBinding> {
253 self.surfaces.remove(surface);
254 self.roles.remove(surface);
255 let mut removed = Vec::new();
256 self.bindings.retain(|binding| {
257 if binding.surface == *surface {
258 removed.push(binding.snapshot());
259 false
260 } else {
261 true
262 }
263 });
264 removed
265 }
266
267 pub fn bindings_for_resource(&self, resource: &Symbol) -> Vec<SurfaceBinding> {
269 self.bindings
270 .iter()
271 .filter(|binding| binding.resource == *resource)
272 .map(Binding::snapshot)
273 .collect()
274 }
275
276 fn commit_change(
277 &mut self,
278 surface: &Symbol,
279 pane: &Symbol,
280 intent: &Expr,
281 new_value: Expr,
282 operation: Expr,
283 ) -> Result<Vec<Broadcast>> {
284 let resource = self
285 .bindings
286 .iter()
287 .find(|binding| binding.surface == *surface && binding.pane == *pane)
288 .map(|binding| binding.resource.clone())
289 .ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
290 self.commit_resource_change_with_operation(resource, intent, new_value, operation)
291 }
292
293 fn commit_resource_change(
294 &mut self,
295 resource: Symbol,
296 intent: &Expr,
297 new_value: Expr,
298 ) -> Result<Vec<Broadcast>> {
299 let operation = set_value_operation(new_value.clone());
300 self.commit_resource_change_with_operation(resource, intent, new_value, operation)
301 }
302
303 fn commit_resource_change_with_operation(
304 &mut self,
305 resource: Symbol,
306 intent: &Expr,
307 new_value: Expr,
308 operation: Expr,
309 ) -> Result<Vec<Broadcast>> {
310 let mut staged: Vec<(usize, Broadcast)> = Vec::new();
316 {
317 let Self {
318 registry,
319 cx,
320 surfaces,
321 bindings,
322 ..
323 } = self;
324 for (index, binding) in bindings.iter().enumerate() {
325 if binding.resource != resource {
326 continue;
327 }
328 let caps = surfaces.get(&binding.surface).ok_or_else(|| {
329 Error::HostError(format!(
330 "surface '{}' lost its capabilities",
331 binding.surface
332 ))
333 })?;
334 let scene = render_for_surface(cx, registry, caps, &new_value)?;
335 let diff = sim_lib_scene::diff(&binding.last_scene, &scene);
336 staged.push((
337 index,
338 Broadcast {
339 surface: binding.surface.clone(),
340 pane: binding.pane.clone(),
341 scene,
342 diff,
343 },
344 ));
345 }
346 }
347
348 self.canonical.insert(resource.clone(), new_value);
351 let (operator, tick) = origin_of(intent);
352 self.ledger.push(EditRow {
353 resource,
354 operator,
355 tick,
356 operation,
357 });
358 let mut broadcasts = Vec::with_capacity(staged.len());
359 for (index, broadcast) in staged {
360 self.bindings[index].last_scene = broadcast.scene.clone();
361 broadcasts.push(broadcast);
362 }
363 Ok(broadcasts)
364 }
365
366 pub fn handoff(
372 &mut self,
373 from: &Symbol,
374 to: &Symbol,
375 resource: Symbol,
376 pane: Symbol,
377 ) -> Result<Expr> {
378 let held = self
379 .bindings
380 .iter()
381 .any(|binding| binding.surface == *from && binding.resource == resource);
382 if !held {
383 return Err(Error::HostError(format!(
384 "surface '{from}' does not hold resource '{resource}' to hand off"
385 )));
386 }
387 self.open(to, pane, resource)
388 }
389
390 pub fn ledger(&self) -> &[EditRow] {
392 &self.ledger
393 }
394
395 pub fn canonical(&self, resource: &Symbol) -> Option<&Expr> {
397 self.canonical.get(resource)
398 }
399
400 fn caps_of(&self, surface: &Symbol) -> Result<SurfaceCaps> {
401 self.surfaces
402 .get(surface)
403 .cloned()
404 .ok_or_else(|| Error::HostError(format!("surface '{surface}' is not registered")))
405 }
406
407 fn value_of(&self, resource: &Symbol) -> Result<Expr> {
408 self.canonical.get(resource).cloned().ok_or_else(|| {
409 Error::HostError(format!("resource '{resource}' has no canonical value"))
410 })
411 }
412}
413
414pub fn replay(rows: &[EditRow], seed: BTreeMap<Symbol, Expr>) -> Result<BTreeMap<Symbol, Expr>> {
426 let mut state = seed;
427 for row in rows {
428 let value = apply_set_value(&row.operation)?;
429 state.insert(row.resource.clone(), value);
430 }
431 Ok(state)
432}
433
434fn render_for_surface(
436 cx: &mut Cx,
437 registry: &LensRegistry,
438 caps: &SurfaceCaps,
439 value: &Expr,
440) -> Result<Expr> {
441 let scene = registry.render(cx, &Symbol::new(UNIVERSAL_VIEW_ID), value)?;
442 Ok(reduce_for_caps(&scene, caps))
443}
444
445fn require_surface_input(caps: &SurfaceCaps, intent: &Expr) -> Result<()> {
446 let required = input_capabilities_for_intent(intent)?;
447 if required
448 .iter()
449 .any(|capability| caps.input_flag(capability))
450 {
451 return Ok(());
452 }
453 Err(Error::HostError(format!(
454 "surface '{}' does not accept any required input for this Intent: {}",
455 caps.client_id,
456 required.join(", ")
457 )))
458}
459
460fn input_capabilities_for_intent(intent: &Expr) -> Result<&'static [&'static str]> {
461 let kind = match sim_value::access::field(intent, "kind") {
462 Some(Expr::Symbol(kind)) if kind.namespace.as_deref() == Some("intent") => {
463 kind.name.as_ref()
464 }
465 Some(Expr::Symbol(_)) => {
466 return Err(Error::HostError(
467 "Intent kind must be in the intent namespace".to_owned(),
468 ));
469 }
470 _ => return Err(Error::HostError("submit input is not an Intent".to_owned())),
471 };
472 match kind {
473 "tap" | "dismiss" | "commit" | "cancel" | "approve" | "reject" | "pause-agent"
474 | "rerun-validation" | "replay-cassette" => Ok(&["tap", "pointer", "touch", "keyboard"]),
475 "select" | "move" | "wire" | "unwire" | "create" | "delete" | "scrub"
476 | "piano-roll-edit" | "player-rack-edit" | "arranger-edit" => Ok(&["pointer", "touch"]),
477 "invoke" => Ok(&[
478 "pointer",
479 "touch",
480 "tap",
481 "button",
482 "gaze",
483 "head",
484 "hand",
485 "controller",
486 "voice",
487 ]),
488 "edit" | "edit-field" | "set-param" | "set-lens" | "set-mode" | "open" | "ask"
489 | "split-mission" | "open-source" => Ok(&["keyboard", "touch", "voice"]),
490 "performance-event" => Ok(&["keyboard", "touch", "camera"]),
491 other => Err(Error::HostError(format!(
492 "no surface input capability mapping for intent/{other}"
493 ))),
494 }
495}
496
497fn set_value_operation(value: Expr) -> Expr {
498 Expr::Map(vec![
499 (
500 Expr::Symbol(Symbol::new("op")),
501 Expr::Symbol(Symbol::new("set-value")),
502 ),
503 (Expr::Symbol(Symbol::new("value")), value),
504 ])
505}
506
507fn apply_set_value(operation: &Expr) -> Result<Expr> {
510 let Expr::Map(entries) = operation else {
511 return Err(Error::HostError("operation is not a map".to_owned()));
512 };
513 let is_set_value = matches!(
514 sim_value::access::entry_field(entries, "op"),
515 Some(Expr::Symbol(symbol)) if &*symbol.name == "set-value"
516 );
517 if !is_set_value {
518 return Err(Error::HostError(
519 "operation is not a set-value op".to_owned(),
520 ));
521 }
522 sim_value::access::entry_field(entries, "value")
523 .cloned()
524 .ok_or_else(|| Error::HostError("set-value operation is missing a 'value'".to_owned()))
525}
526
527fn origin_of(intent: &Expr) -> (Symbol, u64) {
530 let origin = sim_value::access::field(intent, "origin");
531 let operator = origin
532 .and_then(|origin| sim_value::access::field_sym(origin, "operator"))
533 .unwrap_or_else(|| Symbol::new("unknown"));
534 let tick = origin
535 .and_then(|origin| sim_value::access::field_any(origin, "at-tick"))
536 .and_then(|tick| match tick {
537 Expr::Number(number) => number.canonical.parse::<u64>().ok(),
538 _ => None,
539 })
540 .unwrap_or(0);
541 (operator, tick)
542}
543
544#[cfg(test)]
545mod tests;