Flow-ify and cleanup JSTimers

Reviewed By: davidaurelio

Differential Revision: D3819511

fbshipit-source-id: 849133d013b026c3f95e988664f252bc35c65122
This commit is contained in:
Pieter De Baets 2016-09-06 03:57:58 -07:00 committed by Facebook Github Bot 4
parent f07ca31303
commit 66ce1297c2
2 changed files with 104 additions and 112 deletions

View File

@ -7,116 +7,117 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule JSTimers * @providesModule JSTimers
* @flow
*/ */
'use strict'; 'use strict';
// Note that the module JSTimers is split into two in order to solve a cycle // Note that the module JSTimers is split into two in order to solve a cycle
// in dependencies. NativeModules > BatchedBridge > MessageQueue > JSTimersExecution // in dependencies. NativeModules > BatchedBridge > MessageQueue > JSTimersExecution
var RCTTiming = require('NativeModules').Timing; const RCTTiming = require('NativeModules').Timing;
var JSTimersExecution = require('JSTimersExecution'); const JSTimersExecution = require('JSTimersExecution');
// Returns a free index if one is available, and the next consecutive index otherwise.
function _getFreeIndex(): number {
let freeIndex = JSTimersExecution.timerIDs.indexOf(null);
if (freeIndex === -1) {
freeIndex = JSTimersExecution.timerIDs.length;
}
return freeIndex;
}
function _allocateCallback(func: Function, type: $Keys<typeof JSTimersExecution.Type>): number {
const id = JSTimersExecution.GUID++;
const freeIndex = _getFreeIndex();
JSTimersExecution.timerIDs[freeIndex] = id;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.types[freeIndex] = type;
return id;
}
function _freeCallback(timerID: number) {
// JSTimersExecution.timerIDs contains nulls after timers have been removed;
// ignore nulls upfront so indexOf doesn't find them
if (timerID == null) {
return;
}
const index = JSTimersExecution.timerIDs.indexOf(timerID);
// See corresponding comment in `callTimers` for reasoning behind this
if (index !== -1) {
JSTimersExecution._clearIndex(index);
const type = JSTimersExecution.types[index];
if (type !== JSTimersExecution.Type.setImmediate &&
type !== JSTimersExecution.Type.requestIdleCallback) {
RCTTiming.deleteTimer(timerID);
}
}
}
/** /**
* JS implementation of timer functions. Must be completely driven by an * JS implementation of timer functions. Must be completely driven by an
* external clock signal, all that's stored here is timerID, timer type, and * external clock signal, all that's stored here is timerID, timer type, and
* callback. * callback.
*/ */
var JSTimers = { const JSTimers = {
Types: JSTimersExecution.Types,
/**
* Returns a free index if one is available, and the next consecutive index
* otherwise.
*/
_getFreeIndex: function() {
var freeIndex = JSTimersExecution.timerIDs.indexOf(null);
if (freeIndex === -1) {
freeIndex = JSTimersExecution.timerIDs.length;
}
return freeIndex;
},
/** /**
* @param {function} func Callback to be invoked after `duration` ms. * @param {function} func Callback to be invoked after `duration` ms.
* @param {number} duration Number of milliseconds. * @param {number} duration Number of milliseconds.
*/ */
setTimeout: function(func, duration, ...args) { setTimeout: function(func: Function, duration: number, ...args?: any): number {
var newID = JSTimersExecution.GUID++; const id = _allocateCallback(() => func.apply(undefined, args),
var freeIndex = JSTimers._getFreeIndex(); JSTimersExecution.Type.setTimeout);
JSTimersExecution.timerIDs[freeIndex] = newID; RCTTiming.createTimer(id, duration || 0, Date.now(), /* recurring */ false);
JSTimersExecution.callbacks[freeIndex] = function() { return id;
return func.apply(undefined, args);
};
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setTimeout;
RCTTiming.createTimer(newID, duration || 0, Date.now(), /** recurring */ false);
return newID;
}, },
/** /**
* @param {function} func Callback to be invoked every `duration` ms. * @param {function} func Callback to be invoked every `duration` ms.
* @param {number} duration Number of milliseconds. * @param {number} duration Number of milliseconds.
*/ */
setInterval: function(func, duration, ...args) { setInterval: function(func: Function, duration: number, ...args?: any): number {
var newID = JSTimersExecution.GUID++; const id = _allocateCallback(() => func.apply(undefined, args),
var freeIndex = JSTimers._getFreeIndex(); JSTimersExecution.Type.setInterval);
JSTimersExecution.timerIDs[freeIndex] = newID; RCTTiming.createTimer(id, duration || 0, Date.now(), /* recurring */ true);
JSTimersExecution.callbacks[freeIndex] = function() { return id;
return func.apply(undefined, args);
};
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setInterval;
RCTTiming.createTimer(newID, duration || 0, Date.now(), /** recurring */ true);
return newID;
}, },
/** /**
* @param {function} func Callback to be invoked before the end of the * @param {function} func Callback to be invoked before the end of the
* current JavaScript execution loop. * current JavaScript execution loop.
*/ */
setImmediate: function(func, ...args) { setImmediate: function(func: Function, ...args?: any) {
var newID = JSTimersExecution.GUID++; const id = _allocateCallback(() => func.apply(undefined, args),
var freeIndex = JSTimers._getFreeIndex(); JSTimersExecution.Type.setImmediate);
JSTimersExecution.timerIDs[freeIndex] = newID; JSTimersExecution.immediates.push(id);
JSTimersExecution.callbacks[freeIndex] = function() { return id;
return func.apply(undefined, args);
};
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setImmediate;
JSTimersExecution.immediates.push(newID);
return newID;
}, },
/** /**
* @param {function} func Callback to be invoked every frame. * @param {function} func Callback to be invoked every frame.
*/ */
requestAnimationFrame: function(func) { requestAnimationFrame: function(func : Function) {
var newID = JSTimersExecution.GUID++; const id = _allocateCallback(func, JSTimersExecution.Type.requestAnimationFrame);
var freeIndex = JSTimers._getFreeIndex(); RCTTiming.createTimer(id, 1, Date.now(), /* recurring */ false);
JSTimersExecution.timerIDs[freeIndex] = newID; return id;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestAnimationFrame;
RCTTiming.createTimer(newID, 1, Date.now(), /** recurring */ false);
return newID;
}, },
/** /**
* @param {function} func Callback to be invoked every frame and provided * @param {function} func Callback to be invoked every frame and provided
* with time remaining in frame. * with time remaining in frame.
*/ */
requestIdleCallback: function(func) { requestIdleCallback: function(func : Function) {
if (JSTimersExecution.requestIdleCallbacks.length === 0) { if (JSTimersExecution.requestIdleCallbacks.length === 0) {
RCTTiming.setSendIdleEvents(true); RCTTiming.setSendIdleEvents(true);
} }
var newID = JSTimersExecution.GUID++; const id = _allocateCallback(func, JSTimersExecution.Type.requestIdleCallback);
var freeIndex = JSTimers._getFreeIndex(); JSTimersExecution.requestIdleCallbacks.push(id);
JSTimersExecution.timerIDs[freeIndex] = newID; return id;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestIdleCallback;
JSTimersExecution.requestIdleCallbacks.push(newID);
return newID;
}, },
cancelIdleCallback: function(timerID) { cancelIdleCallback: function(timerID: number) {
JSTimers._clearTimerID(timerID); _freeCallback(timerID);
var index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID); const index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID);
if (index !== -1) { if (index !== -1) {
JSTimersExecution.requestIdleCallbacks.splice(index, 1); JSTimersExecution.requestIdleCallbacks.splice(index, 1);
} }
@ -126,43 +127,24 @@ var JSTimers = {
} }
}, },
clearTimeout: function(timerID) { clearTimeout: function(timerID: number) {
JSTimers._clearTimerID(timerID); _freeCallback(timerID);
}, },
clearInterval: function(timerID) { clearInterval: function(timerID: number) {
JSTimers._clearTimerID(timerID); _freeCallback(timerID);
}, },
clearImmediate: function(timerID) { clearImmediate: function(timerID: number) {
JSTimers._clearTimerID(timerID); _freeCallback(timerID);
var index = JSTimersExecution.immediates.indexOf(timerID); const index = JSTimersExecution.immediates.indexOf(timerID);
if (index !== -1) { if (index !== -1) {
JSTimersExecution.immediates.splice(index, 1); JSTimersExecution.immediates.splice(index, 1);
} }
}, },
cancelAnimationFrame: function(timerID) { cancelAnimationFrame: function(timerID: number) {
JSTimers._clearTimerID(timerID); _freeCallback(timerID);
},
_clearTimerID: function(timerID) {
// JSTimersExecution.timerIDs contains nulls after timers have been removed;
// ignore nulls upfront so indexOf doesn't find them
if (timerID == null) {
return;
}
var index = JSTimersExecution.timerIDs.indexOf(timerID);
// See corresponding comment in `callTimers` for reasoning behind this
if (index !== -1) {
JSTimersExecution._clearIndex(index);
var type = JSTimersExecution.types[index];
if (type !== JSTimersExecution.Type.setImmediate &&
type !== JSTimersExecution.Type.requestIdleCallback) {
RCTTiming.deleteTimer(timerID);
}
}
}, },
}; };

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule JSTimersExecution * @providesModule JSTimersExecution
* @flow
*/ */
'use strict'; 'use strict';
@ -39,35 +40,43 @@ const JSTimersExecution = {
requestIdleCallback: null, requestIdleCallback: null,
}), }),
// Parallel arrays: // Parallel arrays
callbacks: [], callbacks: [],
types: [], types: [],
timerIDs: [], timerIDs: [],
immediates: [], immediates: [],
requestIdleCallbacks: [], requestIdleCallbacks: [],
errors: (null : ?[Error]),
/** /**
* Calls the callback associated with the ID. Also unregister that callback * Calls the callback associated with the ID. Also unregister that callback
* if it was a one time timer (setTimeout), and not unregister it if it was * if it was a one time timer (setTimeout), and not unregister it if it was
* recurring (setInterval). * recurring (setInterval).
*/ */
callTimer(timerID, frameTime) { callTimer(timerID: number, frameTime: number) {
warning( warning(
timerID <= JSTimersExecution.GUID, timerID <= JSTimersExecution.GUID,
'Tried to call timer with ID %s but no such timer exists.', 'Tried to call timer with ID %s but no such timer exists.',
timerID timerID
); );
const timerIndex = JSTimersExecution.timerIDs.indexOf(timerID);
// timerIndex of -1 means that no timer with that ID exists. There are // timerIndex of -1 means that no timer with that ID exists. There are
// two situations when this happens, when a garbage timer ID was given // two situations when this happens, when a garbage timer ID was given
// and when a previously existing timer was deleted before this callback // and when a previously existing timer was deleted before this callback
// fired. In both cases we want to ignore the timer id, but in the former // fired. In both cases we want to ignore the timer id, but in the former
// case we warn as well. // case we warn as well.
const timerIndex = JSTimersExecution.timerIDs.indexOf(timerID);
if (timerIndex === -1) { if (timerIndex === -1) {
return; return;
} }
const type = JSTimersExecution.types[timerIndex]; const type = JSTimersExecution.types[timerIndex];
const callback = JSTimersExecution.callbacks[timerIndex]; const callback = JSTimersExecution.callbacks[timerIndex];
if (!callback || !type) {
console.error('No callback found for timerID ' + timerID);
return;
}
// Clear the metadata // Clear the metadata
if (type === JSTimersExecution.Type.setTimeout || if (type === JSTimersExecution.Type.setTimeout ||
@ -83,8 +92,7 @@ const JSTimersExecution = {
type === JSTimersExecution.Type.setImmediate) { type === JSTimersExecution.Type.setImmediate) {
callback(); callback();
} else if (type === JSTimersExecution.Type.requestAnimationFrame) { } else if (type === JSTimersExecution.Type.requestAnimationFrame) {
const currentTime = performanceNow(); callback(performanceNow());
callback(currentTime);
} else if (type === JSTimersExecution.Type.requestIdleCallback) { } else if (type === JSTimersExecution.Type.requestIdleCallback) {
callback({ callback({
timeRemaining: function() { timeRemaining: function() {
@ -96,27 +104,31 @@ const JSTimersExecution = {
}); });
} else { } else {
console.error('Tried to call a callback with invalid type: ' + type); console.error('Tried to call a callback with invalid type: ' + type);
return;
} }
} catch (e) { } catch (e) {
// Don't rethrow so that we can run every other timer. // Don't rethrow so that we can run all timers.
JSTimersExecution.errors = JSTimersExecution.errors || []; if (!JSTimersExecution.errors) {
JSTimersExecution.errors = [e];
} else {
JSTimersExecution.errors.push(e); JSTimersExecution.errors.push(e);
} }
}
}, },
/** /**
* This is called from the native side. We are passed an array of timerIDs, * This is called from the native side. We are passed an array of timerIDs,
* and * and
*/ */
callTimers(timerIDs) { callTimers(timerIDs: [number]) {
invariant( invariant(
timerIDs.length !== 0, timerIDs.length !== 0,
'Cannot call `callTimers` with an empty list of IDs.' 'Cannot call `callTimers` with an empty list of IDs.'
); );
JSTimersExecution.errors = null; JSTimersExecution.errors = null;
timerIDs.forEach((id) => { JSTimersExecution.callTimer(id); }); for (let i = 0; i < timerIDs.length; i++) {
JSTimersExecution.callTimer(timerIDs[i], 0);
}
const errors = JSTimersExecution.errors; const errors = JSTimersExecution.errors;
if (errors) { if (errors) {
@ -135,15 +147,12 @@ const JSTimersExecution = {
} }
}, },
callIdleCallbacks: function(frameTime) { callIdleCallbacks: function(frameTime: number) {
const { Timing } = require('NativeModules');
if (FRAME_DURATION - (performanceNow() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE) { if (FRAME_DURATION - (performanceNow() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE) {
return; return;
} }
JSTimersExecution.errors = null; JSTimersExecution.errors = null;
if (JSTimersExecution.requestIdleCallbacks.length > 0) { if (JSTimersExecution.requestIdleCallbacks.length > 0) {
const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice(); const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice();
JSTimersExecution.requestIdleCallbacks = []; JSTimersExecution.requestIdleCallbacks = [];
@ -154,6 +163,7 @@ const JSTimersExecution = {
} }
if (JSTimersExecution.requestIdleCallbacks.length === 0) { if (JSTimersExecution.requestIdleCallbacks.length === 0) {
const { Timing } = require('NativeModules');
Timing.setSendIdleEvents(false); Timing.setSendIdleEvents(false);
} }
@ -180,7 +190,7 @@ const JSTimersExecution = {
// Use for loop rather than forEach as per @vjeux's advice // Use for loop rather than forEach as per @vjeux's advice
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051 // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
for (let i = 0; i < passImmediates.length; ++i) { for (let i = 0; i < passImmediates.length; ++i) {
JSTimersExecution.callTimer(passImmediates[i]); JSTimersExecution.callTimer(passImmediates[i], 0);
} }
} }
@ -206,7 +216,7 @@ const JSTimersExecution = {
/** /**
* Called from native (in development) when environment times are out-of-sync. * Called from native (in development) when environment times are out-of-sync.
*/ */
emitTimeDriftWarning(warningMessage) { emitTimeDriftWarning(warningMessage: string) {
if (hasEmittedTimeDriftWarning) { if (hasEmittedTimeDriftWarning) {
return; return;
} }
@ -214,7 +224,7 @@ const JSTimersExecution = {
console.warn(warningMessage); console.warn(warningMessage);
}, },
_clearIndex(i) { _clearIndex(i: number) {
JSTimersExecution.timerIDs[i] = null; JSTimersExecution.timerIDs[i] = null;
JSTimersExecution.callbacks[i] = null; JSTimersExecution.callbacks[i] = null;
JSTimersExecution.types[i] = null; JSTimersExecution.types[i] = null;