1use alloc::format;
7use core::cmp::Ordering;
8use num_traits::bounds::{LowerBounded, UpperBounded};
9use numerated::{Bound, Numerated, interval::Interval, iterators::IntervalIterator};
10use scale_decode::DecodeAsType;
11use scale_encode::EncodeAsType;
12use scale_info::{
13 TypeInfo,
14 scale::{Decode, Encode},
15};
16
17pub use numerated::{self, num_traits};
18
19const WASM_PAGE_SIZE: u32 = 64 * 1024;
21
22const GEAR_PAGE_SIZE: u32 = 16 * 1024;
27
28const _: () = assert!(WASM_PAGE_SIZE.is_multiple_of(GEAR_PAGE_SIZE));
29
30#[derive(
34 Clone,
35 Copy,
36 Debug,
37 Decode,
38 DecodeAsType,
39 Encode,
40 EncodeAsType,
41 PartialEq,
42 Eq,
43 PartialOrd,
44 Ord,
45 Hash,
46 TypeInfo,
47 Default,
48 derive_more::Into,
49)]
50pub struct PagesAmount<const SIZE: u32>(u32);
51
52impl<const SIZE: u32> PagesAmount<SIZE> {
53 pub const SIZE: u32 = SIZE;
57
58 pub const UPPER: Self = Self(u32::MAX / SIZE + 1 / if SIZE.is_power_of_two() { 1 } else { 0 });
62
63 #[cfg(test)]
65 pub fn add(&self, other: Self) -> Option<Self> {
66 self.0
67 .checked_add(other.0)
68 .and_then(|r| (r <= Self::UPPER.0).then_some(Self(r)))
69 }
70
71 pub fn to_page_number(&self) -> Option<Page<SIZE>> {
74 self.unbound()
75 }
76
77 pub fn to_pages_amount<const S: u32>(&self) -> PagesAmount<S> {
79 let raw = if Self::SIZE > S {
80 (Self::SIZE / S) * self.0
81 } else {
82 self.0 / (S / Self::SIZE)
83 };
84 PagesAmount(raw)
85 }
86
87 pub fn offset(&self) -> u64 {
92 self.0 as u64 * SIZE as u64
93 }
94}
95
96impl<const SIZE: u32> From<Page<SIZE>> for PagesAmount<SIZE> {
97 fn from(value: Page<SIZE>) -> Self {
98 Self(value.0)
99 }
100}
101
102impl<const SIZE: u32> From<Option<Page<SIZE>>> for PagesAmount<SIZE> {
103 fn from(value: Option<Page<SIZE>>) -> Self {
104 value.map(|page| page.into()).unwrap_or(Self::UPPER)
105 }
106}
107
108impl<const SIZE: u32> Bound<Page<SIZE>> for PagesAmount<SIZE> {
109 fn unbound(self) -> Option<Page<SIZE>> {
110 match self.cmp(&Self::UPPER) {
111 Ordering::Greater => {
112 let err_msg = format!(
114 "PagesAmount::unbound: PageBound must be always less or equal than UPPER. \
115 Page bound - {:?}, UPPER - {:?}",
116 self,
117 Self::UPPER
118 );
119
120 log::error!("{err_msg}");
121 unreachable!("{err_msg}")
122 }
123 Ordering::Equal => None,
124 Ordering::Less => Some(Page(self.0)),
125 }
126 }
127}
128
129#[derive(Debug, Clone, derive_more::Display)]
131#[display("Tries to make pages amount from {_0}, which must be less or equal to {_1}")]
132pub struct PagesAmountError(u32, u32);
133
134impl<const SIZE: u32> TryFrom<u32> for PagesAmount<SIZE> {
135 type Error = PagesAmountError;
136
137 fn try_from(raw: u32) -> Result<Self, Self::Error> {
138 if raw > Self::UPPER.0 {
139 Err(PagesAmountError(raw, Self::UPPER.0))
140 } else {
141 Ok(Self(raw))
142 }
143 }
144}
145
146impl<const SIZE: u32> PartialEq<Page<SIZE>> for PagesAmount<SIZE> {
147 fn eq(&self, other: &Page<SIZE>) -> bool {
148 self.0 == other.0
149 }
150}
151
152impl<const SIZE: u32> PartialOrd<Page<SIZE>> for PagesAmount<SIZE> {
153 fn partial_cmp(&self, other: &Page<SIZE>) -> Option<Ordering> {
154 self.0.partial_cmp(&other.0)
155 }
156}
157
158impl<const SIZE: u32> PartialEq<PagesAmount<SIZE>> for Page<SIZE> {
159 fn eq(&self, other: &PagesAmount<SIZE>) -> bool {
160 self.0 == other.0
161 }
162}
163
164impl<const SIZE: u32> PartialOrd<PagesAmount<SIZE>> for Page<SIZE> {
165 fn partial_cmp(&self, other: &PagesAmount<SIZE>) -> Option<Ordering> {
166 self.0.partial_cmp(&other.0)
167 }
168}
169
170#[derive(
175 Clone,
176 Copy,
177 Debug,
178 Decode,
179 DecodeAsType,
180 Encode,
181 EncodeAsType,
182 PartialEq,
183 Eq,
184 PartialOrd,
185 Ord,
186 Hash,
187 TypeInfo,
188 Default,
189 derive_more::Into,
190 derive_more::Display,
191)]
192#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
193pub struct Page<const SIZE: u32>(u32);
194
195impl<const SIZE: u32> Page<SIZE> {
196 pub const SIZE: u32 = SIZE;
198
199 #[allow(clippy::erasing_op)]
203 pub const UPPER: Self = Self(u32::MAX / SIZE + 0 / if SIZE.is_power_of_two() { 1 } else { 0 });
204
205 pub fn inc(&self) -> PagesAmount<SIZE> {
207 PagesAmount(self.0 + 1)
208 }
209
210 pub fn from_offset(offset: u32) -> Self {
212 Self(offset / Self::SIZE)
213 }
214
215 pub fn offset(&self) -> u32 {
217 self.0 * Self::SIZE
218 }
219
220 pub fn end_offset(&self) -> u32 {
222 self.0 * Self::SIZE + (Self::SIZE - 1)
223 }
224
225 pub fn to_page<const S1: u32>(self) -> Page<S1> {
227 Page::from_offset(self.offset())
228 }
229
230 pub fn to_iter<const S1: u32>(self) -> IntervalIterator<Page<S1>> {
243 let start = Page::<S1>::from_offset(self.offset());
244 let end = Page::<S1>::from_offset(self.end_offset());
245 unsafe { Interval::new_unchecked(start, end).iter() }
247 }
248}
249
250#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
252#[display("Tries to make page from {_0}, which must be less or equal to {_1}")]
253pub struct PageError(u32, u32);
254
255impl<const SIZE: u32> TryFrom<u32> for Page<SIZE> {
256 type Error = PageError;
257
258 fn try_from(raw: u32) -> Result<Self, Self::Error> {
259 if raw > Self::UPPER.0 {
260 Err(PageError(raw, Self::UPPER.0))
261 } else {
262 Ok(Self(raw))
263 }
264 }
265}
266
267impl<const SIZE: u32> Numerated for Page<SIZE> {
268 type Distance = u32;
269 type Bound = PagesAmount<SIZE>;
270
271 fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
272 self.0
273 .checked_add(num)
274 .and_then(|sum| sum.enclosed_by(&self.0, &other.0).then_some(Self(sum)))
275 }
276
277 fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
278 self.0
279 .checked_sub(num)
280 .and_then(|sub| sub.enclosed_by(&self.0, &other.0).then_some(Self(sub)))
281 }
282
283 fn distance(self, other: Self) -> Self::Distance {
284 self.0.abs_diff(other.0)
285 }
286}
287
288impl<const SIZE: u32> LowerBounded for Page<SIZE> {
289 fn min_value() -> Self {
290 Self(0)
291 }
292}
293
294impl<const SIZE: u32> UpperBounded for Page<SIZE> {
295 fn max_value() -> Self {
296 Self::UPPER
297 }
298}
299
300pub type WasmPage = Page<WASM_PAGE_SIZE>;
302pub type GearPage = Page<GEAR_PAGE_SIZE>;
304pub type WasmPagesAmount = PagesAmount<WASM_PAGE_SIZE>;
306pub type GearPagesAmount = PagesAmount<GEAR_PAGE_SIZE>;
308
309impl From<u16> for WasmPagesAmount {
310 fn from(value: u16) -> Self {
311 const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
312 Self(value as u32)
313 }
314}
315
316impl From<u16> for WasmPage {
317 fn from(value: u16) -> Self {
318 const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
319 Self(value as u32)
320 }
321}
322
323impl From<u16> for GearPagesAmount {
324 fn from(value: u16) -> Self {
325 const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
326 Self(value as u32)
327 }
328}
329
330impl From<u16> for GearPage {
331 fn from(value: u16) -> Self {
332 const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
333 Self(value as u32)
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use alloc::{vec, vec::Vec};
341
342 #[test]
343 fn test_page_inc() {
344 assert_eq!(WasmPage::from(10).inc(), WasmPagesAmount::from(11));
345 assert_eq!(WasmPage::UPPER.inc(), WasmPagesAmount::UPPER);
346 }
347
348 #[test]
349 fn test_page_from_offset() {
350 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE - 1), WasmPage::from(0));
351 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE), WasmPage::from(1));
352 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE + 1), WasmPage::from(1));
353 }
354
355 #[test]
356 fn test_page_offset() {
357 assert_eq!(WasmPage::from(80).offset(), 80 * WASM_PAGE_SIZE);
358 }
359
360 #[test]
361 fn test_page_end_offset() {
362 assert_eq!(
363 WasmPage::from(80).end_offset(),
364 80 * WASM_PAGE_SIZE + (WASM_PAGE_SIZE - 1)
365 );
366 }
367
368 #[test]
369 fn test_page_to_page() {
370 assert_eq!(
371 WasmPage::from(80).to_page::<GEAR_PAGE_SIZE>(),
372 GearPage::from(80 * 4)
373 );
374 }
375
376 #[test]
377 fn test_page_to_iter() {
378 assert_eq!(
379 WasmPage::from(5).to_iter().collect::<Vec<GearPage>>(),
380 vec![
381 GearPage::from(20),
382 GearPage::from(21),
383 GearPage::from(22),
384 GearPage::from(23)
385 ]
386 );
387 }
388
389 #[test]
390 fn test_pages_amount_add() {
391 let a = WasmPagesAmount::from(10);
392 let b = WasmPagesAmount::from(20);
393 assert_eq!(a.add(b), Some(WasmPagesAmount::from(30)));
394 assert_eq!(a.add(WasmPagesAmount::UPPER), None);
395 }
396
397 #[test]
398 fn test_pages_amount_to_page_number() {
399 assert_eq!(
400 WasmPagesAmount::from(10).to_page_number(),
401 Some(WasmPage::from(10))
402 );
403 assert_eq!(WasmPagesAmount::UPPER.to_page_number(), None);
404 }
405
406 #[test]
407 fn test_pages_amount_to_pages_amount() {
408 assert_eq!(
409 WasmPagesAmount::from(10).to_pages_amount::<GEAR_PAGE_SIZE>(),
410 GearPagesAmount::from(40)
411 );
412 assert_eq!(
413 GearPagesAmount::from(40).to_pages_amount::<WASM_PAGE_SIZE>(),
414 WasmPagesAmount::from(10)
415 );
416 }
417
418 #[test]
419 fn test_pages_amount_offset() {
420 assert_eq!(
421 WasmPagesAmount::from(10).offset(),
422 10 * WASM_PAGE_SIZE as u64
423 );
424 assert_eq!(WasmPagesAmount::UPPER.offset(), u32::MAX as u64 + 1);
425 }
426}
427
428#[cfg(test)]
429mod property_tests {
430 use super::*;
431 use numerated::mock::{self, IntervalAction};
432 use proptest::{
433 prelude::{Arbitrary, any},
434 proptest,
435 strategy::{BoxedStrategy, Strategy},
436 test_runner::Config as ProptestConfig,
437 };
438
439 impl<const S: u32> Arbitrary for Page<S> {
440 type Parameters = ();
441 type Strategy = BoxedStrategy<Page<S>>;
442
443 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
444 (0..=Page::<S>::UPPER.0).prop_map(Page).boxed()
445 }
446 }
447
448 proptest! {
449 #![proptest_config(ProptestConfig::with_cases(1024))]
450
451 #[test]
452 fn gear_page_numerated(x in any::<GearPage>(), y in any::<GearPage>()) {
453 mock::test_numerated(x, y);
454 }
455
456 #[test]
457 fn gear_page_interval(action in any::<IntervalAction<GearPage>>()) {
458 mock::test_interval(action);
459 }
460
461 #[test]
462 fn wasm_page_numerated(x in any::<WasmPage>(), y in any::<WasmPage>()) {
463 mock::test_numerated(x, y);
464 }
465
466 #[test]
467 fn wasm_page_interval(action in any::<IntervalAction<WasmPage>>()) {
468 mock::test_interval(action);
469 }
470 }
471
472 proptest! {
473 #![proptest_config(ProptestConfig::with_cases(64))]
474
475 #[test]
476 fn gear_page_tree((initial, actions) in mock::tree_actions::<GearPage>(0..128, 2..8)) {
477 mock::test_tree(initial, actions);
478 }
479
480 #[test]
481 fn wasm_page_tree((initial, actions) in mock::tree_actions::<WasmPage>(0..128, 10..20)) {
482 mock::test_tree(initial, actions);
483 }
484 }
485}