RN: Feature Flag to Disable InteractionManager in Batchinator (#47690)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/47690

Creates a feature flag to evalute the impact of disabling `InteractionManager` in `Batchinator` and synchronously invoking callbacks after the timeout.

This also deletes the `dispose` arguments in `Batchinator` that were unused.

Changelog:
[Internal]

Reviewed By: bvanderhoof

Differential Revision: D66139643

fbshipit-source-id: d17bab0cd25c0c69779686cb435c063f707255e4
This commit is contained in:
Tim Yung 2024-11-18 23:12:15 -08:00 committed by Facebook GitHub Bot
parent fbe4c0ed34
commit 29a0d7c3b2
5 changed files with 55 additions and 35 deletions

View File

@ -468,6 +468,15 @@ const definitions: FeatureFlagDefinitions = {
purpose: 'experimentation',
},
},
disableInteractionManagerInBatchinator: {
defaultValue: false,
metadata: {
dateAdded: '2024-11-18',
description:
'Skips InteractionManager in `Batchinator` and invokes callbacks synchronously.',
purpose: 'experimentation',
},
},
enableAccessToHostTreeInFabric: {
defaultValue: false,
metadata: {

View File

@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8334fdc6d34d4a090eb8be73f92ccd0f>>
* @generated SignedSource<<d7715d63338e96f5842026458a3b69c8>>
* @flow strict
*/
@ -31,6 +31,7 @@ export type ReactNativeFeatureFlagsJsOnly = {
animatedShouldDebounceQueueFlush: Getter<boolean>,
animatedShouldUseSingleOp: Getter<boolean>,
disableInteractionManager: Getter<boolean>,
disableInteractionManagerInBatchinator: Getter<boolean>,
enableAccessToHostTreeInFabric: Getter<boolean>,
enableAnimatedAllowlist: Getter<boolean>,
enableAnimatedClearImmediateFix: Getter<boolean>,
@ -119,6 +120,11 @@ export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGe
*/
export const disableInteractionManager: Getter<boolean> = createJavaScriptFlagGetter('disableInteractionManager', false);
/**
* Skips InteractionManager in `Batchinator` and invokes callbacks synchronously.
*/
export const disableInteractionManagerInBatchinator: Getter<boolean> = createJavaScriptFlagGetter('disableInteractionManagerInBatchinator', false);
/**
* Enables access to the host tree in Fabric using DOM-compatible APIs.
*/

View File

@ -4,13 +4,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
* @format
*/
'use strict';
const {InteractionManager} = require('react-native');
import {InteractionManager} from 'react-native';
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
/**
* A simple class for batching up invocations of a low-pri callback. A timeout is set to run the
@ -38,37 +37,47 @@ class Batchinator {
_callback: () => void;
_delay: number;
_taskHandle: ?{cancel: () => void, ...};
constructor(callback: () => void, delayMS: number) {
this._delay = delayMS;
constructor(callback: () => void, delay: number) {
this._delay = delay;
this._callback = callback;
}
/*
* Cleanup any pending tasks.
*
* By default, if there is a pending task the callback is run immediately. Set the option abort to
* true to not call the callback if it was pending.
*/
dispose(options: {abort: boolean, ...} = {abort: false}) {
dispose(): void {
if (this._taskHandle) {
this._taskHandle.cancel();
if (!options.abort) {
this._callback();
}
this._taskHandle = null;
}
}
schedule() {
schedule(): void {
if (this._taskHandle) {
return;
}
const timeoutHandle = setTimeout(() => {
this._taskHandle = InteractionManager.runAfterInteractions(() => {
// Note that we clear the handle before invoking the callback so that if the callback calls
// schedule again, it will actually schedule another task.
this._taskHandle = null;
this._callback();
});
}, this._delay);
const invokeCallback = () => {
// Note that we clear the handle before invoking the callback so that if the callback calls
// schedule again, it will actually schedule another task.
this._taskHandle = null;
this._callback();
};
const timeoutHandle = setTimeout(
// NOTE: When shipping this, delete `Batchinator` instead of only these
// lines of code. Without `InteractionManager`, it's just a `setTimeout`.
ReactNativeFeatureFlags.disableInteractionManagerInBatchinator()
? invokeCallback
: () => {
this._taskHandle =
InteractionManager.runAfterInteractions(invokeCallback);
},
this._delay,
);
this._taskHandle = {cancel: () => clearTimeout(timeoutHandle)};
}
}

View File

@ -10,10 +10,6 @@
'use strict';
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
describe('Batchinator', () => {
const Batchinator = require('../Batchinator');
@ -22,7 +18,7 @@ describe('Batchinator', () => {
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
jest.runAllTimers();
expectToBeCalledOnce(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
it('batches up tasks', () => {
@ -32,20 +28,20 @@ describe('Batchinator', () => {
batcher.schedule();
batcher.schedule();
batcher.schedule();
expect(callback).not.toBeCalled();
expect(callback).not.toHaveBeenCalled();
jest.runAllTimers();
expectToBeCalledOnce(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
it('flushes on dispose', () => {
it('does nothing after dispose', () => {
const callback = jest.fn();
const batcher = new Batchinator(callback, 10000);
batcher.schedule();
batcher.schedule();
batcher.dispose();
expectToBeCalledOnce(callback);
expect(callback).not.toHaveBeenCalled();
jest.runAllTimers();
expectToBeCalledOnce(callback);
expect(callback).not.toHaveBeenCalled();
});
it('should call tasks scheduled by the callback', () => {
@ -69,10 +65,10 @@ describe('Batchinator', () => {
batcher.schedule();
batcher.schedule();
jest.runAllTimers();
expectToBeCalledOnce(callback);
expect(callback).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expectToBeCalledOnce(callback);
expect(callback).toHaveBeenCalledTimes(1);
batcher.dispose();
expectToBeCalledOnce(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
});

View File

@ -687,7 +687,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
if (this._isNestedWithSameOrientation()) {
this.context.unregisterAsNestedChild({ref: this});
}
this._updateCellsToRenderBatcher.dispose({abort: true});
this._updateCellsToRenderBatcher.dispose();
this._viewabilityTuples.forEach(tuple => {
tuple.viewabilityHelper.dispose();
});
@ -1762,7 +1762,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
this._hiPriInProgress = true;
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
// as possible.
this._updateCellsToRenderBatcher.dispose({abort: true});
this._updateCellsToRenderBatcher.dispose();
this._updateCellsToRender();
return;
} else {