mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Merge #2048: Timeline: the disk-cache bar checks only the visible frames (@arnav-hiwarkar)
This commit is contained in:
@@ -784,7 +784,12 @@ impl Frames {
|
||||
/// Frames of the series of `base` in the disk cache but not in RAM — the blue cache bar.
|
||||
/// `opts` are the viewer's render options (part of the disk key). Native queries return
|
||||
/// no blue frames until the content key is ready; hashing never blocks timeline painting.
|
||||
pub fn disk_frames(&self, src: &RenderSource, base: &FrameKey, frames: i64, opts: &RenderOpts) -> Vec<i64> {
|
||||
pub fn disk_frames(&self, src: &RenderSource, base: &FrameKey, range: std::ops::Range<i64>, opts: &RenderOpts) -> Vec<i64> {
|
||||
let start = range.start.clamp(0, 100_000);
|
||||
let end = range.end.clamp(0, 100_000);
|
||||
if start >= end {
|
||||
return vec![];
|
||||
}
|
||||
let remote = self.remote.as_ref().filter(|r| r.disk());
|
||||
let contains = |k: u128| match (&src.disk, remote) {
|
||||
(Some(dc), _) => dc.contains(disk_cache::Kind::Frame, k),
|
||||
@@ -808,10 +813,7 @@ impl Frames {
|
||||
let content = content_key(&self.content_keys, &src.project, base.revision, base.comp, epoch, self.virtual_id.as_deref());
|
||||
let ram: HashSet<i64> = self.cached_frames(base).into_iter().collect();
|
||||
let oh = opts_hash(opts);
|
||||
(0..frames.clamp(0, 100_000))
|
||||
.filter(|f| !ram.contains(f))
|
||||
.filter(|f| contains(disk_cache::frame_key(content, *f, base.scale, base.view ^ oh)))
|
||||
.collect()
|
||||
(start..end).filter(|f| !ram.contains(f)).filter(|f| contains(disk_cache::frame_key(content, *f, base.scale, base.view ^ oh))).collect()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -1437,6 +1439,54 @@ impl Queue {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct ProbeDisk {
|
||||
keys: Mutex<HashSet<u128>>,
|
||||
probes: std::sync::atomic::AtomicUsize,
|
||||
}
|
||||
|
||||
impl RemoteFrames for ProbeDisk {
|
||||
fn slots(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn disk(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn disk_contains(&self, key: u128) -> bool {
|
||||
self.probes.fetch_add(1, Ordering::Relaxed);
|
||||
self.keys.lock().unwrap().contains(&key)
|
||||
}
|
||||
|
||||
fn start(&self, _: RemoteJob, _: RemoteDone) {}
|
||||
}
|
||||
|
||||
fn disk_fixture(stored_frames: &[i64]) -> (Frames, RenderSource, FrameKey, RenderOpts, Arc<ProbeDisk>, u128) {
|
||||
let mut session = effectcraft_engine::Session::default();
|
||||
session.execute("comp.new", serde_json::json!({"width": 32, "height": 18, "duration": 1})).unwrap();
|
||||
let comp = session.active_comp_id().unwrap();
|
||||
let opts = RenderOpts::default();
|
||||
let content = 1234;
|
||||
let mut base = key(1, 0, opts_hash(&opts));
|
||||
base.revision = 7;
|
||||
base.comp = comp.0;
|
||||
let disk = Arc::new(ProbeDisk { keys: Mutex::new(HashSet::new()), probes: 0.into() });
|
||||
disk.keys.lock().unwrap().extend(stored_frames.iter().map(|frame| disk_cache::frame_key(content, *frame, base.scale, base.view ^ opts_hash(&opts))));
|
||||
let mut frames = Frames::default();
|
||||
frames.set_remote(Some(disk.clone()));
|
||||
frames.content_keys.lock().unwrap().insert((base.revision, base.comp, 0), content);
|
||||
let source = RenderSource {
|
||||
project: session.project.clone(),
|
||||
footage: session.footage.clone(),
|
||||
expr: None,
|
||||
layer_cache: Arc::default(),
|
||||
gpu: None,
|
||||
gpu_display: false,
|
||||
disk: None,
|
||||
};
|
||||
(frames, source, base, opts, disk, content)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn disk_indicator_does_not_hash_on_ui_thread_and_coalesces_edits() {
|
||||
@@ -1484,7 +1534,7 @@ mod tests {
|
||||
base.comp = cid.0;
|
||||
for revision in 1..50 {
|
||||
base.revision = revision;
|
||||
assert!(frames.disk_frames(&src, &base, 30, &opts).is_empty());
|
||||
assert!(frames.disk_frames(&src, &base, 0..30, &opts).is_empty());
|
||||
}
|
||||
assert!(frames.content_keys.lock().unwrap().is_empty(), "painting computed a content hash while the worker was blocked");
|
||||
assert_eq!(frames.content_key_jobs.lock().unwrap().pending.len(), 1);
|
||||
@@ -1494,10 +1544,55 @@ mod tests {
|
||||
assert!(Instant::now() < until);
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
assert_eq!(frames.disk_frames(&src, &base, 30, &opts), vec![2], "the blue indicator must return once its key is ready");
|
||||
assert_eq!(frames.disk_frames(&src, &base, 0..30, &opts), vec![2], "the blue indicator must return once its key is ready");
|
||||
assert_eq!(frames.content_keys.lock().unwrap().len(), 1, "superseded revisions were hashed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_indicator_range_tracks_live_membership() {
|
||||
let (frames, src, base, opts, disk, _) = disk_fixture(&[10, 12]);
|
||||
assert_eq!(frames.disk_frames(&src, &base, 10..13, &opts), vec![10, 12]);
|
||||
assert_eq!(disk.probes.load(Ordering::Relaxed), 3);
|
||||
|
||||
disk.keys.lock().unwrap().insert(disk_cache::frame_key(1234, 11, base.scale, base.view ^ opts_hash(&opts)));
|
||||
assert_eq!(frames.disk_frames(&src, &base, 10..13, &opts), vec![10, 11, 12]);
|
||||
|
||||
insert(&frames, FrameKey { frame: 11, ..base });
|
||||
disk.probes.store(0, Ordering::Relaxed);
|
||||
assert_eq!(frames.disk_frames(&src, &base, 10..13, &opts), vec![10, 12]);
|
||||
assert_eq!(disk.probes.load(Ordering::Relaxed), 2);
|
||||
|
||||
disk.keys.lock().unwrap().remove(&disk_cache::frame_key(1234, 12, base.scale, base.view ^ opts_hash(&opts)));
|
||||
assert_eq!(frames.disk_frames(&src, &base, 10..13, &opts), vec![10]);
|
||||
|
||||
let mut revised = base;
|
||||
revised.revision += 1;
|
||||
frames.content_keys.lock().unwrap().insert((revised.revision, revised.comp, 0), 5678);
|
||||
assert!(frames.disk_frames(&src, &revised, 10..13, &opts).is_empty());
|
||||
|
||||
let changed = FrameKey { scale: base.scale + 1, ..base };
|
||||
assert!(frames.disk_frames(&src, &changed, 10..13, &opts).is_empty());
|
||||
let changed = FrameKey { view: base.view + 1, ..base };
|
||||
assert!(frames.disk_frames(&src, &changed, 10..13, &opts).is_empty());
|
||||
assert!(frames.disk_frames(&src, &base, 10..13, &RenderOpts { draft: true, ..opts }).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_indicator_range_is_bounded() {
|
||||
let (frames, src, base, opts, disk, _) = disk_fixture(&[0, 1, 99_999]);
|
||||
assert_eq!(frames.disk_frames(&src, &base, -5..2, &opts), vec![0, 1]);
|
||||
assert_eq!(disk.probes.load(Ordering::Relaxed), 2);
|
||||
disk.probes.store(0, Ordering::Relaxed);
|
||||
assert_eq!(frames.disk_frames(&src, &base, 99_999..i64::MAX, &opts), vec![99_999]);
|
||||
assert_eq!(disk.probes.load(Ordering::Relaxed), 1);
|
||||
disk.probes.store(0, Ordering::Relaxed);
|
||||
assert!(frames.disk_frames(&src, &base, 2..2, &opts).is_empty());
|
||||
// Written as a struct literal: `3..2` trips clippy::reversed_empty_ranges.
|
||||
let reversed = std::ops::Range { start: 3, end: 2 };
|
||||
assert!(frames.disk_frames(&src, &base, reversed, &opts).is_empty());
|
||||
assert_eq!(disk.probes.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
|
||||
/// Frames larger than the texture limit are averaged down by a whole factor (#201: an
|
||||
/// 11000×2200 comp at Full resolution against an 8192 limit).
|
||||
#[test]
|
||||
|
||||
@@ -517,6 +517,19 @@ impl TMap {
|
||||
}
|
||||
}
|
||||
|
||||
fn disk_bar_range(tm: &TMap, clip: Rect, fd: f64, frames: i64) -> std::ops::Range<i64> {
|
||||
if clip.width() <= 0.0 || clip.height() <= 0.0 || !fd.is_finite() || fd <= 0.0 || !tm.pps.is_finite() || tm.pps <= 0.0 {
|
||||
return 0..0;
|
||||
}
|
||||
let start = tm.t(clip.min.x) / fd;
|
||||
let end = tm.t(clip.max.x) / fd;
|
||||
if !start.is_finite() || !end.is_finite() {
|
||||
return 0..0;
|
||||
}
|
||||
let limit = frames.clamp(0, 100_000) as f64;
|
||||
(start.clamp(0.0, limit).floor() as i64)..(end.clamp(0.0, limit).ceil() as i64)
|
||||
}
|
||||
|
||||
/// A Timeline ruler tick label for comp frame `fno`: seconds:frames (`00:15f`, AE) or, when
|
||||
/// the project's Time Display Style is Frames, the frame number the current-time display shows
|
||||
/// (`00015`, counted from Start Numbering Frames At) (#710).
|
||||
@@ -1738,11 +1751,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
if app.session.disk_cache.is_some() || app.frames.remote_disk() {
|
||||
let frames = comp.frame_rate.frame_at(comp.duration);
|
||||
let opts = app.frame_opts(cid, series.scale as f64 / 1000.0);
|
||||
let disk = app.frames.disk_frames(&app.render_source(), &series, frames, &opts);
|
||||
let clip = wa_row.intersect(p.clip_rect());
|
||||
let range = disk_bar_range(&tm, clip, fd, frames);
|
||||
let disk = app.frames.disk_frames(&app.render_source(), &series, range, &opts);
|
||||
let blue_painter = p.with_clip_rect(clip);
|
||||
let draw_blue = |a: i64, b: i64| {
|
||||
let x0 = tm.x(a as f64 * fd);
|
||||
let x1 = tm.x((b + 1) as f64 * fd);
|
||||
p.rect_filled(Rect::from_min_max(pos2(x0, cy0), pos2(x1, cy0 + 2.5)), 0.0, t.cache_blue);
|
||||
blue_painter.rect_filled(Rect::from_min_max(pos2(x0, cy0), pos2(x1, cy0 + 2.5)), 0.0, t.cache_blue);
|
||||
};
|
||||
let mut run: Option<(i64, i64)> = None;
|
||||
for f in disk {
|
||||
@@ -4891,4 +4907,22 @@ mod tests {
|
||||
let sel = bar_color(red, true);
|
||||
assert!(sel.r() > bar.r());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_bar_range_covers_partial_frames() {
|
||||
let clip = Rect::from_min_max(pos2(0.0, 0.0), pos2(2.0, 1.0));
|
||||
let tm = TMap { x0: 0.0, start: 10.25, pps: 1.0 };
|
||||
assert_eq!(disk_bar_range(&tm, clip, 1.0, 100), 10..13);
|
||||
|
||||
let exact = TMap { start: 10.0, ..tm };
|
||||
assert_eq!(disk_bar_range(&exact, clip, 1.0, 100), 10..12);
|
||||
let off_comp = TMap { start: 200.0, ..tm };
|
||||
assert_eq!(disk_bar_range(&off_comp, clip, 1.0, 100), 100..100);
|
||||
assert_eq!(disk_bar_range(&tm, clip, f64::NAN, 100), 0..0);
|
||||
assert_eq!(disk_bar_range(&TMap { pps: 0.0, ..tm }, clip, 1.0, 100), 0..0);
|
||||
assert_eq!(disk_bar_range(&TMap { start: f64::NAN, ..tm }, clip, 1.0, 100), 0..0);
|
||||
assert_eq!(disk_bar_range(&tm, Rect::from_min_max(pos2(2.0, 0.0), pos2(2.0, 1.0)), 1.0, 100), 0..0);
|
||||
let above_limit = TMap { start: 100_001.0, ..tm };
|
||||
assert_eq!(disk_bar_range(&above_limit, clip, 1.0, i64::MAX), 100_000..100_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ fn gpu_worker_renders_effect_frames_and_the_disk_cache_survives_a_reload() {
|
||||
assert!(frames2.remote_disk());
|
||||
let until = std::time::Instant::now() + std::time::Duration::from_secs(5);
|
||||
let bar = loop {
|
||||
let bar = frames2.disk_frames(&source(&s, None), &key(fx), 30, &opts);
|
||||
let bar = frames2.disk_frames(&source(&s, None), &key(fx), 0..30, &opts);
|
||||
if !bar.is_empty() {
|
||||
break bar;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user