salsa/components/salsa-2022/src/routes.rs
Niko Matsakis 5ce5e3c374 track and assert struct ingredient indices
We need a cheap way to compute field indices.
2024-05-24 07:16:50 -04:00

130 lines
5.1 KiB
Rust

use crate::ingredient::IngredientRequiresReset;
use super::{ingredient::Ingredient, storage::HasJars};
/// An ingredient index identifies a particular [`Ingredient`] in the database.
/// The database contains a number of jars, and each jar contains a number of ingredients.
/// Each ingredient is given a unique index as the database is being created.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct IngredientIndex(u32);
impl IngredientIndex {
/// Create an ingredient index from a usize.
pub(crate) fn from(v: usize) -> Self {
assert!(v < (std::u32::MAX as usize));
Self(v as u32)
}
pub(crate) fn as_u32(self) -> u32 {
self.0
}
/// Convert the ingredient index back into a usize.
pub(crate) fn as_usize(self) -> usize {
self.0 as usize
}
}
/// A "route" is a function that, given a `&DB::Jars`, returns an `&dyn Ingredient`.
/// Routes are constructed (in part) from closures generated by the salsa macros.
/// These closures look essentially like `|jar| &jar.some_field` -- i.e., if a jar is a struct,
/// the closure returns a reference to some particular field of the struct
/// (whichever field has the database for this ingredient).
///
/// The key point here is: the struct definitions that are being referencd here come from
/// crates that consume this crate, and hence we cannot name them directly.
/// We have to navigate them through closures generated by that downstream crate.
#[allow(type_alias_bounds)]
#[allow(unused_parens)]
pub type DynRoute<DB: HasJars> = dyn Fn(&DB::Jars) -> (&dyn Ingredient<DB>) + Send + Sync;
/// Like a `DynRoute`, but for `&mut` references.
#[allow(type_alias_bounds)]
#[allow(unused_parens)]
pub type DynMutRoute<DB: HasJars> =
dyn Fn(&mut DB::Jars) -> (&mut dyn Ingredient<DB>) + Send + Sync;
/// The "routes" structure is used to navigate the database.
/// The database contains a number of jars, and each jar contains a number of ingredients.
/// When the database is created, it creates each jar in turn.
/// Each jar then creates its ingredients.
/// Each ingredient is registered with the database by invoking the [`Routes::push`] method.
/// This method assigns it a unique [`IngredientIndex`] and stores some callbacks indicating
/// how to find the ingredient later based only on the index.
pub struct Routes<DB: HasJars> {
/// Vector indexed by ingredient index. Yields the `DynRoute`,
/// a function which can be applied to the `DB::Jars` to yield
/// the `dyn Ingredient.
#[allow(clippy::type_complexity)]
routes: Vec<(Box<DynRoute<DB>>, Box<DynMutRoute<DB>>)>,
/// Indices of routes which need a 'reset' call.
needs_reset: Vec<IngredientIndex>,
}
impl<DB: HasJars> Routes<DB> {
/// Construct an empty ingredients listing.
pub(super) fn new() -> Self {
Routes {
routes: vec![],
needs_reset: vec![],
}
}
/// Adds a new ingredient into the ingredients table, returning
/// the `IngredientIndex` that can be used in a `DatabaseKeyIndex`.
/// This index can then be used to fetch the "route" so that we can
/// dispatch calls to `maybe_changed_after`.
///
/// # Parameters
///
/// * `requires_reset` -- if true, the [`Ingredient::reset_for_new_revision`] method will be called on this ingredient
/// at each new revision. See that method for more information.
/// * `route` -- a closure which, given a database, will identify the ingredient.
/// This closure will be invoked to dispatch calls to `maybe_changed_after`.
/// * `mut_route` -- a closure which identifies the ingredient in a mut
/// database.
pub fn push<I>(
&mut self,
route: impl (Fn(&DB::Jars) -> &I) + Send + Sync + 'static,
mut_route: impl (Fn(&mut DB::Jars) -> &mut I) + Send + Sync + 'static,
) -> IngredientIndex
where
I: Ingredient<DB> + IngredientRequiresReset + 'static,
{
let len = self.routes.len();
self.routes.push((
Box::new(move |jars| route(jars)),
Box::new(move |jars| mut_route(jars)),
));
let index = IngredientIndex::from(len);
if I::RESET_ON_NEW_REVISION {
self.needs_reset.push(index);
}
index
}
/// Given an ingredient index, return the "route"
/// (a function that, given a `&Jars`, returns the ingredient).
pub fn route(&self, index: IngredientIndex) -> &dyn Fn(&DB::Jars) -> &dyn Ingredient<DB> {
&self.routes[index.as_usize()].0
}
/// Given an ingredient index, return the "mut route"
/// (a function that, given an `&mut Jars`, returns the ingredient).
pub fn route_mut(
&self,
index: IngredientIndex,
) -> &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient<DB> {
&self.routes[index.as_usize()].1
}
/// Returns the mut routes for ingredients that need to be reset at the start of each revision.
pub fn reset_routes(
&self,
) -> impl Iterator<Item = &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient<DB>> + '_ {
self.needs_reset.iter().map(|&index| self.route_mut(index))
}
}