diff options
Diffstat (limited to 'src/context/context.rs')
| -rw-r--r-- | src/context/context.rs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/src/context/context.rs b/src/context/context.rs new file mode 100644 index 0000000..adc684f --- /dev/null +++ b/src/context/context.rs @@ -0,0 +1,158 @@ +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: (), + } + } + } +} |
