<Incremental> for incremental rendering
Summary:Everything wrapped in `<Incremental>` is rendered sequentially via `InteractionManager`. The `onDone` callback is called when all descendent incremental components have finished rendering, used by `<IncrementalPresenter>` to make the story visible all at once instead of the parts popping in randomly. This includes an example that demonstrates streaming rendering and the use of `<IncrementalPresenter>`. Pressing down pauses rendering and you can see the `TouchableOpacity` animation runs smoothly. Video: https://youtu.be/4UNf4-8orQ4 Ideally this will be baked into React Core at some point, but according to jordwalke that's going to require a major refactoring and take a long time, so going with this for now. Reviewed By: ericvicenti Differential Revision: D2506522 fb-gh-sync-id: 5969bf248de10d38b0ac22f34d7d49bf1b3ac4b6 shipit-source-id: 5969bf248de10d38b0ac22f34d7d49bf1b3ac4b6
This commit is contained in:
parent
dcfa620628
commit
f21da3aa31
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Incremental
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const InteractionManager = require('InteractionManager');
|
||||
const React = require('React');
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
/**
|
||||
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
|
||||
* not be reliably announced. The whole thing might be deleted, who knows? Use
|
||||
* at your own risk.
|
||||
*
|
||||
* React Native helps make apps smooth by doing all the heavy lifting off the
|
||||
* main thread, in JavaScript. That works great a lot of the time, except that
|
||||
* heavy operations like rendering may block the JS thread from responding
|
||||
* quickly to events like taps, making the app feel sluggish.
|
||||
*
|
||||
* `<Incremental>` solves this by slicing up rendering into chunks that are
|
||||
* spread across multiple event loops. Expensive components can be sliced up
|
||||
* recursively by wrapping pieces of them and their decendents in
|
||||
* `<Incremental>` components. `<IncrementalGroup>` can be used to make sure
|
||||
* everything in the group is rendered recursively before calling `onDone` and
|
||||
* moving on to another sibling group (e.g. render one row at a time, even if
|
||||
* rendering the top level row component produces more `<Incremental>` chunks).
|
||||
* `<IncrementalPresenter>` is a type of `<IncrementalGroup>` that keeps it's
|
||||
* children invisible and out of the layout tree until all rendering completes
|
||||
* recursively. This means the group will be presented to the user as one unit,
|
||||
* rather than pieces popping in sequentially.
|
||||
*
|
||||
* `<Incremental>` only affects initial render - `setState` and other render
|
||||
* updates are unaffected.
|
||||
*
|
||||
* The chunks are rendered sequentially using the `InteractionManager` queue,
|
||||
* which means that rendering will pause if it's interrupted by an interaction,
|
||||
* such as an animation or gesture.
|
||||
*
|
||||
* Note there is some overhead, so you don't want to slice things up too much.
|
||||
* A target of 100-200ms of total work per event loop on old/slow devices might
|
||||
* be a reasonable place to start.
|
||||
*
|
||||
* Below is an example that will incrementally render all the parts of `Row` one
|
||||
* first, then present them together, then repeat the process for `Row` two, and
|
||||
* so on:
|
||||
*
|
||||
* render: function() {
|
||||
* return (
|
||||
* <ScrollView>
|
||||
* {Array(10).fill().map((rowIdx) => (
|
||||
* <IncrementalPresenter key={rowIdx}>
|
||||
* <Row>
|
||||
* {Array(20).fill().map((widgetIdx) => (
|
||||
* <Incremental key={widgetIdx}>
|
||||
* <SlowWidget />
|
||||
* </Incremental>
|
||||
* ))}
|
||||
* </Row>
|
||||
* </IncrementalPresenter>
|
||||
* ))}
|
||||
* </ScrollView>
|
||||
* );
|
||||
* };
|
||||
*
|
||||
* If SlowWidget takes 30ms to render, then without `Incremental`, this would
|
||||
* block the JS thread for at least `10 * 20 * 30ms = 6000ms`, but with
|
||||
* `Incremental` it will probably not block for more than 50-100ms at a time,
|
||||
* allowing user interactions to take place which might even unmount this
|
||||
* component, saving us from ever doing the remaining rendering work.
|
||||
*/
|
||||
export type Props = {
|
||||
/**
|
||||
* Called when all the decendents have finished rendering and mounting
|
||||
* recursively.
|
||||
*/
|
||||
onDone?: () => void;
|
||||
/**
|
||||
* Tags instances and associated tasks for easier debugging.
|
||||
*/
|
||||
name: string;
|
||||
children: any;
|
||||
disabled: boolean;
|
||||
};
|
||||
class Incremental extends React.Component {
|
||||
props: Props;
|
||||
state: {
|
||||
doIncrementalRender: boolean;
|
||||
};
|
||||
context: Context;
|
||||
_incrementId: number;
|
||||
_mounted: boolean;
|
||||
|
||||
constructor(props: Props, context: Context) {
|
||||
super(props, context);
|
||||
this._mounted = false;
|
||||
this.state = {
|
||||
doIncrementalRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
var ctx = this.context.incrementalGroup || {};
|
||||
return ctx.groupId + ':' + this._incrementId + '-' + this.props.name;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
var ctx = this.context.incrementalGroup;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
this._incrementId = ++(ctx.incrementalCount);
|
||||
InteractionManager.runAfterInteractions({
|
||||
name: 'Incremental:' + this.getName(),
|
||||
gen: () => new Promise(resolve => {
|
||||
if (!this._mounted) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
DEBUG && console.log('set doIncrementalRender for ' + this.getName());
|
||||
this.setState({doIncrementalRender: true}, resolve);
|
||||
}),
|
||||
}).then(() => {
|
||||
this._mounted && this.props.onDone && this.props.onDone();
|
||||
});
|
||||
}
|
||||
|
||||
render(): ?ReactElement {
|
||||
if (this.props.disabled || !this.context.incrementalGroupEnabled || this.state.doIncrementalRender) {
|
||||
DEBUG && console.log('render ' + this.getName());
|
||||
return this.props.children;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._mounted = true;
|
||||
if (!this.context.incrementalGroup) {
|
||||
this.props.onDone && this.props.onDone();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
}
|
||||
}
|
||||
Incremental.defaultProps = {
|
||||
name: '',
|
||||
};
|
||||
Incremental.contextTypes = {
|
||||
incrementalGroup: React.PropTypes.object,
|
||||
incrementalGroupEnabled: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
export type Context = {
|
||||
incrementalGroupEnabled: boolean;
|
||||
incrementalGroup: ?{
|
||||
groupId: string;
|
||||
incrementalCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Incremental;
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule IncrementalExample
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('react-native');
|
||||
const {
|
||||
InteractionManager,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
const Incremental = require('Incremental');
|
||||
const IncrementalGroup = require('IncrementalGroup');
|
||||
const IncrementalPresenter = require('IncrementalPresenter');
|
||||
|
||||
const JSEventLoopWatchdog = require('JSEventLoopWatchdog');
|
||||
const StaticContainer = require('StaticContainer.react');
|
||||
|
||||
const performanceNow = require('performanceNow');
|
||||
|
||||
InteractionManager.setDeadline(1000);
|
||||
JSEventLoopWatchdog.install({thresholdMS: 200});
|
||||
|
||||
const NUM_ITEMS = 20;
|
||||
|
||||
let totalWidgets = 0;
|
||||
|
||||
class SlowWidget extends React.Component {
|
||||
state: {ctorTimestamp: number, timeToMount: number};
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
ctorTimestamp: performanceNow(),
|
||||
timeToMount: 0,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
this.state.timeToMount === 0 && burnCPU(20);
|
||||
return (
|
||||
<View style={styles.widgetContainer}>
|
||||
<Text style={styles.widgetText}>
|
||||
{`${this.state.timeToMount || '?'} ms`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
const timeToMount = performanceNow() - this.state.ctorTimestamp;
|
||||
this.setState({timeToMount});
|
||||
totalWidgets++;
|
||||
}
|
||||
}
|
||||
|
||||
let imHandle;
|
||||
function startInteraction() {
|
||||
imHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
function stopInteraction() {
|
||||
InteractionManager.clearInteractionHandle(imHandle);
|
||||
}
|
||||
|
||||
function Block(props: Object) {
|
||||
const IncrementalContainer = props.stream ? IncrementalGroup : IncrementalPresenter;
|
||||
return (
|
||||
<IncrementalContainer name={'b_' + props.idx}>
|
||||
<TouchableOpacity
|
||||
onPressIn={startInteraction}
|
||||
onPressOut={stopInteraction}>
|
||||
<View style={styles.block}>
|
||||
<Text>
|
||||
{props.idx + ': ' + (props.stream ? 'Streaming' : 'Presented')}
|
||||
</Text>
|
||||
{props.children}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</IncrementalContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const Row = (props: Object) => <View style={styles.row} {...props} />;
|
||||
|
||||
class IncrementalExample extends React.Component {
|
||||
static title = '<Incremental*>';
|
||||
static description = 'Enables incremental rendering of complex components.';
|
||||
start: number;
|
||||
state: {stats: ?Object};
|
||||
constructor(props: mixed, context: mixed) {
|
||||
super(props, context);
|
||||
this.start = performanceNow();
|
||||
this.state = {
|
||||
stats: null,
|
||||
};
|
||||
(this: any)._onDone = this._onDone.bind(this);
|
||||
}
|
||||
_onDone() {
|
||||
const onDoneElapsed = performanceNow() - this.start;
|
||||
setTimeout(() => {
|
||||
const stats = {
|
||||
onDoneElapsed,
|
||||
totalWidgets,
|
||||
...JSEventLoopWatchdog.getStats(),
|
||||
setTimeoutElapsed: performanceNow() - this.start,
|
||||
};
|
||||
stats.avgStall = stats.totalStallTime / stats.stallCount;
|
||||
this.setState({stats});
|
||||
console.log('onDone:', stats);
|
||||
}, 0);
|
||||
}
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<IncrementalGroup
|
||||
disabled={false}
|
||||
name="root"
|
||||
onDone={this._onDone}>
|
||||
<ScrollView style={styles.scrollView}>
|
||||
<Text style={styles.headerText}>
|
||||
Press and hold on a row to pause rendering.
|
||||
</Text>
|
||||
{this.state.stats && <Text>
|
||||
Finished: {JSON.stringify(this.state.stats, null, 2)}
|
||||
</Text>}
|
||||
{Array(8).fill().map((_, blockIdx) => {
|
||||
return (
|
||||
<Block key={blockIdx} idx={blockIdx} stream={blockIdx < 2}>
|
||||
{Array(4).fill().map((_b, rowIdx) => (
|
||||
<Row key={rowIdx}>
|
||||
{Array(14).fill().map((_c, widgetIdx) => (
|
||||
<Incremental key={widgetIdx} name={'w_' + widgetIdx}>
|
||||
<SlowWidget idx={widgetIdx} />
|
||||
</Incremental>
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
</Block>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</IncrementalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function burnCPU(milliseconds) {
|
||||
const start = performanceNow();
|
||||
while (performanceNow() < (start + milliseconds)) {}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
scrollView: {
|
||||
margin: 10,
|
||||
backgroundColor: 'white',
|
||||
flex: 1,
|
||||
},
|
||||
headerText: {
|
||||
fontSize: 20,
|
||||
margin: 10,
|
||||
},
|
||||
block: {
|
||||
borderRadius: 6,
|
||||
borderWidth: 2,
|
||||
borderColor: '#a52a2a',
|
||||
padding: 14,
|
||||
margin: 5,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
widgetContainer: {
|
||||
backgroundColor: '#dddddd',
|
||||
padding: 2,
|
||||
margin: 2,
|
||||
},
|
||||
widgetText: {
|
||||
color: 'black',
|
||||
fontSize: 4,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = IncrementalExample;
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule IncrementalGroup
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Incremental = require('Incremental');
|
||||
const React = require('React');
|
||||
|
||||
let _groupCounter = -1;
|
||||
const DEBUG = false;
|
||||
|
||||
import type {Props, Context} from 'Incremental';
|
||||
|
||||
/**
|
||||
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
|
||||
* not be reliably announced. The whole thing might be deleted, who knows? Use
|
||||
* at your own risk.
|
||||
*
|
||||
* `<Incremental>` components must be wrapped in an `<IncrementalGroup>` (e.g.
|
||||
* via `<IncrementalPresenter>`) in order to provide the incremental group
|
||||
* context, otherwise they will do nothing.
|
||||
*
|
||||
* See Incremental.js for more info.
|
||||
*/
|
||||
class IncrementalGroup extends React.Component {
|
||||
props: Props;
|
||||
context: Context;
|
||||
_groupInc: string;
|
||||
componentWillMount() {
|
||||
this._groupInc = `g${++_groupCounter}-`;
|
||||
DEBUG && console.log(
|
||||
'create IncrementalGroup with id ' + this.getGroupId()
|
||||
);
|
||||
}
|
||||
|
||||
getGroupId(): string {
|
||||
const ctx = this.context.incrementalGroup;
|
||||
const prefix = ctx ? ctx.groupId + ':' : '';
|
||||
return prefix + this._groupInc + this.props.name;
|
||||
}
|
||||
|
||||
getChildContext(): Context {
|
||||
if (this.props.disabled || this.context.incrementalGroupEnabled === false) {
|
||||
return {
|
||||
incrementalGroupEnabled: false,
|
||||
incrementalGroup: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
incrementalGroupEnabled: true,
|
||||
incrementalGroup: {
|
||||
groupId: this.getGroupId(),
|
||||
incrementalCount: -1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<Incremental
|
||||
onDone={this.props.onDone}
|
||||
children={this.props.children}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
IncrementalGroup.contextTypes = {
|
||||
incrementalGroup: React.PropTypes.object,
|
||||
incrementalGroupEnabled: React.PropTypes.bool,
|
||||
};
|
||||
IncrementalGroup.childContextTypes = IncrementalGroup.contextTypes;
|
||||
|
||||
module.exports = IncrementalGroup;
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule IncrementalPresenter
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const IncrementalGroup = require('IncrementalGroup');
|
||||
const React = require('React');
|
||||
const View = require('View');
|
||||
|
||||
import type {Context} from 'Incremental';
|
||||
|
||||
/**
|
||||
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
|
||||
* not be reliably announced. The whole thing might be deleted, who knows? Use
|
||||
* at your own risk.
|
||||
*
|
||||
* `<IncrementalPresenter>` can be used to group sets of `<Incremental>` renders
|
||||
* such that they are initially invisible and removed from layout until all
|
||||
* decendents have finished rendering, at which point they are drawn all at once
|
||||
* so the UI doesn't jump around during the incremental rendering process.
|
||||
*
|
||||
* See Incremental.js for more info.
|
||||
*/
|
||||
type Props = {
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
onDone: () => void;
|
||||
onLayout: (event: Object) => void;
|
||||
style: mixed;
|
||||
children: any;
|
||||
}
|
||||
class IncrementalPresenter extends React.Component {
|
||||
props: Props;
|
||||
context: Context;
|
||||
_isDone: boolean;
|
||||
constructor(props: Props, context: Context) {
|
||||
super(props, context);
|
||||
this._isDone = false;
|
||||
(this: any).onDone = this.onDone.bind(this);
|
||||
}
|
||||
onDone() {
|
||||
this._isDone = true;
|
||||
if (this.props.disabled !== true &&
|
||||
this.context.incrementalGroupEnabled !== false) {
|
||||
// Avoid expensive re-renders and use setNativeProps
|
||||
this.refs.view.setNativeProps(
|
||||
{style: [this.props.style, {opacity: 1, position: 'relative'}]}
|
||||
);
|
||||
}
|
||||
this.props.onDone && this.props.onDone();
|
||||
}
|
||||
render() {
|
||||
if (this.props.disabled !== true &&
|
||||
this.context.incrementalGroupEnabled !== false &&
|
||||
!this._isDone) {
|
||||
var style = [this.props.style, {opacity: 0, position: 'absolute'}];
|
||||
} else {
|
||||
var style = this.props.style;
|
||||
}
|
||||
return (
|
||||
<IncrementalGroup
|
||||
onDone={this.onDone}
|
||||
name={this.props.name}
|
||||
disabled={this.props.disabled}>
|
||||
<View
|
||||
children={this.props.children}
|
||||
ref="view"
|
||||
style={style}
|
||||
onLayout={this.props.onLayout}
|
||||
/>
|
||||
</IncrementalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
IncrementalPresenter.propTypes = {
|
||||
name: React.PropTypes.string,
|
||||
disabled: React.PropTypes.bool,
|
||||
onDone: React.PropTypes.func,
|
||||
onLayout: React.PropTypes.func,
|
||||
style: View.propTypes.style,
|
||||
};
|
||||
IncrementalPresenter.contextTypes = {
|
||||
incrementalGroup: React.PropTypes.object,
|
||||
incrementalGroupEnabled: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
module.exports = IncrementalPresenter;
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule JSEventLoopWatchdog
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const performanceNow = require('performanceNow');
|
||||
|
||||
type Handler = {
|
||||
onIterate?: () => void;
|
||||
onStall: (params: {lastInterval: number}) => string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A utility for tracking stalls in the JS event loop that prevent timers and
|
||||
* other events from being processed in a timely manner.
|
||||
*
|
||||
* The "stall" time is defined as the amount of time in access of the acceptable
|
||||
* threshold, which is typically around 100-200ms. So if the treshold is set to
|
||||
* 100 and a timer fires 150 ms later than it was scheduled because the event
|
||||
* loop was tied up, that would be considered a 50ms stall.
|
||||
*
|
||||
* By default, logs stall events to the console when installed. Can also be
|
||||
* queried with `getStats`.
|
||||
*/
|
||||
const JSEventLoopWatchdog = {
|
||||
getStats: function(): Object {
|
||||
return {stallCount, totalStallTime, longestStall, acceptableBusyTime};
|
||||
},
|
||||
reset: function() {
|
||||
console.log('JSEventLoopWatchdog: reset');
|
||||
totalStallTime = 0;
|
||||
stallCount = 0;
|
||||
longestStall = 0;
|
||||
},
|
||||
addHandler: function(handler: Handler) {
|
||||
handlers.push(handler);
|
||||
},
|
||||
install: function({thresholdMS}: {thresholdMS: number}) {
|
||||
acceptableBusyTime = thresholdMS;
|
||||
if (installed) {
|
||||
return;
|
||||
}
|
||||
installed = true;
|
||||
let lastInterval = performanceNow();
|
||||
function iteration() {
|
||||
const now = performanceNow();
|
||||
const busyTime = now - lastInterval;
|
||||
if (busyTime >= thresholdMS) {
|
||||
const stallTime = busyTime - thresholdMS;
|
||||
stallCount++;
|
||||
totalStallTime += stallTime;
|
||||
longestStall = Math.max(longestStall, stallTime);
|
||||
let msg = `JSEventLoopWatchdog: JS thread busy for ${busyTime}ms. ` +
|
||||
`${totalStallTime}ms in ${stallCount} stalls so far. `;
|
||||
handlers.forEach((handler) => {
|
||||
msg += handler.onStall({lastInterval});
|
||||
});
|
||||
console.log(msg);
|
||||
}
|
||||
handlers.forEach((handler) => {
|
||||
handler.onIterate && handler.onIterate();
|
||||
});
|
||||
lastInterval = now;
|
||||
setTimeout(iteration, thresholdMS / 5);
|
||||
}
|
||||
iteration();
|
||||
},
|
||||
};
|
||||
|
||||
let acceptableBusyTime = 0;
|
||||
let installed = false;
|
||||
let totalStallTime = 0;
|
||||
let stallCount = 0;
|
||||
let longestStall = 0;
|
||||
const handlers: Array<Handler> = [];
|
||||
|
||||
module.exports = JSEventLoopWatchdog;
|
Loading…
Reference in New Issue