aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmilio Cobos Álvarez <me@emiliocobos.me>2016-06-19 19:39:32 +0200
committerEmilio Cobos Álvarez <me@emiliocobos.me>2016-06-28 15:09:53 +0000
commitc16c5acade65f45141c4aa824d49cad3dff98b31 (patch)
tree7ad10a44e384c1c84a6828f1930d226e927dc9b6
parent5b27e46d04759e57d06ebe65e74d6a7191f0ab70 (diff)
downloadservo-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.rs2
-rw-r--r--components/layout/animation.rs31
-rw-r--r--components/layout_thread/lib.rs4
-rw-r--r--components/script/script_thread.rs2
-rw-r--r--components/style/animation.rs334
-rw-r--r--components/style/keyframes.rs31
-rw-r--r--components/style/matching.rs10
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);
}