eyeball_im/vector/
transaction.rs

1use std::{fmt, mem, ops};
2
3use imbl::Vector;
4
5use crate::vector::OneOrManyDiffs;
6
7use super::{entry::EntryIndex, BroadcastMessage, ObservableVector, VectorDiff};
8
9/// A transaction that allows making multiple updates to an `ObservableVector`
10/// as an atomic unit.
11///
12/// For updates from the transaction to have affect, it has to be finalized with
13/// [`.commit()`](Self::commit). If the transaction is dropped without that
14/// method being called, the updates will be discarded.
15pub struct ObservableVectorTransaction<'o, T: Clone> {
16    // The observable vector being modified, only modified on commit.
17    inner: &'o mut ObservableVector<T>,
18    // A clone of the observable's values, what the methods operate on until commit.
19    values: Vector<T>,
20    // The batched updates, to be sent to subscribers on commit.
21    batch: Vec<VectorDiff<T>>,
22}
23
24impl<'o, T: Clone + 'static> ObservableVectorTransaction<'o, T> {
25    pub(super) fn new(inner: &'o mut ObservableVector<T>) -> Self {
26        let values = inner.values.clone();
27        Self { inner, values, batch: Vec::new() }
28    }
29
30    /// Commit this transaction, persisting the changes and notifying
31    /// subscribers.
32    pub fn commit(mut self) {
33        #[cfg(feature = "tracing")]
34        tracing::debug!("commit");
35
36        self.inner.values = mem::take(&mut self.values);
37
38        if self.batch.is_empty() {
39            #[cfg(feature = "tracing")]
40            tracing::trace!(
41                target: "eyeball_im::vector::broadcast",
42                "Skipping broadcast of empty list of diffs"
43            );
44        } else {
45            let diffs = OneOrManyDiffs::Many(mem::take(&mut self.batch));
46            let msg = BroadcastMessage { diffs, state: self.inner.values.clone() };
47            let _num_receivers = self.inner.sender.send(msg).unwrap_or(0);
48            #[cfg(feature = "tracing")]
49            tracing::debug!(
50                target: "eyeball_im::vector::broadcast",
51                "New observable value broadcast to {_num_receivers} receivers"
52            );
53        }
54    }
55
56    /// Roll back all changes made using this transaction so far.
57    ///
58    /// Same as dropping the transaction and starting a new one, semantically.
59    pub fn rollback(&mut self) {
60        #[cfg(feature = "tracing")]
61        tracing::debug!("rollback (explicit)");
62
63        self.values = self.inner.values.clone();
64        self.batch.clear();
65    }
66
67    /// Append the given elements at the end of the `Vector` and notify
68    /// subscribers.
69    pub fn append(&mut self, values: Vector<T>) {
70        #[cfg(feature = "tracing")]
71        tracing::debug!(
72            target: "eyeball_im::vector::transaction::update",
73            "append(len = {})", values.len()
74        );
75
76        self.values.append(values.clone());
77        self.add_to_batch(VectorDiff::Append { values });
78    }
79
80    /// Clear out all of the elements in this `Vector` and notify subscribers.
81    pub fn clear(&mut self) {
82        #[cfg(feature = "tracing")]
83        tracing::debug!(target: "eyeball_im::vector::transaction::update", "clear");
84
85        self.values.clear();
86        self.batch.clear(); // All previous batched updates are irrelevant now
87        self.add_to_batch(VectorDiff::Clear);
88    }
89
90    /// Add an element at the front of the list and notify subscribers.
91    pub fn push_front(&mut self, value: T) {
92        #[cfg(feature = "tracing")]
93        tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_front");
94
95        self.values.push_front(value.clone());
96        self.add_to_batch(VectorDiff::PushFront { value });
97    }
98
99    /// Add an element at the back of the list and notify subscribers.
100    pub fn push_back(&mut self, value: T) {
101        #[cfg(feature = "tracing")]
102        tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_back");
103
104        self.values.push_back(value.clone());
105        self.add_to_batch(VectorDiff::PushBack { value });
106    }
107
108    /// Remove the first element, notify subscribers and return the element.
109    ///
110    /// If there are no elements, subscribers will not be notified and this
111    /// method will return `None`.
112    pub fn pop_front(&mut self) -> Option<T> {
113        let value = self.values.pop_front();
114        if value.is_some() {
115            #[cfg(feature = "tracing")]
116            tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_front");
117
118            self.add_to_batch(VectorDiff::PopFront);
119        }
120        value
121    }
122
123    /// Remove the last element, notify subscribers and return the element.
124    ///
125    /// If there are no elements, subscribers will not be notified and this
126    /// method will return `None`.
127    pub fn pop_back(&mut self) -> Option<T> {
128        let value = self.values.pop_back();
129        if value.is_some() {
130            #[cfg(feature = "tracing")]
131            tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_back");
132
133            self.add_to_batch(VectorDiff::PopBack);
134        }
135        value
136    }
137
138    /// Insert an element at the given position and notify subscribers.
139    ///
140    /// # Panics
141    ///
142    /// Panics if `index > len`.
143    #[track_caller]
144    pub fn insert(&mut self, index: usize, value: T) {
145        let len = self.values.len();
146        if index <= len {
147            #[cfg(feature = "tracing")]
148            tracing::debug!(
149                target: "eyeball_im::vector::transaction::update",
150                "insert(index = {index})"
151            );
152
153            self.values.insert(index, value.clone());
154            self.add_to_batch(VectorDiff::Insert { index, value });
155        } else {
156            panic!("index out of bounds: the length is {len} but the index is {index}");
157        }
158    }
159
160    /// Replace the element at the given position, notify subscribers and return
161    /// the previous element at that position.
162    ///
163    /// # Panics
164    ///
165    /// Panics if `index >= len`.
166    #[track_caller]
167    pub fn set(&mut self, index: usize, value: T) -> T {
168        let len = self.values.len();
169        if index < len {
170            #[cfg(feature = "tracing")]
171            tracing::debug!(
172                target: "eyeball_im::vector::transaction::update",
173                "set(index = {index})"
174            );
175
176            let old_value = self.values.set(index, value.clone());
177            self.add_to_batch(VectorDiff::Set { index, value });
178            old_value
179        } else {
180            panic!("index out of bounds: the length is {len} but the index is {index}");
181        }
182    }
183
184    /// Remove the element at the given position, notify subscribers and return
185    /// the element.
186    ///
187    /// # Panics
188    ///
189    /// Panics if `index >= len`.
190    #[track_caller]
191    pub fn remove(&mut self, index: usize) -> T {
192        let len = self.values.len();
193        if index < len {
194            #[cfg(feature = "tracing")]
195            tracing::debug!(
196                target: "eyeball_im::vector::transaction::update",
197                "remove(index = {index})"
198            );
199
200            let value = self.values.remove(index);
201            self.add_to_batch(VectorDiff::Remove { index });
202            value
203        } else {
204            panic!("index out of bounds: the length is {len} but the index is {index}");
205        }
206    }
207
208    /// Truncate the vector to `len` elements and notify subscribers.
209    ///
210    /// Does nothing if `len` is greater or equal to the vector's current
211    /// length.
212    pub fn truncate(&mut self, len: usize) {
213        if len < self.len() {
214            #[cfg(feature = "tracing")]
215            tracing::debug!(target: "eyeball_im::vector::update", "truncate(len = {len})");
216
217            self.values.truncate(len);
218            self.add_to_batch(VectorDiff::Truncate { length: len });
219        }
220    }
221
222    /// Gets an entry for the given index through which only the element at that
223    /// index alone can be updated or removed.
224    ///
225    /// # Panics
226    ///
227    /// Panics if `index >= len`.
228    #[track_caller]
229    pub fn entry(&mut self, index: usize) -> ObservableVectorTransactionEntry<'_, 'o, T> {
230        let len = self.values.len();
231        if index < len {
232            ObservableVectorTransactionEntry::new(self, index)
233        } else {
234            panic!("index out of bounds: the length is {len} but the index is {index}");
235        }
236    }
237
238    /// Call the given closure for every element in this `ObservableVector`,
239    /// with an entry struct that allows updating or removing that element.
240    ///
241    /// Iteration happens in order, i.e. starting at index `0`.
242    pub fn for_each(&mut self, mut f: impl FnMut(ObservableVectorTransactionEntry<'_, 'o, T>)) {
243        let mut entries = self.entries();
244        while let Some(entry) = entries.next() {
245            f(entry);
246        }
247    }
248
249    /// Get an iterator over all the entries in this `ObservableVector`.
250    ///
251    /// This is a more flexible, but less convenient alternative to
252    /// [`for_each`][Self::for_each]. If you don't need to use special control
253    /// flow like `.await` or `break` when iterating, it's recommended to use
254    /// that method instead.
255    ///
256    /// Because `std`'s `Iterator` trait does not allow iterator items to borrow
257    /// from the iterator itself, the returned typed does not implement the
258    /// `Iterator` trait and can thus not be used with a `for` loop. Instead,
259    /// you have to call its `.next()` method directly, as in:
260    ///
261    /// ```rust
262    /// # use eyeball_im::ObservableVector;
263    /// # let mut ob = ObservableVector::<u8>::new();
264    /// let mut entries = ob.entries();
265    /// while let Some(entry) = entries.next() {
266    ///     // use entry
267    /// }
268    /// ```
269    pub fn entries(&mut self) -> ObservableVectorTransactionEntries<'_, 'o, T> {
270        ObservableVectorTransactionEntries::new(self)
271    }
272
273    fn add_to_batch(&mut self, diff: VectorDiff<T>) {
274        if self.inner.sender.receiver_count() != 0 {
275            self.batch.push(diff);
276        }
277    }
278}
279
280impl<T> fmt::Debug for ObservableVectorTransaction<'_, T>
281where
282    T: Clone + fmt::Debug,
283{
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        f.debug_struct("ObservableVectorWriteGuard")
286            .field("values", &self.values)
287            .finish_non_exhaustive()
288    }
289}
290
291// Note: No DerefMut because all mutating must go through inherent methods that
292// notify subscribers
293impl<T: Clone> ops::Deref for ObservableVectorTransaction<'_, T> {
294    type Target = Vector<T>;
295
296    fn deref(&self) -> &Self::Target {
297        &self.values
298    }
299}
300
301impl<T: Clone> Drop for ObservableVectorTransaction<'_, T> {
302    fn drop(&mut self) {
303        #[cfg(feature = "tracing")]
304        if !self.batch.is_empty() {
305            tracing::debug!("rollback (drop)");
306        }
307    }
308}
309
310/// A handle to a single value in an [`ObservableVector`], obtained from a
311/// transaction.
312pub struct ObservableVectorTransactionEntry<'a, 'o, T: Clone> {
313    inner: &'a mut ObservableVectorTransaction<'o, T>,
314    index: EntryIndex<'a>,
315}
316
317impl<'a, 'o, T> ObservableVectorTransactionEntry<'a, 'o, T>
318where
319    T: Clone + 'static,
320{
321    pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>, index: usize) -> Self {
322        Self { inner, index: EntryIndex::Owned(index) }
323    }
324
325    fn new_borrowed(
326        inner: &'a mut ObservableVectorTransaction<'o, T>,
327        index: &'a mut usize,
328    ) -> Self {
329        Self { inner, index: EntryIndex::Borrowed(index) }
330    }
331
332    /// Get the index of the element this `ObservableVectorEntry` refers to.
333    pub fn index(this: &Self) -> usize {
334        this.index.value()
335    }
336
337    /// Replace the given element, notify subscribers and return the previous
338    /// element.
339    pub fn set(this: &mut Self, value: T) -> T {
340        this.inner.set(this.index.value(), value)
341    }
342
343    /// Remove the given element, notify subscribers and return the element.
344    pub fn remove(mut this: Self) -> T {
345        this.inner.remove(this.index.make_owned())
346    }
347}
348
349impl<T> fmt::Debug for ObservableVectorTransactionEntry<'_, '_, T>
350where
351    T: Clone + fmt::Debug,
352{
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        let index = self.index.value();
355        f.debug_struct("ObservableVectorEntry")
356            .field("item", &self.inner[index])
357            .field("index", &index)
358            .finish()
359    }
360}
361
362impl<T: Clone> ops::Deref for ObservableVectorTransactionEntry<'_, '_, T> {
363    type Target = T;
364
365    fn deref(&self) -> &Self::Target {
366        &self.inner[self.index.value()]
367    }
368}
369
370impl<T: Clone> Drop for ObservableVectorTransactionEntry<'_, '_, T> {
371    fn drop(&mut self) {
372        // If there is an association with an externally-stored index, that
373        // index must be incremented on drop. This allows an external iterator
374        // that produces ObservableVectorEntry items to advance conditionally.
375        //
376        // There are two cases this branch is not hit:
377        //
378        // - make_owned was previously called (used for removing the item and resuming
379        //   iteration with the same index)
380        // - the ObservableVectorEntry was created with ObservableVector::entry, i.e.
381        //   it's not used for iteration at all
382        if let EntryIndex::Borrowed(idx) = &mut self.index {
383            **idx += 1;
384        }
385    }
386}
387
388/// An "iterator"¹ that yields entries into an [`ObservableVector`], obtained
389/// from a transaction.
390///
391/// ¹ conceptually, though it does not implement `std::iterator::Iterator`
392#[derive(Debug)]
393pub struct ObservableVectorTransactionEntries<'a, 'o, T: Clone> {
394    inner: &'a mut ObservableVectorTransaction<'o, T>,
395    index: usize,
396}
397
398impl<'a, 'o, T> ObservableVectorTransactionEntries<'a, 'o, T>
399where
400    T: Clone + 'static,
401{
402    pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>) -> Self {
403        Self { inner, index: 0 }
404    }
405
406    /// Advance this iterator, yielding an `ObservableVectorEntry` for the next
407    /// item in the vector, or `None` if all items have been visited.
408    #[allow(clippy::should_implement_trait)]
409    pub fn next(&mut self) -> Option<ObservableVectorTransactionEntry<'_, 'o, T>> {
410        if self.index < self.inner.len() {
411            Some(ObservableVectorTransactionEntry::new_borrowed(self.inner, &mut self.index))
412        } else {
413            None
414        }
415    }
416}