refslots 0.1.0

Reference-counted handles to slots in a slotmap
Documentation
//! Reference-counted handles to slots in a [`SlotMap`](slotmap::SlotMap).
//!
//! This crate tracks the strong reference count to each slot and reports slots that have been
//! released when their last reference is dropped. It does not store values, which makes it a
//! building block for data structures that need reference-counted identity: entity maps, focus
//! tracking, resource tables.
//!
//! # Overview
//!
//! - [`Registry`] maintains an atomic strong count for each slot, keyed by a [`slotmap::Key`]. A
//!   slot is created with a count of 1. When its count reaches 0, the slot is removed and its key
//!   pushed to a release queue to be drained with [`Registry::take_released`].
//! - [`Handle`] is a strong reference to a slot. Cloning a handle increments the count and dropping
//!   it decrements the count. Dropping the last handle releases the slot. A handle can carry
//!   metadata `M`, such as a `TypeId` for dynamically typed entities. The metadata is cloned with
//!   the handle and doesn't take part in equality, ordering, or hashing.
//! - [`WeakHandle`] is a weak reference to a slot. It does not contribute to the strong reference
//!   count and does not keep its slot alive. [`WeakHandle::upgrade`] returns a strong handle only
//!   if the slot is still alive. The liveness check and increment are performed as an atomic step
//!   so a slot that is being released cannot be revived.
//!
//! The internal `SlotMap` uses versioned keys to allow reusing indexes. Therefore, a stale key or
//! weak handle can never resolve to a newer slot that occupies the same index in the `SlotMap`.
//!
//! # Usage
//!
//! A registry is shared as an `Arc<RwLock<Registry<K>>>`. Handles hold a weak reference to the
//! registry that created them. Cloning and dropping a handle attempts to upgrade the weak reference
//! for the operation. If the upgrade fails then there is no effect on the reference counters.
//!
//! Values associated with slots are stored by the owners of the registry, for example in a
//! [`SecondaryMap`](slotmap::SecondaryMap) indexed by [`Handle::key`]. The owner should drain the
//! release queue periodically, removing data associated with each released key and performing any
//! cleanup that is required. The `entitymap` example shows this pattern.
//!
//! A separate registry should be used for each key type. [`Handle::check_context`] compares
//! registries by pointer identity, which detects a handle being used with a registry other than the
//! one that created it.
//!
//! # Locking
//!
//! Handles are `Send` and `Sync` when their metadata is `Send` and `Sync`. This allows them to be
//! cloned and dropped on any thread. Reference counters are atomic, but handles access the registry
//! through its lock. Cloning and upgrading acquire a read lock. Dropping acquires an upgradable
//! read lock which is upgraded to a write lock when the last reference is being dropped. Inserting
//! and draining the release queue acquire a write lock.
//!
//! Handles must not be cloned, dropped, or inserted while the calling thread holds a guard on the
//! registry's lock as this might deadlock. [`Registry::take_released`] returns an iterator that
//! borrows the registry mutably. The iterator should be collected before continuing with operations
//! that might create or drop handles.
//!
//! # Leak Detection
//!
//! When the `recorder` feature is enabled every handle is given a unique, monotonically increasing
//! identifier and records a backtrace of where it was created or cloned. (The feature is always
//! enabled for this crate's tests.) [`Registry`] provides snapshots and assertions for verifying
//! that handles have been dropped. Dropping a registry that still has live handles panics with a
//! report listing each leaked handle and its creation backtrace.
//!
//! Recording is intended for debugging and testing. A backtrace is captured on every creation and
//! clone, and every clone and drop acquires the registry's write lock. Recording is not indented
//! for use in production.
//!
//! # Example
//!
//! ```rust
//! use parking_lot::RwLock;
//! use refslots::{Handle, Registry};
//! use std::sync::Arc;
//!
//! slotmap::new_key_type! { struct FocusId; }
//!
//! let registry = Arc::new(RwLock::new(Registry::<FocusId>::default()));
//!
//! let handle = Handle::insert(&registry, ());
//! let weak = handle.downgrade();
//! drop(handle);
//! assert!(weak.upgrade().is_none());
//!
//! let released: Vec<FocusId> = registry.write().take_released().collect();
//! assert_eq!(released.len(), 1);
//! ```

pub use handle::Handle;
pub use registry::Registry;
pub use weak::WeakHandle;

#[cfg(any(test, feature = "recorder"))]
pub use recorder::{RecorderSnapshot, SlotSnapshot};

mod handle;
mod registry;
mod weak;

#[cfg(any(test, feature = "recorder"))]
mod recorder;