Skip to main content

npe_graph/
library.rs

1//! Component libraries: define a component (data + pinout) once, stamp it
2//! into the graph many times.
3//!
4//! A [`NodeTemplate`] is anything that can produce node data plus an ordered
5//! pinout. It's deliberately **dyn-compatible**, so a GUI palette can hold a
6//! heterogeneous library:
7//!
8//! ```
9//! # use npe_graph::{Graph, NodeTemplate};
10//! # struct OpAmp; struct Resistor;
11//! # impl NodeTemplate<String, String> for OpAmp {
12//! #     fn node_data(&self) -> String { "opamp".into() }
13//! #     fn port_data(&self) -> Vec<String> { vec!["in+".into(), "in-".into(), "out".into()] }
14//! # }
15//! # impl NodeTemplate<String, String> for Resistor {
16//! #     fn node_data(&self) -> String { "R".into() }
17//! #     fn port_data(&self) -> Vec<String> { vec!["a".into(), "b".into()] }
18//! # }
19//! let palette: Vec<Box<dyn NodeTemplate<String, String>>> =
20//!     vec![Box::new(OpAmp), Box::new(Resistor)];
21//!
22//! let mut g: Graph<String, String, ()> = Graph::new();
23//! let (id, pins) = g.instantiate(palette[0].as_ref());
24//! assert_eq!(pins.len(), 3);
25//! ```
26//!
27//! Templates take `&self` and produce owned data: a library entry is shared
28//! and stamped many times, so instantiation is inherently a "make me a fresh
29//! copy" operation. If your `N` needs per-instance state (reference
30//! designators like R1, R2, ...), do that in a wrapper around `instantiate`
31//! that owns the counter — the template stays pure.
32
33use crate::graph::Graph;
34use crate::id::{NodeId, PortId};
35use std::collections::HashMap;
36use std::hash::Hash;
37
38/// A reusable component whose pins each carry a **key** — typically an enum
39/// variant — declared alongside their data.
40///
41/// This is the keyed sibling of [`NodeTemplate`]. It exists to kill positional
42/// index bookkeeping: the pin's identity and its data are stated together in
43/// one list, and [`Graph::instantiate_keyed`] returns a `key → PortId` map, so
44/// no call site ever refers to a pin by integer position.
45///
46/// ```
47/// # use npe_graph::KeyedNodeTemplate;
48/// #[derive(PartialEq, Eq, Hash, Clone, Copy)]
49/// enum Pin { Anode, Cathode }
50///
51/// struct Diode;
52/// impl KeyedNodeTemplate<&'static str, &'static str, Pin> for Diode {
53///     fn node_data(&self) -> &'static str { "D" }
54///     fn keyed_ports(&self) -> Vec<(Pin, &'static str)> {
55///         vec![(Pin::Anode, "+"), (Pin::Cathode, "-")]
56///     }
57/// }
58/// ```
59pub trait KeyedNodeTemplate<N, P, K> {
60    /// Fresh node data for one instance of this component.
61    fn node_data(&self) -> N;
62
63    /// Fresh `(key, port data)` pairs in pinout order. The key is what you'll
64    /// look up by; the data is the port payload.
65    fn keyed_ports(&self) -> Vec<(K, P)>;
66}
67
68/// A reusable component definition: node data plus an ordered pinout.
69///
70/// Object-safe by design (`Vec<P>`, not `impl Iterator`), so libraries can be
71/// `Vec<Box<dyn NodeTemplate<N, P>>>`.
72pub trait NodeTemplate<N, P> {
73    /// Fresh node data for one instance of this component.
74    fn node_data(&self) -> N;
75
76    /// Fresh port data for one instance, in pinout order. The `PortId`s
77    /// returned by [`Graph::instantiate`] correspond to this order, index
78    /// for index.
79    fn port_data(&self) -> Vec<P>;
80}
81
82/// The no-trait-needed escape hatch: a plain blueprint struct.
83///
84/// If your library is data-driven (loaded from RON/JSON at startup) rather
85/// than a set of Rust types, just build these. It implements [`NodeTemplate`]
86/// by cloning.
87#[derive(Debug, Clone)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct NodeProto<N, P> {
90    /// Data for the node itself.
91    pub data: N,
92    /// Port data in pinout order.
93    pub ports: Vec<P>,
94}
95
96impl<N: Clone, P: Clone> NodeTemplate<N, P> for NodeProto<N, P> {
97    fn node_data(&self) -> N {
98        self.data.clone()
99    }
100
101    fn port_data(&self) -> Vec<P> {
102        self.ports.clone()
103    }
104}
105
106impl<N, P, E> Graph<N, P, E> {
107    /// Stamps one instance of `template` into the graph: adds the node, then
108    /// its ports in pinout order.
109    ///
110    /// Returns the new node and its ports, where `ports[i]` corresponds to
111    /// `template.port_data()[i]` — so a GUI symbol or a netlister can address
112    /// pins positionally. (The same ordering is also recoverable later via
113    /// [`Graph::ports`], which preserves insertion order.)
114    ///
115    /// Accepts `&dyn NodeTemplate<N, P>` as well as concrete types.
116    pub fn instantiate(
117        &mut self,
118        template: &(impl NodeTemplate<N, P> + ?Sized),
119    ) -> (NodeId, Vec<PortId>) {
120        let node = self.add_node(template.node_data());
121        let ports = template
122            .port_data()
123            .into_iter()
124            .map(|p| {
125                self.add_port(node, p)
126                    .expect("node was just added; it must exist")
127            })
128            .collect();
129        (node, ports)
130    }
131
132    /// Stamps `template` into the graph and returns its ports keyed by the
133    /// label each pin declared — no positional indices to keep in sync.
134    ///
135    /// This is the fix for the "magic index constant" problem: with
136    /// [`Graph::instantiate`] you address `ports[2]` and must keep that `2`
137    /// aligned with the pinout by hand. Here the key travels *with* the pin
138    /// data in a single declaration, so reordering or inserting pins can't
139    /// break a call site. Use an enum key for compile-time-checked, typo-proof
140    /// lookups: `pins[&Pin::Output]`.
141    ///
142    /// Insertion order still follows the declared order, so [`Graph::ports`]
143    /// remains positional too — you get both views from one source of truth.
144    /// Duplicate keys collapse (last wins); for a pinout that's a bug worth a
145    /// debug assertion in your template.
146    ///
147    /// ```
148    /// use npe_graph::{Graph, KeyedNodeTemplate};
149    ///
150    /// #[derive(PartialEq, Eq, Hash, Clone, Copy)]
151    /// enum Pin { A, B }
152    ///
153    /// struct Resistor;
154    /// impl KeyedNodeTemplate<&'static str, &'static str, Pin> for Resistor {
155    ///     fn node_data(&self) -> &'static str { "R" }
156    ///     fn keyed_ports(&self) -> Vec<(Pin, &'static str)> {
157    ///         vec![(Pin::A, "a"), (Pin::B, "b")]
158    ///     }
159    /// }
160    ///
161    /// let mut g: Graph<&str, &str, ()> = Graph::new();
162    /// let (n, pins) = g.instantiate_keyed(&Resistor);
163    /// assert_eq!(g[pins[&Pin::A]], "a");
164    /// assert_eq!(g.ports(n).count(), 2); // positional order preserved too
165    /// ```
166    pub fn instantiate_keyed<K>(
167        &mut self,
168        template: &(impl KeyedNodeTemplate<N, P, K> + ?Sized),
169    ) -> (NodeId, HashMap<K, PortId>)
170    where
171        K: Eq + Hash,
172    {
173        let node = self.add_node(template.node_data());
174        let map = template
175            .keyed_ports()
176            .into_iter()
177            .map(|(key, data)| {
178                let port = self
179                    .add_port(node, data)
180                    .expect("node was just added; it must exist");
181                (key, port)
182            })
183            .collect();
184        (node, map)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    struct Ic74x00; // quad NAND, abridged pinout
193
194    impl NodeTemplate<&'static str, &'static str> for Ic74x00 {
195        fn node_data(&self) -> &'static str {
196            "74x00"
197        }
198        fn port_data(&self) -> Vec<&'static str> {
199            vec!["1A", "1B", "1Y", "GND", "VCC"]
200        }
201    }
202
203    #[test]
204    fn instantiate_concrete_and_dyn() {
205        let mut g: Graph<&str, &str, ()> = Graph::new();
206
207        // Concrete.
208        let (u1, u1_pins) = g.instantiate(&Ic74x00);
209        assert_eq!(g[u1], "74x00");
210        assert_eq!(u1_pins.len(), 5);
211        assert_eq!(g[u1_pins[3]], "GND");
212
213        // Through a heterogeneous palette.
214        let palette: Vec<Box<dyn NodeTemplate<&str, &str>>> = vec![
215            Box::new(Ic74x00),
216            Box::new(NodeProto {
217                data: "R",
218                ports: vec!["a", "b"],
219            }),
220        ];
221        let (u2, _) = g.instantiate(palette[0].as_ref());
222        let (r1, r1_pins) = g.instantiate(palette[1].as_ref());
223        assert_eq!(g[u2], "74x00");
224        assert_eq!(g[r1], "R");
225        assert_eq!(r1_pins.len(), 2);
226
227        // Instances are independent: pinout order preserved per node.
228        let order: Vec<_> = g.ports(u1).collect();
229        assert_eq!(order, u1_pins);
230    }
231}