Keyframe diamonds: pointing-hand cursor + prefetch during playback

- Pointing-hand cursor when hovering a clickable keyframe diamond.
- Prefetch (Phase 3e, playback only): each update during playback, page in the next
  few upcoming keyframes (PREFETCH_AHEAD=4) per raster layer that aren't resident, via
  the existing async worker. Their full pixels land before the playhead reaches them,
  so playback shows full frames instead of the low-res proxy on every frame (the
  proxy→full pop was the "flicker"). Reactive faults still cover scrubbing.
This commit is contained in:
Skyler Lehmkuhl 2026-06-20 22:44:56 -04:00
parent 3188fc8bb6
commit ed022995bd
2 changed files with 27 additions and 3 deletions

View File

@ -5283,13 +5283,30 @@ impl eframe::App for EditorApp {
}
}
// 2) Dispatch background page-ins for newly-requested keyframes.
let wanted: Vec<uuid::Uuid> = {
// 2) Dispatch background page-ins: reactive misses, plus (during playback)
// a prefetch of upcoming keyframes so their full pixels are resident
// before the playhead reaches them — otherwise every frame shows the
// low-res proxy and the proxy→full pops read as flicker.
let mut to_load: Vec<uuid::Uuid> = {
let mut s = self.raster_fault_requests.lock().unwrap();
s.drain().collect()
};
if self.is_playing && self.raster_store.has_path() {
const PREFETCH_AHEAD: usize = 4;
let t = self.playback_time;
let doc = self.action_executor.document();
for layer in doc.all_layers() {
if let lightningbeam_core::layer::AnyLayer::Raster(rl) = layer {
for kf in rl.keyframes.iter().filter(|kf| kf.time >= t).take(PREFETCH_AHEAD) {
if kf.raw_pixels.is_empty() && kf.needs_fault_in {
to_load.push(kf.id);
}
}
}
}
}
if self.raster_store.has_path() {
for kf_id in wanted {
for kf_id in to_load {
if self.raster_loads_inflight.contains(&kf_id) {
continue;
}

View File

@ -4867,6 +4867,13 @@ impl TimelinePane {
}
}
// Pointing-hand cursor when hovering a clickable keyframe diamond.
if let Some(hover) = response.hover_pos() {
if self.keyframe_diamond_hits.iter().any(|(r, _)| r.contains(hover)) {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
}
}
// Click a keyframe diamond → snap the playhead exactly to that keyframe.
// (Hit targets come from the previous frame's render_layers; diamonds don't
// move between frames, so a click lands on the right one.)