From 236db59a26d93d0a9bfe180dbdee7e04ba88a2a2 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 19 Feb 2025 07:10:22 -0800 Subject: [PATCH] Introduce EventBeat::unstable_setInduceCallback to batch sync state updates (#49520) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49520 changelog: [internal] Adds a new callback to EventBeat that is triggered in EventBeat::induce (that is when the UI thread is about to go IDLE). This will make state updates applied synchronously before the OS has a chance to paint but they will be batched. If two state updates happen in single UI tick, the will be executed at the end of UI's run loop. The state updates are still not batched into a single commit. If two state updates happen on the same component, they are coalesced. If two state updates happen on two different components, they will generate two transactions. This is a follow up optimisation. This feature is hidden behind `ReactNativeFeatureFlags::enableSynchronousStateUpdates` which is a preexisting feature flag. The feature flag was not rolled out yet and is not being used in any QEs at the moment. Reviewed By: rubennorte Differential Revision: D69848997 fbshipit-source-id: 870fd21f1d9d73d84861ddeac95b932a7fe1f5fe --- .../ReactCommon/react/renderer/core/EventBeat.cpp | 8 ++++++++ .../ReactCommon/react/renderer/core/EventBeat.h | 10 ++++++++++ .../react/renderer/core/EventDispatcher.cpp | 7 +------ .../ReactCommon/react/renderer/core/EventQueue.cpp | 9 ++++++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp index a7fe0cd7f00..cdb05f4719f 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp @@ -37,11 +37,19 @@ void EventBeat::setBeatCallback(BeatCallback beatCallback) { beatCallback_ = std::move(beatCallback); } +void EventBeat::unstable_setInduceCallback(std::function callback) { + induceCallback_ = std::move(callback); +} + void EventBeat::induce() const { if (!isEventBeatRequested_) { return; } + if (induceCallback_) { + induceCallback_(); + } + isEventBeatRequested_ = false; if (isBeatCallbackScheduled_) { diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h index ae88c8a5f51..de5cabccbe0 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h @@ -121,6 +121,15 @@ class EventBeat { */ void setBeatCallback(BeatCallback beatCallback); + /* + * The callback will be executed once a consumer (for example EventQueue) + * calls either `EventBeat::request` or `EventBeat::requestSynchronous`. The + * callback will be executed on the UI thread. + * + * If not set, this is a no-op and callback won't be called. + */ + void unstable_setInduceCallback(std::function callback); + protected: /* * Induces the next beat to happen as soon as possible. @@ -129,6 +138,7 @@ class EventBeat { void induce() const; BeatCallback beatCallback_; + std::function induceCallback_; std::shared_ptr ownerBox_; /* diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp index fb18eea2488..8d5476b2137 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp @@ -7,7 +7,6 @@ #include "EventDispatcher.h" #include -#include #include #include "EventQueue.h" @@ -43,11 +42,7 @@ void EventDispatcher::experimental_flushSync() const { } void EventDispatcher::dispatchStateUpdate(StateUpdate&& stateUpdate) const { - if (ReactNativeFeatureFlags::enableSynchronousStateUpdates()) { - statePipe_(stateUpdate); - } else { - eventQueue_.enqueueStateUpdate(std::move(stateUpdate)); - } + eventQueue_.enqueueStateUpdate(std::move(stateUpdate)); } void EventDispatcher::dispatchUniqueEvent(RawEvent&& rawEvent) const { diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp index 6eee3cd102f..c8f9b5c9342 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp @@ -7,6 +7,7 @@ #include "EventQueue.h" +#include #include "EventEmitter.h" #include "ShadowNodeFamily.h" @@ -19,6 +20,10 @@ EventQueue::EventQueue( eventBeat_(std::move(eventBeat)) { eventBeat_->setBeatCallback( [this](jsi::Runtime& runtime) { onBeat(runtime); }); + + if (ReactNativeFeatureFlags::enableSynchronousStateUpdates()) { + eventBeat_->unstable_setInduceCallback([this]() { flushStateUpdates(); }); + } } void EventQueue::enqueueEvent(RawEvent&& rawEvent) const { @@ -84,7 +89,9 @@ void EventQueue::experimental_flushSync() const { } void EventQueue::onBeat(jsi::Runtime& runtime) const { - flushStateUpdates(); + if (!ReactNativeFeatureFlags::enableSynchronousStateUpdates()) { + flushStateUpdates(); + } flushEvents(runtime); }