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}