mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 07:38:24 +00:00
Every undoable edit keeps the previous project for undo, so the comp it touches is shared and Arc::make_mut deep-copies it: every layer and every keyframe. An edit therefore costs time in proportion to the comp's size, and a script that sets many keyframes in a big comp goes quadratic (a 1,600-layer build with ~100k sampled keys took ~45 minutes). Comp::layers is now a Layers collection backed by Vec<Arc<Layer>>: copying the stack copies pointers, and only the layer an edit changes is copied. The API mirrors Vec<Layer>; serialized form is unchanged (a list of layers). Comp::layer_mut and the iter_mut().filter/find call sites use find_mut / iter_mut_where so they copy only the layers they actually change. - prop.addKey in a 400-layer comp: 1.36 ms -> 0.16 ms per key (flat in comp size) - the 1,600-layer script build: ~45 min -> 47 s, frames pixel-identical - regression tests: an edit leaves the other layers shared with undo; layers round-trip as a plain list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG
43 lines
2.0 KiB
Rust
43 lines
2.0 KiB
Rust
//! An edit copies only the layer it changes: the others stay shared with the undo state, so an
|
|
//! edit's cost doesn't grow with the size of the comp (scripts setting many keys went quadratic).
|
|
|
|
use std::sync::Arc;
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::Session;
|
|
|
|
#[test]
|
|
fn a_key_on_one_layer_leaves_the_other_layers_shared_with_undo() {
|
|
let mut s = Session::default();
|
|
s.execute("comp.new", json!({"name": "Big", "duration": 10, "frameRate": 30})).unwrap();
|
|
let ids: Vec<u64> = (0..20).map(|i| s.execute("layer.newNull", json!({"name": format!("N{i}")})).unwrap()["layer"].as_u64().unwrap()).collect();
|
|
let cid = s.active_comp_id().unwrap();
|
|
let before = s.project.clone();
|
|
s.execute("prop.addKey", json!({"layer": ids[7], "path": "transform/position", "time": 1.0, "value": [5, 5]})).unwrap();
|
|
let (old, new) = (before.comp(cid).unwrap(), s.project.comp(cid).unwrap());
|
|
assert_eq!(old.layers.len(), new.layers.len());
|
|
let edited = new.layers.iter().position(|l| l.id.0 == ids[7]).unwrap();
|
|
for i in 0..new.layers.len() {
|
|
assert_eq!(new.layers.same_at(&old.layers, i), i != edited, "layer {i} shared: {}", new.layers.same_at(&old.layers, i));
|
|
}
|
|
// Undo brings back the very same state, and the edited layer is the only one that differed.
|
|
s.execute("edit.undo", json!({})).unwrap();
|
|
assert!(Arc::ptr_eq(&s.project, &before));
|
|
}
|
|
|
|
#[test]
|
|
fn layers_save_and_load_as_a_plain_list() {
|
|
let mut s = Session::default();
|
|
s.execute("comp.new", json!({"name": "Round trip", "duration": 5, "frameRate": 25})).unwrap();
|
|
for i in 0..3 {
|
|
s.execute("layer.newNull", json!({"name": format!("N{i}")})).unwrap();
|
|
}
|
|
let cid = s.active_comp_id().unwrap();
|
|
let comp = s.project.comp(cid).unwrap();
|
|
let text = serde_json::to_value(&comp.layers).unwrap();
|
|
assert_eq!(text.as_array().map(Vec::len), Some(3), "a JSON array of layers, as before");
|
|
let back: effectcraft_project::Layers = serde_json::from_value(text).unwrap();
|
|
assert_eq!(&back, &comp.layers);
|
|
}
|