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                            let old_index = index;
107                            // Process the insertions and moves.
108                            let mut context = ReplacementContext {
109                                index: &mut index,
110                                index_shift: &mut index_shift,
111                                to_delete: &mut to_delete,
112                                container_remap,
113                                deleted_indices: &mut deleted_indices,
114                                next_deleted: &mut next_deleted,
115                            };
116
117                            self.process_replacements(value, attr, &mut context)
118                                .unwrap();
119                            delta_change.push((index - old_index) as isize);
120                        }
121                    }
122                }
123
124                // Apply any remaining deletions.
125                self.apply_remaining_deletions(delta_change, &mut deleted_indices)?;
126                Ok(())
127            }
128        }
129    }
130
131    /// Preprocess deletions to build a map of containers to delete.
132    ///
133    /// # Arguments
134    ///
135    /// * `delta` - The delta containing the deletions.
136    ///
137    /// # Returns
138    ///
139    /// * `FxHashMap<ContainerID, usize>` - A map of containers to their indices that need to be deleted.
140    fn preprocess_deletions(
141        &self,
142        delta: &loro_delta::DeltaRope<
143            loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
144            crate::event::ListDeltaMeta,
145        >,
146    ) -> FxHashMap<ContainerID, usize> {
147        let mut index = 0;
148        let mut to_delete = FxHashMap::default();
149
150        for delta_item in delta.iter() {
151            match delta_item {
152                loro_delta::DeltaItem::Retain { len, .. } => {
153                    index += len;
154                }
155                loro_delta::DeltaItem::Replace { delete, .. } => {
156                    if *delete > 0 {
157                        for i in index..index + *delete {
158                            if let Some(LoroValue::Container(c)) = self.get(i) {
159                                to_delete.insert(c, i);
160                            }
161                        }
162                        index += *delete;
163                    }
164                }
165            }
166        }
167
168        to_delete
169    }
170
171    /// Handles deletions' effect on the index within a replace operation.
172    /// It will not perform the deletions.
173    ///
174    /// # Arguments
175    ///
176    /// * `delete_len` - The number of deletions.
177    /// * `index` - The current index in the list.
178    /// * `index_shift` - The current index shift due to previous operations.
179    /// * `next_deleted` - A heap of indices scheduled for deletion.
180    fn handle_deletions_in_replace(
181        &self,
182        delete_len: usize,
183        index: &mut usize,
184        index_shift: usize,
185        next_deleted: &mut BinaryHeap<Reverse<usize>>,
186    ) {
187        if delete_len > 0 {
188            let mut remaining_deletes = delete_len;
189            while let Some(Reverse(old_index)) = next_deleted.peek() {
190                if *old_index + index_shift < *index + remaining_deletes {
191                    assert!(*index <= *old_index + index_shift);
192                    assert!(remaining_deletes > 0);
193                    next_deleted.pop();
194                    remaining_deletes -= 1;
195                } else {
196                    break;
197                }
198            }
199
200            // Increase the index by the number of deletions handled.
201            *index += remaining_deletes;
202        }
203    }
204
205    /// Processes replacements, handling insertions and moves.
206    ///
207    /// # Arguments
208    ///
209    /// * `values` - The values to insert or move.
210    /// * `attr` - Additional attributes for the delta item.
211    /// * `context` - A context struct containing related parameters.
212    fn process_replacements(
213        &self,
214        values: &loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
215        attr: &crate::event::ListDeltaMeta,
216        context: &mut ReplacementContext,
217    ) -> LoroResult<()> {
218        for v in values.iter() {
219            match v {
220                ValueOrHandler::Value(LoroValue::Container(old_id)) => {
221                    self.apply_insertion(attr, context, old_id.clone())?;
222                }
223                ValueOrHandler::Handler(handler) => {
224                    let old_id = handler.id();
225                    self.apply_insertion(attr, context, old_id)?;
226                }
227                ValueOrHandler::Value(value) => {
228                    self.insert(*context.index, value.clone())?;
229                    Self::update_positions_on_insert(context.to_delete, *context.index, 1);
230                    *context.index += 1;
231                    *context.index_shift += 1;
232                }
233            }
234        }
235
236        Ok(())
237    }
238
239    fn apply_insertion(
240        &self,
241        attr: &crate::event::ListDeltaMeta,
242        context: &mut ReplacementContext<'_>,
243        mut old_id: ContainerID,
244    ) -> Result<(), LoroError> {
245        if !context.to_delete.contains_key(&old_id) {
246            while let Some(new_id) = context.container_remap.get(&old_id) {
247                old_id = new_id.clone();
248                if context.to_delete.contains_key(&old_id) {
249                    break;
250                }
251            }
252        }
253        if let Some(old_index) = context.to_delete.remove(&old_id) {
254            if old_index > *context.index {
255                ensure_cov::notify_cov(
256                    "loro_internal::handler::movable_list_apply_delta::process_replacements::mov_0",
257                );
258                self.mov(old_index, *context.index)?;
259                context.next_deleted.push(Reverse(old_index));
260                *context.index += 1;
261                *context.index_shift += 1;
262            } else {
263                ensure_cov::notify_cov(
264                    "loro_internal::handler::movable_list_apply_delta::process_replacements::mov_1",
265                );
266                self.mov(old_index, *context.index - 1)?;
267            }
268            context.deleted_indices.push(old_index);
269            Self::update_positions_on_delete(context.to_delete, old_index);
270            Self::update_positions_on_insert(context.to_delete, *context.index, 1);
271        } else if !attr.from_move {
272            // Insert a new container if not moved.
273            let new_handler = self.insert_container(
274                *context.index,
275                Handler::new_unattached(old_id.container_type()),
276            )?;
277            let new_id = new_handler.id();
278            context.container_remap.insert(old_id, new_id);
279            Self::update_positions_on_insert(context.to_delete, *context.index, 1);
280            *context.index += 1;
281            *context.index_shift += 1;
282        }
283        Ok(())
284    }
285
286    /// Applies any remaining deletions after processing insertions and moves.
287    ///
288    /// # Arguments
289    ///
290    /// * `delta` - The delta containing the deletions.
291    /// * `deleted_indices` - A list of indices that have been deleted.
292    fn apply_remaining_deletions(
293        &self,
294        delta: Vec<isize>,
295        deleted_indices: &mut Vec<usize>,
296    ) -> LoroResult<()> {
297        // Sort deleted indices from largest to smallest.
298        deleted_indices.sort_by_key(|&x| std::cmp::Reverse(x));
299
300        let mut index: usize = 0;
301        for delta_item in delta.iter() {
302            match *delta_item {
303                x if x > 0 => {
304                    index += x as usize;
305                }
306                neg_delete => {
307                    let delete = neg_delete.unsigned_abs();
308                    let mut remaining_deletes = delete;
309                    while let Some(&last) = deleted_indices.last() {
310                        if last < index {
311                            deleted_indices.pop();
312                            continue;
313                        }
314
315                        if last < index + remaining_deletes {
316                            deleted_indices.pop();
317                            remaining_deletes -= 1;
318                        } else {
319                            break;
320                        }
321                    }
322
323                    self.delete(index, remaining_deletes)?;
324                }
325            }
326        }
327
328        Ok(())
329    }
330
331    /// Updates positions in the map after an insertion.
332    ///
333    /// Increments positions that are greater than or equal to the insertion index.
334    ///
335    /// # Arguments
336    ///
337    /// * `positions` - The map of positions to update.
338    /// * `index` - The index where the insertion occurred.
339    /// * `len` - The length of the insertion.
340    fn update_positions_on_insert(
341        positions: &mut FxHashMap<ContainerID, usize>,
342        index: usize,
343        len: usize,
344    ) {
345        for pos in positions.values_mut() {
346            if *pos >= index {
347                *pos += len;
348            }
349        }
350    }
351
352    /// Updates positions in the map after a deletion.
353    ///
354    /// Decrements positions that are greater than or equal to the deletion index.
355    ///
356    /// # Arguments
357    ///
358    /// * `positions` - The map of positions to update.
359    /// * `index` - The index where the deletion occurred.
360    fn update_positions_on_delete(positions: &mut FxHashMap<ContainerID, usize>, index: usize) {
361        for pos in positions.values_mut() {
362            if *pos >= index {
363                *pos -= 1;
364            }
365        }
366    }
367}