Write animated values into the ComputedValues structures when

animations complete or are interrupted.

This adds a new pair of reader-writer locks. I measured the performance
of style recalculation on Wikipedia and the overhead of the locks was
not measurable.

Closes #7816.
This commit is contained in:
Patrick Walton 2015-11-24 19:46:09 -06:00
parent 6f35b867c9
commit e881f0feeb
5 changed files with 134 additions and 56 deletions

View file

@ -19,11 +19,14 @@ use style::animation::{GetMod, PropertyAnimation};
use style::properties::ComputedValues; use style::properties::ComputedValues;
/// Inserts transitions into the queue of running animations as applicable for the given style /// Inserts transitions into the queue of running animations as applicable for the given style
/// difference. This is called from the layout worker threads. /// difference. This is called from the layout worker threads. Returns true if any animations were
/// kicked off and false otherwise.
pub fn start_transitions_if_applicable(new_animations_sender: &Mutex<Sender<Animation>>, pub fn start_transitions_if_applicable(new_animations_sender: &Mutex<Sender<Animation>>,
node: OpaqueNode, node: OpaqueNode,
old_style: &ComputedValues, old_style: &ComputedValues,
new_style: &mut ComputedValues) { new_style: &mut ComputedValues)
-> bool {
let mut had_animations = false;
for i in 0..new_style.get_animation().transition_property.0.len() { for i in 0..new_style.get_animation().transition_property.0.len() {
// Create any property animations, if applicable. // Create any property animations, if applicable.
let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); let property_animations = PropertyAnimation::from_transition(i, old_style, new_style);
@ -42,15 +45,20 @@ pub fn start_transitions_if_applicable(new_animations_sender: &Mutex<Sender<Anim
start_time: start_time, start_time: start_time,
end_time: start_time + end_time: start_time +
(animation_style.transition_duration.0.get_mod(i).seconds() as f64), (animation_style.transition_duration.0.get_mod(i).seconds() as f64),
}).unwrap() }).unwrap();
had_animations = true
} }
} }
had_animations
} }
/// Processes any new animations that were discovered after style recalculation. /// Processes any new animations that were discovered after style recalculation.
/// Also expire any old animations that have completed. /// Also expire any old animations that have completed, inserting them into `expired_animations`.
pub fn update_animation_state(constellation_chan: &ConstellationChan<ConstellationMsg>, pub fn update_animation_state(constellation_chan: &ConstellationChan<ConstellationMsg>,
running_animations: &mut Arc<HashMap<OpaqueNode, Vec<Animation>>>, running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
new_animations_receiver: &Receiver<Animation>, new_animations_receiver: &Receiver<Animation>,
pipeline_id: PipelineId) { pipeline_id: PipelineId) {
let mut new_running_animations = Vec::new(); let mut new_running_animations = Vec::new();
@ -58,9 +66,7 @@ pub fn update_animation_state(constellation_chan: &ConstellationChan<Constellati
new_running_animations.push(animation) new_running_animations.push(animation)
} }
let mut running_animations_hash = (**running_animations).clone(); if running_animations.is_empty() && new_running_animations.is_empty() {
if running_animations_hash.is_empty() && new_running_animations.is_empty() {
// Nothing to do. Return early so we don't flood the compositor with // Nothing to do. Return early so we don't flood the compositor with
// `ChangeRunningAnimationsState` messages. // `ChangeRunningAnimationsState` messages.
return return
@ -69,21 +75,33 @@ pub fn update_animation_state(constellation_chan: &ConstellationChan<Constellati
// Expire old running animations. // Expire old running animations.
let now = clock_ticks::precise_time_s(); let now = clock_ticks::precise_time_s();
let mut keys_to_remove = Vec::new(); let mut keys_to_remove = Vec::new();
for (key, running_animations) in &mut running_animations_hash { for (key, running_animations) in running_animations.iter_mut() {
running_animations.retain(|running_animation| { let mut animations_still_running = vec![];
now < running_animation.end_time for running_animation in running_animations.drain(..) {
}); if now < running_animation.end_time {
if running_animations.len() == 0 { animations_still_running.push(running_animation);
continue
}
match expired_animations.entry(*key) {
Entry::Vacant(entry) => {
entry.insert(vec![running_animation]);
}
Entry::Occupied(mut entry) => entry.get_mut().push(running_animation),
}
}
if animations_still_running.len() == 0 {
keys_to_remove.push(*key); keys_to_remove.push(*key);
} else {
*running_animations = animations_still_running
} }
} }
for key in keys_to_remove { for key in keys_to_remove {
running_animations_hash.remove(&key).unwrap(); running_animations.remove(&key).unwrap();
} }
// Add new running animations. // Add new running animations.
for new_running_animation in new_running_animations { for new_running_animation in new_running_animations {
match running_animations_hash.entry(OpaqueNode(new_running_animation.node)) { match running_animations.entry(OpaqueNode(new_running_animation.node)) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(vec![new_running_animation]); entry.insert(vec![new_running_animation]);
} }
@ -91,8 +109,6 @@ pub fn update_animation_state(constellation_chan: &ConstellationChan<Constellati
} }
} }
*running_animations = Arc::new(running_animations_hash);
let animation_state; let animation_state;
if running_animations.is_empty() { if running_animations.is_empty() {
animation_state = AnimationState::NoAnimationsPresent; animation_state = AnimationState::NoAnimationsPresent;
@ -112,21 +128,7 @@ pub fn recalc_style_for_animations(flow: &mut Flow,
flow.mutate_fragments(&mut |fragment| { flow.mutate_fragments(&mut |fragment| {
if let Some(ref animations) = animations.get(&OpaqueNode(fragment.node.id())) { if let Some(ref animations) = animations.get(&OpaqueNode(fragment.node.id())) {
for animation in *animations { for animation in *animations {
let now = clock_ticks::precise_time_s(); update_style_for_animation(animation, &mut fragment.style, Some(&mut damage));
let mut progress = (now - animation.start_time) / animation.duration();
if progress > 1.0 {
progress = 1.0
}
if progress <= 0.0 {
continue
}
let mut new_style = fragment.style.clone();
animation.property_animation.update(&mut *Arc::make_mut(&mut new_style),
progress);
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()),
&new_style));
fragment.style = new_style
} }
} }
}); });
@ -137,3 +139,27 @@ pub fn recalc_style_for_animations(flow: &mut Flow,
recalc_style_for_animations(kid, animations) recalc_style_for_animations(kid, 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(animation: &Animation,
style: &mut Arc<ComputedValues>,
damage: Option<&mut RestyleDamage>) {
let now = clock_ticks::precise_time_s();
let mut progress = (now - animation.start_time) / animation.duration();
if progress > 1.0 {
progress = 1.0
}
if progress <= 0.0 {
return
}
let mut new_style = (*style).clone();
animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress);
if let Some(damage) = damage {
damage.insert(incremental::compute_damage(&Some((*style).clone()), &new_style));
}
*style = new_style
}

View file

@ -25,7 +25,7 @@ use std::collections::HashMap;
use std::collections::hash_state::DefaultState; use std::collections::hash_state::DefaultState;
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::{Sender, channel}; use std::sync::mpsc::{Sender, channel};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex, RwLock};
use style::selector_matching::Stylist; use style::selector_matching::Stylist;
use url::Url; use url::Url;
use util::mem::HeapSizeOf; use util::mem::HeapSizeOf;
@ -120,7 +120,10 @@ pub struct SharedLayoutContext {
pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>, pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
/// The animations that are currently running. /// The animations that are currently running.
pub running_animations: Arc<HashMap<OpaqueNode, Vec<Animation>>>, pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
/// The list of animations that have expired since the last style recalculation.
pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
/// Why is this reflow occurring /// Why is this reflow occurring
pub goal: ReflowGoal, pub goal: ReflowGoal,

View file

@ -414,6 +414,10 @@ trait PrivateMatchMethods {
shareable: bool, shareable: bool,
animate_properties: bool) animate_properties: bool)
-> RestyleDamage; -> RestyleDamage;
fn update_animations_for_cascade(&self,
layout_context: &SharedLayoutContext,
style: &mut Option<Arc<ComputedValues>>)
-> bool;
} }
trait PrivateElementMatchMethods { trait PrivateElementMatchMethods {
@ -435,20 +439,12 @@ impl<'ln> PrivateMatchMethods for ServoLayoutNode<'ln> {
shareable: bool, shareable: bool,
animate_properties: bool) animate_properties: bool)
-> RestyleDamage { -> RestyleDamage {
// Finish any transitions. let mut cacheable = true;
if animate_properties { if animate_properties {
if let Some(ref mut style) = *style { cacheable = !self.update_animations_for_cascade(layout_context, style) && cacheable;
let this_opaque = self.opaque();
if let Some(ref animations) = layout_context.running_animations.get(&this_opaque) {
for animation in *animations {
animation.property_animation.update(&mut *Arc::make_mut(style), 1.0);
}
}
}
} }
let mut this_style; let mut this_style;
let cacheable;
match parent_style { match parent_style {
Some(ref parent_style) => { Some(ref parent_style) => {
let cache_entry = applicable_declarations_cache.find(applicable_declarations); let cache_entry = applicable_declarations_cache.find(applicable_declarations);
@ -461,7 +457,7 @@ impl<'ln> PrivateMatchMethods for ServoLayoutNode<'ln> {
shareable, shareable,
Some(&***parent_style), Some(&***parent_style),
cached_computed_values); cached_computed_values);
cacheable = is_cacheable; cacheable = cacheable && is_cacheable;
this_style = the_style this_style = the_style
} }
None => { None => {
@ -470,7 +466,7 @@ impl<'ln> PrivateMatchMethods for ServoLayoutNode<'ln> {
shareable, shareable,
None, None,
None); None);
cacheable = is_cacheable; cacheable = cacheable && is_cacheable;
this_style = the_style this_style = the_style
} }
}; };
@ -479,10 +475,12 @@ impl<'ln> PrivateMatchMethods for ServoLayoutNode<'ln> {
// it did trigger a transition. // it did trigger a transition.
if animate_properties { if animate_properties {
if let Some(ref style) = *style { if let Some(ref style) = *style {
animation::start_transitions_if_applicable(new_animations_sender, let animations_started =
self.opaque(), animation::start_transitions_if_applicable(new_animations_sender,
&**style, self.opaque(),
&mut this_style); &**style,
&mut this_style);
cacheable = cacheable && !animations_started
} }
} }
@ -500,6 +498,50 @@ impl<'ln> PrivateMatchMethods for ServoLayoutNode<'ln> {
*style = Some(this_style); *style = Some(this_style);
damage damage
} }
fn update_animations_for_cascade(&self,
layout_context: &SharedLayoutContext,
style: &mut Option<Arc<ComputedValues>>)
-> bool {
let style = match *style {
None => return false,
Some(ref mut style) => style,
};
// Finish any expired transitions.
let this_opaque = self.opaque();
let had_animations_to_expire;
{
let all_expired_animations = layout_context.expired_animations.read().unwrap();
let animations_to_expire = all_expired_animations.get(&this_opaque);
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(&mut *Arc::make_mut(style), 1.0);
}
}
}
if had_animations_to_expire {
layout_context.expired_animations.write().unwrap().remove(&this_opaque);
}
// Merge any running transitions into the current style, and cancel them.
let had_running_animations = layout_context.running_animations
.read()
.unwrap()
.get(&this_opaque)
.is_some();
if had_running_animations {
let mut all_running_animations = layout_context.running_animations.write().unwrap();
for running_animation in all_running_animations.get(&this_opaque).unwrap() {
animation::update_style_for_animation(running_animation, style, None);
}
all_running_animations.remove(&this_opaque);
}
had_animations_to_expire || had_running_animations
}
} }
impl<'ln> PrivateElementMatchMethods for ServoLayoutElement<'ln> { impl<'ln> PrivateElementMatchMethods for ServoLayoutElement<'ln> {

View file

@ -59,7 +59,7 @@ use std::mem::transmute;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard, RwLock};
use style::computed_values::{filter, mix_blend_mode}; use style::computed_values::{filter, mix_blend_mode};
use style::media_queries::{Device, MediaType}; use style::media_queries::{Device, MediaType};
use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS}; use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS};
@ -194,7 +194,10 @@ pub struct LayoutTask {
visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>, visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
/// The list of currently-running animations. /// The list of currently-running animations.
running_animations: Arc<HashMap<OpaqueNode, Vec<Animation>>>, running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
/// The list of animations that have expired since the last style recalculation.
expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
/// A counter for epoch messages /// A counter for epoch messages
epoch: Epoch, epoch: Epoch,
@ -420,7 +423,8 @@ impl LayoutTask {
outstanding_web_fonts: outstanding_web_fonts_counter, outstanding_web_fonts: outstanding_web_fonts_counter,
root_flow: None, root_flow: None,
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())), visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
running_animations: Arc::new(HashMap::new()), running_animations: Arc::new(RwLock::new(HashMap::new())),
expired_animations: Arc::new(RwLock::new(HashMap::new())),
epoch: Epoch(0), epoch: Epoch(0),
viewport_size: Size2D::new(Au(0), Au(0)), viewport_size: Size2D::new(Au(0), Au(0)),
rw_data: Arc::new(Mutex::new( rw_data: Arc::new(Mutex::new(
@ -471,6 +475,7 @@ impl LayoutTask {
new_animations_sender: Mutex::new(self.new_animations_sender.clone()), new_animations_sender: Mutex::new(self.new_animations_sender.clone()),
goal: goal, goal: goal,
running_animations: self.running_animations.clone(), running_animations: self.running_animations.clone(),
expired_animations: self.expired_animations.clone(),
} }
} }
@ -1137,13 +1142,13 @@ impl LayoutTask {
if let Some(mut root_flow) = self.root_flow.clone() { if let Some(mut root_flow) = self.root_flow.clone() {
// Perform an abbreviated style recalc that operates without access to the DOM. // Perform an abbreviated style recalc that operates without access to the DOM.
let animations = &*self.running_animations; let animations = self.running_animations.read().unwrap();
profile(time::ProfilerCategory::LayoutStyleRecalc, profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(), self.profiler_metadata(),
self.time_profiler_chan.clone(), self.time_profiler_chan.clone(),
|| { || {
animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow), animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow),
animations) &*animations)
}); });
} }
@ -1182,7 +1187,8 @@ impl LayoutTask {
if let Some(mut root_flow) = self.root_flow.clone() { if let Some(mut root_flow) = self.root_flow.clone() {
// Kick off animations if any were triggered, expire completed ones. // Kick off animations if any were triggered, expire completed ones.
animation::update_animation_state(&self.constellation_chan, animation::update_animation_state(&self.constellation_chan,
&mut self.running_animations, &mut *self.running_animations.write().unwrap(),
&mut *self.expired_animations.write().unwrap(),
&self.new_animations_receiver, &self.new_animations_receiver,
self.id); self.id);

View file

@ -5,6 +5,7 @@
#![feature(box_syntax)] #![feature(box_syntax)]
#![feature(cell_extras)] #![feature(cell_extras)]
#![feature(custom_derive)] #![feature(custom_derive)]
#![feature(drain)]
#![feature(hashmap_hasher)] #![feature(hashmap_hasher)]
#![feature(mpsc_select)] #![feature(mpsc_select)]
#![feature(plugin)] #![feature(plugin)]