UNPKG

12.1 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
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