mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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:
parent
6f35b867c9
commit
e881f0feeb
5 changed files with 134 additions and 56 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue