From 2d57b38ec9f7ada838ee130ab75cd795b156c182 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Sun, 25 Sep 2022 13:19:26 -0500 Subject: [PATCH] perf(animationFrames): uses fewer Subscription instances (#7060) Leverages a single id variable instead of creating a new child Subscription for each scheduled animation frame. This is important because animation frames are scheduled very rapidly and this helps prevent CPU and GC thrashing. related #7018 --- .../observable/dom/animationFrames.ts | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/internal/observable/dom/animationFrames.ts b/src/internal/observable/dom/animationFrames.ts index 707174f9c0..38b338bfc4 100644 --- a/src/internal/observable/dom/animationFrames.ts +++ b/src/internal/observable/dom/animationFrames.ts @@ -1,5 +1,4 @@ import { Observable } from '../../Observable'; -import { Subscription } from '../../Subscription'; import { TimestampProvider } from '../../types'; import { performanceTimestampProvider } from '../../scheduler/performanceTimestampProvider'; import { animationFrameProvider } from '../../scheduler/animationFrameProvider'; @@ -82,37 +81,47 @@ export function animationFrames(timestampProvider?: TimestampProvider) { * @param timestampProvider The timestamp provider to use to create the observable */ function animationFramesFactory(timestampProvider?: TimestampProvider) { - const { schedule } = animationFrameProvider; return new Observable<{ timestamp: number; elapsed: number }>((subscriber) => { - const subscription = new Subscription(); // If no timestamp provider is specified, use performance.now() - as it // will return timestamps 'compatible' with those passed to the run // callback and won't be affected by NTP adjustments, etc. const provider = timestampProvider || performanceTimestampProvider; + // Capture the start time upon subscription, as the run callback can remain // queued for a considerable period of time and the elapsed time should // represent the time elapsed since subscription - not the time since the // first rendered animation frame. const start = provider.now(); - const run = (timestamp: DOMHighResTimeStamp | number) => { - // Use the provider's timestamp to calculate the elapsed time. Note that - // this means - if the caller hasn't passed a provider - that - // performance.now() will be used instead of the timestamp that was - // passed to the run callback. The reason for this is that the timestamp - // passed to the callback can be earlier than the start time, as it - // represents the time at which the browser decided it would render any - // queued frames - and that time can be earlier the captured start time. - const now = provider.now(); - subscriber.next({ - timestamp: timestampProvider ? now : timestamp, - elapsed: now - start, - }); + + let id = 0; + const run = () => { if (!subscriber.closed) { - subscription.add(schedule(run)); + id = animationFrameProvider.requestAnimationFrame((timestamp: DOMHighResTimeStamp | number) => { + id = 0; + // Use the provider's timestamp to calculate the elapsed time. Note that + // this means - if the caller hasn't passed a provider - that + // performance.now() will be used instead of the timestamp that was + // passed to the run callback. The reason for this is that the timestamp + // passed to the callback can be earlier than the start time, as it + // represents the time at which the browser decided it would render any + // queued frames - and that time can be earlier the captured start time. + const now = provider.now(); + subscriber.next({ + timestamp: timestampProvider ? now : timestamp, + elapsed: now - start, + }); + run(); + }); + } + }; + + run(); + + return () => { + if (id) { + animationFrameProvider.cancelAnimationFrame(id); } }; - subscription.add(schedule(run)); - return subscription; }); }