diff options
author | Emilio Cobos Álvarez <me@emiliocobos.me> | 2016-06-19 19:39:32 +0200 |
---|---|---|
committer | Emilio Cobos Álvarez <me@emiliocobos.me> | 2016-06-28 15:09:53 +0000 |
commit | c16c5acade65f45141c4aa824d49cad3dff98b31 (patch) | |
tree | 7ad10a44e384c1c84a6828f1930d226e927dc9b6 | |
parent | 5b27e46d04759e57d06ebe65e74d6a7191f0ab70 (diff) | |
download | servo-c16c5acade65f45141c4aa824d49cad3dff98b31.tar.gz servo-c16c5acade65f45141c4aa824d49cad3dff98b31.zip |
style: Rewrite the animation representation to allow having state in layout
I have to make the appropriate changes in layout, but I'm running out of battery
in the bus.
-rw-r--r-- | components/constellation/constellation.rs | 2 | ||||
-rw-r--r-- | components/layout/animation.rs | 31 | ||||
-rw-r--r-- | components/layout_thread/lib.rs | 4 | ||||
-rw-r--r-- | components/script/script_thread.rs | 2 | ||||
-rw-r--r-- | components/style/animation.rs | 334 | ||||
-rw-r--r-- | components/style/keyframes.rs | 31 | ||||
-rw-r--r-- | components/style/matching.rs | 10 |
7 files changed, 260 insertions, 154 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index be73d3895d9..1439f24b0b2 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -1136,7 +1136,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let msg = LayoutControlMsg::TickAnimations; match self.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline.layout_chan.send(msg), - None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id), + None => return warn!("Pipeline {:?} got layout tick after closure.", pipeline_id), } } }; diff --git a/components/layout/animation.rs b/components/layout/animation.rs index cc87a2824a4..b8e5bed96d4 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -23,7 +23,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, new_animations_receiver: &Receiver<Animation>, pipeline_id: PipelineId) { - let mut new_running_animations = Vec::new(); + let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { new_running_animations.push(animation) } @@ -36,22 +36,24 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, // Expire old running animations. let now = time::precise_time_s(); - let mut keys_to_remove = Vec::new(); + let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; for running_animation in running_animations.drain(..) { if now < running_animation.end_time { animations_still_running.push(running_animation); continue + } else if running_animation.state.pending_iterations > 0 { + // if the animation should run again, just tick it... + let duration = running_animation.end_time - running_animation.start_time; + running_animation.start_time += duration; } - match expired_animations.entry(*key) { - Entry::Vacant(entry) => { - entry.insert(vec![running_animation]); - } - Entry::Occupied(mut entry) => entry.get_mut().push(running_animation), - } + expired_animations.entry(*key) + .or_insert_with(Vec::new) + .push(running_animation); } - if animations_still_running.len() == 0 { + + if animations_still_running.is_empty() { keys_to_remove.push(*key); } else { *running_animations = animations_still_running @@ -84,12 +86,15 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, /// Recalculates style for a set of animations. This does *not* run with the DOM lock held. pub fn recalc_style_for_animations(flow: &mut Flow, - animations: &HashMap<OpaqueNode, Vec<Animation>>) { + animations: &mut HashMap<OpaqueNode, Vec<Animation>>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get(&fragment.node) { - for animation in *animations { - update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + if let Some(ref animations) = animations.get_mut(&fragment.node) { + for mut animation in *animations { + if !animation.is_paused() { + update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + animation.increment_keyframe_if_applicable(); + } } } }); diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index fad57c499d3..67e21ab744b 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1302,13 +1302,13 @@ impl LayoutThread { if let Some(mut root_flow) = self.root_flow.clone() { // Perform an abbreviated style recalc that operates without access to the DOM. - let animations = self.running_animations.read().unwrap(); + let mut animations = self.running_animations.write().unwrap(); profile(time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), self.time_profiler_chan.clone(), || { animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow), - &*animations) + &mut animations) }); } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6697f334696..96555524c5d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -671,7 +671,7 @@ impl ScriptThread { } // Store new resizes, and gather all other events. - let mut sequential = vec!(); + let mut sequential = vec![]; // Receive at least one message so we don't spinloop. let mut event = { diff --git a/components/style/animation.rs b/components/style/animation.rs index d6604b40db5..83c7fffc1e2 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -13,6 +13,8 @@ use keyframes::KeyframesStep; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; +use properties::longhands::animation_play_state::computed_value::AnimationPlayState; +use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; use properties::style_struct_traits::Box; use properties::{ComputedValues, ServoComputedValues}; use std::sync::mpsc::Sender; @@ -22,43 +24,60 @@ use values::computed::Time; use selector_impl::SelectorImplExt; use context::SharedStyleContext; use selectors::matching::DeclarationBlock; +use string_cache::Atom; use properties; -#[derive(Clone, Debug)] -pub enum AnimationKind { - Transition, - Keyframe, +/// This structure represents a keyframes animation current iteration state. +/// +/// If the iteration count is infinite, there's no other state, otherwise we +/// have to keep track the current iteration and the max iteration count. +#[derive(Debug, Clone)] +pub enum KeyframesIterationState { + Infinite, + // current, max + Finite(u32, u32), } -/// State relating to an animation. -#[derive(Clone)] -pub struct Animation { - /// The kind of animation, either a transition or a keyframe. - pub kind: AnimationKind, - /// An opaque reference to the DOM node participating in the animation. - pub node: OpaqueNode, - /// A description of the property animation that is occurring. - pub property_animation: PropertyAnimation, - /// The start time of the animation, as returned by `time::precise_time_s()`. - pub start_time: f64, - /// The end time of the animation, as returned by `time::precise_time_s()`. - pub end_time: f64, +/// This structure represents the current keyframe animation state, i.e., the +/// duration, the current and maximum iteration count, and the state (either +/// playing or paused). +#[derive(Debug, Clone)] +pub struct KeyframesAnimationState { + pub started_at: f64, + pub duration: f64, + pub iteration_state: KeyframesIterationState, + pub paused: bool, } -impl Animation { - /// Returns the duration of this animation in seconds. - #[inline] - pub fn duration(&self) -> f64 { - self.end_time - self.start_time - } +/// State relating to an animation. +#[derive(Clone, Debug)] +pub enum Animation { + /// A transition is just a single frame triggered at a time, with a reflow. + /// + /// the f64 field is the start time as returned by `time::precise_time_s()`. + Transition(OpaqueNode, f64, AnimationFrame), + /// A keyframes animation is identified by a name, and can have a + /// node-dependent state (i.e. iteration count, etc.). + Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } +/// A keyframes animation previously sent to layout. -#[derive(Clone, Debug)] +/// A single animation frame of a single property. +#[derive(Debug, Clone)] +pub struct AnimationFrame { + /// A description of the property animation that is occurring. + pub property_animation: PropertyAnimation, + /// The duration of the animation. This is either relative in the keyframes + /// case (a number between 0 and 1), or absolute in the transition case. + pub duration: f64, +} + +#[derive(Debug, Clone)] pub struct PropertyAnimation { property: AnimatedProperty, timing_function: TransitionTimingFunction, - duration: Time, + duration: Time, // TODO: isn't this just repeated? } impl PropertyAnimation { @@ -167,9 +186,12 @@ impl<T> GetMod for Vec<T> { } } -/// Inserts transitions into the queue of running animations as applicable for the given style -/// difference. This is called from the layout worker threads. Returns true if any animations were -/// kicked off and false otherwise. +/// Inserts transitions into the queue of running animations as applicable for +/// the given style difference. This is called from the layout worker threads. +/// Returns true if any animations were kicked off and false otherwise. +// +// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a +// cloneable part and a non-cloneable part.. pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: &Mutex<Sender<Animation>>, node: OpaqueNode, old_style: &C, @@ -188,16 +210,14 @@ pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: let box_style = new_style.as_servo().get_box(); let start_time = now + (box_style.transition_delay.0.get_mod(i).seconds() as f64); - new_animations_sender.lock().unwrap().send(Animation { - kind: AnimationKind::Transition, - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: start_time + - (box_style.transition_duration.0.get_mod(i).seconds() as f64), - }).unwrap(); - - had_animations = true + new_animations_sender + .lock().unwrap() + .send(Animation::Transition(node, start_time, AnimationFrame { + duration: box_style.transition_duration.0.get_mod(i).seconds() as f64, + property_animation: property_animation, + })).unwrap(); + + had_animations = true; } } @@ -227,95 +247,191 @@ pub fn maybe_start_animations<Impl: SelectorImplExt>(context: &SharedStyleContex { let mut had_animations = false; - for (i, name) in new_style.as_servo().get_box().animation_name.0.iter().enumerate() { + let box_style = new_style.as_servo().get_box(); + for (i, name) in box_style.animation_name.0.iter().enumerate() { debug!("maybe_start_animations: name={}", name); - let total_duration = new_style.as_servo().get_box().animation_duration.0.get_mod(i).seconds(); + let total_duration = box_style.animation_duration.0.get_mod(i).seconds(); if total_duration == 0. { continue } - // TODO: This should be factored out, too much indentation. if let Some(ref animation) = context.stylist.animations().get(&name) { - debug!("maybe_start_animations: found animation {}", name); - had_animations = true; - let mut last_keyframe_style = compute_style_for_animation_step(context, - &animation.steps[0], - new_style); - // Apply the style inmediately. TODO: clone()... - // *new_style = last_keyframe_style.clone(); - - let mut ongoing_animation_percentage = animation.steps[0].duration_percentage.0; - let delay = new_style.as_servo().get_box().animation_delay.0.get_mod(i).seconds(); + let delay = box_style.animation_delay.0.get_mod(i).seconds(); let animation_start = time::precise_time_s() + delay as f64; - - // TODO: We can probably be smarter here and batch steps out or - // something. - for step in &animation.steps[1..] { - for transition_property in &animation.properties_changed { - debug!("maybe_start_animations: processing animation prop {:?} for animation {}", transition_property, name); - - let new_keyframe_style = compute_style_for_animation_step(context, - step, - &last_keyframe_style); - // NB: This will get the previous frame timing function, or - // the old one if caught, which is what the spec says. - // - // We might need to reset to the initial timing function - // though. - let timing_function = - *last_keyframe_style.as_servo() - .get_box().animation_timing_function.0.get_mod(i); - - let percentage = step.duration_percentage.0; - let this_keyframe_duration = total_duration * percentage; - if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, - timing_function, - Time(this_keyframe_duration), - &last_keyframe_style, - &new_keyframe_style) { - debug!("maybe_start_animations: got property animation for prop {:?}", transition_property); - - let relative_start_time = ongoing_animation_percentage * total_duration; - let start_time = animation_start + relative_start_time as f64; - let end_time = start_time + (relative_start_time + this_keyframe_duration) as f64; - context.new_animations_sender.lock().unwrap().send(Animation { - kind: AnimationKind::Keyframe, - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: end_time, - }).unwrap(); - } - - last_keyframe_style = new_keyframe_style; - ongoing_animation_percentage += percentage; - } - } + let duration = box_style.animation_duration.0.get_mod(i).seconds(); + let iteration_state = match *box_style.animation_iteration_count.0.get_mod(i) { + AnimationIterationCount::Infinite => KeyframesIterationState::Infinite, + AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0, n), + }; + let paused = *box_style.animation_play_state.0.get_mod(i) == AnimationPlayState::paused; + + context.new_animations_sender + .lock().unwrap() + .send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState { + started_at: animation_start, + duration: duration as f64, + iteration_state: iteration_state, + paused: paused, + })).unwrap(); + had_animations = true; } } had_animations } -/// Updates a single animation and associated style based on the current time. If `damage` is -/// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation<Damage: TRestyleDamage>(animation: &Animation, - style: &mut Arc<Damage::ConcreteComputedValues>, - damage: Option<&mut Damage>) { - let now = time::precise_time_s(); - let mut progress = (now - animation.start_time) / animation.duration(); +/// Updates a given computed style for a given animation frame. Returns a bool +/// representing if the style was indeed updated. +pub fn update_style_for_animation_frame<C: ComputedValues>(mut new_style: &mut Arc<C>, + now: f64, + start_time: f64, + frame: &AnimationFrame) -> bool { + let mut progress = (now - start_time) / frame.duration; if progress > 1.0 { progress = 1.0 } + if progress <= 0.0 { - return + return false; } - let mut new_style = (*style).clone(); - animation.property_animation.update(Arc::make_mut(&mut new_style), progress); - if let Some(damage) = damage { - *damage = *damage | Damage::compute(Some(style), &new_style); - } + frame.property_animation.update(Arc::make_mut(&mut new_style), progress); + + true +} +/// Updates a single animation and associated style based on the current time. If `damage` is +/// provided, inserts the appropriate restyle damage. +pub fn update_style_for_animation<Damage, Impl>(context: &SharedStyleContext<Impl>, + animation: &Animation, + style: &mut Arc<Damage::ConcreteComputedValues>, + damage: Option<&mut Damage>) +where Impl: SelectorImplExt, + Damage: TRestyleDamage<ConcreteComputedValues = Impl::ComputedValues> { + let now = time::precise_time_s(); + match *animation { + Animation::Transition(_, start_time, ref frame) => { + let mut new_style = (*style).clone(); + let updated_style = update_style_for_animation_frame(&mut new_style, + now, start_time, + frame); + if updated_style { + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } + + *style = new_style + } + } + Animation::Keyframes(_, ref name, ref state) => { + debug_assert!(!state.paused); + let duration = state.duration; + let started_at = state.started_at; + + let animation = match context.stylist.animations().get(name) { + None => { + warn!("update_style_for_animation: Animation {:?} not found", name); + return; + } + Some(animation) => animation, + }; + + let maybe_index = style.as_servo() + .get_box().animation_name.0.iter() + .position(|animation_name| name == animation_name); + + let index = match maybe_index { + Some(index) => index, + None => { + warn!("update_style_for_animation: Animation {:?} not found in style", name); + return; + } + }; + + let total_duration = style.as_servo().get_box().animation_duration.0.get_mod(index).seconds() as f64; + if total_duration == 0. { + debug!("update_style_for_animation: zero duration for animation {:?}", name); + return; + } + + let mut total_progress = (now - started_at) / total_duration; + if total_progress < 0. { + warn!("Negative progress found for animation {:?}", name); + } + if total_progress > 1. { + total_progress = 1.; + } + + + let mut last_keyframe = None; + let mut target_keyframe = None; - *style = new_style + // TODO: we could maybe binary-search this? + for i in 1..animation.steps.len() { + if total_progress as f32 <= animation.steps[i].start_percentage.0 { + // We might have found our current keyframe. + target_keyframe = Some(&animation.steps[i]); + last_keyframe = target_keyframe; + } + } + + let target_keyframe = match target_keyframe { + Some(current) => current, + None => { + warn!("update_style_for_animation: No current keyframe found for animation {:?} at progress {}", name, total_progress); + return; + } + }; + + let last_keyframe = match last_keyframe { + Some(last_keyframe) => last_keyframe, + None => { + warn!("update_style_for_animation: No last keyframe found for animation {:?} at progress {}", name, total_progress); + return; + } + }; + + let relative_duration = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0) as f64 * duration; + let last_keyframe_ended_at = state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64); + let relative_progress = (now - last_keyframe_ended_at) / relative_duration; + + // TODO: How could we optimise it? Is it such a big deal? + let from_style = compute_style_for_animation_step(context, + last_keyframe, + &**style); + + // NB: The spec says that the timing function can be overwritten + // from the keyframe style. + let mut timing_function = *style.as_servo().get_box().animation_timing_function.0.get_mod(index); + if !from_style.as_servo().get_box().animation_timing_function.0.is_empty() { + timing_function = from_style.as_servo().get_box().animation_timing_function.0[0]; + } + + let mut target_style = compute_style_for_animation_step(context, + target_keyframe, + &from_style); + + let mut new_style = (*style).clone(); + let mut style_changed = false; + + for transition_property in &animation.properties_changed { + if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, + timing_function, + Time(relative_duration as f32), + &from_style, + &target_style) { + debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); + property_animation.update(Arc::make_mut(&mut new_style), relative_progress); + style_changed = true; + } + } + + if style_changed { + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } + + *style = new_style; + } + } + } } diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 75d4e8ee572..501e3863960 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -113,9 +113,8 @@ impl Keyframe { /// A single step from a keyframe animation. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] pub struct KeyframesStep { - /// The percentage of the animation duration that should be taken for this - /// step. - pub duration_percentage: KeyframePercentage, + /// The percentage of the animation duration when this step starts. + pub start_percentage: KeyframePercentage, /// Declarations that will determine the final style during the step. pub declarations: Arc<Vec<PropertyDeclaration>>, } @@ -125,7 +124,7 @@ impl KeyframesStep { fn new(percentage: KeyframePercentage, declarations: Arc<Vec<PropertyDeclaration>>) -> Self { KeyframesStep { - duration_percentage: percentage, + start_percentage: percentage, declarations: declarations, } } @@ -166,9 +165,6 @@ impl KeyframesAnimation { debug_assert!(keyframes.len() > 1); let mut steps = vec![]; - // NB: we do two passes, first storing the steps in the order of - // appeareance, then sorting them, then updating with the real - // "duration_percentage". let mut animated_properties = get_animated_properties(&keyframes[0]); if animated_properties.is_empty() { return None; @@ -181,24 +177,8 @@ impl KeyframesAnimation { } } - steps.sort_by_key(|step| step.duration_percentage); - - if steps[0].duration_percentage != KeyframePercentage(0.0) { - // TODO: we could just insert a step from 0 and without declarations - // so we won't animate at the beginning. Seems like what other - // engines do, but might be a bit tricky so I'd rather leave it as a - // follow-up. - return None; - } - - let mut remaining = 1.0; - let mut last_step_end = 0.0; - debug_assert!(steps.len() > 1); - for current_step in &mut steps[1..] { - let new_duration_percentage = KeyframePercentage(current_step.duration_percentage.0 - last_step_end); - last_step_end = current_step.duration_percentage.0; - current_step.duration_percentage = new_duration_percentage; - } + // Sort by the start percentage, so we can easily find a frame. + steps.sort_by_key(|step| step.start_percentage); Some(KeyframesAnimation { steps: steps, @@ -206,3 +186,4 @@ impl KeyframesAnimation { }) } } + diff --git a/components/style/matching.rs b/components/style/matching.rs index 2ed642de168..8e1a04577c4 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -6,7 +6,7 @@ #![allow(unsafe_code)] -use animation; +use animation::{self, Animation}; use context::{SharedStyleContext, LocalStyleContext}; use data::PrivateStyleData; use dom::{TElement, TNode, TRestyleDamage}; @@ -471,7 +471,10 @@ trait PrivateMatchMethods: TNode had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { - animation.property_animation.update(Arc::make_mut(style), 1.0); + // TODO: revisit this code for keyframes + if let Animation::Transition(_, _, ref frame) = *animation { + frame.property_animation.update(Arc::make_mut(style), 1.0); + } } } } @@ -489,7 +492,8 @@ trait PrivateMatchMethods: TNode if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::<Self::ConcreteRestyleDamage>(running_animation, style, None); + animation::update_style_for_animation::<Self::ConcreteRestyleDamage, + <Self::ConcreteElement as Element>::Impl>(context, running_animation, style, None); } all_running_animations.remove(&this_opaque); } |