GORBIE 0.11.0

GORBIE! Is a minimalist notebook library for Rust.
Documentation
use std::any::Any;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;

use eframe::egui;
use parking_lot::Mutex;
use parking_lot::RawMutex;
use parking_lot::RwLock;


/// Shared read guard returned by [`StateStore::read`] and [`StateId::read`].
///
/// NOTE: the underlying storage is now a `Mutex`, so "read" and "write"
/// guards both serialize access. The separate type is kept for API
/// compatibility and could evolve to expose shared-reader semantics later.
pub type ArcReadGuard<T> = parking_lot::lock_api::ArcMutexGuard<RawMutex, T>;
/// Shared write guard returned by [`StateStore::read_mut`] and [`StateId::read_mut`].
pub type ArcWriteGuard<T> = parking_lot::lock_api::ArcMutexGuard<RawMutex, T>;

/// Type-erased store that holds all notebook state values.
///
/// Values are keyed by [`StateId`] and stored behind `Arc<Mutex<T>>`. Using
/// `Mutex` (instead of `RwLock`) means the inner `T` only needs to be
/// `Send`, not `Send + Sync` — important for triblespace-backed state
/// like `Workspace` that is `Send` but deliberately `!Sync`.
#[derive(Debug, Default)]
pub struct StateStore {
    states: RwLock<HashMap<egui::Id, Arc<dyn Any + Send + Sync>>>,
}

impl StateStore {
    fn get_raw<T: Send + 'static>(&self, id: egui::Id) -> Option<Arc<Mutex<T>>> {
        let entry = self.states.read().get(&id).cloned()?;
        entry.downcast::<Mutex<T>>().ok()
    }

    fn get_or_insert_raw<T: Send + 'static>(&self, id: egui::Id, init: T) -> Arc<Mutex<T>> {
        {
            let states = self.states.read();
            if let Some(existing) = states.get(&id) {
                if let Ok(state) = existing.clone().downcast::<Mutex<T>>() {
                    return state;
                }
            }
        }

        let state = Arc::new(Mutex::new(init));
        let erased: Arc<dyn Any + Send + Sync> = state.clone();
        let mut states = self.states.write();
        let entry = states.entry(id).or_insert_with(|| erased.clone());
        entry
            .clone()
            .downcast::<Mutex<T>>()
            .expect("state store type mismatch")
    }

    pub(crate) fn get_or_insert<T: Send + 'static>(
        &self,
        id: StateId<T>,
        init: T,
    ) -> Arc<Mutex<T>> {
        self.get_or_insert_raw(id.id(), init)
    }

    /// Returns the state for the given handle or panics when it is missing.
    /// Use `try_get` when the state may be absent (e.g., handle from another store).
    pub fn get<T: Send + 'static>(&self, id: StateId<T>) -> Arc<Mutex<T>> {
        self.get_raw(id.id()).unwrap_or_else(|| {
            let type_name = std::any::type_name::<T>();
            panic!(
                "state missing for id {:?} ({type_name}); this usually means the handle was created in a different StateStore. Use try_read/try_read_mut when the state may be absent.",
                id.id()
            )
        })
    }

    /// Returns the state for the given handle if it exists.
    pub fn try_get<T: Send + 'static>(&self, id: StateId<T>) -> Option<Arc<Mutex<T>>> {
        self.get_raw(id.id())
    }

    /// Returns a guard for the given handle or panics when missing.
    pub fn read<T: Send + 'static>(&self, id: StateId<T>) -> ArcReadGuard<T> {
        self.get(id).lock_arc()
    }

    /// Returns a guard for the given handle if it exists and can be acquired.
    pub fn try_read<T: Send + 'static>(&self, id: StateId<T>) -> Option<ArcReadGuard<T>> {
        self.try_get(id).and_then(|state| state.try_lock_arc())
    }

    /// Returns a guard for the given handle or panics when missing.
    pub fn read_mut<T: Send + 'static>(&self, id: StateId<T>) -> ArcWriteGuard<T> {
        self.get(id).lock_arc()
    }

    /// Returns a guard for the given handle if it exists and can be acquired.
    pub fn try_read_mut<T: Send + 'static>(
        &self,
        id: StateId<T>,
    ) -> Option<ArcWriteGuard<T>> {
        self.try_get(id).and_then(|state| state.try_lock_arc())
    }
}

/// Typed handle for a value stored in a [`StateStore`].
///
/// Cheap to copy and safe to pass between cards. The type parameter `T`
/// prevents accidental access to the wrong type at compile time.
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct StateId<T> {
    id: egui::Id,
    _marker: PhantomData<fn() -> T>,
}

impl<T> Copy for StateId<T> {}

impl<T> Clone for StateId<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> StateId<T> {
    pub(crate) fn new(id: egui::Id) -> Self {
        Self {
            id,
            _marker: PhantomData,
        }
    }

    pub(crate) fn id(&self) -> egui::Id {
        self.id
    }
}

/// Something that exposes a `&StateStore`, so [`StateId::read`] and its
/// siblings can operate uniformly against a `NotebookCtx` or a
/// `CardCtx`.
pub trait StateAccess {
    /// Returns a reference to the backing [`StateStore`].
    fn store(&self) -> &StateStore;
}

impl<T: Send + 'static> StateId<T> {
    /// Acquires a guard on the state value. Panics if the state is missing.
    pub fn read(self, ctx: &impl StateAccess) -> ArcReadGuard<T> {
        ctx.store().read(self)
    }

    /// Acquires a guard if the state exists and is not locked.
    pub fn try_read(self, ctx: &impl StateAccess) -> Option<ArcReadGuard<T>> {
        ctx.store().try_read(self)
    }

    /// Acquires a guard on the state value. Panics if the state is missing.
    pub fn read_mut(self, ctx: &impl StateAccess) -> ArcWriteGuard<T> {
        ctx.store().read_mut(self)
    }

    /// Acquires a guard if the state exists and is not locked.
    pub fn try_read_mut(self, ctx: &impl StateAccess) -> Option<ArcWriteGuard<T>> {
        ctx.store().try_read_mut(self)
    }
}