mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
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:
parent
b290ad95c1
commit
b8874ad6ac
5 changed files with 670 additions and 692 deletions
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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")]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue