feophantlib/engine/io/page_formats/
page_offset.rs

1use crate::{
2    constants::{PAGES_PER_FILE, PAGE_SIZE},
3    engine::io::{
4        format_traits::{Parseable, Serializable},
5        ConstEncodedSize,
6    },
7};
8use bytes::{Buf, BufMut};
9use std::{
10    convert::TryFrom,
11    fmt,
12    mem::size_of,
13    num::TryFromIntError,
14    ops::{Add, AddAssign, Mul},
15};
16use thiserror::Error;
17
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct PageOffset(pub usize);
20
21impl PageOffset {
22    /// This will calculate a page offset based on the max file count and the offset from the first
23    /// non-zero page in a file.
24    ///
25    /// Example: found file blah_blah.1 and in that file found a single non-zero page.
26    ///     We will return a page offset of 2 * PAGES_PER_FILE + 1
27    pub fn calculate_page_offset(file_number: usize, offset_in_file: usize) -> PageOffset {
28        let offset = file_number * PAGES_PER_FILE + (offset_in_file / PAGE_SIZE as usize);
29        PageOffset(offset)
30    }
31
32    /// Gets the needed size for this offset to support resize operations
33    pub fn get_file_chunk_size(&self) -> usize {
34        ((self.0 % PAGES_PER_FILE) + 1) * PAGE_SIZE as usize
35    }
36    /// Gets the file number for use in opening the file chunk
37    pub fn get_file_number(&self) -> usize {
38        self.0 / PAGES_PER_FILE
39    }
40
41    /// Gets the location to seek to in order to write to the block the page offset points at
42    pub fn get_file_seek(&self) -> usize {
43        self.get_file_chunk_size() - PAGE_SIZE as usize
44    }
45
46    /// Gets the position of the free/visibility mask for an offset
47    /// ```
48    /// # use crate::feophantlib::engine::io::page_formats::PageOffset;
49    /// let page = PageOffset(100);
50    /// assert_eq!(page.get_bitmask_offset(), (PageOffset(0), 100));
51    /// ```
52    pub fn get_bitmask_offset(&self) -> (PageOffset, usize) {
53        let offset = self.0 / (PAGE_SIZE as usize * 8);
54        let inside_offset = self.0 % (PAGE_SIZE as usize * 8);
55        (PageOffset(offset), inside_offset)
56    }
57
58    /// Determines if a given offset will be the same file or not
59    pub fn is_same_file(&self, rhs: &PageOffset) -> bool {
60        let diff;
61        if self.0 > rhs.0 {
62            diff = self.0 - rhs.0;
63        } else {
64            diff = rhs.0 - self.0;
65        }
66        PAGES_PER_FILE > diff
67    }
68
69    /// Gets the next offset in sequence
70    pub fn next(&self) -> PageOffset {
71        PageOffset(self.0 + 1)
72    }
73}
74
75impl Add for PageOffset {
76    type Output = PageOffset;
77
78    fn add(self, rhs: Self) -> Self::Output {
79        PageOffset(self.0 + rhs.0)
80    }
81}
82
83impl AddAssign for PageOffset {
84    fn add_assign(&mut self, other: Self) {
85        self.0.add_assign(other.0);
86    }
87}
88
89impl ConstEncodedSize for PageOffset {
90    fn encoded_size() -> usize {
91        size_of::<usize>()
92    }
93}
94
95impl fmt::Display for PageOffset {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        self.0.fmt(f)
98    }
99}
100
101impl Parseable<PageOffsetError> for PageOffset {
102    type Output = Self;
103    fn parse(buffer: &mut impl Buf) -> Result<Self, PageOffsetError> {
104        if buffer.remaining() < size_of::<usize>() {
105            return Err(PageOffsetError::BufferTooShort(
106                size_of::<usize>(),
107                buffer.remaining(),
108            ));
109        }
110
111        let value = buffer.get_uint_le(size_of::<usize>());
112        let page = usize::try_from(value)?;
113
114        Ok(PageOffset(page))
115    }
116}
117
118impl Mul for PageOffset {
119    type Output = PageOffset;
120
121    fn mul(self, rhs: Self) -> Self::Output {
122        PageOffset(self.0 * rhs.0)
123    }
124}
125
126impl Serializable for PageOffset {
127    fn serialize(&self, buffer: &mut impl BufMut) {
128        //TODO switch this and other serialize calls to usize based once my pull request is accepted
129        //https://github.com/tokio-rs/bytes/pull/511
130        buffer.put_uint_le(self.0 as u64, size_of::<usize>());
131    }
132}
133
134#[derive(Debug, Error, PartialEq)]
135pub enum PageOffsetError {
136    #[error("Not enough space to parse usize need {0} got {1}")]
137    BufferTooShort(usize, usize),
138    #[error(transparent)]
139    TryFromIntError(#[from] TryFromIntError),
140}
141
142#[cfg(test)]
143mod tests {
144    use std::collections::HashMap;
145
146    use uuid::Uuid;
147
148    use super::*;
149
150    #[test]
151    fn test_add() -> Result<(), Box<dyn std::error::Error>> {
152        assert_eq!(PageOffset(1) + PageOffset(2), PageOffset(3));
153        Ok(())
154    }
155
156    #[test]
157    fn test_add_assign() -> Result<(), Box<dyn std::error::Error>> {
158        let mut test = PageOffset(1);
159        test += PageOffset(2);
160        assert_eq!(test, PageOffset(3));
161        Ok(())
162    }
163
164    #[test]
165    fn test_mul() -> Result<(), Box<dyn std::error::Error>> {
166        assert_eq!(PageOffset(2) * PageOffset(3), PageOffset(6));
167        Ok(())
168    }
169
170    #[test]
171    fn test_calculate_page_offset() -> Result<(), Box<dyn std::error::Error>> {
172        assert_eq!(PageOffset::calculate_page_offset(0, 0), PageOffset(0));
173        assert_eq!(
174            PageOffset::calculate_page_offset(0, PAGE_SIZE as usize),
175            PageOffset(1)
176        );
177
178        assert_eq!(
179            PageOffset::calculate_page_offset(1, PAGE_SIZE as usize),
180            PageOffset(PAGES_PER_FILE + 1)
181        );
182
183        Ok(())
184    }
185
186    #[test]
187    fn test_get_file_chunk_size() -> Result<(), Box<dyn std::error::Error>> {
188        assert_eq!(PageOffset(0).get_file_chunk_size(), PAGE_SIZE as usize);
189        assert_eq!(PageOffset(1).get_file_chunk_size(), PAGE_SIZE as usize * 2);
190        assert_eq!(
191            PageOffset(PAGES_PER_FILE).get_file_chunk_size(),
192            PAGE_SIZE as usize
193        );
194        assert_eq!(
195            PageOffset(PAGES_PER_FILE - 1).get_file_chunk_size(),
196            PAGE_SIZE as usize * PAGES_PER_FILE
197        );
198        assert_eq!(
199            PageOffset(PAGES_PER_FILE + 1).get_file_chunk_size(),
200            2 * PAGE_SIZE as usize
201        );
202
203        Ok(())
204    }
205
206    #[test]
207    fn test_get_file_number() -> Result<(), Box<dyn std::error::Error>> {
208        assert_eq!(PageOffset(0).get_file_number(), 0);
209        assert_eq!(PageOffset(PAGES_PER_FILE).get_file_number(), 1);
210        assert_eq!(PageOffset(PAGES_PER_FILE - 1).get_file_number(), 0);
211        assert_eq!(PageOffset(PAGES_PER_FILE + 1).get_file_number(), 1);
212
213        Ok(())
214    }
215
216    #[test]
217    fn test_get_file_seek() -> Result<(), Box<dyn std::error::Error>> {
218        assert_eq!(PageOffset(0).get_file_seek(), 0);
219        assert_eq!(PageOffset(PAGES_PER_FILE).get_file_seek(), 0);
220        assert_eq!(
221            PageOffset(PAGES_PER_FILE - 1).get_file_seek(),
222            (PAGES_PER_FILE - 1) * PAGE_SIZE as usize
223        );
224        assert_eq!(
225            PageOffset(PAGES_PER_FILE + 1).get_file_seek(),
226            PAGE_SIZE as usize
227        );
228        assert_eq!(
229            PageOffset(PAGES_PER_FILE + 2).get_file_seek(),
230            2 * PAGE_SIZE as usize
231        );
232
233        Ok(())
234    }
235
236    #[test]
237    fn test_is_same_file() -> Result<(), Box<dyn std::error::Error>> {
238        assert!(!PageOffset(0).is_same_file(&PageOffset(PAGES_PER_FILE)));
239        assert!(PageOffset(0).is_same_file(&PageOffset(0)));
240        assert!(PageOffset(0).is_same_file(&PageOffset(PAGES_PER_FILE - 1)));
241        assert!(!PageOffset(PAGES_PER_FILE).is_same_file(&PageOffset(0)));
242        assert!(PageOffset(PAGES_PER_FILE - 1).is_same_file(&PageOffset(0)));
243
244        Ok(())
245    }
246
247    #[test]
248    fn test_increment_and_hash_map() -> Result<(), Box<dyn std::error::Error>> {
249        let test = PageOffset(0);
250        assert_eq!(test.next(), PageOffset(1));
251
252        let test_uuid = Uuid::new_v4();
253
254        let mut resource_lookup: HashMap<Uuid, PageOffset> = HashMap::new();
255        resource_lookup.insert(test_uuid, PageOffset(0));
256        let test0 = resource_lookup.remove(&test_uuid).unwrap();
257        resource_lookup.insert(test_uuid, test0.next());
258        let test1 = resource_lookup.get(&test_uuid).unwrap();
259
260        assert_eq!(*test1, PageOffset(1));
261        Ok(())
262    }
263}