Split animations and transitions into separate lists

This change splits the list of animations and transitions, which are
almost always handled differently. It also renames
`ElementAnimationState` to `ElementAnimationSet` and establishes an
`AnimationState` for every transition and animation. This allows us to
stop continually reallocating lists every time a transition or animation
needs to be canceled.

Fixes #14419.
This commit is contained in:
Martin Robinson 2020-05-06 14:57:23 +02:00
parent b290ad95c1
commit b8874ad6ac
5 changed files with 670 additions and 692 deletions

View file

@ -12,10 +12,10 @@ use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress; use script_traits::UntrustedNodeAddress;
use script_traits::{ use script_traits::{
AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, AnimationState as AnimationsPresentState, ConstellationControlMsg,
TransitionOrAnimationEvent, TransitionOrAnimationEventType, LayoutMsg as ConstellationMsg, TransitionOrAnimationEvent, TransitionOrAnimationEventType,
}; };
use style::animation::{Animation, ElementAnimationState}; use style::animation::{AnimationState, ElementAnimationSet};
/// Processes any new animations that were discovered after style recalculation and /// Processes any new animations that were discovered after style recalculation and
/// remove animations for any disconnected nodes. Send messages that trigger events /// remove animations for any disconnected nodes. Send messages that trigger events
@ -23,7 +23,7 @@ use style::animation::{Animation, ElementAnimationState};
pub fn do_post_style_animations_update( pub fn do_post_style_animations_update(
constellation_chan: &IpcSender<ConstellationMsg>, constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>, script_chan: &IpcSender<ConstellationControlMsg>,
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>, animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationSet>,
pipeline_id: PipelineId, pipeline_id: PipelineId,
now: f64, now: f64,
out: &mut Vec<UntrustedNodeAddress>, out: &mut Vec<UntrustedNodeAddress>,
@ -31,16 +31,13 @@ pub fn do_post_style_animations_update(
) { ) {
let had_running_animations = animation_states let had_running_animations = animation_states
.values() .values()
.any(|state| !state.running_animations.is_empty()); .any(|state| state.needs_animation_ticks());
cancel_animations_for_disconnected_nodes(animation_states, root_flow); cancel_animations_for_disconnected_nodes(animation_states, root_flow);
collect_newly_animating_nodes(animation_states, out); collect_newly_animating_nodes(animation_states, out);
let mut have_running_animations = false;
for (node, animation_state) in animation_states.iter_mut() { for (node, animation_state) in animation_states.iter_mut() {
update_animation_state(script_chan, animation_state, pipeline_id, now, *node); update_animation_state(script_chan, animation_state, pipeline_id, now, *node);
have_running_animations =
have_running_animations || !animation_state.running_animations.is_empty();
} }
// Remove empty states from our collection of states in order to free // Remove empty states from our collection of states in order to free
@ -48,9 +45,12 @@ pub fn do_post_style_animations_update(
// a node. // a node.
animation_states.retain(|_, state| !state.is_empty()); animation_states.retain(|_, state| !state.is_empty());
let have_running_animations = animation_states
.values()
.any(|state| state.needs_animation_ticks());
let present = match (had_running_animations, have_running_animations) { let present = match (had_running_animations, have_running_animations) {
(true, false) => AnimationState::NoAnimationsPresent, (true, false) => AnimationsPresentState::NoAnimationsPresent,
(false, true) => AnimationState::AnimationsPresent, (false, true) => AnimationsPresentState::AnimationsPresent,
_ => return, _ => return,
}; };
constellation_chan constellation_chan
@ -65,14 +65,25 @@ pub fn do_post_style_animations_update(
/// forced, synchronous reflows to root DOM nodes for the duration of their /// forced, synchronous reflows to root DOM nodes for the duration of their
/// animations or transitions. /// animations or transitions.
pub fn collect_newly_animating_nodes( pub fn collect_newly_animating_nodes(
animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>, animation_states: &FxHashMap<OpaqueNode, ElementAnimationSet>,
out: &mut Vec<UntrustedNodeAddress>, out: &mut Vec<UntrustedNodeAddress>,
) { ) {
// This extends the output vector with an iterator that contains a copy of the node // This extends the output vector with an iterator that contains a copy of the node
// address for every new animation. This is a bit goofy, but the script thread // address for every new animation. The script thread currently stores a rooted node
// currently stores a rooted node for every property that is transitioning. // for every property that is transitioning. The current strategy of repeating the
// node address is a holdover from when the code here looked different.
out.extend(animation_states.iter().flat_map(|(node, state)| { out.extend(animation_states.iter().flat_map(|(node, state)| {
std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len()) let mut num_new_animations = state
.animations
.iter()
.filter(|animation| animation.is_new)
.count();
num_new_animations += state
.transitions
.iter()
.filter(|transition| transition.is_new)
.count();
std::iter::repeat(node.to_untrusted_node_address()).take(num_new_animations)
})); }));
} }
@ -81,7 +92,7 @@ pub fn collect_newly_animating_nodes(
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction. /// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented. /// This also doesn't yet handles nodes that have been reparented.
pub fn cancel_animations_for_disconnected_nodes( pub fn cancel_animations_for_disconnected_nodes(
animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>, animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationSet>,
root_flow: &mut dyn Flow, root_flow: &mut dyn Flow,
) { ) {
// Assume all nodes have been removed until proven otherwise. // Assume all nodes have been removed until proven otherwise.
@ -106,19 +117,12 @@ pub fn cancel_animations_for_disconnected_nodes(
fn update_animation_state( fn update_animation_state(
script_channel: &IpcSender<ConstellationControlMsg>, script_channel: &IpcSender<ConstellationControlMsg>,
animation_state: &mut ElementAnimationState, animation_state: &mut ElementAnimationSet,
pipeline_id: PipelineId, pipeline_id: PipelineId,
now: f64, now: f64,
node: OpaqueNode, node: OpaqueNode,
) { ) {
let send_event = |animation: &Animation, event_type, elapsed_time| { let send_event = |property_or_animation_name, event_type, elapsed_time| {
let property_or_animation_name = match *animation {
Animation::Transition(_, _, ref property_animation) => {
property_animation.property_name().into()
},
Animation::Keyframes(_, _, ref name, _) => name.to_string(),
};
script_channel script_channel
.send(ConstellationControlMsg::TransitionOrAnimationEvent( .send(ConstellationControlMsg::TransitionOrAnimationEvent(
TransitionOrAnimationEvent { TransitionOrAnimationEvent {
@ -132,88 +136,83 @@ fn update_animation_state(
.unwrap() .unwrap()
}; };
handle_cancelled_animations(animation_state, now, send_event); handle_canceled_animations(animation_state, now, send_event);
handle_running_animations(animation_state, now, send_event); finish_running_animations(animation_state, now, send_event);
handle_new_animations(animation_state, send_event); handle_new_animations(animation_state, send_event);
} }
/// Walk through the list of running animations and remove all of the ones that /// Walk through the list of running animations and remove all of the ones that
/// have ended. /// have ended.
fn handle_running_animations( fn finish_running_animations(
animation_state: &mut ElementAnimationState, animation_state: &mut ElementAnimationSet,
now: f64, now: f64,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) { ) {
if animation_state.running_animations.is_empty() { for animation in animation_state.animations.iter_mut() {
return; if animation.state == AnimationState::Running && animation.has_ended(now) {
animation.state = AnimationState::Finished;
send_event(
animation.name.to_string(),
TransitionOrAnimationEventType::AnimationEnd,
animation.active_duration(),
);
}
} }
let mut running_animations = for transition in animation_state.transitions.iter_mut() {
std::mem::replace(&mut animation_state.running_animations, Vec::new()); if transition.state == AnimationState::Running && transition.has_ended(now) {
for running_animation in running_animations.drain(..) { transition.state = AnimationState::Finished;
// If the animation is still running, add it back to the list of running animations. send_event(
if !running_animation.has_ended(now) { transition.property_animation.property_name().into(),
animation_state.running_animations.push(running_animation); TransitionOrAnimationEventType::TransitionEnd,
} else { transition.property_animation.duration,
let (event_type, elapsed_time) = match running_animation { );
Animation::Transition(_, _, ref property_animation) => (
TransitionOrAnimationEventType::TransitionEnd,
property_animation.duration,
),
Animation::Keyframes(_, _, _, ref state) => (
TransitionOrAnimationEventType::AnimationEnd,
state.active_duration(),
),
};
send_event(&running_animation, event_type, elapsed_time);
animation_state.finished_animations.push(running_animation);
} }
} }
} }
/// Send events for cancelled animations. Currently this only handles cancelled /// Send events for canceled animations. Currently this only handles canceled
/// transitions, but eventually this should handle cancelled CSS animations as /// transitions, but eventually this should handle canceled CSS animations as
/// well. /// well.
fn handle_cancelled_animations( fn handle_canceled_animations(
animation_state: &mut ElementAnimationState, animation_state: &mut ElementAnimationSet,
now: f64, now: f64,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) { ) {
for animation in animation_state.cancelled_animations.drain(..) { for transition in &animation_state.transitions {
match animation { if transition.state == AnimationState::Canceled {
Animation::Transition(_, start_time, _) => { // TODO(mrobinson): We need to properly compute the elapsed_time here
// TODO(mrobinson): We need to properly compute the elapsed_time here // according to https://drafts.csswg.org/css-transitions/#event-transitionevent
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent send_event(
send_event( transition.property_animation.property_name().into(),
&animation, TransitionOrAnimationEventType::TransitionCancel,
TransitionOrAnimationEventType::TransitionCancel, (now - transition.start_time).max(0.),
(now - start_time).max(0.), );
);
},
// TODO(mrobinson): We should send animationcancel events.
Animation::Keyframes(..) => {},
} }
} }
// TODO(mrobinson): We need to send animationcancel events.
animation_state.clear_canceled_animations();
} }
fn handle_new_animations( fn handle_new_animations(
animation_state: &mut ElementAnimationState, animation_state: &mut ElementAnimationSet,
mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64),
) { ) {
for animation in animation_state.new_animations.drain(..) { for animation in animation_state.animations.iter_mut() {
match animation { animation.is_new = false;
Animation::Transition(..) => { }
// TODO(mrobinson): We need to properly compute the elapsed_time here
// according to https://drafts.csswg.org/css-transitions/#event-transitionevent for transition in animation_state.transitions.iter_mut() {
send_event( if transition.is_new {
&animation, // TODO(mrobinson): We need to properly compute the elapsed_time here
TransitionOrAnimationEventType::TransitionRun, // according to https://drafts.csswg.org/css-transitions/#event-transitionevent
0., send_event(
) transition.property_animation.property_name().into(),
}, TransitionOrAnimationEventType::TransitionRun,
Animation::Keyframes(..) => {}, 0.,
);
transition.is_new = false;
} }
animation_state.running_animations.push(animation);
} }
} }

View file

@ -97,7 +97,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use style::animation::ElementAnimationState; use style::animation::ElementAnimationSet;
use style::context::SharedStyleContext; use style::context::SharedStyleContext;
use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters};
use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode};
@ -192,7 +192,7 @@ pub struct LayoutThread {
document_shared_lock: Option<SharedRwLock>, document_shared_lock: Option<SharedRwLock>,
/// The animation state for all of our nodes. /// The animation state for all of our nodes.
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationState>>>, animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
/// A counter for epoch messages /// A counter for epoch messages
epoch: Cell<Epoch>, epoch: Cell<Epoch>,
@ -829,7 +829,7 @@ impl LayoutThread {
.animation_states .animation_states
.read() .read()
.values() .values()
.map(|state| state.running_animations.len()) .map(|state| state.running_animation_and_transition_count())
.sum(); .sum();
let _ = sender.send(running_animation_count); let _ = sender.send(running_animation_count);
}, },

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
//! The context within which style is calculated. //! The context within which style is calculated.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use crate::animation::ElementAnimationState; use crate::animation::ElementAnimationSet;
use crate::bloom::StyleBloom; use crate::bloom::StyleBloom;
use crate::data::{EagerPseudoStyles, ElementData}; use crate::data::{EagerPseudoStyles, ElementData};
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
@ -167,7 +167,7 @@ pub struct SharedStyleContext<'a> {
/// The state of all animations for our styled elements. /// The state of all animations for our styled elements.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub animation_states: Arc<RwLock<FxHashMap<OpaqueNode, ElementAnimationState>>>, pub animation_states: Arc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
/// Paint worklets /// Paint worklets
#[cfg(feature = "servo")] #[cfg(feature = "servo")]

View file

@ -452,15 +452,15 @@ trait PrivateMatchMethods: TElement {
&context.thread_local.font_metrics_provider, &context.thread_local.font_metrics_provider,
); );
animation_state.apply_new_and_running_animations::<Self>( animation_state.apply_active_animations::<Self>(
shared_context, shared_context,
new_values, new_values,
&context.thread_local.font_metrics_provider, &context.thread_local.font_metrics_provider,
); );
// If the ElementAnimationState is empty, and don't store it in order to // If the ElementAnimationSet is empty, and don't store it in order to
// save memory and to avoid extra processing later. // save memory and to avoid extra processing later.
animation_state.finished_animations.clear(); animation_state.clear_finished_animations();
if !animation_state.is_empty() { if !animation_state.is_empty() {
animation_states.insert(this_opaque, animation_state); animation_states.insert(this_opaque, animation_state);
} }