1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! The versioning strategy, see [`Version`] for details

/// The versioning strategy
///
/// # Slot Exhaustion
///
/// Arenas store a version alongside each slot they have. If a slot's version is
/// exhausted, then that slot will *never* be reused. For [`DefaultVersion`]
/// this will only happen after about 2 billion insertion/deletion pairs *per slot*,
/// so it shouldn't be an issue. However, for smaller version types like [`TinyVersion`]
/// each slot will exhaust after only 127 insertion/deletion pairs per slot.
///
/// You can avoid version exahustion by using [`Unversioned`], but this suffers from the
/// ABA problem.
///
/// # ABA problem
///
/// [Wikipedia](https://en.wikipedia.org/wiki/ABA_problem)
///
/// Consider this sequence of operations:
///
/// ``` should_panic
/// use pui_arena::version::UnversionedFull;
/// # use pui_arena::Key;
/// # let mut arena = pui_arena::base::sparse::Arena::<_, (), pui_arena::version::Unversioned>::INIT;
/// // insert 0 at index 0, arena = [(version: Unversioned::Full, Some(0))]
/// let a: Key<usize, UnversionedFull> = arena.insert(0);
/// // remove at index 0, arena = [(version: Unversioned::Empty, None)]
/// arena.remove(a);
/// // insert 10 at index 0, arena = [(version: Unversioned::Full, Some(10))]
/// let b: Key<usize, UnversionedFull> = arena.insert(10);
/// // get the value at index 0
/// assert_eq!(arena.get(a), None);
/// ```
///
/// because `arena` is using `Unversioned` slots, even though key `a` was removed from the
/// `arena`, we can still access it! This is fundementally what the ABA problem is. Even
/// though a key was removed, the arena can't distinguish stale keys so it allows access
/// to the new values when it shouldn't.
///
/// Depending on your problem space, this may or may not be a problem you need to sove.
/// If it is a problem you need to solve, then you will need to deal with version exhaustion
/// in some way, because there are never going to be an infinite number of versions. `pui-arena`
/// handles this for you by "leaking" slots with exhausted versions. These slots will not
/// be reused, but will be deallocated once the `Arena` drops.
pub unsafe trait Version: Copy {
    /// Represents a full version
    type Save: Copy;

    /// The initial empty version
    const EMPTY: Self;

    /// Convert an full version to a empty version
    ///
    /// returns `Err` if there are no more versions
    ///
    /// # Safety
    ///
    /// `mark_empty` can only be called on a full version
    unsafe fn mark_empty(self) -> Result<Self, Self>;

    /// Convert an empty version to a full version
    ///
    /// # Safety
    ///
    /// `mark_full` can only be called on a empty version
    unsafe fn mark_full(self) -> Self;

    /// Check if the version is exhausted
    fn is_exhausted(&self) -> bool;
    /// Check if the version is empty
    fn is_empty(self) -> bool { !self.is_full() }
    /// Check if the version is full
    fn is_full(self) -> bool;

    /// Save the current version
    ///
    /// # Safety
    ///
    /// `save` can only be called on a full version
    unsafe fn save(self) -> Self::Save;

    /// Check if the saved version matches the current version
    ///
    /// In particular, this can only be true if the current version is full
    /// and may not be true if there was a call to `mark_empty` in since the
    /// save was created.
    fn equals_saved(self, saved: Self::Save) -> bool;
}

/// The default versioning strategy, that's backed by a [`u32`], that avoids the
/// [`ABA problem`](https://en.wikipedia.org/wiki/ABA_problem)
///
/// This can track up to 2^31 insertion-deletion pairs before exhaustion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DefaultVersion(u32);
/// `<DefaultVersion as Version>::Save`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SavedDefaultVersion(u32);

unsafe impl Version for DefaultVersion {
    type Save = SavedDefaultVersion;

    const EMPTY: Self = Self(1);

    unsafe fn mark_empty(self) -> Result<Self, Self> {
        let next = Self(self.0 | 1);
        match self.0.checked_add(2) {
            Some(_) => Ok(next),
            None => Err(next),
        }
    }

    fn is_exhausted(&self) -> bool { self.0 == u32::MAX }

    unsafe fn mark_full(self) -> Self { Self(self.0.wrapping_add(1)) }

    fn is_full(self) -> bool { self.0 & 1 == 0 }

    unsafe fn save(self) -> Self::Save { SavedDefaultVersion(self.0) }

    fn equals_saved(self, saved: Self::Save) -> bool { self.0 == saved.0 }
}

/// A small versioning strategy, that's backed by a [`u8`], that avoids the
/// [`ABA problem`](https://en.wikipedia.org/wiki/ABA_problem)
///
/// This can track up to 2^7 insertion-deletion pairs before exhaustion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TinyVersion(u8);
/// `<TinyVersion as Version>::Save`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SavedTinyVersion(u8);

unsafe impl Version for TinyVersion {
    type Save = SavedTinyVersion;

    const EMPTY: Self = Self(1);

    unsafe fn mark_empty(self) -> Result<Self, Self> {
        let next = Self(self.0 | 1);
        match self.0.checked_add(2) {
            Some(_) => Ok(next),
            None => Err(next),
        }
    }

    unsafe fn mark_full(self) -> Self { Self(self.0.wrapping_add(1)) }

    fn is_exhausted(&self) -> bool { self.0 == u8::MAX }

    fn is_full(self) -> bool { self.0 & 1 != 0 }

    unsafe fn save(self) -> Self::Save { SavedTinyVersion(self.0) }

    fn equals_saved(self, saved: Self::Save) -> bool { self.0 == saved.0 }
}

/// A versioning strategy that doesn't actually track versions,
/// just the state of the container. This strategy can fall prey
/// to the [`ABA problem`](https://en.wikipedia.org/wiki/ABA_problem)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unversioned {
    /// The contianer is empty
    Empty,
    /// The contianer is full
    Full,
}
/// `<UnversionedFull as Version>::Save`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnversionedFull(());

unsafe impl Version for Unversioned {
    type Save = UnversionedFull;

    const EMPTY: Self = Self::Empty;

    unsafe fn mark_empty(self) -> Result<Self, Self> { Ok(Self::Empty) }

    unsafe fn mark_full(self) -> Self { Self::Full }

    fn is_exhausted(&self) -> bool { false }

    fn is_full(self) -> bool { matches!(self, Self::Full) }

    unsafe fn save(self) -> Self::Save { UnversionedFull(()) }

    fn equals_saved(self, UnversionedFull(()): Self::Save) -> bool { self.is_full() }
}