1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | #import "RCTDisplayLink.h"
|
9 |
|
10 | #import <Foundation/Foundation.h>
|
11 | #import <QuartzCore/CADisplayLink.h>
|
12 |
|
13 | #import "RCTAssert.h"
|
14 | #import "RCTBridgeModule.h"
|
15 | #import "RCTFrameUpdate.h"
|
16 | #import "RCTModuleData.h"
|
17 | #import "RCTProfile.h"
|
18 |
|
19 | #define RCTAssertRunLoop() \
|
20 | RCTAssert(_runLoop == [NSRunLoop currentRunLoop], \
|
21 | @"This method must be called on the CADisplayLink run loop")
|
22 |
|
23 | @implementation RCTDisplayLink
|
24 | {
|
25 | CADisplayLink *_jsDisplayLink;
|
26 | NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
|
27 | NSRunLoop *_runLoop;
|
28 | }
|
29 |
|
30 | - (instancetype)init
|
31 | {
|
32 | if ((self = [super init])) {
|
33 | _frameUpdateObservers = [NSMutableSet new];
|
34 | _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
|
35 | }
|
36 |
|
37 | return self;
|
38 | }
|
39 |
|
40 | - (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
|
41 | withModuleData:(RCTModuleData *)moduleData
|
42 | {
|
43 | if (![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] ||
|
44 | [_frameUpdateObservers containsObject:moduleData]) {
|
45 | return;
|
46 | }
|
47 |
|
48 | [_frameUpdateObservers addObject:moduleData];
|
49 |
|
50 | // Don't access the module instance via moduleData, as this will cause deadlock
|
51 | id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
|
52 | __weak typeof(self) weakSelf = self;
|
53 | observer.pauseCallback = ^{
|
54 | typeof(self) strongSelf = weakSelf;
|
55 | if (!strongSelf) {
|
56 | return;
|
57 | }
|
58 |
|
59 | CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop];
|
60 | if (!cfRunLoop) {
|
61 | return;
|
62 | }
|
63 |
|
64 | if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) {
|
65 | [weakSelf updateJSDisplayLinkState];
|
66 | } else {
|
67 | CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{
|
68 | [weakSelf updateJSDisplayLinkState];
|
69 | });
|
70 | CFRunLoopWakeUp(cfRunLoop);
|
71 | }
|
72 | };
|
73 |
|
74 | // Assuming we're paused right now, we only need to update the display link's state
|
75 | // when the new observer is not paused. If it not paused, the observer will immediately
|
76 | // start receiving updates anyway.
|
77 | if (![observer isPaused] && _runLoop) {
|
78 | CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{
|
79 | [self updateJSDisplayLinkState];
|
80 | });
|
81 | }
|
82 | }
|
83 |
|
84 | - (void)addToRunLoop:(NSRunLoop *)runLoop
|
85 | {
|
86 | _runLoop = runLoop;
|
87 | [_jsDisplayLink addToRunLoop:runLoop forMode:NSRunLoopCommonModes];
|
88 | }
|
89 |
|
90 | - (void)dealloc
|
91 | {
|
92 | [self invalidate];
|
93 | }
|
94 |
|
95 | - (void)invalidate
|
96 | {
|
97 | [_jsDisplayLink invalidate];
|
98 | }
|
99 |
|
100 | - (void)dispatchBlock:(dispatch_block_t)block
|
101 | queue:(dispatch_queue_t)queue
|
102 | {
|
103 | if (queue == RCTJSThread) {
|
104 | block();
|
105 | } else if (queue) {
|
106 | dispatch_async(queue, block);
|
107 | }
|
108 | }
|
109 |
|
110 | - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
|
111 | {
|
112 | RCTAssertRunLoop();
|
113 |
|
114 | RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil);
|
115 |
|
116 | RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
117 | for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
118 | id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
119 | if (!observer.paused) {
|
120 | if (moduleData.methodQueue) {
|
121 | RCTProfileBeginFlowEvent();
|
122 | [self dispatchBlock:^{
|
123 | RCTProfileEndFlowEvent();
|
124 | [observer didUpdateFrame:frameUpdate];
|
125 | } queue:moduleData.methodQueue];
|
126 | } else {
|
127 | [observer didUpdateFrame:frameUpdate];
|
128 | }
|
129 | }
|
130 | }
|
131 |
|
132 | [self updateJSDisplayLinkState];
|
133 |
|
134 | RCTProfileImmediateEvent(RCTProfileTagAlways, @"JS Thread Tick", displayLink.timestamp, 'g');
|
135 |
|
136 | RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call");
|
137 | }
|
138 |
|
139 | - (void)updateJSDisplayLinkState
|
140 | {
|
141 | RCTAssertRunLoop();
|
142 |
|
143 | BOOL pauseDisplayLink = YES;
|
144 | for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
145 | id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
146 | if (!observer.paused) {
|
147 | pauseDisplayLink = NO;
|
148 | break;
|
149 | }
|
150 | }
|
151 |
|
152 | _jsDisplayLink.paused = pauseDisplayLink;
|
153 | }
|
154 |
|
155 | @end
|