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() { 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)); fails.push(this.setTimeout(() => this._fail('testClearMulti-2'), 50));
var delayClear = this.setTimeout(() => this._fail('testClearMulti-3'), 50); var delayClear = this.setTimeout(() => this._fail('testClearMulti-3'), 50);
fails.push(this.setTimeout(() => this._fail('testClearMulti-4'), 0)); 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.push(this.setTimeout(() => this._fail('testClearMulti-5'), 10));
fails.forEach((timeout) => this.clearTimeout(timeout)); fails.forEach((timeout) => this.clearTimeout(timeout));
this.setTimeout(() => this.clearTimeout(delayClear), 20); this.setTimeout(() => this.clearTimeout(delayClear), 20);
this.setTimeout(this.testOrdering, 50);
}, },
testOrdering() { testOrdering() {
@ -110,14 +110,14 @@ var TimersTest = React.createClass({
() => this._fail('testOrdering-Anim, setTimeout 0 should happen before ' + () => this._fail('testOrdering-Anim, setTimeout 0 should happen before ' +
'requestAnimationFrame') 'requestAnimationFrame')
); );
var fail50; var fail25;
this.setTimeout(() => this.clearTimeout(fail50), 20); this.setTimeout(() => { this.clearTimeout(fail25); }, 20);
fail50 = this.setTimeout( fail25 = this.setTimeout(
() => this._fail('testOrdering-t50, setTimeout 20 should happen before ' + () => this._fail('testOrdering-t25, setTimeout 20 should happen before ' +
'setTimeout 50'), 'setTimeout 25'),
50 25
); );
this.setTimeout(this.done, 75); this.setTimeout(this.done, 50);
}, },
done() { done() {

View File

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