Skip to main content

klayout_core/
layer.rs

1//! Layers: local indexing inside a Library, GDS (layer, datatype) backing.
2//!
3//! `LayerIndex` is opaque and only meaningful with the Library that issued it.
4//! Crossing libraries goes through `LayerInfo` and an explicit remap.
5
6use smol_str::SmolStr;
7use std::collections::HashMap;
8use std::num::NonZeroU16;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub struct LayerIndex(NonZeroU16);
12
13impl LayerIndex {
14    pub(crate) fn from_slot(slot: usize) -> Self {
15        // LayerIndex packs a 1-based slot into a NonZeroU16. The 64K
16        // ceiling is the deliberate design budget — exceeding it is a
17        // hard error rather than a recoverable condition because
18        // every consumer assumes a stable LayerIndex domain.
19        let n = u16::try_from(slot + 1)
20            .expect("LayerIndex arena exceeded u16::MAX (65535 layers)");
21        Self(NonZeroU16::new(n).expect("LayerIndex: slot+1 ≥ 1 by construction"))
22    }
23
24    pub(crate) fn slot(self) -> usize {
25        self.0.get() as usize - 1
26    }
27
28    pub fn raw(self) -> u16 {
29        self.0.get()
30    }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct LayerInfo {
35    pub layer: u16,
36    pub datatype: u16,
37    pub name: SmolStr,
38    pub purpose: Option<SmolStr>,
39}
40
41impl LayerInfo {
42    pub fn gds(layer: u16, datatype: u16) -> Self {
43        Self {
44            layer,
45            datatype,
46            name: SmolStr::default(),
47            purpose: None,
48        }
49    }
50
51    pub fn named(name: impl Into<SmolStr>, layer: u16, datatype: u16) -> Self {
52        Self {
53            layer,
54            datatype,
55            name: name.into(),
56            purpose: None,
57        }
58    }
59
60    pub fn key(&self) -> (u16, u16) {
61        (self.layer, self.datatype)
62    }
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct LayerTable {
67    by_index: Vec<LayerInfo>,
68    by_gds: HashMap<(u16, u16), LayerIndex>,
69    by_name: HashMap<SmolStr, LayerIndex>,
70}
71
72impl LayerTable {
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    pub fn get_or_insert(&mut self, info: LayerInfo) -> LayerIndex {
78        if let Some(&idx) = self.by_gds.get(&info.key()) {
79            return idx;
80        }
81        let idx = LayerIndex::from_slot(self.by_index.len());
82        self.by_gds.insert(info.key(), idx);
83        if !info.name.is_empty() {
84            self.by_name.insert(info.name.clone(), idx);
85        }
86        self.by_index.push(info);
87        idx
88    }
89
90    pub fn info(&self, idx: LayerIndex) -> &LayerInfo {
91        &self.by_index[idx.slot()]
92    }
93
94    pub fn by_gds(&self, layer: u16, datatype: u16) -> Option<LayerIndex> {
95        self.by_gds.get(&(layer, datatype)).copied()
96    }
97
98    pub fn by_name(&self, name: &str) -> Option<LayerIndex> {
99        self.by_name.get(name).copied()
100    }
101
102    pub fn len(&self) -> usize {
103        self.by_index.len()
104    }
105
106    pub fn is_empty(&self) -> bool {
107        self.by_index.is_empty()
108    }
109
110    pub fn iter(&self) -> impl Iterator<Item = (LayerIndex, &LayerInfo)> {
111        self.by_index
112            .iter()
113            .enumerate()
114            .map(|(i, info)| (LayerIndex::from_slot(i), info))
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn dedup_by_gds() {
124        let mut t = LayerTable::new();
125        let a = t.get_or_insert(LayerInfo::named("WG", 1, 0));
126        let b = t.get_or_insert(LayerInfo::gds(1, 0));
127        assert_eq!(a, b);
128        assert_eq!(t.len(), 1);
129        assert_eq!(t.by_name("WG"), Some(a));
130    }
131}