mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 01:22:15 +00:00
#2104: bound audio comparisons across reused precomps
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
//! **Scrubbing:** Ctrl/Cmd-dragging the current-time indicator plays a short snippet of the mix
|
||||
//! at each new frame ([`AudioScrub`], `playback.scrubAudio`), as in After Effects.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
@@ -338,11 +338,17 @@ impl Drop for AudioPlayback {
|
||||
/// Effects switch, Audio Levels or effects, or the same for a nested comp. Other edits (video
|
||||
/// properties, names, labels) do not change the mix unless an audio expression can read them.
|
||||
fn audio_changed(old: &Project, new: &Project, comp: ItemId) -> bool {
|
||||
fn comps_differ(old: &Project, new: &Project, comp: ItemId, depth: usize) -> bool {
|
||||
let (Some(a), Some(b)) = (old.comp(comp), new.comp(comp)) else { return old.comp(comp).is_some() != new.comp(comp).is_some() };
|
||||
if depth >= 16 {
|
||||
audio_changed_inner(old, new, comp, &mut HashSet::new())
|
||||
}
|
||||
|
||||
/// Each shared precomp is compared once per depth. A depth limit alone still visits an
|
||||
/// exponentially large number of paths in a small graph of reused precomps, blocking the UI.
|
||||
fn audio_changed_inner(old: &Project, new: &Project, comp: ItemId, seen: &mut HashSet<(ItemId, usize)>) -> bool {
|
||||
fn comps_differ(old: &Project, new: &Project, comp: ItemId, depth: usize, seen: &mut HashSet<(ItemId, usize)>) -> bool {
|
||||
if depth >= 16 || !seen.insert((comp, depth)) {
|
||||
return false;
|
||||
}
|
||||
let (Some(a), Some(b)) = (old.comp(comp), new.comp(comp)) else { return old.comp(comp).is_some() != new.comp(comp).is_some() };
|
||||
a.layers.len() != b.layers.len()
|
||||
|| a.layers.iter().zip(&b.layers).any(|(x, y)| {
|
||||
x.id != y.id
|
||||
@@ -360,7 +366,7 @@ fn audio_changed(old: &Project, new: &Project, comp: ItemId) -> bool {
|
||||
|| audio_has_expression(y)
|
||||
|| match &x.source {
|
||||
LayerSource::Footage { item } => footage_differs(old, new, *item),
|
||||
LayerSource::Comp { item } => comps_differ(old, new, *item, depth + 1),
|
||||
LayerSource::Comp { item } => comps_differ(old, new, *item, depth + 1, seen),
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
@@ -386,7 +392,7 @@ fn audio_changed(old: &Project, new: &Project, comp: ItemId) -> bool {
|
||||
};
|
||||
f(old) != f(new)
|
||||
}
|
||||
comps_differ(old, new, comp, 0)
|
||||
comps_differ(old, new, comp, 0, seen)
|
||||
}
|
||||
|
||||
/// Mix one block at the feeder's cursor into the feed and move the cursor on (wrapping or
|
||||
|
||||
@@ -179,3 +179,31 @@ fn video_edits_keep_the_queue_unless_audio_expressions_can_read_them() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_nested_comps_are_compared_once_per_depth() {
|
||||
let mut project = Project::default();
|
||||
let mut child = None;
|
||||
for _ in 0..10 {
|
||||
let mut comp = Comp::new(64, 36, FrameRate::new(30, 1), Tick::from_seconds_f64(1.0));
|
||||
if let Some(item) = child {
|
||||
for _ in 0..10 {
|
||||
let mut layer = build::layer(&mut project, &comp, "Nested", LayerSource::Comp { item }, (64, 36), None);
|
||||
layer.switches.audio = false;
|
||||
comp.layers.push(layer);
|
||||
}
|
||||
}
|
||||
child = Some(project.add_item("Nested", Label::Sandstone, None, ItemKind::Comp(comp.into())));
|
||||
}
|
||||
let root = child.unwrap();
|
||||
let mut edited = project.clone();
|
||||
edited.comp_mut(root).unwrap().layers[0].name = "Renamed".into();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
assert!(!super::audio_changed_inner(&project, &edited, root, &mut seen));
|
||||
assert_eq!(seen.len(), 10, "compare the ten comps, not a billion paths through them");
|
||||
let leaf = *seen.iter().find(|(_, depth)| *depth == 9).map(|(item, _)| item).unwrap();
|
||||
let mut layer = project.comp(root).unwrap().layers[0].clone();
|
||||
layer.source = LayerSource::Null;
|
||||
edited.comp_mut(leaf).unwrap().layers.push(layer);
|
||||
assert!(super::audio_changed(&project, &edited, root), "memoization must still find a nested edit");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user