janetrs 0.5.0

High level binding for Janet programming language
Documentation
use crate::Janet;

/// The Janet Garbage Collector type.
///
/// It allows the use of garbage collection operations in the Janet public C API.
#[derive(Debug, Default)]
pub struct JanetGc {
    _phantom: core::marker::PhantomData<*const ()>,
}

impl JanetGc {
    /// Obtain the [`JanetGc`].
    #[inline]
    pub fn obtain() -> Self {
        Self {
            _phantom: core::marker::PhantomData,
        }
    }

    /// Run the garbage collection if there is nothing locking or suspending the garbage
    /// collector, like an active [`JanetGcLockGuard`] or a call to a Janet C API that
    /// locks the GC.
    ///
    /// If there is something locking the garbage collection, it simply does a no-op.
    ///
    /// # Safety
    /// This function will free all memory allocated with the [Janet scratch memory
    /// API](crate::allocator::Scratch) and any [non-rooted](JanetGc::root) object
    /// that have no reference to a live object (as example, an empty
    /// [`JanetTable`](crate::JanetTable) or [`JanetArray`](crate::JanetArray) )
    #[inline]
    pub unsafe fn collect(&self) {
        evil_janet::janet_collect()
    }

    /// Lock the Janet GC and suspend any garbage collection until the guard is dropped.
    ///
    /// If there is any attempt to manually trigger the garbage collection while there is
    /// a [`JanetGcLockGuard`] active (or any unsafe call to the Janet C API locking the
    /// GC)
    #[inline]
    pub fn lock(&self) -> JanetGcLockGuard {
        let handle = unsafe { evil_janet::janet_gclock() };
        JanetGcLockGuard::new(handle)
    }

    /// Immediately drops the guard, and consequently unlocks the Janet GC.
    ///
    /// This function is equivalent to calling [`drop`] on the guard but is more
    /// self-documenting. Alternately, the guard will be automatically dropped when it
    /// goes out of scope.
    ///
    /// # Example:
    ///
    /// ```
    /// # let _client = janetrs::client::JanetClient::init().unwrap();
    /// use janetrs::JanetGc;
    ///
    /// let gc = JanetGc::obtain();
    ///
    /// let mut guard = gc.lock();
    ///
    /// // do stuff with the Janet GC locked
    ///
    /// JanetGc::unlock(guard);
    /// ```
    #[inline]
    pub fn unlock(guard: JanetGcLockGuard) {
        drop(guard)
    }

    /// Roots the `value` to the GC. This prevents the GC from removing the `value` and
    /// all of its children in a garbage collection.
    ///
    /// The returned guard will [`unroot`](JanetGc::unroot) the `value` when dropped.
    #[inline]
    pub fn root(&self, value: Janet) -> JanetGcRootGuard {
        JanetGcRootGuard::new(value)
    }

    /// Immediately drops the guard, and consequently unlocks the Janet GC.
    ///
    /// This function is equivalent to calling [`drop`] on the guard but is more
    /// self-documenting. Alternately, the guard will be automatically dropped when it
    /// goes out of scope.
    #[inline]
    pub fn unroot(guard: JanetGcRootGuard) {
        drop(guard)
    }
}

/// An RAII implementation of a “scoped lock” of the Janet GC. When this structure is
/// dropped (falls out of scope), the lock will be unlocked.
#[derive(Debug)]
pub struct JanetGcLockGuard {
    handle:   i32,
    _phantom: core::marker::PhantomData<*const ()>,
}

impl JanetGcLockGuard {
    #[inline]
    pub(crate) fn new(handle: i32) -> Self {
        Self {
            handle,
            _phantom: core::marker::PhantomData,
        }
    }
}

impl Drop for JanetGcLockGuard {
    #[inline]
    fn drop(&mut self) {
        unsafe { evil_janet::janet_gcunlock(self.handle) }
    }
}

/// An RAII implementation of a rooted [`Janet`] value. When this structure is dropped
/// (falls out of scope), the rooting will be undone.
#[derive(Debug)]
pub struct JanetGcRootGuard {
    value:    Janet,
    _phantom: core::marker::PhantomData<*const ()>,
}

impl JanetGcRootGuard {
    #[inline]
    fn new(value: Janet) -> Self {
        unsafe { evil_janet::janet_gcroot(value.inner) };
        Self {
            value,
            _phantom: core::marker::PhantomData,
        }
    }
}

impl Drop for JanetGcRootGuard {
    #[inline]
    fn drop(&mut self) {
        // SAFETY: Since we unrooting the same value we rooted, this should always work.
        // For extra safety, below it's add a debug assert to be sure on debug compilations.
        let res = unsafe { evil_janet::janet_gcunroot(self.value.inner) };

        // Assert in debug mode that the result is 1
        debug_assert_eq!(res, 1)
    }
}