mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 04:57:54 -06:00
perf(presets): reuse cached text and user preset data
This commit is contained in:
@@ -6,6 +6,7 @@ use effectcraft_project::build::{self, Ids};
|
||||
use effectcraft_project::{Layer, LayerSource, Node, PropGroup};
|
||||
use effectcraft_time::Tick;
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::{CommandSpec, b_p, bad, has_comp, layer_mut, layer_p, str_p};
|
||||
use crate::{Result, Session, cmd, query};
|
||||
@@ -13,8 +14,10 @@ use crate::{Result, Session, cmd, query};
|
||||
/// The original text animator presets (`presets/text_animators.json`).
|
||||
pub const PRESETS_JSON: &str = include_str!("../../presets/text_animators.json");
|
||||
|
||||
fn presets() -> Vec<Value> {
|
||||
serde_json::from_str::<Value>(PRESETS_JSON).ok().and_then(|v| v.get("presets").and_then(Value::as_array).cloned()).unwrap_or_default()
|
||||
fn presets() -> &'static [Value] {
|
||||
static PRESETS: OnceLock<Vec<Value>> = OnceLock::new();
|
||||
PRESETS
|
||||
.get_or_init(|| serde_json::from_str::<Value>(PRESETS_JSON).ok().and_then(|v| v.get("presets").and_then(Value::as_array).cloned()).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// (id, name) of every preset.
|
||||
@@ -303,7 +306,7 @@ fn apply_preset(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (cid, lid) = layer_p(s, p, C)?;
|
||||
let want = str_p(p, "preset").ok_or_else(|| bad(C, "`preset` id or name required"))?.to_string();
|
||||
let preset = presets()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.find(|x| x.get("id").and_then(Value::as_str) == Some(&want) || x.get("name").and_then(Value::as_str).is_some_and(|n| n.eq_ignore_ascii_case(&want)))
|
||||
.ok_or_else(|| bad(C, format!("no preset {want}")))?;
|
||||
let name = preset.get("name").and_then(Value::as_str).unwrap_or("Preset").to_string();
|
||||
|
||||
@@ -164,6 +164,64 @@ fn presets_apply_render_and_round_trip() {
|
||||
assert!(matches(tg.sub("pathOptions").unwrap()).contains(&"forceAlignment".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_text_presets_keep_the_catalog_and_application_independent() {
|
||||
let expected = [
|
||||
("typewriter", "Typewriter"),
|
||||
("fadeUpCharacters", "Fade Up Characters"),
|
||||
("bounceInWords", "Bounce In Words"),
|
||||
("trackingIn", "Tracking In"),
|
||||
("scramble", "Scramble"),
|
||||
("blurIn", "Blur In"),
|
||||
("jitter", "Jitter"),
|
||||
("dropInLines", "Drop In Lines"),
|
||||
];
|
||||
let (mut s, layer) = session();
|
||||
let catalog = s.execute("text.presets", json!({})).unwrap();
|
||||
let mut returned = catalog.clone();
|
||||
returned[0]["name"] = json!("Edited copy");
|
||||
let project = s.project.clone();
|
||||
for _ in 0..2 {
|
||||
let names = crate::text_presets();
|
||||
assert_eq!(names.iter().map(|(id, name)| (id.as_str(), name.as_str())).collect::<Vec<_>>(), expected);
|
||||
for (id, _) in expected {
|
||||
let result = s.execute("layer.applyTextPreset", json!({"layer": layer, "preset": id})).unwrap();
|
||||
assert!(!result["animators"].as_array().unwrap().is_empty(), "{id}");
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(s.project, project, "{id}: undo restores the document");
|
||||
}
|
||||
assert_eq!(s.execute("text.presets", json!({})).unwrap(), catalog);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "warm text-preset listing benchmark"]
|
||||
fn text_preset_listing_benchmark() {
|
||||
let old = || {
|
||||
let v: serde_json::Value = serde_json::from_str(include_str!("../presets/text_animators.json")).unwrap();
|
||||
let presets = v["presets"].as_array().unwrap().clone();
|
||||
presets.iter().map(|p| (p["id"].as_str().unwrap().to_string(), p["name"].as_str().unwrap().to_string())).collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(old(), crate::text_presets());
|
||||
let mut before = Vec::new();
|
||||
let mut after = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..1000 {
|
||||
std::hint::black_box(old());
|
||||
}
|
||||
before.push(start.elapsed());
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..1000 {
|
||||
std::hint::black_box(crate::text_presets());
|
||||
}
|
||||
after.push(start.elapsed());
|
||||
}
|
||||
before.sort();
|
||||
after.sort();
|
||||
println!("Text preset names, median per warm lookup: {:?} -> {:?}", before[2] / 1000, after[2] / 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menus_have_text_animation_entries() {
|
||||
let (s, _) = session();
|
||||
|
||||
Reference in New Issue
Block a user