Skip to content

Commit 2d57b38

Browse files
authored
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
1 parent 4afbc16 commit 2d57b38

File tree

1 file changed

+28
-19
lines changed

1 file changed

+28
-19
lines changed

src/internal/observable/dom/animationFrames.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Observable } from '../../Observable';
2-
import { Subscription } from '../../Subscription';
32
import { TimestampProvider } from '../../types';
43
import { performanceTimestampProvider } from '../../scheduler/performanceTimestampProvider';
54
import { animationFrameProvider } from '../../scheduler/animationFrameProvider';
@@ -82,37 +81,47 @@ export function animationFrames(timestampProvider?: TimestampProvider) {
8281
* @param timestampProvider The timestamp provider to use to create the observable
8382
*/
8483
function animationFramesFactory(timestampProvider?: TimestampProvider) {
85-
const { schedule } = animationFrameProvider;
8684
return new Observable<{ timestamp: number; elapsed: number }>((subscriber) => {
87-
const subscription = new Subscription();
8885
// If no timestamp provider is specified, use performance.now() - as it
8986
// will return timestamps 'compatible' with those passed to the run
9087
// callback and won't be affected by NTP adjustments, etc.
9188
const provider = timestampProvider || performanceTimestampProvider;
89+
9290
// Capture the start time upon subscription, as the run callback can remain
9391
// queued for a considerable period of time and the elapsed time should
9492
// represent the time elapsed since subscription - not the time since the
9593
// first rendered animation frame.
9694
const start = provider.now();
97-
const run = (timestamp: DOMHighResTimeStamp | number) => {
98-
// Use the provider's timestamp to calculate the elapsed time. Note that
99-
// this means - if the caller hasn't passed a provider - that
100-
// performance.now() will be used instead of the timestamp that was
101-
// passed to the run callback. The reason for this is that the timestamp
102-
// passed to the callback can be earlier than the start time, as it
103-
// represents the time at which the browser decided it would render any
104-
// queued frames - and that time can be earlier the captured start time.
105-
const now = provider.now();
106-
subscriber.next({
107-
timestamp: timestampProvider ? now : timestamp,
108-
elapsed: now - start,
109-
});
95+
96+
let id = 0;
97+
const run = () => {
11098
if (!subscriber.closed) {
111-
subscription.add(schedule(run));
99+
id = animationFrameProvider.requestAnimationFrame((timestamp: DOMHighResTimeStamp | number) => {
100+
id = 0;
101+
// Use the provider's timestamp to calculate the elapsed time. Note that
102+
// this means - if the caller hasn't passed a provider - that
103+
// performance.now() will be used instead of the timestamp that was
104+
// passed to the run callback. The reason for this is that the timestamp
105+
// passed to the callback can be earlier than the start time, as it
106+
// represents the time at which the browser decided it would render any
107+
// queued frames - and that time can be earlier the captured start time.
108+
const now = provider.now();
109+
subscriber.next({
110+
timestamp: timestampProvider ? now : timestamp,
111+
elapsed: now - start,
112+
});
113+
run();
114+
});
115+
}
116+
};
117+
118+
run();
119+
120+
return () => {
121+
if (id) {
122+
animationFrameProvider.cancelAnimationFrame(id);
112123
}
113124
};
114-
subscription.add(schedule(run));
115-
return subscription;
116125
});
117126
}
118127

0 commit comments

Comments
 (0)