Skip to main content

sim_lib_view_agent/
view.rs

1//! The topology composer view: a `Graph` encoded as a `scene/graph`.
2//!
3//! The view reads the existing `sim-lib-topology` `Graph` -- no second model --
4//! and emits a graph Scene of agent nodes with typed ports, edges, and
5//! role/capability labels. Node positions are read from graph metadata
6//! (`pos:<id>`), so layout rides along as data on the same value.
7
8use sim_kernel::{Expr, Symbol};
9use sim_lib_scene::{data_map, node, sym};
10use sim_lib_topology::{Graph, Node, Port};
11
12/// The composer lens id.
13pub const COMPOSER_LENS: &str = "view:agent-topology";
14
15/// Encode a topology `Graph` into a `scene/graph` Scene.
16pub fn composer_view(graph: &Graph) -> Expr {
17    let nodes = graph.nodes.iter().map(|n| node_scene(graph, n)).collect();
18    let edges = graph
19        .edges
20        .iter()
21        .map(|edge| {
22            node(
23                "edge",
24                vec![
25                    ("from", port_pair(&edge.from.node, &edge.from.port)),
26                    ("to", port_pair(&edge.to.node, &edge.to.port)),
27                    ("status", sym("live")),
28                ],
29            )
30        })
31        .collect();
32    node(
33        "graph",
34        vec![
35            ("id", Expr::Symbol(graph.name.clone())),
36            ("nodes", Expr::List(nodes)),
37            ("edges", Expr::List(edges)),
38            (
39                "intents",
40                data_map(vec![
41                    ("node-move", sym("enabled")),
42                    ("wire", sym("enabled")),
43                    ("select", sym("enabled")),
44                    ("create", sym("enabled")),
45                    ("delete", sym("enabled")),
46                ]),
47            ),
48        ],
49    )
50}
51
52fn node_scene(graph: &Graph, n: &Node) -> Expr {
53    let mut ports: Vec<Expr> = Vec::new();
54    for port in &n.inputs {
55        ports.push(port_descriptor(port, "in"));
56    }
57    for port in &n.outputs {
58        ports.push(port_descriptor(port, "out"));
59    }
60    node(
61        "node",
62        vec![
63            ("id", Expr::Symbol(n.id.as_symbol().clone())),
64            ("title", Expr::String(n.id.as_symbol().name.to_string())),
65            ("verb", Expr::Symbol(n.verb.clone())),
66            (
67                "role",
68                n.role.clone().map(Expr::Symbol).unwrap_or(Expr::Nil),
69            ),
70            ("at", position(graph, n.id.as_symbol())),
71            ("ports", Expr::List(ports)),
72        ],
73    )
74}
75
76fn port_descriptor(port: &Port, dir: &str) -> Expr {
77    data_map(vec![
78        ("id", Expr::Symbol(port.name.clone())),
79        ("dir", sym(dir)),
80        ("label", Expr::String(port.name.name.to_string())),
81        ("required", Expr::Bool(port.required)),
82    ])
83}
84
85fn port_pair(node_id: &sim_lib_topology::NodeId, port: &Symbol) -> Expr {
86    Expr::Vector(vec![
87        Expr::Symbol(node_id.as_symbol().clone()),
88        Expr::Symbol(port.clone()),
89    ])
90}
91
92fn position(graph: &Graph, node_id: &Symbol) -> Expr {
93    let key = Symbol::new(format!("pos:{}", node_id.name));
94    graph
95        .metadata
96        .iter()
97        .find_map(|(meta_key, value)| (meta_key == &key).then(|| value.clone()))
98        .unwrap_or_else(|| data_map(vec![("x", Expr::Nil), ("y", Expr::Nil)]))
99}