use std::{fmt, mem, ops};
use imbl::Vector;
use crate::vector::OneOrManyDiffs;
use super::{entry::EntryIndex, BroadcastMessage, ObservableVector, VectorDiff};
pub struct ObservableVectorTransaction<'o, T: Clone> {
inner: &'o mut ObservableVector<T>,
values: Vector<T>,
batch: Vec<VectorDiff<T>>,
}
impl<'o, T: Clone + 'static> ObservableVectorTransaction<'o, T> {
pub(super) fn new(inner: &'o mut ObservableVector<T>) -> Self {
let values = inner.values.clone();
Self { inner, values, batch: Vec::new() }
}
pub fn commit(mut self) {
#[cfg(feature = "tracing")]
tracing::debug!("commit");
self.inner.values = mem::take(&mut self.values);
if self.batch.is_empty() {
#[cfg(feature = "tracing")]
tracing::trace!(
target: "eyeball_im::vector::broadcast",
"Skipping broadcast of empty list of diffs"
);
} else {
let diffs = OneOrManyDiffs::Many(mem::take(&mut self.batch));
let msg = BroadcastMessage { diffs, state: self.inner.values.clone() };
let _num_receivers = self.inner.sender.send(msg).unwrap_or(0);
#[cfg(feature = "tracing")]
tracing::debug!(
target: "eyeball_im::vector::broadcast",
"New observable value broadcast to {_num_receivers} receivers"
);
}
}
pub fn rollback(&mut self) {
#[cfg(feature = "tracing")]
tracing::debug!("rollback (explicit)");
self.values = self.inner.values.clone();
self.batch.clear();
}
pub fn append(&mut self, values: Vector<T>) {
#[cfg(feature = "tracing")]
tracing::debug!(
target: "eyeball_im::vector::transaction::update",
"append(len = {})", values.len()
);
self.values.append(values.clone());
self.add_to_batch(VectorDiff::Append { values });
}
pub fn clear(&mut self) {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::transaction::update", "clear");
self.values.clear();
self.batch.clear(); self.add_to_batch(VectorDiff::Clear);
}
pub fn push_front(&mut self, value: T) {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_front");
self.values.push_front(value.clone());
self.add_to_batch(VectorDiff::PushFront { value });
}
pub fn push_back(&mut self, value: T) {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_back");
self.values.push_back(value.clone());
self.add_to_batch(VectorDiff::PushBack { value });
}
pub fn pop_front(&mut self) -> Option<T> {
let value = self.values.pop_front();
if value.is_some() {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_front");
self.add_to_batch(VectorDiff::PopFront);
}
value
}
pub fn pop_back(&mut self) -> Option<T> {
let value = self.values.pop_back();
if value.is_some() {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_back");
self.add_to_batch(VectorDiff::PopBack);
}
value
}
#[track_caller]
pub fn insert(&mut self, index: usize, value: T) {
let len = self.values.len();
if index <= len {
#[cfg(feature = "tracing")]
tracing::debug!(
target: "eyeball_im::vector::transaction::update",
"insert(index = {index})"
);
self.values.insert(index, value.clone());
self.add_to_batch(VectorDiff::Insert { index, value });
} else {
panic!("index out of bounds: the length is {len} but the index is {index}");
}
}
#[track_caller]
pub fn set(&mut self, index: usize, value: T) -> T {
let len = self.values.len();
if index < len {
#[cfg(feature = "tracing")]
tracing::debug!(
target: "eyeball_im::vector::transaction::update",
"set(index = {index})"
);
let old_value = self.values.set(index, value.clone());
self.add_to_batch(VectorDiff::Set { index, value });
old_value
} else {
panic!("index out of bounds: the length is {len} but the index is {index}");
}
}
#[track_caller]
pub fn remove(&mut self, index: usize) -> T {
let len = self.values.len();
if index < len {
#[cfg(feature = "tracing")]
tracing::debug!(
target: "eyeball_im::vector::transaction::update",
"remove(index = {index})"
);
let value = self.values.remove(index);
self.add_to_batch(VectorDiff::Remove { index });
value
} else {
panic!("index out of bounds: the length is {len} but the index is {index}");
}
}
pub fn truncate(&mut self, len: usize) {
if len < self.len() {
#[cfg(feature = "tracing")]
tracing::debug!(target: "eyeball_im::vector::update", "truncate(len = {len})");
self.values.truncate(len);
self.add_to_batch(VectorDiff::Truncate { length: len });
}
}
#[track_caller]
pub fn entry(&mut self, index: usize) -> ObservableVectorTransactionEntry<'_, 'o, T> {
let len = self.values.len();
if index < len {
ObservableVectorTransactionEntry::new(self, index)
} else {
panic!("index out of bounds: the length is {len} but the index is {index}");
}
}
pub fn for_each(&mut self, mut f: impl FnMut(ObservableVectorTransactionEntry<'_, 'o, T>)) {
let mut entries = self.entries();
while let Some(entry) = entries.next() {
f(entry);
}
}
pub fn entries(&mut self) -> ObservableVectorTransactionEntries<'_, 'o, T> {
ObservableVectorTransactionEntries::new(self)
}
fn add_to_batch(&mut self, diff: VectorDiff<T>) {
if self.inner.sender.receiver_count() != 0 {
self.batch.push(diff);
}
}
}
impl<T> fmt::Debug for ObservableVectorTransaction<'_, T>
where
T: Clone + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ObservableVectorWriteGuard")
.field("values", &self.values)
.finish_non_exhaustive()
}
}
impl<T: Clone> ops::Deref for ObservableVectorTransaction<'_, T> {
type Target = Vector<T>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl<T: Clone> Drop for ObservableVectorTransaction<'_, T> {
fn drop(&mut self) {
#[cfg(feature = "tracing")]
if !self.batch.is_empty() {
tracing::debug!("rollback (drop)");
}
}
}
pub struct ObservableVectorTransactionEntry<'a, 'o, T: Clone> {
inner: &'a mut ObservableVectorTransaction<'o, T>,
index: EntryIndex<'a>,
}
impl<'a, 'o, T> ObservableVectorTransactionEntry<'a, 'o, T>
where
T: Clone + 'static,
{
pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>, index: usize) -> Self {
Self { inner, index: EntryIndex::Owned(index) }
}
fn new_borrowed(
inner: &'a mut ObservableVectorTransaction<'o, T>,
index: &'a mut usize,
) -> Self {
Self { inner, index: EntryIndex::Borrowed(index) }
}
pub fn index(this: &Self) -> usize {
this.index.value()
}
pub fn set(this: &mut Self, value: T) -> T {
this.inner.set(this.index.value(), value)
}
pub fn remove(mut this: Self) -> T {
this.inner.remove(this.index.make_owned())
}
}
impl<T> fmt::Debug for ObservableVectorTransactionEntry<'_, '_, T>
where
T: Clone + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let index = self.index.value();
f.debug_struct("ObservableVectorEntry")
.field("item", &self.inner[index])
.field("index", &index)
.finish()
}
}
impl<T: Clone> ops::Deref for ObservableVectorTransactionEntry<'_, '_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner[self.index.value()]
}
}
impl<T: Clone> Drop for ObservableVectorTransactionEntry<'_, '_, T> {
fn drop(&mut self) {
if let EntryIndex::Borrowed(idx) = &mut self.index {
**idx += 1;
}
}
}
#[derive(Debug)]
pub struct ObservableVectorTransactionEntries<'a, 'o, T: Clone> {
inner: &'a mut ObservableVectorTransaction<'o, T>,
index: usize,
}
impl<'a, 'o, T> ObservableVectorTransactionEntries<'a, 'o, T>
where
T: Clone + 'static,
{
pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>) -> Self {
Self { inner, index: 0 }
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<ObservableVectorTransactionEntry<'_, 'o, T>> {
if self.index < self.inner.len() {
Some(ObservableVectorTransactionEntry::new_borrowed(self.inner, &mut self.index))
} else {
None
}
}
}