//! The persistent disk cache (PRV-2) through the session: settings, layer cache persistence //! across restarts, invalidation on edits, purging. use serde_json::json; use crate::Session; use crate::render::RenderOpts; fn folder(name: &str) -> String { let d = std::env::temp_dir().join(format!("effectcraft-session-disk-{name}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); d.to_string_lossy().to_string() } fn session(dir: &str) -> Session { let mut s = Session::default(); s.execute_checked("prefs.set", json!({"values": {"disk.diskCacheFolder": dir, "disk.diskCacheEnabled": true}})).unwrap(); // Write every layer buffer, however quick to render. s.disk_cache.as_ref().unwrap().set_min_layer_ms(0); s } fn project(s: &mut Session) -> effectcraft_project::ItemId { s.execute("comp.new", json!({"name": "D", "width": 160, "height": 90, "frameRate": 30, "duration": 1})).unwrap(); s.execute("layer.newSolid", json!({"color": "#3080ff", "width": 80, "height": 40})).unwrap(); s.execute("effect.apply", json!({"layer": "#1", "effect": "Gaussian Blur"})).unwrap(); s.execute("prop.set", json!({"layer": "#1", "path": "effects/#1/blurriness", "value": 12})).unwrap(); s.active_comp_id().unwrap() } #[test] fn disk_cache_settings_persistence_and_invalidation() { let dir = folder("persist"); let mut s = session(&dir); let st = s.execute_checked("cache.diskStats", json!({})).unwrap(); assert_eq!(st["enabled"], json!(true)); assert_eq!(st["maxBytes"], json!(100u64 << 30)); let cid = project(&mut s); let t = s.time(); let first = s.render(cid, t, RenderOpts::default()); let dc = s.disk_cache.clone().unwrap(); dc.flush(); let written = dc.stats().entries; assert!(written > 0, "layer buffers written"); // "Restart": a new session on the same folder and project renders from disk. let saved = s.project.clone(); drop(s); let mut s2 = session(&dir); s2.project = saved; s2.bump(); s2.open_comp(cid); let dc2 = s2.disk_cache.clone().unwrap(); assert_eq!(dc2.stats().entries, written); let again = s2.render(cid, t, RenderOpts::default()); assert!(dc2.stats().hits > 0, "{:?}", dc2.stats()); assert_eq!(again.data, first.data, "disk-cached pixels are exact"); // An edit changes the keys: a miss, and new entries. let hits = dc2.stats().hits; s2.execute("prop.set", json!({"layer": "#1", "path": "effects/#1/blurriness", "value": 20})).unwrap(); let edited = s2.render(cid, t, RenderOpts::default()); assert_ne!(edited.data, first.data); assert_eq!(dc2.stats().hits, hits); dc2.flush(); assert!(dc2.stats().entries > written); // Purge ▸ All Disk Cache empties it; memory purge leaves the disk alone. let r = s2.execute_checked("edit.purge", json!({"what": "memory"})).unwrap(); assert_eq!(r["diskEntries"], json!(0)); assert!(dc2.stats().entries > 0); let r = s2.execute_checked("edit.purge", json!({"what": "disk"})).unwrap(); assert!(r["diskEntries"].as_u64().unwrap() > 0); assert_eq!(dc2.stats().entries, 0); // Disabling detaches it. s2.execute_checked("prefs.set", json!({"key": "disk.diskCacheEnabled", "value": false})).unwrap(); assert!(s2.disk_cache.is_none()); assert!(s2.layer_cache.disk().is_none()); } #[test] fn footage_changes_change_the_salt() { let dir = folder("salt"); let mut s = session(&dir); let foot = std::env::temp_dir().join(format!("effectcraft-salt-{}.png", std::process::id())); std::fs::write(&foot, b"one").unwrap(); s.edit("add", None, |p, _| { let f = effectcraft_project::Footage { path: foot.to_string_lossy().to_string(), kind: effectcraft_project::FootageKind::Still, width: 1, height: 1, pixel_aspect: 1.0, frame_rate: effectcraft_time::FrameRate::FPS_30, native_rate: None, duration: effectcraft_time::Tick::ZERO, has_video: true, has_audio: false, alpha: effectcraft_project::AlphaMode::Ignore, premul_color: [0.0; 3], loop_count: 1, codec: "PNG".into(), missing: false, sequence: vec![], color_profile: None, layer: None, ..Default::default() }; p.add_item("f.png", effectcraft_color::Label::Lavender, None, effectcraft_project::ItemKind::Footage(f)); Ok(()) }) .unwrap(); let a = effectcraft_render::disk_cache::footage_salt(&s.project); std::fs::write(&foot, b"two, longer").unwrap(); let b = effectcraft_render::disk_cache::footage_salt(&s.project); assert_ne!(a, b); let cid = project(&mut s); let k1 = effectcraft_render::disk_cache::comp_content_key(&s.project, cid); s.execute("prop.set", json!({"layer": "#1", "path": "transform/opacity", "value": 50})).unwrap(); let k2 = effectcraft_render::disk_cache::comp_content_key(&s.project, cid); assert_ne!(k1, k2); // Unrelated comps do not change it. s.execute("comp.new", json!({"name": "Other", "width": 10, "height": 10, "frameRate": 30, "duration": 1})).unwrap(); assert_eq!(effectcraft_render::disk_cache::comp_content_key(&s.project, cid), k2); }