1 | #import "REANodesManager.h"
|
2 |
|
3 | #import <React/RCTConvert.h>
|
4 |
|
5 | #import "Nodes/REANode.h"
|
6 | #import "Nodes/REAPropsNode.h"
|
7 | #import "Nodes/REAStyleNode.h"
|
8 | #import "Nodes/REATransformNode.h"
|
9 | #import "Nodes/REAValueNode.h"
|
10 | #import "Nodes/REABlockNode.h"
|
11 | #import "Nodes/REACondNode.h"
|
12 | #import "Nodes/REAOperatorNode.h"
|
13 | #import "Nodes/REASetNode.h"
|
14 | #import "Nodes/READebugNode.h"
|
15 | #import "Nodes/REAClockNodes.h"
|
16 | #import "Nodes/REAJSCallNode.h"
|
17 | #import "Nodes/REABezierNode.h"
|
18 | #import "Nodes/REAEventNode.h"
|
19 | #import "REAModule.h"
|
20 | #import "Nodes/REAAlwaysNode.h"
|
21 | #import "Nodes/REAConcatNode.h"
|
22 | #import "Nodes/REAParamNode.h"
|
23 | #import "Nodes/REAFunctionNode.h"
|
24 | #import "Nodes/REACallFuncNode.h"
|
25 |
|
26 | // Interface below has been added in order to use private methods of RCTUIManager,
|
27 | // RCTUIManager#UpdateView is a React Method which is exported to JS but in
|
28 | // Objective-C it stays private
|
29 | // RCTUIManager#setNeedsLayout is a method which updated layout only which
|
30 | // in its turn will trigger relayout if no batch has been activated
|
31 |
|
32 | @interface RCTUIManager ()
|
33 |
|
34 | - (void)updateView:(nonnull NSNumber *)reactTag
|
35 | viewName:(NSString *)viewName
|
36 | props:(NSDictionary *)props;
|
37 |
|
38 | - (void)setNeedsLayout;
|
39 |
|
40 | @end
|
41 |
|
42 |
|
43 | @implementation REANodesManager
|
44 | {
|
45 | NSMutableDictionary<REANodeID, REANode *> *_nodes;
|
46 | NSMapTable<NSString *, REANode *> *_eventMapping;
|
47 | NSMutableArray<id<RCTEvent>> *_eventQueue;
|
48 | CADisplayLink *_displayLink;
|
49 | REAUpdateContext *_updateContext;
|
50 | BOOL _wantRunUpdates;
|
51 | BOOL _processingDirectEvent;
|
52 | NSMutableArray<REAOnAnimationCallback> *_onAnimationCallbacks;
|
53 | NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
|
54 | }
|
55 |
|
56 | - (instancetype)initWithModule:(REAModule *)reanimatedModule
|
57 | uiManager:(RCTUIManager *)uiManager
|
58 | {
|
59 | if ((self = [super init])) {
|
60 | _reanimatedModule = reanimatedModule;
|
61 | _uiManager = uiManager;
|
62 | _nodes = [NSMutableDictionary new];
|
63 | _eventMapping = [NSMapTable strongToWeakObjectsMapTable];
|
64 | _eventQueue = [NSMutableArray new];
|
65 | _updateContext = [REAUpdateContext new];
|
66 | _wantRunUpdates = NO;
|
67 | _onAnimationCallbacks = [NSMutableArray new];
|
68 | _operationsInBatch = [NSMutableArray new];
|
69 | }
|
70 | return self;
|
71 | }
|
72 |
|
73 | - (void)invalidate
|
74 | {
|
75 | [self stopUpdatingOnAnimationFrame];
|
76 | }
|
77 |
|
78 | - (void)operationsBatchDidComplete
|
79 | {
|
80 | if (_displayLink) {
|
81 | // if display link is set it means some of the operations that have run as a part of the batch
|
82 | // requested updates. We want updates to be run in the same frame as in which operations have
|
83 | // been scheduled as it may mean the new view has just been mounted and expects its initial
|
84 | // props to be calculated.
|
85 | // Unfortunately if the operation has just scheduled animation callback it won't run until the
|
86 | // next frame, so it's being triggered manually.
|
87 | _wantRunUpdates = YES;
|
88 | [self performOperations];
|
89 | }
|
90 | }
|
91 |
|
92 | - (REANode *)findNodeByID:(REANodeID)nodeID
|
93 | {
|
94 | return _nodes[nodeID];
|
95 | }
|
96 |
|
97 | - (void)postOnAnimation:(REAOnAnimationCallback)clb
|
98 | {
|
99 | [_onAnimationCallbacks addObject:clb];
|
100 | [self startUpdatingOnAnimationFrame];
|
101 | }
|
102 |
|
103 | - (void)postRunUpdatesAfterAnimation
|
104 | {
|
105 | _wantRunUpdates = YES;
|
106 | if (!_processingDirectEvent) {
|
107 | [self startUpdatingOnAnimationFrame];
|
108 | }
|
109 | }
|
110 |
|
111 | - (void)startUpdatingOnAnimationFrame
|
112 | {
|
113 | if (!_displayLink) {
|
114 | // Setting _currentAnimationTimestamp here is connected with manual triggering of performOperations
|
115 | // in operationsBatchDidComplete. If new node has been created and clock has not been started,
|
116 | // _displayLink won't be initialized soon enough and _displayLink.timestamp will be 0.
|
117 | // However, CADisplayLink is using CACurrentMediaTime so if there's need to perform one more
|
118 | // evaluation, it could be used it here. In usual case, CACurrentMediaTime is not being used in
|
119 | // favor of setting it with _displayLink.timestamp in onAnimationFrame method.
|
120 | _currentAnimationTimestamp = CACurrentMediaTime();
|
121 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)];
|
122 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
123 | }
|
124 | }
|
125 |
|
126 | - (void)stopUpdatingOnAnimationFrame
|
127 | {
|
128 | if (_displayLink) {
|
129 | [_displayLink invalidate];
|
130 | _displayLink = nil;
|
131 | }
|
132 | }
|
133 |
|
134 | - (void)onAnimationFrame:(CADisplayLink *)displayLink
|
135 | {
|
136 | // We process all enqueued events first
|
137 | _currentAnimationTimestamp = _displayLink.timestamp;
|
138 | for (NSUInteger i = 0; i < _eventQueue.count; i++) {
|
139 | id<RCTEvent> event = _eventQueue[i];
|
140 | [self processEvent:event];
|
141 | }
|
142 | [_eventQueue removeAllObjects];
|
143 |
|
144 | NSArray<REAOnAnimationCallback> *callbacks = _onAnimationCallbacks;
|
145 | _onAnimationCallbacks = [NSMutableArray new];
|
146 |
|
147 | // When one of the callbacks would postOnAnimation callback we don't want
|
148 | // to process it until the next frame. This is why we cpy the array before
|
149 | // we iterate over it
|
150 | for (REAOnAnimationCallback block in callbacks) {
|
151 | block(displayLink);
|
152 | }
|
153 |
|
154 | [self performOperations];
|
155 |
|
156 | if (_onAnimationCallbacks.count == 0) {
|
157 | [self stopUpdatingOnAnimationFrame];
|
158 | }
|
159 | }
|
160 |
|
161 | - (void)performOperations
|
162 | {
|
163 | if (_wantRunUpdates) {
|
164 | [REANode runPropUpdates:_updateContext];
|
165 | }
|
166 | if (_operationsInBatch.count != 0) {
|
167 | NSMutableArray<REANativeAnimationOp> *copiedOperationsQueue = _operationsInBatch;
|
168 | _operationsInBatch = [NSMutableArray new];
|
169 | RCTExecuteOnUIManagerQueue(^{
|
170 | for (int i = 0; i < copiedOperationsQueue.count; i++) {
|
171 | copiedOperationsQueue[i](self.uiManager);
|
172 | }
|
173 | [self.uiManager setNeedsLayout];
|
174 | });
|
175 | }
|
176 | _wantRunUpdates = NO;
|
177 | }
|
178 |
|
179 | - (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
|
180 | viewName:(NSString *) viewName
|
181 | nativeProps:(NSMutableDictionary *)nativeProps {
|
182 | [_operationsInBatch addObject:^(RCTUIManager *uiManager) {
|
183 | [uiManager updateView:reactTag viewName:viewName props:nativeProps];
|
184 | }];
|
185 | }
|
186 |
|
187 | - (void)getValue:(REANodeID)nodeID
|
188 | callback:(RCTResponseSenderBlock)callback
|
189 | {
|
190 | id val = _nodes[nodeID].value;
|
191 | if (val) {
|
192 | callback(@[val]);
|
193 | } else {
|
194 | // NULL is not an object and it's not possible to pass it as callback's argument
|
195 | callback(@[[NSNull null]]);
|
196 | }
|
197 | }
|
198 |
|
199 | #pragma mark -- Graph
|
200 |
|
201 | - (void)createNode:(REANodeID)nodeID
|
202 | config:(NSDictionary<NSString *, id> *)config
|
203 | {
|
204 | static NSDictionary *map;
|
205 | static dispatch_once_t mapToken;
|
206 | dispatch_once(&mapToken, ^{
|
207 | map = @{@"props": [REAPropsNode class],
|
208 | @"style": [REAStyleNode class],
|
209 | @"transform": [REATransformNode class],
|
210 | @"value": [REAValueNode class],
|
211 | @"block": [REABlockNode class],
|
212 | @"cond": [REACondNode class],
|
213 | @"op": [REAOperatorNode class],
|
214 | @"set": [REASetNode class],
|
215 | @"debug": [READebugNode class],
|
216 | @"clock": [REAClockNode class],
|
217 | @"clockStart": [REAClockStartNode class],
|
218 | @"clockStop": [REAClockStopNode class],
|
219 | @"clockTest": [REAClockTestNode class],
|
220 | @"call": [REAJSCallNode class],
|
221 | @"bezier": [REABezierNode class],
|
222 | @"event": [REAEventNode class],
|
223 | @"always": [REAAlwaysNode class],
|
224 | @"concat": [REAConcatNode class],
|
225 | @"param": [REAParamNode class],
|
226 | @"func": [REAFunctionNode class],
|
227 | @"callfunc": [REACallFuncNode class],
|
228 | // @"listener": nil,
|
229 | };
|
230 | });
|
231 |
|
232 | NSString *nodeType = [RCTConvert NSString:config[@"type"]];
|
233 |
|
234 | Class nodeClass = map[nodeType];
|
235 | if (!nodeClass) {
|
236 | RCTLogError(@"Animated node type %@ not supported natively", nodeType);
|
237 | return;
|
238 | }
|
239 |
|
240 | REANode *node = [[nodeClass alloc] initWithID:nodeID config:config];
|
241 | node.nodesManager = self;
|
242 | node.updateContext = _updateContext;
|
243 | _nodes[nodeID] = node;
|
244 | }
|
245 |
|
246 | - (void)dropNode:(REANodeID)nodeID
|
247 | {
|
248 | REANode *node = _nodes[nodeID];
|
249 | if (node) {
|
250 | [_nodes removeObjectForKey:nodeID];
|
251 | }
|
252 | }
|
253 |
|
254 | - (void)connectNodes:(nonnull NSNumber *)parentID childID:(nonnull REANodeID)childID
|
255 | {
|
256 | RCTAssertParam(parentID);
|
257 | RCTAssertParam(childID);
|
258 |
|
259 | REANode *parentNode = _nodes[parentID];
|
260 | REANode *childNode = _nodes[childID];
|
261 |
|
262 | RCTAssertParam(childNode);
|
263 |
|
264 | [parentNode addChild:childNode];
|
265 | }
|
266 |
|
267 | - (void)disconnectNodes:(REANodeID)parentID childID:(REANodeID)childID
|
268 | {
|
269 | RCTAssertParam(parentID);
|
270 | RCTAssertParam(childID);
|
271 |
|
272 | REANode *parentNode = _nodes[parentID];
|
273 | REANode *childNode = _nodes[childID];
|
274 |
|
275 | RCTAssertParam(childNode);
|
276 |
|
277 | [parentNode removeChild:childNode];
|
278 | }
|
279 |
|
280 | - (void)connectNodeToView:(REANodeID)nodeID
|
281 | viewTag:(NSNumber *)viewTag
|
282 | viewName:(NSString *)viewName
|
283 | {
|
284 | RCTAssertParam(nodeID);
|
285 | REANode *node = _nodes[nodeID];
|
286 | RCTAssertParam(node);
|
287 |
|
288 | if ([node isKindOfClass:[REAPropsNode class]]) {
|
289 | [(REAPropsNode *)node connectToView:viewTag viewName:viewName];
|
290 | }
|
291 | }
|
292 |
|
293 | - (void)disconnectNodeFromView:(REANodeID)nodeID
|
294 | viewTag:(NSNumber *)viewTag
|
295 | {
|
296 | RCTAssertParam(nodeID);
|
297 | REANode *node = _nodes[nodeID];
|
298 | RCTAssertParam(node);
|
299 |
|
300 | if ([node isKindOfClass:[REAPropsNode class]]) {
|
301 | [(REAPropsNode *)node disconnectFromView:viewTag];
|
302 | }
|
303 | }
|
304 |
|
305 | - (void)attachEvent:(NSNumber *)viewTag
|
306 | eventName:(NSString *)eventName
|
307 | eventNodeID:(REANodeID)eventNodeID
|
308 | {
|
309 | RCTAssertParam(eventNodeID);
|
310 | REANode *eventNode = _nodes[eventNodeID];
|
311 | RCTAssert([eventNode isKindOfClass:[REAEventNode class]], @"Event node is of an invalid type");
|
312 |
|
313 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
314 | viewTag,
|
315 | RCTNormalizeInputEventName(eventName)];
|
316 | RCTAssert([_eventMapping objectForKey:key] == nil, @"Event handler already set for the given view and event type");
|
317 | [_eventMapping setObject:eventNode forKey:key];
|
318 | }
|
319 |
|
320 | - (void)detachEvent:(NSNumber *)viewTag
|
321 | eventName:(NSString *)eventName
|
322 | eventNodeID:(REANodeID)eventNodeID
|
323 | {
|
324 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
325 | viewTag,
|
326 | RCTNormalizeInputEventName(eventName)];
|
327 | [_eventMapping removeObjectForKey:key];
|
328 | }
|
329 |
|
330 | - (void)processEvent:(id<RCTEvent>)event
|
331 | {
|
332 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
333 | event.viewTag,
|
334 | RCTNormalizeInputEventName(event.eventName)];
|
335 | REAEventNode *eventNode = [_eventMapping objectForKey:key];
|
336 | [eventNode processEvent:event];
|
337 | }
|
338 |
|
339 | - (void)processDirectEvent:(id<RCTEvent>)event
|
340 | {
|
341 | _processingDirectEvent = YES;
|
342 | [self processEvent:event];
|
343 | [self performOperations];
|
344 | _processingDirectEvent = NO;
|
345 | }
|
346 |
|
347 | - (BOOL)isDirectEvent:(id<RCTEvent>)event
|
348 | {
|
349 | static NSArray<NSString *> *directEventNames;
|
350 | static dispatch_once_t directEventNamesToken;
|
351 | dispatch_once(&directEventNamesToken, ^{
|
352 | directEventNames = @[
|
353 | @"topContentSizeChange",
|
354 | @"topMomentumScrollBegin",
|
355 | @"topMomentumScrollEnd",
|
356 | @"topScroll",
|
357 | @"topScrollBeginDrag",
|
358 | @"topScrollEndDrag"
|
359 | ];
|
360 | });
|
361 |
|
362 | return [directEventNames containsObject:RCTNormalizeInputEventName(event.eventName)];
|
363 | }
|
364 |
|
365 | - (void)dispatchEvent:(id<RCTEvent>)event
|
366 | {
|
367 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
368 | event.viewTag,
|
369 | RCTNormalizeInputEventName(event.eventName)];
|
370 | REANode *eventNode = [_eventMapping objectForKey:key];
|
371 |
|
372 | if (eventNode != nil) {
|
373 | if ([self isDirectEvent:event]) {
|
374 | // Bypass the event queue/animation frames and process scroll events
|
375 | // immediately to avoid getting out of sync with the scroll position
|
376 | [self processDirectEvent:event];
|
377 | } else {
|
378 | // enqueue node to be processed
|
379 | [_eventQueue addObject:event];
|
380 | [self startUpdatingOnAnimationFrame];
|
381 | }
|
382 | }
|
383 | }
|
384 |
|
385 | - (void)configureProps:(NSSet<NSString *> *)nativeProps
|
386 | uiProps:(NSSet<NSString *> *)uiProps
|
387 | {
|
388 | _uiProps = uiProps;
|
389 | _nativeProps = nativeProps;
|
390 | }
|
391 |
|
392 | - (void)setValueForNodeID:(nonnull NSNumber *)nodeID value:(nonnull NSNumber *)newValue
|
393 | {
|
394 | RCTAssertParam(nodeID);
|
395 |
|
396 | REANode *node = _nodes[nodeID];
|
397 |
|
398 | REAValueNode *valueNode = (REAValueNode *)node;
|
399 | [valueNode setValue:newValue];
|
400 | }
|
401 |
|
402 | @end
|