Skip to content

Commit

Permalink
Performance metrics (#3015)
Browse files Browse the repository at this point in the history
* Adding stats

* Latest

* Fixing tests

* Latest

* Fixing type name

* Adding animation analytics

* Add motion endpoint and move payload for projection

* Protecting finally
  • Loading branch information
mattgperry authored Feb 6, 2025
1 parent 88f36d2 commit f9a01b6
Show file tree
Hide file tree
Showing 32 changed files with 520 additions and 94 deletions.
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-neighbours.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const duration = 10

Expand Down Expand Up @@ -98,7 +100,6 @@

frame.postRender(() => {
frame.postRender(() => {
console.log(window.ProjectionFrames)
checkFrame(topEl, 2, {
totalNodes: 5,
resolvedTargetDeltas: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")
const parentProjection = createNode(
Expand Down Expand Up @@ -100,7 +102,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 1, {
totalNodes: 4,
resolvedTargetDeltas: 3,
Expand Down
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-parent-child.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")
const parentProjection = createNode(
Expand All @@ -84,7 +86,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 1, {
totalNodes: 3,
resolvedTargetDeltas: 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")
const parentProjection = createNode(
Expand Down Expand Up @@ -96,7 +98,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 2, {
totalNodes: 4,
resolvedTargetDeltas: 3,
Expand Down
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-shared-deep.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
<script type="module">
const { createNode } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()
const duration = 10

const a = document.getElementById("a")
Expand Down Expand Up @@ -127,7 +129,6 @@
*/
requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(a, 1, {
totalNodes: 7,
resolvedTargetDeltas: 3,
Expand Down
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-shared-single.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
<script type="module">
const { createNode } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const a = document.getElementById("a")
const aProjection = createNode(
Expand All @@ -71,7 +73,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(a, 1, {
totalNodes: 3,
resolvedTargetDeltas: 1, // We only need to resolve a target for the lead node
Expand Down
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-single.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")

Expand All @@ -76,7 +78,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 1, {
totalNodes: 2,
resolvedTargetDeltas: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")
const parentProjection = createNode(
Expand Down Expand Up @@ -95,7 +97,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 1, {
totalNodes: 4,
resolvedTargetDeltas: 3,
Expand Down
5 changes: 3 additions & 2 deletions dev/html/public/projection/perf-static-parent-child.skip.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
<script type="module">
const { createNode, relativeEase } = window.Animate
const { matchViewportBox, checkFrame } = window.Assert
const { frame } = window.Projection
const { frame, recordStats } = window.Projection

recordStats()

const parent = document.getElementById("parent")
const parentProjection = createNode(
Expand All @@ -84,7 +86,6 @@

requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log(window.ProjectionFrames)
checkFrame(parent, 2, {
totalNodes: 3,
resolvedTargetDeltas: 1,
Expand Down
28 changes: 17 additions & 11 deletions dev/html/src/imports/script-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@ function showError(element, msg) {

window.showError = showError

window.ProjectionFrames = []
window.MotionDebug = {
record: (action) => {
if (action.type === "projectionFrame") {
window.ProjectionFrames.push({ ...action })
}
},
}

window.Assert = {
matchViewportBox: (element, expected, threshold = 0.01) => {
const bbox = element.getBoundingClientRect()
Expand Down Expand Up @@ -123,9 +114,24 @@ window.Assert = {
}
},
checkFrame(element, frameIndex, expected) {
const frame = window.ProjectionFrames[frameIndex]
const { statsBuffer } = window.Projection

if (!statsBuffer.value) {
showError(element, "No stats buffer found")
return
}

const { nodes, calculatedTargetDeltas, calculatedProjections } =
statsBuffer.value.layoutProjection

const frame = {
totalNodes: nodes[frameIndex],
resolvedTargetDeltas: calculatedTargetDeltas[frameIndex],
recalculatedProjection: calculatedProjections[frameIndex],
}

if (!frame) showError(element, "No frame found for given index")
if (!nodes[frameIndex])
showError(element, "No frame found for given index")

if (frame.totalNodes !== expected.totalNodes) {
showError(
Expand Down
1 change: 1 addition & 0 deletions packages/framer-motion/debug/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This directory is a fallback for `exports["./debug"]` in the root `framer-motion` `package.json`.
6 changes: 6 additions & 0 deletions packages/framer-motion/debug/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"types": "../dist/debug.d.ts",
"main": "../dist/cjs/debug.js",
"module": "../dist/es/debug.mjs"
}
6 changes: 6 additions & 0 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"import": "./dist/es/index.mjs",
"default": "./dist/cjs/index.js"
},
"./debug": {
"types": "./dist/debug.d.ts",
"require": "./dist/cjs/debug.js",
"import": "./dist/es/debug.mjs",
"default": "./dist/cjs/debug.js"
},
"./dom/mini": {
"types": "./dist/dom-mini.d.ts",
"require": "./dist/cjs/dom-mini.js",
Expand Down
6 changes: 5 additions & 1 deletion packages/framer-motion/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,14 @@ const cjs = Object.assign({}, config, {
/**
* Bundle seperately so bundles don't share common modules
*/
const cjsDebug = Object.assign({}, cjs, { input : "lib/debug.js" })
const cjsDom = Object.assign({}, cjs, { input : "lib/dom.js" })
const cjsMini = Object.assign({}, cjs, { input : "lib/mini.js" })
const cjsDomMini = Object.assign({}, cjs, { input : "lib/dom-mini.js" })
const cjsM = Object.assign({}, cjs, { input : "lib/m.js" })

export const es = Object.assign({}, config, {
input: ["lib/index.js", "lib/mini.js", "lib/dom.js", "lib/dom-mini.js", "lib/client.js", "lib/m.js","lib/projection.js"],
input: ["lib/index.js", "lib/mini.js", "lib/debug.js", "lib/dom.js", "lib/dom-mini.js", "lib/client.js", "lib/m.js","lib/projection.js"],
output: {
entryFileNames: "[name].mjs",
format: "es",
Expand Down Expand Up @@ -185,6 +186,7 @@ function createTypes(input, file) {


const miniTypes = createTypes("types/mini.d.ts", "dist/mini.d.ts")
const debugTypes = createTypes("types/debug.d.ts", "dist/debug.d.ts")
const animateTypes = createTypes("types/dom.d.ts", "dist/dom.d.ts")
const animateMiniTypes = createTypes("types/dom-mini.d.ts", "dist/dom-mini.d.ts")
const mTypes = createTypes("types/m.d.ts", "dist/m.d.ts")
Expand All @@ -198,12 +200,14 @@ export default [
umdDomProd,
umdDomMiniProd,
cjs,
cjsDebug,
cjsMini,
cjsDom,
cjsDomMini,
cjsM,
es,
types,
debugTypes,
mTypes,
miniTypes,
animateTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
KeyframeResolver as DefaultKeyframeResolver,
ResolvedKeyframes,
} from "../../render/utils/KeyframesResolver"
import { activeAnimations } from "../../stats/animation-count"
import { clamp } from "../../utils/clamp"
import { mix } from "../../utils/mix"
import { pipe } from "../../utils/pipe"
Expand Down Expand Up @@ -226,6 +227,8 @@ export class MainThreadAnimation<
onPostResolved() {
const { autoplay = true } = this.options

activeAnimations.mainThread++

this.play()

if (this.pendingPlayState === "paused" || !autoplay) {
Expand Down Expand Up @@ -537,6 +540,7 @@ export class MainThreadAnimation<
this.updateFinishedPromise()
this.startTime = this.cancelTime = null
this.resolver.cancel()
activeAnimations.mainThread--
}

private stopDriver() {
Expand Down
16 changes: 15 additions & 1 deletion packages/framer-motion/src/animation/animators/waapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { mapEasingToNativeEasing } from "motion-dom"
import { activeAnimations } from "../../../stats/animation-count"
import { statsBuffer } from "../../../stats/buffer"
import { NativeAnimationOptions } from "./types"

export function startWaapiAnimation(
Expand All @@ -24,12 +26,24 @@ export function startWaapiAnimation(
*/
if (Array.isArray(easing)) keyframeOptions.easing = easing

return element.animate(keyframeOptions, {
if (statsBuffer.value) {
activeAnimations.waapi++
}

const animation = element.animate(keyframeOptions, {
delay,
duration,
easing: !Array.isArray(easing) ? easing : "linear",
fill: "both",
iterations: repeat + 1,
direction: repeatType === "reverse" ? "alternate" : "normal",
})

if (statsBuffer.value) {
animation.finished.finally(() => {
activeAnimations.waapi--
})
}

return animation
}
2 changes: 2 additions & 0 deletions packages/framer-motion/src/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { recordStats } from "./stats"
export type * from "./stats/types"
25 changes: 11 additions & 14 deletions packages/framer-motion/src/frameloop/batcher.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { MotionGlobalConfig } from "../utils/GlobalConfig"
import { stepsOrder } from "./order"
import { createRenderStep } from "./render-step"
import { Batcher, Process, StepId, Steps, FrameData } from "./types"

export const stepsOrder: StepId[] = [
"read", // Read
"resolveKeyframes", // Write/Read/Write/Read
"update", // Compute
"preRender", // Compute
"render", // Write
"postRender", // Compute
]
import { Batcher, FrameData, Process, Steps } from "./types"

const maxElapsed = 40

Expand All @@ -29,7 +21,10 @@ export function createRenderBatcher(
const flagRunNextFrame = () => (runNextFrame = true)

const steps = stepsOrder.reduce((acc, key) => {
acc[key] = createRenderStep(flagRunNextFrame)
acc[key] = createRenderStep(
flagRunNextFrame,
allowKeepAlive ? key : undefined
)
return acc
}, {} as Steps)

Expand All @@ -42,9 +37,11 @@ export function createRenderBatcher(
: performance.now()
runNextFrame = false

state.delta = useDefaultElapsed
? 1000 / 60
: Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1)
if (!MotionGlobalConfig.useManualTiming) {
state.delta = useDefaultElapsed
? 1000 / 60
: Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1)
}

state.timestamp = timestamp
state.isProcessing = true
Expand Down
4 changes: 2 additions & 2 deletions packages/framer-motion/src/frameloop/index-legacy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { frame, cancelFrame } from "."
import { stepsOrder } from "./batcher"
import { cancelFrame, frame } from "."
import { stepsOrder } from "./order"
import { Process } from "./types"

/**
Expand Down
Loading

0 comments on commit f9a01b6

Please sign in to comment.