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}