Skip to main content

loro_internal/handler/
movable_list_apply_delta.rs

1use super::*;
2
3#[derive(Debug)]
4struct ReplacementContext<'a> {
5    index: &'a mut usize,
6    index_shift: &'a mut usize,
7    to_delete: &'a mut FxHashMap<ContainerID, usize>,
8    container_remap: &'a mut FxHashMap<ContainerID, ContainerID>,
9    deleted_indices: &'a mut Vec<usize>,
10    next_deleted: &'a mut BinaryHeap<Reverse<usize>>,
11}
12
13impl MovableListHandler {
14    /// Applies a delta to the movable list handler.
15    ///
16    /// This function processes the given delta, performing the necessary insertions,
17    /// deletions, and moves to update the list accordingly. It handles container elements,
18    /// maintains a map for remapping container IDs, and ensures proper indexing throughout
19    /// the operation.
20    ///
21    /// # Arguments
22    ///
23    /// * `delta` - A delta representing the changes to apply.
24    /// * `container_remap` - A map used to remap container IDs during the operation.
25    ///
26    /// # Returns
27    ///
28    /// * `LoroResult<()>` - Returns `Ok(())` if successful, or an error if something goes wrong.
29    #[tracing::instrument(level = "debug", skip_all)]
30    pub fn apply_delta(
31        &self,
32        delta: loro_delta::DeltaRope<
33            loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
34            crate::event::ListDeltaMeta,
35        >,
36        container_remap: &mut FxHashMap<ContainerID, ContainerID>,
37    ) -> LoroResult<()> {
38        {
39            // Test whether the delta is valid
40            let len = self.len();
41            let mut index = 0;
42            for delta_item in delta.iter() {
43                match delta_item {
44                    loro_delta::DeltaItem::Retain { len, .. } => {
45                        index += *len;
46                    }
47                    loro_delta::DeltaItem::Replace { delete, .. } => {
48                        index += *delete;
49                    }
50                }
51
52                if index > len {
53                    return Err(LoroError::OutOfBound {
54                        pos: index,
55                        len,
56                        info: "apply_delta".into(),
57                    });
58                }
59            }
60        }
61
62        match &self.inner {
63            MaybeDetached::Detached(_) => {
64                unimplemented!();
65            }
66            MaybeDetached::Attached(_) => {
67                // use tracing::debug;
68                // debug!(
69                //     "Movable list value before apply_delta: {:#?}",
70                //     self.get_deep_value_with_id()
71                // );
72                // debug!("Applying delta: {:#?}", &delta);
73
74                // Preprocess deletions to build a map of containers to delete.
75                let mut to_delete = self.preprocess_deletions(&delta);
76                // Process insertions and moves.
77                let mut index = 0;
78                let mut index_shift = 0;
79                let mut deleted_indices = Vec::new();
80                let mut next_deleted = BinaryHeap::new();
81                // - positive values are retain
82                // - negative values are deletions
83                let mut delta_change: Vec<isize> = Vec::new();
84
85                for delta_item in delta.iter() {
86                    match delta_item {
87                        loro_delta::DeltaItem::Retain { len, .. } => {
88                            index += len;
89                            delta_change.push(*len as isize);
90                        }
91                        loro_delta::DeltaItem::Replace {
92                            value,
93                            delete,
94                            attr,
95                        } => {
96                            // Handle deletions in the current replace operation.
97                            let old_index = index;
98                            self.handle_deletions_in_replace(
99                                *delete,
100                                &mut index,
101                                index_shift,
102                                &mut next_deleted,
103                            );
104                            delta_change.push(-((index - old_index) as isize));
105
106                            // Process the insertions and moves.
107                            let mut context = ReplacementContext {
108                                index: &mut index,
109                                index_shift: &mut index_shift,
110                                to_delete: &mut to_delete,
111                                container_remap,
112                                deleted_indices: &mut deleted_indices,
113                                next_deleted: &mut next_deleted,
114                            };
115
116                            self.process_replacements(value, attr, &mut context)
117                                .unwrap();
118                            delta_change.push(value.len() as isize);
119                        }
120                    }
121                }
122
123                // Apply any remaining deletions.
124                self.apply_remaining_deletions(delta_change, &mut deleted_indices)?;
125                Ok(())
126            }
127        }
128    }
129
130    /// Preprocess deletions to build a map of containers to delete.
131    ///
132    /// # Arguments
133    ///
134    /// * `delta` - The delta containing the deletions.
135    ///
136    /// # Returns
137    ///
138    /// * `FxHashMap<ContainerID, usize>` - A map of containers to their indices that need to be deleted.
139    fn preprocess_deletions(
140        &self,
141        delta: &loro_delta::DeltaRope<
142            loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
143            crate::event::ListDeltaMeta,
144        >,
145    ) -> FxHashMap<ContainerID, usize> {
146        let mut index = 0;
147        let mut to_delete = FxHashMap::default();
148
149        for delta_item in delta.iter() {
150            match delta_item {
151                loro_delta::DeltaItem::Retain { len, .. } => {
152                    index += len;
153                }
154                loro_delta::DeltaItem::Replace { delete, .. } => {
155                    if *delete > 0 {
156                        for i in index..index + *delete {
157                            if let Some(LoroValue::Container(c)) = self.get(i) {
158                                to_delete.insert(c, i);
159                            }
160                        }
161                        index += *delete;
162                    }
163                }
164            }
165        }
166
167        to_delete
168    }
169
170    /// Handles deletions' effect on the index within a replace operation.
171    /// It will not perform the deletions.
172    ///
173    /// # Arguments
174    ///
175    /// * `delete_len` - The number of deletions.
176    /// * `index` - The current index in the list.
177    /// * `index_shift` - The current index shift due to previous operations.
178    /// * `next_deleted` - A heap of indices scheduled for deletion.
179    fn handle_deletions_in_replace(
180        &self,
181        delete_len: usize,
182        index: &mut usize,
183        index_shift: usize,
184        next_deleted: &mut BinaryHeap<Reverse<usize>>,
185    ) {
186        if delete_len > 0 {
187            let mut remaining_deletes = delete_len;
188            while let Some(Reverse(old_index)) = next_deleted.peek() {
189                if *old_index + index_shift < *index + remaining_deletes {
190                    assert!(*index <= *old_index + index_shift);
191                    assert!(remaining_deletes > 0);
192                    next_deleted.pop();
193                    remaining_deletes -= 1;
194                } else {
195                    break;
196                }
197            }
198
199            // Increase the index by the number of deletions handled.
200            *index += remaining_deletes;
201        }
202    }
203
204    /// Processes replacements, handling insertions and moves.
205    ///
206    /// # Arguments
207    ///
208    /// * `values` - The values to insert or move.
209    /// * `attr` - Additional attributes for the delta item.
210    /// * `context` - A context struct containing related parameters.
211    fn process_replacements(
212        &self,
213        values: &loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
214        attr: &crate::event::ListDeltaMeta,
215        context: &mut ReplacementContext,
216    ) -> LoroResult<()> {
217        for v in values.iter() {
218            match v {
219                ValueOrHandler::Value(LoroValue::Container(old_id)) => {
220                    self.apply_insertion(attr, context, old_id.clone())?;
221                }
222                ValueOrHandler::Handler(handler) => {
223                    let old_id = handler.id();
224                    self.apply_insertion(attr, context, old_id)?;
225                }
226                ValueOrHandler::Value(value) => {
227                    self.insert(*context.index, value.clone())?;
228                    Self::update_positions_on_insert(context.to_delete, *context.index, 1);
229                    *context.index += 1;
230                    *context.index_shift += 1;
231                }
232            }
233        }
234
235        Ok(())
236    }
237
238    fn apply_insertion(
239        &self,
240        attr: &crate::event::ListDeltaMeta,
241        context: &mut ReplacementContext<'_>,
242        mut old_id: ContainerID,
243    ) -> Result<(), LoroError> {
244        if !context.to_delete.contains_key(&old_id) {
245            while let Some(new_id) = context.container_remap.get(&old_id) {
246                old_id = new_id.clone();
247                if context.to_delete.contains_key(&old_id) {
248                    break;
249                }
250            }
251        }
252        if let Some(old_index) = context.to_delete.remove(&old_id) {
253            if old_index > *context.index {
254                ensure_cov::notify_cov(
255                    "loro_internal::handler::movable_list_apply_delta::process_replacements::mov_0",
256                );
257                self.mov(old_index, *context.index)?;
258                context.next_deleted.push(Reverse(old_index));
259                *context.index += 1;
260                *context.index_shift += 1;
261            } else {
262                ensure_cov::notify_cov(
263                    "loro_internal::handler::movable_list_apply_delta::process_replacements::mov_1",
264                );
265                self.mov(old_index, *context.index - 1)?;
266            }
267            context.deleted_indices.push(old_index);
268            Self::update_positions_on_delete(context.to_delete, old_index);
269            Self::update_positions_on_insert(context.to_delete, *context.index, 1);
270        } else if !attr.from_move || !self.contains_container(&old_id) {
271            // Insert a new container if not moved.
272            let new_handler = self.insert_container(
273                *context.index,
274                Handler::new_unattached(old_id.container_type()),
275            )?;
276            let new_id = new_handler.id();
277            context.container_remap.insert(old_id, new_id);
278            Self::update_positions_on_insert(context.to_delete, *context.index, 1);
279            *context.index += 1;
280            *context.index_shift += 1;
281        }
282        Ok(())
283    }
284
285    fn contains_container(&self, id: &ContainerID) -> bool {
286        (0..self.len()).any(|index| {
287            matches!(
288                self.get(index),
289                Some(LoroValue::Container(container_id)) if container_id == *id
290            )
291        })
292    }
293
294    /// Applies any remaining deletions after processing insertions and moves.
295    ///
296    /// # Arguments
297    ///
298    /// * `delta` - The delta containing the deletions.
299    /// * `deleted_indices` - A list of indices that have been deleted.
300    fn apply_remaining_deletions(
301        &self,
302        delta: Vec<isize>,
303        deleted_indices: &mut Vec<usize>,
304    ) -> LoroResult<()> {
305        // Sort deleted indices from largest to smallest.
306        deleted_indices.sort_by_key(|&x| std::cmp::Reverse(x));
307
308        let mut index: usize = 0;
309        for delta_item in delta.iter() {
310            match *delta_item {
311                x if x > 0 => {
312                    index += x as usize;
313                }
314                neg_delete => {
315                    let delete = neg_delete.unsigned_abs();
316                    let mut remaining_deletes = delete;
317                    while let Some(&last) = deleted_indices.last() {
318                        if last < index {
319                            deleted_indices.pop();
320                            continue;
321                        }
322
323                        if last < index + remaining_deletes {
324                            deleted_indices.pop();
325                            remaining_deletes -= 1;
326                        } else {
327                            break;
328                        }
329                    }
330
331                    self.delete(index, remaining_deletes)?;
332                }
333            }
334        }
335
336        Ok(())
337    }
338
339    /// Updates positions in the map after an insertion.
340    ///
341    /// Increments positions that are greater than or equal to the insertion index.
342    ///
343    /// # Arguments
344    ///
345    /// * `positions` - The map of positions to update.
346    /// * `index` - The index where the insertion occurred.
347    /// * `len` - The length of the insertion.
348    fn update_positions_on_insert(
349        positions: &mut FxHashMap<ContainerID, usize>,
350        index: usize,
351        len: usize,
352    ) {
353        for pos in positions.values_mut() {
354            if *pos >= index {
355                *pos += len;
356            }
357        }
358    }
359
360    /// Updates positions in the map after a deletion.
361    ///
362    /// Decrements positions that are greater than or equal to the deletion index.
363    ///
364    /// # Arguments
365    ///
366    /// * `positions` - The map of positions to update.
367    /// * `index` - The index where the deletion occurred.
368    fn update_positions_on_delete(positions: &mut FxHashMap<ContainerID, usize>, index: usize) {
369        for pos in positions.values_mut() {
370            if *pos >= index {
371                *pos -= 1;
372            }
373        }
374    }
375}