Go to file
Greg Pomerantz 2e9e5b78b6 M3: fundamental thumbnail-loader redesign — viewport is first at every queue level
The reported 'fast scroll → stop → long shimmer → small move → everything
pops' survived the first fix (scroll-handler re-submission) and the second
(central-queue prepend): re-submission only happened in on_scroll, and the
workers' FIFO mpsc still held stale transit work ahead of fresh batches.

Root causes and the full redesign:
- on_scroll now ONLY records the viewport (zero work). The 10 Hz tick is
  the sole authority AND sole submitter: CONSUME (paint viewport results,
  capped), EVICT (far pixels, MAX_PAINTED=120), FILL (viewport,
  center-first), PRUNE (queued jobs outside the retention band).
- Invariant restored unconditionally each tick and debug_asserted: every
  viewport cell is PAINTED, IN-FLIGHT, or FAILED. A visible cell is empty
  only while its decode is genuinely in flight — never until the next
  scroll event.
- Worker queues: Mutex<VecDeque>+Condvar (mpsc is FIFO-only). The
  dispatcher drains the central queue as priority-ordered bursts and
  PREPENDS them, so fresh viewport work always jumps ahead of stale
  transit work. Per-worker cap (96) drops the oldest tail with a
  'skipped' result (UI clears the in-flight marker; re-entry re-submits
  — disk-cache hit, never a re-decode). A burst (<= viewport ~45) always
  survives the cap.

Stress-verified on the emulator (debug build) before phone deployment:
- fast up/down x5 (the repro): fully covered 0-119 ms after stop
- cold landings: 1.0-1.2 s = pure decode throughput (p rises
  monotonically while stale jobs wait behind, harmlessly)
- chaos x6 + 25 s idle soak: covered before every stop; max oldest
  in-flight job 217 ms (pre-fix: 8,055 ms); max queue depth 45; 0
  invariant violations in ~1,450 ticks; 0 panics; idle -> 0 queued
Phone (release aarch64, 8,577 photos): 99.98% cache hits in stress
scroll, 0 panics/ANRs.

Tests: 15 pass incl. fresh_batch_preempts_stale_worker_queue (new) and
the existing scroll_back_cache_hit_not_starved; 5k engine test uses an
uncapped queue (usize::MAX cap param).
2026-10-27 21:02:35 -04:00
scripts M2: thumbnail engine — turbojpeg DCT-shrink decode, worker pool, disk cache 2026-10-27 16:32:12 -04:00
src M3: fundamental thumbnail-loader redesign — viewport is first at every queue level 2026-10-27 21:02:35 -04:00
.gitignore M0: Slint gallery scaffold + development hand-off 2026-10-27 12:22:59 -04:00
build.rs M0: Slint gallery scaffold + development hand-off 2026-10-27 12:22:59 -04:00
Cargo.lock M2: thumbnail engine — turbojpeg DCT-shrink decode, worker pool, disk cache 2026-10-27 16:32:12 -04:00
Cargo.toml M2: thumbnail engine — turbojpeg DCT-shrink decode, worker pool, disk cache 2026-10-27 16:32:12 -04:00
HANDOFF.md M3: fundamental thumbnail-loader redesign — viewport is first at every queue level 2026-10-27 21:02:35 -04:00
LICENSE Initial commit 2026-10-27 12:13:22 -04:00
README.md M1: metadata scan + sqlite DB + placeholder list 2026-10-27 13:28:33 -04:00

gallery

A high-performance image gallery for Android (primary), Linux and macOS — Rust + Slint 1.18.

Status: M1 (metadata scan + DB) done. See HANDOFF.md — the development hand-off with the full plan: architecture, thumbnail pipeline, milestones M1–M7, Slint gotchas, permission flow, and performance targets. Next: M2 (thumbnail engine).

Build

./scripts/build.sh debug     # debug x86_64 APK + install on emulator
./scripts/build.sh           # release x86_64 (emulator)
./scripts/build.sh aarch64   # release aarch64 (device)
cargo run                    # desktop (Linux needs libfontconfig1 + pkg-config)

Requires: Rust (rustup), cargo-apk2, Android SDK + NDK 27 (see HANDOFF.md §3 for exact paths/env).

License

GPL-3.0 (see LICENSE). Built on Slint, used under the Slint royalty-free license for proprietary apps (see HANDOFF.md §10.4).