Layout Events: throttle layout events sent to same node repeatedly

Summary:
Under Fabric only, we can enter an infinite layout loop where the emitted layout event oscillates between two height values that are off by a very small amount.

The cause is, in part, components that use layoutEvents to determine their dimensions: for example, using onLayout event "height" parameters to determine the height of a child. If the onLayout height changes rapidly, the child's height will change, causing another layout, ad infinitum.

This might seem like an extreme case but there are product use-cases where this is done in non-Fabric and layout stabilizes quickly. In Fabric, currently it may never stabilize.

Part of this is due to a longstanding issue that exists in Fabric and non-Fabric, that we cannot immediately fix: If in a single frame, C++ emits 100 layout events to ReactJS, ReactJS may only process 50 before committing the root. That will trigger more layout events, even though product code has only partially processed the layout events. At the next frame, the next 50 events will be processed which may again change the layout, emitting more events... etc. In most cases the tree will converge and layout values will stabilize, but in extreme cases in Fabric, it might not.

Part of this is because Fabric does not drop *stale* layout events. If 10 layout events are dispatched to the same node, it will process all 10 events in older. Non-Fabric does not have this behavior, so we're changing Fabric to drop stale events when they queue up.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D23719494

fbshipit-source-id: e44a3b3e40585b59680299db3a4efdc63cdf0de8
This commit is contained in:
Joshua Gross 2020-09-17 13:17:48 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 0599742db8
Коммит ee38751975
3 изменённых файлов: 31 добавлений и 10 удалений

Просмотреть файл

@ -46,16 +46,31 @@ void ViewEventEmitter::onLayout(const LayoutMetrics &layoutMetrics) const {
lastLayoutMetrics_ = layoutMetrics; lastLayoutMetrics_ = layoutMetrics;
} }
dispatchEvent("layout", [frame = layoutMetrics.frame](jsi::Runtime &runtime) { std::atomic_uint_fast8_t *eventCounter = &eventCounter_;
auto layout = jsi::Object(runtime); uint_fast8_t expectedEventCount = ++*eventCounter;
layout.setProperty(runtime, "x", frame.origin.x);
layout.setProperty(runtime, "y", frame.origin.y); // dispatchUniqueEvent only drops consecutive onLayout events to the same
layout.setProperty(runtime, "width", frame.size.width); // node. We want to drop *any* unprocessed onLayout events when there's a
layout.setProperty(runtime, "height", frame.size.height); // newer one.
auto payload = jsi::Object(runtime); dispatchEvent(
payload.setProperty(runtime, "layout", std::move(layout)); "layout",
return payload; [frame = layoutMetrics.frame, expectedEventCount, eventCounter](
}); jsi::Runtime &runtime) {
uint_fast8_t actualEventCount = eventCounter->load();
if (expectedEventCount != actualEventCount) {
// Drop stale events
return jsi::Value::null();
}
auto layout = jsi::Object(runtime);
layout.setProperty(runtime, "x", frame.origin.x);
layout.setProperty(runtime, "y", frame.origin.y);
layout.setProperty(runtime, "width", frame.size.width);
layout.setProperty(runtime, "height", frame.size.height);
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "layout", std::move(layout));
return jsi::Value(std::move(payload));
});
} }
} // namespace react } // namespace react

Просмотреть файл

@ -40,6 +40,7 @@ class ViewEventEmitter : public TouchEventEmitter {
private: private:
mutable std::mutex layoutMetricsMutex_; mutable std::mutex layoutMetricsMutex_;
mutable LayoutMetrics lastLayoutMetrics_; mutable LayoutMetrics lastLayoutMetrics_;
mutable std::atomic_uint_fast8_t eventCounter_{0};
}; };
} // namespace react } // namespace react

Просмотреть файл

@ -135,6 +135,11 @@ void UIManagerBinding::dispatchEvent(
auto payload = payloadFactory(runtime); auto payload = payloadFactory(runtime);
// If a payload is null, the factory has decided to cancel the event
if (payload.isNull()) {
return;
}
auto instanceHandle = eventTarget auto instanceHandle = eventTarget
? [&]() { ? [&]() {
auto instanceHandle = eventTarget->getInstanceHandle(runtime); auto instanceHandle = eventTarget->getInstanceHandle(runtime);