use core::cmp::Ordering;
use core::fmt;
use crate::{MeshBuffer, Real};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RunPair {
RepeatedCall,
ReusedBuffer,
}
impl fmt::Display for RunPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RepeatedCall => write!(f, "the same call made twice"),
Self::ReusedBuffer => write!(f, "a reused buffer against a fresh one"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Divergence<R: Real> {
VertexCount {
first: u64,
second: u64,
},
IndexCount {
first: u64,
second: u64,
},
Position {
vertex: u64,
axis: usize,
first: R,
second: R,
},
Normal {
vertex: u64,
axis: usize,
first: R,
second: R,
},
Index {
at: u64,
first: u32,
second: u32,
},
}
impl<R: Real> fmt::Display for Divergence<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::VertexCount { first, second } => {
write!(f, "vertex count: {first} then {second}")
}
Self::IndexCount { first, second } => {
write!(f, "index count: {first} then {second}")
}
Self::Position {
vertex,
axis,
first,
second,
} => write!(f, "position[{vertex}][{axis}]: {first:?} then {second:?}"),
Self::Normal {
vertex,
axis,
first,
second,
} => write!(f, "normal[{vertex}][{axis}]: {first:?} then {second:?}"),
Self::Index { at, first, second } => {
write!(f, "index[{at}]: {first} then {second}")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DeterminismReport<R: Real> {
pub vertices: u64,
pub triangles: u64,
pub divergence: Option<(RunPair, Divergence<R>)>,
}
impl<R: Real> DeterminismReport<R> {
#[must_use]
pub fn is_deterministic(&self) -> bool {
self.divergence.is_none()
}
pub fn panic_if_divergent(&self) {
if let Some((pair, divergence)) = self.divergence {
panic!("non-deterministic output between {pair}: {divergence}");
}
}
}
impl<R: Real> fmt::Display for DeterminismReport<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "determinism report")?;
writeln!(f, " vertices {:8}", self.vertices)?;
writeln!(f, " triangles {:8}", self.triangles)?;
match self.divergence {
None => write!(f, " DETERMINISTIC"),
Some((pair, d)) => write!(f, " DIVERGED between {pair}\n {d}"),
}
}
}
#[inline]
fn differs<R: Real>(a: R, b: R) -> bool {
a.total_cmp(&b) != Ordering::Equal
}
fn compare<R: Real>(first: &MeshBuffer<R>, second: &MeshBuffer<R>) -> Option<Divergence<R>> {
if first.positions.len() != second.positions.len() {
return Some(Divergence::VertexCount {
first: first.positions.len() as u64,
second: second.positions.len() as u64,
});
}
if first.indices.len() != second.indices.len() {
return Some(Divergence::IndexCount {
first: first.indices.len() as u64,
second: second.indices.len() as u64,
});
}
for (v, (a, b)) in first.positions.iter().zip(&second.positions).enumerate() {
for axis in 0..3 {
if differs(a[axis], b[axis]) {
return Some(Divergence::Position {
vertex: v as u64,
axis,
first: a[axis],
second: b[axis],
});
}
}
}
for (v, (a, b)) in first.normals.iter().zip(&second.normals).enumerate() {
for axis in 0..3 {
if differs(a[axis], b[axis]) {
return Some(Divergence::Normal {
vertex: v as u64,
axis,
first: a[axis],
second: b[axis],
});
}
}
}
if first.normals.len() != second.normals.len() {
return Some(Divergence::VertexCount {
first: first.normals.len() as u64,
second: second.normals.len() as u64,
});
}
for (i, (a, b)) in first.indices.iter().zip(&second.indices).enumerate() {
if a != b {
return Some(Divergence::Index {
at: i as u64,
first: *a,
second: *b,
});
}
}
None
}
pub fn check_determinism<R, F>(mut extract: F) -> DeterminismReport<R>
where
R: Real,
F: FnMut(&mut MeshBuffer<R>),
{
let mut first = MeshBuffer::<R>::new();
extract(&mut first);
let mut report = DeterminismReport {
vertices: first.positions.len() as u64,
triangles: (first.indices.len() / 3) as u64,
divergence: None,
};
let mut second = MeshBuffer::<R>::new();
extract(&mut second);
if let Some(d) = compare(&first, &second) {
report.divergence = Some((RunPair::RepeatedCall, d));
return report;
}
let mut reused = MeshBuffer::<R>::new();
extract(&mut reused);
reused.reset();
extract(&mut reused);
if let Some(d) = compare(&first, &reused) {
report.divergence = Some((RunPair::ReusedBuffer, d));
}
report
}
#[cfg(test)]
mod tests;