Skip to main content

sparse_vector/wand/
mmap.rs

1//! Cursor over the posting entries of an mmap'd index.
2//!
3//! The mmap file stores, per entry, a record id, a weight and a
4//! `max_next_weight` ceiling. The cursor folds that ceiling into the
5//! inclusive form used here (`tail_max = max(weight, max_next_weight)`),
6//! which is correct whether the file's ceiling includes the entry itself or
7//! only the entries after it.
8
9use crate::mmap_index::{MmapPostingData, PostingEntry};
10
11use super::cursor::PostingCursor;
12use super::{DimId, Posting, RecordId, Weight};
13
14/// Cursor over one dimension of an mmap'd index: a position within the
15/// dimension's entries, read straight from the mapping.
16#[derive(Clone, Debug)]
17pub struct MmapCursor<'a> {
18    entries: &'a [PostingEntry],
19    pos: usize,
20}
21
22impl<'a> MmapCursor<'a> {
23    /// Cursor at the start of `entries`, which must be sorted by id with no
24    /// duplicates (the writer guarantees it).
25    pub fn new(entries: &'a [PostingEntry]) -> Self {
26        Self { entries, pos: 0 }
27    }
28
29    /// Cursor for `dim` (a remapped dimension index), `None` when the
30    /// dimension has no postings.
31    pub fn open(data: &'a MmapPostingData, dim: DimId) -> Option<Self> {
32        let entries = data.entries(dim as usize);
33        (!entries.is_empty()).then(|| Self::new(entries))
34    }
35
36    #[inline]
37    fn fold(e: &PostingEntry) -> Posting {
38        Posting {
39            id: e.record_id,
40            weight: e.weight,
41            tail_max: e.weight.max(e.max_next_weight),
42        }
43    }
44}
45
46impl PostingCursor for MmapCursor<'_> {
47    #[inline]
48    fn peek(&self) -> Option<Posting> {
49        self.entries.get(self.pos).map(Self::fold)
50    }
51
52    #[inline]
53    fn advance(&mut self) {
54        if self.pos < self.entries.len() {
55            self.pos += 1;
56        }
57    }
58
59    fn seek(&mut self, target: RecordId) -> Option<Posting> {
60        let rest = &self.entries[self.pos.min(self.entries.len())..];
61        self.pos += rest.partition_point(|e| e.record_id < target);
62        self.peek()
63    }
64
65    #[inline]
66    fn remaining(&self) -> usize {
67        self.entries.len().saturating_sub(self.pos)
68    }
69
70    #[inline]
71    fn last_id(&self) -> Option<RecordId> {
72        self.entries.last().map(|e| e.record_id)
73    }
74
75    fn exhaust(&mut self) {
76        self.pos = self.entries.len();
77    }
78
79    fn drain_through(&mut self, hi: RecordId, mut visit: impl FnMut(RecordId, Weight)) {
80        let rest = &self.entries[self.pos.min(self.entries.len())..];
81        let mut taken = 0;
82        for e in rest {
83            if e.record_id > hi {
84                break;
85            }
86            visit(e.record_id, e.weight);
87            taken += 1;
88        }
89        self.pos += taken;
90    }
91}