mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-27 12:07:02 -06:00
# Conflicts: # crates/anim/src/tests.rs
This commit is contained in:
@@ -831,3 +831,38 @@ fn sink_down_and_peek_out_to_bottom_move_down() {
|
||||
assert!(s.offset_y > 0.0, "{effect} {option}: {s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_sequences_keep_first_appearance_order_when_interleaved() {
|
||||
let mut anims = vec![];
|
||||
for (i, tr) in [7u32, 5, 7, 0, 9, 5].iter().enumerate() {
|
||||
let mut a = anim(1 + (i as u32 % 4), AnimClass::Entrance, "fade", AnimStart::OnClick);
|
||||
a.trigger = (*tr != 0).then_some(ShapeId(*tr));
|
||||
anims.push(a);
|
||||
}
|
||||
let tl = Timeline::new(&slide_with(anims), W, H);
|
||||
assert_eq!(tl.trigger_shapes(), vec![ShapeId(7), ShapeId(5), ShapeId(9)]);
|
||||
assert_eq!(tl.trigger_steps(ShapeId(7)), 2);
|
||||
assert_eq!(tl.trigger_steps(ShapeId(5)), 2);
|
||||
assert_eq!(tl.trigger_steps(ShapeId(9)), 1);
|
||||
assert_eq!(tl.steps(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_distinct_triggers_past_the_effect_cap_build_in_order() {
|
||||
// One sequence per animation: grouping used to rescan every animation per trigger
|
||||
// (~1.2e10 steps here), now it is one pass, and nothing past the cap is looked at.
|
||||
let n = 110_000u32;
|
||||
let anims: Vec<Animation> = (0..n)
|
||||
.map(|i| {
|
||||
let mut a = anim(1, AnimClass::Entrance, "appear", AnimStart::OnClick);
|
||||
a.trigger = Some(ShapeId(1000 + i));
|
||||
a
|
||||
})
|
||||
.collect();
|
||||
let tl = Timeline::new(&slide_with(anims), W, H);
|
||||
assert_eq!(tl.effects().len(), 100_000);
|
||||
let shapes = tl.trigger_shapes();
|
||||
assert_eq!(shapes.len(), 100_000);
|
||||
assert!(shapes.iter().enumerate().all(|(i, s)| *s == ShapeId(1000 + i as u32)));
|
||||
}
|
||||
|
||||
+23
-15
@@ -1,5 +1,7 @@
|
||||
//! A slide's animation sequence: click steps, effect start times and per-shape state over time.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use deckcraft_model::anim::TextBuild;
|
||||
use deckcraft_model::style::Fill;
|
||||
use deckcraft_model::{AnimClass, AnimStart, Animation, ColorScheme, Rgba, SchemeSlot, ShapeId, Slide, Xfrm};
|
||||
@@ -161,25 +163,31 @@ impl Timeline {
|
||||
let accent = scheme.map(|s| s.get(SchemeSlot::Accent1)).unwrap_or(Rgba::rgb(0x15, 0x60, 0x82));
|
||||
|
||||
let mut tl = Timeline::default();
|
||||
// Sequences in order of first appearance: main first.
|
||||
let mut seqs: Vec<Sequence> = vec![Sequence::Main];
|
||||
for a in &slide.animations {
|
||||
if let Some(tr) = a.trigger {
|
||||
let s = Sequence::Trigger(tr);
|
||||
if !seqs.contains(&s) {
|
||||
seqs.push(s);
|
||||
}
|
||||
// Group the animations by sequence in one pass, sequences in order of first appearance
|
||||
// (main first), so many distinct triggers cost linear work, not a scan per trigger.
|
||||
let mut seqs: Vec<(Sequence, Vec<usize>)> = vec![(Sequence::Main, Vec::new())];
|
||||
let mut seq_index: HashMap<Sequence, usize> = HashMap::from([(Sequence::Main, 0)]);
|
||||
for (ai, a) in slide.animations.iter().enumerate() {
|
||||
let s = a.trigger.map(Sequence::Trigger).unwrap_or(Sequence::Main);
|
||||
let i = *seq_index.entry(s).or_insert_with(|| {
|
||||
seqs.push((s, Vec::new()));
|
||||
seqs.len() - 1
|
||||
});
|
||||
if let Some((_, members)) = seqs.get_mut(i) {
|
||||
members.push(ai);
|
||||
}
|
||||
}
|
||||
for seq in seqs {
|
||||
// Paragraph groups already recorded (the list itself keeps their order).
|
||||
let mut grouped: HashSet<(ShapeId, usize, usize)> = HashSet::new();
|
||||
for (seq, members) in seqs {
|
||||
if tl.effects.len() >= MAX_EFFECTS {
|
||||
break;
|
||||
}
|
||||
let mut steps: Vec<StepInfo> = Vec::new();
|
||||
let mut group_begin = 0.0f64;
|
||||
let mut max_end = 0.0f64;
|
||||
for (ai, a) in slide.animations.iter().enumerate() {
|
||||
let aseq = a.trigger.map(Sequence::Trigger).unwrap_or(Sequence::Main);
|
||||
if aseq != seq {
|
||||
continue;
|
||||
}
|
||||
for ai in members {
|
||||
let Some(a) = slide.animations.get(ai) else { continue };
|
||||
if tl.effects.len() >= MAX_EFFECTS {
|
||||
break;
|
||||
}
|
||||
@@ -198,7 +206,7 @@ impl Timeline {
|
||||
owner = Some(i);
|
||||
v.push(Some(i));
|
||||
} else if let Some(o) = owner
|
||||
&& !tl.groups.contains(&(a.shape, i, o))
|
||||
&& grouped.insert((a.shape, i, o))
|
||||
{
|
||||
tl.groups.push((a.shape, i, o));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user