vexy_vsvg/optimizer/
memory.rs1use std::sync::atomic::{AtomicUsize, Ordering};
9use std::sync::Arc;
10
11#[derive(Debug, Clone)]
12pub struct MemoryBudget {
13 limit: usize,
14 used: Arc<AtomicUsize>,
15}
16
17impl PartialEq for MemoryBudget {
18 fn eq(&self, other: &Self) -> bool {
19 self.limit == other.limit && self.used() == other.used()
20 }
21}
22
23impl MemoryBudget {
24 pub fn new(limit: usize) -> Self {
39 Self {
40 limit,
41 used: Arc::new(AtomicUsize::new(0)),
42 }
43 }
44
45 pub fn allocate(&self, size: usize) -> Result<(), MemoryError> {
66 let current = self.used.fetch_add(size, Ordering::Relaxed);
67 let new_total = current + size;
68
69 if new_total > self.limit {
70 self.used.fetch_sub(size, Ordering::Relaxed);
72 Err(MemoryError::BudgetExceeded {
73 requested: size,
74 used: current,
75 limit: self.limit,
76 })
77 } else {
78 Ok(())
79 }
80 }
81
82 pub fn deallocate(&self, size: usize) {
99 self.used.fetch_sub(size, Ordering::Relaxed);
100 }
101
102 pub fn used(&self) -> usize {
108 self.used.load(Ordering::Relaxed)
109 }
110
111 pub fn limit(&self) -> usize {
117 self.limit
118 }
119
120 pub fn remaining(&self) -> usize {
126 let current = self.used();
127 self.limit.saturating_sub(current)
128 }
129
130 pub fn is_exceeded(&self) -> bool {
136 self.used() > self.limit
137 }
138
139 pub fn reset(&self) {
141 self.used.store(0, Ordering::Relaxed);
142 }
143}
144
145#[derive(Debug, Clone, thiserror::Error)]
147pub enum MemoryError {
148 #[error("Memory budget exceeded: requested {requested} bytes, used {used}/{limit} bytes")]
150 BudgetExceeded {
151 requested: usize,
153 used: usize,
155 limit: usize,
157 },
158}
159
160pub mod estimator {
162 use crate::ast::{Element, Node};
163
164 pub fn estimate_element(elem: &Element<'_>) -> usize {
168 let mut size = 200; for (key, value) in &elem.attributes {
172 size += 50 + key.len() + value.len();
173 }
174
175 for (key, value) in &elem.namespaces {
177 size += 50 + key.len() + value.len();
178 }
179
180 size += elem.name.len();
182
183 size
184 }
185
186 pub fn estimate_node(node: &Node<'_>) -> usize {
188 match node {
189 Node::Element(elem) => estimate_element(elem),
190 Node::Text(text) => 40 + text.len(),
191 Node::Comment(comment) => 40 + comment.len(),
192 Node::ProcessingInstruction { target, data } => 40 + target.len() + data.len(),
193 Node::CData(data) => 40 + data.len(),
194 Node::DocType(doctype) => 40 + doctype.len(),
195 }
196 }
197
198 pub fn estimate_child_addition(child: &Node<'_>) -> usize {
200 estimate_node(child) + 32
202 }
203
204 pub fn estimate_attribute_addition(key: &str, value: &str) -> usize {
206 50 + key.len() + value.len()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_budget_creation() {
216 let budget = MemoryBudget::new(1024);
217 assert_eq!(budget.limit(), 1024);
218 assert_eq!(budget.used(), 0);
219 assert_eq!(budget.remaining(), 1024);
220 assert!(!budget.is_exceeded());
221 }
222
223 #[test]
224 fn test_budget_allocation() {
225 let budget = MemoryBudget::new(1000);
226
227 assert!(budget.allocate(400).is_ok());
228 assert_eq!(budget.used(), 400);
229 assert_eq!(budget.remaining(), 600);
230
231 assert!(budget.allocate(400).is_ok());
232 assert_eq!(budget.used(), 800);
233 assert_eq!(budget.remaining(), 200);
234
235 assert!(budget.allocate(400).is_err());
237 assert_eq!(budget.used(), 800);
239 }
240
241 #[test]
242 fn test_budget_deallocation() {
243 let budget = MemoryBudget::new(1000);
244
245 budget.allocate(500).unwrap();
246 assert_eq!(budget.used(), 500);
247
248 budget.deallocate(200);
249 assert_eq!(budget.used(), 300);
250 assert_eq!(budget.remaining(), 700);
251 }
252
253 #[test]
254 fn test_budget_exceeded() {
255 let budget = MemoryBudget::new(100);
256
257 budget.allocate(50).unwrap();
258 assert!(!budget.is_exceeded());
259
260 budget.allocate(50).unwrap();
261 assert!(!budget.is_exceeded());
262
263 let result = budget.allocate(50);
265 assert!(result.is_err());
266
267 if let Err(MemoryError::BudgetExceeded {
268 requested,
269 used,
270 limit,
271 }) = result
272 {
273 assert_eq!(requested, 50);
274 assert_eq!(used, 100);
275 assert_eq!(limit, 100);
276 }
277 }
278
279 #[test]
280 fn test_budget_reset() {
281 let budget = MemoryBudget::new(1000);
282
283 budget.allocate(500).unwrap();
284 assert_eq!(budget.used(), 500);
285
286 budget.reset();
287 assert_eq!(budget.used(), 0);
288 assert_eq!(budget.remaining(), 1000);
289 }
290
291 #[test]
292 fn test_estimator_node() {
293 use crate::ast::Node;
294
295 let text = Node::Text("Hello, World!".to_string().into_boxed_str());
296 let size = estimator::estimate_node(&text);
297 assert_eq!(size, 40 + 13); let comment = Node::Comment("Test comment".to_string().into_boxed_str());
300 let size = estimator::estimate_node(&comment);
301 assert_eq!(size, 40 + 12);
302 }
303
304 #[test]
305 fn test_estimator_attribute() {
306 let size = estimator::estimate_attribute_addition("width", "100");
307 assert_eq!(size, 50 + 5 + 3); }
309}