verde 0.2.2

A refreshingly simple incremental computation library
Documentation
//! ### Storage routing
//!
//! The database has two tiers of storing data:
//! - The database itself, storing storage structs.
//! - Storage structs, which store the actual type storages.
//!
//! This allows for multi-crate compilation, where each crate exposes a storage struct, with only the main driver crate
//! using the database.

/// A type-erased route through the database storage.
/// Uniquely identifies the storage for a particular [`Tracked`](crate::Tracked) type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Route {
	/// The index of the storage struct in the database.
	/// A storage of `0` is reserved for fake queries generated by `Db::set_input`.
	pub storage: u16,
	/// The index of the type storage in the storage struct.
	pub index: u16,
}

impl Route {
	pub(crate) fn input() -> Self { Self { storage: 0, index: 1 } }
}

#[cfg(not(any(feature = "test", test)))]
mod normal {
	use std::any::TypeId;

	use rustc_hash::FxHashMap;

	use crate::{
		internal::{storage::Route, Storable},
		Db,
	};

	/// A static table that maps [`TypeId`]s to [`Route`]s, generated at database initialization.
	/// This is required because `TypeId`s are not guaranteed to be stable across compilations, while `Route`s are.
	#[derive(Default)]
	pub struct RoutingTable {
		routes: FxHashMap<TypeId, Route>,
		type_names: FxHashMap<Route, &'static str>,
	}

	impl RoutingTable {
		pub fn route<T: Storable>(&self) -> Route { self.route_for(TypeId::of::<T>(), std::any::type_name::<T>()) }

		pub fn route_for(&self, id: TypeId, name: &'static str) -> Route {
			match self.routes.get(&id) {
				Some(route) => *route,
				None => panic!("Database does not contain `{}`", name),
			}
		}

		pub fn name(&self, route: Route) -> &str { self.type_names.get(&route).unwrap() }

		pub fn generate_for_db<T: Db>() -> Self {
			#[cfg(feature = "debug")]
			{
				use std::{thread, time::Duration};

				use parking_lot::deadlock;

				thread::spawn(move || loop {
					thread::sleep(Duration::from_secs(2));
					let deadlocks = deadlock::check_deadlock();
					if deadlocks.is_empty() {
						continue;
					}

					eprintln!("{} deadlocks detected", deadlocks.len());
					for (i, threads) in deadlocks.iter().enumerate() {
						eprintln!("Deadlock #{}", i);
						for t in threads {
							eprintln!("Thread {:#?}", t.thread_id());
							eprintln!("{:#?}", t.backtrace());
						}
						eprintln!()
					}
					std::process::abort();
				});
			}

			let mut builder = RoutingTableBuilder::default();
			T::init_routing(&mut builder);
			builder.finish()
		}
	}

	#[derive(Default)]
	pub struct RoutingTableBuilder {
		routes: FxHashMap<TypeId, Route>,
		type_names: FxHashMap<Route, &'static str>,
	}

	impl RoutingTableBuilder {
		pub fn start_route(&mut self, storage: u16) -> RouteBuilder {
			RouteBuilder {
				routes: &mut self.routes,
				type_names: &mut self.type_names,
				storage,
			}
		}

		pub fn finish(self) -> RoutingTable {
			RoutingTable {
				routes: self.routes,
				type_names: self.type_names,
			}
		}
	}

	pub struct RouteBuilder<'a> {
		routes: &'a mut FxHashMap<TypeId, Route>,
		type_names: &'a mut FxHashMap<Route, &'static str>,
		storage: u16,
	}

	impl RouteBuilder<'_> {
		pub fn add<T: Storable>(&mut self, index: u16) {
			let route = Route {
				storage: self.storage,
				index,
			};
			let id = TypeId::of::<T>();

			if self.routes.insert(id, route).is_some() {
				panic!("Duplicate route for type `{}`", std::any::type_name::<T>());
			}
			self.type_names.insert(route, std::any::type_name::<T>());
		}
	}
}

#[cfg(any(feature = "test", test))]
mod test {
	use std::{
		any::TypeId,
		sync::atomic::{AtomicU16, Ordering},
	};

	use parking_lot::{Mutex, RwLock};
	use rustc_hash::FxHashMap;

	use crate::{
		internal::{storage::Route, Storable},
		test::StorageType,
		Db,
	};

	type GenFunc = Box<dyn FnOnce() -> (StorageType, u16) + Send>;

	/// A static table that maps [`TypeId`]s to [`Route`]s, generated at database initialization.
	/// This is required because `TypeId`s are not guaranteed to be stable across compilations, while `Route`s are.
	#[derive(Default)]
	pub struct RoutingTable {
		routes: RwLock<FxHashMap<TypeId, Route>>,
		type_names: RwLock<FxHashMap<Route, &'static str>>,
		dynamic_storage_index: u16,
		next_route_index: AtomicU16,
		make: Mutex<Vec<GenFunc>>,
	}

	impl RoutingTable {
		pub fn route<T: Storable>(&self) -> Route
		where
			StorageType: From<<T as Storable>::Storage>,
		{
			let route = self.route_for(TypeId::of::<T>(), "");
			self.make
				.lock()
				.push(Box::new(move || (T::Storage::default().into(), route.index)));
			route
		}

		pub fn route_for(&self, id: TypeId, _: &'static str) -> Route {
			*self.routes.write().entry(id).or_insert_with(|| Route {
				storage: self.dynamic_storage_index,
				index: self.next_route_index.fetch_add(1, Ordering::Relaxed),
			})
		}

		pub fn name(&self, route: Route) -> &str { self.type_names.read().get(&route).unwrap() }

		pub fn make(&self) -> Vec<GenFunc> {
			let mut m = self.make.lock();
			std::mem::take(&mut *m)
		}

		pub fn generate_for_db<T: Db>() -> Self {
			let mut builder = RoutingTableBuilder::default();
			T::init_routing(&mut builder);
			builder.finish()
		}
	}

	pub struct RoutingTableBuilder {
		routes: FxHashMap<TypeId, Route>,
		type_names: FxHashMap<Route, &'static str>,
		dynamic_storage_index: u16,
	}

	impl Default for RoutingTableBuilder {
		fn default() -> Self {
			Self {
				routes: FxHashMap::default(),
				type_names: FxHashMap::default(),
				dynamic_storage_index: 1,
			}
		}
	}

	impl RoutingTableBuilder {
		pub fn start_route(&mut self, storage: u16) -> RouteBuilder {
			self.dynamic_storage_index = self.dynamic_storage_index.max(storage + 1);
			RouteBuilder {
				routes: &mut self.routes,
				type_names: &mut self.type_names,
				storage,
			}
		}

		pub fn finish(self) -> RoutingTable {
			RoutingTable {
				routes: RwLock::new(self.routes),
				type_names: RwLock::new(self.type_names),
				dynamic_storage_index: self.dynamic_storage_index,
				next_route_index: AtomicU16::new(0),
				make: Mutex::new(Vec::new()),
			}
		}
	}

	pub struct RouteBuilder<'a> {
		routes: &'a mut FxHashMap<TypeId, Route>,
		type_names: &'a mut FxHashMap<Route, &'static str>,
		storage: u16,
	}

	impl RouteBuilder<'_> {
		pub fn add<T: Storable>(&mut self, index: u16) {
			let route = Route {
				storage: self.storage,
				index,
			};
			let id = TypeId::of::<T>();

			if self.routes.insert(id, route).is_some() {
				panic!("Duplicate route for type `{}`", std::any::type_name::<T>());
			}
			self.type_names.insert(route, std::any::type_name::<T>());
		}
	}
}

#[cfg(not(any(feature = "test", test)))]
pub use normal::*;
#[cfg(any(feature = "test", test))]
pub use test::*;