Skip to content

Commit 2385a66

Browse files
committed
buffer refactor from im vector
Swap im::Vector for plain Vec with checkpoint deltas.
1 parent dfd1405 commit 2385a66

1 file changed

Lines changed: 159 additions & 104 deletions

File tree

asyncgit/src/graph/buffer.rs

Lines changed: 159 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
use super::chunk::{Chunk, Markers};
2-
use im::Vector;
32
use std::collections::BTreeMap;
43

4+
/// A single mutation of the lane state, recorded while processing one
5+
/// commit.
56
#[derive(Clone, Debug)]
67
pub enum DeltaOp {
78
Insert { index: usize, item: Option<Chunk> },
89
Remove { index: usize },
910
Replace { index: usize, new: Option<Chunk> },
1011
}
1112

13+
/// All lane-state mutations caused by processing a single commit.
14+
/// Applying a `Delta` to the lane state of row `n` yields the lane
15+
/// state of row `n + 1`.
1216
#[derive(Clone, Debug)]
1317
pub struct Delta(pub Vec<DeltaOp>);
1418

1519
const CHECKPOINT_INTERVAL: usize = 100;
1620

21+
/// Delta-compressed history of the graph's lane state.
22+
///
23+
/// While walking the log top-down, every commit mutates the set of
24+
/// active lanes (a `Vec<Option<Chunk>>`, one slot per lane). So, storign a
25+
/// full copy of that state for each commit is a waste. This
26+
/// buffer preserves ONLY the latest state PLUS the list of [`Delta`]s that
27+
/// produced it.
28+
/// Use [`Buffer::decompress`] to get the complete version.
1729
pub struct Buffer {
18-
pub current: Vector<Option<Chunk>>,
30+
/// Lane state after the most recently processed commit.
31+
pub current: Vec<Option<Chunk>>,
32+
33+
/// One [`Delta`] per processed commit, in the order of the walk.
1934
pub deltas: Vec<Delta>,
20-
pub checkpoints: BTreeMap<usize, Vector<Option<Chunk>>>,
21-
mergers: Vec<usize>,
35+
36+
/// Full lane-state snapshots taken every `CHECKPOINT_INTERVAL`
37+
/// commits, keyed by delta index, for reducing decompression cost.
38+
pub checkpoints: BTreeMap<usize, Vec<Option<Chunk>>>,
39+
40+
/// Aliases of merge commits whose second parent still needs a new
41+
/// lane.
42+
merge_commits: Vec<usize>,
43+
44+
/// Scratch list of the [`DeltaOp`]s recorded for processing commit
2245
pending_delta: Vec<DeltaOp>,
2346
}
2447

@@ -29,130 +52,162 @@ impl Default for Buffer {
2952
}
3053

3154
impl Buffer {
32-
pub fn new() -> Self {
55+
pub const fn new() -> Self {
3356
Self {
34-
current: Vector::new(),
57+
current: Vec::new(),
3558
deltas: Vec::new(),
3659
checkpoints: BTreeMap::new(),
37-
mergers: Vec::new(),
60+
merge_commits: Vec::new(),
3861
pending_delta: Vec::new(),
3962
}
4063
}
4164

42-
pub fn merger(&mut self, alias: usize) {
43-
self.mergers.push(alias);
65+
/// Remember `alias` as a merge commit whose second parent must be
66+
/// given its own lane.
67+
pub fn track_merge_commit(&mut self, alias: usize) {
68+
self.merge_commits.push(alias);
4469
}
4570

4671
pub fn update(&mut self, new_chunk: &Chunk) {
47-
self.pending_delta.clear();
72+
// Phase 1: place the new chunk into the lane array.
73+
let placement_index = self.place_chunk(new_chunk);
74+
75+
// Phase 2: consume the alias in all other live chunks.
76+
if let Some(alias) = new_chunk.alias {
77+
self.consume_alias_in_other_chunks(
78+
alias,
79+
placement_index,
80+
);
81+
}
4882

49-
let mut empty_lanes: Vec<usize> = self
50-
.current
51-
.iter()
52-
.enumerate()
53-
.filter_map(|(i, c)| c.is_none().then_some(i))
54-
.collect();
83+
// Phase 3: flush any pending merge commits into new lanes.
84+
self.flush_merge_commits();
5585

56-
// sort descending so we can pop the lowest index first
57-
empty_lanes.sort_unstable_by(|a, b| b.cmp(a));
86+
// Phase 4: commit the delta and maybe checkpoint.
87+
self.commit_delta();
88+
}
5889

59-
let found_idx = if new_chunk.alias.is_some() {
60-
self.current.iter().enumerate().find_map(|(i, c)| {
61-
c.as_ref().and_then(|c| {
62-
(c.parent_a == new_chunk.alias).then_some(i)
63-
})
64-
})
65-
} else {
66-
None
67-
};
90+
fn place_chunk(&mut self, new_chunk: &Chunk) -> usize {
91+
// Prefer a lane whose current occupant is waiting for this chunk as parent_a.
92+
let target = self
93+
.find_lane_awaiting_parent(new_chunk.alias)
94+
.or_else(|| self.first_empty_lane())
95+
.unwrap_or(self.current.len());
6896

69-
if let Some(idx) = found_idx {
70-
self.record_replace(idx, Some(new_chunk.clone()));
71-
} else if let Some(empty_idx) = empty_lanes.pop() {
72-
self.record_replace(empty_idx, Some(new_chunk.clone()));
97+
if target < self.current.len() {
98+
self.record_replace(target, Some(new_chunk.clone()));
7399
} else {
74-
self.record_insert(
75-
self.current.len(),
76-
Some(new_chunk.clone()),
77-
);
100+
self.record_insert(target, Some(new_chunk.clone()));
78101
}
102+
target
103+
}
79104

80-
let current_length = self.current.len();
81-
for index in 0..current_length {
82-
if Some(index) == found_idx {
83-
continue;
84-
}
85-
if found_idx.is_none() && index == current_length - 1 {
86-
continue;
87-
}
105+
fn find_lane_awaiting_parent(
106+
&self,
107+
alias: Option<usize>,
108+
) -> Option<usize> {
109+
let alias = alias?;
110+
self.current.iter().position(|slot| {
111+
slot.as_ref()
112+
.is_some_and(|chunk| chunk.parent_a == Some(alias))
113+
})
114+
}
115+
116+
fn first_empty_lane(&self) -> Option<usize> {
117+
self.current.iter().position(Option::is_none)
118+
}
88119

89-
if let Some(mut c) = self.current[index].clone() {
90-
let changed = new_chunk.alias.is_some_and(|alias| {
91-
let a = c.parent_a == Some(alias);
92-
let b = c.parent_b == Some(alias);
93-
if a {
94-
c.parent_a = None;
95-
}
96-
if b {
97-
c.parent_b = None;
98-
}
99-
a || b
100-
});
101-
102-
if changed {
103-
if c.parent_a.is_none() && c.parent_b.is_none() {
104-
self.record_replace(index, None);
105-
} else {
106-
self.record_replace(index, Some(c));
107-
}
120+
fn consume_alias_in_other_chunks(
121+
&mut self,
122+
alias: usize,
123+
skip_index: usize,
124+
) {
125+
for index in 0..self.current.len() {
126+
let mut chunk = match self.current[index].clone() {
127+
Some(chunk) if index != skip_index => chunk,
128+
_ => continue,
129+
};
130+
131+
let changed_a = chunk.parent_a == Some(alias);
132+
let changed_b = chunk.parent_b == Some(alias);
133+
134+
if changed_a || changed_b {
135+
if changed_a {
136+
chunk.parent_a = None;
137+
}
138+
if changed_b {
139+
chunk.parent_b = None;
108140
}
141+
142+
self.record_replace(
143+
index,
144+
chunk.parent_a.is_some().then_some(chunk),
145+
);
109146
}
110147
}
148+
}
111149

112-
while let Some(alias) = self.mergers.pop() {
113-
if let Some(index) = self.current.iter().position(|c| {
114-
c.as_ref()
115-
.is_some_and(|chunk| chunk.alias == Some(alias))
116-
}) {
117-
if let Some(mut c) = self.current[index].clone() {
118-
let parent_b = c.parent_b;
119-
c.parent_b = None;
120-
self.record_replace(index, Some(c));
121-
122-
let new_lane = Chunk {
123-
alias: None,
124-
parent_a: parent_b,
125-
parent_b: None,
126-
marker: Markers::Commit,
127-
};
128-
129-
if let Some(empty_idx) = empty_lanes.pop() {
130-
self.record_replace(
131-
empty_idx,
132-
Some(new_lane),
133-
);
134-
} else {
135-
self.record_insert(
136-
self.current.len(),
137-
Some(new_lane),
138-
);
139-
}
150+
fn flush_merge_commits(&mut self) {
151+
// Collect available empty lanes once, before we start maybe filling them.
152+
let mut empty_lanes: Vec<usize> = self
153+
.current
154+
.iter()
155+
.enumerate()
156+
.filter_map(|(index, slot)| {
157+
slot.is_none().then_some(index)
158+
})
159+
.collect();
160+
161+
while let Some(alias) = self.merge_commits.pop() {
162+
// Search for an occupied slot that matches the target alias.
163+
// If found, extract its index and a mutable clone of the chunk.
164+
let Some((index, mut chunk)) =
165+
self.current.iter().enumerate().find_map(
166+
|(index, slot)| {
167+
let chunk = slot.as_ref()?;
168+
(chunk.alias == Some(alias))
169+
.then(|| (index, chunk.clone()))
170+
},
171+
)
172+
else {
173+
continue;
174+
};
175+
176+
let detached_parent = chunk.parent_b.take();
177+
self.record_replace(index, Some(chunk));
178+
179+
if let Some(parent) = detached_parent {
180+
let new_lane = Chunk {
181+
alias: None,
182+
parent_a: Some(parent),
183+
parent_b: None,
184+
marker: Markers::Commit,
185+
};
186+
187+
if let Some(empty_index) = empty_lanes.pop() {
188+
self.record_replace(empty_index, Some(new_lane));
189+
} else {
190+
self.record_insert(
191+
self.current.len(),
192+
Some(new_lane),
193+
);
140194
}
141195
}
142196
}
197+
}
143198

144-
while self.current.last().is_some_and(Option::is_none) {
145-
self.record_remove(self.current.len() - 1);
199+
fn commit_delta(&mut self) {
200+
while matches!(self.current.last(), Some(None)) {
201+
let last = self.current.len() - 1;
202+
self.record_remove(last);
146203
}
147204

148-
let delta = Delta(self.pending_delta.clone());
149-
self.deltas.push(delta);
205+
self.deltas
206+
.push(Delta(std::mem::take(&mut self.pending_delta)));
150207

151-
let current_step = self.deltas.len();
152-
if current_step > 0 && current_step % CHECKPOINT_INTERVAL == 0
153-
{
154-
self.checkpoints
155-
.insert(current_step - 1, self.current.clone());
208+
let step = self.deltas.len();
209+
if step % CHECKPOINT_INTERVAL == 0 {
210+
self.checkpoints.insert(step - 1, self.current.clone());
156211
}
157212
}
158213

@@ -161,7 +216,7 @@ impl Buffer {
161216
index,
162217
new: new.clone(),
163218
});
164-
self.current.set(index, new);
219+
self.current[index] = new;
165220
}
166221

167222
fn record_insert(&mut self, index: usize, item: Option<Chunk>) {
@@ -181,10 +236,10 @@ impl Buffer {
181236
&self,
182237
start: usize,
183238
end: usize,
184-
) -> Vec<Vector<Option<Chunk>>> {
239+
) -> Vec<Vec<Option<Chunk>>> {
185240
let (current_index, mut state) =
186241
self.checkpoints.range(..=start).next_back().map_or_else(
187-
|| (None, Vector::new()),
242+
|| (None, Vec::new()),
188243
|(&i, s)| (Some(i), s.clone()),
189244
);
190245

@@ -215,7 +270,7 @@ impl Buffer {
215270
}
216271

217272
fn apply_delta_to_state(
218-
state: &mut Vector<Option<Chunk>>,
273+
state: &mut Vec<Option<Chunk>>,
219274
delta: &Delta,
220275
) {
221276
for op in &delta.0 {
@@ -227,7 +282,7 @@ impl Buffer {
227282
state.remove(*index);
228283
}
229284
DeltaOp::Replace { index, new } => {
230-
state.set(*index, new.clone());
285+
state[*index].clone_from(new);
231286
}
232287
}
233288
}

0 commit comments

Comments
 (0)