address review: in_span uses the MAX_CHAIN-bounded span, forward key range cannot overflow

This commit is contained in:
Dylan M. Taylor
2026-11-03 23:57:19 +00:00
parent 6e8030834b
commit 8fb24b6cba
2 changed files with 30 additions and 7 deletions
+16 -7
View File
@@ -155,8 +155,16 @@ impl RotoData {
pub fn strokes_of(&self, frame: i64, object: u32) -> Vec<&Stroke> {
self.strokes.iter().filter(|s| s.frame == frame && (s.object == object || matches!(s.kind, StrokeKind::Refine | StrokeKind::RefineErase))).collect()
}
/// The stored span cut to at most MAX_CHAIN frames either side of the base: a project's
/// numbers must not make one render build billions of keys. Frames past it have no key.
fn bounded_span(&self, base: i64) -> [i64; 2] {
[self.span[0].max(base.saturating_sub(MAX_CHAIN)), self.span[1].min(base.saturating_add(MAX_CHAIN))]
}
pub fn in_span(&self, f: i64) -> bool {
self.base.is_some() && f >= self.span[0] && f <= self.span[1]
self.base.is_some_and(|b| {
let span = self.bounded_span(b);
f >= span[0] && f <= span[1]
})
}
/// Add a stroke: the first segmentation stroke sets the base frame and a span of
/// `default_span` frames each side (clamped to `limits`); strokes outside the span extend it.
@@ -201,12 +209,13 @@ impl RotoData {
let k0 = key(seed, base);
out.insert(base, k0);
let mut k = k0;
// A stored span reaches at most MAX_CHAIN frames either side of the base: a project's
// numbers must not make one render build billions of keys.
let span = [self.span[0].max(base.saturating_sub(MAX_CHAIN)), self.span[1].min(base.saturating_add(MAX_CHAIN))];
for f in base + 1..=span[1] {
k = key(k, f);
out.insert(f, k);
let span = self.bounded_span(base);
// `base + 1` only when there is a frame after the base (it overflows at i64::MAX).
if base < span[1] {
for f in base + 1..=span[1] {
k = key(k, f);
out.insert(f, k);
}
}
k = k0;
for f in (span[0]..base).rev() {
+14
View File
@@ -731,3 +731,17 @@ fn a_stored_span_reaches_a_bounded_number_of_frames() {
assert_eq!(k.len() as i64, 2 * super::MAX_CHAIN + 1);
assert_eq!(k.keys().next(), Some(&-super::MAX_CHAIN));
}
#[test]
fn in_span_matches_the_frames_that_have_keys_and_extreme_bases_do_not_overflow() {
let d = RotoData { base: Some(0), span: [i64::MIN, i64::MAX], strokes: vec![] };
assert!(d.in_span(super::MAX_CHAIN) && !d.in_span(super::MAX_CHAIN + 1));
assert!(d.in_span(-super::MAX_CHAIN) && !d.in_span(-super::MAX_CHAIN - 1));
assert_eq!(d.source_of(super::MAX_CHAIN + 1), None);
assert!(d.chain_keys(1).keys().all(|f| d.in_span(*f)));
// A base at the end of i64: no forward frames, and no `base + 1` overflow.
let d = RotoData { base: Some(i64::MAX), span: [i64::MIN, i64::MAX], strokes: vec![] };
let k = d.chain_keys(1);
assert_eq!(k.len() as i64, super::MAX_CHAIN + 1);
assert!(k.contains_key(&i64::MAX));
}