Repository navigation
FrictionSimulation decays faster than physics, and isn't ballistic #120340
Description
The simulation in FrictionSimulation doesn't correctly model the physics it's described as modeling, when constantDeceleration is nonzero. This is the simulation used for fling-scrolling in BouncingScrollPhysics / BouncingScrollSimulation, which is used by default on macOS.
(On iOS we also use BouncingScrollSimulation by default, but with a zero constantDeceleration, which doesn't trigger this issue.)
This has two consequences:
-
I suspect that the actual macOS behavior this simulation is intended to match does correctly model the physics. If so, then this is a fidelity issue.
-
The physically-incorrect behavior is not ballistic/restartable. (For context on that invariant, see Scroll protocol assumes ballistic scroll physics, and ClampingScrollPhysics isn't ballistic #120338.) The excess deceleration compared to the real physics starts at zero and grows over time, so restarting the simulation makes it decelerate less, and go farther in total.
- This is the inverse of the problem seen with ClampingScrollSimulation / on Android and other non-Apple platforms, in that other issue.
When the simulation is restarted very frequently, its behavior will closely approximate the real physics. If my suspicion above is correct, then this means frequent restarts actually give this scroll physics nearly the desired behavior, and it's when it doesn't get restarted that it behaves incorrectly.
Steps to Reproduce
I haven't yet tested this out empirically or compared to the native macOS scroll behavior, because I wanted to get this issue written down and posted for comparison with the related issues.
Here's why I say it doesn't correctly model the physics it's described as modeling:
- This class models "a particle affected by fluid drag." If the particle's current velocity is
$v$ , then one way that could behave is that its acceleration is$-kv$ , where$k$ is a drag coefficient.- (I think it's typically much steeper than that with real fluids, growing much faster than linearly in the velocity, like
$- k v^4$ . But let's leave that aside.)
- (I think it's typically much steeper than that with real fluids, growing much faster than linearly in the velocity, like
- With
constantDeceleration: 0, this is indeed whatFrictionSimulationdoes:-
dx(time)is_v * math.pow(_drag, time), or$x'(t) = v_0 e^{-kt}$ , where$v_0$ is the initial velocity. - Taking the derivative, we find
$x''(t) = -k v_0 e^{-kt}$ . - These satisfy
$x''(t) = - k x'(t)$ .
-
- With a positive
constantDeceleration… well, this parameter isn't really documented. But presumably it should do what its name says: it adds a constant amount of deceleration. - This means we should have
$x''(t) = -k x'(t) - c$ , where$c$ isconstantDeceleration. - But the actual deceleration is greater than that (meaning
$x''(t)$ is less than that):-
Now
dx(time)is_v * math.pow(_drag, time) - _constantDeceleration * time, or$x'(t) = v_0 e^{-kt} - c t$ . -
So
$x''(t) = - k v_0 e^{-kt} - c$ . -
But we were supposed to have an acceleration of
$-k x'(t) - c$ , which, calling it$x''_*$ , would be$x''_*(t) = - k v_0 e^{-kt} + k c t - c$ . -
So the deceleration is too big, by
$x''_*(t) - x''(t) = k c t$ , i.e._constantDeceleration * (-_dragLog) * time.
-
The crux of the bug is that we've made it so that the effect of constantDeceleration is to make the deceleration at time t be a constant amount more than it would otherwise have been at time t.
But the physics doesn't care how long the simulation has been running: from a physical perspective, adding a constant deceleration means the deceleration at time t should be a constant amount more than it would otherwise have been at velocity dx(t). (And position x(t), but this simulation isn't affected by position.)
Because over time the velocity and deceleration both decrease, this means that at time t we're starting from the deceleration that a simulation with zero constantDeceleration would have had at time t… when that simulation would have been going faster than this one is at time t, and therefore decelerating harder than it will when it later gets down to this one's current speed… which means that we take too large a deceleration as the baseline, and add constantDeceleration to that.
Activity
darshankawar commentedon Feb 9, 2023
Thanks for the report @gnprice
Can you please take a look at this issue and see if it resembles your case ?
If not, can you provide minimal code sample that helps to show current behavior you are reporting ?
gnprice commentedon Feb 9, 2023
Thanks. That is an interesting issue report, but I think it does not involve this issue. That one is about scrolling on iOS:
On iOS, the default Flutter scrolling uses
FrictionSimulationbut with aconstantDecelerationof zero. This issue only applies whenconstantDecelerationis nonzero. The one place the framework uses that is in the default scrolling for macOS.That would be a useful step. I haven't done it yet, but I'm working on related scrolling issues, and I may come back to this one later and do so if nobody does it first.
I discovered this issue from reading the code. As part of investigating #120338, I went and audited all the simulations the framework uses for scroll physics to try to confirm that each one was consistent with the scroll protocol's invariant #120341 which requires the scroll physics to make at least a certain kind of sense physically. One consequence of this bug is that this physics (Flutter's default macOS scroll physics) does not satisfy that invariant.
So the way to reproduce this issue would be with steps similar to #120338, comparing
scroll_overlaywith and withoutitemCount: 1000, but with the scroll physics Flutter defaults to on macOS.That can be done by either of:
scroll_overlayapp, usingflutter create.)ListViewconstructor call inlib/main.darta line likephysics: BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast, parent: RangeMaintainingScrollPhysics()). (Which I've copied from the framework'sScrollBehavior._bouncingDesktopPhysics.)Either approach should exercise this issue, but the first has the added usefulness that it would allow comparison to the native macOS behavior.
Anyway, that isn't a full repro recipe, just some notes for how to produce one. If someone would like to pick those up and investigate, that'd be great. If not, I may do so later.
I filed this issue because I'd found it in that audit related to #120338, and didn't want to leave it a mystery what bugs I had in mind when I said that everything else in the framework satisfies the invariant "modulo a few small bugs".
darshankawar commentedon Feb 10, 2023
Thanks for the update @gnprice
I added
macOSsupport toscroll_overlayproject and ran it on macos desktop, but getting below error in console although the app compiles and runs properly, but I guess due to the error, theplatform velocityvalue is not getting triggered. I tried with and withoutitemCountproperty.120340.mov
Maybe if you could help to fix the platform error, we can properly replicate this and make it actionable for scrolling team to take a look further.
gnprice commentedon Feb 10, 2023
Excellent, thanks for trying that!
I realize now that I wrote this part confusingly:
It looks like you got the app running on macOS, with the Flutter-driven list working. So the next step is to try a fling with a non-null
itemCountpresent, and with it absent, and compare how far the fling goes.That should exercise this bug:
itemCount.itemCount(so that the scroll simulation gets frequently restarted) goes further than a fling withoutitemCount(where it doesn't get restarted).This part shouldn't be affected by the error message you saw.
Then the other question for experiment would be to compare with the native macOS scroll physics. This bug is just as much a bug regardless of how the native physics compares, but that comparison would help decide what the right fix is.
That part will be more work, because it requires getting a native macOS list to compare with. The error message you saw is because the Dart code is expecting to talk to some platform code that's (a) showing a list in the background, (b) making that list respond in parallel to the same swipe gestures that Flutter gets, and (c) reporting its list's velocity to be displayed under "Platform velocity". The
scroll_overlayapp contains versions of that platform code for Android and iOS, but not other platforms.This probably isn't a ton of code — it looks like the iOS implementation is a little over a hundred lines, mostly in
ios/Runner/OverlayScrollView.mandios/Runner/PassiveGestureRecognizer.m. And it's possible that most of that can be reused for macOS, though I don't really know anything about macOS programming so that's a guess at best. But it will take some writing new code, and it would take me some time to figure out exactly how to write that code because I've never used the macOS SDK before.darshankawar commentedon Feb 10, 2023
itemCountoritemCount: null:120340.mov
itemCount: 1000:120340_1.mov
stable, master flutter doctor -v
2 remaining items
Document ScrollPhysics invariant requiring ballistic motion
Document ScrollPhysics invariant requiring ballistic motion (#120400)
flutter-triage-bot commentedon Jun 5, 2024
The
triaged-desktoplabel is irrelevant if there is noteam-desktoplabel orfyi-desktoplabel.