templar_common/
chunked_append_only_list.rs1use borsh::{BorshDeserialize, BorshSerialize};
2use near_sdk::{near, store::Vector, BorshStorageKey, IntoStorageKey};
3
4#[derive(Debug, Clone, Copy, BorshSerialize, BorshStorageKey, PartialEq, Eq, PartialOrd, Ord)]
5enum StorageKey {
6 Inner,
7}
8
9#[derive(Debug)]
12#[near(serializers = [borsh])]
13pub struct ChunkedAppendOnlyList<T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> {
14 inner: Vector<Vec<T>>,
15 last_chunk_next_index: u32,
16}
17
18impl<T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32>
19 ChunkedAppendOnlyList<T, CHUNK_SIZE>
20{
21 pub fn new(prefix: impl IntoStorageKey) -> Self {
22 Self {
23 inner: Vector::new(
24 [
25 prefix.into_storage_key(),
26 StorageKey::Inner.into_storage_key(),
27 ]
28 .concat(),
29 ),
30 last_chunk_next_index: 0,
31 }
32 }
33
34 pub fn len(&self) -> u32 {
35 let Some(last_index) = self.inner.len().checked_sub(1) else {
36 return 0;
37 };
38 last_index * CHUNK_SIZE
39 + if self.last_chunk_next_index == 0 {
40 CHUNK_SIZE
41 } else {
42 self.last_chunk_next_index
43 }
44 }
45
46 pub fn is_empty(&self) -> bool {
47 self.inner.is_empty()
48 }
49
50 pub fn push(&mut self, item: T) {
51 if self.last_chunk_next_index == 0 {
52 let v = vec![item];
53 self.inner.push(v);
54 } else {
55 let last_inner = self
56 .inner
57 .len()
58 .checked_sub(1)
59 .unwrap_or_else(|| crate::panic_with_message("Inconsistent state: len == 0"));
60 let v = self
61 .inner
62 .get_mut(last_inner)
63 .unwrap_or_else(|| crate::panic_with_message("Inconsistent state: tail dne"));
64 v.push(item);
65 }
66 self.last_chunk_next_index = (self.last_chunk_next_index + 1) % CHUNK_SIZE;
67 }
68
69 pub fn get(&self, index: u32) -> Option<&T> {
70 self.inner
71 .get(index / CHUNK_SIZE)
72 .and_then(|v| v.get((index % CHUNK_SIZE) as usize))
73 }
74
75 pub fn replace_last(&mut self, item: T) {
76 let Some(entry) = self
77 .inner
78 .len()
79 .checked_sub(1)
80 .and_then(|last_index| self.inner.get_mut(last_index))
81 .and_then(|v| v.last_mut())
82 else {
83 crate::panic_with_message("Cannot replace_last in empty list");
84 };
85 *entry = item;
86 }
87
88 pub fn iter(&self) -> Iter<'_, T, CHUNK_SIZE> {
89 Iter {
90 list: self,
91 next_index: 0,
92 until_index: self.len(),
93 }
94 }
95
96 pub fn flush(&mut self) {
97 self.inner.flush();
98 }
99}
100
101impl<'a, T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> IntoIterator
102 for &'a ChunkedAppendOnlyList<T, CHUNK_SIZE>
103{
104 type Item = &'a T;
105
106 type IntoIter = Iter<'a, T, CHUNK_SIZE>;
107
108 fn into_iter(self) -> Self::IntoIter {
109 self.iter()
110 }
111}
112
113pub struct Iter<'a, T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> {
114 list: &'a ChunkedAppendOnlyList<T, CHUNK_SIZE>,
115 next_index: u32,
116 until_index: u32,
117}
118
119impl<'a, T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> Iterator
120 for Iter<'a, T, CHUNK_SIZE>
121{
122 type Item = &'a T;
123
124 fn nth(&mut self, n: usize) -> Option<Self::Item> {
125 if self.until_index <= self.next_index {
126 return None;
127 }
128 #[allow(
129 clippy::unwrap_used,
130 reason = "Assume collection size is never > u32::MAX"
131 )]
132 let n = u32::try_from(n).unwrap();
133 let index = self.next_index + n;
134 let value = self.list.get(index)?;
135 self.next_index = index + 1;
136 Some(value)
137 }
138
139 fn next(&mut self) -> Option<Self::Item> {
140 self.nth(0)
141 }
142}
143
144impl<T: BorshSerialize + BorshDeserialize, const CHUNK_SIZE: u32> DoubleEndedIterator
145 for Iter<'_, T, CHUNK_SIZE>
146{
147 fn next_back(&mut self) -> Option<Self::Item> {
148 if self.until_index <= self.next_index {
149 return None;
150 }
151 let (index, value) = self
152 .until_index
153 .checked_sub(1)
154 .and_then(|index| self.list.get(index).map(|x| (index, x)))?;
155 self.until_index = index;
156 Some(value)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn basic() {
166 let mut list = ChunkedAppendOnlyList::<_, 47>::new(b"l");
167 assert_eq!(list.len(), 0);
168 assert!(list.is_empty());
169
170 for i in 0..10_000usize {
171 list.push(i);
172 assert_eq!(list.len() as usize, i + 1);
173 assert!(!list.is_empty());
174 }
175
176 let mut count = 0;
177 for (i, v) in list.iter().enumerate() {
178 assert_eq!(i, *v);
179 count += 1;
180 }
181
182 assert_eq!(count, 10_000);
183 }
184
185 #[test]
186 fn replace_last() {
187 let mut list = ChunkedAppendOnlyList::<_, 47>::new(b"l");
188 for i in 0..10_000u32 {
189 list.push(i);
190 list.replace_last(i * 2);
191 assert_eq!(list.len(), i + 1);
192 assert!(!list.is_empty());
193 }
194
195 for i in 0..10_000u32 {
196 let x = list.get(i).unwrap();
197 assert_eq!(*x, i * 2);
198 }
199
200 assert_eq!(list.len(), 10_000);
201 }
202
203 #[test]
204 fn next_back() {
205 let mut list = ChunkedAppendOnlyList::<_, 47>::new(b"l");
206 for i in 0..10_000u32 {
207 list.push(i);
208 }
209
210 let mut it = list.iter();
211
212 let mut i = 10_000;
213 while let Some(x) = it.next_back() {
214 i -= 1;
215 assert_eq!(*x, i);
216 }
217
218 assert_eq!(i, 0);
219 }
220}