11use super :: chunk:: { Chunk , Markers } ;
2- use im:: Vector ;
32use std:: collections:: BTreeMap ;
43
4+ /// A single mutation of the lane state, recorded while processing one
5+ /// commit.
56#[ derive( Clone , Debug ) ]
67pub 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 ) ]
1317pub struct Delta ( pub Vec < DeltaOp > ) ;
1418
1519const 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.
1729pub 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
3154impl 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