use binbuf::{BytesPtr, bytes_ptr, Fixed as _, Entry as _};
use crate::utils::{slice_to_array, slice_to_array_mut};
use std::{fs::File, marker::PhantomData, path::Path};
use memmap2::{MmapAsRawDesc, MmapMut, MmapOptions};
pub use header::Value as Header;
use super::OpenMode;
pub mod header;
#[derive(Debug)]
pub enum GetError {
InvalidId,
}
#[derive(Debug)]
pub enum AddError {
Io(std::io::Error),
}
#[derive(Debug)]
pub enum RemoveLastError {
Io(std::io::Error),
}
#[derive(Debug)]
pub enum SwapRemoveError {
RemoveLastError(RemoveLastError),
}
#[derive(Debug)]
pub enum CreateError {
Io(std::io::Error),
}
#[derive(Debug)]
pub enum OpenError {
Io(std::io::Error),
}
pub struct OpenConfig {
pub mode: OpenMode,
pub file: File,
pub max_margin: u64,
}
pub struct Value<E> {
next_entry_id: u64,
file: File,
file_map: MmapMut,
margin: u64,
max_margin: u64,
_marker: PhantomData<fn() -> E>
}
impl<E: binbuf::Fixed> Value<E> {
pub unsafe fn open(mode: OpenMode, file: File, max_margin: u64) -> Result<Self, OpenError> {
let header_len = Header::LEN;
if let OpenMode::New = &mode {
file.set_len(header_len as u64).map_err(OpenError::Io)?;
}
let mut file_map = MmapMut::map_mut(&file).map_err(OpenError::Io)?;
let ptr = bytes_ptr::Const::new(file_map[0 .. header_len].as_ptr(), header_len);
let next_entry_id = match mode {
OpenMode::Existing => binbuf::fixed::decode(Header::buf(ptr).next_entry_id()),
OpenMode::New => unsafe {
binbuf::fixed::encode_ptr(
bytes_ptr::Mut::from_slice(&mut file_map[0 .. header_len]),
&Header { next_entry_id: 0 }
);
0
}
};
Ok(Self {
next_entry_id,
margin: 0,
max_margin,
file,
file_map,
_marker: PhantomData
})
}
pub fn len(&self) -> u64 {
self.next_entry_id
}
pub fn last_id(&self) -> u64 {
self.len() - 1
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn entry_offset(&self, id: u64) -> usize {
Header::LEN + E::LEN * (id as usize)
}
pub fn last_entry_id(&self) -> Option<u64> {
match self.next_entry_id {
0 => None,
_ => Some(self.next_entry_id - 1),
}
}
fn header_buf(&self) -> binbuf::BufConst<Header> {
let len = Header::LEN;
let ptr = unsafe { bytes_ptr::Const::from_slice(self.file_map.get_unchecked(0 .. len)) };
unsafe { Header::buf(ptr) }
}
fn header_buf_mut(&mut self) -> binbuf::BufMut<Header> {
let len = Header::LEN;
let ptr = unsafe { bytes_ptr::Mut::from_slice(self.file_map.get_unchecked_mut(0 .. len)) };
unsafe { Header::buf(ptr) }
}
fn set_next_entry_id(&mut self, value: u64) {
self.next_entry_id = value;
let v = self.next_entry_id;
v.encode(self.header_buf_mut().next_entry_id());
}
pub unsafe fn buf_unchecked(&self, id: u64) -> binbuf::BufConst<E> {
let offset = self.entry_offset(id);
let ptr = bytes_ptr::Const::new(self.file_map.get_unchecked(offset .. offset + E::LEN).as_ptr(), E::LEN);
E::buf(ptr)
}
pub unsafe fn buf_mut_unchecked(&mut self, id: u64) -> binbuf::BufMut<E> {
let offset = self.entry_offset(id);
let ptr = bytes_ptr::Mut::new(self.file_map.get_unchecked_mut(offset .. offset + E::LEN).as_mut_ptr(), E::LEN);
E::buf(ptr)
}
pub fn last_buf(&self) -> Option<binbuf::BufConst<E>> {
if self.is_empty() {
None
} else {
Some(unsafe { self.buf_unchecked(self.len() - 1) })
}
}
pub fn is_id_valid(&self, id: u64) -> bool {
self.next_entry_id > id
}
pub fn buf(&self, id: u64) -> binbuf::BufConst<E> {
if self.is_id_valid(id) {
unsafe { self.buf_unchecked(id) }
} else {
panic!("Id is invalid: {id}")
}
}
pub fn buf_mut(&mut self, id: u64) -> binbuf::BufMut<E> {
if self.is_id_valid(id) {
unsafe { self.buf_mut_unchecked(id) }
} else {
panic!("Id is invalid: {id}")
}
}
pub fn add(&mut self, entry: impl binbuf::fixed::Readable<E>) -> Result<u64, AddError> {
let id = self.next_entry_id;
if self.margin == 0 {
let new_len = self.entry_offset(id + self.max_margin + 2);
self.file.set_len(new_len as u64).map_err(AddError::Io)?;
self.file_map = unsafe { MmapOptions::new().len(new_len).map_mut(&self.file).map_err(AddError::Io)? };
self.margin = self.max_margin + 1;
}
self.margin -= 1;
entry.write_to(unsafe { self.buf_mut_unchecked(id) });
self.set_next_entry_id(self.next_entry_id + 1);
Ok(id)
}
pub fn remove_last(&mut self) -> Result<(), RemoveLastError> {
let id = self.next_entry_id;
if self.margin >= self.max_margin {
let new_len = self.entry_offset(id);
self.file.set_len(new_len as u64).map_err(RemoveLastError::Io)?;
self.file_map = unsafe { MmapOptions::new().len(new_len).map_mut(&self.file).map_err(RemoveLastError::Io)? };
self.margin = 0;
}
self.margin += 1;
self.set_next_entry_id(id - 1);
Ok(())
}
pub fn remove_if_last(&mut self, id: u64) -> Result<bool, RemoveLastError> {
if id == self.last_id() {
self.remove_last()?;
Ok(false)
} else {
Ok(true)
}
}
pub fn all_ids(&self) -> impl Iterator<Item = u64> {
0 .. self.next_entry_id
}
pub unsafe fn copy(
&mut self,
src_id: u64,
dst_id: u64,
) {
let mut dst = self.buf_mut_unchecked(dst_id);
let src = self.buf_unchecked(src_id);
binbuf::fixed::buf_copy_to::<E>(src, dst);
}
pub unsafe fn swap(
&mut self,
a_id: u64,
b_id: u64,
) {
if a_id != b_id {
let mut a = self.buf_mut_unchecked(a_id);
let mut b = self.buf_mut_unchecked(b_id);
binbuf::fixed::buf_swap::<E>(a, b);
}
}
pub unsafe fn swap_remove(&mut self, id: u64) -> Result<(), SwapRemoveError> {
if let Some(last_entry_id) = self.last_entry_id() {
self.swap(id, last_entry_id);
}
self.remove_last().map_err(SwapRemoveError::RemoveLastError)?;
Ok(())
}
pub fn set(&mut self, id: u64, value: impl binbuf::fixed::Readable<E>) {
if self.is_id_valid(id) {
unsafe { value.write_to(self.buf_mut_unchecked(id)) }
} else {
panic!("Invalid id: {id}");
}
}
}
impl<E: binbuf::fixed::Decode> Value<E> {
pub fn get(&self, id: u64) -> E {
E::decode(self.buf(id))
}
}