From 32b30074d3e4af3de0bd6fad42e48951f3969788 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Thu, 24 Oct 2019 17:43:08 -0700 Subject: [PATCH] Fabric: Introducing RCTSurfacePresenterBridgeAdapter Summary: Yes, this is a pretty big diff; because of the high interconnectedness of the things, I failed to split it apart. This change does: Introduces a new class RCTSurfacePresenterBridgeAdapter which decouples all Bridge-related functionality from RCTSurfacePresenter. The new class allows "replacing" one instance of the bridge with another for a single RCTSurfacePresenter. This change allows implementing unloading a bridge (e.g. during memory pressure warning) and suspending all RN surfaces with the future possibility of reloading a bridge and resuming surfaces. Changelog: [Internal] Reviewed By: PeteTheHeat Differential Revision: D17960239 fbshipit-source-id: 7ae556ed91030f4c5ab187689ce6bd161fabde93 --- React/Fabric/RCTSurfacePresenter.h | 18 +- React/Fabric/RCTSurfacePresenter.mm | 218 +++++------------- .../Fabric/RCTSurfacePresenterBridgeAdapter.h | 41 ++++ .../RCTSurfacePresenterBridgeAdapter.mm | 178 ++++++++++++++ 4 files changed, 290 insertions(+), 165 deletions(-) create mode 100644 React/Fabric/RCTSurfacePresenterBridgeAdapter.h create mode 100644 React/Fabric/RCTSurfacePresenterBridgeAdapter.mm diff --git a/React/Fabric/RCTSurfacePresenter.h b/React/Fabric/RCTSurfacePresenter.h index 51a5f73e46d..5234c113e43 100644 --- a/React/Fabric/RCTSurfacePresenter.h +++ b/React/Fabric/RCTSurfacePresenter.h @@ -6,13 +6,10 @@ */ #import -#import -#import -#import #import #import -#import + #import #import @@ -21,22 +18,21 @@ NS_ASSUME_NONNULL_BEGIN @class RCTFabricSurface; @class RCTImageLoader; @class RCTMountingManager; +@class RCTComponentViewFactory; /** * Coordinates presenting of React Native Surfaces and represents application * facing interface of running React Native core. - * SurfacePresenter incapsulates a bridge object inside and discourage direct - * access to it. */ @interface RCTSurfacePresenter : NSObject -- (instancetype)initWithBridge:(RCTBridge *_Nullable)bridge - config:(std::shared_ptr)config - imageLoader:(RCTImageLoader *)imageLoader - runtimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor; +- (instancetype)initWithContextContainer:(facebook::react::ContextContainer::Shared)contextContainer + runtimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor; @property (nonatomic, readonly) RCTComponentViewFactory *componentViewFactory; -@property (nonatomic, readonly) facebook::react::ContextContainer::Shared contextContainer; + +@property (nonatomic) facebook::react::ContextContainer::Shared contextContainer; +@property (nonatomic) facebook::react::RuntimeExecutor runtimeExecutor; /* * Suspends/resumes all surfaces associated with the presenter. diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 52eef59ae48..2d2933d0405 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -7,18 +7,13 @@ #import "RCTSurfacePresenter.h" -#import -#import -#import #import #import -#import #import #import #import #import -#import #import #import #import @@ -41,11 +36,6 @@ using namespace facebook::react; -@interface RCTBridge () -- (std::shared_ptr)jsMessageThread; -- (void)invokeAsync:(std::function &&)func; -@end - @interface RCTSurfacePresenter () @end @@ -53,70 +43,64 @@ using namespace facebook::react; std::mutex _schedulerMutex; RCTScheduler *_Nullable _scheduler; // Thread-safe. Mutation of the instance variable is protected by `_schedulerMutex`. + ContextContainer::Shared _contextContainer; + RuntimeExecutor _runtimeExecutor; RCTMountingManager *_mountingManager; // Thread-safe. RCTSurfaceRegistry *_surfaceRegistry; // Thread-safe. - RCTBridge *_bridge; // Unsafe. We are moving away from Bridge. - RCTBridge *_batchedBridge; - std::shared_ptr _reactNativeConfig; - ContextContainer::Shared _contextContainer; better::shared_mutex _observerListMutex; NSMutableArray> *_observers; - RCTImageLoader *_imageLoader; - RuntimeExecutor _runtimeExecutor; } -- (instancetype)initWithBridge:(RCTBridge *_Nullable)bridge - config:(std::shared_ptr)config - imageLoader:(RCTImageLoader *)imageLoader - runtimeExecutor:(RuntimeExecutor)runtimeExecutor +- (instancetype)initWithContextContainer:(ContextContainer::Shared)contextContainer + runtimeExecutor:(RuntimeExecutor)runtimeExecutor { if (self = [super init]) { - _imageLoader = imageLoader; + assert(contextContainer && "RuntimeExecutor must be not null."); + _runtimeExecutor = runtimeExecutor; - _bridge = bridge; - _batchedBridge = [_bridge batchedBridge] ?: _bridge; - [_batchedBridge setSurfacePresenter:self]; + _contextContainer = contextContainer; _surfaceRegistry = [[RCTSurfaceRegistry alloc] init]; - _mountingManager = [[RCTMountingManager alloc] init]; _mountingManager.delegate = self; - if (config != nullptr) { - _reactNativeConfig = config; - } else { - _reactNativeConfig = std::make_shared(); - } - - _contextContainer = std::make_shared(); - _observers = [NSMutableArray array]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleBridgeWillReloadNotification:) - name:RCTBridgeWillReloadNotification - object:_bridge]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleJavaScriptDidLoadNotification:) - name:RCTJavaScriptDidLoadNotification - object:_bridge]; - _scheduler = [self _createScheduler]; } return self; } -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - - (RCTComponentViewFactory *)componentViewFactory { return _mountingManager.componentViewRegistry.componentViewFactory; } +- (ContextContainer::Shared)contextContainer +{ + std::lock_guard lock(_schedulerMutex); + return _contextContainer; +} + +- (void)setContextContainer:(ContextContainer::Shared)contextContainer +{ + std::lock_guard lock(_schedulerMutex); + _contextContainer = contextContainer; +} + +- (void)setRuntimeExecutor:(RuntimeExecutor)runtimeExecutor +{ + std::lock_guard lock(_schedulerMutex); + _runtimeExecutor = runtimeExecutor; +} + +- (RuntimeExecutor)runtimeExecutor +{ + std::lock_guard lock(_schedulerMutex); + return _runtimeExecutor; +} + #pragma mark - Internal Surface-dedicated Interface - (void)registerSurface:(RCTFabricSurface *)surface @@ -156,10 +140,8 @@ using namespace facebook::react; { std::lock_guard lock(_schedulerMutex); LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()}; - LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize), .maximumSize = RCTSizeFromCGSize(maximumSize)}; - return [_scheduler measureSurfaceWithLayoutConstraints:layoutConstraints layoutContext:layoutContext surfaceId:surface.rootTag]; @@ -169,10 +151,8 @@ using namespace facebook::react; { std::lock_guard lock(_schedulerMutex); LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()}; - LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize), .maximumSize = RCTSizeFromCGSize(maximumSize)}; - [_scheduler constraintSurfaceLayoutWithLayoutConstraints:layoutConstraints layoutContext:layoutContext surfaceId:surface.rootTag]; @@ -187,11 +167,39 @@ using namespace facebook::react; return NO; // This view probably isn't managed by Fabric } ComponentHandle handle = [[componentView class] componentDescriptorProvider].handle; - const facebook::react::ComponentDescriptor &componentDescriptor = [_scheduler getComponentDescriptor:handle]; + ComponentDescriptor const &componentDescriptor = [_scheduler getComponentDescriptor:handle]; [_mountingManager synchronouslyUpdateViewOnUIThread:tag changedProps:props componentDescriptor:componentDescriptor]; return YES; } +- (BOOL)suspend +{ + std::lock_guard lock(_schedulerMutex); + + if (!_scheduler) { + return NO; + } + + [self _stopAllSurfaces]; + _scheduler = nil; + + return YES; +} + +- (BOOL)resume +{ + std::lock_guard lock(_schedulerMutex); + + if (_scheduler) { + return NO; + } + + _scheduler = [self _createScheduler]; + [self _startAllSurfaces]; + + return YES; +} + #pragma mark - Private - (RCTScheduler *)_createScheduler @@ -203,12 +211,10 @@ using namespace facebook::react; createComponentDescriptorRegistryWithParameters:{eventDispatcher, contextContainer}]; }; - auto runtimeExecutor = [self getRuntimeExecutor]; - - [self _updateContextContainerIfNeeded_DEPRECATED]; + auto runtimeExecutor = _runtimeExecutor; auto toolbox = SchedulerToolbox{}; - toolbox.contextContainer = self.contextContainer; + toolbox.contextContainer = _contextContainer; toolbox.componentRegistryFactory = componentRegistryFactory; toolbox.runtimeExecutor = runtimeExecutor; @@ -226,56 +232,6 @@ using namespace facebook::react; return scheduler; } -- (RuntimeExecutor)getRuntimeExecutor -{ - if (_runtimeExecutor) { - return _runtimeExecutor; - } - - auto messageQueueThread = _batchedBridge.jsMessageThread; - if (messageQueueThread) { - // Make sure initializeBridge completed - messageQueueThread->runOnQueueSync([] {}); - } - - auto runtime = (facebook::jsi::Runtime *)((RCTCxxBridge *)_batchedBridge).runtime; - - RuntimeExecutor runtimeExecutor = [self, runtime](std::function &&callback) { - // For now, ask the bridge to queue the callback asynchronously to ensure that - // it's not invoked too early, e.g. before the bridge is fully ready. - // Revisit this after Fabric/TurboModule is fully rolled out. - [((RCTCxxBridge *)_batchedBridge) invokeAsync:[runtime, callback = std::move(callback)]() { callback(*runtime); }]; - }; - - return runtimeExecutor; -} - -- (ContextContainer::Shared)contextContainer -{ - return _contextContainer; -} - -- (void)_updateContextContainerIfNeeded_DEPRECATED -{ - // Please do not add stuff here; `SurfacePresenter` must not alter `ContextContainer`. - // Those two pieces eventually should be moved out there: - // * `RCTImageLoader` should be moved to `RCTImageComponentView`. - // * `ReactNativeConfig` should be set by outside product code. - _contextContainer->erase("ReactNativeConfig"); - _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); - - _contextContainer->erase("Bridge"); - _contextContainer->insert("Bridge", wrapManagedObjectWeakly(_bridge)); - - // TODO T47869586 petetheheat: Delete else case when TM rollout 100% - _contextContainer->erase("RCTImageLoader"); - if (_imageLoader) { - _contextContainer->insert("RCTImageLoader", wrapManagedObject(_imageLoader)); - } else { - _contextContainer->insert("RCTImageLoader", wrapManagedObject([_bridge moduleForClass:[RCTImageLoader class]])); - } -} - - (void)_startSurface:(RCTFabricSurface *)surface { RCTMountingManager *mountingManager = _mountingManager; @@ -330,35 +286,9 @@ using namespace facebook::react; }]; } -- (BOOL)suspend -{ - std::lock_guard lock(_schedulerMutex); - if (!_scheduler) { - return NO; - } - - [self _stopAllSurfaces]; - _scheduler = nil; - - return YES; -} - -- (BOOL)resume -{ - std::lock_guard lock(_schedulerMutex); - if (_scheduler) { - return NO; - } - - _scheduler = [self _createScheduler]; - [self _startAllSurfaces]; - - return YES; -} - #pragma mark - RCTSchedulerDelegate -- (void)schedulerDidFinishTransaction:(facebook::react::MountingCoordinator::Shared const &)mountingCoordinator +- (void)schedulerDidFinishTransaction:(MountingCoordinator::Shared const &)mountingCoordinator { RCTFabricSurface *surface = [_surfaceRegistry surfaceForRootTag:mountingCoordinator->getSurfaceId()]; @@ -367,7 +297,7 @@ using namespace facebook::react; [_mountingManager scheduleTransaction:mountingCoordinator]; } -- (void)schedulerDidDispatchCommand:(facebook::react::ShadowView const &)shadowView +- (void)schedulerDidDispatchCommand:(ShadowView const &)shadowView commandName:(std::string const &)commandName args:(folly::dynamic const)args { @@ -426,24 +356,4 @@ using namespace facebook::react; } } -#pragma mark - Bridge events - -- (void)handleBridgeWillReloadNotification:(NSNotification *)notification -{ - [self suspend]; -} - -- (void)handleJavaScriptDidLoadNotification:(NSNotification *)notification -{ - RCTBridge *bridge = notification.userInfo[@"bridge"]; - if (bridge == _batchedBridge) { - // Nothing really changed. - return; - } - - _batchedBridge = bridge; - - [self resume]; -} - @end diff --git a/React/Fabric/RCTSurfacePresenterBridgeAdapter.h b/React/Fabric/RCTSurfacePresenterBridgeAdapter.h new file mode 100644 index 00000000000..76287e32dc1 --- /dev/null +++ b/React/Fabric/RCTSurfacePresenterBridgeAdapter.h @@ -0,0 +1,41 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCTSurfacePresenter; +@class RCTBridge; + +/* + * Controls a life-cycle of a Surface Presenter based on Bridge's life-cycle. + * We are moving away from using Bridge. + * This class is intended to be used only during the transition period. + */ +@interface RCTSurfacePresenterBridgeAdapter : NSObject + +- (instancetype)initWithBridge:(RCTBridge *)bridge + contextContainer:(facebook::react::ContextContainer::Shared)contextContainer; + +/* + * Returns a stored instance of Surface Presenter which is managed by a bridge. + */ +@property (nonatomic, readonly) RCTSurfacePresenter *surfacePresenter; + +/* + * Controls a stored instance of the Bridge. A consumer can re-set the stored Bridge using that method; the class is + * responsible to coordinate this change with a SurfacePresetner accordingly. + */ +@property (nonatomic, weak) RCTBridge *bridge; + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm b/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm new file mode 100644 index 00000000000..73c1ac724a0 --- /dev/null +++ b/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm @@ -0,0 +1,178 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTSurfacePresenterBridgeAdapter.h" + +#import +#import + +#import +#import +#import +#import +#import + +#import +#import +#import + +using namespace facebook::react; + +@interface RCTBridge () +- (std::shared_ptr)jsMessageThread; +- (void)invokeAsync:(std::function &&)func; +@end + +static ContextContainer::Shared RCTContextContainerFromBridge(RCTBridge *bridge) +{ + auto contextContainer = std::make_shared(); + + RCTImageLoader *imageLoader = RCTTurboModuleEnabled() + ? [bridge moduleForName:@"RCTImageLoader" lazilyLoadIfNecessary:YES] + : [bridge moduleForClass:[RCTImageLoader class]]; + + contextContainer->insert("Bridge", wrapManagedObjectWeakly(bridge)); + contextContainer->insert("RCTImageLoader", wrapManagedObject(imageLoader)); + return contextContainer; +} + +static RuntimeExecutor RCTRuntimeExecutorFromBridge(RCTBridge *bridge) +{ + RCTBridge *batchedBridge = [bridge batchedBridge] ?: bridge; + + auto messageQueueThread = batchedBridge.jsMessageThread; + if (messageQueueThread) { + // Make sure initializeBridge completed + messageQueueThread->runOnQueueSync([] {}); + } + + auto runtime = (facebook::jsi::Runtime *)((RCTCxxBridge *)batchedBridge).runtime; + + RuntimeExecutor runtimeExecutor = [batchedBridge, + runtime](std::function &&callback) { + // For now, ask the bridge to queue the callback asynchronously to ensure that + // it's not invoked too early, e.g. before the bridge is fully ready. + // Revisit this after Fabric/TurboModule is fully rolled out. + [((RCTCxxBridge *)batchedBridge) invokeAsync:[runtime, callback = std::move(callback)]() { callback(*runtime); }]; + }; + + return runtimeExecutor; +} + +@implementation RCTSurfacePresenterBridgeAdapter { + RCTSurfacePresenter *_Nullable _surfacePresenter; + __weak RCTBridge *_bridge; + __weak RCTBridge *_batchedBridge; +} + +- (instancetype)initWithBridge:(RCTBridge *)bridge contextContainer:(ContextContainer::Shared)contextContainer +{ + if (self = [super init]) { + contextContainer->update(*RCTContextContainerFromBridge(bridge)); + _surfacePresenter = [[RCTSurfacePresenter alloc] initWithContextContainer:contextContainer + runtimeExecutor:RCTRuntimeExecutorFromBridge(bridge)]; + + _bridge = bridge; + _batchedBridge = [_bridge batchedBridge] ?: _bridge; + + [self _updateSurfacePresenter]; + [self _addBridgeObservers:_bridge]; + } + + return self; +} + +- (void)dealloc +{ + [_surfacePresenter suspend]; + [self _removeBridgeObservers:_bridge]; +} + +- (RCTBridge *)bridge +{ + return _bridge; +} + +- (void)setBridge:(RCTBridge *)bridge +{ + if (bridge == _bridge) { + return; + } + + [self _removeBridgeObservers:_bridge]; + + [_surfacePresenter suspend]; + + _bridge = bridge; + _batchedBridge = [_bridge batchedBridge] ?: _bridge; + + [self _updateSurfacePresenter]; + + [self _addBridgeObservers:_bridge]; + + [_surfacePresenter resume]; +} + +- (void)_updateSurfacePresenter +{ + _surfacePresenter.runtimeExecutor = RCTRuntimeExecutorFromBridge(_bridge); + _surfacePresenter.contextContainer->update(*RCTContextContainerFromBridge(_bridge)); + + [_bridge setSurfacePresenter:_surfacePresenter]; + [_batchedBridge setSurfacePresenter:_surfacePresenter]; +} + +- (void)_addBridgeObservers:(RCTBridge *)bridge +{ + if (!bridge) { + return; + } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleBridgeWillReloadNotification:) + name:RCTBridgeWillReloadNotification + object:bridge]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleJavaScriptDidLoadNotification:) + name:RCTJavaScriptDidLoadNotification + object:bridge]; +} + +- (void)_removeBridgeObservers:(RCTBridge *)bridge +{ + if (!bridge) { + return; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillReloadNotification object:bridge]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTJavaScriptDidLoadNotification object:bridge]; +} + +#pragma mark - Bridge events + +- (void)handleBridgeWillReloadNotification:(NSNotification *)notification +{ + [_surfacePresenter suspend]; +} + +- (void)handleJavaScriptDidLoadNotification:(NSNotification *)notification +{ + RCTBridge *bridge = notification.userInfo[@"bridge"]; + if (bridge == _batchedBridge) { + // Nothing really changed. + return; + } + + _batchedBridge = bridge; + _batchedBridge.surfacePresenter = _surfacePresenter; + + [self _updateSurfacePresenter]; + + [_surfacePresenter resume]; +} + +@end