#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
macro_rules! family_cfg {
(for $name:literal; $($item:item)*) => {
$(
#[cfg(target_family = $name)]
$item
)*
};
(for !$name:literal; $($item:item)*) => {
$(
#[cfg(not(target_family = $name))]
$item
)*
};
}
macro_rules! feature_cfg {
(for $name:literal; $($item:item)*) => {
$(
#[cfg(feature = $name)]
$item
)*
};
(for !$name:literal; $($item:item)*) => {
$(
#[cfg(not(feature = $name))]
$item
)*
};
}
use crate::reexported::{iter, Box, Map, Mutex, NonZeroUsize, Set, Vec};
use async_trait::async_trait;
use derive_more::{From, Into};
use futures::stream::{FuturesUnordered, StreamExt};
pub mod reexported;
#[cfg(all(feature = "js-bindings", target_family = "wasm"))]
mod js;
#[cfg(test)]
mod test;
#[async_trait]
pub trait Problem {
type Error;
async fn direct_dependencies(
&self,
id: FragmentId,
dependecies: &mut Vec<FragmentId>,
);
async fn evaluate(&self, id: FragmentId) -> Result<(), Self::Error>;
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into,
)]
pub struct FragmentId(pub usize);
pub struct Solver<P> {
state: Mutex<State>,
dependencies: Mutex<Vec<FragmentId>>,
problem_instance: P,
}
struct State {
to_solve: Set<FragmentId>,
pending_on: Map<FragmentId, Vec<FragmentId>>,
punted: Map<FragmentId, usize>,
solved: Set<FragmentId>,
}
impl<P> Solver<P> {
pub fn new(problem_instance: P) -> Self {
Self {
state: Mutex::new(State {
to_solve: Set::new(),
pending_on: Map::new(),
punted: Map::new(),
solved: Set::new(),
}),
dependencies: Mutex::new(Vec::new()),
problem_instance,
}
}
pub fn into_problem_instance(self) -> P {
self.problem_instance
}
pub async fn status(&self) -> Status {
let state = self.state.lock().await;
if state.to_solve.is_empty() {
if state.punted.is_empty() {
Status::Done
} else {
Status::DoneWithCycles
}
} else {
Status::Pending
}
}
pub async fn enqueue_fragment(&self, id: FragmentId) -> &Self {
self.state.lock().await.to_solve.insert(id);
self
}
pub async fn punted_iter(&self) -> Vec<FragmentId> {
self.state.lock().await.punted.keys().copied().collect()
}
}
impl<P> Solver<P>
where
P: Problem,
{
pub async fn assume_evaluated(&self, id: FragmentId) -> &Self {
self.mark_solved(id, &mut *self.state.lock().await);
self
}
pub async fn run(
&self,
concurrency: NonZeroUsize,
) -> Result<Vec<FragmentId>, P::Error> {
let mut steps = iter::repeat_with(|| self.step())
.take(concurrency.into())
.collect::<FuturesUnordered<_>>();
loop {
match steps.next().await.unwrap() {
Ok(false) => break,
Ok(true) => steps.push(self.step()),
Err(err) => return Err(err),
}
}
while let Some(res) = steps.next().await {
if let Err(err) = res {
return Err(err);
}
}
Ok(self.punted_iter().await)
}
pub async fn step(&self) -> Result<bool, P::Error> {
let item = {
let mut state = self.state.lock().await;
state
.to_solve
.iter()
.next()
.copied()
.map(|x| state.to_solve.take(&x).unwrap())
};
match item {
Some(id) => {
let mut dependencies = self.dependencies.lock().await;
dependencies.clear();
self.problem_instance
.direct_dependencies(id, &mut dependencies)
.await;
let mut state = self.state.lock().await;
dependencies.retain(|x| !state.solved.contains(x));
if dependencies.is_empty() {
drop(dependencies);
drop(state);
match self.problem_instance.evaluate(id).await {
Ok(()) => {
self.mark_solved(id, &mut *self.state.lock().await);
Ok(true)
}
Err(err) => Err(err),
}
} else {
self.mark_punted(id, &dependencies, &mut state);
Ok(true)
}
}
None => Ok(false),
}
}
fn mark_solved(&self, id: FragmentId, state: &mut State) {
state.solved.insert(id);
if let Some(dependents) = state.pending_on.remove(&id) {
for dependent in dependents {
if *state.punted.get(&dependent).unwrap() == 1 {
state.punted.remove(&dependent);
state.to_solve.insert(dependent);
} else {
*state.punted.get_mut(&dependent).unwrap() -= 1;
}
}
}
}
fn mark_punted(
&self,
id: FragmentId,
dependencies: &[FragmentId],
state: &mut State,
) {
state.punted.insert(id, dependencies.len());
for dependency in dependencies.iter().copied() {
if dependency != id
&& !state.solved.contains(&dependency)
&& !state.punted.contains_key(&dependency)
{
state.to_solve.insert(dependency);
}
state.pending_on.entry(dependency).or_default().push(id);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Status {
Done,
DoneWithCycles,
Pending,
}