batch sync events based on event beats second try (#47255)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/47255

changelog: [internal]

Batching sync events into a single JavaScript task. The batching mechanism uses EventBeat's induce method.

Reviewed By: javache

Differential Revision: D64930580

fbshipit-source-id: dd68444ae5679155bb360353eec9e38dceb604ce
This commit is contained in:
Samuel Susla
2024-10-29 07:50:29 -07:00
committed by Facebook GitHub Bot
parent 741ae296d9
commit 63f8c3dcd6
7 changed files with 68 additions and 33 deletions
@@ -25,6 +25,14 @@ void EventBeat::request() const {
isRequested_ = true;
}
void EventBeat::requestSynchronous() const {
react_native_assert(
beatCallback_ &&
"Unexpected state: EventBeat::setBeatCallback was not called before EventBeat::requestSynchronous.");
isSynchronousRequested_ = true;
request();
}
void EventBeat::setBeatCallback(BeatCallback beatCallback) {
beatCallback_ = std::move(beatCallback);
}
@@ -37,7 +45,7 @@ void EventBeat::induce() const {
isRequested_ = false;
isBeatCallbackScheduled_ = true;
runtimeScheduler_.scheduleWork(
auto beat = std::function<void(jsi::Runtime&)>(
[this, ownerBox = ownerBox_](jsi::Runtime& runtime) {
auto owner = ownerBox->owner.lock();
if (!owner) {
@@ -49,6 +57,13 @@ void EventBeat::induce() const {
beatCallback_(runtime);
}
});
if (isSynchronousRequested_) {
isSynchronousRequested_ = false;
runtimeScheduler_.executeNowOnTheSameThread(std::move(beat));
} else {
runtimeScheduler_.scheduleWork(std::move(beat));
}
}
} // namespace facebook::react
@@ -24,6 +24,9 @@ namespace facebook::react {
* Event Beat serves two interleaving purposes: synchronization of event queues
* and ensuring that event dispatching happens on proper threads.
*
* You must set beat callback by calling `EventBeat::setBeatCallback` before
* calling `EventBeat::request` and `EventBeat::requestSynchronous`.
*
* EventBeat is meant to be subclassed by platform-specific implementations.
* The platform-specific implementation must call EventBeat::induce(). The
* appropriate time to call induce is when the host platform events were queued
@@ -68,14 +71,53 @@ class EventBeat {
EventBeat& operator=(const EventBeat& other) = delete;
/*
* Communicates to the Beat that a consumer is waiting for the coming beat.
* A consumer must request coming beat after the previous beat happened
* to receive a next coming one.
* Communicates to the Beat that a consumer (for example EventQueue) is
* waiting for the coming beat. A consumer must request coming beat after the
* previous beat happened to receive a next coming one.
*
* Callback, that was set by `setBeatCallback`, will be dispatched to
* JavaScript thread asynchronously via `RuntimeScheduler::scheduleWork`.
*
* tick (provided by host platform)
* UI │
* thread─┬───────┬─▼──────┬─────────────────────────────▶
* │request│ │induce│ ┌────────────┐
* └───────┘ └──────┴─▶│scheduleWork│
* └───────────┬┘
* JS │
* thread─────────────────────────────────▼─────────────┬▶
* │beat callback│
* └─────────────┘
*
*/
virtual void request() const;
/*
* The callback is must be called on the proper thread.
* Communicates to the Beat that a consumer (for example EventQueue) is
* waiting for the coming beat. A consumer must request coming beat after the
* previous beat happened to receive a next coming one.
*
* Callback, that was set by `setBeatCallback`, will be called on the thread
* where `induce` is called synchronously via via
* `RuntimeScheduler::executeNowOnTheSameThread`. Both threads are blocked
* until beat callback finishes.
*
* tick
* UI │
* thread─┬───────┬─▼──────┬─┬─────────────────────────┬▶
* │request│ │induce│ │executeNowOnTheSameThread│
* └───────┘ └──────┴▶├─────────────────────────┤
* JS │ beat callback │
* thread────────────────────┴─────────────────────────┴▶
* Both JS and UI thread are
* blocked.
*/
virtual void requestSynchronous() const;
/*
* 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 proper thread.
*/
void setBeatCallback(BeatCallback beatCallback);
@@ -93,6 +135,7 @@ class EventBeat {
private:
RuntimeScheduler& runtimeScheduler_;
mutable std::atomic<bool> isBeatCallbackScheduled_{false};
mutable std::atomic<bool> isSynchronousRequested_{false};
};
} // namespace facebook::react
@@ -18,11 +18,9 @@ namespace facebook::react {
EventDispatcher::EventDispatcher(
const EventQueueProcessor& eventProcessor,
std::unique_ptr<EventBeat> eventBeat,
RuntimeScheduler& runtimeScheduler,
StatePipe statePipe,
std::weak_ptr<EventLogger> eventLogger)
: eventQueue_(
EventQueue(eventProcessor, std::move(eventBeat), runtimeScheduler)),
: eventQueue_(EventQueue(eventProcessor, std::move(eventBeat))),
statePipe_(std::move(statePipe)),
eventLogger_(std::move(eventLogger)) {}
@@ -33,7 +33,6 @@ class EventDispatcher {
EventDispatcher(
const EventQueueProcessor& eventProcessor,
std::unique_ptr<EventBeat> eventBeat,
RuntimeScheduler& runtimeScheduler,
StatePipe statePipe,
std::weak_ptr<EventLogger> eventLogger);
@@ -15,11 +15,9 @@ namespace facebook::react {
EventQueue::EventQueue(
EventQueueProcessor eventProcessor,
std::unique_ptr<EventBeat> eventBeat,
RuntimeScheduler& runtimeScheduler)
std::unique_ptr<EventBeat> eventBeat)
: eventProcessor_(std::move(eventProcessor)),
eventBeat_(std::move(eventBeat)),
runtimeScheduler_(&runtimeScheduler) {
eventBeat_(std::move(eventBeat)) {
eventBeat_->setBeatCallback(
[this](jsi::Runtime& runtime) { onBeat(runtime); });
}
@@ -79,26 +77,14 @@ void EventQueue::enqueueStateUpdate(StateUpdate&& stateUpdate) const {
}
void EventQueue::onEnqueue() const {
if (synchronousAccessRequested_) {
// Sync flush has been scheduled, no need to request access to the runtime.
return;
}
eventBeat_->request();
}
void EventQueue::experimental_flushSync() const {
synchronousAccessRequested_ = true;
runtimeScheduler_->executeNowOnTheSameThread([this](jsi::Runtime& runtime) {
synchronousAccessRequested_ = false;
onBeat(runtime);
});
eventBeat_->requestSynchronous();
}
void EventQueue::onBeat(jsi::Runtime& runtime) const {
if (synchronousAccessRequested_) {
// Sync flush has been scheduled, let's yield to it.
return;
}
flushStateUpdates();
flushEvents(runtime);
}
@@ -29,8 +29,7 @@ class EventQueue {
public:
EventQueue(
EventQueueProcessor eventProcessor,
std::unique_ptr<EventBeat> eventBeat,
RuntimeScheduler& runtimeScheduler);
std::unique_ptr<EventBeat> eventBeat);
/*
* Enqueues and (probably later) dispatch a given event.
@@ -75,10 +74,6 @@ class EventQueue {
mutable std::vector<RawEvent> eventQueue_;
mutable std::vector<StateUpdate> stateUpdateQueue_;
mutable std::mutex queueMutex_;
// TODO: T183075253
RuntimeScheduler* runtimeScheduler_;
mutable std::atomic_bool synchronousAccessRequested_{false};
};
} // namespace facebook::react
@@ -102,7 +102,6 @@ Scheduler::Scheduler(
EventQueueProcessor(
eventPipe, eventPipeConclusion, statePipe, eventPerformanceLogger_),
std::move(eventBeat),
*runtimeScheduler,
statePipe,
eventPerformanceLogger_);