use super::registry::FactRegistry;
use super::view::{Declared, Tables, count_production};
use super::{Demand, FactConsumer, FactError, FactGroup, FactTable, FactView};
pub struct FactManager<'r, A: ?Sized + 'static> {
pub(crate) registry: &'r FactRegistry<A>,
pub(crate) tables: Tables,
}
impl<'r, A: ?Sized + 'static> FactManager<'r, A> {
#[must_use]
pub fn new(registry: &'r FactRegistry<A>) -> Self {
Self {
registry,
tables: Tables::new(),
}
}
#[must_use]
pub fn computed(&self) -> Demand {
self.tables.computed
}
pub fn compute(&mut self, artifact: &A, demand: Demand) -> Result<Demand, FactError> {
let pending = self.pending(demand)?;
compute_into(self.registry, &mut self.tables, artifact, pending, true);
Ok(pending)
}
pub fn prepare<C: FactConsumer>(&mut self, artifact: &A) -> Result<FactView<'_>, FactError> {
self.compute(artifact, C::DEMAND)?;
Ok(self.view::<C>())
}
pub fn prepare_owned<C: FactConsumer, G: FactGroup>(
mut self,
artifact: &A,
) -> Result<FactTable<G>, FactError> {
self.prepare::<C>(artifact)?.get::<G>()?;
let Some(table) = self
.tables
.slots
.get_mut(G::ID.index() as usize)
.and_then(Option::take)
else {
return Err(FactError::NotComputed { group: G::ID });
};
table
.downcast::<FactTable<G>>()
.map(|table| *table)
.map_err(|_| FactError::TypeMismatch {
group: G::ID,
expected: core::any::type_name::<G>(),
})
}
#[must_use]
pub fn view<C: FactConsumer>(&self) -> FactView<'_> {
FactView::new(&self.tables, Declared::new(C::NAME, C::DEMAND))
}
fn pending(&self, demand: Demand) -> Result<Demand, FactError> {
let closure = self.registry.closure(demand);
match closure.minus(self.registry.registered()).iter().next() {
Some(group) => Err(FactError::Unregistered { group }),
None => Ok(closure.minus(self.tables.computed)),
}
}
}
pub(crate) fn compute_into<A: ?Sized + 'static>(
registry: &FactRegistry<A>,
tables: &mut Tables,
artifact: &A,
pending: Demand,
count: bool,
) {
if pending.is_empty() {
return;
}
let producers = registry.producers();
let top = producers
.iter()
.map(|entry| entry.desc.stratum)
.max()
.unwrap_or(0);
for stratum in 0..=top {
for entry in producers {
let desc = entry.desc;
if desc.stratum != stratum || !pending.contains(desc.id) {
continue;
}
let inputs = FactView::new(tables, Declared::new(desc.name, desc.depends));
let table = (entry.run)(artifact, &inputs);
if count {
count_production(desc.id);
}
tables.store(desc.id, table);
}
}
}