Fix order of timers called in the same frame

Reviewed By: mmmulani

Differential Revision: D4802858

fbshipit-source-id: 8d8400c20b7e487aea5a0943f91ac7adc2d23108
This commit is contained in:
Pieter De Baets 2017-04-03 18:42:39 -07:00 committed by Facebook Github Bot
parent ecf4c48966
commit e0bd35f76f
2 changed files with 37 additions and 23 deletions

View File

@ -82,17 +82,17 @@ var TimersTest = React.createClass({
},
testClearMulti() {
var fails = [this.setTimeout(() => this._fail('testClearMulti-1'), 20)];
var fails = [];
fails.push(this.setTimeout(() => this._fail('testClearMulti-1'), 20));
fails.push(this.setTimeout(() => this._fail('testClearMulti-2'), 50));
var delayClear = this.setTimeout(() => this._fail('testClearMulti-3'), 50);
fails.push(this.setTimeout(() => this._fail('testClearMulti-4'), 0));
this.setTimeout(this.testOrdering, 100); // Next test interleaved
fails.push(this.setTimeout(() => this._fail('testClearMulti-5'), 10));
fails.forEach((timeout) => this.clearTimeout(timeout));
this.setTimeout(() => this.clearTimeout(delayClear), 20);
this.setTimeout(this.testOrdering, 50);
},
testOrdering() {
@ -110,14 +110,14 @@ var TimersTest = React.createClass({
() => this._fail('testOrdering-Anim, setTimeout 0 should happen before ' +
'requestAnimationFrame')
);
var fail50;
this.setTimeout(() => this.clearTimeout(fail50), 20);
fail50 = this.setTimeout(
() => this._fail('testOrdering-t50, setTimeout 20 should happen before ' +
'setTimeout 50'),
50
var fail25;
this.setTimeout(() => { this.clearTimeout(fail25); }, 20);
fail25 = this.setTimeout(
() => this._fail('testOrdering-t25, setTimeout 20 should happen before ' +
'setTimeout 25'),
25
);
this.setTimeout(this.done, 75);
this.setTimeout(this.done, 50);
},
done() {

View File

@ -10,8 +10,8 @@
#import "RCTTiming.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@ -51,16 +51,20 @@ static const NSTimeInterval kIdleCallbackFrameDeadline = 0.001;
/**
* Returns `YES` if we should invoke the JS callback.
*/
- (BOOL)updateFoundNeedsJSUpdate
- (BOOL)shouldFire:(NSDate *)now
{
if (_target && _target.timeIntervalSinceNow <= 0) {
// The JS Timers will do fine grained calculating of expired timeouts.
_target = _repeats ? [NSDate dateWithTimeIntervalSinceNow:_interval] : nil;
if (_target && [_target timeIntervalSinceDate:now] <= 0) {
return YES;
}
return NO;
}
- (void)reschedule
{
// The JS Timers will do fine grained calculating of expired timeouts.
_target = [NSDate dateWithTimeIntervalSinceNow:_interval];
}
@end
@interface _RCTTimingProxy : NSObject
@ -178,13 +182,11 @@ RCT_EXPORT_MODULE()
- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
NSDate *nextScheduledTarget = [NSDate distantFuture];
NSMutableArray<NSNumber *> *timersToCall = [NSMutableArray new];
NSMutableArray<_RCTTimer *> *timersToCall = [NSMutableArray new];
NSDate *now = [NSDate date]; // compare all the timers to the same base time
for (_RCTTimer *timer in _timers.allValues) {
if ([timer updateFoundNeedsJSUpdate]) {
[timersToCall addObject:timer.callbackID];
}
if (!timer.target) {
[_timers removeObjectForKey:timer.callbackID];
if ([timer shouldFire:now]) {
[timersToCall addObject:timer];
} else {
nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
}
@ -192,12 +194,24 @@ RCT_EXPORT_MODULE()
// Call timers that need to be called
if (timersToCall.count > 0) {
NSArray<NSNumber *> *sortedTimers = [[timersToCall sortedArrayUsingComparator:^(_RCTTimer *a, _RCTTimer *b) {
return [a.target compare:b.target];
}] valueForKey:@"callbackID"];
[_bridge enqueueJSCall:@"JSTimersExecution"
method:@"callTimers"
args:@[timersToCall]
args:@[sortedTimers]
completion:NULL];
}
for (_RCTTimer *timer in timersToCall) {
if (timer.repeats) {
[timer reschedule];
nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
} else {
[_timers removeObjectForKey:timer.callbackID];
}
}
if (_sendIdleEvents) {
NSTimeInterval frameElapsed = (CACurrentMediaTime() - update.timestamp);
if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) {