Skip to content

Commit

Permalink
fixup! refactor(core): add batching for defer blocks with on idle c…
Browse files Browse the repository at this point in the history
…onditions
  • Loading branch information
AndrewKushnir committed Sep 25, 2023
1 parent f3051c2 commit f5d5147
Show file tree
Hide file tree
Showing 2 changed files with 437 additions and 104 deletions.
50 changes: 37 additions & 13 deletions packages/core/src/render3/instructions/defer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {InjectionToken, Injector, ɵɵdefineInjectable} from '../../di';
import {inject} from '../../di/injector_compatibility';
import {findMatchingDehydratedView} from '../../hydration/views';
import {populateDehydratedViewsInContainer} from '../../linker/view_container_ref';
import {assertDefined, assertElement, assertEqual, throwError} from '../../util/assert';
Expand Down Expand Up @@ -470,19 +471,24 @@ function onIdle(callback: VoidFunction, lView: LView, withLViewCleanup: boolean)
const injector = lView[INJECTOR]!;
const scheduler = injector.get(OnIdleScheduler);
const cleanupFn = () => scheduler.remove(callback);
const _callback = withLViewCleanup ? wrapWithLViewCleanup(callback, lView, cleanupFn) : callback;
scheduler.add(_callback);
const wrappedCallback =
withLViewCleanup ? wrapWithLViewCleanup(callback, lView, cleanupFn) : callback;
scheduler.add(wrappedCallback);
return cleanupFn;
}

/**
* Wraps a given callback into a logic that registers a cleanup function
* in the LView cleanup slot, to be invoked when an LView is destroyed.
*/
function wrapWithLViewCleanup(
callback: VoidFunction, lView: LView, cleanup: VoidFunction): VoidFunction {
const _callback = () => {
const wrappedCallback = () => {
callback();
removeLViewOnDestroy(lView, cleanup);
};
storeLViewOnDestroy(lView, cleanup);
return _callback;
return wrappedCallback;
}

/**
Expand Down Expand Up @@ -957,25 +963,39 @@ class DeferBlockCleanupManager {
}

/**
* Shims for the `requestIdleCallback` and `cancelIdleCallback` functions for environments
* where those functions are not available (e.g. Node.js).
* Use shims for the `requestIdleCallback` and `cancelIdleCallback` functions for
* environments where those functions are not available (e.g. Node.js and Safari).
*
* Note: we wrap the `requestIdleCallback` call into a function, so that it can be
* overridden/mocked in test environment and picked up by the runtime code.
*/
const _requestIdleCallback =
const _requestIdleCallback = () =>
typeof requestIdleCallback !== 'undefined' ? requestIdleCallback : setTimeout;
const _cancelIdleCallback =
const _cancelIdleCallback = () =>
typeof requestIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout;

/**
* Helper service to schedule `requestIdleCallback`s for batches of defer blocks,
* to avoid scheduling `requestIdleCallback` for each defer block (e.g. if
* a number of defer blocks are created inside a for loop).
* to avoid calling `requestIdleCallback` for each defer block (e.g. if
* defer blocks are defined inside a for loop).
*/
class OnIdleScheduler {
// Indicates whether current callbacks are being invoked.
executingCallbacks = false;

// Currently scheduled idle callback id.
idleId: number|null = null;

// Set of callbacks to be invoked next.
current = new Set<VoidFunction>();

// Set of callbacks collected while invoking current set of callbacks.
// Those callbacks are scheduled for the next idle period.
deferred = new Set<VoidFunction>();

requestIdleCallback = _requestIdleCallback().bind(globalThis);
cancelIdleCallback = _cancelIdleCallback().bind(globalThis);

add(callback: VoidFunction) {
const target = this.executingCallbacks ? this.deferred : this.current;
target.add(callback);
Expand All @@ -991,8 +1011,9 @@ class OnIdleScheduler {

private scheduleIdleCallback() {
const callback = () => {
_cancelIdleCallback(this.idleId!);
this.cancelIdleCallback(this.idleId!);
this.idleId = null;

this.executingCallbacks = true;

for (const callback of this.current) {
Expand All @@ -1002,6 +1023,9 @@ class OnIdleScheduler {

this.executingCallbacks = false;

// If there are any callbacks added during an invocation
// of the current ones - make them "current" and schedule
// a new idle callback.
if (this.deferred.size > 0) {
for (const callback of this.deferred) {
this.current.add(callback);
Expand All @@ -1010,12 +1034,12 @@ class OnIdleScheduler {
this.scheduleIdleCallback();
}
};
this.idleId = _requestIdleCallback(callback) as number;
this.idleId = this.requestIdleCallback(callback) as number;
}

ngOnDestroy() {
if (this.idleId !== null) {
_cancelIdleCallback(this.idleId);
this.cancelIdleCallback(this.idleId);
this.idleId = null;
}
this.current.clear();
Expand Down
Loading

0 comments on commit f5d5147

Please sign in to comment.