oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use crate::block::{BlockCell, BlockRange, ClientID, Item, ItemPtr, GC, ID};
2use crate::slice::ItemSlice;
3use crate::types::TypePtr;
4use crate::utils::client_hasher::ClientHasher;
5use crate::*;
6use std::collections::hash_map::Entry;
7use std::collections::HashMap;
8use std::hash::BuildHasherDefault;
9use std::ops::{Index, IndexMut, Range, RangeInclusive};
10use std::vec::Vec;
11
12/// A resizable list of blocks inserted by a single client.
13#[derive(PartialEq, Default)]
14pub(crate) struct ClientBlockList {
15 list: Vec<BlockCell>,
16}
17
18struct SquashBlockRange {
19 range: Range<usize>,
20 gc_block: bool,
21}
22
23impl ClientBlockList {
24 pub fn clock(&self) -> u32 {
25 let len = self.list.len();
26 if len == 0 {
27 0
28 } else {
29 match &self.list[len - 1] {
30 BlockCell::GC(gc) => gc.end + 1,
31 BlockCell::Block(block) => block.id.clock + block.len,
32 }
33 }
34 }
35
36 pub(crate) fn get(&self, index: usize) -> Option<&BlockCell> {
37 self.list.get(index)
38 }
39
40 /// Given a block's identifier clock value, return an offset under which this block could be
41 /// found using binary search algorithm, or a index under which this block should be inserted.
42 pub(crate) fn find_pivot(&self, clock: u32) -> Option<usize> {
43 let mut left = 0;
44 let mut right = self.list.len() - 1;
45 let mut block = &self[right];
46 let (mut start, mut end) = block.clock_range();
47 if start == clock {
48 // a common case is to just append a block at the end, so check first if we can do that
49 Some(right)
50 } else {
51 let mut mid = ((clock / end) * right as u32) as usize;
52 while left <= right {
53 block = &self[mid];
54 (start, end) = block.clock_range();
55 if start <= clock {
56 if clock <= end {
57 return Some(mid);
58 }
59 left = mid + 1;
60 } else {
61 right = mid - 1;
62 }
63 mid = (left + right) / 2;
64 }
65
66 None
67 }
68 }
69
70 /// Attempts to find a Block which contains given clock sequence number within current block
71 /// list. Clocks are considered to work in left-side inclusive way, meaning that block with
72 /// an ID (<client-id>, 0) and length 2, with contain all elements with clock values
73 /// corresponding to {0,1} but not 2.
74 fn get_block(&self, clock: u32) -> Option<&BlockCell> {
75 let idx = self.find_pivot(clock)?;
76 Some(&self[idx])
77 }
78
79 fn get_block_mut(&mut self, clock: u32) -> Option<&mut BlockCell> {
80 let idx = self.find_pivot(clock)?;
81 Some(&mut self[idx])
82 }
83
84 /// Pushes a new block at the end of this block list.
85 fn push(&mut self, cell: BlockCell) {
86 self.list.push(cell);
87 }
88
89 /// Inserts a new block at a given `index` position within this block list. This method may
90 /// panic if `index` is greater than a length of the list.
91 pub(crate) fn insert(&mut self, index: usize, cell: BlockCell) {
92 self.list.insert(index, cell);
93 }
94
95 /// Returns a number of blocks stored within this list.
96 pub fn len(&self) -> usize {
97 self.list.len()
98 }
99
100 pub fn iter(&self) -> ClientBlockListIter<'_> {
101 ClientBlockListIter(self.list.iter())
102 }
103
104 pub fn iter_mut(&mut self) -> ClientBlockListIterMut<'_> {
105 ClientBlockListIterMut(self.list.iter_mut())
106 }
107
108 /// Attempts to squash multiple blocks within the given range of indices.
109 /// For each block in the `indices_range`, it will check if the block can be squashed with its left neighbor.
110 /// If consecutive blocks are squashable, they are tracked in a range and processed in bulk to compact
111 /// the list efficiently. The function supports both GC and Block cells.
112 ///
113 /// - For GC blocks: If blocks are consecutive, the range is extended and squashing is deferred until
114 /// all squashable blocks are identified.
115 ///
116 /// - For Block cells: The function attempts to squash the contents of the right block into the left block.
117 /// If successful, it tracks the blocks to be removed and rewires references in the parent node if necessary.
118 /// Block cells currently don't support range compaction due to the complexity of squashing Blocks.
119 ///
120 /// The function processes all blocks in reverse order (from the end of the range to the start),
121 /// compacts the list by removing squashed blocks, and updates references for any parent-child relationships
122 /// affected by the squashing.
123 ///
124 /// # Arguments
125 /// * `indices_range` - A range of indices, where each index represents a block in the list to be examined
126 /// for squashing. The range must be non-empty (`start` must be <= `end`).
127 ///
128 /// # Panics
129 /// * Panics if `indices_range.start()` is greater than `indices_range.end()`.
130 ///
131 pub(crate) fn squash_left_range_compaction(&mut self, indices_range: RangeInclusive<usize>) {
132 assert!(indices_range.start() <= indices_range.end());
133 let mut squash_intervals: Vec<SquashBlockRange> = Vec::new();
134
135 for right_index in indices_range.rev() {
136 let (l, r) = self.list.split_at_mut(right_index);
137 let left = &mut l.last_mut().unwrap();
138 let right = &mut r[0];
139
140 match (left, right) {
141 (BlockCell::GC(_), BlockCell::GC(_)) => {
142 let mut extended = false;
143 match squash_intervals.last_mut() {
144 Some(last_range) if last_range.gc_block => {
145 // Extend if consecutive
146 if last_range.range.start - 1 == right_index {
147 last_range.range.start = right_index;
148 extended = true;
149 }
150 }
151 _ => {}
152 }
153
154 if !extended {
155 // Add new range if no consecutive block found
156 squash_intervals.push(SquashBlockRange {
157 range: Range {
158 start: right_index,
159 end: right_index,
160 },
161 gc_block: true,
162 });
163 }
164 }
165 (BlockCell::Block(left), BlockCell::Block(right)) => {
166 let mut left = ItemPtr::from(left);
167 let right = ItemPtr::from(right);
168 if left.try_squash(right) {
169 // Merge right into left Blocks one by one.
170 squash_intervals.push(SquashBlockRange {
171 range: Range {
172 start: right_index,
173 end: right_index,
174 },
175 gc_block: false,
176 });
177 }
178 }
179 _ => { /* cannot squash incompatible types */ }
180 }
181 }
182
183 for squash_range in &squash_intervals {
184 let start_idx = squash_range.range.start;
185 let end_idx = squash_range.range.end;
186 assert!(start_idx <= end_idx);
187
188 let (left_slice, right_slice) = self.list.split_at_mut(end_idx);
189
190 // The start_idx - 1 element is the one want to squash into.
191 let left = &mut left_slice[start_idx - 1];
192 let right = &right_slice[0];
193
194 match (left, right) {
195 (BlockCell::GC(left), BlockCell::GC(right)) => {
196 left.end = right.end;
197 }
198 (BlockCell::Block(left), BlockCell::Block(right)) => {
199 let left = ItemPtr::from(left);
200 let right = ItemPtr::from(right);
201 if let Some(key) = right.parent_sub.as_deref() {
202 if let TypePtr::Branch(mut parent) = right.parent {
203 if let Some(e) = parent.map.get_mut(key) {
204 if right == *e {
205 *e = ItemPtr::from(left);
206 }
207 }
208 }
209 }
210 }
211 _ => { /* cannot squash incompatible types */ }
212 }
213
214 // Finally, remove the BlockCells in bulk.
215 self.list.drain(start_idx..=end_idx);
216 }
217 }
218
219 /// Attempts to squash block at a given `index` with a corresponding block on its left side.
220 /// If this succeeds, block under a given `index` will be removed, and its contents will be
221 /// squashed into its left neighbor. In such case a squash result will be returned in order to
222 /// later on rewire left/right neighbor changes that may have occurred as a result of squashing
223 /// and block removal.
224 pub(crate) fn squash_left(&mut self, index: usize) {
225 let (l, r) = self.list.split_at_mut(index);
226 let left = &mut l[index - 1];
227 let right = &mut r[0];
228 match (left, right) {
229 (BlockCell::GC(left), BlockCell::GC(right)) => {
230 left.end = right.end;
231 self.list.remove(index);
232 }
233 (BlockCell::Block(left), BlockCell::Block(right)) => {
234 let mut left = ItemPtr::from(left);
235 let right = ItemPtr::from(right);
236 if left.try_squash(right) {
237 if let Some(key) = right.parent_sub.as_deref() {
238 if let TypePtr::Branch(mut parent) = right.parent {
239 if let Some(e) = parent.map.get_mut(key) {
240 if right == *e {
241 *e = ItemPtr::from(left);
242 }
243 }
244 }
245 }
246 self.list.remove(index);
247 }
248 }
249 _ => { /* cannot squash incompatible types */ }
250 }
251 }
252}
253
254impl Index<usize> for ClientBlockList {
255 type Output = BlockCell;
256
257 fn index(&self, index: usize) -> &Self::Output {
258 &self.list[index]
259 }
260}
261
262impl IndexMut<usize> for ClientBlockList {
263 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
264 &mut self.list[index]
265 }
266}
267
268pub(crate) struct ClientBlockListIter<'a>(std::slice::Iter<'a, BlockCell>);
269
270impl<'a> Iterator for ClientBlockListIter<'a> {
271 type Item = &'a BlockCell;
272
273 fn next(&mut self) -> Option<Self::Item> {
274 self.0.next()
275 }
276}
277
278pub(crate) struct ClientBlockListIterMut<'a>(std::slice::IterMut<'a, BlockCell>);
279
280impl<'a> Iterator for ClientBlockListIterMut<'a> {
281 type Item = &'a mut BlockCell;
282
283 fn next(&mut self) -> Option<Self::Item> {
284 self.0.next()
285 }
286}
287
288/// Block store is a collection of all blocks known to a document owning instance of this type.
289/// Blocks are organized per client ID and contain a resizable list of all blocks inserted by that
290/// client.
291#[derive(PartialEq, Default)]
292pub(crate) struct BlockStore {
293 clients: HashMap<ClientID, ClientBlockList, BuildHasherDefault<ClientHasher>>,
294}
295
296pub(crate) type Iter<'a> = std::collections::hash_map::Iter<'a, ClientID, ClientBlockList>;
297pub(crate) type IterMut<'a> = std::collections::hash_map::IterMut<'a, ClientID, ClientBlockList>;
298
299impl BlockStore {
300 /// Checks if block store is empty. Empty block store doesn't contain any blocks, neither active
301 /// nor tombstoned.
302 pub fn is_empty(&self) -> bool {
303 self.clients.is_empty()
304 }
305
306 pub fn contains(&self, id: &ID) -> bool {
307 if let Some(clients) = self.clients.get(&id.client) {
308 id.clock < clients.clock()
309 } else {
310 false
311 }
312 }
313
314 pub fn push_block(&mut self, block: Box<Item>) {
315 let id = block.id();
316 match self.clients.entry(id.client) {
317 Entry::Occupied(mut e) => {
318 let list = e.get_mut();
319 list.push(block.into());
320 }
321 Entry::Vacant(e) => {
322 let list = e.insert(ClientBlockList::default());
323 list.push(block.into());
324 }
325 }
326 }
327
328 pub fn push_gc(&mut self, gc: BlockRange) {
329 let id = gc.id;
330 let gc: BlockCell = GC::from(gc).into();
331 match self.clients.entry(id.client) {
332 Entry::Occupied(mut e) => {
333 let list = e.get_mut();
334 list.push(gc);
335 }
336 Entry::Vacant(e) => {
337 let list = e.insert(ClientBlockList::default());
338 list.push(gc);
339 }
340 }
341 }
342
343 /// Returns an iterator over the client and block lists pairs known to a current block store.
344 pub fn iter(&self) -> Iter<'_> {
345 self.clients.iter()
346 }
347
348 /// Returns an iterator over the client and mutable block lists pairs known to a current block store.
349 pub fn iter_mut(&mut self) -> IterMut<'_> {
350 self.clients.iter_mut()
351 }
352
353 /// Returns a state vector, which is a compact representation of the state of blocks integrated
354 /// into a current block store. This state vector can later be encoded and send to a remote
355 /// peers in order to calculate differences between two stored and produce a compact update,
356 /// that can be applied in order to fill missing update information.
357 pub fn get_state_vector(&self) -> StateVector {
358 let map = self
359 .clients
360 .iter()
361 .map(|(client_id, list)| (*client_id, list.clock()))
362 .collect();
363 StateVector::new(map)
364 }
365
366 pub(crate) fn get_client(&self, client_id: &ClientID) -> Option<&ClientBlockList> {
367 self.clients.get(client_id)
368 }
369
370 pub(crate) fn get_client_mut(&mut self, client_id: &ClientID) -> Option<&mut ClientBlockList> {
371 self.clients.get_mut(client_id)
372 }
373
374 /// Returns immutable reference to a block, given its pointer. Returns `None` if not such
375 /// block could be found.
376 pub(crate) fn get_block(&self, id: &ID) -> Option<&BlockCell> {
377 let clients = self.clients.get(&id.client)?;
378 clients.get_block(id.clock)
379 }
380
381 pub(crate) fn get_block_mut(&mut self, id: &ID) -> Option<&mut BlockCell> {
382 let clients = self.clients.get_mut(&id.client)?;
383 clients.get_block_mut(id.clock)
384 }
385
386 pub(crate) fn get_item(&self, id: &ID) -> Option<ItemPtr> {
387 let cell = self.get_block(id)?;
388 if let BlockCell::Block(item) = cell {
389 Some(ItemPtr::from(item))
390 } else {
391 None
392 }
393 }
394
395 /// Returns a block slice that represents a range of data within a particular block containing
396 /// provided [ID], starting from that [ID] until the end of the block.
397 ///
398 /// Example: *for a block `A:1..=5` and id `A:3`, the returned slice will represent `A:3..=5`*.
399 pub(crate) fn get_item_clean_start(&self, id: &ID) -> Option<ItemSlice> {
400 let ptr = self.get_item(id)?;
401 let offset = id.clock - ptr.id().clock;
402 Some(ItemSlice::new(ptr, offset, ptr.len() - 1))
403 }
404
405 /// Returns a block slice that represents a range of data within a particular block containing
406 /// provided [ID], starting from the beginning of the block until the that [ID] (inclusive).
407 ///
408 /// Example: *for a block `A:1..=5` and id `A:3`, the returned slice will represent `A:1..=3`*.
409 pub(crate) fn get_item_clean_end(&self, id: &ID) -> Option<ItemSlice> {
410 let ptr = self.get_item(id)?;
411 let block_id = ptr.id();
412 let offset = id.clock - block_id.clock;
413 Some(ItemSlice::new(ptr, 0, offset))
414 }
415
416 /// Returns the last observed clock sequence number for a given `client`. This is exclusive
417 /// value meaning it describes a clock value of the beginning of the next block that's about
418 /// to be inserted. You cannot use that clock value to find any existing block content.
419 pub fn get_clock(&self, client: &ClientID) -> u32 {
420 if let Some(list) = self.clients.get(client) {
421 list.clock()
422 } else {
423 0
424 }
425 }
426
427 /// Returns a mutable reference to block list for the given `client`. In case when no such list
428 /// existed, a new one will be created and returned.
429 pub(crate) fn get_client_blocks_mut(&mut self, client: ClientID) -> &mut ClientBlockList {
430 self.clients
431 .entry(client)
432 .or_insert_with(ClientBlockList::default)
433 }
434
435 /// Given block pointer, tries to split it, returning a true, if block was split in result of
436 /// calling this action, and false otherwise.
437 pub fn split_block(
438 &mut self,
439 mut block: ItemPtr,
440 offset: u32,
441 encoding: OffsetKind,
442 ) -> Option<ItemPtr> {
443 let id = block.id().clone();
444 let blocks = self.clients.get_mut(&id.client)?;
445 let index = blocks.find_pivot(id.clock)?;
446 let mut right = block.splice(offset, encoding)?;
447 let right_ptr = ItemPtr::from(&mut right);
448 blocks.insert(index + 1, right.into());
449
450 Some(right_ptr)
451 }
452
453 pub(crate) fn split_block_inner(&mut self, block: ItemPtr, offset: u32) -> Option<ItemPtr> {
454 self.split_block(block, offset, OffsetKind::Utf16)
455 }
456}
457
458impl std::fmt::Debug for ClientBlockList {
459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460 std::fmt::Display::fmt(self, f)
461 }
462}
463
464impl std::fmt::Display for ClientBlockList {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 f.debug_list().entries(self.list.iter()).finish()
467 }
468}
469
470impl std::fmt::Debug for BlockStore {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 std::fmt::Display::fmt(self, f)
473 }
474}
475
476impl std::fmt::Display for BlockStore {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 let mut s = f.debug_struct("");
479 for (k, v) in self.clients.iter() {
480 s.field(&k.to_string(), v);
481 }
482 s.finish()
483 }
484}
485
486pub(crate) struct Blocks<'a> {
487 current_client: std::vec::IntoIter<(&'a ClientID, &'a ClientBlockList)>,
488 current_block: Option<ClientBlockListIter<'a>>,
489}
490
491impl<'a> Blocks<'a> {
492 fn new(update: &'a BlockStore) -> Self {
493 let mut client_blocks: Vec<(&'a ClientID, &'a ClientBlockList)> =
494 update.clients.iter().collect();
495 // sorting to return higher client ids first
496 client_blocks.sort_by(|a, b| b.0.cmp(a.0));
497 let mut current_client = client_blocks.into_iter();
498
499 let current_block = current_client.next().map(|(_, v)| v.iter());
500 Blocks {
501 current_client,
502 current_block,
503 }
504 }
505}
506
507impl<'a> Iterator for Blocks<'a> {
508 type Item = &'a BlockCell;
509
510 fn next(&mut self) -> Option<Self::Item> {
511 if let Some(blocks) = self.current_block.as_mut() {
512 let block = blocks.next();
513 if block.is_some() {
514 return block;
515 }
516 }
517
518 if let Some(entry) = self.current_client.next() {
519 self.current_block = Some(entry.1.iter());
520 self.next()
521 } else {
522 None
523 }
524 }
525}
526