diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index ae1005d367..2a7089d62f 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -28,11 +28,14 @@ #import #import #import +#import #import +#import #import #import #import "MainRunLoopEventBeat.h" +#import "PlatformRunLoopObserver.h" #import "RCTConversions.h" #import "RuntimeEventBeat.h" @@ -275,6 +278,8 @@ static inline LayoutContext RCTGetLayoutContext() - (RCTScheduler *)_createScheduler { + auto reactNativeConfig = _contextContainer->at>("ReactNativeConfig"); + auto componentRegistryFactory = [factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)]( EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) { @@ -288,19 +293,36 @@ static inline LayoutContext RCTGetLayoutContext() toolbox.contextContainer = _contextContainer; toolbox.componentRegistryFactory = componentRegistryFactory; toolbox.runtimeExecutor = runtimeExecutor; - - toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { - return std::make_unique(ownerBox, runtimeExecutor); + toolbox.mainRunLoopObserverFactory = [](RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner) { + return std::make_unique(activities, owner); }; - toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { - return std::make_unique(ownerBox, runtimeExecutor); - }; + if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_run_loop_based_event_beat_ios")) { + toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + auto runLoopObserver = + std::make_unique(RunLoopObserver::Activity::BeforeWaiting, ownerBox->owner); + return std::make_unique(std::move(runLoopObserver), runtimeExecutor); + }; + + toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + auto runLoopObserver = + std::make_unique(RunLoopObserver::Activity::BeforeWaiting, ownerBox->owner); + return std::make_unique(std::move(runLoopObserver), runtimeExecutor); + }; + } else { + toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + return std::make_unique(ownerBox, runtimeExecutor); + }; + + toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + return std::make_unique(ownerBox, runtimeExecutor); + }; + } RCTScheduler *scheduler = [[RCTScheduler alloc] initWithToolbox:toolbox]; scheduler.delegate = self; - auto reactNativeConfig = _contextContainer->at>("ReactNativeConfig"); if (reactNativeConfig) { _mountingManager.useModernDifferentiatorMode = reactNativeConfig->getBool("react_fabric:enabled_optimized_moves_differ_ios"); diff --git a/React/Fabric/Utils/PlatformRunLoopObserver.h b/React/Fabric/Utils/PlatformRunLoopObserver.h new file mode 100644 index 0000000000..bbdf31fbe8 --- /dev/null +++ b/React/Fabric/Utils/PlatformRunLoopObserver.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +/* + * Concrete iOS-specific implementation of `RunLoopObserver` using + * `CFRunLoopObserver` under the hood. + */ +class PlatformRunLoopObserver : public RunLoopObserver { + public: + PlatformRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner, + CFRunLoopRef runLoop); + + ~PlatformRunLoopObserver(); + + virtual bool isOnRunLoopThread() const noexcept override; + + private: + void startObserving() const noexcept override; + void stopObserving() const noexcept override; + + CFRunLoopRef runLoop_; + CFRunLoopObserverRef mainRunLoopObserver_; +}; + +/* + * Convenience specialization of `PlatformRunLoopObserver` observing the main + * run loop. + */ +class MainRunLoopObserver final : public PlatformRunLoopObserver { + public: + MainRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner) + : PlatformRunLoopObserver(activities, owner, CFRunLoopGetMain()) {} +}; + +} // namespace react +} // namespace facebook diff --git a/React/Fabric/Utils/PlatformRunLoopObserver.mm b/React/Fabric/Utils/PlatformRunLoopObserver.mm new file mode 100644 index 0000000000..09731eda3b --- /dev/null +++ b/React/Fabric/Utils/PlatformRunLoopObserver.mm @@ -0,0 +1,97 @@ +/* + * 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 "PlatformRunLoopObserver.h" + +#import + +namespace facebook { +namespace react { + +static CFRunLoopActivity toCFRunLoopActivity(RunLoopObserver::Activity activity) +{ + auto result = CFRunLoopActivity{}; + + if (RunLoopObserver::Activity(activity & RunLoopObserver::Activity::BeforeWaiting) == + RunLoopObserver::Activity::BeforeWaiting) { + result = result | kCFRunLoopBeforeWaiting; + } + + if (RunLoopObserver::Activity(activity & RunLoopObserver::Activity::AfterWaiting) == + RunLoopObserver::Activity::AfterWaiting) { + result = result | kCFRunLoopAfterWaiting; + } + + return result; +} + +static RunLoopObserver::Activity toRunLoopActivity(CFRunLoopActivity activity) +{ + auto result = RunLoopObserver::Activity{}; + + if (CFRunLoopActivity(activity & kCFRunLoopBeforeWaiting) == kCFRunLoopBeforeWaiting) { + result = RunLoopObserver::Activity(result | RunLoopObserver::Activity::BeforeWaiting); + } + + if (CFRunLoopActivity(activity & kCFRunLoopAfterWaiting) == kCFRunLoopAfterWaiting) { + result = RunLoopObserver::Activity(result | RunLoopObserver::Activity::AfterWaiting); + } + + return result; +} + +PlatformRunLoopObserver::PlatformRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner, + CFRunLoopRef runLoop) + : RunLoopObserver(activities, owner), runLoop_(runLoop) +{ + // A value (not a reference) to be captured by the block. + auto weakOwner = owner; + + // The documentation for `CFRunLoop` family API states that all of the methods are thread-safe. + // See "Thread Safety and Run Loop Objects" section of the "Threading Programming Guide" for more details. + mainRunLoopObserver_ = CFRunLoopObserverCreateWithHandler( + NULL /* allocator */, + toCFRunLoopActivity(activities_) /* activities */, + true /* repeats */, + 0 /* order */, + ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + auto strongOwner = weakOwner.lock(); + if (!strongOwner) { + return; + } + + this->activityDidChange(toRunLoopActivity(activity)); + }); + + assert(mainRunLoopObserver_); +} + +PlatformRunLoopObserver::~PlatformRunLoopObserver() +{ + stopObserving(); + CFRelease(mainRunLoopObserver_); +} + +void PlatformRunLoopObserver::startObserving() const noexcept +{ + CFRunLoopAddObserver(runLoop_, mainRunLoopObserver_, kCFRunLoopCommonModes); +} + +void PlatformRunLoopObserver::stopObserving() const noexcept +{ + CFRunLoopRemoveObserver(runLoop_, mainRunLoopObserver_, kCFRunLoopCommonModes); +} + +bool PlatformRunLoopObserver::isOnRunLoopThread() const noexcept +{ + return CFRunLoopGetCurrent() == runLoop_; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/scheduler/SchedulerToolbox.h b/ReactCommon/fabric/scheduler/SchedulerToolbox.h index 87462d8937..0f56dbcccb 100644 --- a/ReactCommon/fabric/scheduler/SchedulerToolbox.h +++ b/ReactCommon/fabric/scheduler/SchedulerToolbox.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace facebook { namespace react { @@ -36,6 +37,11 @@ struct SchedulerToolbox final { */ RuntimeExecutor runtimeExecutor; + /* + * Represent connections with a platform-specific UI run loops. + */ + RunLoopObserver::Factory mainRunLoopObserverFactory; + /* * Asynchronous & synchronous event beats. * Represent connections with the platform-specific run loops and general