summaryrefslogtreecommitdiff
path: root/src/context/context.rs
blob: adc684fb8b4bd6cd656525cb21878b05866b677f (plain)
use std::marker::PhantomData;

use crate::{
	context::{LockContext, LockingIterator, LockingTuple},
	lockable::{Lockable, OwnedLockable},
	ThreadKey,
};

impl<'l, L> LockContext<'l, L> {
	pub(crate) const fn new(lockable: &'l L) -> Self
	where
		L: OwnedLockable,
	{
		Self {
			key: None,
			lockable,
		}
	}

	/// Unlocks all locks in the collection, returning the [`ThreadKey`].
	///
	/// This requires a mutable reference to the context, so it cannot be called
	/// without first dropping any [`ContextGuard`]s that reference this context.
	/// This method will also return `None` if the context has not been locked
	/// with a `ThreadKey`.
	///
	/// # Example
	///
	/// ```
	/// use happylock::{Mutex, ThreadKey};
	/// use happylock::collection::OwnedLockCollection;
	///
	/// let key = ThreadKey::get().unwrap();
	/// let data = (Mutex::new(42), Mutex::new(true));
	/// let locks = OwnedLockCollection::new(data);
	/// let mut ctx = locks.context();
	/// let tuple = ctx.tuple(key);
	///
	/// let (use_other, tuple) = tuple.lock_1();
	/// if **use_other {
	///     drop(use_other);
	///     drop(tuple);
	///     let key = ctx.unlock().unwrap();
	///     let tuple = ctx.tuple(key);
	///     let (mut item, _) = tuple.lock_0();
	///     **item = 67;
	/// } else {
	///     drop(use_other);
	///     drop(tuple);
	/// };
	///
	/// let key = ctx.unlock().unwrap();
	/// let tuple = ctx.tuple(key);
	/// let (number, _) = tuple.lock_0();
	/// assert_eq!(**number, 67);
	/// ```
	///
	/// [`ContextGuard`]: `crate::context::ContextGuard`
	pub fn unlock(&mut self) -> Option<ThreadKey> {
		self.key.take()
	}
}

impl<L: Lockable> LockContext<'_, L> {
	/// Creates a [`LockingTuple`], which can lock a subset of a tuple of locks,
	/// in a specific order.
	///
	/// Sometimes, partial allocation of locks is useful. For example, you may want
	/// to acquire a lock on one item before deciding if the second item should be
	/// locked. If the locks can be organized into a tuple, [`LockingTuple`] is
	/// capable of doing exactly that.
	///
	/// # Example
	///
	/// ```
	/// use happylock::{Mutex, ThreadKey};
	/// use happylock::collection::OwnedLockCollection;
	///
	/// let key = ThreadKey::get().unwrap();
	/// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67));
	/// let locks = OwnedLockCollection::new(data);
	/// let mut ctx = locks.context();
	/// let tuple = ctx.tuple(key);
	///
	/// let (use_other, tuple) = tuple.lock_0();
	/// let number = if **use_other {
	///     tuple.lock_2().0
	/// } else {
	///     tuple.lock_1().0
	/// };
	/// assert_eq!(**number, 67);
	/// ```
	pub fn tuple(&mut self, key: ThreadKey) -> LockingTuple<'_, L, L> {
		unsafe {
			self.key = Some(key);

			LockingTuple {
				_lockable: PhantomData,
				// safety: we just inserted a key
				key: self.key.as_ref().unwrap_unchecked(),
				tuple: self.lockable,
				outer: (),
			}
		}
	}
}

impl<'l, L> LockContext<'l, L>
where
	&'l L: IntoIterator,
{
	/// Creates a [`LockingIterator`] to iterate through a collection of locks
	/// without locking everything at once.
	///
	/// Sometimes, partial allocation of locks is useful. For example, you may
	/// want to acquire a lock on the first element of a list before deciding if
	/// the second element should be locked. If the list is iterable, then a
	/// [`LockingIterator`] is capable of doing exactly that.
	///
	/// # Example
	///
	/// ```
	/// use happylock::{Mutex, ThreadKey};
	/// use happylock::collection::OwnedLockCollection;
	///
	/// let key = ThreadKey::get().unwrap();
	/// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)];
	/// let locks = OwnedLockCollection::new(data);
	/// let mut ctx = locks.context();
	/// let mut iter = ctx.iter(key);
	///
	/// let mut sum = 0;
	/// while let Some(item) = iter.lock_next() {
	///     sum += **item;
	/// }
	///
	/// assert_eq!(sum, 12);
	/// ```
	// TODO: support scoped locks
	// TODO: implement get_disjoint
	// TODO: support some sort of index tower thing
	#[expect(clippy::iter_not_returning_iterator)]
	pub fn iter(
		&mut self,
		key: ThreadKey,
	) -> LockingIterator<'_, <&'l L as IntoIterator>::IntoIter> {
		unsafe {
			self.key = Some(key);

			LockingIterator {
				// safety: we just inserted a key
				key: self.key.as_ref().unwrap_unchecked(),
				iterator: self.lockable.into_iter(),
				outer: (),
			}
		}
	}
}