use crate::features::FinitePoint3;
use crate::geometry::sampled::{PolylineCurve, PolylineSamples, PolylineVertex};
use crate::math::Point3;
use crate::scalar::FiniteReal;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy, ResourceDimension};
use cadmpeg_core::CodecError;
fn raw(parameters: Option<[f64; 3]>) -> PolylineSamples {
let points = [
Point3::new(0., 0., 0.),
Point3::new(1., 0., 0.),
Point3::new(1., 1., 0.),
];
match parameters {
None => PolylineSamples::Unparameterized {
points: points.to_vec().try_into().expect("three points"),
},
Some(parameters) => PolylineSamples::Parameterized {
vertices: points
.into_iter()
.zip(parameters)
.map(|(point, parameter)| PolylineVertex { parameter, point })
.collect::<Vec<_>>()
.try_into()
.expect("three vertices"),
},
}
}
fn checked(parameters: Option<[f64; 3]>) -> PolylineSamples<FiniteReal, FinitePoint3> {
match raw(parameters) {
PolylineSamples::Unparameterized { points } => PolylineSamples::Unparameterized {
points: points.map(|point| FinitePoint3::new(point).expect("finite")),
},
PolylineSamples::Parameterized { vertices } => PolylineSamples::Parameterized {
vertices: vertices.map(|vertex| PolylineVertex {
parameter: FiniteReal::new(vertex.parameter).expect("finite"),
point: FinitePoint3::new(vertex.point).expect("finite"),
}),
},
}
}
#[test]
fn polyline_construction_admits_every_raw_visit_and_final_storage_once() {
for (parameters, work) in [(None, 6), (Some([0., 1., 2.]), 8), (Some([2., 1., 0.]), 9)] {
for cap in 0..work {
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_work_units = cap;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let Err(CodecError::ResourceLimit(limit)) =
PolylineCurve::new(raw(parameters), 0.25, &ctx)
else {
panic!("each input visit requires admission");
};
assert_eq!(limit.dimension, ResourceDimension::WorkUnits);
assert_eq!(limit.used, cap);
assert_eq!(
limit.operation,
if cap < 3 {
"IR polyline point finiteness"
} else if cap < 6 {
"IR polyline admitted samples"
} else if parameters == Some([2., 1., 0.]) && cap > 6 {
"IR polyline decreasing parameter comparison"
} else {
"IR polyline increasing parameter comparison"
}
);
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}
let bytes = u64::try_from(
3 * if parameters.is_some() {
std::mem::size_of::<PolylineVertex<FiniteReal, FinitePoint3>>()
} else {
std::mem::size_of::<FinitePoint3>()
},
)
.expect("bytes");
for dimension in [
ResourceDimension::RetainedBytes,
ResourceDimension::CollectionItems,
] {
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
match dimension {
ResourceDimension::RetainedBytes => policy.limits.max_retained_bytes = bytes - 1,
ResourceDimension::CollectionItems => policy.limits.max_collection_items = 2,
_ => panic!("test dimension"),
}
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let Err(CodecError::ResourceLimit(limit)) =
PolylineCurve::new(raw(parameters), 0.25, &ctx)
else {
panic!("final sample storage requires admission");
};
assert_eq!(limit.dimension, dimension);
assert_eq!(limit.operation, "IR polyline admitted samples");
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_work_units = work;
policy.limits.max_retained_bytes = bytes;
policy.limits.max_collection_items = 3;
policy.limits.max_materialized_bytes = 0;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let curve = PolylineCurve::new(raw(parameters), 0.25, &ctx)
.expect("exact admission")
.expect("valid");
ctx.finish_session()
.expect("one final allocation and no scratch copies");
assert_eq!(
serde_json::from_str::<PolylineCurve>(&serde_json::to_string(&curve).expect("wire"))
.expect("context-free serde"),
curve
);
}
}
#[test]
fn polyline_construction_moves_checked_rows_and_admits_comparisons() {
for (parameters, work) in [(None, 0), (Some([0., 1., 2.]), 2), (Some([2., 1., 0.]), 3)] {
for cap in 0..work {
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_work_units = cap;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let Err(CodecError::ResourceLimit(limit)) =
PolylineCurve::from_checked_samples(checked(parameters), 0.25, &ctx)
else {
panic!("comparisons require caller admission");
};
assert_eq!(limit.dimension, ResourceDimension::WorkUnits);
assert_eq!(limit.used, cap);
assert_eq!(
limit.operation,
if parameters == Some([2., 1., 0.]) && cap > 0 {
"IR polyline decreasing parameter comparison"
} else {
"IR polyline increasing parameter comparison"
}
);
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}
let samples = checked(parameters);
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_work_units = work;
policy.limits.max_retained_bytes = 0;
policy.limits.max_collection_items = 0;
policy.limits.max_materialized_bytes = 0;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let curve = match samples {
PolylineSamples::Unparameterized { points } => {
let address = points.as_ptr();
let curve = PolylineCurve::from_checked_samples(
PolylineSamples::Unparameterized { points },
0.25,
&ctx,
)
.expect("only comparisons")
.expect("valid");
let PolylineSamples::Unparameterized { points } = &curve.samples else {
panic!("same sample lane");
};
assert_eq!(points.as_ptr(), address);
curve
}
PolylineSamples::Parameterized { vertices } => {
let address = vertices.as_ptr();
let curve = PolylineCurve::from_checked_samples(
PolylineSamples::Parameterized { vertices },
0.25,
&ctx,
)
.expect("only comparisons")
.expect("valid");
let PolylineSamples::Parameterized { vertices } = &curve.samples else {
panic!("same sample lane");
};
assert_eq!(vertices.as_ptr(), address);
curve
}
};
assert_eq!(curve.chordal_deflection().get(), 0.25);
ctx.finish_session().expect("no sample allocation or copy");
}
}
#[test]
fn polyline_construction_preserves_diagnostic_order_and_refusal() {
let ctx = cadmpeg_test_support::service_decode_context();
let mut samples = raw(Some([f64::INFINITY, 0., 0.]));
let PolylineSamples::Parameterized { vertices } = &mut samples else {
panic!("parameterized fixture");
};
vertices[0].point.x = f64::INFINITY;
assert_eq!(
PolylineCurve::new(samples, -1., &ctx)
.expect("admission")
.expect_err("points precede deflection")
.to_string(),
"points must be finite"
);
assert_eq!(
PolylineCurve::new(raw(Some([f64::INFINITY, 0., 0.])), -1., &ctx)
.expect("admission")
.expect_err("deflection precedes parameters")
.to_string(),
"chordal_deflection must be finite and non-negative"
);
assert_eq!(
PolylineCurve::new(raw(Some([f64::INFINITY, 0., 0.])), 0., &ctx)
.expect("admission")
.expect_err("nonfinite parameter")
.to_string(),
"parameters must be finite and strictly monotonic"
);
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_work_units =
u64::try_from("chordal_deflection must be finite and non-negative".len())
.expect("diagnostic copy work");
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
assert_eq!(
PolylineCurve::from_checked_samples(checked(Some([0., 0., 0.])), -1., &ctx)
.expect("no parameter scan before deflection")
.expect_err("negative deflection")
.to_string(),
"chordal_deflection must be finite and non-negative"
);
ctx.finish_session()
.expect("only the diagnostic text is copied");
let arena = DecodeArena::new();
policy.limits.max_retained_bytes = 0;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("root");
let samples = PolylineSamples::Unparameterized {
points: vec![Point3::new(f64::INFINITY, 0., 0.)]
.try_into()
.expect("nonempty"),
};
let Err(CodecError::ResourceLimit(limit)) = PolylineCurve::new(samples, -1., &ctx) else {
panic!("count diagnostic needs retained admission before any sample visit");
};
assert_eq!(limit.dimension, ResourceDimension::RetainedBytes);
assert_eq!(limit.operation, "IR sampled construction refusal");
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}