[ReactNative] Send batched calls from objc to js every frame + add bridge profiling
This commit is contained in:
parent
70a28547dd
commit
1883ba535c
|
@ -74,7 +74,8 @@ var BatchedBridgeFactory = {
|
|||
setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue),
|
||||
getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue),
|
||||
getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue),
|
||||
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue)
|
||||
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue),
|
||||
processBatch: messageQueue.processBatch.bind(messageQueue),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -307,6 +307,25 @@ var MessageQueueMixin = {
|
|||
);
|
||||
},
|
||||
|
||||
processBatch: function (batch) {
|
||||
var self = this;
|
||||
batch.forEach(function (call) {
|
||||
invariant(
|
||||
call.module === 'BatchedBridge',
|
||||
'All the calls should pass through the BatchedBridge module'
|
||||
);
|
||||
if (call.method === 'callFunctionReturnFlushedQueue') {
|
||||
self.callFunction.apply(self, call.args);
|
||||
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
|
||||
self.invokeCallback.apply(self, call.args);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unrecognized method called on BatchedBridge: ' + call.method);
|
||||
}
|
||||
});
|
||||
return this.flushedQueue();
|
||||
},
|
||||
|
||||
setLoggingEnabled: function(enabled) {
|
||||
this._enableLogging = enabled;
|
||||
this._loggedIncomingItems = [];
|
||||
|
|
|
@ -42,6 +42,45 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
|||
RCTBridgeFieldFlushDateMillis
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporarily allow to turn on and off the call batching in case someone wants
|
||||
* to profile both
|
||||
*/
|
||||
#define BATCHED_BRIDGE 1
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define RCT_PROFILE_START() \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
NSTimeInterval __rct_profile_start = CACurrentMediaTime() \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define RCT_PROFILE_END(cat, args, profileName...) \
|
||||
do { \
|
||||
if (_profile) { \
|
||||
[_profileLock lock]; \
|
||||
[_profile addObject:@{ \
|
||||
@"name": [@[profileName] componentsJoinedByString: @"_"], \
|
||||
@"cat": @ #cat, \
|
||||
@"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \
|
||||
@"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \
|
||||
@"ph": @"X", \
|
||||
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
|
||||
@"tid": [[NSThread currentThread] description], \
|
||||
@"args": args ?: [NSNull null], \
|
||||
}]; \
|
||||
[_profileLock unlock]; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define RCT_PROFILE_START(...)
|
||||
#define RCT_PROFILE_END(...)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
typedef uint64_t RCTHeaderValue;
|
||||
typedef struct section_64 RCTHeaderSection;
|
||||
|
@ -191,10 +230,16 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
|||
|
||||
@interface RCTBridge ()
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
@ -754,7 +799,13 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
RCTDisplayLink *_displayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
NSMutableArray *_scheduledCalls;
|
||||
NSMutableArray *_scheduledCallbacks;
|
||||
BOOL _loading;
|
||||
|
||||
NSUInteger _startingTime;
|
||||
NSMutableArray *_profile;
|
||||
NSLock *_profileLock;
|
||||
}
|
||||
|
||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
@ -782,6 +833,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
|
@ -1005,20 +1058,54 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private hack to support `setTimeout(fn, 0)`
|
||||
*/
|
||||
- (void)_immediatelyCallTimer:(NSNumber *)timer
|
||||
{
|
||||
NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
|
||||
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
|
||||
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
|
||||
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
|
||||
|
||||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
if (!_loading) {
|
||||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
|
||||
#else
|
||||
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
RCT_PROFILE_START();
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
|
||||
if (scriptLoadError) {
|
||||
onComplete(scriptLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
RCT_PROFILE_START();
|
||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
callback:^(id json, NSError *error) {
|
||||
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
onComplete(error);
|
||||
}];
|
||||
}];
|
||||
|
@ -1028,11 +1115,46 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCT_PROFILE_START();
|
||||
|
||||
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||
for (NSDictionary *call in _scheduledCalls) {
|
||||
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
|
||||
[_scheduledCalls removeObject:call];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
[_scheduledCallbacks addObject:call];
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
{
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
||||
|
||||
NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method];
|
||||
RCT_PROFILE_START();
|
||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
||||
RCT_PROFILE_END(js_call, args, moduleDotMethod);
|
||||
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
|
@ -1151,12 +1273,34 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
- (void)_update:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCT_PROFILE_START();
|
||||
|
||||
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
||||
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
|
||||
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
||||
[observer didUpdateFrame:frameUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
[self _runScheduledCalls];
|
||||
|
||||
RCT_PROFILE_END(display_link, nil, @"main_thread");
|
||||
}
|
||||
|
||||
- (void)_runScheduledCalls
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
|
||||
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
if (calls.count > 0) {
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"processBatch"
|
||||
arguments:@[calls]];
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
||||
|
@ -1194,4 +1338,42 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
callback:^(id json, NSError *error) {}];
|
||||
}
|
||||
|
||||
- (void)startProfiling
|
||||
{
|
||||
if (![_bundleURL.scheme isEqualToString:@"http"]) {
|
||||
RCTLogError(@"To run the profiler you must be running from the dev server");
|
||||
return;
|
||||
}
|
||||
_profileLock = [[NSLock alloc] init];
|
||||
_startingTime = CACurrentMediaTime();
|
||||
|
||||
[_profileLock lock];
|
||||
_profile = [[NSMutableArray alloc] init];
|
||||
[_profileLock unlock];
|
||||
}
|
||||
|
||||
- (void)stopProfiling
|
||||
{
|
||||
[_profileLock lock];
|
||||
NSArray *profile = _profile;
|
||||
_profile = nil;
|
||||
[_profileLock unlock];
|
||||
_profileLock = nil;
|
||||
|
||||
NSString *log = RCTJSONStringify(profile, NULL);
|
||||
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
|
||||
NSURL *URL = [NSURL URLWithString:URLString];
|
||||
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
URLRequest.HTTPMethod = @"POST";
|
||||
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
||||
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (error) {
|
||||
RCTLogError(@"%@", error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
#import "RCTSourceCode.h"
|
||||
#import "RCTWebViewExecutor.h"
|
||||
|
||||
@interface RCTBridge (RCTDevMenu)
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||
|
||||
- (void)startProfiling;
|
||||
- (void)stopProfiling;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTDevMenu () <UIActionSheetDelegate>
|
||||
|
||||
@end
|
||||
|
@ -37,11 +46,12 @@
|
|||
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil];
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
||||
}
|
||||
|
@ -61,6 +71,12 @@
|
|||
} else if (buttonIndex == 3) {
|
||||
_liveReload = !_liveReload;
|
||||
[self _pollAndReload];
|
||||
} else if (buttonIndex == 4) {
|
||||
if (_bridge.profile) {
|
||||
[_bridge stopProfiling];
|
||||
} else {
|
||||
[_bridge startProfiling];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@
|
|||
|
||||
_bridge = bridge;
|
||||
_moduleName = moduleName;
|
||||
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(bundleFinishedLoading)
|
||||
|
@ -105,7 +104,6 @@
|
|||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[_touchHandler invalidate];
|
||||
if (_contentView) {
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[_contentView.reactTag]];
|
||||
|
@ -148,7 +146,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
|||
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
||||
* the react tag is assigned every time we load new content.
|
||||
*/
|
||||
[_touchHandler invalidate];
|
||||
[_contentView removeFromSuperview];
|
||||
_contentView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
|
||||
|
|
|
@ -9,14 +9,12 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
|
||||
@interface RCTTouchHandler : UIGestureRecognizer
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
- (void)startOrResetInteractionTiming;
|
||||
- (NSDictionary *)endAndResetInteractionTiming;
|
||||
|
||||
@end
|
||||
|
|
|
@ -20,39 +20,6 @@
|
|||
|
||||
// TODO: this class behaves a lot like a module, and could be implemented as a
|
||||
// module if we were to assume that modules and RootViews had a 1:1 relationship
|
||||
|
||||
@interface RCTTouchEvent : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger id;
|
||||
@property (nonatomic, copy, readonly) NSString *eventName;
|
||||
@property (nonatomic, copy, readonly) NSArray *touches;
|
||||
@property (nonatomic, copy, readonly) NSArray *changedIndexes;
|
||||
@property (nonatomic, assign, readonly) CFTimeInterval originatingTime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation RCTTouchEvent
|
||||
|
||||
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
|
||||
{
|
||||
RCTTouchEvent *touchEvent = [[self alloc] init];
|
||||
touchEvent->_id = [self newTaskID];
|
||||
touchEvent->_eventName = [eventName copy];
|
||||
touchEvent->_touches = [touches copy];
|
||||
touchEvent->_changedIndexes = [changedIndexes copy];
|
||||
touchEvent->_originatingTime = originatingTime;
|
||||
return touchEvent;
|
||||
}
|
||||
|
||||
+ (NSUInteger)newTaskID
|
||||
{
|
||||
static NSUInteger taskID = 0;
|
||||
return ++taskID;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTouchHandler
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
|
@ -69,7 +36,6 @@
|
|||
|
||||
BOOL _recordingInteractionTiming;
|
||||
CFTimeInterval _mostRecentEnqueueJS;
|
||||
CADisplayLink *_displayLink;
|
||||
NSMutableArray *_pendingTouches;
|
||||
NSMutableArray *_bridgeInteractionTiming;
|
||||
}
|
||||
|
@ -86,12 +52,9 @@
|
|||
_reactTouches = [[NSMutableArray alloc] init];
|
||||
_touchViews = [[NSMutableArray alloc] init];
|
||||
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
|
||||
_pendingTouches = [[NSMutableArray alloc] init];
|
||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
||||
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
|
||||
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
|
||||
// level components not build using RCT, will fail to recognize gestures.
|
||||
self.cancelsTouchesInView = NO;
|
||||
|
@ -99,17 +62,6 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
RCTTouchEventTypeStart,
|
||||
RCTTouchEventTypeMove,
|
||||
|
@ -216,7 +168,6 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
|
|||
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
|
||||
{
|
||||
// Update touches
|
||||
CFTimeInterval enqueueTime = CACurrentMediaTime();
|
||||
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
|
||||
for (UITouch *touch in touches) {
|
||||
NSInteger index = [_nativeTouches indexOfObject:touch];
|
||||
|
@ -239,71 +190,8 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
|
|||
[reactTouches addObject:[touch copy]];
|
||||
}
|
||||
|
||||
RCTTouchEvent *touch = [RCTTouchEvent touchWithEventName:eventName
|
||||
touches:reactTouches
|
||||
changedIndexes:changedIndexes
|
||||
originatingTime:originatingTime];
|
||||
[_pendingTouches addObject:touch];
|
||||
|
||||
if (_recordingInteractionTiming) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(touch.originatingTime),
|
||||
@"operation": @"taskOriginated",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(enqueueTime),
|
||||
@"operation": @"taskEnqueuedPending",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_update:(CADisplayLink *)sender
|
||||
{
|
||||
// Dispatch touch event
|
||||
NSUInteger pendingCount = _pendingTouches.count;
|
||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
||||
_mostRecentEnqueueJS = CACurrentMediaTime();
|
||||
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
|
||||
args:@[touch.eventName, touch.touches, touch.changedIndexes]];
|
||||
}
|
||||
|
||||
if (_recordingInteractionTiming) {
|
||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"frameAlignedDispatch",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
|
||||
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"mainThreadDisplayLink",
|
||||
@"taskID": @([RCTTouchEvent newTaskID]),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
[_pendingTouches removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)startOrResetInteractionTiming
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
[_bridgeInteractionTiming removeAllObjects];
|
||||
_recordingInteractionTiming = YES;
|
||||
}
|
||||
|
||||
- (NSDictionary *)endAndResetInteractionTiming
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
_recordingInteractionTiming = NO;
|
||||
NSArray *_prevInteractionTimingData = _bridgeInteractionTiming;
|
||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
||||
return @{ @"interactionTiming": _prevInteractionTimingData };
|
||||
args:@[eventName, reactTouches, changedIndexes]];
|
||||
}
|
||||
|
||||
#pragma mark - Gesture Recognizer Delegate Callbacks
|
||||
|
|
|
@ -15,6 +15,15 @@
|
|||
#import "RCTSparseArray.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTBridge (Private)
|
||||
|
||||
/**
|
||||
* Allow super fast, one time, timers to skip the queue and be directly executed
|
||||
*/
|
||||
- (void)_immediatelyCallTimer:(NSNumber *)timer;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTTimer : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSDate *target;
|
||||
|
@ -160,7 +169,7 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID
|
|||
{
|
||||
if (jsDuration == 0 && repeats == NO) {
|
||||
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
|
||||
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[@[callbackID]]];
|
||||
[_bridge _immediatelyCallTimer:callbackID];
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -677,14 +677,12 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
|||
|
||||
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
||||
|
||||
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
||||
|
||||
// Figure out what to insert - merge temporary inserts and adds
|
||||
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
||||
for (NSInteger index = 0; index < temporarilyRemovedChildren.count; index++) {
|
||||
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
||||
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
||||
}
|
||||
for (NSInteger index = 0; index < addAtIndices.count; index++) {
|
||||
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
|
||||
id view = registry[addChildReactTags[index]];
|
||||
if (view) {
|
||||
destinationsToChildrenToAdd[addAtIndices[index]] = view;
|
||||
|
@ -1420,41 +1418,6 @@ RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config
|
|||
callback:callback];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startOrResetInteractionTiming)
|
||||
{
|
||||
NSSet *rootViewTags = [_rootViewTags copy];
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
for (NSNumber *reactTag in rootViewTags) {
|
||||
UIView *rootView = viewRegistry[reactTag];
|
||||
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
|
||||
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
|
||||
[handler startOrResetInteractionTiming];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess
|
||||
onError:(RCTResponseSenderBlock)onError)
|
||||
{
|
||||
NSSet *rootViewTags = [_rootViewTags copy];
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init];
|
||||
for (NSNumber *reactTag in rootViewTags) {
|
||||
UIView *rootView = viewRegistry[reactTag];
|
||||
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
|
||||
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
|
||||
[handler endAndResetInteractionTiming];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSuccess(@[timingData]);
|
||||
}];
|
||||
}
|
||||
|
||||
static UIView *_jsResponder;
|
||||
|
||||
+ (UIView *)JSResponder
|
||||
|
|
|
@ -17,6 +17,8 @@ var Activity = require('../Activity');
|
|||
var AssetServer = require('../AssetServer');
|
||||
var Promise = require('bluebird');
|
||||
var _ = require('underscore');
|
||||
var exec = require('child_process').exec;
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
|
@ -252,6 +254,42 @@ Server.prototype._processAssetsRequest = function(req, res) {
|
|||
).done();
|
||||
};
|
||||
|
||||
Server.prototype._processProfile = function(req, res) {
|
||||
console.log('Dumping profile information...');
|
||||
var dumpName = '/tmp/dump_' + Date.now() + '.json';
|
||||
var prefix = process.env.TRACE_VIEWER_PATH || '';
|
||||
var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName;
|
||||
fs.writeFileSync(dumpName, req.rawBody);
|
||||
exec(cmd, function (error) {
|
||||
if (error) {
|
||||
if (error.code === 127) {
|
||||
console.error(
|
||||
'\n** Failed executing `' + cmd + '` **\n\n' +
|
||||
'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' +
|
||||
'You can get it at:\n\n' +
|
||||
' https://github.com/google/trace-viewer\n\n' +
|
||||
'If it\'s not in your path, you can set a custom path with:\n\n' +
|
||||
' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' +
|
||||
'NOTE: Your profile data was kept at:\n\n' +
|
||||
' ' + dumpName
|
||||
);
|
||||
} else {
|
||||
console.error('Unknown error', error);
|
||||
}
|
||||
res.end();
|
||||
return;
|
||||
} else {
|
||||
exec('rm ' + dumpName);
|
||||
exec('open ' + dumpName.replace(/json$/, 'html'), function (error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype.processRequest = function(req, res, next) {
|
||||
var urlObj = url.parse(req.url, true);
|
||||
var pathname = urlObj.pathname;
|
||||
|
@ -270,6 +308,9 @@ Server.prototype.processRequest = function(req, res, next) {
|
|||
} else if (pathname.match(/^\/assets\//)) {
|
||||
this._processAssetsRequest(req, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/profile\/?$/)) {
|
||||
this._processProfile(req, res);
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
|
|
Loading…
Reference in New Issue