diff --git a/React/Fabric/Mounting/RCTComponentViewProtocol.h b/React/Fabric/Mounting/RCTComponentViewProtocol.h index e2301348dc4..c27e46f319e 100644 --- a/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -102,6 +102,11 @@ typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) { - (void)updateLayoutMetrics:(facebook::react::LayoutMetrics const &)layoutMetrics oldLayoutMetrics:(facebook::react::LayoutMetrics const &)oldLayoutMetrics; +/* + * Called when receiving a command + */ +- (void)handleCommand:(NSString const *)commandName args:(NSArray const *)args; + /* * Called right after all update methods were called for a particular component view. * Useful for performing updates that require knowledge of several independent aspects of the compound mounting change diff --git a/React/Fabric/Mounting/RCTMountingManager.h b/React/Fabric/Mounting/RCTMountingManager.h index 8bf62da780a..dbc0d604d87 100644 --- a/React/Fabric/Mounting/RCTMountingManager.h +++ b/React/Fabric/Mounting/RCTMountingManager.h @@ -32,10 +32,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)scheduleTransaction:(facebook::react::MountingCoordinator::Shared const &)mountingCoordinator; +/** + * Dispatch a command to be performed on the main thread. + * Can be called from any thread. + */ +- (void)dispatchCommand:(ReactTag)reactTag commandName:(NSString *)commandName args:(NSArray *)args; + - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag changedProps:(NSDictionary *)props componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor; - @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index bf781fea21b..48eeeff15ee 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -225,6 +225,22 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, }); } +- (void)dispatchCommand:(ReactTag)reactTag commandName:(NSString *)commandName args:(NSArray *)args +{ + if (RCTIsMainQueue()) { + // Already on the proper thread, so: + // * No need to do a thread jump; + // * No need to allocate a block. + [self synchronouslyDispatchCommandOnUIThread:reactTag commandName:commandName args:args]; + return; + } + + RCTExecuteOnMainQueue(^{ + RCTAssertMainQueue(); + [self synchronouslyDispatchCommandOnUIThread:reactTag commandName:commandName args:args]; + }); +} + - (void)mountMutations:(MountingCoordinator::Shared const &)mountingCoordinator { SystraceSection s("-[RCTMountingManager mountMutations:]"); @@ -253,4 +269,13 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, [componentView updateProps:newProps oldProps:oldProps]; } +- (void)synchronouslyDispatchCommandOnUIThread:(ReactTag)reactTag + commandName:(NSString *)commandName + args:(NSArray *)args +{ + RCTAssertMainQueue(); + UIView *componentView = [_componentViewRegistry componentViewByTag:reactTag]; + [componentView handleCommand:commandName args:args]; +} + @end diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 24d7889989a..225e7bf4a2c 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -60,6 +60,11 @@ using namespace facebook::react; // Default implementation does nothing. } +- (void)handleCommand:(NSString *)commandName args:(NSArray *)args +{ + // Default implementation does nothing. +} + - (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics { diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index a48ef1f0592..1dbd74b49e8 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -28,6 +28,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)schedulerDidFinishTransaction:(facebook::react::MountingCoordinator::Shared const &)mountingCoordinator; +- (void)schedulerDidDispatchCommand:(facebook::react::ShadowView const &)shadowView + commandName:(std::string const &)commandName + args:(folly::dynamic const)args; + @end /** diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index a21051f04fb..07d0872a0de 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -34,6 +34,15 @@ class SchedulerDelegateProxy : public SchedulerDelegate { // Preemptive allocation of native views on iOS does not require this call. } + void schedulerDidDispatchCommand( + const ShadowView &shadowView, + const std::string &commandName, + const folly::dynamic args) override + { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler.delegate schedulerDidDispatchCommand:shadowView commandName:commandName args:args]; + } + private: void *scheduler_; }; diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 17f4491a7c5..dc3963b88ad 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -321,6 +321,17 @@ using namespace facebook::react; [_mountingManager scheduleTransaction:mountingCoordinator]; } +- (void)schedulerDidDispatchCommand:(facebook::react::ShadowView const &)shadowView + commandName:(std::string const &)commandName + args:(folly::dynamic const)args +{ + ReactTag tag = shadowView.tag; + NSString *commandStr = [[NSString alloc] initWithUTF8String:commandName.c_str()]; + NSArray *argsArray = convertFollyDynamicToId(args); + + [self->_mountingManager dispatchCommand:tag commandName:commandStr args:argsArray]; +} + - (void)addObserver:(id)observer { std::unique_lock lock(_observerListMutex); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index 2ce223e1c6f..0245ee22468 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -248,6 +248,11 @@ inline local_ref castReadableMap( return make_local(reinterpret_cast(nativeMap.get())); } +inline local_ref castReadableArray( + local_ref nativeArray) { + return make_local(reinterpret_cast(nativeArray.get())); +} + // TODO: this method will be removed when binding for components are code-gen local_ref getPlatformComponentName(const ShadowView &shadowView) { local_ref componentName; @@ -672,6 +677,31 @@ void Binding::schedulerDidRequestPreliminaryViewAllocation( isLayoutableShadowNode); } +void Binding::schedulerDidDispatchCommand( + const ShadowView &shadowView, + std::string const &commandName, + folly::dynamic const args) { + + jni::global_ref localJavaUIManager = getJavaUIManager(); + if (!localJavaUIManager) { + LOG(ERROR) << "Binding::schedulerDidDispatchCommand: JavaUIManager disappeared"; + return; + } + + static auto dispatchCommand = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod( + "dispatchCommand"); + + local_ref command = make_jstring(commandName); + + local_ref argsArray = castReadableArray( + ReadableNativeArray::newObjectCxxArgs(args)); + + dispatchCommand(localJavaUIManager, shadowView.tag, command.get(), argsArray.get()); +} + void Binding::registerNatives() { registerHybrid( {makeNativeMethod("initHybrid", Binding::initHybrid), diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index de1822989df..0589e6985cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -82,6 +82,11 @@ class Binding : public jni::HybridClass, public SchedulerDelegate { const SurfaceId surfaceId, const ShadowView &shadowView); + void schedulerDidDispatchCommand( + const ShadowView &shadowView, + std::string const &commandName, + folly::dynamic const args); + void setPixelDensity(float pointScaleFactor); void uninstallFabricUIManager(); diff --git a/ReactCommon/fabric/uimanager/Scheduler.cpp b/ReactCommon/fabric/uimanager/Scheduler.cpp index 1029584f9fa..af6e080f0cf 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.cpp +++ b/ReactCommon/fabric/uimanager/Scheduler.cpp @@ -258,5 +258,17 @@ void Scheduler::uiManagerDidCreateShadowNode( } } +void Scheduler::uiManagerDispatchCommand( + const SharedShadowNode &shadowNode, + std::string const &commandName, + folly::dynamic const args) { + SystraceSection s("Scheduler::uiManagerDispatchCommand"); + + if (delegate_) { + auto shadowView = ShadowView(*shadowNode); + delegate_->schedulerDidDispatchCommand(shadowView, commandName, args); + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/uimanager/Scheduler.h b/ReactCommon/fabric/uimanager/Scheduler.h index 6cd4322501c..163d3d945a5 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.h +++ b/ReactCommon/fabric/uimanager/Scheduler.h @@ -86,6 +86,10 @@ class Scheduler final : public UIManagerDelegate, public ShadowTreeDelegate { const SharedShadowNodeUnsharedList &rootChildNodes) override; void uiManagerDidCreateShadowNode( const SharedShadowNode &shadowNode) override; + void uiManagerDispatchCommand( + const SharedShadowNode &shadowNode, + std::string const &commandName, + folly::dynamic const args) override; #pragma mark - ShadowTreeDelegate diff --git a/ReactCommon/fabric/uimanager/SchedulerDelegate.h b/ReactCommon/fabric/uimanager/SchedulerDelegate.h index 45212d92dd7..d6cafde2ac3 100644 --- a/ReactCommon/fabric/uimanager/SchedulerDelegate.h +++ b/ReactCommon/fabric/uimanager/SchedulerDelegate.h @@ -22,7 +22,7 @@ class SchedulerDelegate { public: /* * Called right after Scheduler computed (and laid out) a new updated version - * of the tree and calculated a set of mutations which are suffisient + * of the tree and calculated a set of mutations which are sufficient * to construct a new one. */ virtual void schedulerDidFinishTransaction( @@ -35,6 +35,11 @@ class SchedulerDelegate { SurfaceId surfaceId, const ShadowView &shadowView) = 0; + virtual void schedulerDidDispatchCommand( + const ShadowView &shadowView, + std::string const &commandName, + folly::dynamic const args) = 0; + virtual ~SchedulerDelegate() noexcept = default; }; diff --git a/ReactCommon/fabric/uimanager/UIManager.cpp b/ReactCommon/fabric/uimanager/UIManager.cpp index 927b448a240..d62e89f76e8 100644 --- a/ReactCommon/fabric/uimanager/UIManager.cpp +++ b/ReactCommon/fabric/uimanager/UIManager.cpp @@ -167,6 +167,15 @@ void UIManager::updateState( }); } +void UIManager::dispatchCommand( + const SharedShadowNode &shadowNode, + std::string const &commandName, + folly::dynamic const args) const { + if (delegate_) { + delegate_->uiManagerDispatchCommand(shadowNode, commandName, args); + } +} + void UIManager::setShadowTreeRegistry(ShadowTreeRegistry *shadowTreeRegistry) { shadowTreeRegistry_ = shadowTreeRegistry; } diff --git a/ReactCommon/fabric/uimanager/UIManager.h b/ReactCommon/fabric/uimanager/UIManager.h index a1c39458dc1..8ef9da19210 100644 --- a/ReactCommon/fabric/uimanager/UIManager.h +++ b/ReactCommon/fabric/uimanager/UIManager.h @@ -75,6 +75,11 @@ class UIManager { const SharedShadowNode &shadowNode, const StateData::Shared &rawStateData) const; + void dispatchCommand( + const SharedShadowNode &shadowNode, + std::string const &commandName, + folly::dynamic const args) const; + ShadowTreeRegistry *shadowTreeRegistry_; SharedComponentDescriptorRegistry componentDescriptorRegistry_; UIManagerDelegate *delegate_; diff --git a/ReactCommon/fabric/uimanager/UIManagerBinding.cpp b/ReactCommon/fabric/uimanager/UIManagerBinding.cpp index ed06ab2bef1..945dba956a1 100644 --- a/ReactCommon/fabric/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/fabric/uimanager/UIManagerBinding.cpp @@ -141,7 +141,7 @@ jsi::Value UIManagerBinding::get( runtime, uiManager.createNode( tagFromValue(runtime, arguments[0]), - componentNameFromValue(runtime, arguments[1]), + stringFromValue(runtime, arguments[1]), surfaceIdFromValue(runtime, arguments[2]), RawProps(runtime, arguments[3]), eventTargetFromValue(runtime, arguments[4], arguments[0]))); @@ -332,6 +332,25 @@ jsi::Value UIManagerBinding::get( }); } + if (methodName == "dispatchCommand") { + return jsi::Function::createFromHostFunction( + runtime, + name, + 3, + [&uiManager]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value { + uiManager.dispatchCommand( + shadowNodeFromValue(runtime, arguments[0]), + stringFromValue(runtime, arguments[1]), + commandArgsFromValue(runtime, arguments[2])); + + return jsi::Value::undefined(); + }); + } + // Legacy API if (methodName == "measureLayout") { return jsi::Function::createFromHostFunction( diff --git a/ReactCommon/fabric/uimanager/UIManagerDelegate.h b/ReactCommon/fabric/uimanager/UIManagerDelegate.h index cd6b881615b..48df4e2f2f2 100644 --- a/ReactCommon/fabric/uimanager/UIManagerDelegate.h +++ b/ReactCommon/fabric/uimanager/UIManagerDelegate.h @@ -20,7 +20,7 @@ class UIManagerDelegate { public: /* * Called right after the new/updated Shadow Node tree is constructed. - * The tree is not layed out and not sealed at this time. + * The tree is not laid out and not sealed at this time. */ virtual void uiManagerDidFinishTransaction( SurfaceId surfaceId, @@ -28,12 +28,20 @@ class UIManagerDelegate { /* * Called each time when UIManager constructs a new Shadow Node. Receiver - * maight use this to preluminary optimistically allocate a new native view + * might use this to optimistically allocate a new native view * instances. */ virtual void uiManagerDidCreateShadowNode( const SharedShadowNode &shadowNode) = 0; + /* + * Called when UIManager wants to dispatch a command to the mounting layer. + */ + virtual void uiManagerDispatchCommand( + const SharedShadowNode &shadowNode, + std::string const &commandName, + folly::dynamic const args) = 0; + virtual ~UIManagerDelegate() noexcept = default; }; diff --git a/ReactCommon/fabric/uimanager/primitives.h b/ReactCommon/fabric/uimanager/primitives.h index e56b501da4e..6d66f1620af 100644 --- a/ReactCommon/fabric/uimanager/primitives.h +++ b/ReactCommon/fabric/uimanager/primitives.h @@ -83,11 +83,17 @@ inline static SurfaceId surfaceIdFromValue( return (SurfaceId)value.getNumber(); } -inline static std::string componentNameFromValue( +inline static std::string stringFromValue( jsi::Runtime &runtime, const jsi::Value &value) { return value.getString(runtime).utf8(runtime); } +inline static folly::dynamic commandArgsFromValue( + jsi::Runtime &runtime, + const jsi::Value &value) { + return jsi::dynamicFromValue(runtime, value); +} + } // namespace react } // namespace facebook