UNPKG

18 kBPlain TextView Raw
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#import <React/RCTShadowView.h>
26
27// Interface below has been added in order to use private methods of RCTUIManager,
28// RCTUIManager#UpdateView is a React Method which is exported to JS but in
29// Objective-C it stays private
30// RCTUIManager#setNeedsLayout is a method which updated layout only which
31// in its turn will trigger relayout if no batch has been activated
32
33@interface RCTUIManager ()
34
35- (void)updateView:(nonnull NSNumber *)reactTag
36 viewName:(NSString *)viewName
37 props:(NSDictionary *)props;
38
39- (void)setNeedsLayout;
40
41@end
42
43@interface RCTUIManager (SyncUpdates)
44
45- (BOOL)hasEnqueuedUICommands;
46
47- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer;
48
49@end
50
51@implementation RCTUIManager (SyncUpdates)
52
53- (BOOL)hasEnqueuedUICommands
54{
55 // Accessing some private bits of RCTUIManager to provide missing functionality
56 return [[self valueForKey:@"_pendingUIBlocks"] count] > 0;
57}
58
59- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer
60{
61 // before we run uimanager batch complete, we override coordinator observers list
62 // to avoid observers from firing. This is done because we only want the uimanager
63 // related operations to run and not all other operations (including the ones enqueued
64 // by reanimated or native animated modules) from being scheduled. If we were to allow
65 // other modules to execute some logic from this sync uimanager run there is a possibility
66 // that the commands will execute out of order or that we intercept a batch of commands that
67 // those modules may be in a middle of (we verify that batch isn't in progress for uimodule
68 // but can't do the same for all remaining modules)
69
70 // store reference to the observers array
71 id oldObservers = [self.observerCoordinator valueForKey:@"_observers"];
72
73 // temporarily replace observers with a table conatining just nodesmanager (we need
74 // this to capture mounting block)
75 NSHashTable<id<RCTUIManagerObserver>> *soleObserver = [NSHashTable new];
76 [soleObserver addObject:observer];
77 [self.observerCoordinator setValue:soleObserver forKey:@"_observers"];
78
79 // run batch
80 [self batchDidComplete];
81 // restore old observers table
82 [self.observerCoordinator setValue:oldObservers forKey:@"_observers"];
83}
84
85@end
86
87@interface REANodesManager() <RCTUIManagerObserver>
88
89@end
90
91
92@implementation REANodesManager
93{
94 NSMutableDictionary<REANodeID, REANode *> *_nodes;
95 NSMapTable<NSString *, REANode *> *_eventMapping;
96 NSMutableArray<id<RCTEvent>> *_eventQueue;
97 CADisplayLink *_displayLink;
98 REAUpdateContext *_updateContext;
99 BOOL _wantRunUpdates;
100 BOOL _processingDirectEvent;
101 NSMutableArray<REAOnAnimationCallback> *_onAnimationCallbacks;
102 NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
103 BOOL _tryRunBatchUpdatesSynchronously;
104 REAEventHandler _eventHandler;
105 volatile void (^_mounting)(void);
106}
107
108- (instancetype)initWithModule:(REAModule *)reanimatedModule
109 uiManager:(RCTUIManager *)uiManager
110{
111 if ((self = [super init])) {
112 _reanimatedModule = reanimatedModule;
113 _uiManager = uiManager;
114 _nodes = [NSMutableDictionary new];
115 _eventMapping = [NSMapTable strongToWeakObjectsMapTable];
116 _eventQueue = [NSMutableArray new];
117 _updateContext = [REAUpdateContext new];
118 _wantRunUpdates = NO;
119 _onAnimationCallbacks = [NSMutableArray new];
120 _operationsInBatch = [NSMutableArray new];
121 }
122
123 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)];
124 [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
125 [_displayLink setPaused:true];
126 return self;
127}
128
129- (void)invalidate
130{
131 _eventHandler = nil;
132 [self stopUpdatingOnAnimationFrame];
133}
134
135- (void)operationsBatchDidComplete
136{
137 if (![_displayLink isPaused]) {
138 // if display link is set it means some of the operations that have run as a part of the batch
139 // requested updates. We want updates to be run in the same frame as in which operations have
140 // been scheduled as it may mean the new view has just been mounted and expects its initial
141 // props to be calculated.
142 // Unfortunately if the operation has just scheduled animation callback it won't run until the
143 // next frame, so it's being triggered manually.
144 _wantRunUpdates = YES;
145 [self performOperations];
146 }
147}
148
149- (REANode *)findNodeByID:(REANodeID)nodeID
150{
151 return _nodes[nodeID];
152}
153
154- (void)postOnAnimation:(REAOnAnimationCallback)clb
155{
156 [_onAnimationCallbacks addObject:clb];
157 [self startUpdatingOnAnimationFrame];
158}
159
160- (void)postRunUpdatesAfterAnimation
161{
162 _wantRunUpdates = YES;
163 if (!_processingDirectEvent) {
164 [self startUpdatingOnAnimationFrame];
165 }
166}
167
168- (void)registerEventHandler:(REAEventHandler)eventHandler
169{
170 _eventHandler = eventHandler;
171}
172
173- (void)startUpdatingOnAnimationFrame
174{
175 // Setting _currentAnimationTimestamp here is connected with manual triggering of performOperations
176 // in operationsBatchDidComplete. If new node has been created and clock has not been started,
177 // _displayLink won't be initialized soon enough and _displayLink.timestamp will be 0.
178 // However, CADisplayLink is using CACurrentMediaTime so if there's need to perform one more
179 // evaluation, it could be used it here. In usual case, CACurrentMediaTime is not being used in
180 // favor of setting it with _displayLink.timestamp in onAnimationFrame method.
181 _currentAnimationTimestamp = CACurrentMediaTime();
182 [_displayLink setPaused:false];
183}
184
185- (void)stopUpdatingOnAnimationFrame
186{
187 if (_displayLink) {
188 [_displayLink setPaused:true];
189 }
190}
191
192- (void)onAnimationFrame:(CADisplayLink *)displayLink
193{
194 _currentAnimationTimestamp = _displayLink.timestamp;
195
196 // We process all enqueued events first
197 for (NSUInteger i = 0; i < _eventQueue.count; i++) {
198 id<RCTEvent> event = _eventQueue[i];
199 [self processEvent:event];
200 }
201 [_eventQueue removeAllObjects];
202
203 NSArray<REAOnAnimationCallback> *callbacks = _onAnimationCallbacks;
204 _onAnimationCallbacks = [NSMutableArray new];
205
206 // When one of the callbacks would postOnAnimation callback we don't want
207 // to process it until the next frame. This is why we cpy the array before
208 // we iterate over it
209 for (REAOnAnimationCallback block in callbacks) {
210 block(displayLink);
211 }
212
213 [self performOperations];
214
215 if (_onAnimationCallbacks.count == 0) {
216 [self stopUpdatingOnAnimationFrame];
217 }
218}
219
220- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block {
221 RCTAssert(_mounting == nil, @"Mouting block is expected to not be set");
222 _mounting = block;
223 return YES;
224}
225
226- (void)performOperations
227{
228 if (_wantRunUpdates) {
229 [REANode runPropUpdates:_updateContext];
230 }
231 if (_operationsInBatch.count != 0) {
232 NSMutableArray<REANativeAnimationOp> *copiedOperationsQueue = _operationsInBatch;
233 _operationsInBatch = [NSMutableArray new];
234
235 BOOL trySynchronously = _tryRunBatchUpdatesSynchronously;
236 _tryRunBatchUpdatesSynchronously = NO;
237
238 __weak typeof(self) weakSelf = self;
239 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
240 RCTExecuteOnUIManagerQueue(^{
241 __typeof__(self) strongSelf = weakSelf;
242 if (strongSelf == nil) {
243 return;
244 }
245 BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager hasEnqueuedUICommands];
246
247 if (!canUpdateSynchronously) {
248 dispatch_semaphore_signal(semaphore);
249 }
250
251 for (int i = 0; i < copiedOperationsQueue.count; i++) {
252 copiedOperationsQueue[i](strongSelf.uiManager);
253 }
254
255 if (canUpdateSynchronously) {
256 [strongSelf.uiManager runSyncUIUpdatesWithObserver:self];
257 dispatch_semaphore_signal(semaphore);
258 }
259 //In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event
260 //to observers because some components (e.g. TextInput) update their UIViews only on that event.
261 [strongSelf.uiManager setNeedsLayout];
262 });
263 if (trySynchronously) {
264 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
265 }
266
267 if (_mounting) {
268 _mounting();
269 _mounting = nil;
270 }
271 }
272 _wantRunUpdates = NO;
273}
274
275- (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
276 viewName:(NSString *) viewName
277 nativeProps:(NSMutableDictionary *)nativeProps
278 trySynchronously:(BOOL)trySync {
279 if (trySync) {
280 _tryRunBatchUpdatesSynchronously = YES;
281 }
282 [_operationsInBatch addObject:^(RCTUIManager *uiManager) {
283 [uiManager updateView:reactTag viewName:viewName props:nativeProps];
284 }];
285}
286
287- (void)getValue:(REANodeID)nodeID
288 callback:(RCTResponseSenderBlock)callback
289{
290 id val = _nodes[nodeID].value;
291 if (val) {
292 callback(@[val]);
293 } else {
294 // NULL is not an object and it's not possible to pass it as callback's argument
295 callback(@[[NSNull null]]);
296 }
297}
298
299#pragma mark -- Graph
300
301- (void)createNode:(REANodeID)nodeID
302 config:(NSDictionary<NSString *, id> *)config
303{
304 static NSDictionary *map;
305 static dispatch_once_t mapToken;
306 dispatch_once(&mapToken, ^{
307 map = @{@"props": [REAPropsNode class],
308 @"style": [REAStyleNode class],
309 @"transform": [REATransformNode class],
310 @"value": [REAValueNode class],
311 @"block": [REABlockNode class],
312 @"cond": [REACondNode class],
313 @"op": [REAOperatorNode class],
314 @"set": [REASetNode class],
315 @"debug": [READebugNode class],
316 @"clock": [REAClockNode class],
317 @"clockStart": [REAClockStartNode class],
318 @"clockStop": [REAClockStopNode class],
319 @"clockTest": [REAClockTestNode class],
320 @"call": [REAJSCallNode class],
321 @"bezier": [REABezierNode class],
322 @"event": [REAEventNode class],
323 @"always": [REAAlwaysNode class],
324 @"concat": [REAConcatNode class],
325 @"param": [REAParamNode class],
326 @"func": [REAFunctionNode class],
327 @"callfunc": [REACallFuncNode class]
328// @"listener": nil,
329 };
330 });
331
332 NSString *nodeType = [RCTConvert NSString:config[@"type"]];
333
334 Class nodeClass = map[nodeType];
335 if (!nodeClass) {
336 RCTLogError(@"Animated node type %@ not supported natively", nodeType);
337 return;
338 }
339
340 REANode *node = [[nodeClass alloc] initWithID:nodeID config:config];
341 node.nodesManager = self;
342 node.updateContext = _updateContext;
343 _nodes[nodeID] = node;
344}
345
346- (void)dropNode:(REANodeID)nodeID
347{
348 REANode *node = _nodes[nodeID];
349 if (node) {
350 [node onDrop];
351 [_nodes removeObjectForKey:nodeID];
352 }
353}
354
355- (void)connectNodes:(nonnull NSNumber *)parentID childID:(nonnull REANodeID)childID
356{
357 RCTAssertParam(parentID);
358 RCTAssertParam(childID);
359
360 REANode *parentNode = _nodes[parentID];
361 REANode *childNode = _nodes[childID];
362
363 RCTAssertParam(childNode);
364
365 [parentNode addChild:childNode];
366}
367
368- (void)disconnectNodes:(REANodeID)parentID childID:(REANodeID)childID
369{
370 RCTAssertParam(parentID);
371 RCTAssertParam(childID);
372
373 REANode *parentNode = _nodes[parentID];
374 REANode *childNode = _nodes[childID];
375
376 RCTAssertParam(childNode);
377
378 [parentNode removeChild:childNode];
379}
380
381- (void)connectNodeToView:(REANodeID)nodeID
382 viewTag:(NSNumber *)viewTag
383 viewName:(NSString *)viewName
384{
385 RCTAssertParam(nodeID);
386 REANode *node = _nodes[nodeID];
387 RCTAssertParam(node);
388
389 if ([node isKindOfClass:[REAPropsNode class]]) {
390 [(REAPropsNode *)node connectToView:viewTag viewName:viewName];
391 }
392}
393
394- (void)disconnectNodeFromView:(REANodeID)nodeID
395 viewTag:(NSNumber *)viewTag
396{
397 RCTAssertParam(nodeID);
398 REANode *node = _nodes[nodeID];
399 RCTAssertParam(node);
400
401 if ([node isKindOfClass:[REAPropsNode class]]) {
402 [(REAPropsNode *)node disconnectFromView:viewTag];
403 }
404}
405
406- (void)attachEvent:(NSNumber *)viewTag
407 eventName:(NSString *)eventName
408 eventNodeID:(REANodeID)eventNodeID
409{
410 RCTAssertParam(eventNodeID);
411 REANode *eventNode = _nodes[eventNodeID];
412 RCTAssert([eventNode isKindOfClass:[REAEventNode class]], @"Event node is of an invalid type");
413
414 NSString *key = [NSString stringWithFormat:@"%@%@",
415 viewTag,
416 RCTNormalizeInputEventName(eventName)];
417 RCTAssert([_eventMapping objectForKey:key] == nil, @"Event handler already set for the given view and event type");
418 [_eventMapping setObject:eventNode forKey:key];
419}
420
421- (void)detachEvent:(NSNumber *)viewTag
422 eventName:(NSString *)eventName
423 eventNodeID:(REANodeID)eventNodeID
424{
425 NSString *key = [NSString stringWithFormat:@"%@%@",
426 viewTag,
427 RCTNormalizeInputEventName(eventName)];
428 [_eventMapping removeObjectForKey:key];
429}
430
431- (void)processEvent:(id<RCTEvent>)event
432{
433 NSString *key = [NSString stringWithFormat:@"%@%@",
434 event.viewTag,
435 RCTNormalizeInputEventName(event.eventName)];
436 REAEventNode *eventNode = [_eventMapping objectForKey:key];
437 [eventNode processEvent:event];
438}
439
440- (void)processDirectEvent:(id<RCTEvent>)event
441{
442 _processingDirectEvent = YES;
443 [self processEvent:event];
444 [self performOperations];
445 _processingDirectEvent = NO;
446}
447
448- (BOOL)isDirectEvent:(id<RCTEvent>)event
449{
450 static NSArray<NSString *> *directEventNames;
451 static dispatch_once_t directEventNamesToken;
452 dispatch_once(&directEventNamesToken, ^{
453 directEventNames = @[
454 @"topContentSizeChange",
455 @"topMomentumScrollBegin",
456 @"topMomentumScrollEnd",
457 @"topScroll",
458 @"topScrollBeginDrag",
459 @"topScrollEndDrag"
460 ];
461 });
462
463 return [directEventNames containsObject:RCTNormalizeInputEventName(event.eventName)];
464}
465
466- (void)dispatchEvent:(id<RCTEvent>)event
467{
468 NSString *key = [NSString stringWithFormat:@"%@%@",
469 event.viewTag,
470 RCTNormalizeInputEventName(event.eventName)];
471
472 NSString *eventHash = [NSString stringWithFormat:@"%@%@",
473 event.viewTag,
474 event.eventName];
475
476 if (_eventHandler != nil) {
477 __weak REAEventHandler eventHandler = _eventHandler;
478 __weak typeof(self) weakSelf = self;
479 RCTExecuteOnMainQueue(^void(){
480 __typeof__(self) strongSelf = weakSelf;
481 if (strongSelf == nil) {
482 return;
483 }
484 eventHandler(eventHash, event);
485 if ([strongSelf isDirectEvent:event]) {
486 [strongSelf performOperations];
487 }
488 });
489 }
490
491 REANode *eventNode = [_eventMapping objectForKey:key];
492
493 if (eventNode != nil) {
494 if ([self isDirectEvent:event]) {
495 // Bypass the event queue/animation frames and process scroll events
496 // immediately to avoid getting out of sync with the scroll position
497 [self processDirectEvent:event];
498 } else {
499 // enqueue node to be processed
500 [_eventQueue addObject:event];
501 [self startUpdatingOnAnimationFrame];
502 }
503 }
504}
505
506- (void)configureProps:(NSSet<NSString *> *)nativeProps
507 uiProps:(NSSet<NSString *> *)uiProps
508{
509 _uiProps = uiProps;
510 _nativeProps = nativeProps;
511}
512
513- (void)setValueForNodeID:(nonnull NSNumber *)nodeID value:(nonnull NSNumber *)newValue
514{
515 RCTAssertParam(nodeID);
516
517 REANode *node = _nodes[nodeID];
518
519 REAValueNode *valueNode = (REAValueNode *)node;
520 [valueNode setValue:newValue];
521}
522
523- (void)updateProps:(nonnull NSDictionary *)props
524 ofViewWithTag:(nonnull NSNumber *)viewTag
525 withName:(nonnull NSString *)viewName
526{
527 // TODO: refactor PropsNode to also use this function
528 NSMutableDictionary *uiProps = [NSMutableDictionary new];
529 NSMutableDictionary *nativeProps = [NSMutableDictionary new];
530 NSMutableDictionary *jsProps = [NSMutableDictionary new];
531
532 void (^addBlock)(NSString *key, id obj, BOOL * stop) = ^(NSString *key, id obj, BOOL * stop){
533 if ([self.uiProps containsObject:key]) {
534 uiProps[key] = obj;
535 } else if ([self.nativeProps containsObject:key]) {
536 nativeProps[key] = obj;
537 } else {
538 jsProps[key] = obj;
539 }
540 };
541
542 [props enumerateKeysAndObjectsUsingBlock:addBlock];
543
544 if (uiProps.count > 0) {
545 [self.uiManager
546 synchronouslyUpdateViewOnUIThread:viewTag
547 viewName:viewName
548 props:uiProps];
549 }
550 if (nativeProps.count > 0) {
551 [self enqueueUpdateViewOnNativeThread:viewTag viewName:viewName nativeProps:nativeProps trySynchronously:YES];
552 }
553 if (jsProps.count > 0) {
554 [self.reanimatedModule sendEventWithName:@"onReanimatedPropsChange"
555 body:@{@"viewTag": viewTag, @"props": jsProps }];
556 }
557}
558
559- (NSString*)obtainProp:(nonnull NSNumber *)viewTag
560 propName:(nonnull NSString *)propName
561{
562 UIView* view = [self.uiManager viewForReactTag:viewTag];
563
564 NSString* result = [NSString stringWithFormat:@"error: unknown propName %@, currently supported: opacity, zIndex", propName];
565
566 if ([propName isEqualToString:@"opacity"]) {
567 CGFloat alpha = view.alpha;
568 result = [@(alpha) stringValue];
569 } else if ([propName isEqualToString:@"zIndex"]) {
570 NSInteger zIndex = view.reactZIndex;
571 result = [@(zIndex) stringValue];
572 }
573
574 return result;
575}
576
577@end