formualizer_eval/engine/
spill.rs1use crate::engine::graph::DependencyGraph;
5use crate::engine::interval_tree::IntervalTree;
6use crate::engine::vertex::VertexId;
7use crate::engine::{SpillBufferMode, SpillCancellationPolicy, SpillConfig, SpillVisibility};
8use crate::reference::CellRef;
9use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
10use rustc_hash::{FxHashMap, FxHashSet};
11
12#[derive(Debug, Clone, Copy)]
13pub struct SpillShape {
14 pub rows: u32,
15 pub cols: u32,
16}
17
18#[derive(Debug)]
19pub struct SpillMeta {
20 pub epoch: u64,
21 pub config: SpillConfig,
22}
23
24#[derive(Debug)]
25pub struct SpillReservation {
26 pub anchor: CellRef,
27 pub shape: SpillShape,
28 pub writer_mode: SpillBufferMode,
29 pub visibility: SpillVisibility,
30 pub cancellation: SpillCancellationPolicy,
31}
32
33pub trait SpillWriter {
34 fn write_row(&mut self, r: u32, values: &[LiteralValue]) -> Result<(), ExcelError>;
35}
36
37pub trait SpillManager {
38 fn reserve(
39 &mut self,
40 graph: &DependencyGraph,
41 anchor: CellRef,
42 shape: SpillShape,
43 meta: SpillMeta,
44 ) -> Result<(SpillReservation, Box<dyn SpillWriter>), ExcelError>;
45
46 fn commit(&mut self, reservation: SpillReservation) -> Result<(), ExcelError>;
47
48 fn rollback(&mut self, reservation: SpillReservation);
49}
50
51#[derive(Default, Debug)]
53pub struct RegionLockManager {
54 next_id: u64,
55 row_trees: FxHashMap<u32, IntervalTree<u64>>, col_trees: FxHashMap<u32, IntervalTree<u64>>, locks: FxHashMap<u64, RegionLock>, }
60
61#[derive(Debug, Clone, Copy)]
62pub struct Region {
63 pub sheet_id: u32,
64 pub row_start: u32,
65 pub row_end: u32,
66 pub col_start: u32,
67 pub col_end: u32,
68}
69
70#[derive(Debug)]
71struct RegionLock {
72 id: u64,
73 owner: VertexId,
74 region: Region,
75}
76
77impl RegionLockManager {
78 pub fn reserve(&mut self, region: Region, owner: VertexId) -> Result<u64, ExcelError> {
79 if region.row_end < region.row_start || region.col_end < region.col_start {
81 return Ok(0);
82 }
83 let row_tree = self.row_trees.entry(region.sheet_id).or_default();
84 let col_tree = self.col_trees.entry(region.sheet_id).or_default();
85
86 let mut row_ids: FxHashSet<u64> = FxHashSet::default();
88 for (_low, _high, set) in row_tree.query(region.row_start, region.row_end) {
89 for id in set {
90 row_ids.insert(id);
91 }
92 }
93 if !row_ids.is_empty() {
94 let mut col_ids: FxHashSet<u64> = FxHashSet::default();
95 for (_low, _high, set) in col_tree.query(region.col_start, region.col_end) {
96 for id in set {
97 col_ids.insert(id);
98 }
99 }
100 let conflict = row_ids.iter().any(|id| col_ids.contains(id));
102 if conflict {
103 return Err(ExcelError::new(ExcelErrorKind::Spill)
104 .with_message("Region reserved by another spill"));
105 }
106 }
107
108 self.next_id = self.next_id.wrapping_add(1).max(1);
110 let id = self.next_id;
111 row_tree.insert(region.row_start, region.row_end, id);
112 col_tree.insert(region.col_start, region.col_end, id);
113 self.locks.insert(id, RegionLock { id, owner, region });
114 Ok(id)
115 }
116
117 pub fn release(&mut self, id: u64) {
118 if id == 0 {
119 return;
120 }
121 if let Some(lock) = self.locks.remove(&id) {
122 if let Some(tree) = self.row_trees.get_mut(&lock.region.sheet_id) {
123 tree.remove(lock.region.row_start, lock.region.row_end, &lock.id);
124 }
125 if let Some(tree) = self.col_trees.get_mut(&lock.region.sheet_id) {
126 tree.remove(lock.region.col_start, lock.region.col_end, &lock.id);
127 }
128 }
129 }
130
131 #[cfg(test)]
132 pub fn active_count(&self, sheet_id: u32) -> usize {
133 let r = self.row_trees.get(&sheet_id).map(|t| t.len()).unwrap_or(0);
134 let c = self.col_trees.get(&sheet_id).map(|t| t.len()).unwrap_or(0);
135 r.max(c)
136 }
137}