mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 18:18:17 -06:00
G5: a playing preview prefetches past the frames it already cached
The playback prefetch only looked ahead*3 frames (96 with 16 workers) for uncached frames. After a loop or a restart from the start those were all cached, so the workers stood idle until the playhead came within that window of the end of the cached run, then the preview stalled. A playing preview now looks as far ahead as the RAM preview holds (and the range spans) for the next uncached frames, still queuing at most ahead at a time; eviction already spares the frames the playhead reaches first (#595). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1238,8 +1238,16 @@ impl EffectcraftApp {
|
||||
}
|
||||
let step = plan.map_or(1, |p| p.step);
|
||||
// Never further ahead than the RAM preview holds: those frames would evict the ones the
|
||||
// preview shows first, or be evicted before it gets there (#595).
|
||||
let window = (ahead * 3).min(self.frames_that_fit(&c, scale) - 1);
|
||||
// preview shows first, or be evicted before it gets there (#595). A playing preview
|
||||
// looks that far for the next uncached frames: after a loop the frames just ahead are
|
||||
// cached, and a shorter window left the workers idle until the playhead came near the
|
||||
// end of the cached run.
|
||||
let fit_ahead = self.frames_that_fit(&c, scale) - 1;
|
||||
let window = match plan.filter(|_| self.playback.playing) {
|
||||
Some(p) => fit_ahead.min((p.end - p.start) / p.step.max(1) + 1),
|
||||
None => (ahead * 3).min(fit_ahead),
|
||||
};
|
||||
let cached = self.frames.cached_frames(&series);
|
||||
for k in 0..window {
|
||||
if queued >= ahead as usize || !video {
|
||||
break;
|
||||
@@ -1257,7 +1265,7 @@ impl EffectcraftApp {
|
||||
f
|
||||
}
|
||||
};
|
||||
if !self.frames.is_cached(&FrameKey { frame: f, ..series }) {
|
||||
if cached.binary_search(&f).is_err() {
|
||||
self.request_frame(cid, f, scale);
|
||||
queued += 1;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,45 @@ fn cache_frames_when_idle_fills_the_work_area() {
|
||||
assert_eq!(n, 240, "the work area is cached while idle");
|
||||
}
|
||||
|
||||
/// A preview that plays into frames it already cached (after a loop) keeps rendering the next
|
||||
/// uncached ones: the prefetch window used to end within the cached run, and the workers stood
|
||||
/// idle until the playhead came near its end.
|
||||
#[test]
|
||||
fn playback_prefetches_past_the_cached_frames_ahead() {
|
||||
let mut h = harness();
|
||||
let cid = h.state().session.active_comp_id().unwrap();
|
||||
let series = h.state().shown_series(cid);
|
||||
let scale = series.scale as f64 / 1000.0;
|
||||
// The first 120 frames (5 s) are cached: more than the widest old window (16 workers × 6).
|
||||
for f in 0..120 {
|
||||
h.state().request_frame(cid, f, scale);
|
||||
}
|
||||
for _ in 0..4000 {
|
||||
if h.state().frames.cached_frames(&series).len() >= 120 && h.state().frames.inflight() == 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
}
|
||||
assert_eq!(h.state().frames.cached_frames(&series), (0..120).collect::<Vec<_>>());
|
||||
h.state_mut().session.set_time(Tick::ZERO);
|
||||
invoke(&mut h, "playback.toggle", json!({}));
|
||||
h.run_steps(2);
|
||||
assert!(h.state().playback.playing);
|
||||
let cur = h.state().session.time();
|
||||
assert!(cur < Tick::from_seconds_f64(1.0), "{cur:?}");
|
||||
for _ in 0..4000 {
|
||||
if h.state().frames.cached_frames(&series).iter().any(|&f| f >= 120) {
|
||||
break;
|
||||
}
|
||||
h.step();
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
if h.state().session.time() >= Tick::from_seconds_f64(1.0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(h.state().frames.cached_frames(&series).iter().any(|&f| f >= 120), "frames past the cached run render while it plays the first second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_frames_when_idle_survives_an_inverted_work_area() {
|
||||
let mut h = harness();
|
||||
|
||||
Reference in New Issue
Block a user