mirror of
https://github.com/status-im/react-native.git
synced 2025-01-16 12:34:17 +00:00
7b718b03eb
Summary: By default we run the the JS display link, even if there are no modules listening. Given that most listeners will be lazily constructed, let's make it paused by default. Since RCTTiming almost never unpauses due to some long-lived timers, implement a sleep timer that pauses the displaylink but uses an NSTimer to wake up in time. Reviewed By: mhorowitz Differential Revision: D3235044 fbshipit-source-id: 4a340fea552ada1bd8bc0d83b596a7df6f992387
150 lines
4.0 KiB
Objective-C
150 lines
4.0 KiB
Objective-C
/**
|
|
* 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.
|
|
*/
|
|
|
|
#import "RCTDisplayLink.h"
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <QuartzCore/CADisplayLink.h>
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTBridgeModule.h"
|
|
#import "RCTFrameUpdate.h"
|
|
#import "RCTModuleData.h"
|
|
#import "RCTProfile.h"
|
|
|
|
#define RCTAssertRunLoop() \
|
|
RCTAssert(_runLoop == [NSRunLoop currentRunLoop], \
|
|
@"This method must be called on the CADisplayLink run loop")
|
|
|
|
@implementation RCTDisplayLink
|
|
{
|
|
CADisplayLink *_jsDisplayLink;
|
|
NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
|
|
NSRunLoop *_runLoop;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
_frameUpdateObservers = [NSMutableSet new];
|
|
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
|
|
withModuleData:(RCTModuleData *)moduleData
|
|
{
|
|
if (![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] ||
|
|
[_frameUpdateObservers containsObject:moduleData]) {
|
|
return;
|
|
}
|
|
|
|
[_frameUpdateObservers addObject:moduleData];
|
|
|
|
// Don't access the module instance via moduleData, as this will cause deadlock
|
|
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
|
|
__weak typeof(self) weakSelf = self;
|
|
observer.pauseCallback = ^{
|
|
typeof(self) strongSelf = weakSelf;
|
|
if (!strongSelf) {
|
|
return;
|
|
}
|
|
|
|
CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop];
|
|
if (!cfRunLoop) {
|
|
return;
|
|
}
|
|
|
|
if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) {
|
|
[weakSelf updateJSDisplayLinkState];
|
|
} else {
|
|
CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{
|
|
[weakSelf updateJSDisplayLinkState];
|
|
});
|
|
CFRunLoopWakeUp(cfRunLoop);
|
|
}
|
|
};
|
|
|
|
// Assuming we're paused right now, we only need to update the display link's state
|
|
// when the new observer is not paused. If it not paused, the observer will immediately
|
|
// start receiving updates anyway.
|
|
if (![observer isPaused] && _runLoop) {
|
|
CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{
|
|
[self updateJSDisplayLinkState];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)addToRunLoop:(NSRunLoop *)runLoop
|
|
{
|
|
_runLoop = runLoop;
|
|
[_jsDisplayLink addToRunLoop:runLoop forMode:NSRunLoopCommonModes];
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
[_jsDisplayLink invalidate];
|
|
}
|
|
|
|
- (void)dispatchBlock:(dispatch_block_t)block
|
|
queue:(dispatch_queue_t)queue
|
|
{
|
|
if (queue == RCTJSThread) {
|
|
block();
|
|
} else if (queue) {
|
|
dispatch_async(queue, block);
|
|
}
|
|
}
|
|
|
|
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
|
|
{
|
|
RCTAssertRunLoop();
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil);
|
|
|
|
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
|
for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
|
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
|
if (!observer.paused) {
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
[self dispatchBlock:^{
|
|
RCTProfileEndFlowEvent();
|
|
[observer didUpdateFrame:frameUpdate];
|
|
} queue:moduleData.methodQueue];
|
|
}
|
|
}
|
|
|
|
[self updateJSDisplayLinkState];
|
|
|
|
RCTProfileImmediateEvent(RCTProfileTagAlways, @"JS Thread Tick", displayLink.timestamp, 'g');
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call", nil);
|
|
}
|
|
|
|
- (void)updateJSDisplayLinkState
|
|
{
|
|
RCTAssertRunLoop();
|
|
|
|
BOOL pauseDisplayLink = YES;
|
|
for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
|
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
|
if (!observer.paused) {
|
|
pauseDisplayLink = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_jsDisplayLink.paused = pauseDisplayLink;
|
|
}
|
|
|
|
@end
|