zerodds_ast_layout/
cache.rs1use crate::key::LayoutKey;
11use crate::plan::{LayoutError, TypeLayout};
12use crate::xcdr::{DepCollector, plan};
13use alloc::collections::BTreeMap;
14use alloc::vec::Vec;
15use zerodds_ast::{Ast, TypeNode};
16use zerodds_ast_core::{Change, Changeset, Id, NodeId};
17
18#[derive(Debug, Clone)]
20struct Entry {
21 layout: TypeLayout,
22 deps: Vec<NodeId>,
23}
24
25#[derive(Debug, Clone, Default)]
31pub struct LayoutCache {
32 entries: BTreeMap<(NodeId, crate::OffsetKey), Entry>,
33 hits: u64,
34 misses: u64,
35}
36
37impl LayoutCache {
38 #[must_use]
40 pub const fn new() -> Self {
41 Self {
42 entries: BTreeMap::new(),
43 hits: 0,
44 misses: 0,
45 }
46 }
47
48 pub fn layout(
54 &mut self,
55 ast: &Ast,
56 type_: Id<TypeNode>,
57 key: LayoutKey,
58 ) -> Result<&TypeLayout, LayoutError> {
59 let node_id = ast.type_(type_)?.node_id;
60 let slot = (node_id, key.offset_key());
61
62 if let alloc::collections::btree_map::Entry::Vacant(vacant) = self.entries.entry(slot) {
63 let mut deps = DepCollector::new();
64 let layout = plan(ast, type_, key, &mut deps)?;
65 let _ = vacant.insert(Entry {
66 layout,
67 deps: deps.into_deps(),
68 });
69 self.misses = self.misses.saturating_add(1);
70 } else {
71 self.hits = self.hits.saturating_add(1);
72 }
73
74 self.entries
75 .get(&slot)
76 .map(|entry| &entry.layout)
77 .ok_or(LayoutError::Unresolved { node: node_id })
78 }
79
80 pub fn invalidate(&mut self, changeset: &Changeset) {
87 let touched: Vec<NodeId> = changeset.nodes_touched().collect();
88 if touched.is_empty() {
89 return;
90 }
91 self.entries.retain(|(node, _), entry| {
92 !touched.contains(node) && !entry.deps.iter().any(|dep| touched.contains(dep))
93 });
94 }
95
96 pub fn clear(&mut self) {
98 self.entries.clear();
99 }
100
101 #[must_use]
103 pub fn len(&self) -> usize {
104 self.entries.len()
105 }
106
107 #[must_use]
109 pub fn is_empty(&self) -> bool {
110 self.entries.is_empty()
111 }
112
113 #[must_use]
115 pub const fn hits(&self) -> u64 {
116 self.hits
117 }
118
119 #[must_use]
121 pub const fn misses(&self) -> u64 {
122 self.misses
123 }
124
125 #[must_use]
127 pub fn contains(&self, type_: NodeId, key: LayoutKey) -> bool {
128 self.entries.contains_key(&(type_, key.offset_key()))
129 }
130}
131
132#[cfg(feature = "std")]
137pub struct SharedLayoutCache {
138 inner: std::sync::Mutex<LayoutCache>,
139}
140
141#[cfg(feature = "std")]
142impl SharedLayoutCache {
143 #[must_use]
145 pub fn new() -> Self {
146 Self {
147 inner: std::sync::Mutex::new(LayoutCache::new()),
148 }
149 }
150
151 pub fn with<R>(&self, f: impl FnOnce(&mut LayoutCache) -> R) -> Option<R> {
157 self.inner.lock().ok().map(|mut guard| f(&mut guard))
158 }
159
160 #[must_use]
162 pub fn len(&self) -> usize {
163 self.with(|cache| cache.len()).unwrap_or(0)
164 }
165
166 #[must_use]
168 pub fn is_empty(&self) -> bool {
169 self.len() == 0
170 }
171}
172
173#[cfg(feature = "std")]
174impl Default for SharedLayoutCache {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[cfg(feature = "std")]
181impl core::fmt::Debug for SharedLayoutCache {
182 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183 f.debug_struct("SharedLayoutCache")
184 .field("len", &self.len())
185 .finish()
186 }
187}
188
189#[cfg(feature = "std")]
190impl zerodds_ast_core::AstObserver for SharedLayoutCache {
191 fn on_commit(&self, changeset: &Changeset) {
192 let _ = self.with(|cache| cache.invalidate(changeset));
193 }
194}
195
196#[must_use]
201pub fn touches_layout(changeset: &Changeset) -> bool {
202 changeset.iter().any(|change| {
203 matches!(
204 change,
205 Change::Updated { .. } | Change::Deleted { .. } | Change::Created { .. }
206 )
207 })
208}