hopper_core/collections/
journal.rs1use crate::account::{FixedLayout, Pod};
39use hopper_runtime::error::ProgramError;
40
41pub const JOURNAL_HEADER_SIZE: usize = 16;
43
44pub const JOURNAL_FLAG_CIRCULAR: u32 = 1 << 0;
46
47pub struct Journal<'a, T: Pod + FixedLayout> {
49 data: &'a mut [u8],
50 capacity: usize,
51 _phantom: core::marker::PhantomData<T>,
52}
53
54impl<'a, T: Pod + FixedLayout> Journal<'a, T> {
55 #[inline]
57 pub fn from_bytes_mut(data: &'a mut [u8]) -> Result<Self, ProgramError> {
58 if data.len() < JOURNAL_HEADER_SIZE {
59 return Err(ProgramError::AccountDataTooSmall);
60 }
61 let usable = data.len() - JOURNAL_HEADER_SIZE;
62 if T::SIZE == 0 {
63 return Err(ProgramError::InvalidArgument);
64 }
65 let capacity = usable / T::SIZE;
66 Ok(Self {
67 data,
68 capacity,
69 _phantom: core::marker::PhantomData,
70 })
71 }
72
73 #[inline]
75 pub fn from_bytes(data: &[u8]) -> Result<JournalReader<'_, T>, ProgramError> {
76 if data.len() < JOURNAL_HEADER_SIZE {
77 return Err(ProgramError::AccountDataTooSmall);
78 }
79 let usable = data.len() - JOURNAL_HEADER_SIZE;
80 if T::SIZE == 0 {
81 return Err(ProgramError::InvalidArgument);
82 }
83 let capacity = usable / T::SIZE;
84 Ok(JournalReader {
85 data,
86 capacity,
87 _phantom: core::marker::PhantomData,
88 })
89 }
90
91 #[inline(always)]
93 pub fn capacity(&self) -> usize {
94 self.capacity
95 }
96
97 #[inline(always)]
99 pub fn write_head(&self) -> u32 {
100 u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]])
101 }
102
103 #[inline(always)]
105 pub fn total_written(&self) -> u32 {
106 u32::from_le_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
107 }
108
109 #[inline(always)]
111 pub fn flags(&self) -> u32 {
112 u32::from_le_bytes([self.data[8], self.data[9], self.data[10], self.data[11]])
113 }
114
115 #[inline(always)]
117 pub fn is_circular(&self) -> bool {
118 self.flags() & JOURNAL_FLAG_CIRCULAR != 0
119 }
120
121 #[inline(always)]
123 pub fn has_wrapped(&self) -> bool {
124 (self.total_written() as usize) > self.capacity
125 }
126
127 #[inline(always)]
129 pub fn entry_count(&self) -> usize {
130 let total = self.total_written() as usize;
131 if total < self.capacity {
132 total
133 } else {
134 self.capacity
135 }
136 }
137
138 #[inline]
140 pub fn append(&mut self, entry: T) -> Result<(), ProgramError> {
141 let mut head = self.write_head() as usize;
142
143 if head >= self.capacity {
144 if !self.is_circular() {
145 return Err(ProgramError::AccountDataTooSmall);
146 }
147 head %= self.capacity;
149 }
150
151 if !self.is_circular() && head >= self.capacity {
152 return Err(ProgramError::AccountDataTooSmall);
153 }
154
155 let offset = JOURNAL_HEADER_SIZE + head * T::SIZE;
157 let end = offset + T::SIZE;
158 if end > self.data.len() {
159 return Err(ProgramError::AccountDataTooSmall);
160 }
161
162 unsafe {
164 core::ptr::copy_nonoverlapping(
165 &entry as *const T as *const u8,
166 self.data.as_mut_ptr().add(offset),
167 T::SIZE,
168 );
169 }
170
171 let new_head = if self.is_circular() {
173 ((head + 1) % self.capacity) as u32
174 } else {
175 (head + 1) as u32
176 };
177 self.set_write_head(new_head);
178
179 let total = self.total_written().wrapping_add(1);
181 self.set_total_written(total);
182
183 Ok(())
184 }
185
186 #[inline]
188 pub fn read(&self, index: usize) -> Result<T, ProgramError> {
189 let count = self.entry_count();
190 if index >= count {
191 return Err(ProgramError::InvalidArgument);
192 }
193
194 let physical = if self.total_written() as usize > self.capacity {
195 (self.write_head() as usize + index) % self.capacity
197 } else {
198 index
199 };
200
201 let offset = JOURNAL_HEADER_SIZE + physical * T::SIZE;
202 let end = offset + T::SIZE;
203 if end > self.data.len() {
204 return Err(ProgramError::AccountDataTooSmall);
205 }
206
207 Ok(unsafe { core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T) })
209 }
210
211 #[inline]
213 pub fn latest(&self) -> Result<T, ProgramError> {
214 let count = self.entry_count();
215 if count == 0 {
216 return Err(ProgramError::InvalidArgument);
217 }
218 self.read(count - 1)
219 }
220
221 #[inline(always)]
223 pub const fn required_bytes(capacity: usize) -> usize {
224 JOURNAL_HEADER_SIZE + capacity * T::SIZE
225 }
226
227 #[inline]
229 pub fn init(&mut self, circular: bool) {
230 self.set_write_head(0);
231 self.set_total_written(0);
232 let flags: u32 = if circular { JOURNAL_FLAG_CIRCULAR } else { 0 };
233 self.data[8..12].copy_from_slice(&flags.to_le_bytes());
234 self.data[12..16].copy_from_slice(&0u32.to_le_bytes());
235 }
236
237 #[inline(always)]
238 fn set_write_head(&mut self, head: u32) {
239 self.data[0..4].copy_from_slice(&head.to_le_bytes());
240 }
241
242 #[inline(always)]
243 fn set_total_written(&mut self, total: u32) {
244 self.data[4..8].copy_from_slice(&total.to_le_bytes());
245 }
246}
247
248pub struct JournalReader<'a, T: Pod + FixedLayout> {
250 data: &'a [u8],
251 capacity: usize,
252 _phantom: core::marker::PhantomData<T>,
253}
254
255impl<'a, T: Pod + FixedLayout> JournalReader<'a, T> {
256 #[inline(always)]
258 pub fn capacity(&self) -> usize {
259 self.capacity
260 }
261
262 #[inline(always)]
264 pub fn write_head(&self) -> u32 {
265 u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]])
266 }
267
268 #[inline(always)]
270 pub fn total_written(&self) -> u32 {
271 u32::from_le_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
272 }
273
274 #[inline(always)]
276 pub fn entry_count(&self) -> usize {
277 let total = self.total_written() as usize;
278 if total < self.capacity {
279 total
280 } else {
281 self.capacity
282 }
283 }
284
285 #[inline(always)]
287 pub fn is_circular(&self) -> bool {
288 let flags = u32::from_le_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]);
289 flags & JOURNAL_FLAG_CIRCULAR != 0
290 }
291
292 #[inline]
294 pub fn read(&self, index: usize) -> Result<T, ProgramError> {
295 let count = self.entry_count();
296 if index >= count {
297 return Err(ProgramError::InvalidArgument);
298 }
299
300 let physical = if self.total_written() as usize > self.capacity {
301 (self.write_head() as usize + index) % self.capacity
302 } else {
303 index
304 };
305
306 let offset = JOURNAL_HEADER_SIZE + physical * T::SIZE;
307 let end = offset + T::SIZE;
308 if end > self.data.len() {
309 return Err(ProgramError::AccountDataTooSmall);
310 }
311
312 Ok(unsafe { core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T) })
314 }
315
316 #[inline]
318 pub fn latest(&self) -> Result<T, ProgramError> {
319 let count = self.entry_count();
320 if count == 0 {
321 return Err(ProgramError::InvalidArgument);
322 }
323 self.read(count - 1)
324 }
325}