cycle_ptr 0.1.0

Smart pointers, with cycles
//! Define a way for users of the library to control execution of the garbage collector.
use crate::errors::{Error, ErrorEnum};
use crate::generation::Generation;
use crate::generation::defer_gc::maybe_defer_task;
use crate::generation::stats::GcStats;
use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::rc::Weak;

/// Function-pointer used to refer to a task.
type TaskPtr = Box<dyn FnMut(GcTask)>;

thread_local! {
    static GC_TASK_FN: RefCell<Option<TaskPtr>> = const{RefCell::new(None)};
}

/// A garbage collection task.
///
/// This task is used to ensure a garbage collection is run,
/// but also holds on to the run, allowing you to control when it runs.
/// (Or you can [std::mem::forget] it, to never run the garbage collector and leak the memory.)
///
/// Note that the garbage collection must be performed on the same thread,
/// and can't be done on a separate thread,
/// because the objects in this generation are not [Send]/[Sync].
///
/// # Example
///
/// ```
/// use cycle_ptr::prelude::*;
/// use cycle_ptr::{GcPtr, GcTask, GcMemberPtr, Metadata};
/// use std::cell::RefCell;
///
/// /// Struct that'll print a text when it is dropped.
/// struct PrintOnDrop {
///     text: &'static str,
///
///     /// Include a self-reference, to force the garbage collector to do work.
///     _self_reference: RefCell<Option<GcMemberPtr<PrintOnDrop>>>,
///     metadata: Metadata,
/// }
///
/// impl PrintOnDrop {
///     fn new(text: &'static str) -> GcPtr<PrintOnDrop> {
///         let ptr = GcPtr::new(|metadata| PrintOnDrop {
///             text,
///             _self_reference: RefCell::new(None),
///             metadata,
///         });
///         /// Create a self reference.
///         /// This will force the garbage collector task
///         /// to confirm the object is no longer reachable.
///         ptr._self_reference
///             .borrow_mut()
///             .insert(ptr.metadata.new_pointer(ptr.clone()));
///         ptr
///     }
/// }
///
/// impl Drop for PrintOnDrop {
///     fn drop(&mut self) {
///         eprint!("{}", self.text);
///     }
/// }
///
/// let world = PrintOnDrop::new(" world\n");
/// let hello = PrintOnDrop::new("hello");
///
/// let mut pending = Vec::<GcTask>::default();
/// let callback_handle =
///     GcTask::install_callback(|task| pending.push(task)).expect("there should not be a callback");
///
/// // Dropping world won't print anything,
/// // because the garbage collector task is added to `pending`.
/// drop(world);
///
/// // Drop the callback handle.
/// // Future garbage collections will run immediately,
/// // but the ones in `pending` are still pending.
/// drop(callback_handle);
///
/// // Print "hello", because the GC runs immediately.
/// drop(hello);
///
/// // Now run all the delayed tasks.
/// // This will discover `world` is unreachable, and drop it,
/// // which causes "world" to be printed.
/// for task in pending.into_iter() {
///     task.run();
/// }
/// ```
#[derive(Debug)]
pub struct GcTask {
    /// Generation that is to be garbage collected.
    generation: Option<Weak<Generation>>,
}

/// A handle on a GC callback.
///
/// Dropping the handle will uninstall the associated callback function.
pub struct GcTaskCallback<'a> {
    /// Track the lifetime.
    lifetime: PhantomData<&'a ()>,
}

impl Drop for GcTask {
    #[inline]
    fn drop(&mut self) {
        if let Some(generation) = self
            .generation
            .take()
            .and_then(|generation_ptr| Weak::upgrade(&generation_ptr))
        {
            generation.run_gc();
        }
    }
}

impl GcTask {
    /// Create a new task.
    pub(super) const fn new(generation: Weak<Generation>) -> Self {
        GcTask {
            generation: Some(generation),
        }
    }

    /// Run the GC task.
    ///
    /// Note: if you don't run this function, the [drop][Drop::drop] of [GcTask] will run the task anyway.
    /// Invoking this function might be more readable than simply dropping the task however.
    ///
    /// Returns the number of objects that were collected, and the number of objects that were retained.
    /// Returns [None] if the garbage collector didn't run.
    #[inline]
    pub fn run(mut self) -> Option<GcStats> {
        self.generation
            .take()
            .as_ref()
            .and_then(Weak::upgrade)
            .map(Generation::run_gc)
    }

    /// Post the task on a user-supplied callback.
    ///
    /// If the user has not created a callback, the task will be returned.
    pub(super) fn post(self) -> Option<Self> {
        maybe_defer_task(self).err().and_then(Self::post_no_defer)
    }

    /// Post the task on a user-supplied callback.
    ///
    /// This method skips the `defer` handler.
    /// If the user has not created a callback, the task will be returned.
    pub(super) fn post_no_defer(self) -> Option<Self> {
        GC_TASK_FN.with(|gc_task_fn| {
            if let Some(gc_task_fn) = &mut *gc_task_fn.borrow_mut() {
                gc_task_fn(self);
                None
            } else {
                Some(self)
            }
        })
    }

    /// Install a callback for running [GcTask].
    ///
    /// Returns a handle to the installed callback.
    /// Dropping the handle will uninstall the callback function.
    ///
    /// # Errors
    ///
    /// If there is already a callback present, the function will return an [Error].
    #[allow(
        clippy::missing_inline_in_public_items,
        reason = "This is rarely called."
    )]
    pub fn install_callback<'a>(
        callback: impl FnMut(GcTask) + 'a,
    ) -> Result<GcTaskCallback<'a>, Error> {
        GC_TASK_FN.with(move |gc_task_fn| {
            let gc_task_fn = &mut (*gc_task_fn.borrow_mut());
            if gc_task_fn.is_some() {
                Err(Error::new(ErrorEnum::CallbackAlreadyInstalled))
            } else {
                let callback = Box::new(callback);
                let callback: Box<dyn FnMut(GcTask) + 'a> = callback;
                let callback = unsafe {
                    std::mem::transmute::<
                        Box<dyn FnMut(GcTask) + 'a>,
                        Box<dyn FnMut(GcTask) + 'static>,
                    >(callback)
                };
                *gc_task_fn = Some(callback);
                Ok(GcTaskCallback {
                    lifetime: PhantomData,
                })
            }
        })
    }
}

impl Drop for GcTaskCallback<'_> {
    #[allow(
        clippy::missing_inline_in_public_items,
        reason = "This is rarely called."
    )]
    fn drop(&mut self) {
        GC_TASK_FN.with(|gc_task_fn| *gc_task_fn.borrow_mut() = None);
    }
}

impl fmt::Debug for GcTaskCallback<'_> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.write_str("GcTaskCallback")
    }
}