use ouroboros::self_referencing;
use parking_lot::{Mutex, MutexGuard, ReentrantMutex, ReentrantMutexGuard};
use core::{
cell::{Ref, RefCell},
ops,
};
use crate::{GetMock, Guard, LockMock, SetMock};
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "shared")))]
pub struct Shared<T> {
inner: ReentrantMutex<RefCell<Option<T>>>,
write_lock: Mutex<()>,
}
impl<T> Default for Shared<T> {
fn default() -> Self {
Self {
inner: ReentrantMutex::new(RefCell::new(None)),
write_lock: Mutex::new(()),
}
}
}
impl<T> Shared<T> {
fn lock(&self) -> ReentrantMutexGuard<'_, RefCell<Option<T>>> {
self.inner.lock()
}
}
impl<'a, T: 'static> GetMock<'a, T> for Shared<T> {
type Ref = SharedRef<'a, T>;
fn get(&self) -> Option<SharedRef<'_, T>> {
let guard = self.lock();
if guard.borrow().is_some() {
Some(SharedRef::from_guard(guard))
} else {
None
}
}
}
impl<'a, T: 'static> SetMock<'a, T> for Shared<T> {
type Guard = SharedGuard<'a, T>;
fn set(&self, state: T) -> SharedGuard<'_, T> {
let guard = self.write_lock.lock();
*self.lock().borrow_mut() = Some(state);
SharedGuard {
_guard: guard,
mock: self,
}
}
}
impl<'a, T: 'static> LockMock<'a, T> for Shared<T> {
type EmptyGuard = MutexGuard<'a, ()>;
fn lock(&'a self) -> Self::EmptyGuard {
self.write_lock.lock()
}
}
#[self_referencing]
pub struct SharedRef<'a, T> {
guard: ReentrantMutexGuard<'a, RefCell<Option<T>>>,
#[borrows(guard)]
#[covariant]
state: Ref<'this, T>,
}
impl<T> ops::Deref for SharedRef<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.borrow_state()
}
}
impl<'a, T> SharedRef<'a, T> {
fn from_guard(guard: ReentrantMutexGuard<'a, RefCell<Option<T>>>) -> Self {
SharedRefBuilder {
guard,
state_builder: |guard| Ref::map(guard.borrow(), |option| option.as_ref().unwrap()),
}
.build()
}
}
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "shared")))]
pub struct SharedGuard<'a, T> {
mock: &'a Shared<T>,
_guard: MutexGuard<'a, ()>,
}
impl<T: 'static> Guard<T> for SharedGuard<'_, T> {
fn with<R>(&mut self, action: impl FnOnce(&mut T) -> R) -> R {
let locked = self.mock.lock();
let mut borrowed = locked.borrow_mut();
action(borrowed.as_mut().unwrap())
}
fn into_inner(self) -> T {
self.mock.lock().take().unwrap()
}
}
impl<T> Drop for SharedGuard<'_, T> {
fn drop(&mut self) {
self.mock.lock().take();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Static;
use static_assertions::assert_impl_all;
assert_impl_all!(Shared<()>: Send, Sync);
assert_impl_all!(Static<Shared<()>>: Send, Sync);
}