Fetch the photographs around the open one ahead of the step to them
Benchmarks / CPU and I/O (per commit) (push) Successful in 3m59s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / android-image (push) Canceled after 0s
🐳 Android image / Build and push (push) Canceled after 0s
Build and test / Android (aarch64) (push) Canceled after 0s
Build and test / windows-image (push) Canceled after 0s
🐳 Windows image / Build and push (push) Canceled after 0s
Build and test / Windows (x86_64, cross) (push) Canceled after 0s
Build and test / Layer separation (push) Canceled after 0s
Build and test / Desktop (Linux) (push) Canceled after 16m26s
Traceability / Requirement traces (push) Canceled after 0s
Benchmarks / CPU and I/O (per commit) (push) Successful in 3m59s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / android-image (push) Canceled after 0s
🐳 Android image / Build and push (push) Canceled after 0s
Build and test / Android (aarch64) (push) Canceled after 0s
Build and test / windows-image (push) Canceled after 0s
🐳 Windows image / Build and push (push) Canceled after 0s
Build and test / Windows (x86_64, cross) (push) Canceled after 0s
Build and test / Layer separation (push) Canceled after 0s
Build and test / Desktop (Linux) (push) Canceled after 16m26s
Traceability / Requirement traces (push) Canceled after 0s
Walking the photo roll was one download per frame: every step showed "Downloading…" over an empty canvas while tens of megabytes came down, and moving between a pair of near-identical frames paid that a dozen times. Now, once the opened photograph has landed, the ones around it are fetched into the originals cache while it is being looked at, so the next step is a disk read. A single worker serves the latest wish only, closest first and working outwards — next, previous, next-but-one, previous-but-one… — one file at a time. Each open replaces the wish, so a fast walk never leaves a trail of stale downloads competing with the one being waited on. A process-wide in-flight registry makes a click on a photograph that is still being fetched ahead wait for that transfer and read it from disk, rather than start a second download of the same file. How far each side is a setting under STORAGE — Off, 2, 5, 10 or 20, defaulting to 5 — and it is moot while "keep originals after opening" is off, since a fetch the cache would discard on arrival is transfer for nothing. Nothing is fetched ahead while offline. The transfers show in the activity list while they run and are removed when they end.
This commit is contained in:
@@ -508,6 +508,28 @@ pub struct CacheSettings {
|
||||
/// no bandwidth and saves the whole transfer next time. Off is for metered
|
||||
/// or small-disk devices, where the user would rather re-fetch than store.
|
||||
pub keep_opened_originals: bool,
|
||||
|
||||
/// How many photographs either side of the open one are fetched into the
|
||||
/// cache ahead of being asked for, so a step along the roll is a disk
|
||||
/// read rather than a download. Zero fetches nothing ahead.
|
||||
///
|
||||
/// Each side, so the total is twice this; and closest first, working
|
||||
/// outwards, so a small number still covers the step most likely to be
|
||||
/// taken next. Bounded by [`Self::AHEAD_CHOICES`] because every unit is a
|
||||
/// whole RAW file: 10 each side is a few hundred megabytes per open,
|
||||
/// which a wired desktop shrugs at and a phone on a hotel connection does
|
||||
/// not. Moot while `keep_opened_originals` is off — a fetch the cache
|
||||
/// would discard on arrival is not made.
|
||||
pub fetch_ahead: u32,
|
||||
}
|
||||
|
||||
impl CacheSettings {
|
||||
/// The look-ahead depths the settings page offers, each side.
|
||||
///
|
||||
/// Snapped to, not clamped, for the reason `LibrarySettings::BAR_CHOICES`
|
||||
/// is: the page lights the chip that matches, and a number it does not
|
||||
/// offer would leave every chip dark.
|
||||
pub const AHEAD_CHOICES: [u32; 5] = [0, 2, 5, 10, 20];
|
||||
}
|
||||
|
||||
/// 8 GB of passively cached originals — roughly 250 full-frame RAWs.
|
||||
@@ -532,6 +554,10 @@ impl Default for CacheSettings {
|
||||
original_budget_bytes: Some(DEFAULT_ORIGINAL_BUDGET_BYTES),
|
||||
thumbnail_budget_bytes: Some(DEFAULT_THUMBNAIL_BUDGET_BYTES),
|
||||
keep_opened_originals: true,
|
||||
// Enough that the next few steps in either direction are already
|
||||
// here by the time they are taken, without a click on one frame
|
||||
// committing a phone to half a gigabyte.
|
||||
fetch_ahead: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1175,6 +1201,15 @@ impl Settings {
|
||||
.unwrap_or(LibrarySettings::default().timeline_bars);
|
||||
}
|
||||
|
||||
// The same snap, for the same page.
|
||||
if !CacheSettings::AHEAD_CHOICES.contains(&self.cache.fetch_ahead) {
|
||||
let wanted = self.cache.fetch_ahead;
|
||||
self.cache.fetch_ahead = CacheSettings::AHEAD_CHOICES
|
||||
.into_iter()
|
||||
.min_by_key(|n| n.abs_diff(wanted))
|
||||
.unwrap_or(CacheSettings::default().fetch_ahead);
|
||||
}
|
||||
|
||||
// A zero-pixel or zero-percent export produces no image. Nudged to the
|
||||
// smallest thing that does, rather than back to the default: the user
|
||||
// clearly wanted "small", and silently restoring 2048 would ignore
|
||||
@@ -1410,6 +1445,10 @@ mod tests {
|
||||
Some(DEFAULT_THUMBNAIL_BUDGET_BYTES)
|
||||
);
|
||||
assert!(cache.keep_opened_originals);
|
||||
assert!(
|
||||
CacheSettings::AHEAD_CHOICES.contains(&cache.fetch_ahead),
|
||||
"the default look-ahead must be one the page can light"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1740,6 +1779,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitise_snaps_a_hand_edited_look_ahead_to_an_offered_one() {
|
||||
let mut s = Settings::default();
|
||||
s.cache.fetch_ahead = 7;
|
||||
s.sanitise();
|
||||
assert_eq!(s.cache.fetch_ahead, 5);
|
||||
|
||||
s.cache.fetch_ahead = 100;
|
||||
s.sanitise();
|
||||
assert_eq!(s.cache.fetch_ahead, 20);
|
||||
|
||||
for n in CacheSettings::AHEAD_CHOICES {
|
||||
s.cache.fetch_ahead = n;
|
||||
s.sanitise();
|
||||
assert_eq!(s.cache.fetch_ahead, n, "an offered depth is left alone");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitise_rescues_a_zero_dimension() {
|
||||
let mut s = Settings::default();
|
||||
|
||||
Reference in New Issue
Block a user