initial commit taken from gitlab.lrz.de

This commit is contained in:
privatereese
2018-08-24 18:09:42 +02:00
parent ae54ed4c48
commit fc05486403
28494 changed files with 2159823 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule Batchinator
* @flow
*/
'use strict';
const InteractionManager = require('InteractionManager');
/**
* A simple class for batching up invocations of a low-pri callback. A timeout is set to run the
* callback once after a delay, no matter how many times it's scheduled. Once the delay is reached,
* InteractionManager.runAfterInteractions is used to invoke the callback after any hi-pri
* interactions are done running.
*
* Make sure to cleanup with dispose(). Example:
*
* class Widget extends React.Component {
* _batchedSave: new Batchinator(() => this._saveState, 1000);
* _saveSate() {
* // save this.state to disk
* }
* componentDidUpdate() {
* this._batchedSave.schedule();
* }
* componentWillUnmount() {
* this._batchedSave.dispose();
* }
* ...
* }
*/
class Batchinator {
_callback: () => void;
_delay: number;
_taskHandle: ?{cancel: () => void};
constructor(callback: () => void, delayMS: number) {
this._delay = delayMS;
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}) {
if (this._taskHandle) {
this._taskHandle.cancel();
if (!options.abort) {
this._callback();
}
this._taskHandle = null;
}
}
schedule() {
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);
this._taskHandle = {cancel: () => clearTimeout(timeoutHandle)};
}
}
module.exports = Batchinator;

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule BridgeSpyStallHandler
* @flow
*/
'use strict';
const JSEventLoopWatchdog = require('JSEventLoopWatchdog');
const MessageQueue = require('MessageQueue');
const infoLog = require('infoLog');
const BridgeSpyStallHandler = {
register: function() {
let spyBuffer = [];
MessageQueue.spy((data) => {
spyBuffer.push(data);
});
const TO_JS = 0;
JSEventLoopWatchdog.addHandler({
onStall: () => {
infoLog(
spyBuffer.length + ' bridge messages during stall: ',
spyBuffer.map((info) => {
let args = '<args>';
try {
args = JSON.stringify(info.args);
} catch (e1) {
if (Array.isArray(info.args)) {
args = info.args.map((arg) => {
try {
return JSON.stringify(arg);
} catch (e2) {
return '?';
}
});
} else {
args = 'keys:' + JSON.stringify(Object.keys(info.args));
}
}
return `${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
`${info.module ? (info.module + '.') : ''}${info.method}(${JSON.stringify(args)})`;
}),
);
},
onIterate: () => {
spyBuffer = [];
},
});
},
};
module.exports = BridgeSpyStallHandler;

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule FrameRateLogger
* @flow
*/
'use strict';
const NativeModules = require('NativeModules');
const invariant = require('fbjs/lib/invariant');
/**
* Flow API for native FrameRateLogger module. If the native module is not installed, function calls
* are just no-ops.
*
* Typical behavior is that `setContext` is called when a new screen is loaded (e.g. via a
* navigation integration), and then `beginScroll` is called by `ScrollResponder` at which point the
* native module then begins tracking frame drops. When `ScrollResponder` calls `endScroll`, the
* native module gathers up all it's frame drop data and reports it via an analytics pipeline for
* analysis.
*
* Note that `beginScroll` may be called multiple times by `ScrollResponder` - unclear if that's a
* bug, but the native module should be robust to that.
*
* In the future we may add support for tracking frame drops in other types of interactions beyond
* scrolling.
*/
const FrameRateLogger = {
/**
* Enable `debug` to see local logs of what's going on. `reportStackTraces` will grab stack traces
* during UI thread stalls and upload them if the native module supports it.
*/
setGlobalOptions: function(options: {debug?: boolean, reportStackTraces?: boolean}) {
if (options.debug !== undefined) {
invariant(
NativeModules.FrameRateLogger,
'Trying to debug FrameRateLogger without the native module!',
);
}
NativeModules.FrameRateLogger && NativeModules.FrameRateLogger.setGlobalOptions(options);
},
/**
* Must call `setContext` before any events can be properly tracked, which is done automatically
* in `AppRegistry`, but navigation is also a common place to hook in.
*/
setContext: function(context: string) {
NativeModules.FrameRateLogger && NativeModules.FrameRateLogger.setContext(context);
},
/**
* Called in `ScrollResponder` so any component that uses that module will handle this
* automatically.
*/
beginScroll() {
NativeModules.FrameRateLogger && NativeModules.FrameRateLogger.beginScroll();
},
/**
* Called in `ScrollResponder` so any component that uses that module will handle this
* automatically.
*/
endScroll() {
NativeModules.FrameRateLogger && NativeModules.FrameRateLogger.endScroll();
},
};
module.exports = FrameRateLogger;

View File

@@ -0,0 +1,218 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule InteractionManager
* @flow
*/
'use strict';
const BatchedBridge = require('BatchedBridge');
const EventEmitter = require('EventEmitter');
const Set = require('Set');
const TaskQueue = require('TaskQueue');
const infoLog = require('infoLog');
const invariant = require('fbjs/lib/invariant');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const keyMirror = require('fbjs/lib/keyMirror');
type Handle = number;
import type {Task} from 'TaskQueue';
const _emitter = new EventEmitter();
const DEBUG_DELAY = 0;
const DEBUG = false;
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
* animations to run smoothly.
*
* Applications can schedule tasks to run after interactions with the following:
*
* ```
* InteractionManager.runAfterInteractions(() => {
* // ...long-running synchronous task...
* });
* ```
*
* Compare this to other scheduling alternatives:
*
* - requestAnimationFrame(): for code that animates a view over time.
* - setImmediate/setTimeout(): run code later, note this may delay animations.
* - runAfterInteractions(): run code later, without delaying active animations.
*
* The touch handling system considers one or more active touches to be an
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
* touches have ended or been cancelled.
*
* InteractionManager also allows applications to register animations by
* creating an interaction 'handle' on animation start, and clearing it upon
* completion:
*
* ```
* var handle = InteractionManager.createInteractionHandle();
* // run animation... (`runAfterInteractions` tasks are queued)
* // later, on animation completion:
* InteractionManager.clearInteractionHandle(handle);
* // queued tasks run if all handles were cleared
* ```
*
* `runAfterInteractions` takes either a plain callback function, or a
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
* dependencies that also schedule more tasks via `runAfterInteractions`) before
* starting on the next task that might have been queued up synchronously
* earlier.
*
* By default, queued tasks are executed together in a loop in one
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
* tasks will only be executed until the deadline (in terms of js event loop run
* time) approaches, at which point execution will yield via setTimeout,
* allowing events such as touches to start interactions and block queued tasks
* from executing, making apps more responsive.
*/
var InteractionManager = {
Events: keyMirror({
interactionStart: true,
interactionComplete: true,
}),
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*/
runAfterInteractions(task: ?Task): {then: Function, done: Function, cancel: Function} {
const tasks = [];
const promise = new Promise(resolve => {
_scheduleUpdate();
if (task) {
tasks.push(task);
}
tasks.push({run: resolve, name: 'resolve ' + (task && task.name || '?')});
_taskQueue.enqueueTasks(tasks);
});
return {
then: promise.then.bind(promise),
done: (...args) => {
if (promise.done) {
return promise.done(...args);
} else {
console.warn('Tried to call done when not supported by current Promise implementation.');
}
},
cancel: function() {
_taskQueue.cancelTasks(tasks);
},
};
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): Handle {
DEBUG && infoLog('create interaction handle');
_scheduleUpdate();
var handle = ++_inc;
_addInteractionSet.add(handle);
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: Handle) {
DEBUG && infoLog('clear interaction handle');
invariant(
!!handle,
'Must provide a handle to clear.'
);
_scheduleUpdate();
_addInteractionSet.delete(handle);
_deleteInteractionSet.add(handle);
},
addListener: _emitter.addListener.bind(_emitter),
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*/
setDeadline(deadline: number) {
_deadline = deadline;
},
};
const _interactionSet = new Set();
const _addInteractionSet = new Set();
const _deleteInteractionSet = new Set();
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
let _nextUpdateHandle = 0;
let _inc = 0;
let _deadline = -1;
declare function setImmediate(callback: any, ...args: Array<any>): number;
/**
* Schedule an asynchronous update to the interaction state.
*/
function _scheduleUpdate() {
if (!_nextUpdateHandle) {
if (_deadline > 0) {
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.63 was deployed. To see the error delete this
* comment and run Flow. */
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
} else {
_nextUpdateHandle = setImmediate(_processUpdate);
}
}
}
/**
* Notify listeners, process queue, etc
*/
function _processUpdate() {
_nextUpdateHandle = 0;
var interactionCount = _interactionSet.size;
_addInteractionSet.forEach(handle =>
_interactionSet.add(handle)
);
_deleteInteractionSet.forEach(handle =>
_interactionSet.delete(handle)
);
var nextInteractionCount = _interactionSet.size;
if (interactionCount !== 0 && nextInteractionCount === 0) {
// transition from 1+ --> 0 interactions
_emitter.emit(InteractionManager.Events.interactionComplete);
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
// transition from 0 --> 1+ interactions
_emitter.emit(InteractionManager.Events.interactionStart);
}
// process the queue regardless of a transition
if (nextInteractionCount === 0) {
while (_taskQueue.hasTasksToProcess()) {
_taskQueue.processNext();
if (_deadline > 0 &&
BatchedBridge.getEventLoopRunningTime() >= _deadline) {
// Hit deadline before processing all tasks, so process more later.
_scheduleUpdate();
break;
}
}
}
_addInteractionSet.clear();
_deleteInteractionSet.clear();
}
module.exports = InteractionManager;

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule InteractionMixin
* @flow
*/
'use strict';
var InteractionManager = require('InteractionManager');
/**
* This mixin provides safe versions of InteractionManager start/end methods
* that ensures `clearInteractionHandle` is always called
* once per start, even if the component is unmounted.
*/
var InteractionMixin = {
componentWillUnmount: function() {
while (this._interactionMixinHandles.length) {
InteractionManager.clearInteractionHandle(
this._interactionMixinHandles.pop()
);
}
},
_interactionMixinHandles: ([]: Array<number>),
createInteractionHandle: function() {
var handle = InteractionManager.createInteractionHandle();
this._interactionMixinHandles.push(handle);
return handle;
},
clearInteractionHandle: function(clearHandle: number) {
InteractionManager.clearInteractionHandle(clearHandle);
this._interactionMixinHandles = this._interactionMixinHandles.filter(
handle => handle !== clearHandle
);
},
/**
* Schedule work for after all interactions have completed.
*
* @param {function} callback
*/
runAfterInteractions: function(callback: Function) {
InteractionManager.runAfterInteractions(callback);
},
};
module.exports = InteractionMixin;

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule InteractionStallDebugger
* @flow
*/
'use strict';
const BridgeSpyStallHandler = require('BridgeSpyStallHandler');
const JSEventLoopWatchdog = require('JSEventLoopWatchdog');
const ReactPerfStallHandler = require('ReactPerfStallHandler');
const InteractionStallDebugger = {
install: function(options: {thresholdMS: number}) {
JSEventLoopWatchdog.install(options);
BridgeSpyStallHandler.register();
if (__DEV__) {
ReactPerfStallHandler.register();
}
},
};
module.exports = InteractionStallDebugger;

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule JSEventLoopWatchdog
* @flow
*/
'use strict';
const infoLog = require('infoLog');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const performanceNow = require('fbjs/lib/performanceNow');
type Handler = {
onIterate?: () => void,
onStall: (params: {lastInterval: number, busyTime: 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() {
infoLog('JSEventLoopWatchdog: reset');
totalStallTime = 0;
stallCount = 0;
longestStall = 0;
lastInterval = performanceNow();
},
addHandler: function(handler: Handler) {
handlers.push(handler);
},
install: function({thresholdMS}: {thresholdMS: number}) {
acceptableBusyTime = thresholdMS;
if (installed) {
return;
}
installed = true;
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, busyTime}) || '';
});
infoLog(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;
let lastInterval = 0;
const handlers: Array<Handler> = [];
module.exports = JSEventLoopWatchdog;

View File

@@ -0,0 +1,413 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule PanResponder
*/
'use strict';
const InteractionManager = require('./InteractionManager');
const TouchHistoryMath = require('TouchHistoryMath');
const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
const currentCentroidX = TouchHistoryMath.currentCentroidX;
const currentCentroidY = TouchHistoryMath.currentCentroidY;
/**
* `PanResponder` reconciles several touches into a single gesture. It makes
* single-touch gestures resilient to extra touches, and can be used to
* recognize simple multi-touch gestures.
*
* By default, `PanResponder` holds an `InteractionManager` handle to block
* long-running JS events from interrupting active gestures.
*
* It provides a predictable wrapper of the responder handlers provided by the
* [gesture responder system](docs/gesture-responder-system.html).
* For each handler, it provides a new `gestureState` object alongside the
* native event object:
*
* ```
* onPanResponderMove: (event, gestureState) => {}
* ```
*
* A native event is a synthetic touch event with the following form:
*
* - `nativeEvent`
* + `changedTouches` - Array of all touch events that have changed since the last event
* + `identifier` - The ID of the touch
* + `locationX` - The X position of the touch, relative to the element
* + `locationY` - The Y position of the touch, relative to the element
* + `pageX` - The X position of the touch, relative to the root element
* + `pageY` - The Y position of the touch, relative to the root element
* + `target` - The node id of the element receiving the touch event
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
* + `touches` - Array of all current touches on the screen
*
* A `gestureState` object has the following:
*
* - `stateID` - ID of the gestureState- persisted as long as there at least
* one touch on screen
* - `moveX` - the latest screen coordinates of the recently-moved touch
* - `moveY` - the latest screen coordinates of the recently-moved touch
* - `x0` - the screen coordinates of the responder grant
* - `y0` - the screen coordinates of the responder grant
* - `dx` - accumulated distance of the gesture since the touch started
* - `dy` - accumulated distance of the gesture since the touch started
* - `vx` - current velocity of the gesture
* - `vy` - current velocity of the gesture
* - `numberActiveTouches` - Number of touches currently on screen
*
* ### Basic Usage
*
* ```
* componentWillMount: function() {
* this._panResponder = PanResponder.create({
* // Ask to be the responder:
* onStartShouldSetPanResponder: (evt, gestureState) => true,
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
*
* onPanResponderGrant: (evt, gestureState) => {
* // The gesture has started. Show visual feedback so the user knows
* // what is happening!
*
* // gestureState.d{x,y} will be set to zero now
* },
* onPanResponderMove: (evt, gestureState) => {
* // The most recent move distance is gestureState.move{X,Y}
*
* // The accumulated gesture distance since becoming responder is
* // gestureState.d{x,y}
* },
* onPanResponderTerminationRequest: (evt, gestureState) => true,
* onPanResponderRelease: (evt, gestureState) => {
* // The user has released all touches while this view is the
* // responder. This typically means a gesture has succeeded
* },
* onPanResponderTerminate: (evt, gestureState) => {
* // Another component has become the responder, so this gesture
* // should be cancelled
* },
* onShouldBlockNativeResponder: (evt, gestureState) => {
* // Returns whether this component should block native components from becoming the JS
* // responder. Returns true by default. Is currently only supported on android.
* return true;
* },
* });
* },
*
* render: function() {
* return (
* <View {...this._panResponder.panHandlers} />
* );
* },
*
* ```
*
* ### Working Example
*
* To see it in action, try the
* [PanResponder example in RNTester](https://github.com/facebook/react-native/blob/master/RNTester/js/PanResponderExample.js)
*/
const PanResponder = {
/**
*
* A graphical explanation of the touch data flow:
*
* +----------------------------+ +--------------------------------+
* | ResponderTouchHistoryStore | |TouchHistoryMath |
* +----------------------------+ +----------+---------------------+
* |Global store of touchHistory| |Allocation-less math util |
* |including activeness, start | |on touch history (centroids |
* |position, prev/cur position.| |and multitouch movement etc) |
* | | | |
* +----^-----------------------+ +----^---------------------------+
* | |
* | (records relevant history |
* | of touches relevant for |
* | implementing higher level |
* | gestures) |
* | |
* +----+-----------------------+ +----|---------------------------+
* | ResponderEventPlugin | | | Your App/Component |
* +----------------------------+ +----|---------------------------+
* |Negotiates which view gets | Low level | | High level |
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
* |Also records history into | touchHistory| | Pan | multitouch + |
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
* +----------------------------+ attached to | | | distance and |
* each event | +---------+ velocity. |
* | |
* | |
* +--------------------------------+
*
*
*
* Gesture that calculates cumulative movement over time in a way that just
* "does the right thing" for multiple touches. The "right thing" is very
* nuanced. When moving two touches in opposite directions, the cumulative
* distance is zero in each dimension. When two touches move in parallel five
* pixels in the same direction, the cumulative distance is five, not ten. If
* two touches start, one moves five in a direction, then stops and the other
* touch moves fives in the same direction, the cumulative distance is ten.
*
* This logic requires a kind of processing of time "clusters" of touch events
* so that two touch moves that essentially occur in parallel but move every
* other frame respectively, are considered part of the same movement.
*
* Explanation of some of the non-obvious fields:
*
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
* invalid. If a move event has been observed, `(moveX, moveY)` is the
* centroid of the most recently moved "cluster" of active touches.
* (Currently all move have the same timeStamp, but later we should add some
* threshold for what is considered to be "moving"). If a palm is
* accidentally counted as a touch, but a finger is moving greatly, the palm
* will move slightly, but we only want to count the single moving touch.
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
* responder.
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
* distance. Accounts for touch moves that are clustered together in time,
* moving the same direction. Only valid when currently responder (otherwise,
* it only represents the drag distance below the threshold).
* - vx/vy: Velocity.
*/
_initializeGestureState: function (gestureState) {
gestureState.moveX = 0;
gestureState.moveY = 0;
gestureState.x0 = 0;
gestureState.y0 = 0;
gestureState.dx = 0;
gestureState.dy = 0;
gestureState.vx = 0;
gestureState.vy = 0;
gestureState.numberActiveTouches = 0;
// All `gestureState` accounts for timeStamps up until:
gestureState._accountsForMovesUpTo = 0;
},
/**
* This is nuanced and is necessary. It is incorrect to continuously take all
* active *and* recently moved touches, find the centroid, and track how that
* result changes over time. Instead, we must take all recently moved
* touches, and calculate how the centroid has changed just for those
* recently moved touches, and append that change to an accumulator. This is
* to (at least) handle the case where the user is moving three fingers, and
* then one of the fingers stops but the other two continue.
*
* This is very different than taking all of the recently moved touches and
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
* changes* in the centroid of recently moved touches.
*
* There is also some nuance with how we handle multiple moved touches in a
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
* individual events, multiple touches generate two 'move' events, each of
* them triggering `onResponderMove`. But with the way `PanResponder` works,
* all of the gesture inference is performed on the first dispatch, since it
* looks at all of the touches (even the ones for which there hasn't been a
* native dispatch yet). Therefore, `PanResponder` does not call
* `onResponderMove` passed the first dispatch. This diverges from the
* typical responder callback pattern (without using `PanResponder`), but
* avoids more dispatches than necessary.
*/
_updateGestureStateOnMove: function (gestureState, touchHistory) {
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
const movedAfter = gestureState._accountsForMovesUpTo;
const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
const nextDX = gestureState.dx + (x - prevX);
const nextDY = gestureState.dy + (y - prevY);
// TODO: This must be filtered intelligently.
const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo;
gestureState.vx = (nextDX - gestureState.dx) / dt;
gestureState.vy = (nextDY - gestureState.dy) / dt;
gestureState.dx = nextDX;
gestureState.dy = nextDY;
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
},
/**
* @param {object} config Enhanced versions of all of the responder callbacks
* that provide not only the typical `ResponderSyntheticEvent`, but also the
* `PanResponder` gesture state. Simply replace the word `Responder` with
* `PanResponder` in each of the typical `onResponder*` callbacks. For
* example, the `config` object would look like:
*
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onPanResponderReject: (e, gestureState) => {...}`
* - `onPanResponderGrant: (e, gestureState) => {...}`
* - `onPanResponderStart: (e, gestureState) => {...}`
* - `onPanResponderEnd: (e, gestureState) => {...}`
* - `onPanResponderRelease: (e, gestureState) => {...}`
* - `onPanResponderMove: (e, gestureState) => {...}`
* - `onPanResponderTerminate: (e, gestureState) => {...}`
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
*
* In general, for events that have capture equivalents, we update the
* gestureState once in the capture phase and can use it in the bubble phase
* as well.
*
* Be careful with onStartShould* callbacks. They only reflect updated
* `gestureState` for start/end events that bubble/capture to the Node.
* Once the node is the responder, you can rely on every start/end event
* being processed by the gesture and `gestureState` being updated
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create: function (config) {
const interactionState = {
handle: (null: ?number),
};
const gestureState = {
// Useful for debugging
stateID: Math.random(),
};
PanResponder._initializeGestureState(gestureState);
const panHandlers = {
onStartShouldSetResponder: function (e) {
return config.onStartShouldSetPanResponder === undefined ?
false :
config.onStartShouldSetPanResponder(e, gestureState);
},
onMoveShouldSetResponder: function (e) {
return config.onMoveShouldSetPanResponder === undefined ?
false :
config.onMoveShouldSetPanResponder(e, gestureState);
},
onStartShouldSetResponderCapture: function (e) {
// TODO: Actually, we should reinitialize the state any time
// touches.length increases from 0 active to > 0 active.
if (e.nativeEvent.touches.length === 1) {
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture !== undefined ?
config.onStartShouldSetPanResponderCapture(e, gestureState) :
false;
},
onMoveShouldSetResponderCapture: function (e) {
const touchHistory = e.touchHistory;
// Responder system incorrectly dispatches should* to current responder
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
return false;
}
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
return config.onMoveShouldSetPanResponderCapture ?
config.onMoveShouldSetPanResponderCapture(e, gestureState) :
false;
},
onResponderGrant: function (e) {
if (!interactionState.handle) {
interactionState.handle = InteractionManager.createInteractionHandle();
}
gestureState.x0 = currentCentroidX(e.touchHistory);
gestureState.y0 = currentCentroidY(e.touchHistory);
gestureState.dx = 0;
gestureState.dy = 0;
if (config.onPanResponderGrant) {
config.onPanResponderGrant(e, gestureState);
}
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined ?
true :
config.onShouldBlockNativeResponder();
},
onResponderReject: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState);
},
onResponderRelease: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderStart: function (e) {
const touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
if (config.onPanResponderStart) {
config.onPanResponderStart(e, gestureState);
}
},
onResponderMove: function (e) {
const touchHistory = e.touchHistory;
// Guard against the dispatch of two touch moves when there are two
// simultaneously changed touches.
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
return;
}
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
if (config.onPanResponderMove) {
config.onPanResponderMove(e, gestureState);
}
},
onResponderEnd: function (e) {
const touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState);
},
onResponderTerminate: function (e) {
clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderTerminationRequest: function (e) {
return config.onPanResponderTerminationRequest === undefined ?
true :
config.onPanResponderTerminationRequest(e, gestureState);
}
};
return {
panHandlers,
getInteractionHandle(): ?number {
return interactionState.handle;
},
};
}
};
function clearInteractionHandle(
interactionState: {handle: ?number},
callback: Function,
event: Object,
gestureState: Object
) {
if (interactionState.handle) {
InteractionManager.clearInteractionHandle(interactionState.handle);
interactionState.handle = null;
}
if (callback) {
callback(event, gestureState);
}
}
module.exports = PanResponder;

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule ReactPerfStallHandler
* @flow
*/
'use strict';
const JSEventLoopWatchdog = require('JSEventLoopWatchdog');
const ReactPerf = require('ReactPerf');
const ReactPerfStallHandler = {
register: function() {
ReactPerf.start();
JSEventLoopWatchdog.addHandler({
onStall: () => {
ReactPerf.stop();
ReactPerf.printInclusive();
ReactPerf.printWasted();
ReactPerf.start();
},
onIterate: () => {
ReactPerf.stop();
ReactPerf.start();
},
});
},
};
module.exports = ReactPerfStallHandler;

View File

@@ -0,0 +1,168 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule TaskQueue
* @flow
*/
'use strict';
const infoLog = require('infoLog');
const invariant = require('fbjs/lib/invariant');
type SimpleTask = {
name: string,
run: () => void,
};
type PromiseTask = {
name: string,
gen: () => Promise<any>,
};
export type Task = Function | SimpleTask | PromiseTask;
const DEBUG = false;
/**
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
* trees of dependent tasks based on Promises. No tasks are executed unless
* `processNext` is called.
*
* `enqueue` takes a Task object with either a simple `run` callback, or a
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
* function is supplied, then the promise it returns will block execution of
* tasks already in the queue until it resolves. This can be used to make sure
* the first task is fully resolved (including asynchronous dependencies that
* also schedule more tasks via `enqueue`) before starting on the next task.
* The `onMoreTasks` constructor argument is used to inform the owner that an
* async task has resolved and that the queue should be processed again.
*
* Note: Tasks are only actually executed with explicit calls to `processNext`.
*/
class TaskQueue {
/**
* TaskQueue instances are self contained and independent, so multiple tasks
* of varying semantics and priority can operate together.
*
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
* tasks to process.
*/
constructor({onMoreTasks}: {onMoreTasks: () => void}) {
this._onMoreTasks = onMoreTasks;
this._queueStack = [{tasks: [], popable: false}];
}
/**
* Add a task to the queue. It is recommended to name your tasks for easier
* async debugging. Tasks will not be executed until `processNext` is called
* explicitly.
*/
enqueue(task: Task): void {
this._getCurrentQueue().push(task);
}
enqueueTasks(tasks: Array<Task>): void {
tasks.forEach((task) => this.enqueue(task));
}
cancelTasks(tasksToCancel: Array<Task>): void {
// search through all tasks and remove them.
this._queueStack = this._queueStack
.map((queue) => ({
...queue,
tasks: queue.tasks.filter((task) => tasksToCancel.indexOf(task) === -1),
}))
.filter((queue, idx) => (queue.tasks.length > 0 || idx === 0));
}
/**
* Check to see if `processNext` should be called.
*
* @returns {boolean} Returns true if there are tasks that are ready to be
* processed with `processNext`, or returns false if there are no more tasks
* to be processed right now, although there may be tasks in the queue that
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
* tasks ready to run at that point.
*/
hasTasksToProcess(): bool {
return this._getCurrentQueue().length > 0;
}
/**
* Executes the next task in the queue.
*/
processNext(): void {
const queue = this._getCurrentQueue();
if (queue.length) {
const task = queue.shift();
try {
if (task.gen) {
DEBUG && infoLog('genPromise for task ' + task.name);
this._genPromise((task: any)); // Rather than annoying tagged union
} else if (task.run) {
DEBUG && infoLog('run task ' + task.name);
task.run();
} else {
invariant(
typeof task === 'function',
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
JSON.stringify(task, null, 2)
);
DEBUG && infoLog('run anonymous task');
task();
}
} catch (e) {
e.message = 'TaskQueue: Error with task ' + (task.name || '') + ': ' +
e.message;
throw e;
}
}
}
_queueStack: Array<{tasks: Array<Task>, popable: bool}>;
_onMoreTasks: () => void;
_getCurrentQueue(): Array<Task> {
const stackIdx = this._queueStack.length - 1;
const queue = this._queueStack[stackIdx];
if (queue.popable &&
queue.tasks.length === 0 &&
this._queueStack.length > 1) {
this._queueStack.pop();
DEBUG && infoLog('popped queue: ', {stackIdx, queueStackSize: this._queueStack.length});
return this._getCurrentQueue();
} else {
return queue.tasks;
}
}
_genPromise(task: PromiseTask) {
// Each async task pushes it's own queue onto the queue stack. This
// effectively defers execution of previously queued tasks until the promise
// resolves, at which point we allow the new queue to be popped, which
// happens once it is fully processed.
this._queueStack.push({tasks: [], popable: false});
const stackIdx = this._queueStack.length - 1;
DEBUG && infoLog('push new queue: ', {stackIdx});
DEBUG && infoLog('exec gen task ' + task.name);
task.gen()
.then(() => {
DEBUG && infoLog(
'onThen for gen task ' + task.name,
{stackIdx, queueStackSize: this._queueStack.length},
);
this._queueStack[stackIdx].popable = true;
this.hasTasksToProcess() && this._onMoreTasks();
})
.catch((ex) => {
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
throw ex;
})
.done();
}
}
module.exports = TaskQueue;