use std::cmp::Ordering;
use super::Chain;
use super::graph::Axis;
pub(crate) struct Ctx<'a> {
pub chains: &'a [Option<Chain>],
pub ests: &'a [Vec<f64>],
}
#[derive(Clone, Copy)]
struct Cursor {
ci: usize,
ri: usize,
e: usize,
}
#[derive(Clone, Copy, Debug)]
enum Ev {
Turn { q: f64, n: i8, next: (Axis, usize) },
Term { q: f64, pin: f64 },
}
fn neighbour(chain: &Chain, ri: usize, e: usize) -> Option<usize> {
if e == 1 {
(ri + 1 < chain.runs.len()).then_some(ri + 1)
} else {
ri.checked_sub(1)
}
}
impl Ctx<'_> {
fn chain(&self, ci: usize) -> &Chain {
self.chains[ci].as_ref().expect("routed chain")
}
fn est(&self, ci: usize, ri: usize) -> f64 {
self.ests[ci][ri]
}
fn end_q(&self, ci: usize, ri: usize, e: usize) -> f64 {
let chain = self.chain(ci);
match neighbour(chain, ri, e) {
Some(ni) => self.est(ci, ni),
None => chain.ends[e].side_coord(),
}
}
fn event(&self, cur: Cursor) -> Ev {
let chain = self.chain(cur.ci);
match neighbour(chain, cur.ri, cur.e) {
None => Ev::Term {
q: chain.ends[cur.e].side_coord(),
pin: self.est(cur.ci, cur.ri),
},
Some(ni) => {
let near = self.est(cur.ci, cur.ri);
let far = self.end_q(cur.ci, ni, cur.e);
Ev::Turn {
q: self.est(cur.ci, ni),
n: if far >= near { 1 } else { -1 },
next: (chain.runs[ni].axis, chain.runs[ni].chan),
}
}
}
}
fn tie(&self, a: usize, b: usize) -> Ordering {
self.chain(a).link.cmp(&self.chain(b).link).then(a.cmp(&b))
}
}
fn advance(cur: Cursor) -> Cursor {
Cursor {
ri: if cur.e == 1 { cur.ri + 1 } else { cur.ri - 1 },
..cur
}
}
fn flip(o: Ordering, m: i8) -> Ordering {
if m < 0 { o.reverse() } else { o }
}
fn walk(ctx: &Ctx, a: Cursor, b: Cursor, dir: i8) -> (Ordering, bool) {
let (mut ca, mut cb) = (a, b);
let mut sigma = dir;
let mut m: i8 = 1;
loop {
let (ea, eb) = (ctx.event(ca), ctx.event(cb));
match (ea, eb) {
(
Ev::Turn {
q: qa,
n: na,
next: xa,
},
Ev::Turn {
q: qb,
n: nb,
next: xb,
},
) if qa.total_cmp(&qb) == Ordering::Equal && na == nb && xa == xb => {
m *= -(sigma * na);
sigma = na;
ca = advance(ca);
cb = advance(cb);
}
(Ev::Turn { q: qa, n: na, .. }, Ev::Turn { q: qb, n: nb, .. }) => {
let (sa, sb) = (qa * f64::from(sigma), qb * f64::from(sigma));
let o = match sa.total_cmp(&sb) {
Ordering::Less => {
if na > 0 {
Ordering::Greater
} else {
Ordering::Less
}
}
Ordering::Greater => {
if nb > 0 {
Ordering::Less
} else {
Ordering::Greater
}
}
Ordering::Equal if na != nb => na.cmp(&nb),
Ordering::Equal => return (flip(ctx.tie(ca.ci, cb.ci), m), false),
};
return (flip(o, m), true);
}
(Ev::Turn { q, n, .. }, Ev::Term { q: qt, .. }) => {
let o = if n > 0 {
Ordering::Greater
} else {
Ordering::Less
};
return (flip(o, m), qt * f64::from(sigma) >= q * f64::from(sigma));
}
(Ev::Term { q: qt, .. }, Ev::Turn { q, n, .. }) => {
let o = if n > 0 {
Ordering::Less
} else {
Ordering::Greater
};
return (flip(o, m), qt * f64::from(sigma) >= q * f64::from(sigma));
}
(Ev::Term { pin: pa, .. }, Ev::Term { pin: pb, .. }) => {
let o = pa.total_cmp(&pb);
if o != Ordering::Equal {
return (flip(o, m), true);
}
return (flip(ctx.tie(ca.ci, cb.ci), m), false);
}
}
}
}
fn convention(ctx: &Ctx, a: (usize, usize), b: (usize, usize)) -> Ordering {
let led = ctx.tie(a.0, b.0) == Ordering::Less;
let (ci, ri) = if led { a } else { b };
let downstream = ctx.end_q(ci, ri, 1) >= ctx.end_q(ci, ri, 0);
let leader = match (ctx.chain(ci).runs[ri].axis, downstream) {
(Axis::V, true) | (Axis::H, false) => Ordering::Greater,
_ => Ordering::Less,
};
if led { leader } else { leader.reverse() }
}
fn cursor(ctx: &Ctx, (ci, ri): (usize, usize), dir: i8) -> Cursor {
let plus = usize::from(ctx.end_q(ci, ri, 1) >= ctx.end_q(ci, ri, 0));
Cursor {
ci,
ri,
e: if dir > 0 { plus } else { 1 - plus },
}
}
pub(crate) fn cmp_runs(ctx: &Ctx, a: (usize, usize), b: (usize, usize)) -> Ordering {
if a == b {
return Ordering::Equal;
}
if a.0 == b.0 {
return ctx
.est(a.0, a.1)
.total_cmp(&ctx.est(b.0, b.1))
.then(a.1.cmp(&b.1));
}
let (op, gp) = walk(ctx, cursor(ctx, a, 1), cursor(ctx, b, 1), 1);
if gp {
return op;
}
let (om, gm) = walk(ctx, cursor(ctx, a, -1), cursor(ctx, b, -1), -1);
if gm {
return om;
}
convention(ctx, a, b)
}