Skip to content

Commit 792e450

Browse files
lunaleapsfacebook-github-bot
authored andcommitted
Introduce hysteresis window (#53345)
Summary: Pull Request resolved: #53345 Changelog: [Internal] - Introduce hysteresis window that is nested between the prerender and hidden window sizes. Currently set to enlargening the prerender window by hysteresis ratio When a VirtualView intersects with the hysteresis window, it mode remains unchanged. This prevents us dispatch mode changes for things like overscroll. I put the hysteresis between prerender and hidden because we already avoid dispatching mode changes from visible -> prerender. For prerender -> visible, we use renderState Reviewed By: yungsters Differential Revision: D80511627 fbshipit-source-id: cd14256abc898e7120705e277147d52a06c865a9
1 parent 870836f commit 792e450

22 files changed

+178
-29
lines changed

packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,34 @@ - (void)dispatchOnModeChangeIfNeeded:(BOOL)checkForTargetRectChange
230230
scrollView.contentOffset.y,
231231
scrollView.frame.size.width,
232232
scrollView.frame.size.height);
233+
const CGFloat visibleWidth = thresholdRect.size.width;
234+
const CGFloat visibleHeight = thresholdRect.size.height;
235+
233236
if (CGRectOverlaps(targetRect, thresholdRect)) {
234237
newMode = RCTVirtualViewModeVisible;
235238
} else {
236239
auto prerender = false;
237240
const CGFloat prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio();
238241
if (prerenderRatio > 0) {
239-
thresholdRect = CGRectInset(
240-
thresholdRect, -thresholdRect.size.width * prerenderRatio, -thresholdRect.size.height * prerenderRatio);
242+
thresholdRect = CGRectInset(thresholdRect, -visibleWidth * prerenderRatio, -visibleHeight * prerenderRatio);
241243
prerender = CGRectOverlaps(targetRect, thresholdRect);
242244
}
243245
if (prerender) {
244246
newMode = RCTVirtualViewModePrerender;
245247
} else {
246-
newMode = RCTVirtualViewModeHidden;
247-
thresholdRect = CGRectZero;
248+
const CGFloat hysteresisRatio = ReactNativeFeatureFlags::virtualViewHysteresisRatio();
249+
if (_mode.has_value() && hysteresisRatio > 0) {
250+
thresholdRect = CGRectInset(thresholdRect, -visibleWidth * hysteresisRatio, -visibleHeight * hysteresisRatio);
251+
if (CGRectOverlaps(targetRect, thresholdRect)) {
252+
newMode = _mode.value();
253+
} else {
254+
newMode = RCTVirtualViewModeHidden;
255+
thresholdRect = CGRectZero;
256+
}
257+
} else {
258+
newMode = RCTVirtualViewModeHidden;
259+
thresholdRect = CGRectZero;
260+
}
248261
}
249262
}
250263

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<1e8a84a53072fa0c8665aead80d0199f>>
7+
* @generated SignedSource<<34c12f5a31aab5bfb874953f1beefef1>>
88
*/
99

1010
/**
@@ -432,6 +432,12 @@ public object ReactNativeFeatureFlags {
432432
@JvmStatic
433433
public fun useTurboModules(): Boolean = accessor.useTurboModules()
434434

435+
/**
436+
* Sets a hysteresis window for transition between prerender and hidden modes.
437+
*/
438+
@JvmStatic
439+
public fun virtualViewHysteresisRatio(): Double = accessor.virtualViewHysteresisRatio()
440+
435441
/**
436442
* Initial prerender ratio for VirtualView.
437443
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<d07316109dce5cc1ce7b0dc010962c3c>>
7+
* @generated SignedSource<<392da016e0bf4193b72c44a508811e10>>
88
*/
99

1010
/**
@@ -87,6 +87,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
8787
private var useShadowNodeStateOnCloneCache: Boolean? = null
8888
private var useTurboModuleInteropCache: Boolean? = null
8989
private var useTurboModulesCache: Boolean? = null
90+
private var virtualViewHysteresisRatioCache: Double? = null
9091
private var virtualViewPrerenderRatioCache: Double? = null
9192

9293
override fun commonTestFlag(): Boolean {
@@ -692,6 +693,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
692693
return cached
693694
}
694695

696+
override fun virtualViewHysteresisRatio(): Double {
697+
var cached = virtualViewHysteresisRatioCache
698+
if (cached == null) {
699+
cached = ReactNativeFeatureFlagsCxxInterop.virtualViewHysteresisRatio()
700+
virtualViewHysteresisRatioCache = cached
701+
}
702+
return cached
703+
}
704+
695705
override fun virtualViewPrerenderRatio(): Double {
696706
var cached = virtualViewPrerenderRatioCache
697707
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<6e384a07f0e7bc237f72b49a1268e16b>>
7+
* @generated SignedSource<<a0453230524ebca2bfb8fad656a6f54a>>
88
*/
99

1010
/**
@@ -162,6 +162,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
162162

163163
@DoNotStrip @JvmStatic public external fun useTurboModules(): Boolean
164164

165+
@DoNotStrip @JvmStatic public external fun virtualViewHysteresisRatio(): Double
166+
165167
@DoNotStrip @JvmStatic public external fun virtualViewPrerenderRatio(): Double
166168

167169
@DoNotStrip @JvmStatic public external fun override(provider: Any)

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<2daac10f81c205728db8127da4dd36cc>>
7+
* @generated SignedSource<<719706a983a073b6c286c49d993f7f80>>
88
*/
99

1010
/**
@@ -157,5 +157,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
157157

158158
override fun useTurboModules(): Boolean = false
159159

160+
override fun virtualViewHysteresisRatio(): Double = 0.0
161+
160162
override fun virtualViewPrerenderRatio(): Double = 5.0
161163
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<1a19e0569371a038ba8d3849fb26eb5c>>
7+
* @generated SignedSource<<594815ba6a984c460ab8bddd91c5cae2>>
88
*/
99

1010
/**
@@ -91,6 +91,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
9191
private var useShadowNodeStateOnCloneCache: Boolean? = null
9292
private var useTurboModuleInteropCache: Boolean? = null
9393
private var useTurboModulesCache: Boolean? = null
94+
private var virtualViewHysteresisRatioCache: Double? = null
9495
private var virtualViewPrerenderRatioCache: Double? = null
9596

9697
override fun commonTestFlag(): Boolean {
@@ -763,6 +764,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
763764
return cached
764765
}
765766

767+
override fun virtualViewHysteresisRatio(): Double {
768+
var cached = virtualViewHysteresisRatioCache
769+
if (cached == null) {
770+
cached = currentProvider.virtualViewHysteresisRatio()
771+
accessedFeatureFlags.add("virtualViewHysteresisRatio")
772+
virtualViewHysteresisRatioCache = cached
773+
}
774+
return cached
775+
}
776+
766777
override fun virtualViewPrerenderRatio(): Double {
767778
var cached = virtualViewPrerenderRatioCache
768779
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<5f84a9a17209b9ecb95e13e6567e7c3c>>
7+
* @generated SignedSource<<dfbd5e84392f1fda0e68324582c328b2>>
88
*/
99

1010
/**
@@ -157,5 +157,7 @@ public interface ReactNativeFeatureFlagsProvider {
157157

158158
@DoNotStrip public fun useTurboModules(): Boolean
159159

160+
@DoNotStrip public fun virtualViewHysteresisRatio(): Double
161+
160162
@DoNotStrip public fun virtualViewPrerenderRatio(): Double
161163
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualView.kt

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class ReactVirtualView(context: Context) :
4141
internal var modeChangeEmitter: VirtualViewModeChangeEmitter? = null
4242
internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
4343
internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()
44+
private val hysteresisRatio: Double = ReactNativeFeatureFlags.virtualViewHysteresisRatio()
4445

4546
private val onWindowFocusChangeListener =
4647
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
@@ -222,6 +223,8 @@ public class ReactVirtualView(context: Context) :
222223
bottom + offsetY,
223224
)
224225
scrollView.getDrawingRect(thresholdRect)
226+
val visibleHeight = thresholdRect.height()
227+
val visibleWidth = thresholdRect.width()
225228

226229
// TODO: Validate whether this is still the case and whether these checks are still needed.
227230
// updateRects will initially get called before the targetRect has any dimensions set, so if
@@ -260,16 +263,32 @@ public class ReactVirtualView(context: Context) :
260263
var prerender = false
261264
if (prerenderRatio > 0.0) {
262265
thresholdRect.inset(
263-
(-thresholdRect.width() * prerenderRatio).toInt(),
264-
(-thresholdRect.height() * prerenderRatio).toInt(),
266+
(-visibleWidth * prerenderRatio).toInt(),
267+
(-visibleHeight * prerenderRatio).toInt(),
265268
)
266269
prerender = rectsOverlap(targetRect, thresholdRect)
267270
}
268271
if (prerender) {
269272
newMode = VirtualViewMode.Prerender
270273
} else {
271-
newMode = VirtualViewMode.Hidden
272-
thresholdRect.setEmpty()
274+
val _mode = mode // local variable so Kotlin knows its not nullable
275+
if (_mode != null && hysteresisRatio > 0.0) {
276+
thresholdRect.inset(
277+
(-visibleWidth * hysteresisRatio).toInt(),
278+
(-visibleHeight * hysteresisRatio).toInt(),
279+
)
280+
if (rectsOverlap(targetRect, thresholdRect)) {
281+
// In hysteresis window, no change to mode
282+
newMode = _mode
283+
debugLog("dispatchOnModeChangeIfNeeded") { "hysteresis, mode=$newMode" }
284+
} else {
285+
newMode = VirtualViewMode.Hidden
286+
thresholdRect.setEmpty()
287+
}
288+
} else {
289+
newMode = VirtualViewMode.Hidden
290+
thresholdRect.setEmpty()
291+
}
273292
}
274293
}
275294
debugLog("dispatchOnModeChangeIfNeeded") {

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<1eff5bade524e3ad8e827ad4adb37f1a>>
7+
* @generated SignedSource<<16b12024bb363358ef09b9a42cb2fc97>>
88
*/
99

1010
/**
@@ -441,6 +441,12 @@ class ReactNativeFeatureFlagsJavaProvider
441441
return method(javaProvider_);
442442
}
443443

444+
double virtualViewHysteresisRatio() override {
445+
static const auto method =
446+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jdouble()>("virtualViewHysteresisRatio");
447+
return method(javaProvider_);
448+
}
449+
444450
double virtualViewPrerenderRatio() override {
445451
static const auto method =
446452
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jdouble()>("virtualViewPrerenderRatio");
@@ -786,6 +792,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useTurboModules(
786792
return ReactNativeFeatureFlags::useTurboModules();
787793
}
788794

795+
double JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio(
796+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
797+
return ReactNativeFeatureFlags::virtualViewHysteresisRatio();
798+
}
799+
789800
double JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio(
790801
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
791802
return ReactNativeFeatureFlags::virtualViewPrerenderRatio();
@@ -1023,6 +1034,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
10231034
makeNativeMethod(
10241035
"useTurboModules",
10251036
JReactNativeFeatureFlagsCxxInterop::useTurboModules),
1037+
makeNativeMethod(
1038+
"virtualViewHysteresisRatio",
1039+
JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio),
10261040
makeNativeMethod(
10271041
"virtualViewPrerenderRatio",
10281042
JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio),

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<05dba4cd49bd4f490e1ea943dd02cae0>>
7+
* @generated SignedSource<<54118ccd475a8bf1d7db83304b1f17d0>>
88
*/
99

1010
/**
@@ -231,6 +231,9 @@ class JReactNativeFeatureFlagsCxxInterop
231231
static bool useTurboModules(
232232
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
233233

234+
static double virtualViewHysteresisRatio(
235+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
236+
234237
static double virtualViewPrerenderRatio(
235238
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
236239

0 commit comments

Comments
 (0)