use super::ServerCompositor;
use std::mem::ManuallyDrop;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex, OnceLock};
use std::thread::{self, ThreadId};
#[derive(Default)]
pub struct CompositorLock {
state: Mutex<LockState>,
released: Condvar,
}
#[derive(Default)]
struct LockState {
owner: Option<ThreadId>,
depth: usize,
}
impl CompositorLock {
pub fn new() -> Self {
Self::default()
}
pub fn enter(&self) -> CompositorLockGuard<'_> {
let current = thread::current().id();
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
while state.owner.is_some_and(|owner| owner != current) {
state = self.released.wait(state).unwrap_or_else(|e| e.into_inner());
}
state.owner = Some(current);
state.depth += 1;
CompositorLockGuard { lock: self }
}
pub fn is_held_by_current_thread(&self) -> bool {
self.state.lock().unwrap_or_else(|e| e.into_inner()).owner == Some(thread::current().id())
}
}
pub struct CompositorLockGuard<'a> {
lock: &'a CompositorLock,
}
impl Drop for CompositorLockGuard<'_> {
fn drop(&mut self) {
let mut state = self.lock.state.lock().unwrap_or_else(|e| e.into_inner());
state.depth -= 1;
if state.depth == 0 {
state.owner = None;
drop(state);
self.lock.released.notify_one();
}
}
}
pub struct LockedServerCompositor {
lock: CompositorLock,
server: std::cell::UnsafeCell<Option<Rc<ServerCompositor>>>,
confined_to: OnceLock<ThreadId>,
}
unsafe impl Send for LockedServerCompositor {}
unsafe impl Sync for LockedServerCompositor {}
impl LockedServerCompositor {
pub(crate) fn new(server: Rc<ServerCompositor>) -> Self {
Self {
lock: CompositorLock::new(),
server: std::cell::UnsafeCell::new(Some(server)),
confined_to: OnceLock::new(),
}
}
pub(crate) fn confine_to_current_thread(&self) {
let current = thread::current().id();
assert!(
*self.confined_to.get_or_init(|| current) == current,
"a compositor that is confined to its render thread was rendered by another thread"
);
}
pub(crate) fn is_confined_to_another_thread(&self) -> bool {
self.confined_to.get().is_some_and(|thread| *thread != thread::current().id())
}
pub fn with<R>(&self, f: impl FnOnce(&Rc<ServerCompositor>) -> R) -> R {
match self.try_with(f) {
Some(result) => result,
None => panic!("the server compositor has been released"),
}
}
pub fn try_with<R>(&self, f: impl FnOnce(&Rc<ServerCompositor>) -> R) -> Option<R> {
let _guard = self.lock.enter();
let server = unsafe { (*self.server.get()).clone() }?;
Some(f(&server))
}
pub(crate) fn is_server(&self, server: &ServerCompositor) -> bool {
unsafe { (*self.server.get()).as_ref() }.is_some_and(|own| std::ptr::eq(Rc::as_ptr(own), server))
}
pub(crate) fn under_lock<R>(&self, f: impl FnOnce() -> R) -> R {
let _guard = self.lock.enter();
f()
}
pub(crate) fn release(&self) {
let _guard = self.lock.enter();
let server = unsafe { (*self.server.get()).take() };
drop(server);
}
pub(crate) fn same_thread(&self) -> &Rc<ServerCompositor> {
match unsafe { (*self.server.get()).as_ref() } {
Some(server) => server,
None => panic!("the server compositor has been released"),
}
}
}
impl Drop for LockedServerCompositor {
fn drop(&mut self) {
if self.is_confined_to_another_thread() {
if let Some(server) = self.server.get_mut().take() {
std::mem::forget(server);
}
}
}
}
pub struct LockBound<T> {
value: ManuallyDrop<T>,
server: Arc<LockedServerCompositor>,
}
unsafe impl<T> Send for LockBound<T> {}
unsafe impl<T> Sync for LockBound<T> {}
impl<T> LockBound<T> {
pub(crate) fn new(locked: &Arc<LockedServerCompositor>, server: &ServerCompositor, value: T) -> Self {
let bound = Self { value: ManuallyDrop::new(value), server: locked.clone() };
bound.verify(server);
bound
}
fn verify(&self, server: &ServerCompositor) {
assert!(
self.server.is_server(server),
"the value is bound to the lock of another compositor"
);
}
pub fn get<'a>(&'a self, server: &'a ServerCompositor) -> &'a T {
self.verify(server);
&self.value
}
pub(crate) fn with<R>(&self, f: impl FnOnce(&ServerCompositor, &T) -> R) -> Option<R> {
self.server.try_with(|server| f(server, &self.value))
}
}
impl<T> Drop for LockBound<T> {
fn drop(&mut self) {
if self.server.is_confined_to_another_thread() {
return;
}
let server = self.server.clone();
server.under_lock(|| unsafe { ManuallyDrop::drop(&mut self.value) });
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[test]
fn the_holder_enters_again_and_others_wait() {
let lock = Arc::new(CompositorLock::new());
let inside = Arc::new(AtomicUsize::new(0));
let outer = lock.enter();
let inner = lock.enter();
assert!(lock.is_held_by_current_thread());
let (thread_lock, thread_inside) = (lock.clone(), inside.clone());
let other = thread::spawn(move || {
assert!(!thread_lock.is_held_by_current_thread());
let _guard = thread_lock.enter();
thread_inside.fetch_add(1, Ordering::SeqCst);
});
thread::sleep(std::time::Duration::from_millis(20));
assert_eq!(0, inside.load(Ordering::SeqCst));
drop(inner);
thread::sleep(std::time::Duration::from_millis(20));
assert_eq!(0, inside.load(Ordering::SeqCst));
drop(outer);
other.join().unwrap();
assert_eq!(1, inside.load(Ordering::SeqCst));
assert!(!lock.is_held_by_current_thread());
}
}