mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-25 02:27:00 -06:00
Merge #1946: #1926: an edit no longer stamps every image-sequence frame for the disk cache (@ded-achtung)
This commit is contained in:
@@ -193,6 +193,8 @@ fn forget(s: &mut Session, paths: &BTreeSet<String>) {
|
||||
s.footage.forget(p);
|
||||
}
|
||||
s.layer_cache.clear();
|
||||
// Reloaded files may keep their footage settings but not their size and time.
|
||||
s.refresh_disk_salt(true);
|
||||
s.events.push(crate::Event::PurgeCaches);
|
||||
}
|
||||
|
||||
|
||||
@@ -521,6 +521,8 @@ pub struct Session {
|
||||
pub footage_stamps: std::collections::HashMap<String, footage_reload::Stamp>,
|
||||
/// The stamps being taken on a worker thread ([`footage_reload::start_scan`]).
|
||||
pub footage_scan: Option<footage_reload::Scan>,
|
||||
/// The disk cache's footage salt and the footage it was taken for ([`Session::refresh_disk_salt`]).
|
||||
pub disk_salt: Option<(u64, u64)>,
|
||||
/// How many script-issued commands are running ([`Session::execute_for_script`]): while one
|
||||
/// is, settings that guard what scripts may do can't change (see
|
||||
/// [`prefs::Scripting::same_permissions`]).
|
||||
@@ -618,6 +620,7 @@ impl Default for Session {
|
||||
check_footage_on_open: false,
|
||||
stored_paths: Default::default(),
|
||||
footage_stamps: Default::default(),
|
||||
disk_salt: None,
|
||||
footage_scan: None,
|
||||
script_calls: ScriptCalls::default(),
|
||||
}
|
||||
@@ -736,12 +739,42 @@ impl Session {
|
||||
if Arc::ptr_eq(&self.project, &self.saved_project) {
|
||||
self.saved_revision = self.revision;
|
||||
}
|
||||
if self.disk_cache.is_some() {
|
||||
self.layer_cache.set_disk_salt(effectcraft_render::disk_cache::footage_salt(&self.project));
|
||||
}
|
||||
self.refresh_disk_salt(false);
|
||||
self.events.push(Event::ProjectChanged { revision: self.revision });
|
||||
}
|
||||
|
||||
/// Salt the disk cache's layer keys with the project's footage files
|
||||
/// ([`effectcraft_render::disk_cache::footage_salt`]). Taking it reads the size and time of
|
||||
/// every file, every frame of an image sequence included, so it is taken again only when the
|
||||
/// footage items changed, or with `force` (a project opened, footage reloaded, the disk cache
|
||||
/// attached), not at every edit: with a few large sequences that cost each keystroke in a
|
||||
/// text field over half a second (#1926).
|
||||
pub fn refresh_disk_salt(&mut self, force: bool) -> Option<u64> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
if self.disk_cache.is_none() {
|
||||
self.disk_salt = None;
|
||||
return None;
|
||||
}
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
for (id, it) in &self.project.items {
|
||||
if let effectcraft_project::ItemKind::Footage(f) = &it.kind {
|
||||
id.hash(&mut h);
|
||||
format!("{f:?}").hash(&mut h);
|
||||
}
|
||||
}
|
||||
let footage = h.finish();
|
||||
if let Some((seen, salt)) = self.disk_salt
|
||||
&& seen == footage
|
||||
&& !force
|
||||
{
|
||||
return Some(salt);
|
||||
}
|
||||
let salt = effectcraft_render::disk_cache::footage_salt(&self.project);
|
||||
self.disk_salt = Some((footage, salt));
|
||||
self.layer_cache.set_disk_salt(salt);
|
||||
Some(salt)
|
||||
}
|
||||
|
||||
/// Set a comp's Resolution. As in After Effects it is saved with the project (which becomes
|
||||
/// modified) but is no undo step, and it changes no pixels: the comp's cached frames stay
|
||||
/// valid ([`effectcraft_project::Comp::same_pixels`]).
|
||||
@@ -1175,6 +1208,8 @@ impl Session {
|
||||
}
|
||||
self.layer_cache.clear();
|
||||
self.project = Arc::new(p);
|
||||
// Its files may have changed since they were last stamped.
|
||||
self.disk_salt = None;
|
||||
self.history = History::default();
|
||||
self.state = EditorState { snapping: true, view_layout: 1, ..Default::default() };
|
||||
self.path = path;
|
||||
|
||||
@@ -102,7 +102,7 @@ impl Session {
|
||||
/// and attach it to the layer cache. Without a folder setting the platform cache folder is
|
||||
/// used, but only by frontends with a settings store (headless sessions stay off disk).
|
||||
pub fn configure_disk_cache(&mut self) {
|
||||
use effectcraft_render::disk_cache::{DiskCache, default_folder, footage_salt};
|
||||
use effectcraft_render::disk_cache::{DiskCache, default_folder};
|
||||
let d = &self.prefs.disk;
|
||||
let folder = if d.disk_cache_folder.trim().is_empty() {
|
||||
(self.config.is_some() && !cfg!(target_arch = "wasm32")).then(default_folder)
|
||||
@@ -136,7 +136,9 @@ impl Session {
|
||||
}
|
||||
},
|
||||
};
|
||||
self.layer_cache.set_disk(Some((dc.clone(), footage_salt(&self.project))));
|
||||
self.disk_cache = Some(dc.clone());
|
||||
let salt = self.refresh_disk_salt(true).unwrap_or_default();
|
||||
self.layer_cache.set_disk(Some((dc.clone(), salt)));
|
||||
// Roto Brush / Object Matte segmentations persist there too (held weakly: switching the
|
||||
// disk cache off drops it).
|
||||
let store: std::sync::Arc<dyn effectcraft_effects::roto::SegStore> = dc.clone();
|
||||
|
||||
@@ -207,3 +207,31 @@ fn dpx_and_cineon_runs_import_as_sequences() {
|
||||
got.sort();
|
||||
assert_eq!(got, [("plate.[0001-0003].dpx".to_string(), 3), ("scan_[01-02].cin".to_string(), 2)], "{r}");
|
||||
}
|
||||
|
||||
/// #1926: an edit that leaves the footage alone doesn't stamp every frame of its image sequences
|
||||
/// again for the disk cache's salt (with large sequences each keystroke in a text field took over
|
||||
/// half a second); Reload Footage does, and so does new footage.
|
||||
#[test]
|
||||
fn edits_keep_the_disk_salt_and_reload_stamps_the_files_again() {
|
||||
let dir = folder(&RUN);
|
||||
let cache = std::env::temp_dir().join(format!("ec-seq-salt-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&cache);
|
||||
let mut s = session();
|
||||
s.execute_checked("prefs.set", json!({"values": {"disk.diskCacheFolder": cache.to_string_lossy(), "disk.diskCacheEnabled": true}})).unwrap();
|
||||
s.execute("comp.new", json!({"name": "C", "width": 64, "height": 32, "frameRate": 30, "duration": 1})).unwrap();
|
||||
let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0002.png")], "sequence": true, "addToComp": true})).unwrap();
|
||||
let item = r["items"][0].clone();
|
||||
let salt = s.refresh_disk_salt(false).expect("a disk cache");
|
||||
// A frame rewritten in place: same footage settings, another size on disk.
|
||||
std::fs::write(at(&dir, "shot_0004.png"), b"a new render of this frame").unwrap();
|
||||
s.execute("layer.newText", json!({"text": "title"})).unwrap();
|
||||
s.execute("layer.setText", json!({"text": "title 2"})).unwrap();
|
||||
assert_eq!(s.refresh_disk_salt(false), Some(salt), "edits don't stamp the files again");
|
||||
s.execute("file.reloadFootage", json!({"items": [item]})).unwrap();
|
||||
let reloaded = s.refresh_disk_salt(false).unwrap();
|
||||
assert_ne!(reloaded, salt, "Reload Footage stamps them again");
|
||||
std::fs::write(at(&dir, "extra.png"), b"").unwrap();
|
||||
s.execute("file.import", json!({"paths": [at(&dir, "extra.png")]})).unwrap();
|
||||
assert_ne!(s.refresh_disk_salt(false), Some(reloaded), "new footage counts");
|
||||
let _ = std::fs::remove_dir_all(&cache);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user