use std::collections::BTreeMap;
use std::sync::Arc;
use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Error, Expr, Result, Symbol};
use sim_lib_view::codec::reduce_for_caps;
use sim_lib_view::{
LensRegistry, SurfaceCaps, UNIVERSAL_EDITOR_ID, UNIVERSAL_VIEW_ID, register_universal_default,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SurfaceRole {
Main,
Peer,
}
#[derive(Clone, Debug)]
pub struct Broadcast {
pub surface: Symbol,
pub pane: Symbol,
pub scene: Expr,
pub diff: Expr,
}
#[derive(Clone, Debug)]
pub struct EditRow {
pub resource: Symbol,
pub operator: Symbol,
pub tick: u64,
pub operation: Expr,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SurfaceBinding {
pub surface: Symbol,
pub pane: Symbol,
pub resource: Symbol,
}
struct Binding {
surface: Symbol,
pane: Symbol,
resource: Symbol,
last_scene: Expr,
}
impl Binding {
fn snapshot(&self) -> SurfaceBinding {
SurfaceBinding {
surface: self.surface.clone(),
pane: self.pane.clone(),
resource: self.resource.clone(),
}
}
}
pub struct SurfaceHub {
canonical: BTreeMap<Symbol, Expr>,
registry: LensRegistry,
cx: Cx,
surfaces: BTreeMap<Symbol, SurfaceCaps>,
roles: BTreeMap<Symbol, SurfaceRole>,
bindings: Vec<Binding>,
ledger: Vec<EditRow>,
}
impl Default for SurfaceHub {
fn default() -> Self {
Self::new()
}
}
impl SurfaceHub {
pub fn new() -> Self {
let mut registry = LensRegistry::new();
register_universal_default(&mut registry, false);
Self {
canonical: BTreeMap::new(),
registry,
cx: Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory)),
surfaces: BTreeMap::new(),
roles: BTreeMap::new(),
bindings: Vec::new(),
ledger: Vec::new(),
}
}
pub fn seed(&mut self, resource: Symbol, value: Expr) {
self.canonical.insert(resource, value);
}
pub fn register_surface(&mut self, surface: Symbol, caps: SurfaceCaps) {
self.register_surface_with_role(surface, caps, SurfaceRole::Main);
}
pub fn register_surface_with_role(
&mut self,
surface: Symbol,
caps: SurfaceCaps,
role: SurfaceRole,
) {
self.roles.insert(surface.clone(), role);
self.surfaces.insert(surface, caps);
}
pub fn surface_role(&self, surface: &Symbol) -> Option<SurfaceRole> {
self.roles.get(surface).copied()
}
pub fn open(&mut self, surface: &Symbol, pane: Symbol, resource: Symbol) -> Result<Expr> {
let caps = self.caps_of(surface)?;
let value = self.value_of(&resource)?;
let scene = render_for_surface(&mut self.cx, &self.registry, &caps, &value)?;
self.bindings
.retain(|binding| !(binding.surface == *surface && binding.pane == pane));
self.bindings.push(Binding {
surface: surface.clone(),
pane,
resource,
last_scene: scene.clone(),
});
Ok(scene)
}
pub fn submit(
&mut self,
surface: &Symbol,
pane: &Symbol,
intent: &Expr,
) -> Result<Vec<Broadcast>> {
let caps = self.caps_of(surface)?;
require_surface_input(&caps, intent)?;
let resource = self
.bindings
.iter()
.find(|binding| binding.surface == *surface && binding.pane == *pane)
.map(|binding| binding.resource.clone())
.ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
let value = self.value_of(&resource)?;
let editor = Symbol::new(UNIVERSAL_EDITOR_ID);
let draft = self
.registry
.propose(&mut self.cx, &editor, &value, intent)?;
let operation = self.registry.commit(&mut self.cx, &editor, &draft)?;
let new_value = apply_set_value(&operation.form)?;
self.commit_change(surface, pane, intent, new_value, operation.form)
}
pub fn commit_value_from(
&mut self,
surface: &Symbol,
pane: &Symbol,
intent: &Expr,
new_value: Expr,
) -> Result<Vec<Broadcast>> {
sim_lib_intent::validate_intent(intent)
.map_err(|error| Error::HostError(format!("invalid intent: {error}")))?;
let caps = self.caps_of(surface)?;
require_surface_input(&caps, intent)?;
let resource = self
.bindings
.iter()
.find(|binding| binding.surface == *surface && binding.pane == *pane)
.map(|binding| binding.resource.clone())
.ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
self.commit_resource_change(resource, intent, new_value)
}
pub fn detach_surface(&mut self, surface: &Symbol) -> Vec<SurfaceBinding> {
self.surfaces.remove(surface);
self.roles.remove(surface);
let mut removed = Vec::new();
self.bindings.retain(|binding| {
if binding.surface == *surface {
removed.push(binding.snapshot());
false
} else {
true
}
});
removed
}
pub fn bindings_for_resource(&self, resource: &Symbol) -> Vec<SurfaceBinding> {
self.bindings
.iter()
.filter(|binding| binding.resource == *resource)
.map(Binding::snapshot)
.collect()
}
fn commit_change(
&mut self,
surface: &Symbol,
pane: &Symbol,
intent: &Expr,
new_value: Expr,
operation: Expr,
) -> Result<Vec<Broadcast>> {
let resource = self
.bindings
.iter()
.find(|binding| binding.surface == *surface && binding.pane == *pane)
.map(|binding| binding.resource.clone())
.ok_or_else(|| Error::HostError(format!("({surface}, {pane}) is not open")))?;
self.commit_resource_change_with_operation(resource, intent, new_value, operation)
}
fn commit_resource_change(
&mut self,
resource: Symbol,
intent: &Expr,
new_value: Expr,
) -> Result<Vec<Broadcast>> {
let operation = set_value_operation(new_value.clone());
self.commit_resource_change_with_operation(resource, intent, new_value, operation)
}
fn commit_resource_change_with_operation(
&mut self,
resource: Symbol,
intent: &Expr,
new_value: Expr,
operation: Expr,
) -> Result<Vec<Broadcast>> {
let mut staged: Vec<(usize, Broadcast)> = Vec::new();
{
let Self {
registry,
cx,
surfaces,
bindings,
..
} = self;
for (index, binding) in bindings.iter().enumerate() {
if binding.resource != resource {
continue;
}
let caps = surfaces.get(&binding.surface).ok_or_else(|| {
Error::HostError(format!(
"surface '{}' lost its capabilities",
binding.surface
))
})?;
let scene = render_for_surface(cx, registry, caps, &new_value)?;
let diff = sim_lib_scene::diff(&binding.last_scene, &scene);
staged.push((
index,
Broadcast {
surface: binding.surface.clone(),
pane: binding.pane.clone(),
scene,
diff,
},
));
}
}
self.canonical.insert(resource.clone(), new_value);
let (operator, tick) = origin_of(intent);
self.ledger.push(EditRow {
resource,
operator,
tick,
operation,
});
let mut broadcasts = Vec::with_capacity(staged.len());
for (index, broadcast) in staged {
self.bindings[index].last_scene = broadcast.scene.clone();
broadcasts.push(broadcast);
}
Ok(broadcasts)
}
pub fn handoff(
&mut self,
from: &Symbol,
to: &Symbol,
resource: Symbol,
pane: Symbol,
) -> Result<Expr> {
let held = self
.bindings
.iter()
.any(|binding| binding.surface == *from && binding.resource == resource);
if !held {
return Err(Error::HostError(format!(
"surface '{from}' does not hold resource '{resource}' to hand off"
)));
}
self.open(to, pane, resource)
}
pub fn ledger(&self) -> &[EditRow] {
&self.ledger
}
pub fn canonical(&self, resource: &Symbol) -> Option<&Expr> {
self.canonical.get(resource)
}
fn caps_of(&self, surface: &Symbol) -> Result<SurfaceCaps> {
self.surfaces
.get(surface)
.cloned()
.ok_or_else(|| Error::HostError(format!("surface '{surface}' is not registered")))
}
fn value_of(&self, resource: &Symbol) -> Result<Expr> {
self.canonical.get(resource).cloned().ok_or_else(|| {
Error::HostError(format!("resource '{resource}' has no canonical value"))
})
}
}
pub fn replay(rows: &[EditRow], seed: BTreeMap<Symbol, Expr>) -> Result<BTreeMap<Symbol, Expr>> {
let mut state = seed;
for row in rows {
let value = apply_set_value(&row.operation)?;
state.insert(row.resource.clone(), value);
}
Ok(state)
}
fn render_for_surface(
cx: &mut Cx,
registry: &LensRegistry,
caps: &SurfaceCaps,
value: &Expr,
) -> Result<Expr> {
let scene = registry.render(cx, &Symbol::new(UNIVERSAL_VIEW_ID), value)?;
Ok(reduce_for_caps(&scene, caps))
}
fn require_surface_input(caps: &SurfaceCaps, intent: &Expr) -> Result<()> {
let required = input_capabilities_for_intent(intent)?;
if required
.iter()
.any(|capability| caps.input_flag(capability))
{
return Ok(());
}
Err(Error::HostError(format!(
"surface '{}' does not accept any required input for this Intent: {}",
caps.client_id,
required.join(", ")
)))
}
fn input_capabilities_for_intent(intent: &Expr) -> Result<&'static [&'static str]> {
let kind = match sim_value::access::field(intent, "kind") {
Some(Expr::Symbol(kind)) if kind.namespace.as_deref() == Some("intent") => {
kind.name.as_ref()
}
Some(Expr::Symbol(_)) => {
return Err(Error::HostError(
"Intent kind must be in the intent namespace".to_owned(),
));
}
_ => return Err(Error::HostError("submit input is not an Intent".to_owned())),
};
match kind {
"tap" | "dismiss" | "commit" | "cancel" | "approve" | "reject" | "pause-agent"
| "rerun-validation" | "replay-cassette" => Ok(&["tap", "pointer", "touch", "keyboard"]),
"select" | "move" | "wire" | "unwire" | "create" | "delete" | "scrub"
| "piano-roll-edit" | "player-rack-edit" | "arranger-edit" => Ok(&["pointer", "touch"]),
"invoke" => Ok(&[
"pointer",
"touch",
"tap",
"button",
"gaze",
"head",
"hand",
"controller",
"voice",
]),
"edit" | "edit-field" | "set-param" | "set-lens" | "set-mode" | "open" | "ask"
| "split-mission" | "open-source" => Ok(&["keyboard", "touch", "voice"]),
"performance-event" => Ok(&["keyboard", "touch", "camera"]),
other => Err(Error::HostError(format!(
"no surface input capability mapping for intent/{other}"
))),
}
}
fn set_value_operation(value: Expr) -> Expr {
Expr::Map(vec![
(
Expr::Symbol(Symbol::new("op")),
Expr::Symbol(Symbol::new("set-value")),
),
(Expr::Symbol(Symbol::new("value")), value),
])
}
fn apply_set_value(operation: &Expr) -> Result<Expr> {
let Expr::Map(entries) = operation else {
return Err(Error::HostError("operation is not a map".to_owned()));
};
let is_set_value = matches!(
sim_value::access::entry_field(entries, "op"),
Some(Expr::Symbol(symbol)) if &*symbol.name == "set-value"
);
if !is_set_value {
return Err(Error::HostError(
"operation is not a set-value op".to_owned(),
));
}
sim_value::access::entry_field(entries, "value")
.cloned()
.ok_or_else(|| Error::HostError("set-value operation is missing a 'value'".to_owned()))
}
fn origin_of(intent: &Expr) -> (Symbol, u64) {
let origin = sim_value::access::field(intent, "origin");
let operator = origin
.and_then(|origin| sim_value::access::field_sym(origin, "operator"))
.unwrap_or_else(|| Symbol::new("unknown"));
let tick = origin
.and_then(|origin| sim_value::access::field_any(origin, "at-tick"))
.and_then(|tick| match tick {
Expr::Number(number) => number.canonical.parse::<u64>().ok(),
_ => None,
})
.unwrap_or(0);
(operator, tick)
}
#[cfg(test)]
mod tests;