yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! The crate's lock chokepoint (Bootstrap rule 7): the cross-thread
//! shared-mutable-state locks live in this one file, so the whole-crate
//! shared-state inventory is auditable in one place.
//! `rules/locks-outside-state.yml` enforces the confinement; the only carve-outs
//! are test scaffolding and one documented exception,
//! [`git_tree::probe_cache`](crate::git_tree) — the macOS TTL cache's `Mutex` is
//! single-thread interior mutability local to the probe stack, not cross-thread
//! shared state, and a generic decorator folded in here would break llvm-cov's
//! per-line coverage (see that module's doc). Two residents:
//!
//! - [`WatchSetHandle`] — the shared [`WatchSet`](crate::watch::WatchSet) behind
//!   `Arc<Mutex<…>>`; the frame reconciles it, the
//!   [`Bridge`](crate::watch::Bridge) polls it. [`new_watchset`] is the single
//!   site its `Mutex` is constructed.
//! - [`DirtySet`] — the bridge→frame dirty-root hand-off (§7.2): a mutex-guarded
//!   set the bridge fills and the frame drains.

use std::collections::BTreeSet;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};

use crate::watch::WatchSet;

/// The shared [`WatchSet`](crate::watch::WatchSet): the frame reconciles it, the
/// [`Bridge`](crate::watch::Bridge) polls it. A transparent alias so `.lock()`
/// stays ergonomic at the use sites while the `Mutex` token itself is confined
/// here.
pub type WatchSetHandle = Arc<Mutex<WatchSet>>;

/// Build a fresh, empty [`WatchSet`](crate::watch::WatchSet) behind its shared
/// handle — the one place `Mutex::new` is applied to the watch set (the app then
/// reconciles the desired roots through the returned handle).
pub(crate) fn new_watchset() -> WatchSetHandle {
    Arc::new(Mutex::new(WatchSet::new()))
}

/// Lock the shared watch set, poison-immune: a panic while the guard was held
/// leaves the data intact, so we recover it rather than propagate the panic
/// ([`PoisonError::into_inner`]). Keeping the `.lock()` and the recovery on one
/// line is deliberate — a split isolates the never-taken recovery on its own
/// line, which reads as uncovered under `ignore-panics`.
pub(crate) fn lock_watchset(handle: &WatchSetHandle) -> MutexGuard<'_, WatchSet> {
    handle.lock().unwrap_or_else(PoisonError::into_inner)
}

/// The bridge→frame hand-off: dirty root paths. Cloning shares the inner set
/// (the bridge holds one clone, the frame another).
#[derive(Clone, Default)]
pub struct DirtySet {
    inner: Arc<Mutex<BTreeSet<PathBuf>>>,
}

impl DirtySet {
    /// The poison-immune guard — the one `.lock()` site for the dirty set (see
    /// [`lock_watchset`] for the same discipline on the watch set).
    fn guard(&self) -> MutexGuard<'_, BTreeSet<PathBuf>> {
        self.inner.lock().unwrap_or_else(PoisonError::into_inner)
    }

    /// Mark every root in `roots` dirty.
    pub(crate) fn mark_all<I: IntoIterator<Item = PathBuf>>(&self, roots: I) {
        self.guard().extend(roots);
    }

    /// Take and clear the dirty set (the frame consumes it each tick).
    pub fn drain(&self) -> BTreeSet<PathBuf> {
        std::mem::take(&mut self.guard())
    }

    pub fn is_empty(&self) -> bool {
        self.guard().is_empty()
    }
}