use crate::{EntityPtr, Repo};
use alloc::collections::BTreeSet;
use core::any::Any;
use core::fmt;
use thiserror::Error;
pub struct PreallocTx<'a> {
repo: &'a mut Repo,
preallocations: BTreeSet<crate::EntityRecord>,
}
impl<'a> fmt::Debug for PreallocTx<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PreAllocTx {{..}}")
}
}
#[derive(Error)]
pub enum PreallocTxError<T: Any, R = Repo> {
#[error("invalid preallocation entity pointer")]
InvalidPreallocPtr(EntityPtr<T, R>),
#[error("invalid preallocation entity pointer with value")]
InvalidPreallocPtrWithValue(EntityPtr<T, R>, T),
#[error("preallocation is already occupied")]
PreallocOccupied(EntityPtr<T, R>),
#[error("internal error")]
RepoError(#[from] crate::Error),
}
impl<T: Any, R> fmt::Debug for PreallocTxError<T, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PreallocTxError::InvalidPreallocPtr(p) => write!(f, "InvalidPreallocPtr({:?})", p),
PreallocTxError::InvalidPreallocPtrWithValue(p, _) => {
write!(f, "InvalidPreallocPtr({:?}, <value>)", p)
}
PreallocTxError::PreallocOccupied(p) => write!(f, "PreallocOccupied({:?})", p),
PreallocTxError::RepoError(e) => write!(f, "RepoError({:?})", e),
}
}
}
impl<'a> PreallocTx<'a> {
pub(crate) fn new(repo: &'a mut Repo) -> Self {
PreallocTx {
repo,
preallocations: Default::default(),
}
}
pub fn repo(&self) -> &Repo {
self.repo
}
pub fn repo_mut(&mut self) -> &mut Repo {
self.repo
}
pub fn preallocate<T: Any>(&mut self) -> EntityPtr<T> {
let v = self.repo.preallocate_entity::<T>();
assert!(
!self.preallocations.contains(&v.record),
"unusable preallocation retrieved"
);
self.preallocations.insert(v.record);
v
}
pub fn init_preallocation<T: Any, R>(
&mut self,
ptr: EntityPtr<T, R>,
value: T,
) -> Result<(), PreallocTxError<T, R>> {
if let Some(_) = self.preallocations.take(&ptr.record) {
if !self.repo.validate_record_type::<T>(ptr.record) {
return Err(PreallocTxError::InvalidPreallocPtr(ptr));
}
if let Err((_, v)) = self.repo.init_preallocate_entity(ptr.record, value) {
return Err(PreallocTxError::InvalidPreallocPtrWithValue(ptr, v));
}
Ok(())
} else {
Err(PreallocTxError::InvalidPreallocPtrWithValue(ptr, value))
}
}
pub fn cancel_preallocation<T: Any, R>(
&mut self,
ptr: EntityPtr<T, R>,
) -> Result<(), PreallocTxError<T, R>> {
if self.preallocations.contains(&ptr.record) {
if !self.repo.validate_record_type::<T>(ptr.record) {
return Err(PreallocTxError::InvalidPreallocPtr(ptr));
}
if !self.repo.cancel_preallocate_entity(ptr.record)? {
return Err(PreallocTxError::PreallocOccupied(ptr));
}
self.preallocations.remove(&ptr.record);
Ok(())
} else {
Err(PreallocTxError::InvalidPreallocPtr(ptr))
}
}
}
impl<'a> Drop for PreallocTx<'a> {
fn drop(&mut self) {
use core::mem;
let mut preallocations = Default::default();
mem::swap(&mut preallocations, &mut self.preallocations);
for record in preallocations {
let _ = self.repo.cancel_preallocate_entity(record);
}
}
}
impl Repo {
pub fn transaction_preallocate<TxFn, T, E>(&mut self, transaction: TxFn) -> Result<T, E>
where
TxFn: FnOnce(&mut PreallocTx<'_>) -> Result<T, E>,
{
let mut prealloc_tx = PreallocTx::new(self);
(transaction)(&mut prealloc_tx)
}
}