From af058e633261328f01ae9f1bf2405726e29e972c Mon Sep 17 00:00:00 2001 From: David Shin Date: Sat, 9 Jul 2022 17:33:59 +0000 Subject: [PATCH] style: C++ `ComputedTimingFunction` uses Rust's timing function calculation This was made economical by having Rust's computed `easing::TimingFunction` use a fully resolved function for `linear(...)` easing, as per draft resolution from https://github.com/w3c/csswg-drafts/issues/7415 Differential Revision: https://phabricator.services.mozilla.com/D151295 --- components/style/piecewise_linear.rs | 54 ++++++++++-- components/style/values/computed/easing.rs | 33 +------- components/style/values/generics/easing.rs | 37 ++------ components/style/values/resolved/mod.rs | 1 + components/style/values/specified/easing.rs | 94 ++++++++++++++++----- 5 files changed, 134 insertions(+), 85 deletions(-) diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs index acc064fdbcc..36f656ec82b 100644 --- a/components/style/piecewise_linear.rs +++ b/components/style/piecewise_linear.rs @@ -3,25 +3,64 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A piecewise linear function, following CSS linear easing +use crate::values::computed::Percentage; +use core::slice::Iter; /// draft as in https://github.com/w3c/csswg-drafts/pull/6533. use euclid::approxeq::ApproxEq; use itertools::Itertools; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; use crate::values::CSSFloat; type ValueType = CSSFloat; /// a single entry in a piecewise linear function. -#[derive(Clone, Copy)] +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToResolvedValue, + Serialize, + Deserialize, +)] #[repr(C)] -struct PiecewiseLinearFunctionEntry { - x: ValueType, - y: ValueType, +pub struct PiecewiseLinearFunctionEntry { + pub x: ValueType, + pub y: ValueType, +} + +impl ToCss for PiecewiseLinearFunctionEntry { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + self.y.to_css(dest)?; + dest.write_str(" ")?; + Percentage(self.x).to_css(dest) + } } /// Representation of a piecewise linear function, a series of linear functions. -#[derive(Default)] +#[derive( + Default, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToResolvedValue, + ToCss, + Serialize, + Deserialize, +)] #[repr(C)] +#[css(comma)] pub struct PiecewiseLinearFunction { + #[css(iterable)] entries: crate::OwnedSlice, } @@ -102,6 +141,11 @@ impl PiecewiseLinearFunction { } builder.build() } + + #[allow(missing_docs)] + pub fn iter(&self) -> Iter { + self.entries.iter() + } } /// Entry of a piecewise linear function while building, where the calculation of x value can be deferred. diff --git a/components/style/values/computed/easing.rs b/components/style/values/computed/easing.rs index 6bcf875527a..06916b5001e 100644 --- a/components/style/values/computed/easing.rs +++ b/components/style/values/computed/easing.rs @@ -7,31 +7,16 @@ use euclid::approxeq::ApproxEq; use crate::bezier::Bezier; -use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuildParameters}; -use crate::values::computed::{Integer, Number, Percentage}; +use crate::piecewise_linear::PiecewiseLinearFunction; +use crate::values::computed::{Integer, Number}; use crate::values::generics::easing::{self, BeforeFlag, StepPosition, TimingKeyword}; /// A computed timing function. -pub type ComputedTimingFunction = easing::TimingFunction; +pub type ComputedTimingFunction = easing::TimingFunction; /// An alias of the computed timing function. pub type TimingFunction = ComputedTimingFunction; -/// A computed linear easing entry. -pub type ComputedLinearStop = easing::LinearStop; - -impl ComputedLinearStop { - /// Convert this type to entries that can be used to build PiecewiseLinearFunction. - pub fn to_piecewise_linear_build_parameters( - x: &ComputedLinearStop, - ) -> PiecewiseLinearFunctionBuildParameters { - ( - x.output, - x.input.into_rust().map(|x| x.0), - ) - } -} - impl ComputedTimingFunction { fn calculate_step_output( steps: i32, @@ -91,17 +76,7 @@ impl ComputedTimingFunction { TimingFunction::Steps(steps, pos) => { Self::calculate_step_output(*steps, *pos, progress, before_flag) }, - TimingFunction::LinearFunction(elements) => { - // TODO(dshin): For servo, which uses this code path, constructing the function - // every time the animation advances seem... expensive. - PiecewiseLinearFunction::from_iter( - elements - .iter() - .map(ComputedLinearStop::to_piecewise_linear_build_parameters), - ) - .at(progress as f32) - .into() - }, + TimingFunction::LinearFunction(function) => function.at(progress as f32).into(), TimingFunction::Keyword(keyword) => match keyword { TimingKeyword::Linear => return progress, TimingKeyword::Ease => { diff --git a/components/style/values/generics/easing.rs b/components/style/values/generics/easing.rs index f15b8f0e0ac..f99333fbad0 100644 --- a/components/style/values/generics/easing.rs +++ b/components/style/values/generics/easing.rs @@ -6,31 +6,6 @@ //! https://drafts.csswg.org/css-easing/#timing-functions use crate::parser::ParserContext; -use crate::values::generics::Optional; - -/// An entry for linear easing function. -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - Serialize, - Deserialize, -)] -#[repr(C)] -pub struct LinearStop { - /// Output of the function at the given point. - pub output: Number, - /// Playback progress at which this output is given. - #[css(skip_if = "Optional::is_none")] - pub input: Optional, -} /// A generic easing function. #[derive( @@ -39,9 +14,7 @@ pub struct LinearStop { MallocSizeOf, PartialEq, SpecifiedValueInfo, - ToComputedValue, ToCss, - ToResolvedValue, ToShmem, Serialize, Deserialize, @@ -49,7 +22,7 @@ pub struct LinearStop { #[value_info(ty = "TIMING_FUNCTION")] #[repr(u8, C)] /// cbindgen:private-default-tagged-enum-constructor=false -pub enum TimingFunction { +pub enum TimingFunction { /// `linear | ease | ease-in | ease-out | ease-in-out` Keyword(TimingKeyword), /// `cubic-bezier(, , , )` @@ -69,8 +42,8 @@ pub enum TimingFunction { /// linear([]#) /// = && ? /// = {1, 2} - #[css(comma, function = "linear")] - LinearFunction(#[css(iterable)] crate::OwnedSlice>), + #[css(function = "linear")] + LinearFunction(LinearStops), } #[allow(missing_docs)] @@ -106,7 +79,7 @@ pub enum TimingKeyword { #[repr(u8)] pub enum BeforeFlag { Unset, - Set + Set, } #[cfg(feature = "gecko")] @@ -154,7 +127,7 @@ fn is_end(position: &StepPosition) -> bool { *position == StepPosition::JumpEnd || *position == StepPosition::End } -impl TimingFunction { +impl TimingFunction { /// `ease` #[inline] pub fn ease() -> Self { diff --git a/components/style/values/resolved/mod.rs b/components/style/values/resolved/mod.rs index 817585002bb..a765980756c 100644 --- a/components/style/values/resolved/mod.rs +++ b/components/style/values/resolved/mod.rs @@ -86,6 +86,7 @@ trivial_to_resolved_value!(crate::Namespace); trivial_to_resolved_value!(crate::Prefix); trivial_to_resolved_value!(computed::LengthPercentage); trivial_to_resolved_value!(style_traits::values::specified::AllowedNumericType); +trivial_to_resolved_value!(computed::TimingFunction); impl ToResolvedValue for (A, B) where diff --git a/components/style/values/specified/easing.rs b/components/style/values/specified/easing.rs index d2535101bde..26c66345413 100644 --- a/components/style/values/specified/easing.rs +++ b/components/style/values/specified/easing.rs @@ -4,12 +4,10 @@ //! Specified types for CSS Easing functions. use crate::parser::{Parse, ParserContext}; -use crate::values::computed::easing::ComputedLinearStop; +use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuildParameters}; use crate::values::computed::easing::TimingFunction as ComputedTimingFunction; -use crate::values::computed::Percentage as ComputedPercentage; -use crate::values::generics::easing::{ - LinearStop as GenericLinearStop, TimingFunction as GenericTimingFunction, -}; +use crate::values::computed::{Context, ToComputedValue}; +use crate::values::generics::easing::TimingFunction as GenericTimingFunction; use crate::values::generics::easing::{StepPosition, TimingKeyword}; use crate::values::specified::{Integer, Number, Percentage}; use cssparser::{Delimiter, Parser, Token}; @@ -17,10 +15,32 @@ use selectors::parser::SelectorParseErrorKind; use std::iter::FromIterator; use style_traits::{ParseError, StyleParseErrorKind}; -/// A specified timing function. -pub type TimingFunction = GenericTimingFunction; +/// An entry for linear easing function. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub struct LinearStop { + /// Output of the function at the given point. + pub output: Number, + /// Playback progress at which this output is given. + #[css(skip_if = "Option::is_none")] + pub input: Option, +} -type LinearStop = GenericLinearStop; +/// A list of specified linear stops. +#[derive(Clone, Default, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[css(comma)] +pub struct LinearStops { + #[css(iterable)] + entries: crate::OwnedSlice, +} + +impl LinearStops { + fn new(list: crate::OwnedSlice) -> Self { + LinearStops { entries: list } + } +} + +/// A specified timing function. +pub type TimingFunction = GenericTimingFunction; #[cfg(feature = "gecko")] fn linear_timing_function_enabled() -> bool { @@ -115,7 +135,7 @@ impl TimingFunction { return Err(input.new_custom_error(StyleParseErrorKind::ExperimentalProperty)); } if input.is_exhausted() { - return Ok(GenericTimingFunction::LinearFunction(crate::OwnedSlice::default())) + return Ok(GenericTimingFunction::LinearFunction(LinearStops::default())); } let mut result = vec![]; loop { @@ -145,7 +165,18 @@ impl TimingFunction { } } - Ok(GenericTimingFunction::LinearFunction(crate::OwnedSlice::from(result))) + Ok(GenericTimingFunction::LinearFunction(LinearStops::new( + crate::OwnedSlice::from(result), + ))) + } +} + +impl LinearStop { + /// Convert this type to entries that can be used to build PiecewiseLinearFunction. + pub fn to_piecewise_linear_build_parameters( + x: &LinearStop, + ) -> PiecewiseLinearFunctionBuildParameters { + (x.output.get(), x.input.map(|x| x.get())) } } @@ -169,15 +200,40 @@ impl TimingFunction { }, GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword), GenericTimingFunction::LinearFunction(steps) => { - let iter = steps.iter().map(|e| ComputedLinearStop { - output: e.output.get(), - input: e - .input - .into_rust() - .map(|x| ComputedPercentage(x.get())) - .into(), - }); - GenericTimingFunction::LinearFunction(crate::OwnedSlice::from_iter(iter)) + GenericTimingFunction::LinearFunction(PiecewiseLinearFunction::from_iter( + steps + .entries + .iter() + .map(|e| LinearStop::to_piecewise_linear_build_parameters(e)), + )) + }, + } + } +} + +impl ToComputedValue for TimingFunction { + type ComputedValue = ComputedTimingFunction; + fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { + self.to_computed_value_without_context() + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match &computed { + ComputedTimingFunction::Steps(steps, pos) => Self::Steps(Integer::new(*steps), *pos), + ComputedTimingFunction::CubicBezier { x1, y1, x2, y2 } => Self::CubicBezier { + x1: Number::new(*x1), + y1: Number::new(*y1), + x2: Number::new(*x2), + y2: Number::new(*y2), + }, + ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword), + ComputedTimingFunction::LinearFunction(function) => { + GenericTimingFunction::LinearFunction(LinearStops { + entries: crate::OwnedSlice::from_iter(function.iter().map(|e| LinearStop { + output: Number::new(e.y), + input: Some(Percentage::new(e.x)).into(), + })), + }) }, } }