repository/
prealloc_tx.rs

1//! Transaction for `Repo` that supports preallocation.
2//!
3//! With `PreallocTx`, `EntityPtr`s can be preallocated before the actual data
4//! is ready to be inserted into the `Repo`.
5//!
6//! Preallocated `EntityPtr`s are created using `PreallocTx::preallocate` and initialized
7//! with `PreallocTx::init_preallocation`. If the `EntityPtr` is not initialized, the
8//! preallocation will be cancelled when `PreallocTx` is dropped, as if
9//! `PreallocTx::cancel_preallocation` is called.
10//!
11//! This is especially useful for building data structures with circular references
12//! but without the ability to represent the intermediate invalid state during construction.
13
14use crate::{EntityPtr, Repo};
15use alloc::collections::BTreeSet;
16use core::any::Any;
17use core::fmt;
18use thiserror::Error;
19
20/// Transaction for `Repo` that supports preallocation.
21///
22/// See the [module documentation] for more details.
23///
24/// [module documentation]: index.html
25pub struct PreallocTx<'a> {
26    repo: &'a mut Repo,
27    preallocations: BTreeSet<crate::EntityRecord>,
28}
29
30impl<'a> fmt::Debug for PreallocTx<'a> {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "PreAllocTx {{..}}")
33    }
34}
35
36#[derive(Error)]
37/// Errors for `PreallocTxError` operations.
38pub enum PreallocTxError<T: Any, R = Repo> {
39    #[error("invalid preallocation entity pointer")]
40    /// Preallocation entity pointer is invalid.
41    InvalidPreallocPtr(EntityPtr<T, R>),
42    #[error("invalid preallocation entity pointer with value")]
43    /// A value is passed when preallocation entity pointer is invalid.
44    InvalidPreallocPtrWithValue(EntityPtr<T, R>, T),
45    #[error("preallocation is already occupied")]
46    /// Preallocation entity pointer is already occupied by another value.
47    PreallocOccupied(EntityPtr<T, R>),
48    #[error("internal error")]
49    /// `Repo` internal error is generated during operation.
50    RepoError(#[from] crate::Error),
51}
52
53impl<T: Any, R> fmt::Debug for PreallocTxError<T, R> {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            PreallocTxError::InvalidPreallocPtr(p) => write!(f, "InvalidPreallocPtr({:?})", p),
57            PreallocTxError::InvalidPreallocPtrWithValue(p, _) => {
58                write!(f, "InvalidPreallocPtr({:?}, <value>)", p)
59            }
60            PreallocTxError::PreallocOccupied(p) => write!(f, "PreallocOccupied({:?})", p),
61            PreallocTxError::RepoError(e) => write!(f, "RepoError({:?})", e),
62        }
63    }
64}
65
66impl<'a> PreallocTx<'a> {
67    pub(crate) fn new(repo: &'a mut Repo) -> Self {
68        PreallocTx {
69            repo,
70            preallocations: Default::default(),
71        }
72    }
73
74    /// Retrieves a reference to the underlying `Repo`.
75    pub fn repo(&self) -> &Repo {
76        self.repo
77    }
78
79    /// Retrieves a mutable reference to the underlying `Repo`.
80    pub fn repo_mut(&mut self) -> &mut Repo {
81        self.repo
82    }
83
84    /// Preallocates an `EntityPtr` with type `T`.
85    pub fn preallocate<T: Any>(&mut self) -> EntityPtr<T> {
86        let v = self.repo.preallocate_entity::<T>();
87        assert!(
88            !self.preallocations.contains(&v.record),
89            "unusable preallocation retrieved"
90        );
91        self.preallocations.insert(v.record);
92        v
93    }
94
95    /// Initializes a preallocated `EntityPtr` with value.
96    pub fn init_preallocation<T: Any, R>(
97        &mut self,
98        ptr: EntityPtr<T, R>,
99        value: T,
100    ) -> Result<(), PreallocTxError<T, R>> {
101        if let Some(_) = self.preallocations.take(&ptr.record) {
102            if !self.repo.validate_record_type::<T>(ptr.record) {
103                return Err(PreallocTxError::InvalidPreallocPtr(ptr));
104            }
105            if let Err((_, v)) = self.repo.init_preallocate_entity(ptr.record, value) {
106                return Err(PreallocTxError::InvalidPreallocPtrWithValue(ptr, v));
107            }
108            Ok(())
109        } else {
110            Err(PreallocTxError::InvalidPreallocPtrWithValue(ptr, value))
111        }
112    }
113
114    /// Cancel a preallocated `EntityPtr` and make it available for further use.
115    ///
116    /// Since all unused preallocated entity pointers are automatically recycled when `PreallocTx`
117    /// is dropped, so this is seldom necessary.
118    pub fn cancel_preallocation<T: Any, R>(
119        &mut self,
120        ptr: EntityPtr<T, R>,
121    ) -> Result<(), PreallocTxError<T, R>> {
122        if self.preallocations.contains(&ptr.record) {
123            if !self.repo.validate_record_type::<T>(ptr.record) {
124                return Err(PreallocTxError::InvalidPreallocPtr(ptr));
125            }
126            if !self.repo.cancel_preallocate_entity(ptr.record)? {
127                return Err(PreallocTxError::PreallocOccupied(ptr));
128            }
129            self.preallocations.remove(&ptr.record);
130            Ok(())
131        } else {
132            Err(PreallocTxError::InvalidPreallocPtr(ptr))
133        }
134    }
135}
136
137impl<'a> Drop for PreallocTx<'a> {
138    fn drop(&mut self) {
139        use core::mem;
140        let mut preallocations = Default::default();
141        mem::swap(&mut preallocations, &mut self.preallocations);
142        for record in preallocations {
143            let _ = self.repo.cancel_preallocate_entity(record);
144        }
145    }
146}
147
148impl Repo {
149    /// Executes a transaction with preallocation support
150    ///
151    /// See the documentation of `PreallocTx` for more information.
152    pub fn transaction_preallocate<TxFn, T, E>(&mut self, transaction: TxFn) -> Result<T, E>
153    where
154        TxFn: FnOnce(&mut PreallocTx<'_>) -> Result<T, E>,
155    {
156        let mut prealloc_tx = PreallocTx::new(self);
157        (transaction)(&mut prealloc_tx)
158    }
159}