wasm_dbms_memory/
unclaimed_pages.rs1use std::borrow::Cow;
13
14use wasm_dbms_api::prelude::{
15 DEFAULT_ALIGNMENT, DataSize, Encode, MSize, MemoryError, MemoryResult, Page, PageOffset,
16};
17
18const HEADER_SIZE: u64 = 4;
20const ENTRY_SIZE: u64 = 4;
22
23pub const UNCLAIMED_PAGES_CAPACITY: u32 = {
28 let max_bytes = MSize::MAX as u64;
29 let entries = (max_bytes - HEADER_SIZE) / ENTRY_SIZE;
30 entries as u32
31};
32
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct UnclaimedPages {
39 pages: Vec<Page>,
40}
41
42impl UnclaimedPages {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn len(&self) -> usize {
50 self.pages.len()
51 }
52
53 pub fn remaining_capacity(&self) -> u32 {
55 UNCLAIMED_PAGES_CAPACITY - (self.pages.len() as u32)
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.pages.is_empty()
61 }
62
63 pub fn pop(&mut self) -> Option<Page> {
65 self.pages.pop()
66 }
67
68 pub fn push(&mut self, page: Page) -> MemoryResult<()> {
74 if self.pages.len() as u32 >= UNCLAIMED_PAGES_CAPACITY {
75 return Err(MemoryError::UnclaimedPagesFull {
76 capacity: UNCLAIMED_PAGES_CAPACITY,
77 });
78 }
79 self.pages.push(page);
80 Ok(())
81 }
82
83 pub fn as_slice(&self) -> &[Page] {
85 &self.pages
86 }
87}
88
89impl Encode for UnclaimedPages {
90 const SIZE: DataSize = DataSize::Dynamic;
91 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
92
93 fn encode(&'_ self) -> Cow<'_, [u8]> {
94 let mut buf = Vec::with_capacity(self.size() as usize);
95 buf.extend_from_slice(&(self.pages.len() as u32).to_le_bytes());
96 for &page in &self.pages {
97 buf.extend_from_slice(&page.to_le_bytes());
98 }
99 Cow::Owned(buf)
100 }
101
102 fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
103 where
104 Self: Sized,
105 {
106 if data.len() < HEADER_SIZE as usize {
107 return Ok(Self::default());
108 }
109 let count = u32::from_le_bytes(data[0..4].try_into()?) as usize;
110 if count > UNCLAIMED_PAGES_CAPACITY as usize {
111 return Err(MemoryError::UnclaimedPagesFull {
112 capacity: UNCLAIMED_PAGES_CAPACITY,
113 });
114 }
115 let mut pages = Vec::with_capacity(count);
116 let mut cursor = HEADER_SIZE as usize;
117 for _ in 0..count {
118 let page = Page::from_le_bytes(data[cursor..cursor + 4].try_into()?);
119 pages.push(page);
120 cursor += 4;
121 }
122 Ok(Self { pages })
123 }
124
125 fn size(&self) -> MSize {
126 (HEADER_SIZE as MSize) + (self.pages.len() as MSize) * (ENTRY_SIZE as MSize)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_should_be_empty_by_default() {
136 let ledger = UnclaimedPages::new();
137 assert!(ledger.is_empty());
138 assert_eq!(ledger.len(), 0);
139 }
140
141 #[test]
142 fn test_should_push_and_pop() {
143 let mut ledger = UnclaimedPages::new();
144 ledger.push(10).expect("push");
145 ledger.push(20).expect("push");
146 ledger.push(30).expect("push");
147
148 assert_eq!(ledger.len(), 3);
149 assert_eq!(ledger.pop(), Some(30));
150 assert_eq!(ledger.pop(), Some(20));
151 assert_eq!(ledger.pop(), Some(10));
152 assert_eq!(ledger.pop(), None);
153 }
154
155 #[test]
156 fn test_should_round_trip_encode_decode() {
157 let mut ledger = UnclaimedPages::new();
158 for page in [3u32, 5, 7, 11] {
159 ledger.push(page).expect("push");
160 }
161
162 let encoded = ledger.encode();
163 let decoded = UnclaimedPages::decode(encoded).expect("decode");
164 assert_eq!(ledger, decoded);
165 }
166
167 #[test]
168 fn test_should_decode_empty_buffer_as_empty_ledger() {
169 let buf = vec![0u8; 65536];
170 let ledger = UnclaimedPages::decode(Cow::Owned(buf)).expect("decode");
171 assert!(ledger.is_empty());
172 }
173
174 #[test]
175 fn test_should_reject_push_when_full() {
176 let mut ledger = UnclaimedPages {
177 pages: vec![0; UNCLAIMED_PAGES_CAPACITY as usize],
178 };
179 let err = ledger.push(42).expect_err("push at capacity");
180 assert!(matches!(err, MemoryError::UnclaimedPagesFull { .. }));
181 }
182
183 #[test]
184 fn test_size_matches_encoded_length() {
185 let mut ledger = UnclaimedPages::new();
186 ledger.push(1).expect("push");
187 ledger.push(2).expect("push");
188 let encoded = ledger.encode();
189 assert_eq!(ledger.size() as usize, encoded.len());
190 }
191}