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