use super::error::{CollectionError as Error, Result as CollectionResult, ensure};
use super::tree::Tree;
use super::{Memory, Storable};
use crate::DefaultMemoryImpl;
use crate::{RuntimeGrowError, RuntimeMemory, WASM_PAGE_SIZE_BYTES as PAGE_BYTES};
use std::{marker::PhantomData, ops::Range, ops::RangeBounds, rc::Rc};
const MAX_PAGES: u64 = 4_194_304;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum RegionGrowError {
#[error("requested region extent {minimum} exceeds {maximum} pages")]
LimitExceeded {
minimum: u64,
maximum: u64,
},
#[error("{0}")]
Runtime(#[source] RuntimeGrowError),
}
pub struct BTreeMapRegion<K: Storable + Ord + Clone, V: Storable, M: Memory = DefaultMemoryImpl> {
memory: RegionMemory<M>,
record: PhantomData<(K, V)>,
}
impl<K: Storable + Ord + Clone, V: Storable, M: Memory> BTreeMapRegion<K, V, M> {
#[must_use]
pub fn new(memory: Rc<RuntimeMemory<M>>, pages: Range<u64>) -> Self {
assert!(pages.start < pages.end && pages.end <= MAX_PAGES);
Self {
memory: RegionMemory { memory, pages },
record: PhantomData,
}
}
#[must_use]
pub fn allocated_pages(&self) -> u64 {
(&self.memory).size()
}
pub fn read(&self, offset: u64, dst: &mut [u8]) {
(&self.memory).read(offset, dst);
}
pub fn initialize(&mut self) -> CollectionResult<()> {
ensure(self.allocated_pages() == 0, Error::NotEmpty)?;
Tree::<K, V, _>::reset(&self.memory)?;
Ok(())
}
pub fn reserve_pages(&mut self, minimum: u64) -> Result<(), RegionGrowError> {
self.memory.reserve_pages(minimum)
}
pub fn get(&self, key: &K) -> CollectionResult<Option<V>> {
self.current_map()?.get(key)
}
pub fn insert(&mut self, key: K, value: V) -> CollectionResult<Option<V>> {
self.current_map()?.insert(key, value)
}
pub fn remove(&mut self, key: &K) -> CollectionResult<Option<V>> {
self.current_map()?.remove(key)
}
pub fn len(&self) -> CollectionResult<u64> {
Ok(self.current_map()?.len())
}
pub fn is_empty(&self) -> CollectionResult<bool> {
Ok(self.current_map()?.len() == 0)
}
pub fn keys_range(
&self,
bounds: impl RangeBounds<K>,
) -> CollectionResult<std::vec::IntoIter<K>> {
self.keys_range_limited(bounds, usize::MAX)
}
pub fn keys_range_limited(
&self,
bounds: impl RangeBounds<K>,
limit: usize,
) -> CollectionResult<std::vec::IntoIter<K>> {
if limit == 0 {
return Ok(Vec::new().into_iter());
}
Ok(self
.current_map()?
.keys_range(bounds)
.take(limit)
.collect::<CollectionResult<Vec<_>>>()?
.into_iter())
}
fn current_map(&self) -> CollectionResult<Tree<K, V, &RegionMemory<M>>> {
Tree::load(&self.memory)
}
}
struct RegionMemory<M: Memory> {
memory: Rc<RuntimeMemory<M>>,
pages: Range<u64>,
}
impl<M: Memory> RegionMemory<M> {
fn reserve_pages(&self, minimum: u64) -> Result<(), RegionGrowError> {
let maximum = self.pages.end - self.pages.start;
if minimum > maximum {
return Err(RegionGrowError::LimitExceeded { minimum, maximum });
}
if minimum > self.size() {
let additional = self.pages.start + minimum - self.memory.size();
self.memory
.as_ref()
.grow(additional)
.map_err(RegionGrowError::Runtime)?;
}
Ok(())
}
fn check_io(&self, offset: u64, count: usize) {
let extent = self.size() * PAGE_BYTES;
assert!(offset <= extent && count as u64 <= extent - offset);
}
}
impl<M: Memory> Memory for &RegionMemory<M> {
fn size(&self) -> u64 {
self.memory.size().clamp(self.pages.start, self.pages.end) - self.pages.start
}
fn grow(&self, delta: u64) -> i64 {
let previous = self.size();
let Some(minimum) = previous.checked_add(delta) else {
return -1;
};
if self.reserve_pages(minimum).is_ok() {
i64::try_from(previous).unwrap()
} else {
-1
}
}
fn read(&self, offset: u64, dst: &mut [u8]) {
self.check_io(offset, dst.len());
if !dst.is_empty() {
self.memory
.read(self.pages.start * PAGE_BYTES + offset, dst);
}
}
fn write(&self, offset: u64, src: &[u8]) {
self.check_io(offset, src.len());
if !src.is_empty() {
self.memory
.write(self.pages.start * PAGE_BYTES + offset, src);
}
}
}