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}