use crate::map::FxHashSet;
use std::ops::ControlFlow;
type F<Cx, T, B> = fn(Cx, &mut CycleDetector<Cx, T, B>, T) -> CycleDetectorResult<B, T>;
#[macro_export]
macro_rules! cdr_try {
($e:expr) => {
match $e {
CycleDetectorResult::Continue => {}
e => return e,
}
};
}
pub use cdr_try;
pub struct CycleDetector<Cx, T, B> {
cx: Cx,
first_cycle_vertex: Option<T>,
depth: u32,
processed: FxHashSet<T>,
processing: FxHashSet<T>,
f: F<Cx, T, B>,
}
pub enum CycleDetectorResult<B, T> {
Continue,
Break(B),
Cycle(T),
}
impl<B, T> CycleDetectorResult<B, T> {
pub fn to_controlflow(self) -> ControlFlow<Self> {
match self {
Self::Continue => ControlFlow::Continue(()),
_ => ControlFlow::Break(self),
}
}
#[inline]
pub fn is_continue(&self) -> bool {
matches!(self, Self::Continue)
}
#[inline]
pub fn is_err(&self) -> bool {
!self.is_continue()
}
#[inline]
pub fn break_value(self) -> Option<B> {
match self {
Self::Break(b) => Some(b),
_ => None,
}
}
#[inline]
pub fn cycle_value(self) -> Option<T> {
match self {
Self::Cycle(t) => Some(t),
_ => None,
}
}
}
impl<Cx: Copy, T: Copy + Eq + std::hash::Hash, B> CycleDetector<Cx, T, B> {
#[inline]
pub fn detect(cx: Cx, vertex: T, f: F<Cx, T, B>) -> CycleDetectorResult<B, T> {
Self::new(cx, f).run(vertex)
}
#[inline]
pub fn new(cx: Cx, f: F<Cx, T, B>) -> Self {
Self {
cx,
first_cycle_vertex: None,
depth: 0,
processed: FxHashSet::default(),
processing: FxHashSet::default(),
f,
}
}
#[inline]
pub fn run(&mut self, vertex: T) -> CycleDetectorResult<B, T> {
cdr_try!(self.cycle_result());
if self.processed.contains(&vertex) {
return CycleDetectorResult::Continue;
}
if !self.processing.insert(vertex) {
self.first_cycle_vertex = Some(vertex);
return CycleDetectorResult::Cycle(vertex);
}
self.depth += 1;
cdr_try!((self.f)(self.cx, self, vertex));
self.depth -= 1;
self.processing.remove(&vertex);
self.processed.insert(vertex);
self.cycle_result()
}
#[inline]
fn cycle_result(&self) -> CycleDetectorResult<B, T> {
match self.first_cycle_vertex {
Some(vx) => CycleDetectorResult::Cycle(vx),
None => CycleDetectorResult::Continue,
}
}
#[inline]
pub fn depth(&self) -> usize {
self.depth as usize
}
}