automerge/rust/automerge/src/query/nth.rs

127 lines
4.4 KiB
Rust

use crate::error::AutomergeError;
use crate::op_set::OpSet;
use crate::op_tree::{OpTree, OpTreeNode};
use crate::query::{QueryResult, TreeQuery};
use crate::types::{Key, ListEncoding, Op, OpIds};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Nth<'a> {
target: usize,
seen: usize,
encoding: ListEncoding,
last_width: usize,
/// last_seen is the target elemid of the last `seen` operation.
/// It is used to avoid double counting visible elements (which arise through conflicts) that are split across nodes.
last_seen: Option<Key>,
pub(crate) ops: Vec<&'a Op>,
pub(crate) ops_pos: Vec<usize>,
pub(crate) pos: usize,
}
impl<'a> Nth<'a> {
pub(crate) fn new(target: usize, encoding: ListEncoding) -> Self {
Nth {
target,
seen: 0,
last_width: 1,
encoding,
last_seen: None,
ops: vec![],
ops_pos: vec![],
pos: 0,
}
}
pub(crate) fn pred(&self, ops: &OpSet) -> OpIds {
ops.m.sorted_opids(self.ops.iter().map(|o| o.id))
}
/// Get the key
pub(crate) fn key(&self) -> Result<Key, AutomergeError> {
// the query collects the ops so we can use that to get the key they all use
if let Some(e) = self.ops.first().and_then(|op| op.elemid()) {
Ok(Key::Seq(e))
} else {
Err(AutomergeError::InvalidIndex(self.target))
}
}
pub(crate) fn index(&self) -> usize {
self.seen - self.last_width
}
}
impl<'a> TreeQuery<'a> for Nth<'a> {
fn equiv(&mut self, other: &Self) -> bool {
self.index() == other.index() && self.key() == other.key()
}
fn can_shortcut_search(&mut self, tree: &'a OpTree) -> bool {
if let Some((index, pos)) = &tree.last_insert {
if *index == self.target {
if let Some(op) = tree.internal.get(*pos) {
self.last_width = op.width(self.encoding);
self.seen = *index + self.last_width;
self.ops.push(op);
self.ops_pos.push(*pos);
self.pos = *pos + 1;
return true;
}
}
}
false
}
fn query_node(&mut self, child: &OpTreeNode, ops: &[Op]) -> QueryResult {
let mut num_vis = child.index.visible_len(self.encoding);
if let Some(last_seen) = self.last_seen {
if child.index.has_visible(&last_seen) {
num_vis -= 1;
}
}
if self.seen + num_vis > self.target {
QueryResult::Descend
} else {
// skip this node as no useful ops in it
self.pos += child.len();
self.seen += num_vis;
// We have updated seen by the number of visible elements in this index, before we skip it.
// We also need to keep track of the last elemid that we have seen (and counted as seen).
// We can just use the elemid of the last op in this node as either:
// - the insert was at a previous node and this is a long run of overwrites so last_seen should already be set correctly
// - the visible op is in this node and the elemid references it so it can be set here
// - the visible op is in a future node and so it will be counted as seen there
let last_elemid = ops[child.last()].elemid_or_key();
if child.index.has_visible(&last_elemid) {
self.last_seen = Some(last_elemid);
}
QueryResult::Next
}
}
fn query_element(&mut self, element: &'a Op) -> QueryResult {
if element.insert {
if self.seen > self.target {
return QueryResult::Finish;
}
// we have a new potentially visible element so reset last_seen
self.last_seen = None
}
let visible = element.visible();
if visible && self.last_seen.is_none() {
self.last_width = element.width(self.encoding);
self.seen += self.last_width;
// we have a new visible element
self.last_seen = Some(element.elemid_or_key())
}
if self.seen > self.target && visible {
self.ops.push(element);
self.ops_pos.push(self.pos);
}
self.pos += 1;
QueryResult::Next
}
}