Skip to main content

Conversations

info

This page contains an auto-generated copy of discussions happened on various places. The original sources can be seen in scripts/generation/comments.

content

Main content starts roughly at 2022-09-15.

fzyzcjy2021-02-15T02:40:41Z GitHub

Correct the unit of file size from "kb" (maybe "kilo bits") to "KB"

The error message when enabling "invert oversized images" tells us how many extra space the image uses. That is quite helpful! However, when I read ...an additional 1234kb...I thought that means kilo bits (since in network programming kb and kB are definitely different and should be taken with care). However, after reading the source code, I suddenly realized that it is kilo bytes. Thus, this PR is here to ensure that people do not misunderstand the unit.

List which issues are fixed by this PR. You must list at least one issue. Fixes https://github.com/flutter/flutter/issues/76032

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy. N/A

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2021-02-15T02:40:43Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2021-02-18T01:11:47Z GitHub

@goderbauer Thanks! I only change 2 characters in a literal String, so I am a bit confused why it the tests are failed... I have looked at the CI logs, but seems no clues (since I am not very familiar with all the infrastructures of Flutter). Could you please give some hints?

goderbauer2021-02-18T01:13:49Z GitHub

There appear to be tests that verify the error message you're modifying. You can probably reproduce this locally by going into the packages/flutter directory and running flutter test there.

fzyzcjy2021-02-18T01:22:55Z GitHub

@goderbauer Thanks! I will have a look

fzyzcjy2021-02-18T05:47:06Z GitHub

@goderbauer Hi all checks have passed!

fzyzcjy2021-02-18T11:56:31Z GitHub

Wait a bit... The flutter-build fails? Hours ago it was all green...

Is that related to my PR or not? When I click "Details" I see a dashboard, which seems to have no relationship to me...

fzyzcjy2021-09-18T14:16:14Z GitHub

Fix duplicated documentation

Hi thanks for the lib! When reading doc, I find:

If multiple draw commands intersect with the clip boundary, this can result multiple draw commands intersect with the clip boundary, this can result in incorrect blending at the clip boundary.

That sounds like a typo (duplicated doc line) so I fixed it ;)

List which issues are fixed by this PR. You must list at least one issue. N/A but I can create one

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy. no

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above. -> see explanations
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2021-09-18T14:16:15Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2021-09-18T14:16:44Z GitHub

No tests needed - it changes the doc only

fzyzcjy2021-10-30T03:53:30Z GitHub

Fix typo that is introduced in hotfix 2.5.x

Well there is a typo when I examine the hotfix 2.5.x.

Fix #92744

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-01-19T00:10:48Z GitHub

Fixing bug when ListenableEditingState is provided initialState with a valid composing range, android.view.inputmethod.BaseInputConnection.setComposingRegion(int, int) has NPE

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots. See https://github.com/flutter/flutter/issues/96570

List which issues are fixed by this PR. You must list at least one issue. Fix https://github.com/flutter/flutter/issues/96570

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-01-19T00:10:50Z GitHub

This pull request was opened against a branch other than main. Since Flutter pull requests should not normally be opened against branches other than main, I have changed the base to main. If this was intended, you may modify the base back to master. See the Release Process for information about how other branches get updated.

Reviewers: Use caution before merging pull requests to branches other than main, unless this is an intentional hotfix/cherrypick.

flutter-dashboard2022-01-19T00:10:52Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

flutter-dashboard2022-01-19T00:10:52Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-01-19T00:42:51Z GitHub

Well cannot understand that CI error... Have searched keyword ListenableEditingState but no related things. The log seems also unrelated to my code change... Could anyone plz give some hints?

Possibly related @chinmaygarde @darshankawar

jason-simmons2022-01-19T01:13:49Z GitHub

The "Linux Unopt" CI error is asking for a change to the code formatting (see https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8824653695614065553/+/u/test:_format_and_dart_test/stdout)

The "build_and_test_linux_unopt_debug" CI error is a flaky test that has been seen before (see https://github.com/flutter/flutter/issues/95751)

fzyzcjy2022-01-19T01:23:05Z GitHub

@jason-simmons thanks! done

fzyzcjy2022-01-20T00:27:11Z GitHub

You are welcome!

kobi6662022-01-21T00:22:43Z GitHub

@chinmaygarde yo, so when can we expect this fix to reach stable?

is there an HF version?

FabioPaganoAPSystem2022-02-03T23:58:28Z GitHub

In flutter version "2.10.0-stable" the bug seems not resolved, I've tested with my app.

fzyzcjy2022-03-17T13:50:39Z GitHub

Fix wrong documentation: There is no LeaderLayer._lastOffset anymore

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

Seems that LeaderLayer has been refactored (#96144, #96486) and no more _lastOffset exists. So doc oudated.

List which issues are fixed by this PR. You must list at least one issue.

Well this just fix code comments. If you need PR I can create one.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

skia-gold2022-03-17T17:41:29Z GitHub

Gold has detected about 1 new digest(s) on patchset 1. View them at https://flutter-gold.skia.org/cl/github/100300

fluttergithubbot2022-03-18T22:10:25Z GitHub

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.
fluttergithubbot2022-03-18T23:30:17Z GitHub

This pull request is not suitable for automatic merging in its current state.

  • The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.
fzyzcjy2022-03-19T05:33:48Z GitHub

Fix simple typo: or or

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue. Seems no need for issue since just typo

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-03-19T05:33:50Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fluttergithubbot2022-03-21T19:10:12Z GitHub

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.
fzyzcjy2022-03-24T05:23:58Z GitHub

Fix FollowerLayer (CompositedTransformFollower) has null pointer error when using with some kinds of Layers

Please see https://github.com/flutter/flutter/issues/100670

List which issues are fixed by this PR. You must list at least one issue. Close #100670

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-03-24T05:24:00Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-03-24T05:24:32Z GitHub

Going to add tests, wait for a few minutes

fzyzcjy2022-03-24T05:36:45Z GitHub

Tests added

fzyzcjy2022-03-24T12:17:29Z GitHub

Fix typo (again)

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots. Just fix a typo (again)... Making a PR and requesting flutter devs to review it sounds resource-consuming, so I wonder whether there any approach that is lighter when I only want to fix a typo?

List which issues are fixed by this PR. You must list at least one issue. none

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-03-25T05:47:56Z GitHub

/cc @chunhtai @goderbauer (not sure whom to cc so sorry if disturbing)

everskies2022-03-28T18:10:32Z GitHub

Would be great if this could be merged into stable! This is our most occurring issue at the moment

fzyzcjy2022-03-28T23:55:30Z GitHub

Given my understanding of flutter, seems that we need to wait until next stable (which is roughly 2 months later, unfortunately) if it is not in 2.10. Or, we may make a cherry-pick request

fzyzcjy2022-03-30T10:09:40Z GitHub

Create ImageFilter.dilate/ImageFilter.erode

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots. Please see https://github.com/flutter/flutter/issues/100830

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/100830

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-03-30T10:48:14Z GitHub

(Test-only) Add tests for new ImageFilter.dilate/ImageFilter.erode in flutter engine

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots. Please see https://github.com/flutter/flutter/issues/100830

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/100830

Related: https://github.com/flutter/engine/pull/32334

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-03-30T10:49:42Z GitHub

This should fail until https://github.com/flutter/engine/pull/32334 is merged and flutter/flutter repo uses the latest flutter/engine.

fzyzcjy2022-03-31T03:05:44Z GitHub

The error seems to be:

02:50 +259 ~6 -1: test/html/paragraph/bidi_golden_test.dart: bidi with selection (DOM) [E]                                                                                                             
TimeoutException after 0:00:30.000000: Test timed out after 30 seconds. See https://pub.dev/packages/test#timeouts
/Users/chrome-bot/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/src/backend/invoker.dart 333:9 call
org-dartlang-sdk:///lib/async/zone.dart 1418:39 _rootRun
org-dartlang-sdk:///lib/async/zone.dart 1327:34 run
/Users/chrome-bot/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/src/backend/invoker.dart 331:5 _handleError
/Users/chrome-bot/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/src/backend/invoker.dart 326:8 _handleError
/Users/chrome-bot/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/src/backend/invoker.dart 287:9 call
org-dartlang-sdk:///lib/async/zone.dart 1426:12 _rootRun
org-dartlang-sdk:///lib/async/zone.dart 1327:34 run
/Users/chrome-bot/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.1/lib/src/backend/invoker.dart 286:38 call
org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 131:9 call
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart 1818:14 invokeClosure
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart 1852:1 <fn>

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'pub run test --chain-stack-traces'.

Thus maybe unrelated to my PR?

fzyzcjy2022-03-31T09:11:12Z GitHub

By the way, I see 25. Upload artifacts android-arm64-profile in the CI. So I wonder where is the uploaded artifacts? I want to have a try on them to ensure they are really correct when used in my app.

fzyzcjy2022-04-01T00:13:33Z GitHub

Hi, is there any updates?

dnfield2022-04-01T06:08:41Z GitHub

There were some CI issues today or yesterday - not sure if they're resolved yet but might be worth just merging up to head and pushing a new commit to kick CI.

fzyzcjy2022-04-01T09:50:53Z GitHub

@flar @dnfield It is green now!

image

fzyzcjy2022-04-02T01:43:15Z GitHub

Everything seems green now, what should I do next?

image

fzyzcjy2022-04-02T05:45:54Z GitHub

[Proposal]Let Flutter run animations at 60fps even if there are heavy widgets, possibly using React Fiber-like or suspend-like algorithm?

EDIT: Design proposal https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit?usp=sharing


Below (folded) are the initial proposal. However, I have realized the initial proposal has many drawbacks, and have raised new proposals. For example, the dual isolate (click to view that comment).

Details

Hi thanks for the framework! As we all know, React Fiber improves the performance and smoothness of React. Currently I am also observing some jank for Flutter app even after optimizing it using the tooltips in official doc, and I do hope there can be something similar to Fiber in Flutter side.

p.s. Some doc about react fiber: https://github.com/acdlite/react-fiber-architecture

I am interested in making contribution when having time as well.

flar2022-04-02T07:07:50Z GitHub

Everything seems green now, what should I do next?

image

In general, we are moving to a state where all PRs have to use the "Waiting for tree to go green" label to get submitted. I seem to recall a requirement that developers need 2 approving reviews from authorized reviewers to be eligible to be pushed, but I don't see the checks asking for that. I'll add the label and see if it goes in.

flar2022-04-02T07:09:30Z GitHub

If the bot comes along and removes the label, then tag @dnfield to complete a review and then the label should work.

Note that for now the tree is broken (the "luci-engine" failure in the list) so the label is currently in a wait state on that condition.

fluttergithubbot2022-04-02T07:11:05Z GitHub

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.
fzyzcjy2022-04-02T08:22:05Z GitHub

if I understand correctly you compare reconciling DOMs to rebuilding the elements in a widget tree and you are proposing to rebuild only certain elements the same way react-fiber prioritize

Possibly not only Widget build, but also layout, paint, etc. Since it is often the case that the layout/paint cost time.

give the developer the ability to set a widgets to low rendering priority

Sounds reasonable.

fzyzcjy2022-04-02T08:23:50Z GitHub

@flar I see.

@dnfield Hi could you please approve this PR such that it can be merged?

dnfield2022-04-04T02:52:05Z GitHub

Once the tree is open this should get landed by the bot.

fzyzcjy2022-04-04T06:50:37Z GitHub

@dnfield Thank you

maheshmnj2022-04-04T07:48:34Z GitHub

Hi @fzyzcjy, Thanks for filing the issue. I am quite not sure about the algorithm and its effectiveness. Labeling this issue for further insights from the team.

cc: @dnfield

fzyzcjy2022-04-04T08:07:41Z GitHub

@maheshmnj Hi thanks for the reply.

I have made an attempt about doing async rendering without modifying Flutter framework: https://github.com/fzyzcjy/flutter_smooth_render But the result is not very interesting - seems that we really need to dig into the framework itself instead of making a wrapper layer around it.

dnfield2022-04-04T14:56:51Z GitHub

I've been talking about something somewhat like this on the #hackers-framework channel in the past, but it's not a trivial problem to solve. I'd be interested in seeing more details about your designproposal and/or discussing on discord.

dnfield2022-04-04T16:37:22Z GitHub

And FWIW, this is likely a pretty significant amount of work to do, but there are some people who have already started looking at parts of it @hixie @goderbauer

fzyzcjy2022-04-05T00:31:09Z GitHub

@dnfield Hi thanks for the reply!

but there are some people who have already started looking at parts of it @Hixie @goderbauer

To avoid reinventing the wheel, I hope to listen to the parts before thinking about what to do next

fzyzcjy2022-04-06T01:43:57Z GitHub

We know dilate/erode is not implemented for the web platform, just like the compose filter. So what should I do (skip tests when in web?)?

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following UnimplementedError was thrown running a test:
ImageFilter.dilate not implemented for web platform.

When the exception was thrown, this was the stack:
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
../lib/ui/painting.dart 411:5 dilate
image_filter_test.dart.js 428:133 <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54 runBody
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 123:5 _async
image_filter_test.dart.js 427:80 <fn>
../packages/flutter_test/src/matchers.dart.js 4522:19 <fn>
fzyzcjy2022-04-06T01:44:29Z GitHub

/cc @dnfield

dnfield2022-04-06T03:19:14Z GitHub

Skip these on web with a reference to the bug filed to implement them for web.

fzyzcjy2022-04-06T03:56:01Z GitHub

Thanks, I will do that.

goderbauer2022-04-13T21:22:58Z GitHub

@fzyzcjy Can you update the tests per the suggestion above to make the checks happy?

fzyzcjy2022-04-14T00:10:32Z GitHub

Sure. I forgot it

flutter-dashboard2022-04-14T01:07:52Z GitHub

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #101036 at sha c39d7b80811c4903213d9559c764386ab26ada02

goderbauer2022-04-20T21:34:51Z GitHub

@fzyzcjy Can you take a look at the golden images linked in the previous comment to confirm that that's what you expected them to look like? One of the images looks empty, which seems odd...

fzyzcjy2022-04-21T00:23:06Z GitHub

@goderbauer I guess it is right, just b/c it erodes too much. I have updated the test so it will be less strange.

flutter-dashboard2022-04-21T01:04:39Z GitHub

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #101036 at sha e3d3659c4911cfa6caa84c6c3e5ec0b6cf69b146

fzyzcjy2022-04-21T03:10:40Z GitHub

The gold looks better. (Notice it has very very thin lines)

goderbauer2022-04-21T16:06:14Z GitHub

Thanks, @fzyzcjy

@dnfield or @flar could you review this one (and also approve the goldens) since you reviewed the upstream PR in the engine?

flar2022-04-22T01:05:42Z GitHub

Thanks, @fzyzcjy

@dnfield or @flar could you review this one (and also approve the goldens) since you reviewed the upstream PR in the engine?

I still don't see anything rendered in the erode test even if I click on it to see it full size. Am I clicking on the wrong link?

Actually, it looks like I was reviewing the goldens from before I requested a larger stroke width. Maybe they haven't been updated yet?

fzyzcjy2022-04-22T01:09:51Z GitHub

I still don't see anything rendered in the erode test even if I click on it to see it full size. Am I clicking on the wrong link? Actually, it looks like I was reviewing the goldens from before I requested a larger stroke width. Maybe they haven't been updated yet?

I guess the CI is even not executed after modifying strokewidth...

image

fzyzcjy2022-04-22T01:10:38Z GitHub

I guess the failure (and seems skipped executing all tests related to golden) is not caused by me. So maybe need to wait

flar2022-04-22T01:24:12Z GitHub

I guess the failure (and seems skipped executing all tests related to golden) is not caused by me. So maybe need to wait

I mentioned it on the infra Discord chat to see if someone can poke it.

flar2022-04-22T01:24:48Z GitHub

I would just LGTM to let it merge, but I'm worried it may merge with bad goldens and cause problems down the line.

fzyzcjy2022-04-22T01:52:23Z GitHub

Take your time. Since this is a test-only PR I am not that urgent :)

flar2022-04-22T21:54:20Z GitHub

It looks like you need to rebase in order to get the ci.yaml to gel with the latest CI recipes and then hopefully final goldens will be generated.

flutter-dashboard2022-04-23T00:25:28Z GitHub

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #101036 at sha 692b2715bd2b0cc9b076228b007c7506cc7d25e6

fzyzcjy2022-04-23T00:27:08Z GitHub

Now seems better

darrenaustin2022-05-03T23:41:44Z GitHub

Where the goldens for this approved? It looks like this is breaking the build:

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8815144942697521329/+/u/run_test.dart_for_framework_tests_shard_and_subshard_widgets/test_stdout

The following _Exception was thrown while running async test code:
Exception: Skia Gold received an unapproved image in post-submit
testing. Golden file images in flutter/flutter are triaged
in pre-submit during code review for the given PR.

Visit https://flutter-gold.skia.org// to view and approve
the image(s), or revert the associated change. For more
information, visit the wiki:
https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter

Debug information for Gold:
stdout: Given image with hash 94028cfacbe41a55af47edcddeff6540 for test widgets.image_filter_erode
Untriaged or negative image:
https://flutter-gold.skia.org/detail?test=widgets.image_filter_erode&digest=94028cfacbe41a55af47edcddeff6540


stderr: Test: widgets.image_filter_erode FAIL

When the exception was thrown, this was the stack:
#0 SkiaGoldClient.imgtestAdd (package:flutter_goldens_client/skia_client.dart:216:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

Can we just approve the new goldens? If not I will need to revert this to get the tree green again.

fzyzcjy2022-05-04T00:46:54Z GitHub

I do not have permission to approve the goldens, but seems that @flar has said that the goldens are ok, and he may have permission to approve

darrenaustin2022-05-04T01:20:57Z GitHub

It looks like it has been resolved and the tree is green again, so it's all good now.

flar2022-05-04T06:55:33Z GitHub

I did approve the goldens, so I'm not sure what happened.

fzyzcjy2022-05-11T13:19:05Z GitHub

Create takeExceptionDetails which is similar to takeException but allows the user to know details (e.g. stack trace)

Please see https://github.com/flutter/flutter/issues/103487

Close #103487

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-05-11T13:19:07Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-05-11T14:40:00Z GitHub

all tests passed.

image

goderbauer2022-05-12T22:19:28Z GitHub

What's the use case for needing that? What can you test now that you weren't able to test before?

fzyzcjy2022-05-12T23:08:30Z GitHub

What's the use case for needing that? What can you test now that you weren't able to test before?

Not to test indeed, but to inform. Firstly, in https://github.com/fzyzcjy/flutter_convenient_test readme, there is a 1-minute video demonstrating how this open source lib looks like. Then you see this lib shows errors etc in the command panel in an intuitive way for the programmers. When talking about "errors", surely we want to show stack traces as well, but currently for errors like golden mismatch I have to use takeException and stacktrace is lost.

Where this lib needs it: https://github.com/fzyzcjy/flutter_convenient_test/blob/f9d38c9fd870a5dab5a61755f66214b29caadf2f/packages/convenient_test_dev/lib/src/functions/command.dart#L99

fzyzcjy2022-05-16T01:42:29Z GitHub

👀

apoleo882022-05-24T10:43:33Z GitHub

Still get the error with:

Flutter 2.10.4 • channel stable 
ToolsDart 2.16.2DevTools 2.9.2

Specifically when I double-tap to select the text in a text field.

goderbauer2022-05-24T20:51:04Z GitHub

It is a little odd to expose that here and may lead to confusion in the API. Have you considered just overwriting FlutterError.onError in your framework yourself to catch the error directly?

fzyzcjy2022-05-24T23:12:42Z GitHub

Well I hope to reuse the logic about Flutter errors in testing...

Piinks2022-06-16T00:00:33Z GitHub

(Triage) Hi @fzyzcjy, does @goderbauer's suggestion suit your needs? I do not know that we want to merge this given the previous comments.

fzyzcjy2022-06-16T00:15:08Z GitHub

@Piinks Hi that does not suit. Btw FlutterError.onError cannot be rewritten outside a widgetTest since widgetTest overrides it again.

Piinks2022-06-16T20:32:44Z GitHub

FlutterError.onError cannot be rewritten outside a widgetTest since widgetTest overrides it again

I think that is by design. You can override it for the given test, or even use setUp to override it for a group of tests.

fzyzcjy2022-06-16T23:10:49Z GitHub

even use setUp to override it for a group of tests.

That seems not possible, surprisingly. Because FlutterError.onError is overrided in TestWidgetsFlutterBinding._runTest, which seems to be run after all setup?

Piinks2022-06-22T22:04:37Z GitHub

Oh right. That is true! It looks like we usually set this in the given test.

fzyzcjy2022-06-23T03:48:03Z GitHub

Yes, so it is not very convenient as we cannot do it in setup

goderbauer2022-06-29T22:34:44Z GitHub

We need to come up with another solution for this that doesn't require adding confusing API. This will need some more thinking. I suggest you describe what you want to achieve in an issue without proposing a concrete solution just yet.

fzyzcjy2022-07-01T00:01:19Z GitHub

Remove workaround since #66006 is fixed

The comments say "@jiahaog Remove when https://github.com/flutter/flutter/issues/66006 is fixed." and that issue is already fixed

List which issues are fixed by this PR. You must list at least one issue. #66006

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

jiahaog2022-07-01T06:46:48Z GitHub

Thank you! Can you take a look at the test failures? I think you need to remove https://github.com/flutter/flutter/blob/1add0d790d78918a560387c23802afd2979d15d7/packages/integration_test/test/binding_test.dart#L126-L131

fzyzcjy2022-07-01T06:49:50Z GitHub

You are right. I have removed them. (I forgot to check the CI just now...)

fzyzcjy2022-07-01T07:28:59Z GitHub

@jiahaog Now tests seem to pass

fzyzcjy2022-07-01T07:29:22Z GitHub

only remove code (no modified or added lines) to remove a feature or remove dead code.

So seems it is test-exempt

jiahaog2022-07-01T08:23:09Z GitHub

I ran a global presubmit (tap/458352815) for this change. This will require a g3fix internally.

jiahaog2022-07-01T08:27:44Z GitHub

Actually I'm not really sure why removing the repaint boundary results in a significant resize of the screenshot goldens. @dnfield can you take a quick look at shortn/_z3wWrukdZc?

fzyzcjy2022-07-01T10:02:19Z GitHub

What is tap/458352815 and shortn/_z3wWrukdZc...? Is it some internal links that I cannot view?

jiahaog2022-07-01T15:00:17Z GitHub

Oh don't worry about it, yes it's an internal system for running tests :)

dnfield2022-07-01T16:06:52Z GitHub

From what I recall, this needed some changes internally to pump widgets - cl/392929256

jiahaog2022-07-04T04:31:13Z GitHub

Ah thanks, I didn't realise that the fix (PR https://github.com/flutter/flutter/pull/88609) for issue https://github.com/flutter/flutter/issues/66006 was actually reverted in PR https://github.com/flutter/flutter/pull/88889 so the issue shouldn't be closed, and I completely forgot all about the discussion we had previously regarding this.

Checking through internal tests failures again, it seems like this change causes some test to be especially flaky. This is not the same issue which caused it to be reverted though. I think the next step would be for us to investigate further to find out what is wrong with that test. We can track that on https://github.com/flutter/flutter/issues/66006, which I just reopened. Sorry about that!

gonzalonm2022-08-05T09:49:49Z GitHub

Looks like this fix has not been included in stable channel yet. Does anybody know where it was included?

fzyzcjy2022-08-05T10:23:34Z GitHub

@gonzalonm May I know where you see it not in stable? from the release date it should have been stable

gonzalonm2022-08-05T13:49:40Z GitHub

I didn't see it in the release notes. Anyways, I could not reproduce the issue with version 3.0.0, so in that version it has been fixed. Thanks!

fzyzcjy2022-08-05T13:51:01Z GitHub

It may be in release notes of engine, while flutter/flutter repo (not flutter/engine) will not say this

fzyzcjy2022-08-10T05:54:50Z GitHub

Fix that RenderEditable (TextField) ignores offset in painting, making text selections shifted when offset is nonzero

I will start adding tests once flutter team thinks this PR is acceptable, since I am not sure whether the team like to fix it, and if the team does not like it I will not waste time on it :)


When implementing RenderBox.paint, we know we should respect the offset parameter. However, even though _paintContents respects it, the _paintHandleLayers does not - instead it completely ignores the offset parameter. This is logically wrong as it does not respect the definition and requirements of the function.

Luckily (or unluckily), currently it does not have visible harm yet. This is because RenderEditable is used by _Editable and future by EditableText. And, the _Editable's ancestors widgets do not provide any offset (because the widgets in the same Layer paint child without shifting). Thus, offset parameter is always zero in the current version of Flutter.

However, I am still proposing this PR because of the following reasons:

  1. It is logically wrong (violates definition of paint), so by definition it is a bug and we should fix it.
  2. It may be a visible bug in future releases. With adding more functionality to text fields, surely we cannot guarantee RenderEditable's ancestors always give a zero offset. (If we think so, I should immediately raise another PR with assert(offset==Offset.zero) to enforce it.)
  3. Users who need deep customization may directly use RenderEditable (since it is public), and even copy some of the source code and hack it (e.g. me). With their customizations, it is highly possible that the offset is no longer always zero, then they will find suddenly the text selections shifted weirdly. (Disclaimer: That is my case, and why I find this bug)

I have not come up with a way to make tests about this, because _Editable is private, and as mentioned above, it is logically wrong but not visible now. However, this PR does not add any new logic, so making existing tests pass IMHO may be sufficient.

Close #109289

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

P.S. https://discord.com/channels/608014603317936148/608018585025118217/1006808038914654218

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-08-10T06:06:31Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-08-12T14:19:32Z GitHub

@justinmc Hi, as discussed by @Hixie at here:

@Hixie : seems fine to me but @Justin McCandless (justinmc) is the person we should really ask

So I wonder whether this PR is ok for you? If so I will spend time adding tests.

justinmc2022-08-12T22:06:59Z GitHub

As discussed on Discord, the approach in this PR looks good to me. Please tag me or request a review from me when you finish the tests.

fzyzcjy2022-08-13T00:45:25Z GitHub

@justinmc New tests added and passed :)

image

fzyzcjy2022-08-15T07:28:15Z GitHub

@justinmc Hi, is there any updates?

fzyzcjy2022-08-16T06:47:15Z GitHub

All tests have passed

image

fzyzcjy2022-08-18T01:45:53Z GitHub

gradlew/gradlew.bat should not be gitignored according to official doc

Close https://github.com/flutter/flutter/issues/109749

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

jonahwilliams2022-08-18T02:32:11Z GitHub

This is the repo's gitignore, not a template file gitignore.

fzyzcjy2022-08-18T02:46:09Z GitHub

I know. The problem is, maybe flutter repo itself also need to follow gradle's official suggestion?

jonahwilliams2022-08-18T02:52:23Z GitHub

We've done this historically because the tool can handle re-generating this and to avoid churn in the repo when the contents of this file changes.

jonahwilliams2022-08-18T16:42:31Z GitHub

I'm going to close this PR, as we don't intend to accept this change. If something changes we can always reconsider

fzyzcjy2022-08-18T22:51:33Z GitHub

I see. Thanks all the same

fzyzcjy2022-08-24T01:11:09Z GitHub

Fix Image's logical flow which disposes its image too early, causing errors such as "Cannot clone a disposed image"

Close #110129

Please see the issue for paragraphs about the bug cause and fix etc

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-08-24T01:14:53Z GitHub

👀 Any updates here after two months? "workaround" does not sound very good so hoping it can be removed

fzyzcjy2022-08-24T02:01:02Z GitHub

The test, without the fix, fails. This verifies the PR is needed.

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8804990179272978753/+/u/run_test.dart_for_framework_tests_shard_and_subshard_widgets/test_stdout

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following StateError was thrown building KeyedSubtree-[#c5088]:
Bad state: Cannot clone a disposed image.
The clone() method of a previously-disposed Image was called. Once an Image object has been
disposed, it can no longer be used to create handles, as the underlying data may have been released.

When the exception was thrown, this was the stack:
#0 Image.clone (dart:ui/painting.dart:1808:7)
#1 RawImage.createRenderObject (package:flutter/src/widgets/basic.dart:5951:21)
#2 RenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5744:52)
... Normal element mounting (10 frames)
#12 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3883:16)
#13 Element.updateChild (package:flutter/src/widgets/framework.dart:3606:20)
#14 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4920:16)
#15 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5060:11)
#16 Element.rebuild (package:flutter/src/widgets/framework.dart:4617:5)
#17 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2687:19)
#18 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1244:19)
#19 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:374:5)
#20 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
#21 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
#22 AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:1093:9)
#25 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#26 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1079:27)
#27 WidgetTester.pump.<anonymous closure> (package:flutter_test/src/widget_tester.dart:618:53)
#30 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#31 WidgetTester.pump (package:flutter_test/src/widget_tester.dart:618:27)
#32 main.<anonymous closure> (file:///b/s/w/ir/x/w/flutter/packages/flutter/test/widgets/image_test.dart:83:20)
<asynchronous suspension>
<asynchronous suspension>
(elided 5 frames from dart:async and package:stack_trace)

════════════════════════════════════════════════════════════════════════════════════════════════════

02:21 +3329 ~2 -1: /b/s/w/ir/x/w/flutter/packages/flutter/test/widgets/image_test.dart: Verify Image does not use disposed handles [E]
Test failed. See exception logs above.
The test description was: Verify Image does not use disposed handles

══╡ EXCEPTION CAUGHT BY WIDGET INSPECTOR ╞══════════════════════════════════════════════════════════
fzyzcjy2022-08-24T03:04:53Z GitHub

All tests have passed

image

dnfield2022-08-25T00:07:05Z GitHub

LGTM once tests are passing :)

fzyzcjy2022-08-25T02:16:37Z GitHub

@dnfield Tests are passing :)

image

fzyzcjy2022-08-26T00:23:22Z GitHub

What should I do now 👀

dnfield2022-08-26T00:32:39Z GitHub

Sorry, thought I had added the label!

auto-submit2022-08-26T00:32:57Z GitHub
  • Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.
auto-submit2022-08-26T00:32:58Z GitHub

Validations Fail.

fzyzcjy2022-08-26T00:36:33Z GitHub

@dnfield That's OK. Oops seems to need one more reviewer

fzyzcjy2022-09-01T08:00:30Z GitHub

Improve performance by removing redundant null checks on hot paths

As we know, RenderObject.constraints field is called frequently, because every performLayout usually reads that. However, by looking at the code, it seems that it is having a redundant null check, which reduces the performance.

List which issues are fixed by this PR. You must list at least one issue. Close https://github.com/flutter/flutter/issues/110764

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-01T08:00:55Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Hixie2022-09-01T08:23:54Z GitHub

Did you run any benchmarks by any chance to verify if this has any effect?

fzyzcjy2022-09-01T08:57:31Z GitHub

@Hixie I have done some further experiments: Look into its assembly code. The Dart compiler is much smarter than I thought before - it generates completely the same assembly!

https://godbolt.org/z/rKo77K5vn

image

wangying34262022-09-08T02:57:10Z GitHub

@fzyzcjy Any update please? We are also interested in this feature.

fzyzcjy2022-09-08T03:06:09Z GitHub

@wangying3426 Well, no updates from me since I want to firstly listen to the "who have already started looking at parts of it @Hixie @goderbauer"

Hixie2022-09-12T23:23:41Z GitHub

@fzyzcjy thanks for looking that deeply! That's awesome news.

fzyzcjy2022-09-13T00:06:01Z GitHub

😄

fzyzcjy2022-09-13T23:11:13Z GitHub

Add my name to authors list

Please see https://github.com/flutter/flutter/pull/90413#issuecomment-1245628544 for details :)

I have made a few merged PRs, including several bug fixes in flutter/flutter and also a little feature in flutter/engine as well.

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

auto-submit2022-09-13T23:15:04Z GitHub

auto label is removed for flutter/flutter, pr: 111522, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-09-13T23:15:04Z GitHub

auto label is removed for flutter/flutter, pr: 111522, due to Validations Fail.

fzyzcjy2022-09-13T23:26:25Z GitHub

The Offstage has strong relationship with debugVisitOnstageChildren: When using Offstage widget, the widget subtree of debugVisitOnstageChildren is changed, and widget testers will not find an offstage widget. In addition, when we are talking about the word "offstage", we may need to refer to both pages, because they have slightly different meanings.

This origins partially from https://github.com/flutter/flutter/pull/111479#issuecomment-1245534877. Indeed, when talking about "offstage widgets", I quickly remembered and opened Offstage page, but never realize there is another page about debugVisitOnstageChildren, which even has a bit different explanation from Offstage. This PR fixes this problem.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-13T23:26:59Z GitHub

Hmm seems to need one more PR approval...

JsouLiang2022-09-14T07:04:33Z GitHub

@Hixie @goderbauer @dnfield How do you think about this fiber proposal?

dnfield2022-09-14T16:21:36Z GitHub

No one has come up with a workable proposal at this point in time. I think it's worth doing but it's not my top priority at the moment.

fzyzcjy2022-09-14T23:11:50Z GitHub

but it's not my top priority at the moment.

As mentioned earlier, I am willing to PR and contribute. But surely need some suggestions and discussions prior to start implementing :)

Btw I am not thinking about strictly implementing Fiber, since web model is not the same as Flutter model, but something inspired by it that can make our animations smoother.

fzyzcjy2022-09-15T00:10:00Z GitHub

@Piinks green now :)

JsouLiang2022-09-15T02:56:03Z GitHub

but it's not my top priority at the moment.

As mentioned earlier, I am willing to PR and contribute. But surely need some suggestions and discussions prior to start implementing :)

Btw I am not thinking about strictly implementing Fiber, since web model is not the same as Flutter model, but something inspired by it that can make our animations smoother.

I think so. Maybe we can create a document and then a detailed description

  • the fiber node archive and it can be interrupted by reconciler
  • how does the Fiber reconciler work
  • how does the flutter framework need to do and how to design

How do you think about it? @fzyzcjy

fzyzcjy2022-09-15T03:01:06Z GitHub

@JsouLiang Maybe we can create a document and then a detailed description

Sure! Maybe we can firstly discuss about it (maybe just here? - just like how I have seen many Dart/Flutter design discussions happen) and a detailed doc after we draw a (draft) conclusion

how does the flutter framework need to do and how to design

Btw, fiber can make animations smoother, but if I understand correctly, the smoothness is because that specific animation is driven by css, not js. This is contrary to flutter. For example, a CircularProgressIndicator, or even a scrolling of ListView, is driven by Dart code. Thus, we cannot easily say "let's give control to flutter engine / android / ios / whatever once in a while when we are doing build/layout/paint/whatever". If we simply do so, we will not get a smooth animation automatically. Instead, we may need to find out a more sophisticated approach.

JsouLiang2022-09-15T03:27:32Z GitHub

but if I understand correctly, the smoothness is because that specific animation is driven by css, not js.

Yes, the CSS animation is driven by css, not through js That mean, the CSS associated with the HTML element can calculate the animation difference directly, without going through JS. @fzyzcjy

fzyzcjy2022-09-15T03:28:45Z GitHub

Yes, that is why fiber is so useful. Indeed it is like, the web ui is driven by two things - the JS and CSS. Fiber pause JS once in a while so CSS things can come in and animate.

xanahopper2022-09-15T03:28:53Z GitHub

@fzyzcjy Btw, fiber can make animations smoother, but if I understand correctly, the smoothness is because that specific animation is driven by css, not js. This is contrary to flutter. For example, a CircularProgressIndicator, or even a scrolling of ListView, is driven by Dart code. Thus, we cannot easily say "let's give control to flutter engine / android / ios / whatever once in a while when we are doing build/layout/paint/whatever". If we simply do so, we will not get a smooth animation automatically. Instead, we may need to find out a more sophisticated approach.

As you say, Web animation like css animate, Android ViewPropertyAnimator (maybe iOS also has similar animate mechanism), they all are driven by browser/system, but in Flutter it is driven by ourselves with all other business logic.

for more, Android's window transition animation is driven by WindowService seperately

fzyzcjy2022-09-15T03:38:50Z GitHub

@JsouLiang @xanahopper

So, it is possible we come up with something slightly different?

For example, if we want to make some animations faster, like CircularProgressIndicator and ListView-scrolling, is it possible to do the following: We give CircularProgressIndicator high priority, and it must be layout/paint at 60fps. In the meanwhile, all other widgets will run one layout/paint across multiple frames with suspending just like what Fiber does. In other words, when vsync comes, CircularProgressIndicator will do all the layout/paint job, while other widgets will continue working on its layout/paint but will pause once it is near 16ms. Then, we can see CircularProgressIndicator smooth at 60fps, while other widgets having similar rendering speed as before.

Btw, some side remarks that is less like Fiber: Here is a tool that defers Widgets from being built https://github.com/LianjiaTech/keframe. But I guess we can make it more fine-grained and with more improvements since we are going to modify the flutter framework itself. For example, (very rough draft idea), can we modify the layout phase (or paint, or others), such that it pauses layouting the remainder (and will do it in the next frame), and let painting and other phases go first?

dnfield2022-09-15T03:44:07Z GitHub

The hard part of all of this is to figure out how to do it without breaking existing Framework code.

I think it's probably possible, but it's not easy.

fzyzcjy2022-09-15T03:44:58Z GitHub

@dnfield without breaking existing Framework code

We are allowed to modify anything in Flutter, don't we :) Just not allowed to break existing API that is used by flutter users.

Then, maybe we can have a feature flag?

xanahopper2022-09-15T03:45:53Z GitHub

@fzyzcjy Yes, we all farmilar with KeFrame and has already applied some optimize like it.

I guess we can make it more fine-grained and with more improvements since we are going to modify the flutter framework itself.

I think this may the point we are going to discuss.

The hard part of all of this is to figure out how to do it without breaking existing Framework code.

I think it's probably possible, but it's not easy. @dnfield we cannot just stop because is not easy. if it is a right way to improve it.

fzyzcjy2022-09-15T03:46:35Z GitHub

we cannot just stop because is not easy. if it is a right way to improve it.

Same here :) I like challenging, i.e. exciting, work!

Nayuta4032022-09-15T03:50:41Z GitHub

@fzyzcjy I'm interested in this topic and have been trying to go in a direction where Keframe can make the best use of each 16.7ms, since now each item will take the full 16.7ms (even though it may only take 1ms on some good devices). I'm trying to count the time taken by individual items to determine how many items should be rendered in the next frame.

fzyzcjy2022-09-15T03:52:19Z GitHub

Continue from the animation proposal above, with @dnfield's "without breaking existing Framework code":

Maybe we can have a global flag, say, bool enableFiber = false. By default it is false, so users can use existing API freely without any change. When user manually set it to true, our new feature runs.

The API may be as simple as a Widget, say, HighPrioritySubTree(builder: (context, child) => build_your_subtree_here, child: put_static_child_here), just like animation builder widgets. That builder should wrap the CircularProgressIndicator in the example above. We may also add a CancelHighPrioritySubTree if needed. For example, when scrolling ListView, we may want the scrolling animation be at 60fps, while we have to accept that a big widget in ListView is slow to build. Then, we may wrap ListView with HighPrioritySubTree, and each child of ListView with CancelHighPrioritySubTree. By doing so, our ListView will be forcefully built at each frame, while its contents will be stale for a few frames.

fzyzcjy2022-09-15T03:54:47Z GitHub

@Nayuta403 I have had similar thoughts before. The problem is, build phase is not the most costly one. There are layout and paint phase, etc, as well. What's worse, Flutter has C++ engine code which rasterizes and flush to the screen. That one can take a long time in some cases (for example, in my own app, when there is a ton of bezier curves). A widget may, for example, have very short build phase time but very long C++ rasterize time.

xanahopper2022-09-15T04:00:38Z GitHub

The API may be as simple as a Widget, say, HighPrioritySubTree(builder: (context, child) => build_your_subtree_here, child: put_static_child_here), just like animation builder widgets. That builder should wrap the CircularProgressIndicator in the example above. We may also add a CancelHighPrioritySubTree if needed. For example, when scrolling ListView, we may want the scrolling animation be at 60fps, while we have to accept that a big widget in ListView is slow to build. Then, we may wrap ListView with HighPrioritySubTree, and each child of ListView with CancelHighPrioritySubTree. By doing so, our ListView will be forcefully built at each frame, while its contents will be stale for a few frames.

@fzyzcjy I agree with switch flag, but some individual widget may still look verbose. I'd rather like to add a optional parameter to base class Widget to specify it's build/layout/render priority. Than change default page transition widget, scrollable container to high priority and wrap its content to low priority.

xanahopper2022-09-15T04:06:56Z GitHub

And here is another case may need to be consider: the list. in general container such as page, content size has no effect with container and other siblings, but things are different in list. if we have different size of different item, we cannot just show placeholder with same size, scrolling when and after content item is building/layouting may cause a sudden change in list.

fzyzcjy2022-09-15T04:09:32Z GitHub

I'd rather like to add a optional parameter to base class Widget to specify it's build/layout/render priority.

That sounds good to me. With that flag, we can also very easily create the widgets I mention. Just like the repaintBoundary is a flag and we create a widget to set it.

fzyzcjy2022-09-15T04:12:13Z GitHub

if we have different size of different item, we cannot just show placeholder with same size, scrolling when and after content item is building/layouting may cause a sudden change in list.

That's true. keframe workaround by letting the developer specify a placeholder size manually. But surely, for complex list items, we can never predict the size in advance so it still "jumps" when real content loads.

Maybe this is inevitable, and we have to live with it? Or, maybe we just place background color on those non-built entries?

xanahopper2022-09-15T04:25:56Z GitHub

we have to live with it, but we can give some different solutions, such like allow jumps, background color or some other...

I remember that iOS has very high priority with scrolling. If we can get item's size before build, this may not be a problem.

pre-measure for many things is possible but we have two considerations:

  • it cannot block UI/main thread otherwise it means nothing
  • it should has slice cost for developer to do that

this may conflict with principle of Flutter for single pass measure……but I think it has already has many cases in practice against that, it may not be a big deal.

fzyzcjy2022-09-15T05:20:21Z GitHub

@xanahopper I am not sure whether that is another isolated problem, or we can directly solve it within our proposal about this issue. For example, if we are to add a pre-measure phase, we may add computeSomething in addition to existing computeLayout, computeDryLayout etc, and that may be orthogonal to this issue.

Btw, I suspect whether pre-measure can happen before build phase, since we even do not know the widget tree then. Maybe it can happen before layout phase?

fzyzcjy2022-09-15T05:47:02Z GitHub

@fzyzcjy reply to @Nayuta403 I have had similar thoughts before. The problem is, build phase is not the most costly one. There are layout and paint phase, etc, as well. What's worse, Flutter has C++ engine code which rasterizes and flush to the screen. That one can take a long time in some cases (for example, in my own app, when there is a ton of bezier curves). A widget may, for example, have very short build phase time but very long C++ rasterize time.

Just to make it a little bit more detailed: On the contrary, if my proposal above works, the following may happen -

  1. Animations are in perfect 60fps, since low-priority job auto pause when near timeout. If we use keframe or similar solution, and give too many widgets in one frame, our animation will stuck.
  2. No cpu cycles are wasted, because we will never early-pause but will only pause when near timeout. For example, suppose widget A needs 160ms to build+layout+paint+raster, then it will be done in (roughly) 10 frames. If we use keframe or similar solution, and give too little widgets in one frame, we are wasting cpu cycles.
  3. It avoids our need to measure, or guess, the time needed for a widget in build/layout/paint/raster phase. Just as I mentioned above, I personally find it hard to guess how long a widget will need in those phases, especially raster phase which is C++ and varies greatly on different CPU/GPUs (different phones).
  4. It is OK to have a non-separable widget that is heavy in one phase.
  5. It is automatic and declarative. Programmers only need to specify priorities and that's all.

Btw I also like your (@Nayuta403) keframe solution :) Just trying to propose something that we can make flutter even better

Nayuta4032022-09-15T06:09:38Z GitHub

@fzyzcjy Thank you, I think we all want to make Flutter better. ❤️

So I think of a few problems we might have to solve:

  1. How to get the current UI cost, I think we still need to know this information even if we put the animation in the high priority queue, so that we can determine when the low priority task should end.
  2. How does the ListVIew item handle sliding when there is no width and height information
  3. How Fiber builds interruptible. It might be a little easier for a ListVIew, because its items are siblings. But what about parent-child nodes like Container?
fzyzcjy2022-09-15T06:10:03Z GitHub

More thoughts here.

1. For CircularProgressIndicator, or high-priority widgets without low-priority children

A very draft idea:

We may have multiple sub-trees, i.e. have a forest, in flutter. In this example, CircularProgressIndicator may be subtree 1, and everything else may be subtree 2. The subtree 1 goes through build/layout/paint/raster etc for each frame, and subtree 2 may go slowly, i.e. suspend.

Suppose it needs 10 frames for subtree 2 to finish the whole build/layout/paint/raster process. Then, we just allow all inconsistent and dirty states to exist during that 10 frames. For example, a node may have several layouted children and several un-layouted children. Same goes for rasterizing etc. We also need to ensure nobody can mutate the state accidentally when they are dirty.

In addition, I think we may not need to add this suspend feature to the build phase, but only add to layout/paint/raster if possible, contrary to React. This is because, if the time-consuming operation is only at build phase, keframe or similar solutions should already work. It may be deep in the rendering pipeline that makes this proposed method more interesting.

Surely this is just a draft and brainstorm, and I am willing to hear any thoughts!

2. For ListView scrolling problem, or high-priority widgets with low-priority children

The problem is, those big low-priority children may need a lot of frames (say 10 frames) to build/layout/paint/rasterize, and during those 10 frames, their internal data structure are not ready for use. For example, we cannot let it to paint at 5th frame, because its layout tree (or layer tree or something like that) may have a child that has been layouted and another child that has not yet been layouted.

However, we are doing nothing but scrolling. Then what about simply raster cache the screen, and scrolling is nothing but shifting this ui.Image. More details can mimic this PR: https://github.com/flutter/flutter/pull/106621 In that PR, during a "zoom page transition", no real widgets are built in each frame. Instead, a ui.Image snapshot is taken in the first frame, and during the whole transition we are just zooming that Image. Our solution is different from #106621, though. In that PR, no work is done during the whole page transition, but in our case, we can perform useful build/layout/paint/raster in the remaining time of each frame.

This solution also has some spirit similar to React Fiber: In Fiber, our JS-driven DOM elements are freezed indeed, and it is the CSS animation that still works. In our case, the "scrolling ui.Image" is a bit mimic a scrolling CSS animation.

fzyzcjy2022-09-15T06:13:41Z GitHub

How to get the current UI cost, I think we still need to know this information even if we put the animation in the high priority queue, so that we can determine when the low priority task should end.

Seems we do not need? We just blindly run whatever should be done next, and suspend when we are near 16ms.

We do need to let the the high priority job (say CircularProgressIndicator or ListView-scrolling) finish within the totally 16ms though.

My first thought is that, it would be best if we execute all phases of this subtree first, and then execute (and suspend when timeout) all phases of the second subtree in whatever time remain. Then we never need to get the cost.

If that is impossible, I wonder whether we can use some heuristics. We all know a CircularProgressIndicator should be very lightweight, so is a ListView-scrolling (if using the ui.Image approach above). We may also learn from the history.

fzyzcjy2022-09-15T06:15:32Z GitHub

How does the ListVIew item handle sliding when there is no width and height information

If using the approach mentioned above, it will just be blank. But not blank whenever there is a scrolling! Because we know Flutter has some cache extent for ListViews, we can also capture those cached extents in our ui.Image snapshot. Then, only if the following happens, we will see blank:

  1. The user scrolls so much that all cache extent are used up
  2. Our heavy widgets are so heavy that it even does not finish one frame up to now
fzyzcjy2022-09-15T06:18:41Z GitHub

How Fiber builds interruptible. It might be a little easier for a ListVIew, because its items are siblings. But what about parent-child nodes like Container?

As a very rough draft, I am considering yield. For example:

Iterable<void> performLayout() sync* {
yield* myFirstChild.layout();
some_computation_here;
yield* mySecondChild.layout();
}

Each yield point is suspendable.

IIRC, Redux Saga https://redux-saga.js.org/docs/introduction/BeginnerTutorial/ uses something similar to this.

Have not digged into React Fiber's source code yet. Have you checked it, how does it implement it?

But as I am not an expert in Dart compiler implementation, I am not sure about the performance penalty. (Hope it to be tiny!)

dnfield2022-09-15T06:31:44Z GitHub

A few pointers:

  • We cannot use sync generators, they create code that is large and slow.
  • A good canonical case here would be something like https://github.com/flutter/flutter/blob/master/dev/benchmarks/macrobenchmarks/lib/src/list_text_layout.dart. This ends up being janky because layout gets expensive for all that text (on a lower end phone it can easily take 20-30+ms just to layout all the text there, and the ListTile is a little deceptive because Material introduces expense - this is the kind of thing we want to figure out how to break up "automatically").
  • We should probably worry about prioritization of jobs until after we figure out how to sensibly budget and interrupt layout/painting/compositing. It doesn't matter what priority we'd want to give things if we can't do that, and it will probably be hard to come up with a good/fair prioritization scheme.
fzyzcjy2022-09-15T06:40:01Z GitHub

@dnfield Thanks for the ideas!

how to sensibly budget and interrupt layout/painting/compositing.

Quick answer to budget: As suggested in my comments above, we may not need to budget things (unlike the keframe-like solution). We just run the high-priority subtree (one with animation) until it finishes, and then run low-priority heavy subtree until whenever timeouts.

dnfield2022-09-15T06:42:05Z GitHub

Animations might not ever finish.

dnfield2022-09-15T06:42:34Z GitHub

And you might be animating the entire screen, e.g. for a route transition

fzyzcjy2022-09-15T06:43:27Z GitHub

Animations might not ever finish.

Well, I mean, run its build+layout+paint+raster fully instead of partially, not wait until there is no animations at all. For a CircularProgressIndicator it may take, say, <1ms. The rest 16.66-1=15.66ms will be given to low-priority subtree.

fzyzcjy2022-09-15T06:44:23Z GitHub

And you might be animating the entire screen, e.g. for a route transition

That sounds similar to the "a scrolling ListView" example above in https://github.com/flutter/flutter/issues/101227#issuecomment-1247625317. Just as mentioned there (and a little bit similar to https://github.com/flutter/flutter/pull/106621), we may take a snapshot of the heavy children, when the heavy widgets are rebuilding.

Nayuta4032022-09-15T06:52:42Z GitHub

Seems we do not need? We just blindly run whatever should be done next, and suspend when we are near 16ms.

Well, I think there should be a timer for how long the UI is currently built, since you also mentioned near 16ms, and the remaining time. I think it's easier (and that's what I'm going to try) if I just count the time spent on the framework. But as you say, the problem becomes more complicated when you consider the Raster thread.

fzyzcjy2022-09-15T06:55:41Z GitHub

Well, I think there should be a timer for how long the UI is currently built

I guess that is easy :) Maybe as simple as DateTime.now(), but probably there are something with higher precision etc.

Nayuta4032022-09-15T07:08:13Z GitHub

@Nayuta403 I have had similar thoughts before. The problem is, build phase is not the most costly one. There are layout and paint phase, etc, as well. What's worse, Flutter has C++ engine code which rasterizes and flush to the screen. That one can take a long time in some cases (for example, in my own app, when there is a ton of bezier curves). A widget may, for example, have very short build phase time but very long C++ rasterize time.

Yes, the timing of the statistical framework is not complicated, so I'm just trying to perform more tasks in a frame based on that time, regardless of Raster

fzyzcjy2022-09-15T07:10:21Z GitHub

@Nayuta403 Yes, the timing of the statistical framework is not complicated, so I'm just trying to perform more tasks in a frame based on that time, regardless of Raster

Sorry I do not quite get it. Are you using history timing information to estimate future timing?

Nayuta4032022-09-15T07:38:31Z GitHub

It's Keframe. I'm trying to count the time it takes to build/layout/paint item widgets so that each frame can be rendered as many times as possible (currently only one item per frame is rendered). (Am I making myself clear? (* ̄︶ ̄))

fzyzcjy2022-09-15T07:43:51Z GitHub

@Nayuta403 Clear :)

So seems that it is based on history. Then what if different items have (very) different time needed? That happens frequently IMHO. For example, suppose we have a ListView of posts. Post 1 may be a simple sentence so it is fast. Post 2 may be a long rich text paragraph and complex Paths etc, so it is slow.

Nayuta4032022-09-15T07:57:05Z GitHub

Yes, you are absolutely right, because now every task is setState() and only goes back to rendering the real widget on the next frame. One idea I have now is to make this task a real rendering task, similar to marking it as dirty and then executing drawFrame() to get the real time.

I can create an issue later to describe my thinking in detail and make the issue clearer :>

fzyzcjy2022-09-15T08:00:57Z GitHub

I can create an issue later to describe my thinking in detail and make the issue clearer :>

Looking forward to it :)

fzyzcjy2022-09-15T09:39:44Z GitHub

More about "how to build suspendable/interruptable", given that sync generators are slow

Is it possible we create a RenderSuspendable RenderObject (and corresponding Suspendable widget) which does the following:

  1. Users need to insert this widget into tree whenever they want suspendable. This may be reasonable given this spirit is similar to RepaintBoundary. And users will not need to insert too much, just insert at coarse subtrees.
  2. It behaves like a most naive proxy render box in normal cases.
  3. When time is near used up, and when RenderSuspendable.layout is called, it will not call child.layout, but instead set a flag (say needsLayoutLaterWhenPossible) and directly return. As for the return value, it may return the last layout size or user-defined default size (similar to what keframe does in widget-build level). By doing this, ancestor render objects will be happy and finish its layout function very fast.
  4. For a RenderSuspendable with needsLayoutLaterWhenPossible=true, when a new frame comes in, it will this.markNeedsLayout(), and thus get a chance to execute its layout method again in this new frame. If time is enough, it is done normally as in "2.", and the needsLayoutLaterWhenPossible is cleared; otherwise, it is done as in "3.".

Remark: May need a tweak a bit about layout's caching mechanism.

Remark: RenderSuspendable's sub-tree will not be redundantly layouted more than once. For example, say we have a Column with two Suspendable children, the first one has done layout, and the second one does not because of timeout. Then, when the next frame comes, Suspendable 2 calls markNeedsLayout, and Column starts performLayout. Then Suspendable 1 does have layout() called. However, we should recognize it (possibly flutter caching already does so?), and no need to layout its child at all.

Features

  • Solves the problem of "how to build suspendable/interruptable", without sync generators
  • No need to modify existing render objects, only need to add a new one

Potential problem

Unnecessary (i.e. redundant) relayout will happen for ancestors of Suspendable, until meeting a relayout-boundary.

Not sure how large the penalty is. If we can give near enough relayout boundary, looks like it is no problem? In addition, if we wrap all expensive subtrees into Suspendables, then the rest may be quite cheap.

How is layout / paint / rasterize related?

Done one by one. Layout of everything will be firstly finished. Only after that, we start doing painting of everything. And then rasterizing.

What about paint tree? layer tree? engine(c++) rasterizer?

TBD, I guess will be similar to above. Looking forward to hearing some feedbacks about the approach for layout first!

fzyzcjy2022-09-15T09:57:10Z GitHub

How can we paint UI onto screen, if we are in half-way of layout/paint/rasterize, and many nodes are dirty / half-way updated?

Basically I have two draft ideas:

Firstly, we may hack the Flutter engine. Let it keep the old content available until the new content is fully available.

Secondly, we may be able to solve it without big modifications to engine. We may just "take a screenshot" before starting the journey of heavy updating. For example, suppose we need 10 frames to fully build/layout/paint/rasterize this widget subtree. Then, we use the new toImageSync() to take a photo of it. Then, during the 10 frames, we can do anything to the render/layer/engineLayer trees, and whenever the parents let us to paint, we just canvas.drawImage() using that. After 10 frames when we are done, we will finally paint the new thing.

By the way, this also has a bonus about predictable time consumption. IMHO, the time of drawing (paint+rasterize+...) a ui.Image may be easily computed, given it is nothing but a rasterized image.


@dnfield Given that you implemented this great new toImageSync feature (https://github.com/flutter/engine/pull/33736), I have a question about its performance:

In the solution above, instead of painting normally, we may have to convert child into ui.Image for each and every paint call.

In other words, in pseudo code:

class OurRenderObject {
void paint() {
// child.paint(); // cannot do this

if (everything_is_not_dirty) {
image = toImageSync(child_render_tree); // save a screenshot
}

// ...do some expensive work here if time is sufficient...

canvas.drawImage(image);
}
}

So, will this have performance penalty or not?

fzyzcjy2022-09-15T13:37:39Z GitHub

Fix typo

Just fix 2 char typo... Hope there is a more lightweight method that creating a PR!

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-15T14:41:50Z GitHub

Looking forward to some early feedbacks about the proposal :)

Maybe /cc @dnfield @JsouLiang @Nayuta403 @xanahopper (based on today's activity)

fzyzcjy2022-09-16T00:57:12Z GitHub

P.S. I am starting to work on a prototype about smoothing the "layout" phase. Will report any progress I make :)

Code: https://github.com/fzyzcjy/flutter/tree/feat-smooth

fzyzcjy2022-09-16T04:06:27Z GitHub

Progress: 62ms -> 22ms for 99th build time of list_text_layout, and its limitations

(Limitations is discussed in the last section of this comment)

The list_text_layout is still too fast on my old android, so I enlarged its scale (have more items in column, more text in each item, etc) a little bit. Code is seen in https://github.com/fzyzcjy/flutter/commit/857210213531e76b3eb5c256a8ef3599ed434703. This yields:

{
"average_frame_build_time_millis": 2.8446626506024097,
"90th_percentile_frame_build_time_millis": 1.213,
"99th_percentile_frame_build_time_millis": 62.531,
"worst_frame_build_time_millis": 63.101,
"missed_frame_build_budget_count": 14,
"average_frame_rasterizer_time_millis": 3.296915662650604,
"90th_percentile_frame_rasterizer_time_millis": 8.09,
"99th_percentile_frame_rasterizer_time_millis": 13.82,
"worst_frame_rasterizer_time_millis": 15.178,
"missed_frame_rasterizer_budget_count": 0,
"frame_count": 249,
"frame_rasterizer_count": 249,
"new_gen_gc_count": 34,
"old_gen_gc_count": 4,
"frame_build_times": [

image

Then, I implement a proof-of-concept Suspendable. Code is at https://github.com/fzyzcjy/flutter/commit/0babd5b6856bc799c9f369bce75aada7c10fcd0b. Code diff can be found in https://github.com/flutter/flutter/compare/master...fzyzcjy:flutter:feat-smooth?expand=1.

It yields:

{
"average_frame_build_time_millis": 4.24028,
"90th_percentile_frame_build_time_millis": 17.769,
"99th_percentile_frame_build_time_millis": 22.235,
"worst_frame_build_time_millis": 23.829,
"missed_frame_build_budget_count": 41,
"average_frame_rasterizer_time_millis": 3.9516548672566385,
"90th_percentile_frame_rasterizer_time_millis": 8.949,
"99th_percentile_frame_rasterizer_time_millis": 11.202,
"worst_frame_rasterizer_time_millis": 11.604,
"missed_frame_rasterizer_budget_count": 0,
"frame_count": 225,
"frame_rasterizer_count": 226,
"new_gen_gc_count": 17,
"old_gen_gc_count": 4,

image


Limitations

This is just proof-of-concept and is very naive.

  • It only suspends the layout and build phase. (The build phase is wrapped inside layout phase by adding a LayoutBuilder.) Indeed, it does not suspend the paint or raster phase, which should be done in future work.
  • It paints nothing (i.e. do not call child.paint) if a Suspendable is suspending. This will destroy the layer tree and C++ engine layer trees, making performance much worse. We should address this problem later, possibly by keeping the layer tree not used but not removed.
  • It lets the whole ancestors (up until relayout-boundary) to relayout in each frame.
  • Overhead will become non-neglectable, if we want it to run in 60fps. In other words, if we want each frame to be under 16ms, looks like we will only have <10ms for handling the suspendable widgets (rough estimate, but anyway numbers differ on different phones). Then, the price of 60fps smooth animation is that, the suspendable needs longer time to be loaded.
  • The current implementation does not run suspendable layouts last. Instead, they are run inside non-suspendable layout. Thus, we have to set a "earlier" deadline (e.g. 12ms, instead of 16.6ms in the example above), and hope that the remaining job will finish quick enough.
  • Element.performLayout says, "In implementing this function, you must call layout on each of your children". But, when implementing Suspendable, we have to violate this. We will face troubles, or just minor changes are enough?
  • If a child under Suspendable mark itself as needed to relayout/rebuild, and there is relayout boundary between that child and Suspendable, then the suspending mechanism will not work at all.
  • Originally all code (implicitly) assume that, when a frame ends, build/didUpdateWidget has been called. But now this no longer holds. That will make a ton of widget fail to work, including those inside flutter framework, and many external packages. For example, those who assume this inside their addPostFrameCallback.
  • The demo does not yet provide any animations (e.g. a CircularProgressIndicator), so by merely looking at the screen, we cannot see it becomes much smoother ;)
rekire2022-09-16T04:58:43Z GitHub

Just curious how many approved PR are required? I also fixed something in the engine

JsouLiang2022-09-16T06:49:08Z GitHub

Furthermore, I think should we able to break the Build call if the Widget is complex and the Widget Build call is too deep and stalling?

@fzyzcjy

fzyzcjy2022-09-16T07:15:11Z GitHub

New idea: Dual isolates

(This comment is updated)

Advantages

The main goal is similar: No matter how heavy your widget build/layout/... is, animations/gestures should be 60fps.

It does not require existing Flutter/Dart code to accept new assumptions. For example, in the old proposal, the layout may not be called within a frame, and thus build will also not be called. This may violate many existing code. For example, addPostFrameCallback may assume build is done when post-frame.

On the contrary, the "Dual isolates" solution will not have those assumptions at all. It seems not to break existing explicit or implicit assumptions about the code. Except that it will make Dart isolate "freeze" once in a while - but that should not be a problem, since we are all happy with stop-the-world GC and OS's suspending a thread.

In addition, it should have much lower overhead, indeed almost zero. No wasted build/layout happens (unlike RenderSuspendable approach). No unnecessary tree destory and recreate happens.

Background

The approach above, with the minimal sample in https://github.com/flutter/flutter/issues/101227#issuecomment-1248894781, has many known problems which I am not sure whether can all be overcome. I will probably also experiment further on that path as well. At the same time, I find out a new approach without most problems above.

I am not an expert in flutter/engine, so please correct me if I am wrong!

Design

Originally, IMHO we have a UI thread, which runs both C++ code and Dart main isolate code. Now, we have three (but no worries, they will not be parallel most of the time!):

  • C++ UI thread.
  • Dart main isolate: Run everything you know, i.e. the heavy build/layout/paint/.... Say it takes 2 frames to finish.
  • Dart sidecar isolate: Run CircularProgressIndicator, or ShiftTheChild(for scrolling ListView, to be explained below).

An UML diagram is attached below (best read with text explanations here).

Here is what happens when a vsync comes in:

  • C++ ui thread receives the vsync. In the old days, it will call dart's DrawFrame. But now, it will set up a timer for a bit less than ~16.67ms (say 15ms), pause self thread, and call Dart main isolate's DrawFrame.
  • Dart main isolate's DrawFrame starts running. It runs build/layout happily.
  • At 15ms, timer wakes up C++ ui thread. C++ ui thread then immediately "pause" the dart main isolate. This is done by "safepoints". In other words, we insert safepoint() call to layout() function of Dart RenderObjects. And that function is a native function reading, say, a mutex lock. When C++ ui thread wants to pause the dart main isolate, it simply acquire the mutex. When Dart goes to the next safepoint() call, it will simply be pause there forever waiting to acquire the mutex (until next frame indeed).
  • C++ ui thread calls sidecar isolate to compute the whole build/layout/paint procedure. This is done serially now for simplicity, but should be easily parallizable with some locks.
  • Sidecar Dart code is a bit different from the traditional widget/renderobject/layers. Instead, it knows which EngineLayer it owns, and only mutates it. For example, for a CircularProgressIndicator in sidecar, it will know it owns a DisplayListLayer, and only modify pictures in it, without touching other layers. For a ShiftTheChild, it owns a OffsetLayer and modifies its offset.
  • Now go back to our C++ ui thread. We will simply utilize the current engine layer tree in C++, and the rest is the same, such as giving data to rasterizer thread and render to the screen.

This is not the end of story - notice our main isolate is still computing some layout and is hanging. Now suppose 2nd vsync comes in.

  • Again, C++ ui thread receives vsync. It notices there is still remaining job in main isolate. Then it just resume the main isolate, without telling it anything about the second frame. Thus, in the eyes of main isolate, it will think the whole phone just "freezed" for a few milliseconds without other problems, and will happily continue build/layout/etc.
  • Suppose the heavy job of the main isolate is finally finished in this frame. Then, it will do painting. In other words, it will mutate the Layer tree in C++ code. We deliberately put no safepoint() during painting, so the C++ layer tree will either be non-mutated or fully-mutated without intermediate case.
  • The rest is similar to the first frame, except that our engine layer tree is updated to the new one.

Further improvements

  • sidecar isolate should be executed concurrently
  • main isolate should also be executed concurrently, with locks protecting critical regions such as mutating the engine layer tree. But otherwise, it should run freely. By doing this, we are guaranteed that, we can let main isolate run using almost a full cpu core. On the contrary, the "RenderSuspendable" approach above will only give, say, 10.67ms out of 16.67ms for heavy widget build/layout, because it need (say) 6ms to paint/rasterize existing things.

What is ShiftTheChild

I want to solve the problem of "ListView scrolling". In other words, when scrolling a ListView, the widget build/layout may be arbitrary heavy, while we should get 60fps.

Thus, let us do the following:

ParentWidgets(
child: ShiftTheChild(
child: ListView.builder( ... )
)
)

Suppose ListView subtree takes 10 frames to rebuild/layout/etc, and suppose the user is scrolling it. Then, during the 10 frames, ShiftTheChild will receive data packets about user dragging and perform a shift (i.e. OffsetLayer's offset) to its child content. ShiftTheChild will be in the sidecar isolate.

P.S. It may not even be a widget or RenderObject, but may be built on some other lower level primitives mutating corresponding C++ engine Layer. But surely we can wrap those primitives and maybe create a RenderSidecar or something new, that should not be a problem.

Minimal example

I plan not to implement sidecar isolate in the minimal example. Instead, just create a C++ function that shifts an OffsetLayer in each frame, as if a sidecar isolate is doing so. This is because the sidecar isolate is nontrivial engineering work but is not the core problem.


UML Diagram

UML

fzyzcjy2022-09-16T07:15:58Z GitHub

@JsouLiang For the "RenderSuspendable" proposal, I guess we can have nested ones. For the "Isolates" proposal just now, I guess we do not have this problem - the main isolate will be paused at any safepoint, i.e. any layout function.

fzyzcjy2022-09-16T07:20:48Z GitHub

@dnfield @JsouLiang @Nayuta403 (and other engine masters)

For the new proposal, I hope to see some feedback... Since I am not an expert in flutter/engine (and few materials are about it on the internet). Thus:

  1. Is there any suggested materials (docs/articles/...) to understand the engine? How do you learn the engine?
  2. Does my proposal above looks OK?
xanahopper2022-09-16T09:23:36Z GitHub

@JsouLiang For the "RenderSuspendable" proposal, I guess we can have nested ones. For the "Isolates" proposal just now, I guess we do not have this problem - the main isolate will be paused at any safepoint, i.e. any layout function.

multiple isolates is one of my optimize and working in progress, the key to this is some build/layout callback function/method should not be called in non-main isolate, or just serialize/deseralize build/layout request and response to another isolate like a local RPC service. But! Multiple isolates may agains Flutter's principle, I don't sure whether it can be merged.

(Dude, you are really high-producing and I'm reading your new comments try to catch up

And for more, I may offer you some complex card widget case for benchmark.

xanahopper2022-09-16T09:59:40Z GitHub

For this Suspendable render, we may introduce structure like Fiber, I think it is Threaded tree.

First thing to drawing a frame including heavy/suspendable part is transform tree to a list (or just a threaded tree)

image

Render task 5 and 6 should and can be suspended at any place in it. (What if a widget/node cost timeout?)

we can tell from figure that suspendable is contagious, content in suspendable cannot be non-suspendable.

fzyzcjy2022-09-16T10:27:32Z GitHub

@xanahopper

multiple isolates is one of my optimize and working in progress

Looks interesting, could you please share the link? I have checked your github but seems cannot find anything. (All my Flutter work are done open-source and can be found at my github).

the key to this is some build/layout callback function/method should not be called in non-main isolate, or just serialize/deseralize build/layout request and response to another isolate like a local RPC service.

Could you please provide an example? Thanks

Btw, my solution does not use non-main isolate with callbacks :) Indeed, I only put CircularProgressIndicator and ShiftTheChild and things like that there. No normal user code should be done in the sidecar isolate, because otherwise it is quite unfriendly to the users (the sidecar isolate has no memory sharing w/ the main isolate).

So I hope it is not a blocker!

But! Multiple isolates may agains Flutter's principle, I don't sure whether it can be merged.

Could you please elaborate a little bit more?

My solution is still mainly single-isolate, and the sidecar isolate (as mentioned above) is just used very limitedly to support animations.

In addition, my multi-thread is still mostly serialized instead of parallel running. There are multiple threads, simply because I want to suspend/pause one thread easily.

Dude, you are really high-producing and I'm reading your new comments try to catch up

Haha take your time! It takes me a day thinking and trying all these things :)

And for more, I may offer you some complex card widget case for benchmark.

Sure, looking forward to that. Btw I also have very complex cases for my own app, but I decide to start from the simple - you know, one of the fundamental rules in software engineering.

fzyzcjy2022-09-16T10:35:09Z GitHub

@xanahopper's comment in https://github.com/flutter/flutter/issues/101227#issuecomment-1249172293

If I understand correctly, the figure is a bit like a extended version of my prototype https://github.com/flutter/flutter/issues/101227#issuecomment-1248894781 above.

The question is, how are you going to transform a tree to a (suspendable) list - In other words, for example, how to make subtree rooted at 5 become a list that can be paused?

I have proposed using yield in performLayout but @dnfield mentioned it is very slow. Given that your suspendable widgets are contagious, we cannot use yield at all (otherwise we will be using it in a big subtree).

Then in my prototype above I decide to let a Suspendable return zero size when it is near timeout (note: different from your figure, but the problem to solve is similar). But such approach seems not possible for your proposal.

This is solved very easily with my "Dual isolates" proposal. It just call safepoint() in every RenderObject's layout(). Then, whenever the C++ code wants to suspend Dart, C++ will just let safepoint() hang (probably by occupying a mutex). Then Dart code is just hang there, without doing anything, without feeling anything. In Dart code's view it is like a stop-the-world GC indeed.

fzyzcjy2022-09-16T10:43:25Z GitHub

@wangying3426 https://github.com/flutter/flutter/issues/101227#issuecomment-1240156979

Any update please? We are also interested in this feature.

Btw I forget to mention you (too above in the comments). Yes, now I have many updates :)

xanahopper2022-09-16T11:08:52Z GitHub

Multiple isolates and optimize with it is before the specification phase, just for you known that we both have the same idea that I and my colleagues are working on it. for very early part we think that this way may need modify engine or even the Dart VM.

Last time we coming with a issue #110063 and got a refuse with tough attitude.

Transform

When we need build a Widget, we must already got a widget or state(element), that means we have a factory for children widgets. All Flutter's build (as long as other declaration UI) is a function call, just like

UI = f(g(h(state)))

We just change this to

ui1 = h(state) ui2 = g(ui1) UI = f(ui2)

wrap ever build into a node/task and change all that to a chain list. In practice, we can use a deque to collect deeper call.

Widget tree build:

  1. Got a node to build from queue, we dont know whether it will be a leaf node.
  2. Execute node's build, add all children to queue.
  3. Add executed node to a deque tail.
  4. Repeat goto 1

Element & RO tree build

Because elements generally need children to be ready, so we have to produce it from leaf.

  1. Take a node from tail of executed node deque(this will like a stack)
  2. Produce Element/RenderObject
  3. repeat

It just like traversal a tree without recursion, so we can suspend and resume at any iteration of traversal. this is a prototype of pseudo code, hope it help.

fzyzcjy2022-09-16T11:42:29Z GitHub

@xanahopper

Multiple isolates and optimize with it is before the specification phase, just for you known that we both have the same idea that I and my colleagues are working on it. for very early part we think that this way may need modify engine or even the Dart VM. Last time we coming with a issue https://github.com/flutter/flutter/issues/110063 and got a refuse with tough attitude.

I see. Willing to collaborate to make it into reality as soon as possible!

I looked at #110063 now. If I understand correctly, seems that @jonahwilliams refuses because "Splitting the UI thread work into multiple theads is infeasible for several reasons", such as "a single thread means that newspace allocations don't need any locking". However, my proposal above deliberately avoids these problem. In my case, the c++ ui thread is sleeping while dart main isolate is running, and (if flutter does not like multi concurrent isolates) the thread and main isolate can also be sleeping while dart sidecar isolate is running. So, we are still running single isolate, and no lock is needed at all!

In short, I am not using multi threading. Instead, all threads are there only to implement suspending.

or even the Dart VM.

This inspire me of something: If we can implement a suspend mechanism in Dart VM, maybe we do not need that safepoint + one extra thread approach.

Widget tree build

Fully understand now :) That should be very workable, just like React Fiber does.

Element & RO tree build so we have to produce it from leaf Produce Element/RenderObject

Sorry I do not get it. We are not going to produce RenderObjects, but (most of the time) modify (update) them. For example, say you have a RenderPadding. Then we will only modify its padding field and markNeedsRelayout, instead of throwing away the old padding and create a new one.

Most importantly, how can we get the BoxConstraints (suppose we are dealing with RenderBox)? For example, when we are layout() for a leaf, we must know the BoxConstraints its parent wants to give it. But the parent is not yet layouted.

fzyzcjy2022-09-16T11:46:14Z GitHub

@xanahopper In addition, I have mentioned many limitations of the suspendable tree traversal in https://github.com/flutter/flutter/issues/101227#issuecomment-1248894781 (see last section there). Looking forward to see some solutions about it!

For example, a big problem: Originally all code (implicitly) assume that, when a frame ends, build/didUpdateWidget has been called. But now this no longer holds. That will make a ton of widget fail to work, including those inside flutter framework, and many external packages. For example, those who assume this inside their addPostFrameCallback.


Update: More problems are added to that comment. For example, "If a child under Suspendable mark itself as needed to relayout/rebuild, and there is relayout boundary between that child and Suspendable, then the suspending mechanism will not work at all."

dnfield2022-09-16T15:39:01Z GitHub

Just one is enough.

fzyzcjy2022-09-16T23:25:10Z GitHub

Fix UiKitView which wrongly unconditionally repaints

How the bug is found

This bug is surely not found by human eyes reading each and every line of Flutter code :)

I proposed the idea that a linter can be created (https://github.com/dart-code-checker/dart-code-metrics/issues/997) to validate whether RenderObject field setters have correct early-return code. @incendial implemented it and ran it (https://github.com/dart-code-checker/dart-code-metrics/pull/1003).

Thus, thanks @incendial for implementing the linter and run it through flutter framework code!

Bug description

As we know, when implementing a setter in RenderObject, we usually early halt if the new value is equal to the old value (such as this one, indeed almost all fields in RenderObject child classes are examples). This is very necessary, because we are setting fields in updateRenderObject unconditionally. If we do not early return, we will execute the full logic like markNeedsPaint and so on unconditionally on every updateRenderObject. That will be performance penalty.

The current PR fixes one bug of such case. In more details, the viewController field setter lacks such early-return, and thus unconditionally calls markNeedsPaint. The setter is called from updateRenderObject as follows:

image

And _UiKitPlatformView is used here:

image

In other words, the controller is not changed in every frame. Instead, in common cases, it should be the same one for many frames. However, it triggers repaint for each and every rebuild.

Close #111788

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-16T23:25:13Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-09-16T23:45:23Z GitHub

Test passes here.

image

Without the fix, test fails:

image image

fzyzcjy2022-09-17T00:41:55Z GitHub

Update: I am thinking whether we can remove the need of new threads in https://github.com/flutter/flutter/issues/101227#issuecomment-1249005541. If we can pause a Dart isolate without needing new threads, we can remove those threads.

Details can be found in:


Update: I am trying to use the spirit of stackful coroutines to implement it.

I do get stuck. We have a ton of callbacks from C++ calling into Dart, such as when the image data has been loaded successfully. If the dart main isolate is freezed (either by stackful coroutine, or by a normal thread with mutex), C++ code cannot call Dart at all. Delaying those calls also seem very troublesome because of resource deallocation problems.


Update: Search a bit on Discord history and here is a summary.

Well I see some parts of my experiment above has already been discussed there

auto-submit2022-09-17T01:50:37Z GitHub

auto label is removed for flutter/flutter, pr: 111790, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-09-17T01:50:38Z GitHub

auto label is removed for flutter/flutter, pr: 111790, due to Validations Fail.

fzyzcjy2022-09-17T01:54:58Z GitHub

Oops seems to because need 2 approvals

fzyzcjy2022-09-17T06:19:39Z GitHub

Fix typo again

Well I come again... Again fix one single word typo... Hope there exist a lighter method to do so!

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-17T08:02:13Z GitHub

Rethinking (overcoming) the shortcomings of the Suspendable "62ms->22ms" experiment

The quoted text are the shortcomings mentioned in the experiment, and black text are my re-thoughts.

It only suspends the layout and build phase. (The build phase is wrapped inside layout phase by adding a LayoutBuilder.) Indeed, it does not suspend the paint or raster phase, which should be done in future work.

Given the discord discussions among @Hixie and @dnfield etc, seems build/layout is mostly the expensive one. So paint or raster may not needed to be considered at the highest priority, at least not implemented in this issue and may defer to future work.

It paints nothing (i.e. do not call child.paint) if a Suspendable is suspending. This will destroy the layer tree and C++ engine layer trees, making performance much worse. We should address this problem later, possibly by keeping the layer tree not used but not removed.

This is not a problem if we only consider the jank caused by widget creation/deletion (like going to a new page or ListView scroll to make a new widget visible).

It lets the whole ancestors (up until relayout-boundary) to relayout in each frame.

But I guess this should not be a big problem in real world, because we should keep the heavy things in Suspendable subtrees and keep the ancestors simple.

Overhead will become non-neglectable, if we want it to run in 60fps. In other words, if we want each frame to be under 16ms, looks like we will only have <10ms for handling the suspendable widgets (rough estimate, but anyway numbers differ on different phones). Then, the price of 60fps smooth animation is that, the suspendable needs longer time to be loaded.

However, if we want to keep it single-threaded (single isolate), as #110063 (multi isolate) is refused, this is the price we have to pay.

If a child under Suspendable mark itself as needed to relayout/rebuild, and there is relayout boundary between that child and Suspendable, then the suspending mechanism will not work at all.

We should add more Suspendables if we observe such situation. More specifically, we should add a Suspendable (or, if using keframe-like solution, the FrameSeparateWidget) near that specific widget. Then, this is no longer a problem.

p.s. This is not a problem if we only consider the jank caused by widget creation/deletion (like going to a new page or ListView scroll to make a new widget visible).

The current implementation does not run suspendable layouts last. Instead, they are run inside non-suspendable layout. Thus, we have to set a "earlier" deadline (e.g. 12ms, instead of 16.6ms in the example above), and hope that the remaining job will finish quick enough.

Not a critical problem indeed.

Element.performLayout says, "In implementing this function, you must call layout on each of your children". But, when implementing Suspendable, we have to violate this. We will face troubles, or just minor changes are enough?

Will see whether it is a problem after doing more experiments.

Originally all code (implicitly) assume that, when a frame ends, build/didUpdateWidget has been called. But now this no longer holds. That will make a ton of widget fail to work, including those inside flutter framework, and many external packages. For example, those who assume this inside their addPostFrameCallback.

Since this feature is completely opt-in (you have to manually put the Suspendable widget into your tree), users may be able to migrate their widgets when they decide to use Suspendable.

The problem is, it may take efforts to migrate each and every widget, and it also takes time to migrate all inside flutter framework itself. Luckily, it is opt-in, so we can do it steadily and slowly, just like how we migrate to Material 3 theme (it has been months but still not finished).

Many code may migrate smoothly without any problem. (For example, I personally used MobX for my Flutter app, which has reactive states and automatic rebuild, so I seldom touch the raw frame callbacks. For many widgets in flutter framework we can reason about it in our heads and they seem ok as well.)

We may need to provide some information to the users, indeed States or BulidContexts, telling them they have been suspended. A simple method may be adding a field to State/BuildContext, or use a InheritedWidget. I may defer this work after seeing what info a real widget wants when migrating real widgets.

fzyzcjy2022-09-17T11:46:14Z GitHub

Enhance keframe: Now seems it can build/layout as many items as possible until time is up, i.e. have strategy similar to the "layout" proposal above

@Nayuta403

The problem

As is discussed in https://github.com/LianjiaTech/keframe/issues/12#issuecomment-1238873216 and (IIRC) earlier comments, keframe now blindly builds one widget per frame, even if it can build (for example) 5 widgets. This makes the UI need much longer time to display fully. In addition, it always lag by one frame, because it uses setState in a addPostFrameCallback to update its widget.

The solution

IMHO, the following suggestion can avoid the problems above. Now it can build/layout as many items as possible until time is up, i.e. have strategy similar to the "layout" proposal above. Please correct me if I am wrong!

As can be seen in the code example below, the key point is a LayoutBuilder wrapped as parent of FrameSeparateWidget. By doing so, we ensure that the build and layout phase of widgets prior to the current widget has already been done. Now, FrameSeparateWidget can do a simple decision in its build method - if time is sufficient just return new child, otherwise return the old one and rebuild in the next frame.

By the way, this is partially equivalent to the "layout" proposal because of the following: IMHO, the builder callback inside a LayoutBuilder is called within performLayout. Therefore, the build of the child widget is strongly related to the layout of the LayoutBuilder render object. Then, I can partially migrate the idea in the "layout" proposal (where I hacked the performLayout) to this case (where I hack the build).

Full code example and output

The dummy timeRemain simulates the real world where we may have (e.g.) 16ms for each frame.

Details

// ignore_for_file: avoid_print, no_runtimetype_tostring

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

late int timeRemain;

void main() {
testWidgets('example', (tester) async {
print('frame #1');
timeRemain = 3;
await tester.pumpWidget(MaterialApp(
home: Column(
children: [
for (var i = 0; i < 5; ++i)
LayoutBuilder(
builder: (_, __) => FrameSeparateWidget(
name: '$i',
child: i.isOdd ? SlowBuildWidget(name: '$i') : SlowLayoutWidget(name: '$i'),
),
),
],
),
));

print('frame #2');
timeRemain = 3;
await tester.pump();

print('frame #3');
timeRemain = 3;
await tester.pump();
});
}

// the `keframe` one
class FrameSeparateWidget extends StatefulWidget {
final String name;
final Widget child;

const FrameSeparateWidget({super.key, required this.child, required this.name});


State<FrameSeparateWidget> createState() => _FrameSeparateWidgetState();
}

class _FrameSeparateWidgetState extends State<FrameSeparateWidget> {

Widget build(BuildContext context) {
if (timeRemain > 0) {
print('$runtimeType#${widget.name} build: time is ok, give normal child');
return widget.child;
} else {
print('$runtimeType#${widget.name} build: time is up, give dummy');
SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {}));
return Container();
}
}
}

class SlowBuildWidget extends StatelessWidget {
final String name;

const SlowBuildWidget({super.key, required this.name});


Widget build(BuildContext context) {
print('$runtimeType#$name simulates slow build (timeRemain: $timeRemain -> ${timeRemain - 1})');
timeRemain--;
return Container();
}
}

class SlowLayoutWidget extends SingleChildRenderObjectWidget {
final String name;

const SlowLayoutWidget({super.key, super.child, required this.name});


RenderSlowLayout createRenderObject(BuildContext context) => RenderSlowLayout(name: name);


void updateRenderObject(BuildContext context, RenderSlowLayout renderObject) => renderObject.name = name;
}

class RenderSlowLayout extends RenderProxyBox {
RenderSlowLayout({RenderBox? child, required this.name}) : super(child);

String name;


void performLayout() {
super.performLayout();
print('$runtimeType#$name simulates slow layout (timeRemain: $timeRemain -> ${timeRemain - 1})');
timeRemain--;
}
}

outputs

frame #1
_FrameSeparateWidgetState#0 build: time is ok, give normal child
RenderSlowLayout#0 simulates slow layout (timeRemain: 3 -> 2)
_FrameSeparateWidgetState#1 build: time is ok, give normal child
SlowBuildWidget#1 simulates slow build (timeRemain: 2 -> 1)
_FrameSeparateWidgetState#2 build: time is ok, give normal child
RenderSlowLayout#2 simulates slow layout (timeRemain: 1 -> 0)
_FrameSeparateWidgetState#3 build: time is up, give dummy
_FrameSeparateWidgetState#4 build: time is up, give dummy
frame #2
_FrameSeparateWidgetState#3 build: time is ok, give normal child
SlowBuildWidget#3 simulates slow build (timeRemain: 3 -> 2)
_FrameSeparateWidgetState#4 build: time is ok, give normal child
RenderSlowLayout#4 simulates slow layout (timeRemain: 2 -> 1)
frame #3
fzyzcjy2022-09-17T14:35:35Z GitHub

@Nayuta403 If you are interested, I can try to make it a full library. Given that it is based on keframe's idea (hack widget build), but at the same time it is quite different from the existing implementation (do not use addPostFrameCallback and use the LayoutBuilder hack), I am not sure whether I should make a PR to keframe, or I should create a separate lib by myself (and mention keframe)?

Nayuta4032022-09-17T16:22:11Z GitHub

@fzyzcjy Hi man, you are very thoughtful and full of passion, thank you for your thoughts. Recently I have been busy with work.I want to first communicate with you about Keframe idea and then follow up your discussion.

As can be seen in the code example below, the key point is a LayoutBuilder wrapped as parent of FrameSeparateWidget. By doing so, we ensure that the build and layout phase of widgets prior to the current widget has already been done.

👍 👍 Your idea is great, we can hit the timer at the beginning of a frame and it seems to calculate the timeRemian. If you don't mind, I think you can create a branch/PR in KeFrame for discussion (I've given you a Write access) because there's some basic mechanics in there and a ready-made example in there.

In addition, it always lag by one frame, because it uses setState in a addPostFrameCallback to update its widget.

I think this will not happen, because KeFrame calls addPostTimeCallBack during initState (i.e.https://github.com/flutter/flutter/blob/5816d20b86b95205c40921fa91ee3434b9c97ac6/packages/flutter/lib/src/scheduler/binding.dart#L1197-L1201) and _postFrameCallbacks call after _persistentCallbacks is finished (i.e. https://github.com/flutter/flutter/blob/5816d20b86b95205c40921fa91ee3434b9c97ac6/packages/flutter/lib/src/scheduler/binding.dart#L1203-L1210), They're both in the handleDrawFrame method, so I think they're still in the same frame.

Ps: I actually think Fiber and Keframe will end up with similar results, but Keframe will work within the existing framework and won't require a lot of changes to the framework and engine. I think we can contribute it to the flutter after we've optimized it, like nested in a ListView or a Column or something, and open it with flags, like a RepaintBoundary.

fzyzcjy2022-09-17T23:12:10Z GitHub

@Nayuta403 You are welcome!

we can hit the timer at the beginning of a frame and it seems to calculate the timeRemian

Yes, just like my "layout" demo, which I recorded when the frame begins.

In addition, it always lag by one frame, ... I think this will not happen

Well yes the function call is in the same frame; but indeed, if I understand correctly, the build will lag one frame. Consider the simplest example, where we are building a new widget tree (thus initState) with a child. Then, in frame 1, FrameSeparateWidget has initState and build called. But it is only at the post-frame callback phase that FrameSeparateWidget.result is filled with the real child. So it is only at frame 2 that FrameSeparateWidget really renders the child onto the screen.

Ps: I actually think Fiber and Keframe will end up with similar results, but Keframe will work within the existing framework and won't require a lot of changes to the framework and engine. I think we can contribute it to the flutter after we've optimized it, like nested in a ListView or a Column or something, and open it with flags, like a RepaintBoundary.

That looks interesting, and I love to contribute to Flutter :) But I am worried whether Flutter will accept such widgets that can live in thirdparty packages. On the contrary, if we need to modify the framework and it has to be integrated with the framework, then surely we need to put it into flutter framework.

create a branch/PR in KeFrame for discussion (I've given you a Write access)

Thanks for your invitation (I see it). However, I realize keframe is under LianJia, a commercial company. It is not a person (e.g. you), a nonprofit organization (e.g. the flutter organization, the llvm org, the mobx org), a company known to have a ton of open source contributions (e.g. google), or something like that. So I am very sorry I cannot join it. But anyway, all my work will be open-sourced, and under license like MIT, so everyone can use it!

fzyzcjy2022-09-17T23:33:32Z GitHub

@Nayuta403 A bit more explanation: Why we do not need to worry about "the child subtree build&layout for a FrameSeparateWidget is so long that it makes everything slow"?

Because if that is the case, we can wrap several FrameSeparateWidget in the heavy parts of that subtree. Then, because each (new version I proposed yesterday) FrameSeparateWidget builds normally if not timeout, it will behave normally if time is ok; on the contrary, as long as time is up, subtree will pause to build. By doing this, we can ensure every FrameSeparateWidget takes moderate time length (say, 1ms), and there is no such case as one FrameSeparateWidget taking (e.g.) 100ms so everything is jank.

fzyzcjy2022-09-18T04:11:00Z GitHub

Update: Some experiments here using the new implementation (proposed here).

https://github.com/fzyzcjy/flutter_smooth

Btw I find that performance boost varies a lot when considering different experiments.

Nayuta4032022-09-18T16:05:22Z GitHub

But anyway, all my work will be open-sourced, and under license like MIT, so everyone can use it!

Yes, I was negligent. Lianjia is the company I used to work for. Just because this project is completed by me and has a certain number of users, I am still maintaining it personally. You are absolutely right, I also wish we had some open-sourced work available to everyone. We can work on your project https://github.com/fzyzcjy/flutter_smooth

A bit more explanation: Why we do not need to worry about "the child subtree build&layout for a FrameSeparateWidget is so long that it makes everything slow"?

Yes, I can understand that. For the subtree to time out, we can delay the build again by nesting the FrameSeparateWidget, which I've used before. I think from this point of view, all widget builds are interruptible, this Fiber-like mechanism. I have a crazy idea that if we add a placholder property to all widget(Not all, we can add this property to some base class), we will build the placholder if the frame timeRemain time is 0. Then the jank will never happen ! HHHHH

Update: Some experiments here using the new implementation.

I like your new implementation, which seems to have solved the problem we mentioned earlier by laying out as many widgets as possible in each frame. I think there may be some details that need to be added :

  • Whether other lifecycle states should be considered for _SmoothState, such as didUpdateWidget or onDispose. For example, I encountered an error in keframe when setState was called from outside because result was cached in State. If the widget.child is changed externally so that it does not work (It doesn't look like it's going to happen because you're using widget.child directly in build, but I think you might need to think about it when using State, I can add a https://github.com/LianjiaTech/keframe/blob/master/example/lib/page/complex_list_example.dart in your example)
  • In your example, the height of the item is 24. But for the list, many times we don't know the width and height at code time, and jitter will occur when the placeholder and the actual list are not the same width and height.(because placeholder becomes item, Causing sibling layout changes). like this example in keframe. I did this by using SizeCacheWidget to cache the width and height of the item and force it to the placeholder so that it would not shake the second time the item was displayed. (You can't avoid it the first time, because the Item doesn't have a layout.) Do you have any other ideas?

On the contrary, if we need to modify the framework and it has to be integrated with the framework, then surely we need to put it into flutter framework.

Yes, I think if we do it well enough, we can communicate with the Flutter Team and submit it to the Flutter Framework. I communicated with @dnfield a long time ago and he was also interested in it. discord If we want to commit to Flutter Framework , what is the value of kTimeThreshold? Since we are only counting the build/layout time now, using 16.7 doesn't seem particularly appropriate, and for 120HZ devices, this value should be 16.7/2 ms

How do you think about it?

fzyzcjy2022-09-18T23:03:56Z GitHub

Yes, I was negligent. Lianjia is the company I used to work for. Just because this project is completed by me and has a certain number of users, I am still maintaining it personally. You are absolutely right, I also wish we had some open-sourced work available to everyone. We can work on your project https://github.com/fzyzcjy/flutter_smooth

Sure! Looking forward to collaborations :)

I have a crazy idea that if we add a placholder property to all widget(Not all, we can add this property to some base class), we will build the placholder if the frame timeRemain time is 0. Then the jank will never happen ! HHHHH

Haha that is really a crazy idea! The problem is overhead will be very big though :)

Whether other lifecycle states should be considered for _SmoothState, such as...

Agree! At least we should add a test in our code, asserting its correctness

I did this by using SizeCacheWidget

I think that is a pretty smart idea, and has not found other solutions yet. If you approve I will add things similar to that into the codebase. The idea will be the same, while implementation will differ slightly (e.g. use a InheritedWidget + StatefulWidget + controller).

Yes, I think if we do it well enough, we can communicate with the Flutter Team and submit it to the Flutter Framework. I communicated with @dnfield a long time ago and he was also interested in it. discord

Totally agree. (Btw I have searched through the history a few days ago: https://github.com/flutter/flutter/issues/101227#issuecomment-1249961627)

If we want to commit to Flutter Framework , what is the value of kTimeThreshold? Since we are only counting the build/layout time now, using 16.7 doesn't seem particularly appropriate, and for 120HZ devices, this value should be 16.7/2 ms

The 120hz should be simple since we can detect what frequency we are under.

The problem is "we are only counting the build/layout time now".

Btw, I realized that, for a scrolling list, the "finalizing" phase also takes time. Let alone the paint/compositing phase we all have known.

They (paint/compositing/finalizing) each take a little of time, but when accumulated, it is non-neglectible for that 16ms.

fzyzcjy2022-09-18T23:09:40Z GitHub

Btw, recent ideas:

  • I am considering halting the paint phase as well: Maybe we can directly reuse the old Layer, so we can get the same UI and at the same time do not call paint on subtree. This is just very naive idea and I will make an experiment later.
  • "for a scrolling list, the "finalizing" phase also takes time" - Maybe we can hack ListView itself, and control when it disposes its widgets.
fzyzcjy2022-09-19T02:42:48Z GitHub

Change type in ImplicitlyAnimatedWidget to remove type cast to improve performance and style

Hi, hope to have a quick view whether this PR is acceptable or not? If yes I will add an issue and maybe ask for test-exempt.


Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-19T12:58:40Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-09-20T13:46:34Z GitHub

New idea: Preemption

Advantages

It can nearly achieve my goal: 60fps, no matter how heavy your build/layout are. Without limitations of other approaches, such as the ones in flutter_smooth, or the ones in "layout" proposal where the widget subtree has to allow their build/layout not called in some frames.

It also has zero overhead about re-layouting, i.e. it will never need to pay any extra cost to layout, compared to the widget/layout based approaches. It also solves the problem of "how to suspend a layout". I can explain more advantages and comparisons if needed.

Compared with the "dual isolate" proposal above, that one seems very hard to implement as it requires threads or coroutines, but this proposal is not. In addition, this proposal eliminate the second "sidecar" isolate, and everything is in main isolate, so we can run any code with all data in main isolate memory visible.

Details

Continue and modified from https://github.com/flutter/flutter/issues/101227#issuecomment-1249005541 (the "dual isolates" idea, but without the need of adding new threads (very troublesome to do syncing), or c++ coroutines (troublesome when c++ wants to call dart callback).

Notice that, the c++ code, main isolate, and sidecar isolate all run on "ui thread". No new threads, no coroutines, etc. So this time, the diagram draws nothing but very normal function calls.

Description of the figure:

  • vsync comes.
  • As normal, C++ calls Dart's drawFrame.
  • Suppose Dart has 3 widgets to build/layout. It build/layout the 1st, then 2nd.
  • Then it realizes time has up (say, 15ms has come), when layout() the 2nd widget. Then it calls preemptRaster() (a dart function).
  • In preemptRaster, we firstly call preemptModifyLayerTree to modify the layer tree a bit, like CircularProgressIndicator or the scrolling ListView wrapper widget case, described in "dual isolate" proposal above. For simplicity, imagine this preemptModifyLayerTree is implemented via very low level API, such as containerLayer.offset = Offset(10,20).
  • In preemptRaster, we then call a probably modified version of FlutterView.render. In other words, we provide layer tree to C++ code, and c++ code provide it to raster thread. Notice what layer tree we provide here: Because preemptRaster is called within a layout(), the paint phase has not started, so the layer tree is completely old (instead of mixed). ThusIn addition, preemptModifyLayerTree will modify the layer tree a bit. That's all. We will send this to raster.
  • Raster thread renders that layer tree as usual, so we see beautiful things on screen.
  • UI thread C++/Dart goes on, because preemptRaster function returns. The Dart code will continue from where preemptRaster is called (you know, just very plain function calls; but this solves the "how to suspend a layout call" implicitly indeed). In Dart's view, it thinks it is still the 1st frame. Let's say it continues layouting the 2nd widget. Then 3rd widget. Then paint, flush compositing bits, semantics, etc.
  • Then finally, as a normal pipeline stage, dart provides the new layer tree and let c++ to throw it to the raster thread.
  • Raster thread renders it to screen in the background.
  • Then, just like what will be done normally in frame 1, call post frame callbacks, c++ calls dart for some callbacks, etc.
  • Now ui thread is idle. When next vsync comes, the same loop will go.

UML时序图 (2)

Nayuta4032022-09-20T13:47:17Z GitHub

I think that is a pretty smart idea, and has not found other solutions yet. If you approve I will add things similar to that into the codebase. The idea will be the same, while implementation will differ slightly (e.g. use a InheritedWidget + StatefulWidget + controller).

Yeah, I think the code willn't be much different, or I can directly PR to your repo? This jitter usually occurs in ListView, which needs to be nested with SizeCacheWidget in KeFrame, and LayoutInfoNotification is emitted in FrameSpeWidget. So the user has to specify SizeCacheWidget if they want to user ListView. if it's in Flutter framework, we can add it directly, or do you have other ideas?

The 120hz should be simple since we can detect what frequency we are under.

Yes, we can get it directly from the engine, but I have to see how to get it in the framework. It may be necessary to add an API

"for a scrolling list, the "finalizing" phase also takes time" - Maybe we can hack ListView itself, and control when it disposes its widgets.

Yes, I think we can ignore this factor for now as I understand it is not particularly time consuming. Or we can directly change the "finalizing" timing of the ListView.

I am considering halting the paint phase as well: Maybe we can directly reuse the old Layer, so we can get the same UI and at the same time do not call paint on subtree. This is just very naive idea and I will make an experiment later.

I agree with that, I think you just need to nest RepaintBoundary on the subtree, right? Just like ListView item, avoid subtree paint causing pain in other widgets.

@fzyzcjy

I got a bad cold yesterday, so I was late in answering the message

fzyzcjy2022-09-20T13:48:45Z GitHub

@dnfield @Nayuta403 @JsouLiang (and other experts) I have made a "preemption" proposal, which is like a easy-to-implement version of "dual isolate". Looking forward to any feedbacks! I am going to implement a prototype tomorrow :)


Same thing in discord: https://discord.com/channels/608014603317936148/608021234516754444/1021783497112821861

There are some discussions going on there as well. For completeness, a reader of this github thread may need to go to this link and view comments there.

fzyzcjy2022-09-20T13:53:17Z GitHub

@Nayuta403

or I can directly PR to your repo?

Sure! But I am hesitate whether going on flutter_smooth now, as the "preemption" proposal seems quite appealing and addresses many problems of flutter_smooth, the layout proposal, and the keframe.

Could you please have a look at "preemption" proposal :) I want to implement a prototype tomorrow (UTC+8 timezone).

This jitter usually occurs in ListView, which needs to be nested with SizeCacheWidget in KeFrame, and LayoutInfoNotification is emitted in FrameSpeWidget. So the user has to specify SizeCacheWidget if they want to user ListView. if it's in Flutter framework, we can add it directly, or do you have other ideas?

That LGTM. Indeed I will do something like: The SizeCacheWidget (I may call it SmoothParent) has some inherited widget to provide a controller to its child subtree. Then child can save anything they want to that controller. Anyway, those are simple things, and I can also do it if you like (just need e.g. 15 minutes).

Yes, we can get it directly from the engine, but I have to see how to get it in the framework. It may be necessary to add an API

I remembered I did that via calling java/swift. Anyway this is minor problem :)

I agree with that, I think you just need to nest RepaintBoundary on the subtree, right? Just like ListView item, avoid subtree paint causing pain in other widgets.

Yes, but I hope not too many RepaintBoundary in the meanwhile. IIRC during some testing they add overheads. Btw the "preemption" proposal does not have this problem.

I got a bad cold yesterday, so I was late in answering the message

Sorry to hear that, and hope you are getting well!

fzyzcjy2022-09-20T14:02:49.531+00:00 Discord

Hi, I have proposed an approach for 60fps smooth animation no matter how heavy widget tree build and layout is, without paying extra cost (such as redundant re-layout). https://github.com/flutter/flutter/issues/101227#issuecomment-1252379787

fzyzcjy2022-09-20T14:04:36.482+00:00 Discord

@Hixie @dnfield (since @dnfield mentioned that layout suspending has been discussed between them; this one does suspend layout and render first)

fzyzcjy2022-09-20T14:05:29.934+00:00 Discord

I am planning to start working on a prototype ~9hr later, but want to hear some hints from you experts, since I have not quite hacked the engine before

dnfield2022-09-20T14:15:41.659+00:00 Discord

I'm curious about how preemtRaster would modify the layer tree and how it would know where to resume. Those are the more difficult bits.

fzyzcjy2022-09-20T14:24:21.395+00:00 Discord

Let's say, for simplicity as a demo, just modify a layer directly via lowest level api. - But we can definitely wrap it to some higher level. And with future thinking maybe we can also do something with existing widget framework.

fzyzcjy2022-09-20T14:24:54.538+00:00 Discord

how it would know where to resume: Just call function and it returns! Example:

fzyzcjy2022-09-20T14:25:29.615+00:00 Discord
class RenderObject {
void layout() {
if (time_is_nearly_out) preemptRaster();
normal_layout_things;
}
}

void preemptRaster() {
modify_layer_tree_for_animation();
FlutterView.render(the_layer_tree);
}
fzyzcjy2022-09-20T14:26:17.673+00:00 Discord

@dnfield

Nayuta4032022-09-20T14:27:41Z GitHub

Anyway, those are simple things, and I can also do it if you like (just need e.g. 15 minutes).

Haha OK, you do it 👍🏻 If I do I think it will probably take more than 15 minutes to communicate. hhhh

Could you please have a look at "preemption" proposal :) I want to implement a prototype tomorrow (UTC+8 timezone).

I wonder how this Frame1 is generated, now there are only two widgets with build/layout and neither of them have paint/comp etc. If you use the LayerTree from the previous frame that It looks the same as it does now. (A jank happened) Am I getting it wrong? I'm looking forward to seeing your prototype : )

image

fzyzcjy2022-09-20T14:31:51.181+00:00 Discord

A question: Take RenderOpacity for example, and suppose we want that in preemptRaster. Currently it is:

layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?);

// definition
OpacityLayer pushOpacity(Offset offset, int alpha, PaintingContextCallback painter, { OpacityLayer? oldLayer }) {
final OpacityLayer layer = oldLayer ?? OpacityLayer();
layer
..alpha = alpha
..offset = offset;
pushLayer(layer, painter, Offset.zero);
return layer;
}

So, my naive thought of directly manipulating it:

void directlyManipulateOpacityInPreemptRaster() {
final OpacityLayer layer = layer_tree.children.where(...); // find opacity layer
layer.alpha = some_new_value_for_animation;
}

If I call merely this, without all those PictureRecorder/create-new-layer etc, will it be acceptable?

fzyzcjy2022-09-20T14:34:30Z GitHub

If I do I think it will probably take more than 15 minutes to communicate. hhhh

Haha I think so!

I wonder how this Frame1 is generated, now there are only two widgets with build/layout and neither of them have paint/comp etc

Well I should say, this diagram happens after we have rendered a lot of frames. So the layer tree is already there, just without the several newly added/modified widgets.

If you use the LayerTree from the previous frame that It looks the same as it does now. (A jank happened) Am I getting it wrong?

No, I call preemptModifyLayerTree. That one handles animations, e.g. CircularProgressIndicator, or ListView scrolling, or opacity changing animation. For simplest example, for opacity, it may update a OpacityLayer.opacity from 0.1 to 0.2 etc.

I'm looking forward to seeing your prototype : )

Thanks :)

dnfield2022-09-20T14:37:20.433+00:00 Discord

What happens if a render object doesn't implement preemptRaster?

fzyzcjy2022-09-20T14:40:06.791+00:00 Discord

Just put it into RenderObject.layout

fzyzcjy2022-09-20T14:40:47.215+00:00 Discord

e.g. add if (time_is_nearly_out) preemptRaster(); as 1st line of RenderObject.layout. (Surely we may optimize it to be faster, but idea is same)

dnfield2022-09-20T15:36:59.851+00:00 Discord

What happens if devs write something slow into preemptRaster? 🙂

dnfield2022-09-20T15:37:49.022+00:00 Discord

I'm curious about this, maybe it'd help to see a little bit more detail around implemenation. Perhaps write up a doc?

fzyzcjy2022-09-20T22:55:58.432+00:00 Discord

Just do not do that 😉 It is like asking "what if dev use a million of pushLayer today (answer: it will be slow)"

fzyzcjy2022-09-20T22:56:39.564+00:00 Discord

Sure! Do you mean https://docs.google.com/document/d/1SFRO8U2toOlAaZ38dsuEU7Wm5fn41wvBCWKiwADqfmw/edit the flutter design doc template?

dnfield2022-09-20T22:56:55.481+00:00 Discord

So I think an ideal solution will not require developers to update their widgets/render objects, and will not break if developers decide to just throw tons of work into a new method they have to implement

dnfield2022-09-20T22:57:01.155+00:00 Discord

Yes, that would be good

fzyzcjy2022-09-20T22:57:16.457+00:00 Discord

not require developers to update their widgets/render objects -> Yes, this solution do not require

fzyzcjy2022-09-20T22:57:32.949+00:00 Discord

will not break if developers decide to just throw tons of work into a new method they have to implement -> dev do not implement preemptRaster

fzyzcjy2022-09-20T22:57:50.486+00:00 Discord

Instead, the API we give is like this:

fzyzcjy2022-09-20T22:58:52.527+00:00 Discord

No dev will know what is preemptRaster. They only know that, when they want a smooth CircularProgressIndicator, or a smooth ListView scroll, or a smooth opacity animation, they put a special widget into the tree, say, PreemptCircularProgressIndicator()

fzyzcjy2022-09-20T22:59:24.608+00:00 Discord

Under the hood, our PreemptCircularProgressIndicator will utilize preemptRaster.

fzyzcjy2022-09-20T22:59:46.738+00:00 Discord

Btw, preemptRaster is not a method in RenderObject that everyone needs to implement, unlike layout/paint/... which everyone should impl

fzyzcjy2022-09-20T23:00:05.99+00:00 Discord

preemptRaster is like some utility function, that only we flutter framework dev need to impl once

ping2022-09-20T23:00:20.697+00:00 Discord

when they want a smooth CircularProgressIndicator, or a smooth ListView scroll, or a smooth opacity animation Isn't that always?

fzyzcjy2022-09-20T23:00:49.781+00:00 Discord

Haha, then maybe make it the default 🙂

fzyzcjy2022-09-20T23:00:55.773+00:00 Discord

Or, maybe add a flag into it

fzyzcjy2022-09-20T23:01:13.475+00:00 Discord

say, CircularProgressIndicator(preempt: true/false)

fzyzcjy2022-09-20T23:02:00.261+00:00 Discord

Anyway I have not think about the details about the high level apis inside preemptRender. It may or may not support arbitrary widgets. I am thinking about making it run firstly.

fzyzcjy2022-09-20T23:07:10.509+00:00 Discord

Btw, what are you guy's timezone? I will create a design doc probably within an hour, not sure whether you guys are online or not

jonahwilliams2022-09-20T23:09:58.572+00:00 Discord

Just sharing some experience I've had working on performance: it's very rare to find that applications are blocked on the UI thread, except for 1) incorrectly implemented scrolling 2) lack of isolate usage for data processing. Thus I would be quite skeptical that this sort of change would actually be beneficial to most Flutter developers, and design doc or not there is almost no chance I would be in favor of adding this to the framework.

@dnfield pointed out another case to me, which is that on particularly low end android devices, even simple UIs can jank due to text layout costs. Though I think its also fairly common for these particularly low end devices to have very few cores, or only one or two fast cores - meaning that multithreading may not help much either.

I think the only thing that would cause me to change my mind is a prototype that demonstrated substantially better performance on a real-ish app; that is one that did not intentionally do way too much work.

Not trying to be too discouraging, but I want to make sure that we're on the same page on the expectations for a feature like this.

fzyzcjy2022-09-20T23:10:35.063+00:00 Discord

it's very rare to find that applications are blocked on the UI thread, Me 😦 Quite complex UI, on very low end devices

jonahwilliams2022-09-20T23:11:01.449+00:00 Discord

I understand that, but then every time I get source code access, its always 1) or 2).

jonahwilliams2022-09-20T23:11:28.992+00:00 Discord

not saying that you're wrong, just that I'm not willing to take anyone at their word for this. I want to see the example code

fzyzcjy2022-09-20T23:12:21.304+00:00 Discord

(Sorry you already mentioned that example)

fzyzcjy2022-09-20T23:12:30.121+00:00 Discord

(wait a minute I first read all messages)

fzyzcjy2022-09-20T23:13:21.041+00:00 Discord

Though I think its also fairly common for these particularly low end devices to have very few cores, or only one or two fast cores - meaning that multithreading may not help much either. My suggestion is not multithreading, it is still single thread 🙂

jonahwilliams2022-09-20T23:14:00.138+00:00 Discord

I might be mixing this up with the other github issue on a separate animation thread

fzyzcjy2022-09-20T23:14:03.651+00:00 Discord

I think the only thing that would cause me to change my mind is a prototype that demonstrated substantially better performance on a real-ish app; that is one that did not intentionally do way too much work. @Jsouliang @Nayuta I think their keframe has demonstrated some real cases where it boosts performance. I will find an article. Wait for a minute

jonahwilliams2022-09-20T23:14:26.055+00:00 Discord

A document would be a good place to start then 🙂

fzyzcjy2022-09-20T23:14:38.886+00:00 Discord

https://github.com/LianjiaTech/keframe

fzyzcjy2022-09-20T23:14:49.044+00:00 Discord

The readme explains a bit

fzyzcjy2022-09-20T23:15:08.166+00:00 Discord

They also use it in LianJia app IIRC, a somewhat large company

fzyzcjy2022-09-20T23:15:21.299+00:00 Discord

And in Bytedance, they said they have done sth similar to optimize

fzyzcjy2022-09-20T23:15:33.032+00:00 Discord

so I guess these are evidence of optimization of speed in real world

fzyzcjy2022-09-20T23:15:52.744+00:00 Discord

https://github.com/flutter/flutter/issues/101227#issuecomment-1247545240 Yes, we [people in bytedance] all farmilar with KeFrame and has already applied some optimize like it.

fzyzcjy2022-09-20T23:16:13.658+00:00 Discord

Note that my solution is quite diff from keframe.

jonahwilliams2022-09-20T23:16:35.906+00:00 Discord

If your solution is quite different, definitely write up a doc

fzyzcjy2022-09-20T23:16:40.29+00:00 Discord

The only similarity is that, we both want to address the less-than-60fps jank

fzyzcjy2022-09-20T23:16:48.097+00:00 Discord

Sure! I will do that in an hour

fzyzcjy2022-09-20T23:17:05.635+00:00 Discord

Btw there is a brief (1-page) proposal currently: https://github.com/flutter/flutter/issues/101227#issuecomment-1252379787

jonahwilliams2022-09-20T23:17:34.448+00:00 Discord

I thought that was the dual isolates/multithreading idea?

fzyzcjy2022-09-20T23:18:05.519+00:00 Discord

Nonono

fzyzcjy2022-09-20T23:18:15.211+00:00 Discord

I have removed dual isolates and multithreading or coroutine 🙂

fzyzcjy2022-09-20T23:18:24.082+00:00 Discord

Now look at the biggest fig in that comment

fzyzcjy2022-09-20T23:18:29.108+00:00 Discord

it is nothing but NORMAL function calls

fzyzcjy2022-09-20T23:19:10.379+00:00 Discord

Btw, keframe has popularity of 93% with 100+ likes in pub https://pub.dev/packages/keframe

fzyzcjy2022-09-20T23:19:28.12+00:00 Discord

If nobody is facing build/layout jank, I guess it should not be a popular lib at all 😉

fzyzcjy2022-09-20T23:19:51.784+00:00 Discord

keframe has some hard-to-overcome shortcomings, but it is still already this popoular

jonahwilliams2022-09-20T23:23:25.786+00:00 Discord

most popular packages don't get folded into the SDK

jonahwilliams2022-09-20T23:25:48.381+00:00 Discord

@fzyzcjy I would second @dnfield 's recommendation to write a doc. Most of us are going to have trouble following a github issue with dozens of comments, and we're not sure which parts of the proposal are still valid and which aren't

stuartmorgan2022-09-20T23:30:48.34+00:00 Discord

93% isn't actually as popular as it sounds; there are enough packages uploaded now that the percentage can be somewhat misleading. That puts it at something like 2000th.

fzyzcjy2022-09-20T23:38:38.467+00:00 Discord

most popular packages don't get folded into the SDK Sure, I know that 🙂 I just want to say "there do exist real-world cases who needs to be extra smooth"

fzyzcjy2022-09-20T23:38:45.778+00:00 Discord

Most of us are going to have trouble following a github issue with dozens of comments, and we're not sure which parts of the proposal are still valid and which aren't

fzyzcjy2022-09-20T23:38:54.715+00:00 Discord

I see, just ate and now start writing

fzyzcjy2022-09-20T23:39:04.566+00:00 Discord

93% isn't actually as popular as it sounds; there are enough packages uploaded now that the percentage can be somewhat misleading. That puts it at something like 2000th.

fzyzcjy2022-09-20T23:39:11.723+00:00 Discord

Did not know that before 🙂

fzyzcjy2022-09-20T23:39:55.702+00:00 Discord

But anyway, hopefully my comments above already show realworld cases: The bytedance and the lianjia. Especially bytedance (IIRC it is even on flutter.dev frontpage?).

jonahwilliams2022-09-21T00:01:55.326+00:00 Discord

I understand that this technique has been successful for bytedance and others, I'm not disputing or disagreeing with this. But adding something to the SDK, especially if it is a large intrusive change, is going to be held to the standard of whether it will be successful for all or most users of Flutter today. To evaluate this, we'll need at least a design doc, so we can understand the change you're trying to make. Ideally we would also have some sort of prototype, so that we can understand the behavioral changes, if any, required.

fzyzcjy2022-09-21T00:05:23.984+00:00 Discord

"large intrusive change" - I am trying to make it small 🙂 "we'll need at least a design doc" - writing! https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit# (surely not finished yet though) " Ideally we would also have some sort of prototype, so that we can understand the behavioral changes, if any, required." - I also want to do that

fzyzcjy2022-09-21T00:05:33.017+00:00 Discord

Will ping here when finish writing

jonahwilliams2022-09-21T00:09:23.107+00:00 Discord

Thank you!

fzyzcjy2022-09-21T00:19:38.891+00:00 Discord

You are welcome!

fzyzcjy2022-09-21T01:03:39.649+00:00 Discord

@Jonah Williams @dnfield (who said I should provide a doc)

jonahwilliams2022-09-21T01:14:35.939Z Google Doc

If we can stop rendering at any point, how do we resolve the sizes of render objects that depend on their children?

What about something like a layout builder? If we pre-empted layout then we may not actually be able to finish building?

jonahwilliams2022-09-21T01:16:01.875Z Google Doc

If we stop rendering based on time elapsed at an arbitrary RO, there is a risk we end up with a UI that makes no sense. i.e. we could get buttons with no labels or half filled in text. I'd be concerned that without a developer making an intentional choice of where to stop rendering, we'd be worse off than if we janked and took longer to render

jonahwilliams2022-09-21T01:17:55.051Z Google Doc

It might be worth contrasting this approach with keframe, or elaborating on what problems this solves that keframe cannot

fzyzcjy2022-09-21T01:19:00.455Z Google Doc

We do not resolve sizes. Indeed, we are using the previous fully rendered UI (plus modifications in preemptModifyLayer).

fzyzcjy2022-09-21T01:19:56.363Z Google Doc

Well we are using the previous fully rendered UI + modifications in preemptModifyLayer. We will never see half filled UI!

jonahwilliams2022-09-21T01:20:49.234Z Google Doc

The flutter test framework generally allows developers to elapsed arbitrary amounts of time with fake async usage. This is intentional to ensure that unit tests can be reasonably deterministic.

It would be massively breaking for unit tests to take a different number of frames to reach the same conclusion, depending on the speed of the host hardware.

fzyzcjy2022-09-21T01:21:19.125Z Google Doc

Indeed, imagine the whole proposal like this: We are still running the janky slow UI that is less than 60FPS. But, once in a while, we "secretly" flush old layer tree + some preemptModifyLayer modifications to the screen.

jonahwilliams2022-09-21T01:22:26.962Z Google Doc

It seems like that would only work if the previous UI was quite similar to the current UI, but that may not be the case.

How do you connect the current render object with the previous UI? The ROs are stateful objects, the only stable representation may be the old layer tree.

fzyzcjy2022-09-21T01:23:19.756Z Google Doc

We should fake the meaning of "time" in this proposal as well. For example, we may provide a variable called preemptStrategy:

abstract class PreemptStrategy {
bool shouldWePreemptNow();
} 

and call it in place of "checking whether 15 ms has passed".

Then, when testing, we are in full control. For example, we can disable the whole preempt. We can decide to preempt at a specific RenderObject we like to test. etc

fzyzcjy2022-09-21T01:23:58.886Z Google Doc

Sure. I will do that in a minute.

jonahwilliams2022-09-21T01:24:34.176Z Google Doc

Seems reasonable. FWIW, the amount of time available will vary per platform, and in the case of devices with dynamic refresh rates it may even vary frame to frame. I believe we should know the approximate target time for each frame when it starts though

fzyzcjy2022-09-21T01:26:55.990Z Google Doc

I am mainly thinking about janks in animations. The examples - progress indicator, scrolling listview, enter page transition, all are examples.

If your previous UI does not look similar to current, it is also OK. My proposal is just like, "originally the UI is janky, now we add some extra frames into its normal frames".

Anyway they can always disable it by a simple flag in widgets.

When flushing ui during animation, we just provide (old + minor modified) layer tree. We do not touch RO indeed.

fzyzcjy2022-09-21T01:27:57.841Z Google Doc

Agree, and that should be fetchable from some kind of platform APIs. I am saying 15ms or 16.6ms just because it is simple to explain :)

jonahwilliams2022-09-21T01:31:57.627Z Google Doc

Yes, understood! :)

jonahwilliams2022-09-21T01:33:54.168Z Google Doc

What I mean is - how do you detect that the previous UI is not like the current UI? Its OK if this isn't a performance improvement for that case, but I don't see how you would actually determine that it was "Safe" to use an old layer tree

fzyzcjy2022-09-21T01:36:30.982Z Google Doc

It is always safe. Because we are just "inserting" extra frames into the plain old frames!

For example, suppose it originally runs at 10 fps. Now, between frame 1 and frame 2, we insert frame 1a,1b,1c, ..., which is very alike frame 1 except for minor modifications (such as one OffsetLayer.offset).

If the users find this UI weird in their special case, they can also choose to disable surely.

jonahwilliams2022-09-21T01:47:25.545+00:00 Discord

Thanks @fzyzcjy , I left some comments in the doc which I see you've responded to. In general, my feedback is that I don't see how some parts of your proposal would actually work in practice. Re-using the previous frame and yielding during layout may break many fundamental assumptions we've made throughout the framework, and without a runnable example/prototype I don't think we're going to be able to understand the trade-offs you're making.

I'd also add that I don't think you need new engine APIS for this.

fzyzcjy2022-09-21T01:50:29.792+00:00 Discord

@Jonah Williams Hi thanks for the suggestions!

I don't think you need new engine APIS for this. I am worried about this. FlutterView.render says: /// If this function is called a second time during a single /// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame] /// callback sequence or called outside the scope of those callbacks, the call /// will be ignored.

fzyzcjy2022-09-21T01:50:50.815+00:00 Discord

So in our case it will be ignored. We have to change the engine...

jonahwilliams2022-09-21T01:51:27.695+00:00 Discord

that is pretty fundamental

jonahwilliams2022-09-21T01:51:33.378+00:00 Discord

you can only submit a single frame in a vsync

fzyzcjy2022-09-21T01:51:35.445+00:00 Discord

without a runnable example/prototype I don't think we're going to be able to understand the trade-offs you're making. I will try to do so today

jonahwilliams2022-09-21T01:51:39.561+00:00 Discord

otherwise you're just doing extra work

jonahwilliams2022-09-21T01:51:55.133+00:00 Discord

you'd have to submit a frame with you pre-empted frame, and then schedule a new frame to continue running

jonahwilliams2022-09-21T01:52:10.289+00:00 Discord

I think you'd want that approach anyway.

fzyzcjy2022-09-21T01:52:15.921+00:00 Discord

Yes, only one in single vsync. But when the main code is very slow (say 10 frames to build/layout/paint/...), we want to respond to 2nd 3rd ... vsync

fzyzcjy2022-09-21T01:52:30.188+00:00 Discord

So engine has to be modified IMHO

fzyzcjy2022-09-21T01:53:31.601+00:00 Discord

I am not an expert in engine TBH (never did such big changes before!), but I will try my best to prototype it.

jonahwilliams2022-09-21T01:54:53.42+00:00 Discord

That is a very substantial change. What is the advantage to that approach over scheduling a new frame after the preemption?

fzyzcjy2022-09-21T01:56:45.384+00:00 Discord

Sorry not quite get it. If we schedule a new frame, and vsync comes, what should we do? We cannot call Flutter's onDrawFrame definitely, because Flutter is still busy doing build/layout/... of the first frame.

jonahwilliams2022-09-21T01:59:18.221+00:00 Discord

So backing up a bit, you're not really pre-empting in the way I thought you were. My idea was something like:

  • Start drawing a frame
  • Exceed threshold
  • Submit frame
  • Schedule new frame with some metadata that allows continuation.
  • Repeat

Then your idea is more like:

  • Start drawing a frame
  • Exceed threshold
  • Submit frame
  • Continue building
  • Submit frame
  • Finish building

Is that correct?

fzyzcjy2022-09-21T02:02:19.45+00:00 Discord

Yes!

fzyzcjy2022-09-21T02:02:28.588+00:00 Discord

That is why I have zero overhead for suspending the layout phase

fzyzcjy2022-09-21T02:04:05.278+00:00 Discord

"with some metadata that allows continuation" - that seems to be the approach that was discussed in Jan 2022 by @Hixie etc, and discussed again in May (?) 2022 by bytedance people, and discussed again by me in github. I am writing comparison about it in google doc now (WIP), under the title"Compared with modify-the-layout-function methods".

jonahwilliams2022-09-21T02:04:44.844+00:00 Discord

one disadvantage of not yielding is that I don't think you'll receive input events. i.e. you start a page transition, start janking and then the user cancels. How do you avoid just continuing the animation?

At least if you yield, you could receive input events and run event handlers

fzyzcjy2022-09-21T02:05:12.05+00:00 Discord

Please have a look at preemptHandleTouchEvents

fzyzcjy2022-09-21T02:05:30.914+00:00 Discord

The preempt-aware special widgets will do that. For example, see "scrolling ListView" example. The scrolling will be 60fps.

jonahwilliams2022-09-21T02:07:15.993+00:00 Discord

You're going to end up rebuilding widgets recursively from within layout of a layer tree. I'm not really sure if that would work.

jonahwilliams2022-09-21T02:07:41.176+00:00 Discord

I guess have pre-emption you can yield from layout, handle events, and then go back?

jonahwilliams2022-09-21T02:08:11.978+00:00 Discord

But TBH that seems a lot more complicated than using the existing drawFrame APIs and scheduling new tasks.

jonahwilliams2022-09-21T02:09:22.339+00:00 Discord

and preemptHandleTouchEvents isn't sufficient, because you need to account for the existing gesture areas, otherwise event behavior may change between non pre-empted and pre-empted rendering

fzyzcjy2022-09-21T02:10:09.297+00:00 Discord

rebuilding widgets recursively from within layout of a layer tree Well no? I will just grab the layer tree and send to raster thread, without touching RenderObject, let alone element or widget

jonahwilliams2022-09-21T02:10:13.806+00:00 Discord

I think if you broke it down so that each pre-emption behaved like a regular flutter frame though, it might work

jonahwilliams2022-09-21T02:10:25.02+00:00 Discord

but then you might as well just schedule a frame

jonahwilliams2022-09-21T02:10:36.586+00:00 Discord

but event handlers can setState

fzyzcjy2022-09-21T02:11:26.835+00:00 Discord

and preemptHandleTouchEvents isn't sufficient, because you need to account for the existing gesture areas, otherwise event behavior may change between non pre-empted and pre-empted rendering It is just for simple things like "shifting a listview". For normal gestures, let it be done in normal frame pipeline. In other words, suppose it takes 1s to run a full pipeline, then the ListView will see all touch events during this 1s.

jonahwilliams2022-09-21T02:11:43.88+00:00 Discord

No, you can't create a distinct pre-empty only set of gestures

fzyzcjy2022-09-21T02:11:50.42+00:00 Discord

The preemptHandleTouchEvents will see events in each of 60fps frame, but that is like "secretly peeking at it"

jonahwilliams2022-09-21T02:12:01.234+00:00 Discord

you'll get different behavior between pre epmted and non pre-empted rendering

jonahwilliams2022-09-21T02:12:08.729+00:00 Discord

you have to go through the full event dispatch for correctness

fzyzcjy2022-09-21T02:12:26.083+00:00 Discord

each pre-emption behaved like a regular flutter frame though, it might work I am worried about that, b/c we will be build/layout a whole tree, inside the middle of build/layout a whole tree

jonahwilliams2022-09-21T02:12:33.865+00:00 Discord

consider the case where a ListView is behind something like a pointer interceptor

jonahwilliams2022-09-21T02:13:01.238+00:00 Discord

you don't want the listview to get scroll events in pre-empted frames because the pointer interceptor isn't aware of the pre-empt behavior

fzyzcjy2022-09-21T02:13:06.057+00:00 Discord

consider the case where a ListView is behind something like a pointer interceptor That's why preemptHandleTouchEvents is for animations, not general-purpose

fzyzcjy2022-09-21T02:13:53.303+00:00 Discord

Users may set a preempt handler in this case, specifying it not to scroll

fzyzcjy2022-09-21T02:14:26.016+00:00 Discord

It is like how React Fiber does things

fzyzcjy2022-09-21T02:14:36.527+00:00 Discord

With fiber, JS animation still jank. Only css animation is smooth

jonahwilliams2022-09-21T02:14:54.095+00:00 Discord

React does not have a layout or paint phase, and ultimately works much different from flutter

fzyzcjy2022-09-21T02:15:01.187+00:00 Discord

preemptHandleTouchEvents and its brothers are like "css animation" - another thing, parallel to traditional flutter widgets etc

jonahwilliams2022-09-21T02:15:21.327+00:00 Discord

i.e. React essentially yields during the equivalent of build

fzyzcjy2022-09-21T02:15:22.377+00:00 Discord

Sure, I am just analogy 🙂

fzyzcjy2022-09-21T02:15:55.253+00:00 Discord

I am analogy about the framework users' feeling: They have to write down something different (CSS instead of JS) for smooth animation.

jonahwilliams2022-09-21T02:16:07.174+00:00 Discord

Introducing a different set of event handlers, that may only fire sometimes during scrolling, is not a reasonable change IMO

jonahwilliams2022-09-21T02:16:13.568+00:00 Discord

it will be too hard for users to predict the behavior

fzyzcjy2022-09-21T02:16:15.46+00:00 Discord

Anyway for standard cases, such as ListView scrolling, or any animation that requires one DisplayListLayer, we can embed into framework

fzyzcjy2022-09-21T02:18:12.305+00:00 Discord

Hmm

fzyzcjy2022-09-21T02:18:22.137+00:00 Discord

So what solution do you think?

jonahwilliams2022-09-21T02:18:48.759+00:00 Discord

adjust your design 🙂

jonahwilliams2022-09-21T02:18:58.931+00:00 Discord

you are essentially proposing a new, Flutter-like framework

jonahwilliams2022-09-21T02:19:06.495+00:00 Discord

which, maybe that is the right thing for your use-case

fzyzcjy2022-09-21T02:19:22.631+00:00 Discord

We cannot run hitTest for not-yet-layout widgets I guess

fzyzcjy2022-09-21T02:19:29.026+00:00 Discord

What?

fzyzcjy2022-09-21T02:20:03.49+00:00 Discord

Well I am considering the general use case: Loading indicator, scrolling ListView, enter page transition. Isn't that many people needs 🙂

jonahwilliams2022-09-21T02:20:08.29+00:00 Discord

event handling is pretty core to how the framework behaves. not that we can't adjust the behavior, but what you are describing sounds like a huge departure.

jonahwilliams2022-09-21T02:20:46.019+00:00 Discord

and, if you are seriously dropping frames on the UI side of things with those examples, you will also drop raster frames

fzyzcjy2022-09-21T02:20:50.688+00:00 Discord

Indeed for my own case, I have heavy rasterize. But I do not propose PRs about this, since I guess few people have my own case

jonahwilliams2022-09-21T02:21:00.645+00:00 Discord

so that may not help reduce the perception of jank

fzyzcjy2022-09-21T02:21:36.742+00:00 Discord

Sorry I do not quite get it. I send layer tree to rasterizer at 60fps, so no jank?

fzyzcjy2022-09-21T02:21:42.983+00:00 Discord

I see.

jonahwilliams2022-09-21T02:21:51.329+00:00 Discord

no, because each layer tree also has to be rasterized and then submitted

jonahwilliams2022-09-21T02:22:00.23+00:00 Discord

and that requires work on the engine side and then the GPU

jonahwilliams2022-09-21T02:22:03.364+00:00 Discord

which can also jank

JsouLiang2022-09-21T02:22:08.037+00:00 Discord

I think the React generator the VirtualDom Tree and Diff VDomTree will cost too long time, so in React18 it use a Fiber to interrupt the process if a recursion level is too deep

fzyzcjy2022-09-21T02:22:10.259+00:00 Discord

You mean rastierzed at ui thread or raster thread?

jonahwilliams2022-09-21T02:22:20.267+00:00 Discord

raster thread

fzyzcjy2022-09-21T02:22:37.927+00:00 Discord

"raster thread runs too slow that it causes visual jank" is not addressed in this proposal indeed.

fzyzcjy2022-09-21T02:22:43.991+00:00 Discord

That may be a separate proposal

fzyzcjy2022-09-21T02:23:25.332+00:00 Discord

Yes I am not analogy to that specific algorithm, just analogy about dev experience.

jonahwilliams2022-09-21T02:23:36.341+00:00 Discord

Which I think is essentially yielding during build, for the Flutter analogy. But Flutter runs layout/paint in the same thread, whereas browsers already have separate threads

fzyzcjy2022-09-21T02:24:03.131+00:00 Discord

Yes, so my design is not an analogy to fiber when it comes to implementation

jonahwilliams2022-09-21T02:24:04.878+00:00 Discord

html is also much more tolerant of half finished UIs....

fzyzcjy2022-09-21T02:24:20.624+00:00 Discord

Indeed this design very different from fiber 🙂

JsouLiang2022-09-21T02:24:30.92+00:00 Discord

But the widget build or layout is run on the UI Thread, if build or layout is cost too long time, it will jank

fzyzcjy2022-09-21T02:24:46.17+00:00 Discord

Yes, and this proposal tries to address the problem

JsouLiang2022-09-21T02:24:50.285+00:00 Discord

that same like the vdom diff or build in the js thread

fzyzcjy2022-09-21T02:25:04.027+00:00 Discord

So, currently our problem is, how to handle input events?

JsouLiang2022-09-21T02:25:04.109+00:00 Discord

i think they are the same issue

jonahwilliams2022-09-21T02:25:21.515+00:00 Discord

I think you should handle input events by using the regular event handling pipeline

fzyzcjy2022-09-21T02:25:28.167+00:00 Discord

Yes that is our goal

jonahwilliams2022-09-21T02:25:50.857+00:00 Discord

which requires you to implement this by scheduling new frames instead of allowing multiple submissions from a single frame

fzyzcjy2022-09-21T02:26:33.477+00:00 Discord

I am thinking about it - is it possible to do this inside preempt...

fzyzcjy2022-09-21T02:29:40.078+00:00 Discord

Is it possible to do like this:

fzyzcjy2022-09-21T02:30:13.226+00:00 Discord

Do not propagate to ROs that are dirty. Only propagate to those who are clean.

fzyzcjy2022-09-21T02:30:16.165+00:00 Discord

i.e. call hitTests etc

fzyzcjy2022-09-21T02:30:24.35+00:00 Discord

All done inside the preemptRender.

fzyzcjy2022-09-21T02:31:06.892+00:00 Discord

@Jonah Williams Will we have trouble?

jonahwilliams2022-09-21T02:31:26.588+00:00 Discord

TBH I think you'll have a tremendous amount of trouble

jonahwilliams2022-09-21T02:31:37.41+00:00 Discord

good luck!

fzyzcjy2022-09-21T02:31:42.713+00:00 Discord

Ah??

fzyzcjy2022-09-21T02:32:07.575+00:00 Discord

For design of event handler, or for the whole proposal?

jonahwilliams2022-09-21T02:32:12.837+00:00 Discord

whole thing

fzyzcjy2022-09-21T02:32:17.835+00:00 Discord

Ah

jonahwilliams2022-09-21T02:33:04.669+00:00 Discord

I would really be interested in a using a prototype

fzyzcjy2022-09-21T02:33:10.654+00:00 Discord

Yes I will do that

fzyzcjy2022-09-21T02:33:17.358+00:00 Discord

do you think it is worthwhile to prototype it

jonahwilliams2022-09-21T02:33:20.266+00:00 Discord

but I am worried this would break in many unexpected ways

fzyzcjy2022-09-21T02:33:39.022+00:00 Discord

if it is a meaningless proposal, I will just halt now

jonahwilliams2022-09-21T02:34:39.332+00:00 Discord

It depends on what your goal is. If you want to keep exploring this, I don't see any way forward besides building a prototype. I may yet be wrong, or we may learn something from the proposal even if it doesn't get accepted.

But building a prototype doesn't mean that we're committed to accepting it in the framework or engine

jonahwilliams2022-09-21T02:35:10.082+00:00 Discord

or you may find ways to adjust the proposal to implement it with few or no changes to the framework

fzyzcjy2022-09-21T02:35:13.758+00:00 Discord

But building a prototype doesn't mean that we're committed to accepting it in the framework or engine

fzyzcjy2022-09-21T02:35:17.806+00:00 Discord

Sure I know that

fzyzcjy2022-09-21T02:41:11.233+00:00 Discord

@Jonah Williams About the event handler problem: In React Fiber example, a programmer has to think about two systems as well - the JS animation system and CSS animation system. They have to think about "the JS animation is jank while CSS animation runs smoothly" and collaborate with that. So in our Flutter even handler problem, maybe it is OK for dev to think about "the ListView event handler is janky, while the preempt scrolling is smooth"?

jonahwilliams2022-09-21T02:41:47.336+00:00 Discord

No

jonahwilliams2022-09-21T02:41:57.25+00:00 Discord

I mean, maybe

jonahwilliams2022-09-21T02:42:00.715+00:00 Discord

but my guess is no

fzyzcjy2022-09-21T02:42:06.777+00:00 Discord

Hmm

fzyzcjy2022-09-21T02:42:16.23+00:00 Discord

It is opt-in. If someone hates it just set flag to false.

jonahwilliams2022-09-21T02:42:18.356+00:00 Discord

HTML+CSS+JS Is not exactly a high water mark of ease of use

jonahwilliams2022-09-21T02:42:29.903+00:00 Discord

and its not a goal to be as complicated as it can be

jonahwilliams2022-09-21T02:43:11.629+00:00 Discord

because the problem isn't just that "the ListView event handler is janky, while the preempt scrolling is smooth"?, its that the ListView only scrolls when pre-empted because I'm actually expecting the events to be sent to a different event handler

fzyzcjy2022-09-21T02:43:12.81+00:00 Discord

I know its limitation and love Flutter (as you can see - I write Flutter code now instead of web code)

jonahwilliams2022-09-21T02:43:53.1+00:00 Discord

or consider the case where the listview is literally covered

fzyzcjy2022-09-21T02:44:00.864+00:00 Discord

"the ListView only scrolls when pre-empted because I'm actually expecting the events to be sent to a different event handler" When preempt: true they are expecting it to happen maybe?

jonahwilliams2022-09-21T02:44:10.971+00:00 Discord

so it wouldn't get events normally because the dispatchEvent code handles that

fzyzcjy2022-09-21T02:44:23.878+00:00 Discord

I will think about thta

jonahwilliams2022-09-21T02:44:24.662+00:00 Discord

but now suddenly its receiving scroll events?

jonahwilliams2022-09-21T02:45:39.795+00:00 Discord

With my apologies to @Hixie 😆

fzyzcjy2022-09-21T02:48:11.404+00:00 Discord

I agree that should never happen

fzyzcjy2022-09-21T02:49:26.36+00:00 Discord

Is it possible we let developers manually handle it? Flutter is like a automatic car, but when really needed (for performance), give dev a manual control button?

fzyzcjy2022-09-21T02:49:47.208+00:00 Discord

everything is opt in, not opt out

jonahwilliams2022-09-21T02:50:10.83+00:00 Discord

If I'm trying to provide a generic ListView like widget, how can I tell within a frame if I am obscured or not?

jonahwilliams2022-09-21T02:50:43.202+00:00 Discord

(generally you don't need to handle this, because the hitTest system does for you)

fzyzcjy2022-09-21T02:51:10.044+00:00 Discord

Let the dev do the job. For example, suppose we are developing this Discord mobile app. Then I will specify "it is always visible" if I am the dev.

fzyzcjy2022-09-21T02:51:36.833+00:00 Discord

If we are developing docs.google.com I will also specify "always visible"

fzyzcjy2022-09-21T02:52:05.899+00:00 Discord

Of course, except that it is not the latest route entry - but that case is simple to be built-in

jonahwilliams2022-09-21T02:52:19.517+00:00 Discord

that means that nothing in the framework will be able to use this

fzyzcjy2022-09-21T02:52:38.055+00:00 Discord

That maybe means framework needs to accept a parameter

fzyzcjy2022-09-21T02:52:41.895+00:00 Discord

by users

fzyzcjy2022-09-21T02:52:51.433+00:00 Discord

eg. ListView(prempt: PreemptStrategy?)

fzyzcjy2022-09-21T02:53:09.651+00:00 Discord

where abstract class PreemptStrategy { bool shouldWeAcceptThatHitTest(); }. And provide null to disable preempt.

fzyzcjy2022-09-21T02:53:35.142+00:00 Discord

I know it is not an automatic car in such cases 🙂

fzyzcjy2022-09-21T02:54:12.627+00:00 Discord

And, for the enter page transition, and the loading indicator, we even do not need gestures handling

jonahwilliams2022-09-21T02:54:22.114+00:00 Discord

you do, because they can be cancelled

fzyzcjy2022-09-21T02:54:54.919+00:00 Discord

I mean, no extra gesture handling at 60fps. Just normal handling at low fps

jonahwilliams2022-09-21T02:55:52.423+00:00 Discord

I don't really think it would be OK. You've got a performance improvement that is off by default, and using requires knowing exactly how your layout will look at all times. And nothing in the framework can use it....

fzyzcjy2022-09-21T02:55:53.867+00:00 Discord

For example, I tap a button and enter a page. Maybe we do not care that the system cannot respond to my touch when the new page is transitioning in (indeed, what gesture will we have there?)

fzyzcjy2022-09-21T02:56:37.055+00:00 Discord

But, when new page is transitioning in, if that transition is, say, 15fps, humans eyes can see it and feel it not good

jonahwilliams2022-09-21T02:57:26.301+00:00 Discord

have you looked at how we made the Android ZoomPageTransition faster (on master currently)? FWIW It was almost entirely raster thread issues

fzyzcjy2022-09-21T02:57:39.632+00:00 Discord

Yes I followed that github issue

jonahwilliams2022-09-21T02:57:54.388+00:00 Discord

ahh right, I remember you were in the Github review

fzyzcjy2022-09-21T02:58:00.11+00:00 Discord

haha

fzyzcjy2022-09-21T02:58:19.428+00:00 Discord

But when the new page has a ton of widgets, we will face build/layout jank I guess?

fzyzcjy2022-09-21T02:58:45.162+00:00 Discord

raster slowness is like a separate issue that my current design doc does not address

jonahwilliams2022-09-21T02:58:50.043+00:00 Discord

even on the low end android devices I tested, all of the jank was raster jank

jonahwilliams2022-09-21T02:59:07.94+00:00 Discord

that or GCs, which you will hit even with your system

fzyzcjy2022-09-21T02:59:40.343+00:00 Discord

Hmm so you are saying we do not need to optimize build/layout jank?

fzyzcjy2022-09-21T03:00:07.91+00:00 Discord

@Jsouliang do you see build/layout jank in your bytedance app? Or are all of them raster jank?

fzyzcjy2022-09-21T03:00:15.863+00:00 Discord

Personally speaking my app has build/layout jank as well

fzyzcjy2022-09-21T03:00:43.194+00:00 Discord

But given it is not open sourced and I cannot say I am the expert in optimization this may not be a big evidence

jonahwilliams2022-09-21T03:01:20.528+00:00 Discord

text layout can be quite slow

JsouLiang2022-09-21T03:02:04.375+00:00 Discord

Raster jank we call look forward to impeller to solve it, case most raster thread jank are caused by share compile

JsouLiang2022-09-21T03:02:34.254+00:00 Discord

Yes, some UI jank are cased the measure text content

jonahwilliams2022-09-21T03:02:39.979+00:00 Discord

I wish that were true, some Skia functionality is actually quite slow the way we use it. Clips for example, not slow due to shader compile. Same with ImageFilters

JsouLiang2022-09-21T03:02:43.414+00:00 Discord

that will block the UI Layout

JsouLiang2022-09-21T03:03:22.283+00:00 Discord

do you have some idea to optimize the text measure?

jonahwilliams2022-09-21T03:04:29.49+00:00 Discord

We've not had any success

JsouLiang2022-09-21T03:04:39.354+00:00 Discord

image image

fzyzcjy2022-09-21T03:04:48.084+00:00 Discord

Then maybe my proposal is a bit useful I guess?

jonahwilliams2022-09-21T03:05:24.632+00:00 Discord

If your prototype works and doesn't require Flutter 4.0, then maybe

fzyzcjy2022-09-21T03:05:38.119+00:00 Discord

Haha surely not 4.0 - all user visible API are opt in (and btw Flutter 3.0 has no breaking change)

jonahwilliams2022-09-21T03:06:31.455+00:00 Discord

the problem with opt in, is that most folks won't. Similarly, I don't think creating a parallel event handling system is reasonable. I think you will have better luck trying to break apart expensive scenes into multiple frames. But ultimately its your time + resources, so spend them how you see fit

fzyzcjy2022-09-21T03:07:10.44+00:00 Discord

break apart expensive scenes into multiple frames I guess that is how hixie, dnfield, bytedance people, and I have tried. No success yet 😦

fzyzcjy2022-09-21T03:07:23.545+00:00 Discord

Anyway I will also try on that

fzyzcjy2022-09-21T03:14:16.548+00:00 Discord

@Jonah Williams Another workaround: What if we throw away support for gesture system in preemptRender? When scrolling a ListView, it is mainly the inertia the drives the list to move, and seems that human finger speed will not dramatically change during a swipe. Then, if we never support gesture in preemptRender, the following will happen: (1) ListView is scrolled at 60fps, instead of (e.g.) 15fps, so user eyes will not see jank. (2) ListView is responding to user finger at only 15fps, but since this is not a game but just a scrolling, users may not feel it

fzyzcjy2022-09-21T03:14:35.687+00:00 Discord

Now we can by default enable the feature, and no parallel event handling system

fzyzcjy2022-09-21T03:15:02.383+00:00 Discord

Btw for the loading indicator and the enter page transition example, already no need for event handling by default

Callum2022-09-21T03:15:42.021+00:00 Discord

I looked at the document, an interesting idea for sure with the synchronous/parallel tree. But I also think it won't be feasible to implement the preemptModifyLayerTree() unless the animation is very simple. The more applicable it is, the more you are just reimplementing the framework.

fzyzcjy2022-09-21T03:18:23.727+00:00 Discord

But I also think it won't be feasible to implement the preemptModifyLayerTree() unless the animation is very simple. The more applicable it is, the more you are just reimplementing the framework. It already supports the following with simple code, see doc for details:

  • any widgets fit within DisplayListLayer, such as CircularProgressIndicator. Arbitrarily fancy animation goes here, as long as in one DisplayListLayer. Or more generally, if they fit in a leaf subtree.
  • let ListView scroll at 60fps
JsouLiang2022-09-21T03:19:11.66+00:00 Discord

@Jonah Williams what's the 'ghost text' ? image

fzyzcjy2022-09-21T03:24:58.836+00:00 Discord

@Callum We may integrate with the existing framework and allow a lot of widgets to animate (just a very rough proposal): Indeed what is done in preemptModifyLayerTree is just to modify arbitrary layers, so maybe we can reuse existing framework to arbitrarily render a subtree etc. Not come up with details yet though. But anyway, I guess most people just need a smooth loading indicator, a smooth scrolling listview?

fzyzcjy2022-09-21T03:26:48.543+00:00 Discord

@Jonah Williams So is this proposal looks ok? (repeat here in case the original comment is already so above that it is not read)

jonahwilliams2022-09-21T03:31:10.112+00:00 Discord

Rendering placeholder text, like a bunch of grey blocks in place of your actual text on first frames or when doing larger transitions

jonahwilliams2022-09-21T03:32:13.192+00:00 Discord

If the behavior of the framework is changing substantially in pre-empty frames then I think most developers would interpret that as a bug

fzyzcjy2022-09-21T03:34:27.622+00:00 Discord

But I wonder how should we know how many blocks? You know each english letter has different width, and paragraphing is quite hard to guess. If we put wrong number of grey blocks, the UI will be of wrong height. Then, after it gets the real height, all widgets below it (suppose we have a ListView) will jump

fzyzcjy2022-09-21T03:35:16.572+00:00 Discord

@Jonah Williams Well, what about this: It is still the plain old Flutter with (e.g.) 15fps. But we just have some "magic" here, such that it automatically generates something as a "tween" to have 60fps.

fzyzcjy2022-09-21T03:36:37.045+00:00 Discord

IIRC, some researches or nvidia has some "magic" such that, they input some low resolution low fps frames, and output high resolution high fps frames Consider this proposal as such kind of "tween creator", then maybe dev will feel it natural

dnfield2022-09-21T03:58:10.415+00:00 Discord
dnfield2022-09-21T03:58:10.415+00:00 Discord

Started a thread.

dnfield2022-09-21T03:58:10.845+00:00 Discord

This is a good part of what makes it hard. The difficult case right now isn't the progress indicator, it's when you have a large scene change (like a route transition or some other full screen animation).

dnfield2022-09-21T03:58:49.047+00:00 Discord

And lets say you want to render a lot of small pieces of text, which now all have to get laid out for the first time and will send you over budget for a single frame.

fzyzcjy2022-09-21T03:59:52.684+00:00 Discord

I see, I guess my proposal can solve the problem?

fzyzcjy2022-09-21T04:00:10.347+00:00 Discord

@dnfield Btw what do you think about the proposal 🙂

dnfield2022-09-21T04:03:57.720Z Google Doc

Layout for a single widget can easily blow through frame budget. That's part of what we'd like to solve with an interruptible approach, assuming such an approach is possible.

dnfield2022-09-21T04:04:10.196Z Google Doc

This will cause problems with scrolling/touch events.

dnfield2022-09-21T04:05:06.582Z Google Doc

These are not particularly pressing examples right now - showing a single progress indicator or simple animation tends not to be the issue, it's more like doing a full screen route transition where you're building a a large new tree or subtree with a few hundred widgets to inflate and layout.

dnfield2022-09-21T04:05:58.504Z Google Doc

This needs a lot more details about how you would sensibly interrupt and restart layout at a meaningful point.

dnfield2022-09-21T04:06:14.838+00:00 Discord

I think it's interesting but it's missing a lot of important details

fzyzcjy2022-09-21T04:11:57.797+00:00 Discord

I am willing to fill in details

fzyzcjy2022-09-21T04:12:07.121+00:00 Discord

and will prototype as well

fzyzcjy2022-09-21T04:12:12.307+00:00 Discord

so what is missing?

fzyzcjy2022-09-21T04:13:53.415Z Google Doc

Hmm do you mean one single leaf widget? 

Indeed that is also very simple: I am proposing adding if (timeout) preemptRender() at the beginning of each RenderObject.layout.

Now, we can add more. Say, for YourHeavyRenderObject.performLayout() function, add 10 of such if timeout preemptRender

fzyzcjy2022-09-21T04:14:24.576Z Google Doc

That's "that" approach which I am comparing to. My proposal does not have this :)

fzyzcjy2022-09-21T04:16:01.715Z Google Doc

 showing a single progress indicator or simple animation tends not to be the issue

Well the example is indeed, "have progress indicator 60fps, while we are doing something really heavy". i.e. just the interesting case you said :)

Content edited

fzyzcjy2022-09-21T04:18:05.939Z Google Doc

The proposal do not interrupt, neither restart. It just calls a function (the preemptRender).

Not sure but maybe you have the same understanding as @JonahWilliams? This comment may be helpful: https://discord.com/channels/608014603317936148/608021234516754444/1021963804747255929

fzyzcjy2022-09-21T04:18:30.886+00:00 Discord

I have replied to google doc (not see that just now)

fzyzcjy2022-09-21T06:54:27Z GitHub

PerformanceOverlay's multiple fields are not updated when the user wants to update it

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-21T06:57:43Z GitHub

Fix logical error in TimePickerDialog - the RenderObject forgets to update fields

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-21T06:57:45Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-09-21T06:58:55Z GitHub

Fix CupertinoAlertDialog and CupertinoActionSheet, which mis-behave when orientation changes

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-21T06:59:48Z GitHub

Fix SliverScrollingPersistentHeader not able to update stretchConfiguration

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-21T07:00:29Z GitHub

Fix SliverPinnedPersistentHeader, also not able to update stretchConfiguration and showOnScreenConfiguration

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-21T07:01:05Z GitHub

Add assertion to _CupertinoSwitchRenderObjectWidget, otherwise it is confusing why updateRenderObject omits state update

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-21T07:01:08Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-09-21T07:01:41Z GitHub

Fix RenderEditable not able to update backgroundCursorColor when the user provides a new one

Close #112038. Please see description there.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Nayuta4032022-09-21T12:15:28.332Z Google Doc

How is this Layertree generated (since the current RO is not painted), it still looks like the LayerTree from the previous frame?

Nayuta4032022-09-21T12:20:48.095Z Google Doc

If I understand correctly, you want to use the full LayerTree result from the last time. Like this object in the Raster thread?

https://github.com/flutter/engine/blob/4839a3b5d225f7d522bf9edf5cfe395c187b0e93/shell/common/rasterizer.cc#L381

JsouLiang2022-09-21T13:02:01.56+00:00 Discord

In the current Framework design, once entering the RenderFlexObject, all child nodes will be Layout, and when the RO tree Layout is completed, paint will be submitted. Now we add a timer to the RenderFlexObject layout, the first child node layout is almost finished (16ms), at which point we interrupt the layout and submit the current RO tree for Paint. At this point, RenderFlexObject contains a child node. When the next schedule comes, do I continue to start from this RenderFlexObject node or something else?

fzyzcjy2022-09-21T13:04:08.683+00:00 Discord

So I guess you mean your example has an app which has Flex as its root widget, and thus RenderFlex as its root RenderObject?

fzyzcjy2022-09-21T13:05:14.064+00:00 Discord

When the next schedule comes, do I continue to start from this RenderFlexObject node or something else? We do not continue to "start from" anywhere. We just call the preemptRender function inside RenderObject.layout(). And then, when preemptRender function returns, we continue running our code.

fzyzcjy2022-09-21T13:05:28.703+00:00 Discord

preemptRender is nothing but a very normal function

fzyzcjy2022-09-21T13:06:00.227Z Google Doc

Yes, from previous frame, but modified a bit from preemptModifyLayerTree.

fzyzcjy2022-09-21T13:06:28.964Z Google Doc

Almost the same full layer tree, except for preemptModifylayerTree

Nayuta4032022-09-21T13:06:29.111+00:00 Discord

I want to know more details of preemptModifyLayerTree CircularProgressIndicator such as you mentioned. What will do in preemptModifyLayerTree?

fzyzcjy2022-09-21T13:06:48.179+00:00 Discord
void preemptModifyLayerTree() {
for (final renderObject in renderObjectsWhoWantsToPreemptModifyTheLayerTree) renderObject.preemptModifyLayerTree();
}
fzyzcjy2022-09-21T13:07:00.748+00:00 Discord
class RenderPreemptDisplayList extends RenderBox {

void paint(PaintingContext context, Offset offset) {
layer = paint_child_subtree_inside_a_DisplayListLayer();
}


void preemptModifyLayerTree() {
layer = paint_child_subtree_inside_a_DisplayListLayer();
}
}

fzyzcjy2022-09-21T13:07:12.544+00:00 Discord

So, it calls RenderPreemptDisplayList.preemptModifyLayerTree

fzyzcjy2022-09-21T13:07:26.587+00:00 Discord

Then, RenderPreemptDisplayList.preemptModifyLayerTree paints its child.

fzyzcjy2022-09-21T13:07:42.24+00:00 Discord

For simplicity, let's say, we do not want a CircularProgressIndicator, but only want a color rect

fzyzcjy2022-09-21T13:07:44.288+00:00 Discord

Then it is like:

fzyzcjy2022-09-21T13:08:21.273+00:00 Discord
class RenderWhatever extends RenderBox {

void paint(PaintingContext context, Offset offset) {
layer = create_DisplayListLayer_and_paint_a_colored_rect(color: red);
}


void preemptModifyLayerTree() {
layer = create_DisplayListLayer_and_paint_a_colored_rect(color: whatever_color_you_like_in_animation);
}
}
Nayuta4032022-09-21T13:09:53.23+00:00 Discord

What would you do in the paint_child_subtree_inside_a_DisplayListLayer method at this point in the interrupt, maybe the child node hasn't been built/laid out yet?

Nayuta4032022-09-21T13:10:41.189+00:00 Discord

layer = create_DisplayListLayer_and_paint_a_colored_rect(color: red); like placeholder ?

fzyzcjy2022-09-21T13:11:00.194+00:00 Discord

For circular progress indicator, just paint a indicator you need

fzyzcjy2022-09-21T13:11:13.74+00:00 Discord

for scrolling listview, it is a longer story, maybe see doc

fzyzcjy2022-09-21T13:15:53Z GitHub

For readers of GitHub and not yet read Discord: Some discussions happen in Discord as well, see - https://discord.com/channels/608014603317936148/608021234516754444/1021783497112821861

As well as the sub-discussion in discord: https://discord.com/channels/608014603317936148/1021987751710699632

Nayuta4032022-09-21T13:27:13.838+00:00 Discord

@fzyzcjy The build/layout phase is a recursive call, how do you break it?On keframe, we're actually doing recursion on the current frame, just using placeholders

fzyzcjy2022-09-21T13:28:54.27+00:00 Discord

Just do not break it 🙂 We are calling preemptRender. The call stack become deeper when calling that function

fzyzcjy2022-09-21T13:29:49.613+00:00 Discord

image

fzyzcjy2022-09-21T13:30:14.191+00:00 Discord

In that figure, a rectangle means the life of a function.

JsouLiang2022-09-21T13:30:42.184+00:00 Discord

so for not layout node we will paint a placeholder rect like this?

fzyzcjy2022-09-21T13:31:13.365+00:00 Discord

We will know nothing for a node that has not been painted before this frame

fzyzcjy2022-09-21T13:31:20.06+00:00 Discord

Because it is not in the layer tree

fzyzcjy2022-09-21T13:31:41.969+00:00 Discord

No placeholder rect indeed. But if you like, what about just putting a placeholder rect there, in previous frame

fzyzcjy2022-09-21T13:31:50.671+00:00 Discord

Well the story is like:

fzyzcjy2022-09-21T13:32:19.411+00:00 Discord

Some researches or nvidia has some "magic" such that, they input some low resolution low fps frames, and output high resolution high fps frames. Consider this proposal as such kind of "tween creator".

fzyzcjy2022-09-21T13:33:00.707+00:00 Discord

So this proposal will output some extra frames, which are just "previous jank frame's layer tree + preemotModifyLayerTree minor changes like animation"

Nayuta4032022-09-21T13:35:16.087+00:00 Discord

Does this mean that the build/layout of all nodes needs to be synchronized for this to happen? Normally build, layout are recursive calls.

fzyzcjy2022-09-21T13:35:56.459+00:00 Discord

Does this mean that the build/layout of all nodes needs to be synchronized for this to happen? Normally build, layout are recursive calls. It is still recursive call. Just like plain old. Except that one extra line:

fzyzcjy2022-09-21T13:36:00.38+00:00 Discord
class RenderObject {
void layout(Constraints constraints, { bool parentUsesSize = false }) {
if (nearTimeout) { preemptRender(); }
… the original layout code …
}
}
Nayuta4032022-09-21T13:45:41.984+00:00 Discord

I kind of figured it out. The core thing is that when my time is running out, submit a LayerTree to the engine in preemptRender for rendering, and then continue with layout (The recursion does not exit at this point ).

fzyzcjy2022-09-21T13:46:05.994+00:00 Discord

Yes!

fzyzcjy2022-09-21T13:46:22.79+00:00 Discord

Then we get zero overhead for a lot of thing, etc

Nayuta4032022-09-21T13:46:34.291+00:00 Discord

What if the whole element tree and RO tree change in the next frame

fzyzcjy2022-09-21T13:46:47.516+00:00 Discord

Just do the same thing in the next cycle

fzyzcjy2022-09-21T13:46:49.718+00:00 Discord

Is there any problem

fzyzcjy2022-09-21T13:47:02.625+00:00 Discord

Just use this mimic

Nayuta4032022-09-21T13:50:18.014+00:00 Discord

Is paint applied to the parent of the interrupt node? I'm more concerned with the generation of this Layertree.

fzyzcjy2022-09-21T13:53:22.71+00:00 Discord

No

fzyzcjy2022-09-21T13:53:50.641+00:00 Discord

no paint is happened in preemptRender, except for preemptModifyLayerTree - which only few specialized RO will handle

fzyzcjy2022-09-21T13:54:12.274+00:00 Discord

but anyway, if you use PreemptDisplayList and put progress indicator as its child, then that child does get painted

Nayuta4032022-09-21T13:56:09.617+00:00 Discord

The key point I think is that the generation of this Layertree, if we just call preemptRender(), For example, layer = create_DisplayListLayer_and_paint_a_colored_rect(color: whatever_color_you_like_in_animation); , it could be very different from the previous UI

fzyzcjy2022-09-21T14:01:14.513+00:00 Discord

We do not generate a whole new layer tree. Instead, we modify it a little bit in the preemptModifyLayerTree

fzyzcjy2022-09-21T14:01:33.409+00:00 Discord

For example, modify the color of a box a little bit, modify the shifting of the listview a bit

fzyzcjy2022-09-21T14:01:45.918+00:00 Discord

so it will be very similar to previous (janky) frame except for a bit of animation change

fzyzcjy2022-09-21T14:02:08.794+00:00 Discord

surely it may be different from next frame, if your prev frame is diff from next frame - but that definitely should be like that

Nayuta4032022-09-21T14:06:09.008+00:00 Discord

Yes, I think it depends on the height similarity between the two frames

Nayuta4032022-09-21T14:10:40.567+00:00 Discord

There are two issues I would love to see more detail on:

  1. How to generate this Layertree to the Engine when an interrupt occurs (e.g., two frames are completely different)
  2. How to handle vsync scheduling in case of interruption (for example, the whole Widget tree changes in the next vsync)
Nayuta4032022-09-21T14:11:02.801+00:00 Discord

Would you consider making a prototype of this solution any time soon?

fzyzcjy2022-09-21T14:11:45.216+00:00 Discord

How to generate this Layertree to the Engine when an interrupt occurs (e.g., two frames are completely different) Since when preemptRender happens, we are in build & layout phase, we never touch layer tree yet. So we have the old layer tree. Now, we provide the old layer tree (with minor modify from preemptModifyLayerTree), to the engine

fzyzcjy2022-09-21T14:12:53.7+00:00 Discord

How to handle vsync scheduling in case of interruption (for example, the whole Widget tree changes in the next vsync) No vsync is passed to dart layer during jank. Say we have a build/layout that takes 1s. Then during the 60 hardware vsync, we render 60 frames to screen via preemptRender. However, for the Dart code (excluding the preemptRender part), it only sees one vsync, one build/paint/layout/etc.

fzyzcjy2022-09-21T14:12:59.112+00:00 Discord

Would you consider making a prototype of this solution any time soon?

fzyzcjy2022-09-21T14:13:04.157+00:00 Discord

Sure 🙂

Nayuta4032022-09-21T14:16:40.946+00:00 Discord

I think the core is the design of this preemptModifyLayerTree which sounds similar to some low FPS conversion high FPS (inserting excessive frames in the middle).

fzyzcjy2022-09-21T14:17:01.026+00:00 Discord

Yes, I have said that here:

fzyzcjy2022-09-21T14:19:15.846+00:00 Discord

Indeed I am inserting a paragraph saying similar things into the doc at the same minute haha image image

Nayuta4032022-09-21T14:19:58.387+00:00 Discord

Yes, (sorry I didn't really understand that before)

fzyzcjy2022-09-21T14:20:06.373+00:00 Discord

image

fzyzcjy2022-09-21T14:20:14.726+00:00 Discord

That's OK

xanahopper2022-09-21T14:28:20.895+00:00 Discord

If the core is preemptModifyLayerTree, that means when you want do some high priority render, you have to implement it?

fzyzcjy2022-09-21T14:30:41.943+00:00 Discord

Yes and no.

  1. For whatever widget tree that fit in a layer subtree without disturbing others, like a CircularProgressIndicator, reuse existing (well, to-be-written) PreemptDisplayList
  2. For listview scrolling, maybe just let ListView's RO implement preemptModifyLayerTree to implement inertia etc. (The design doc is a bit old, see discussions above for details)
  3. But I will think about more general solution to support arbitrary case. Anyway this is not the top priority - we should have the main idea running
xanahopper2022-09-21T14:33:54.189+00:00 Discord

that means a new mechanism to implement for widgets who want use it, and has a cost to migrate

fzyzcjy2022-09-21T14:34:37.014+00:00 Discord

For widgets who want 60fps, I guess not much - you can reuse it in many places

fzyzcjy2022-09-21T14:35:07.218+00:00 Discord

It is like, we write down computeIntrinsicWidth, performLayout, performDryLayout, etc, a lot of things manually nowadays

fzyzcjy2022-09-21T14:36:05.385+00:00 Discord

Notice we never need to implement new functions for things like CircularProgressIndicator

fzyzcjy2022-09-21T14:36:26.001+00:00 Discord

for those, just use the existing (will-be-in-framework) PreemptDisplayList

xanahopper2022-09-21T14:37:56.198+00:00 Discord

And according to you pseudo code, one RO has a layer for previous frame, if it laid with all its relative parts, what should we do for its layer, will it be updated? and what happened when it completed? Did that means layer painting is un-interrupted?

fzyzcjy2022-09-21T14:38:46.083+00:00 Discord

if it laid with all its relative parts Do you mean, what happen if that RO has its layer be the new data (instead of old data of previous frame)?

fzyzcjy2022-09-21T14:38:53.726+00:00 Discord

That will not happen. We only preempt during build & layout phase. Paint is not happen yet

xanahopper2022-09-21T14:39:40.937+00:00 Discord

I got it, layers only change when paint commit

fzyzcjy2022-09-21T14:39:50.202+00:00 Discord

Yes, IIRC

xanahopper2022-09-21T14:40:48.299+00:00 Discord

So there is no such thing like Fiber do, build/layout, record and diff?

fzyzcjy2022-09-21T14:41:05.453+00:00 Discord

Maybe yes?

xanahopper2022-09-21T14:44:44.795+00:00 Discord

What if the preemptModifyLayerTree overcost?

fzyzcjy2022-09-21T14:45:25.064+00:00 Discord

Then it janks, surely

fzyzcjy2022-09-21T14:45:28.435+00:00 Discord

but why it overcost

xanahopper2022-09-21T14:45:47.764+00:00 Discord

when the implement sucks

fzyzcjy2022-09-21T14:45:49.511+00:00 Discord

e.g. painting a tiny progress indicator, should be fast; for listview scrolling example, just modify a little bit of OffsetLayer.offset

fzyzcjy2022-09-21T14:46:03.555+00:00 Discord

It is like asking "what if you misuse flutter" - answer is you get trouble 🙂

Nayuta4032022-09-21T14:51:48.119+00:00 Discord

A question popped into my mind, I'm thinking of what the difference between this and Keframe(Especially after your optimization, build/layout as many widgets as possible in one frame)? For example, for some Wigdet you mentioned, need to implement preemptModifyLayerTree, and in this case, what if you use KeFrame and you set a placeholder like that?

fzyzcjy2022-09-21T14:53:25.002+00:00 Discord

tell me the difference between this and Keframe I am going to sleep in a minute, will compare it tomorrow morning. Indeed the design doc has talked a little bit, and github issues also talked a bit.

SecondFlight2022-09-21T14:53:39.283+00:00 Discord

@fzyzcjy Hey! I've been lurking on the GitHub thread and didn't want to ask this there as I feel like I'm missing something.

Does your most recent proposal allow trivial layout changes to take priority over multi-frame changes? It seems a lot of nontrivial animations require some amount of layout shifting, but your diagram seems to indicate that all widgets must still build and layout in turn, so if an expensive widget needed to rebuild in a different part of the tree then it would still jank since the animation builder would still need to wait its turn.

Sorry if this is somewhat ignorant, I'm still trying to wrap my mind around it all.

SecondFlight2022-09-21T14:53:51.039+00:00 Discord

Also please sleep if you need to, the question can wait 🙂

fzyzcjy2022-09-21T14:54:20.679+00:00 Discord

@SecondFlight Hi, I will reply tomorrow morning 🙂

Nayuta4032022-09-21T14:55:15.345+00:00 Discord

Well, I actually know the answer to that, but I think it might need to be spelled out a little bit more clearly

Nayuta4032022-09-21T14:56:55.553+00:00 Discord

I'm still looking forward to your prototype ,Well done

Nayuta4032022-09-21T14:57:04.828+00:00 Discord

good night 🌙

fzyzcjy2022-09-21T14:57:27.502+00:00 Discord

Thanks, good night

dnfield2022-09-21T16:54:33.542+00:00 Discord

I've already lost track of this thread, but I think there's some good questions here about how this would work in a flex based widget (e.g. a column or row). We also should try to think about how it'd work in general for multi-child ROs

gaaclarke2022-09-21T22:26:53.326+00:00 Discord

I think we should probably have a linter that sorts required keyword parameters first. See this screenshot of the api popup in vscode, you have to scroll around and hunt for the signature of itembuilder image

gaaclarke2022-09-21T22:40:02.791+00:00 Discord

https://github.com/dart-lang/linter/issues/3708

fzyzcjy2022-09-21T23:05:35.414+00:00 Discord

@Jonah Williams Continuing from the problem of gesture subsystem of the design, here is a mental modal:

Some researchers/nvidia/etc have some "magic" such that, they can input some low resolution low fps frames, and output high resolution high fps frames. Consider this proposal as such a kind of "tween creator". In other words, originally we have janky rendering (say, 15fps). And now, we add three extra animating frames after each of the 15fps frames, to get a 60fps smooth feeling.

(Copied from updated design doc)

fzyzcjy2022-09-21T23:06:06.989+00:00 Discord

I've already lost track of this thread Ah feel free to ask any questions and I am willing to answer!

fzyzcjy2022-09-21T23:06:27.019+00:00 Discord

how this would work in a flex based widget (e.g. a column or row). We also should try to think about how it'd work in general for multi-child ROs It works well, no need to do any special treatment indeed 🙂

fzyzcjy2022-09-21T23:07:01.441+00:00 Discord

@dnfield Btw I have answered your comments in google doc as well

fzyzcjy2022-09-21T23:08:53.036+00:00 Discord

allow trivial layout changes to take priority over multi-frame changes yes, allow things like a animating indicator or a scrolling listview to take priority over normal janky heavy build/layout

fzyzcjy2022-09-21T23:09:39.7+00:00 Discord

if an expensive widget needed to rebuild in a different part of the tree then it would still jank since the animation builder would still need to wait its turn

fzyzcjy2022-09-21T23:10:34.282+00:00 Discord

If you mean that, your animation requires an expensive widget built onto a different part of tree, then yes it will jank

fzyzcjy2022-09-21T23:12:27.285+00:00 Discord

But will that be common? "some amount of layout shifting" - For example, a scrolling listview is very common case where we need 60fps smoothness. See the design doc, it indeed only needs a tiny change to OffsetLayer.offset

fzyzcjy2022-09-21T23:15:26.117+00:00 Discord

(I will update the doc probably within an hour)

jonahwilliams2022-09-21T23:16:53.525+00:00 Discord

I understand the argument you are trying to make but I don't find it convincing. Adding a second gesture system is not reasonable IMO. You should be working on how to eliminate that from your proposal. More time spent discussing with me is not going to help you there

fzyzcjy2022-09-21T23:17:58.101+00:00 Discord

Adding a second gesture system is not reasonable IMO. I am proposing to have no second gesture system 🙂

fzyzcjy2022-09-21T23:18:13.235+00:00 Discord

Ok I will update my proposal, reflecting there is no second gesture system at all

fzyzcjy2022-09-21T23:18:18.327+00:00 Discord

Forget to do that, sorry

fzyzcjy2022-09-21T23:20:50.499+00:00 Discord

And I am going to prototype today btw

gaaclarke2022-09-21T23:28:16.183Z Google Doc

I don't see a clear description of what your proposal is.  The first information under "detailed design" are questions.

fzyzcjy2022-09-21T23:29:52.258Z Google Doc

I should move it, wait a minute

gaaclarke2022-09-21T23:30:47.877+00:00 Discord

I don't understand the proposal, is it that if build + layout > 1/60ms then capture a continuation of that work, submit a frame to the renderer, and finish the build/layout on the next frame?

fzyzcjy2022-09-21T23:32:07.059+00:00 Discord

@Jonah Williams Hi I have removed gesture system from the proposal, and also add explanations why that looks reasonable. Mainly change the "example 2 implementation" at the bottom of proposal.

fzyzcjy2022-09-21T23:32:32.973+00:00 Discord

"on the next frame" - if it is quite slow, maybe on the next next next frame etc indeed

fzyzcjy2022-09-21T23:32:59.584+00:00 Discord

"capture a continuation of that work" - Well I do not capture anything. The preemptRender is nothing but a normal function call.

fzyzcjy2022-09-21T23:33:25.153+00:00 Discord

We just call preemptRender, which sends layer tree to raster it, and later when it returns we continue doing layout/build

gaaclarke2022-09-21T23:33:40.897+00:00 Discord

what are you rendering while that work is happening? a partial representation of the ui or the previous ui?

fzyzcjy2022-09-21T23:34:14.377+00:00 Discord

Previous layer tree (given that we preempt at build/layout, no modify to layer tree in current frame). But, we call preemptModifylayerTree, which modifies the layer a little bit for animations

fzyzcjy2022-09-21T23:34:37.735+00:00 Discord

e.g. in listview scrolling example, we call OffsetLayer.offset += 123.45 so content is moved

gaaclarke2022-09-21T23:35:00.233+00:00 Discord

ahh so, the previous layer, but some effort is made it keep it animating, interesting

fzyzcjy2022-09-21T23:35:16.411+00:00 Discord

Yes, like that

fzyzcjy2022-09-21T23:35:20.371+00:00 Discord

Thanks 🙂

gaaclarke2022-09-21T23:37:09.874+00:00 Discord

do you have a profile of those build + layout frames that are going over budget by any chance?

gaaclarke2022-09-21T23:37:50.383+00:00 Discord

I believe it can happen, but my first thought would be that maybe something like synchronous calls that shouldn't be synchronous are happening.

gaaclarke2022-09-21T23:41:52.568Z Google Doc

I think that stuff we just said on the discord would be a good description: preempt build/layout, update the last layer, render the last frame, resume build/layout

fzyzcjy2022-09-21T23:42:12.788+00:00 Discord

IIRC some discussions above confirm this jank does happens (especially on low end devices), let me find a link

fzyzcjy2022-09-21T23:43:11.019+00:00 Discord

In short: bytedance people reported jank of build/layout in their real world app

fzyzcjy2022-09-21T23:43:39.382+00:00 Discord

I am not bytedance so maybe need to ask them for a profiling data

fzyzcjy2022-09-21T23:44:10.228+00:00 Discord

I also see jank in my app, but given that I am not expert in optimization and the app is not open sourced, my words is not that helpful

fzyzcjy2022-09-21T23:44:41.631Z Google Doc

Thanks! I will add that

gaaclarke2022-09-21T23:46:16.589+00:00 Discord

A couple of months ago I looked into build/layout performance. I tried to squeeze as much as I could out of the framework but ran out of ideas. My understanding from looking into is a lot of jank was shader compilation. The thing that might be holding back layout / build is locality which will be hard to fix. But I didn't see a lot of evidence from what I remember that it is worth the investment =T

Hixie2022-09-21T23:47:38.511Z Google Doc

the way flutter does build and layout is interleaved, but not fine-grained. lots of building happens, then lots of layout, then lots of building, etc. You can run out of time at any time in this process, with a hundred widgets built but not laid out, for example. now the system is in a very inconsistent state.

gaaclarke2022-09-21T23:48:34.765+00:00 Discord

I guess my point is that if you want to propose a solution that fixes long (build + layout) times, we should establish that that is a worthwhile problem to fix backed up with some data.

Hixie2022-09-21T23:48:55.117Z Google Doc

since we haven't done paint yet, what we're painting here is just the last frame. there's no need to send it to the engine, it's already got it.

Hixie2022-09-21T23:51:38.614Z Google Doc

how does it know what to do?

Hixie2022-09-21T23:52:01.162Z Google Doc

what if the preempt happened before the progress indicator got to rebuild?

fzyzcjy2022-09-21T23:54:55.527+00:00 Discord

Thanks I will add a section to design doc. "shader compilation" - seems that @Jsouliang has mentioned above, they expect Impeller to solve the problem. Personally speaking I am not very sure b/c have not checked into impeller deeply. "might be holding back layout / build is locality" - could you please elaborate it a bit - Do you mean memory locality that causes page fault etc? Anyway, any form of build/layout slowness can be fixed by this proposal, no matter the cause. Even if you just heavily compute synchronously inside initState, this proposal can also fix 🙂 "I guess my point is that if you want to propose a solution that fixes long (build + layout) times, we should establish that that is a worthwhile problem to fix backed up with some data." - I agree. I wonder whether this is enough: @dnfield has gived a pointer, "A good canonical case here would be something like https://github.com/flutter/flutter/blob/master/dev/benchmarks/macrobenchmarks/lib/src/list_text_layout.dart. This ends up being janky because layout gets expensive for all that text (on a lower end phone it can easily take 20-30+ms just to layout all the text there, and the ListTile is a little deceptive because Material introduces expense - this is the kind of thing we want to figure out how to break up "automatically")."

gaaclarke2022-09-22T00:01:02.914+00:00 Discord

Oh interesting, I hadn't seen the list text layout benchmark. WRT heavily computing inside initState shouldn't happen on the isolate, we should be giving users the tools to quickly offload work to a background isolate. I think flutter/dart can do better there. With the locality, yea talking about memory locality. When I ran profiles I saw numbers that couldn't be associated with a single widget. I suspect it is the recursive nature of crawling down the widget / render trees which jumps all over in memory. Also Dart doesn't have value types so every single rectangle in layout is an indirection to another location in memory. The locality thing is my pet theory, I can't prove it though 😛

gaaclarke2022-09-22T00:05:07.58+00:00 Discord

also @fzyzcjy you should look into impeller, i haven't run it recently and I don't want to hype it much, but it should make a huge impact. it's almost not worth looking into jank until after it. you can turn it on with a flag.

fzyzcjy2022-09-22T00:06:27.358+00:00 Discord

WRT heavily computing inside initState shouldn't happen on the isolate, we should be giving users the tools to quickly offload work to a background isolate. Sure, that should be a misuse in most of the time 🙂 But indeed, sometimes it is sane to do so. For a simple example, you may have a giant tree in main isolate, and have to modify a lot of its nodes. It is hard (or impossible) to send it to another isolate, modify, and go back, otherwise copying is either too slow or even impossible for some types of objects. Anyway that is just a side remark, and my proposal is not designed to solely solve this special case

fzyzcjy2022-09-22T00:07:02.858+00:00 Discord

With the locality, yea talking about memory locality. When I ran profiles I saw numbers that couldn't be associated with a single widget. I suspect it is the recursive nature of crawling down the widget / render trees which jumps all over in memory. Also Dart doesn't have value types so every single rectangle in layout is an indirection to another location in memory. The locality thing is my pet theory, I can't prove it though 😛 I see. So my design should solve it 😛

fzyzcjy2022-09-22T00:07:49.225+00:00 Discord

IMHO impeller is purely about raster thread? (Correct me if I am wrong!) In other words, even if we have impeller, if our dart code janks, it still janks just like the old days

fzyzcjy2022-09-22T00:09:05.191Z Google Doc

Sure. Then I should insert if timeout then preemptRender into the build() as well, and seems we have no problem.

Btw, no inconsistent state :) I am sending the layer tree in preemptRender, so I just accept the dart state to be completely in middle

gaaclarke2022-09-22T00:09:48.125+00:00 Discord

I can't remember for sure but shader compilation may happen on the platform thread while the raster thread waits for it synchronously.

fzyzcjy2022-09-22T00:10:03.149Z Google Doc

Last frame,  but plus "preemptModifyLayerTree". For the scrolling listview example, we will modify the OffsetLayer.offset a bit (the amount by inertia). Then, users will see the list content shifted a little bit. So users will see list scrolling smoothly at 60fps, even if one build/layout/paint/... cycle takes, say, 10fps.

gaaclarke2022-09-22T00:10:39.453+00:00 Discord

It is hard (or impossible) to send it to another isolate, modify, and go back, Yea a couple months ago I tried to come up with a way to get move semantics for sending data between isolates in constant time. I couldn't find a good proposal that the dart team liked.

fzyzcjy2022-09-22T00:10:42.809Z Google Doc

See examples below, flutter framework dev (e.g. me) write down some code to cover a range of cases. Users can also write some if want very special behavior

fzyzcjy2022-09-22T00:11:20.716Z Google Doc

No problem :) Progress indicator updates the UI also inside preemptModifyLayerTree, and that is why it is smooth 60fps.

jonahwilliams2022-09-22T00:11:24.889+00:00 Discord

Isolate.exit does send most data in constant time

jonahwilliams2022-09-22T00:11:39.499+00:00 Discord

but that doesn't help for long-lived isolates of course, just compute

fzyzcjy2022-09-22T00:12:16.448+00:00 Discord

Yeah, but the problem is I have to send data to that new isolate, which is linear. Anyway, that is just a special corner case, and my proposal does not aim to solve it as the main problem

gaaclarke2022-09-22T00:12:34.331+00:00 Discord

yea, sorry to digress but it's an interesting problem

fzyzcjy2022-09-22T00:12:37.998+00:00 Discord

I see. Btw platform thread is not ui thread IIRC, so ui thread is still ok?

fzyzcjy2022-09-22T00:12:46.044+00:00 Discord

Haha I also feel it interesting

fzyzcjy2022-09-22T00:13:16.034+00:00 Discord

Const time transfering in both side will be quite helpful indeed

gaaclarke2022-09-22T00:15:02.645+00:00 Discord

Yea, sorta, but if you are blocking the platform thread you can be blocking events which feels janky. it really depends on the embedder too what effect it can have. I'm not saying build/layout isn't a potential issue, but from my having looked into this a couple months ago I think shader compilation was by and far the biggest issue which is good the flutter team is making a big investment there

gaaclarke2022-09-22T00:15:22.837+00:00 Discord

i gotta run, good luck @fzyzcjy nice meeting you

fzyzcjy2022-09-22T00:15:33.442+00:00 Discord

I see. Eagerly looking to see impeller be in production as well!

fzyzcjy2022-09-22T00:15:39.95+00:00 Discord

@gaaclarke See you 🙂 Nice meeting you

fzyzcjy2022-09-22T00:17:29.713+00:00 Discord

@Hixie Btw I have replied your questions on google doc (not seeing it just now)

Hixie2022-09-22T00:47:21.780Z Google Doc

This is the part I don't really understand.

Hixie2022-09-22T00:47:48.550Z Google Doc

How does the framework know this is the one that needs painting?

What if it this is the slow part?

fzyzcjy2022-09-22T00:49:30.019Z Google Doc

Users manually write down "PreemptDisplayList(child:CircularProgressIndicator(preempt: false, … other argos …),)"

Then, when framework wants to preempt render an extra frame, it calls PreemptDisplayList.preemptModifyLayerTree.

 What if it this is the slow part?

Then it jank. But IMHO this happens rarely, at least should be ok for progress indicator and scrolling list view - could you please provide some real world case where it will be slow?

fzyzcjy2022-09-22T00:50:08.815Z Google Doc

I have replied to another comment in google doc. Does that help? If not I can elaborate

fzyzcjy2022-09-22T00:50:59.190Z Google Doc

If the animation is really the slow part under that special case, then any single-threaded solution will not work IMHO. (Seems flutter does not want multithread and I agree with that, so we only consider single thread solutions)

fzyzcjy2022-09-22T01:39:25.441+00:00 Discord

Update: The google doc https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit?usp=sharing is updated, most changes are in "touch event handling system" and "comparison"

Nayuta4032022-09-22T02:41:15.941+00:00 Discord

@dnfield Yes, if I understand correctly, this case should not need to be considered separately. Since this solution does not affect the existing flow, it just calls preemptRender to submit a frame of drawing to engine at the end of the 16ms. Then continue back to the existing Layout flow

fzyzcjy2022-09-22T02:42:24.33+00:00 Discord

Yes it is like that

Nayuta4032022-09-22T02:46:45.594+00:00 Discord

@fzyzcjy Yeah, I thought about it later, and I think one of the challenges is. In the case of video, when we go from 15FPS to 30fps, we actually know the content of each frame, so we can calculate the difference between two frames and interpolate them (IIRC some frames are diff messages). But with Flutter, we don't know what it looks like until we paint it on the next frame.

Nayuta4032022-09-22T02:47:15.518+00:00 Discord

For example, in this case, when I navigate from loading to a specific page

Nayuta4032022-09-22T02:47:37.11+00:00 Discord

image

Nayuta4032022-09-22T02:48:07.523+00:00 Discord

video source https://github.com/flutter/flutter/issues/110063#issuecomment-1223774091

Nayuta4032022-09-22T02:50:13.980Z Google Doc
Nayuta4032022-09-22T02:50:15.062Z Google Doc
fzyzcjy2022-09-22T02:52:50.331+00:00 Discord

@Nayuta I have not checked Nvidia's tech in details yet. But I guess, because of latency reasons, it will only "guess" extra frames from previous frame? (Will check this soon)

fzyzcjy2022-09-22T02:54:04.644+00:00 Discord

For the navigation, say the new page takes 3 frame to render and the page shift animation is 10 frame. Then, users will see 60fps smooth page shift, and during frame 0-3 they see new page is purely empty and in frame 4-10 they see new page with content

dnfield2022-09-22T03:10:09.262+00:00 Discord

I think that a solution that requires developers to implement their own render objects to achieve this may be interesting for a package but probably won't end up in the core framework

dnfield2022-09-22T03:10:52.538+00:00 Discord

If we require developers to think more about whether their widget/RO needs this, and how to create a custom one that will do the right thing, it's going to be very difficult for them to use. It might be very useful and a skilled developer might find very clever ways to do it, but it won't be something that will scale very well.

fzyzcjy2022-09-22T03:16:48.074+00:00 Discord

requires developers to implement their own render objects to achieve Well I guess yes and no.

  1. For anything that fit into a subtree, no need for dev to do anything except for inserting PreemptDisplayList widget to the tree
  2. For list view scrolling, we do need to change listview's source code a bit, but flutter devs will not even know that change
fzyzcjy2022-09-22T03:17:48.701+00:00 Discord

Let me think about whether I can do something like "just drop in a widget and it works" for end users 🤔

fzyzcjy2022-09-22T03:22:05.653+00:00 Discord

Do you think the 1. and 2. above are enough to cover most common use cases?

fzyzcjy2022-09-22T03:22:28.469+00:00 Discord

If so then maybe we are ok - we speed up common cases, and for rare edge cases they need to craft something

dnfield2022-09-22T03:26:03.135+00:00 Discord

It's really hard to tell whether something fits into a subtree, particularly in a large project with many people working on it and in situations where large subtrees are getting built (route transitions)

dnfield2022-09-22T03:26:29.586+00:00 Discord

Scrolling isn't usually the big offender here - we have mechanisms to lazily build the contents of what's getting scrolled, and changing the offset on the layer of what's already built is cheap

dnfield2022-09-22T03:27:19.068+00:00 Discord

The problem is more like "I need to build an entire new tree of widgets in 8ms or less and I have 300+ widgets to build to get there and on my low end phone it's taking a long time"

fzyzcjy2022-09-22T03:29:37.627+00:00 Discord

@dnfield

It's really hard to tell whether something fits into a subtree, particularly in a large project with many people working on it and in situations where large subtrees are getting built (route transitions) Route transitions will always not fit into a leaf subtree indeed, but it is in my example 3. Scrolling isn't usually the big offender here - we have mechanisms to lazily build the contents of what's getting scrolled, and changing the offset on the layer of what's already built is cheap @Nayuta 's Keframe deals with scrolling and is popular, and I also see problems in scrolling (both in my realworld app and my experiments), so I guess it may be a problem sometimes The problem is more like "I need to build an entire new tree of widgets in 8ms or less and I have 300+ widgets to build to get there and on my low end phone it's taking a long time" i.e. page transition? (the example 3)

fzyzcjy2022-09-22T03:31:04.502+00:00 Discord

So maybe what I should do is answering: "Devs want to write fancy route transition animations, by themselves and without need to think hard, and at the same time, the new page is heavy and cannot be rendered in one frame"

fzyzcjy2022-09-22T03:31:13.074+00:00 Discord

I will come back when having a solution 🙂

dnfield2022-09-22T03:32:08.139+00:00 Discord

I think it's a little more like "devs want to inflate a large widget tree and be confident that if it doesn't fit into frame budget it will get broken up into pieces that make sense"

dnfield2022-09-22T03:32:34.404+00:00 Discord

(And they can't spend hours altering the logic controlling that every time they refactor or add widgets)

fzyzcjy2022-09-22T03:33:14.667+00:00 Discord

"devs want to inflate a large widget tree and be confident that if it doesn't fit into frame budget it will get broken up into pieces that make sense" I see. That is exactly what the proposal aims to solve. (And they can't spend hours altering the logic controlling that every time they refactor or add widgets) Totally agree 🙂 I should make the dev-facing API easier

fzyzcjy2022-09-22T03:35:45.629+00:00 Discord

@dnfield Well, maybe I forget to explain: Dev never need to do anything special for most of their app. The only extra thing is the part for 60fps smooth animation. Only if they want something to be very smooth, and it is not a leaf subtree (e.g. loading indicator) and not a scrolling listview and not a already-provided-in-framework page transition, they will have to write some extra code (in the current proposal).

dnfield2022-09-22T03:38:00.388+00:00 Discord

It might help to have some example application showing the improvements

fzyzcjy2022-09-22T03:38:09.454+00:00 Discord

Would you mind giving a realworld example where userrs, using my currently proposed api, have to write a lot of extra code? such that I will have an example in mind to optimize

fzyzcjy2022-09-22T03:38:13.875+00:00 Discord

Sure, I should do that. Probably create an app w/ page transition etc

fzyzcjy2022-09-22T05:50:00.678+00:00 Discord

Quick update: I am now experimenting multiple subtrees (i.e. a forest). Flutter seems to allow so (e.g. examples/api/lib/widgets/framework/build_owner.0.dart example). If this works, we may have a natural API, i.e. only insert a few extra Widgets to dev's widget tree and that's all, and @dnfield's worry will be solved.

xanahopper2022-09-22T06:27:33.378+00:00 Discord

for some reason I think maybe build in one frame, layout and paint in next frame is acceptable

fzyzcjy2022-09-22T06:34:40.226+00:00 Discord

Agree, if that is implementable looks like also an idea, as long as the build fits in 16ms

Nayuta4032022-09-22T13:01:35.648Z Google Doc

I would like to add:  this solution has less overhead than keframe. When Keframe replaces placeholder with real widget, it is driven by vsync signal to execute drawFrame process and submit to engine, so it will execute the complete build/layout/paint etc process. But build/layout/paint other than the actual widget is not necessary. In the Preempt Layout scheme, the UI thread just voluntarily submits a frame to the Engine after 16ms of detection, and then returns to the normal rendering flow without much additional overhead.

fzyzcjy2022-09-22T13:18:08.710Z Google Doc

Thanks, I added it

fzyzcjy2022-09-22T14:45:23.226+00:00 Discord

Update: minimalist API (one widget for everything) + a prototype about framework layer.

Now, developers will only need to insert PreemptBuilder(builder: (context, child) => whatever_you_like, child: also_free_to_choose) widget, and that's all. Arbitrary builder, arbitrary child subtree, and smooth 60fps will be there for the builder. The google doc is updated to discuss this - mainly in (1) "usage examples" (2) "From preemptModifyLayerTree to PreemptBuilder" in "detailed design".

fzyzcjy2022-09-22T14:45:25.792+00:00 Discord

Update: minimalist API (one widget for everything) + a prototype about framework layer.

Now, developers will only need to insert PreemptBuilder(builder: (context, child) => whatever_you_like, child: also_free_to_choose) widget, and that's all. Arbitrary builder, arbitrary child subtree, and smooth 60fps will be there for the builder. The google doc is updated to discuss this - mainly in (1) "usage examples" (2) "From preemptModifyLayerTree to PreemptBuilder" in "detailed design".

fzyzcjy2022-09-22T14:45:39.214+00:00 Discord

@dnfield I guess your worry is now solved? 😄

fzyzcjy2022-09-22T14:46:52Z GitHub

Update: minimalist API (one widget for everything) + a prototype about framework layer.

Now, developers will only need to insert PreemptBuilder(builder: (context, child) => whatever_you_like, child: also_free_to_choose) widget, and that's all. Arbitrary builder, arbitrary child subtree, and smooth 60fps will be there for the builder. The google doc is updated to discuss this - mainly in (1) "usage examples" (2) "From preemptModifyLayerTree to PreemptBuilder" in "detailed design".

Code prototype:

Details
// ignore_for_file: avoid_print, prefer_const_constructors, invalid_use_of_protected_member

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';

final secondTreePack = SecondTreePack();

// since prototype, only one [RenderAdapterInSecondTree], so do like this
final mainSubTreeLayerHandle = LayerHandle(OffsetLayer());

void main() {
debugPrintBeginFrameBanner = debugPrintEndFrameBanner = true;
secondTreePack; // touch it
mainSubTreeLayerHandle; // touch it
runApp(const MyApp());
}

class MyApp extends StatefulWidget {
const MyApp({super.key});


State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
var buildCount = 0;


Widget build(BuildContext context) {
buildCount++;
print('$runtimeType.build ($buildCount)');

if (buildCount < 5) {
Future.delayed(Duration(seconds: 1), () {
print('$runtimeType.setState after a second');
setState(() {});
});
}

return MaterialApp(
home: Scaffold(
body: _buildBody(),
),
);
}

Widget _buildBody() {
return Column(
children: [
Text('A$buildCount', style: TextStyle(fontSize: 30)),
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.orange[(buildCount % 8 + 1) * 100]!,
width: 10,
),
),
width: 300,
height: 300,
// hack: [AdapterInMainTreeWidget] does not respect "offset" in paint
// now, so we add a RepaintBoundary to let offset==0
// hack: [AdapterInMainTreeWidget] does not respect "offset" in paint
// now, so we add a RepaintBoundary to let offset==0
child: RepaintBoundary(
child: AdapterInMainTreeWidget(
parentBuildCount: buildCount,
// child: DrawCircleWidget(parentBuildCount: buildCount),
child: Center(
child: Container(
width: 100,
height: 100,
color: Colors.pink[(buildCount % 8 + 1) * 100],
),
),
),
),
),
Text('B$buildCount', style: TextStyle(fontSize: 30)),
WindowRenderWhenLayoutWidget(parentBuildCount: buildCount),
Text('C$buildCount', style: TextStyle(fontSize: 30)),
],
);
}
}

class WindowRenderWhenLayoutWidget extends SingleChildRenderObjectWidget {
final int parentBuildCount;

const WindowRenderWhenLayoutWidget({
super.key,
required this.parentBuildCount,
super.child,
});


WindowRenderWhenLayoutRender createRenderObject(BuildContext context) =>
WindowRenderWhenLayoutRender(
parentBuildCount: parentBuildCount,
);


void updateRenderObject(
BuildContext context, WindowRenderWhenLayoutRender renderObject) {
renderObject.parentBuildCount = parentBuildCount;
}
}

class WindowRenderWhenLayoutRender extends RenderProxyBox {
WindowRenderWhenLayoutRender({
required int parentBuildCount,
RenderBox? child,
}) : _parentBuildCount = parentBuildCount,
super(child);

int get parentBuildCount => _parentBuildCount;
int _parentBuildCount;

set parentBuildCount(int value) {
if (_parentBuildCount == value) return;
_parentBuildCount = value;
print('$runtimeType markNeedsLayout because parentBuildCount changes');
markNeedsLayout();
}


void performLayout() {
// unconditionally call this, as an experiment
pseudoPreemptRender();

super.performLayout();
}

void pseudoPreemptRender() {
print('$runtimeType pseudoPreemptRender start');

// ref: https://github.com/fzyzcjy/yplusplus/issues/5780#issuecomment-1254562485
// ref: RenderView.compositeFrame

final builder = SceneBuilder();

// final recorder = PictureRecorder();
// final canvas = Canvas(recorder);
// final rect = Rect.fromLTWH(0, 0, 500, 500);
// canvas.drawRect(Rect.fromLTWH(100, 100, 50, 50.0 * parentBuildCount),
// Paint()..color = Colors.green);
// final pictureLayer = PictureLayer(rect);
// pictureLayer.picture = recorder.endRecording();
// final rootLayer = OffsetLayer();
// rootLayer.append(pictureLayer);
// final scene = rootLayer.buildScene(builder);

final binding = WidgetsFlutterBinding.ensureInitialized();

preemptModifyLayerTree(binding);

// why this layer? from RenderView.compositeFrame
final scene = binding.renderView.layer!.buildScene(builder);

print('call window.render');
window.render(scene);

scene.dispose();

print('$runtimeType pseudoPreemptRender end');
}

void preemptModifyLayerTree(WidgetsBinding binding) {
// hack, just want to prove we can change something (preemptModifyLayerTree)
// inside the preemptRender
final rootLayer = binding.renderView.layer! as TransformLayer;
rootLayer.transform =
rootLayer.transform!.multiplied(Matrix4.translationValues(0, 50, 0));
print('preemptModifyLayerTree rootLayer=$rootLayer (after)');

refreshSecondTree();
}

void refreshSecondTree() {
print('$runtimeType refreshSecondTree start');
secondTreePack.innerStatefulBuilderSetState(() {});

// NOTE reference: WidgetsBinding.drawFrame & RendererBinding.drawFrame
// https://github.com/fzyzcjy/yplusplus/issues/5778#issuecomment-1254490708
secondTreePack.buildOwner.buildScope(secondTreePack.element);
secondTreePack.pipelineOwner.flushLayout();
secondTreePack.pipelineOwner.flushCompositingBits();
secondTreePack.pipelineOwner.flushPaint();
// renderView.compositeFrame(); // this sends the bits to the GPU
// pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
secondTreePack.buildOwner.finalizeTree();

print('$runtimeType refreshSecondTree end');
}
}

class AdapterInMainTreeWidget extends SingleChildRenderObjectWidget {
final int parentBuildCount;

const AdapterInMainTreeWidget({
super.key,
required this.parentBuildCount,
super.child,
});


RenderAdapterInMainTree createRenderObject(BuildContext context) =>
RenderAdapterInMainTree(parentBuildCount: parentBuildCount);


void updateRenderObject(
BuildContext context, RenderAdapterInMainTree renderObject) {
renderObject.parentBuildCount = parentBuildCount;
}
}

class RenderAdapterInMainTree extends RenderBox
with RenderObjectWithChildMixin<RenderBox> {
RenderAdapterInMainTree({
required int parentBuildCount,
// RenderBox? child,
}) : _parentBuildCount = parentBuildCount;

// super(child);

int get parentBuildCount => _parentBuildCount;
int _parentBuildCount;

set parentBuildCount(int value) {
if (_parentBuildCount == value) return;
_parentBuildCount = value;
print('$runtimeType markNeedsLayout because parentBuildCount changes');
markNeedsLayout();
}

// should not be singleton, but we are prototyping so only one such guy
static RenderAdapterInMainTree? instance;


void attach(covariant PipelineOwner owner) {
super.attach(owner);
assert(instance == null);
instance = this;
}


void detach() {
assert(instance == this);
instance == null;
super.detach();
}


void layout(Constraints constraints, {bool parentUsesSize = false}) {
print('$runtimeType.layout called');
super.layout(constraints, parentUsesSize: parentUsesSize);
}


void performLayout() {
print('$runtimeType.performLayout start');

// NOTE
secondTreePack.rootView.configuration =
SecondTreeRootViewConfiguration(size: constraints.biggest);

print('$runtimeType.performLayout child.layout start');
child!.layout(constraints);
print('$runtimeType.performLayout child.layout end');

size = constraints.biggest;
}

// TODO correct?

bool get alwaysNeedsCompositing => true;

// static final staticPseudoRootLayerHandle = () {
// final recorder = PictureRecorder();
// final canvas = Canvas(recorder);
// final rect = Rect.fromLTWH(0, 0, 200, 200);
// canvas.drawRect(
// Rect.fromLTWH(0, 0, 50, 100), Paint()..color = Colors.green);
// final pictureLayer = PictureLayer(rect);
// pictureLayer.picture = recorder.endRecording();
// final wrapperLayer = OffsetLayer();
// wrapperLayer.append(pictureLayer);
//
// final pseudoRootLayer = TransformLayer(transform: Matrix4.identity());
// pseudoRootLayer.append(wrapperLayer);
//
// pseudoRootLayer.attach(secondTreePack.rootView);
//
// return LayerHandle(pseudoRootLayer);
// }();


void paint(PaintingContext context, Offset offset) {
assert(offset == Offset.zero,
'$runtimeType prototype has not deal with offset yet');

print('$runtimeType.paint called');

// super.paint(context, offset);
// return;

// context.canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50.0 * parentBuildCount),
// Paint()..color = Colors.green);
// return;

// context.pushLayer(
// OpacityLayer(alpha: 100),
// (context, offset) {
// context.canvas.drawRect(
// Rect.fromLTWH(0, 0, 50, 50.0 * parentBuildCount),
// Paint()..color = Colors.green);
// },
// offset,
// );
// return;

// context.addLayer(PerformanceOverlayLayer(
// overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
// optionsMask: 1 <<
// PerformanceOverlayOption.displayRasterizerStatistics.index |
// 1 << PerformanceOverlayOption.visualizeRasterizerStatistics.index |
// 1 << PerformanceOverlayOption.displayEngineStatistics.index |
// 1 << PerformanceOverlayOption.visualizeEngineStatistics.index,
// rasterizerThreshold: 0,
// checkerboardRasterCacheImages: true,
// checkerboardOffscreenLayers: true,
// ));
// return;

// {
// final recorder = PictureRecorder();
// final canvas = Canvas(recorder);
// final rect = Rect.fromLTWH(0, 0, 200, 200);
// canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50.0 * parentBuildCount),
// Paint()..color = Colors.green);
// final pictureLayer = PictureLayer(rect);
// pictureLayer.picture = recorder.endRecording();
// final wrapperLayer = OffsetLayer();
// wrapperLayer.append(pictureLayer);
//
// // NOTE addLayer vs pushLayer
// context.addLayer(wrapperLayer);
//
// print('pictureLayer.attached=${pictureLayer.attached} '
// 'wrapperLayer.attached=${wrapperLayer.attached}');
//
// return;
// }

// {
// if (staticPseudoRootLayerHandle.layer!.attached) {
// print('pseudoRootLayer.detach');
// staticPseudoRootLayerHandle.layer!.detach();
// }
//
// print('before addLayer staticPseudoRootLayer=${staticPseudoRootLayerHandle.layer!.toStringDeep()}');
//
// context.addLayer(staticPseudoRootLayerHandle.layer!);
//
// print('after addLayer staticPseudoRootLayer=${staticPseudoRootLayerHandle.layer!.toStringDeep()}');
//
// return;
// }

// ref: RenderOpacity

// TODO this makes "second tree root layer" be *removed* from its original
// parent. shall we move it back later? o/w can be slow!
final secondTreeRootLayer = secondTreePack.rootView.layer!;

// print(
// 'just start secondTreeRootLayer=${secondTreeRootLayer.toStringDeep()}');

// HACK!!!
if (secondTreeRootLayer.attached) {
print('$runtimeType.paint detach the secondTreeRootLayer');
// TODO attach again later?
secondTreeRootLayer.detach();
}

// print(
// 'before addLayer secondTreeRootLayer=${secondTreeRootLayer.toStringDeep()}');

print('$runtimeType.paint addLayer');
// NOTE addLayer, not pushLayer!!!
context.addLayer(secondTreeRootLayer);
// context.pushLayer(secondTreeRootLayer, (context, offset) {}, offset);

print('secondTreeRootLayer.attached=${secondTreeRootLayer.attached}');
print(
'after addLayer secondTreeRootLayer=${secondTreeRootLayer.toStringDeep()}');

// ================== paint those child in main tree ===================

// NOTE do *not* have any relation w/ self's PaintingContext, as we will not paint there
{
// ref: [PaintingContext.pushLayer]
if (mainSubTreeLayerHandle.layer!.hasChildren) {
mainSubTreeLayerHandle.layer!.removeAllChildren();
}
final childContext = PaintingContext(
mainSubTreeLayerHandle.layer!, context.estimatedBounds);
child!.paint(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}

// =====================================================================
}

// TODO handle layout!
}

class AdapterInSecondTreeWidget extends SingleChildRenderObjectWidget {
final int parentBuildCount;

const AdapterInSecondTreeWidget({
super.key,
required this.parentBuildCount,
super.child,
});


RenderAdapterInSecondTree createRenderObject(BuildContext context) =>
RenderAdapterInSecondTree(parentBuildCount: parentBuildCount);


void updateRenderObject(
BuildContext context, RenderAdapterInSecondTree renderObject) {
renderObject.parentBuildCount = parentBuildCount;
}
}

class RenderAdapterInSecondTree extends RenderBox {
RenderAdapterInSecondTree({
required int parentBuildCount,
}) : _parentBuildCount = parentBuildCount;

int get parentBuildCount => _parentBuildCount;
int _parentBuildCount;

set parentBuildCount(int value) {
if (_parentBuildCount == value) return;
_parentBuildCount = value;
print('$runtimeType markNeedsLayout because parentBuildCount changes');
markNeedsLayout();
}

// should not be singleton, but we are prototyping so only one such guy
static RenderAdapterInSecondTree? instance;


void attach(covariant PipelineOwner owner) {
super.attach(owner);
assert(instance == null);
instance = this;
}


void detach() {
assert(instance == this);
instance == null;
super.detach();
}


void layout(Constraints constraints, {bool parentUsesSize = false}) {
print('$runtimeType.layout called');
super.layout(constraints, parentUsesSize: parentUsesSize);
}


void performLayout() {
print('$runtimeType.performLayout called');
size = constraints.biggest;
}

// TODO correct?

bool get alwaysNeedsCompositing => true;


void paint(PaintingContext context, Offset offset) {
print('$runtimeType paint');
context.addLayer(mainSubTreeLayerHandle.layer!);
}
}

class SecondTreePack {
late final PipelineOwner pipelineOwner;
late final SecondTreeRootView rootView;
late final BuildOwner buildOwner;
late final RenderObjectToWidgetElement<RenderBox> element;

var innerStatefulBuilderBuildCount = 0;
late StateSetter innerStatefulBuilderSetState;

SecondTreePack() {
pipelineOwner = PipelineOwner();
rootView = pipelineOwner.rootNode = SecondTreeRootView(
configuration: SecondTreeRootViewConfiguration(size: Size.zero),
);
buildOwner = BuildOwner(
focusManager: FocusManager(),
onBuildScheduled: () =>
print('second tree BuildOwner.onBuildScheduled called'),
);

rootView.prepareInitialFrame();

final secondTreeWidget = StatefulBuilder(builder: (_, setState) {
print(
'secondTreeWidget(StatefulBuilder).builder called ($innerStatefulBuilderBuildCount)');

innerStatefulBuilderSetState = setState;
innerStatefulBuilderBuildCount++;

return Container(
width: 50 * innerStatefulBuilderBuildCount.toDouble(),
height: 100,
color: Colors.blue[(innerStatefulBuilderBuildCount * 100) % 800 + 100],
child: AdapterInSecondTreeWidget(
parentBuildCount: innerStatefulBuilderBuildCount,
),
);
});

element = RenderObjectToWidgetAdapter<RenderBox>(
container: rootView,
debugShortDescription: '[root]',
child: secondTreeWidget,
).attachToRenderTree(buildOwner);
}
}

// ref: [ViewConfiguration]
class SecondTreeRootViewConfiguration {
const SecondTreeRootViewConfiguration({
required this.size,
});

final Size size;


bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ViewConfiguration && other.size == size;
}


int get hashCode => size.hashCode;


String toString() => '$size';
}

class SecondTreeRootView extends RenderObject
with RenderObjectWithChildMixin<RenderBox> {
SecondTreeRootView({
RenderBox? child,
required SecondTreeRootViewConfiguration configuration,
}) : _configuration = configuration {
this.child = child;
}

// NOTE ref [RenderView.size]
/// The current layout size of the view.
Size get size => _size;
Size _size = Size.zero;

// NOTE ref [RenderView.configuration] which has size and some other things
/// The constraints used for the root layout.
SecondTreeRootViewConfiguration get configuration => _configuration;
SecondTreeRootViewConfiguration _configuration;

set configuration(SecondTreeRootViewConfiguration value) {
if (configuration == value) {
return;
}
print(
'$runtimeType set configuration(i.e. size) $_configuration -> $value');
_configuration = value;
markNeedsLayout();
}


void performLayout() {
print(
'$runtimeType performLayout configuration.size=${configuration.size}');

_size = configuration.size;

assert(child != null);
child!.layout(BoxConstraints.tight(_size));
}

// ref RenderView

void paint(PaintingContext context, Offset offset) {
// NOTE we have to temporarily remove debugActiveLayout
// b/c [SecondTreeRootView.paint] is called inside [preemptRender]
// which is inside main tree's build/layout.
// thus, if not set it to null we will see error
// https://github.com/fzyzcjy/yplusplus/issues/5783#issuecomment-1254974511
// In short, this is b/c [debugActiveLayout] is global variable instead
// of per-tree variable
final oldDebugActiveLayout = RenderObject.debugActiveLayout;
RenderObject.debugActiveLayout = null;
try {
print('$runtimeType paint child start');
context.paintChild(child!, offset);
print('$runtimeType paint child end');
} finally {
RenderObject.debugActiveLayout = oldDebugActiveLayout;
}
}


void debugAssertDoesMeetConstraints() => true;

void prepareInitialFrame() {
// ref: RenderView
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}

// ref: RenderView
TransformLayer _updateMatricesAndCreateNewRootLayer() {
final rootLayer = TransformLayer(transform: Matrix4.identity());
rootLayer.attach(this);
return rootLayer;
}

// ref: RenderView

bool get isRepaintBoundary => true;

// ref: RenderView

Rect get paintBounds => Offset.zero & size;

// ref: RenderView

void performResize() {
assert(false);
}

// hack: just give non-sense value, this is prototype

Rect get semanticBounds => paintBounds;
}

class DrawCircleWidget extends LeafRenderObjectWidget {
final int parentBuildCount;

const DrawCircleWidget({
super.key,
required this.parentBuildCount,
});


RenderDrawCircle createRenderObject(BuildContext context) => RenderDrawCircle(
parentBuildCount: parentBuildCount,
);


void updateRenderObject(BuildContext context, RenderDrawCircle renderObject) {
renderObject.parentBuildCount = parentBuildCount;
}
}

class RenderDrawCircle extends RenderProxyBox {
RenderDrawCircle({
required int parentBuildCount,
RenderBox? child,
}) : _parentBuildCount = parentBuildCount,
super(child);

int get parentBuildCount => _parentBuildCount;
int _parentBuildCount;

set parentBuildCount(int value) {
if (_parentBuildCount == value) return;
_parentBuildCount = value;
print('$runtimeType markNeedsLayout because parentBuildCount changes');
markNeedsLayout();
}


void layout(Constraints constraints, {bool parentUsesSize = false}) {
print('$runtimeType performLayout');
super.layout(constraints, parentUsesSize: parentUsesSize);
}


void performLayout() {
size = constraints.biggest;
}


void paint(PaintingContext context, Offset offset) {
print('$runtimeType paint');
context.canvas
.drawCircle(Offset(50, 50), 100, Paint()..color = Colors.cyan);
}
}

Next time I may only update progress in Discord, since there are already >hundred comments there - seems everyone is there instead of in github :)

dnfield2022-09-22T14:51:17.626+00:00 Discord

It'd help to see usage

fzyzcjy2022-09-22T14:52:09.857+00:00 Discord

In the example section of the google doc, updated

fzyzcjy2022-09-22T14:53:18.359+00:00 Discord
YourParentWidgets(
child: PreemptBuilder(
builder: (_, child) => YourFancyAnimationWhichNeeds60FPS(child: child)),
child: YourNewPageAndSoOn(),
)
)
fzyzcjy2022-09-22T14:53:24.613+00:00 Discord

For page transition

fzyzcjy2022-09-22T14:53:28.901+00:00 Discord

@dnfield

fzyzcjy2022-09-22T14:53:55.31+00:00 Discord

All those YourSomthing are plain old widget trees, no special

fzyzcjy2022-09-22T14:56:52.049+00:00 Discord

@dnfield Curious is it OK to you? Btw I am going to sleep in a minute and will reply 8hr later for later comments

dnfield2022-09-22T15:11:02.225+00:00 Discord

A demo would help

Callum2022-09-22T15:44:26.104+00:00 Discord

I would rather think of an API like this would be useful

SlowSubtree(
placeholder: Center(child: CupertinoActivityIndicator())
child: MyBigScrollingPage()
)

And that child could be built over multiple frames. Main use case would be to not frame-drop during page push animation when the new page is complex. Seems like based on build_owner.0.dart example it can be possible to build arbitrary subtrees? But would need to modify the build owner in order to yield back and show the placeholder.

gaaclarke2022-09-22T16:34:07.815+00:00 Discord

If someone has stackoverflow comment abilities can you please comment on this answer: https://stackoverflow.com/a/64167746/20063373 It assumes that the builder for StreamBuilder will get called 1-to-1 for the Stream events. So the code will fail depending on the timing of the Stream. It's kind of a nasty gotcha.

gaaclarke2022-09-22T16:51:12.545+00:00 Discord

Filed an issue to propose the ability to make StreamBuilder lossless since the fail case is too dire imo

BetoMan02022-09-22T17:29:23.496+00:00 Discord

could you link it please?

Hixie2022-09-22T18:10:23.481Z Google Doc

I think if we can find a way to do multithreading nobody is against it a priori.

If I may, it sounds like your proposal boils down to "provide developers with a way to mark areas of the tree that should be updated first (doing build, layout, paint, and semantics), then, if the time runs out during a frame, send the updated layer tree that only contains those parts and not the others, then continue doing the frame". Is that right?

If so, I think the main problem with this approach is that we'd have to rerun the rarely-mentioned (because it's so cheap) "animation" step that happens before build, so that the parts of the tree that need animating early can be updated with the new time stamp, but in the current model, that requires winding down the stack frame because between the "animation" and "build" phases we run microtasks.

I also worry that it would lead to some strange effects like how to determine which parts of the tree to update and which to not. For example, suppose you have three parts to your page. Part A is marked as needing fast updates, and parts B and C other two are expensive animations.

We start frame 1, we update all the AnimationControllers, we build/layout/paint/layer/semantics the first (A), then we start on B, and we do no build and layout of B. Then we interrupt the work to send the tree to the engine with A's update. We do a new animation phase to update all the AnimationControllers again, we build/layout/paint/layer/semantics the first (A), then we resume the work on the original frame. Now we build/layout C. Finally build and layout are done so we paint B and C and do the layer tree and render it. Unfortunately, because B and C were painted after different animation phases, they'll be out of sync. It'll look like the two animations are running at a low frame rate and out of sync with each other, which IMHO is worse than today (where they're just at a low frame rate).

That's assuming we can figure out how to do the animation phase at all.

gaaclarke2022-09-22T19:10:30.254+00:00 Discord

oops, sorry https://github.com/flutter/flutter/issues/112197

Hixie2022-09-22T22:26:49Z GitHub

would be good to have a test for this.

jonahwilliams2022-09-22T22:33:18Z GitHub

Ahh my bad, I should have asked for a test for this one

fzyzcjy2022-09-22T22:53:54.057+00:00 Discord

It is still a prototype :/

fzyzcjy2022-09-22T22:54:22.401+00:00 Discord

I will have a more complete prototype today. Just want to confirm whether the user-facing API looks good to you or not (b/c it is an API, not an implementation detail)

fzyzcjy2022-09-22T22:56:01.128+00:00 Discord

If the API is still bad I need to think another

fzyzcjy2022-09-22T22:56:26.936+00:00 Discord

@Callum That would be great, and looks like what the modify-the-build and modify-the-layout will do (see compaison between them and my proposal, in the google doc https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit#)

fzyzcjy2022-09-22T22:57:24.608+00:00 Discord

However, the main problem is that, seems not to exist efficient implementable solutions for those ideas (some by googlers, some by bytedancers, some by me). They have either performance problems, or logical problems IIRC. I am also looking forward to see progress on those methods!

fzyzcjy2022-09-22T22:59:25.237+00:00 Discord

Done (under @ch271828n account, also me)

fzyzcjy2022-09-22T23:07:21.894Z Google Doc

boils down to ... Is that right?

Quite similar to my existing proposal, if it is implementable, sure.

we'd have to rerun the rarely-mentioned (because it's so cheap) "animation" step ... because between the "animation" and "build" phases we run microtasks.

Indeed I am also quite curious why it was design like that, instead of putting animation into build/layout phase. Anyway, if using the PreemptBuilder dev-facing API, dev just use a builder callback (like what we have done a million times) to drive animation, which is flexible.

Unfortunately, because B and C were painted after different animation phases, they'll be out of sync.

Sorry I cannot get it. In my proposal, they always see the same vsync signal. Indeed, using the mental model (a section in this design doc), every code should just see the plain old janky environment, except for a small portion of the code (the one inside PreemptBuilder) which knows extra info.

That's assuming we can figure out how to do the animation phase at all.

Currently I plan not to do the animation phase indeed, and just use the builder callback.

fzyzcjy2022-09-22T23:15:15Z GitHub

@Hixie I have tried but failed, that is why this one has no tests though others have :/ It is so deep that it is hard to construct a test, without relaxing the code visibility of the widget/renderobjects.

(I forget to ask for a test-preempt as well)

fzyzcjy2022-09-22T23:26:20.434Z Google Doc

IIRC, Flutter officially supports us to using builder pattern for animations. For example, AnimatedBuilder is indeed implemented as setState per Listenable update, very similar to how we treat PreemptBuilder - build (setState?) per 60fps frame.

dnfield2022-09-22T23:45:23.3+00:00 Discord

It's still not quite clear to me how it will work or if it's solving the problem that I'm particularly interested in (building big widget trees on slow devices)

fzyzcjy2022-09-22T23:48:12.796+00:00 Discord

if it's solving the problem that I'm particularly interested in (building big widget trees on slow devices) At the API level I guess yes?

fzyzcjy2022-09-22T23:48:17.904+00:00 Discord
YourParentWidgets(
child: PreemptBuilder(
builder: (_, child) => YourFancyAnimationWhichNeeds60FPS(child: child)),
child: ABigWidgetTreeThatYouWant(),
)
)
fzyzcjy2022-09-22T23:49:25.394+00:00 Discord

how it will work The code is already working (except that I have not modify engine, so it just omits later window.render to screen...), anyway I will prototype more today

fzyzcjy2022-09-23T13:28:16Z GitHub

Prototype: Enter-new-heavy-page, smoothly even though it takes 0.5s to build/layout

Also posted in discord and google doc

Defects in the prototype compared to the future full implementation

  • The page is so heavy that even paint without the time of build and layout causes a visible jank with PreemptBuilder; in real world should not be that slow (since in real world build/layout does not take 30 frames)
  • Extra frame is driven by simple DateTime.now (instead of vsync), so it is not at its best performance
  • Prototype code has not been fully clean up yet

Code

https://github.com/fzyzcjy/flutter/tree/experiment-forest and https://github.com/fzyzcjy/engine/tree/experiment-smooth

Downloadable app

app-profile.apk.zip

Video

Firstly the slow (plain old) case, then the fast (using PreemptBuilder) case. The grey circle appears when I touch the screen (by android system recorder).

https://user-images.githubusercontent.com/5236035/191970843-a9c82a38-1276-4024-8a1b-c102c9b8e22f.mp4

fzyzcjy2022-09-23T13:28:49.53+00:00 Discord

Prototype: Enter-new-heavy-page, smoothly even though it takes 0.5s to build/layout

Defects in the prototype compared to the future full implementation

  • The page is so heavy that even paint without the time of build and layout causes a visible jank with PreemptBuilder; in real world should not be that slow (since in real world build/layout does not take 30 frames)
  • Extra frame is driven by simple DateTime.now (instead of vsync), so it is not at its best performance
  • Prototype code has not been fully clean up yet

Code

github.com/fzyzcjy/flutter/tree/experiment-forest and github.com/fzyzcjy/engine/tree/experiment-smooth (deliberately removed https o/w discord pop up big cards)

fzyzcjy2022-09-23T13:29:26.821+00:00 Discord

apk:

fzyzcjy2022-09-23T13:29:53.764+00:00 Discord

https://github.com/flutter/flutter/issues/101227#issuecomment-1256212759 has an attachment (the apk)

fzyzcjy2022-09-23T13:29:56.486+00:00 Discord

video:

fzyzcjy2022-09-23T13:30:09.083+00:00 Discord
fzyzcjy2022-09-23T13:30:14.043+00:00 Discord

Firstly the slow (plain old) case, then the fast (using PreemptBuilder) case. The grey circle appears when I touch the screen (by android system recorder).

fzyzcjy2022-09-23T13:31:35.653+00:00 Discord

I made the prototype 😄

fzyzcjy2022-09-23T13:43:47.615+00:00 Discord

/cc @Hixie @Jonah Williams @dnfield @gaaclarke @XanaHopper @Nayuta @Jsouliang @SecondFlight who had comments about the design doc (hope I remembered everyone)

Piero5122022-09-23T14:15:22.85+00:00 Discord

I noticed that we're calling it pre-emptive rendering

Piero5122022-09-23T14:16:01.664+00:00 Discord

but since Flutter is a reactive framework

Piero5122022-09-23T14:16:10.048+00:00 Discord

https://reactjs.org/blog/2022/03/29/react-v18.html#gradually-adopting-concurrent-features this is what react is doing

fzyzcjy2022-09-23T14:17:14.136+00:00 Discord

@Piero512 Not sure which part in the link are you refer to - do you mean Fiber?

fzyzcjy2022-09-23T14:17:30.713+00:00 Discord

Indeed Fiber is the very beginning: https://github.com/flutter/flutter/issues/101227

fzyzcjy2022-09-23T14:17:44.5+00:00 Discord

But my current solution is indeed quite different from fiber (indeed little similarity)

HrX2022-09-23T14:18:43.954+00:00 Discord

it seems to break the specified transition duration, is it expected?

fzyzcjy2022-09-23T14:18:54.457+00:00 Discord

Break what?

HrX2022-09-23T14:18:59.234+00:00 Discord

like, if a transition was meant to be (say) 400ms then that constraint is not respected anymore

fzyzcjy2022-09-23T14:19:07.48+00:00 Discord

Should be 400ms

fzyzcjy2022-09-23T14:19:10.239+00:00 Discord

If not, it is a bug

fzyzcjy2022-09-23T14:19:23.602+00:00 Discord

This is prototype indeed, so please bear some bugs 🙂

HrX2022-09-23T14:19:28.34+00:00 Discord

hm hm i see

HrX2022-09-23T14:19:41.139+00:00 Discord

was asking out of curiosity indeed, cuz was wondering if it was expected or no

fzyzcjy2022-09-23T14:19:54.813+00:00 Discord

I see 🙂

fzyzcjy2022-09-23T14:20:23.034+00:00 Discord

Your app should be exactly like what you expect, except that you have extra smooth frames, when using this PreemptBuilder

gaaclarke2022-09-23T16:28:26.323+00:00 Discord

is that dilating the time of the animation? What is supposed to be the duration of that transition?

fzyzcjy2022-09-23T23:05:44.564+00:00 Discord

It looks like a bug in my prototype. I use DateTime.now() everywhere (since this is a prototype), so it is weird that it dilates so much. It should be 0.3s.

fzyzcjy2022-09-23T23:06:22.945+00:00 Discord

I will debug it soon

fzyzcjy2022-09-24T02:58:20.581+00:00 Discord

Btw, I realized that, the video is only <30FPS (even if recording non-flutter apps), so cannot demonstrate the real case... Will change a software.

Update (2022.9.24 22:00 @ UTC+8): Given that you guys seem to be on weekend, I will not post details to avoid disturbing. But if you are interested in my today (and tomorrow)'s progress, all my progress can be seen in realtime in the github branches posted above. Have a good weekend! 🙂

fzyzcjy2022-09-25T01:48:52.641+00:00 Discord

Brief visual update: It runs at ~60fps, while widget build/layout needs ~500ms

Video description: (1) The slow (plain-old) case is repeated twice (2) Then the fast (using PreemptBuilder) case is done twice (3) Lastly a debug animation is shown (to be explained below).

How to verify it is 60fps: I personally use ffmpeg -i $VIDEO -vsync 0 -frame_pts true -vf drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf:fontsize=80:text='%{pts}':fontcolor=white@0.8:x=7:y=7 ~/temp/video_frames/output_%04d.jpg to extract every frame of the video.

P.S. The last section in the video (debug animation) is used to verify the file transfer. If that part is seen janky, then it is probably a problem when transferring the video file etc, since that should definitely be 60FPS.

As usual, the code is at the GitHub branch mentioned above.

fzyzcjy2022-09-25T01:50:08Z GitHub

Brief visual update: It runs at ~60fps, while widget build/layout needs ~500ms

X-Posted: https://discord.com/channels/608014603317936148/608021234516754444/1023410732336939129

Video description: (1) The slow (plain-old) case is repeated twice (2) Then the fast (using PreemptBuilder) case is done twice (3) Lastly a debug animation is shown (to be explained below).

How to verify it is 60fps: I personally use ffmpeg -i $VIDEO -vsync 0 -frame_pts true -vf drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf:fontsize=80:text='%{pts}':fontcolor=white@0.8:x=7:y=7 ~/temp/video_frames/output_%04d.jpg to extract every frame of the video.

P.S. The last section in the video (debug animation) is used to verify the file transfer. If that part is seen janky, then it is probably a problem when transferring the video file etc, since that should definitely be 60FPS.

As usual, the code is at the GitHub branch mentioned above.

https://user-images.githubusercontent.com/5236035/192124851-19bae792-ad31-4ae3-8717-8a0821038d00.mp4

fzyzcjy2022-09-26T02:44:43Z GitHub

VsyncWaiter schedules unneeded extra AwaitVSync callbacks for one frame

Consider the following sequence:

  • ScheduleSecondaryCallback(id1, callback1)
  • ScheduleSecondaryCallback(id2, callback2)

Then, without this fix, the AwaitVSync will be called twice. Then, FireCallback will be called twice at the beginning of the next frame. But we know that FireCallback calls all primary and secondary callbacks, so being called twice just waste some computation resource.

By the way, the following sequences are all ok without bugs:

  • AsyncWaitForVsync
  • ScheduleSecondaryCallback(id1)
  • ScheduleSecondaryCallback(id2)

or

  • ScheduleSecondaryCallback(id1)
  • AsyncWaitForVsync
  • ScheduleSecondaryCallback(id2)

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/112439

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-09-26T02:44:47Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-09-26T05:50:57.984+00:00 Discord

Brief update in recent experiments: Overhead per frame is medium=0.53ms, p95=0.81ms, p99=1.10ms, on my low-end testing device, for the "enter-new-screen" demo. (Indeed it may not be called "overhead", since those time are really needed to update data for screen)

moffatman2022-09-26T12:15:54Z GitHub

Is build progress occuring during animation here? Because the effect could be replicated by just delaying the complex content build for ~500 ms (animation duration).

fzyzcjy2022-09-26T12:37:42Z GitHub

@moffatman

Is build progress occuring during animation here

Not completely get the question... If the question is, whether animation happens when the complex widget is being built/layouted, the answer is yes.

Because the effect could be replicated by just delaying the complex content build for ~500 ms (animation duration).

You need some extra preempt points, instead of a single sleep(500ms). For example, this should work:

build() {
for(var i=0;i<100;++i) {
Actor.instance.maybePreemptRender();
sleep(const Duration(milliseconds: 5));
}
return your widget;
}

Indeed, preempt points are auto injected via PreemptPoint, and (possibly done in the real PR) in every RenderObject.layout. So usually no need to manually write that.

By the way, your original modification does not work, because by default I do not expect a single widget.build to exceed 16ms. Anyway if that is the case just insert a few maybePreemptRender.

fzyzcjy2022-09-26T14:21:09.217+00:00 Discord

Good morning friends! Should I start making the PR? @Hixie @Jonah Williams @dnfield @gaaclarke @Callum @ping (googlers who had discussions about this proposal)

The design doc has been overhauled:

  • Add chapter "experiments (prototype)", showing videos, code demos, FPS analysis, and overhead analysis.
  • Add section "needed code change", demonstrating what framework and engine code (few) needs to be modified.
  • Overhaul chapter "comparison", elaborating the differences.

I want to sincerely say thanks to Flutter. Flutter has saved me months, if not a year, by allowing me to write a single codebase and run on dual platforms (saving half of the time), to use the very productive declarative framework and hot reload, to utilize the quickly-progressing Dart language with expressive and safe type system, to easily customize the appearance as accurate as pixel level, to be able to dig down and modify engine code when I need a new feature (impossible with Web), and many more.

That is a big reason why I decided to contribute this PR (Preemption for 60 FPS) to Flutter. Flutter has saved me so much time, so now it is my turn to provide my time to Flutter to make it better. I also love open source very much, such as my flutter_rust_bridge open-source library (which could have been closed-source and only used by myself), and my previous PRs to Flutter fixing bugs, which is another driving force.

fzyzcjy2022-09-26T14:21:30.357+00:00 Discord

(overhauled design doc) https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko

Hixie2022-09-26T18:48:10.767+00:00 Discord

i still don't understand how you run the animation phase in your proposal. (the discussion in the last comment in the doc)

gaaclarke2022-09-26T20:17:01.105Z Google Doc

It's unclear the mechanism that is used to resume layout.  Are we really resuming or retrying?  In order to resume the whole stack would have to be captured (ie a continuation).

gaaclarke2022-09-26T20:35:41.851Z Google Doc

I'm unfamiliar with how keframe works.  Would this make that library obsolete?  Does it handle all the cases it does?

gaaclarke2022-09-26T21:21:30.304Z Google Doc

What happens if we can't update the existing tree in the 1.667ms we have then?  How do we select this number 15ms?

fzyzcjy2022-09-26T22:41:49.728+00:00 Discord

@Hixie Hi I add a section "How is animation implemented" just now. Hope I explain it clearly now!

fzyzcjy2022-09-26T22:42:12.045+00:00 Discord

(last section in the "detailed design")

Hixie2022-09-26T22:51:01.919+00:00 Discord

ah, i see

Hixie2022-09-26T22:51:06.903+00:00 Discord

so it only works for animations driven from tickers

Hixie2022-09-26T22:51:20.268+00:00 Discord

this whole design seems super fragile to me

fzyzcjy2022-09-26T22:51:53.514Z Google Doc

My proposed method is not sensitive to the threshold indeed. In other words, suppose you happen to pause at 15ms, then you do not necessarily need to finish UI thread work and submit to raster thread in 16.67-15=1.67ms. On the contrary, you can, e.g. finish in 2ms or 3ms or 4ms or whatever, as long as raster thread have enough time to finish his hob.

Some details can be found in the "when to call preemptRender" section

fzyzcjy2022-09-26T22:52:11.486+00:00 Discord

May I know what other kinds of animations exist in Flutter?

fzyzcjy2022-09-26T22:52:17.671+00:00 Discord

I personally have only seen those from tickers

Hixie2022-09-26T22:52:24.228+00:00 Discord

could be anything

fzyzcjy2022-09-26T22:52:34.991+00:00 Discord

Hmm could you please point out where so I can try to improve?

Hixie2022-09-26T22:53:19.727+00:00 Discord

what i mean by fragile is that there's lots of ways you could write a flutter app that violates the assumptions made here

fzyzcjy2022-09-26T22:53:33.925+00:00 Discord

If I understand correctly, AnimatedBuilder, AnimatedWidget, FooTransition, TweenAnimationBuilder etc all use vsync and tickers

Hixie2022-09-26T22:54:02.907+00:00 Discord

like, for example, what if two widgets communicate via some other object, and one is in one of these interruptible subtrees and the other isn't. they'll be seeing different phases at different times

fzyzcjy2022-09-26T22:54:03.985+00:00 Discord

Dev only needs to pay attention to the code inside PreemptBuilder.builder, which should be a little of code (instead of a lot)

fzyzcjy2022-09-26T22:54:19.685+00:00 Discord

That builder is only for smooth animations

fzyzcjy2022-09-26T22:54:26.213+00:00 Discord

not for very huge heavy things 🙂

Hixie2022-09-26T22:54:35.681+00:00 Discord

that's what i mean by fragile :-)

Hixie2022-09-26T22:54:49.953+00:00 Discord

something that isn't fragile would work in many different scenarios

fzyzcjy2022-09-26T22:55:13.954+00:00 Discord

Sure I agree with that. But it seems to solve the smoothness at 60fps problem

fzyzcjy2022-09-26T22:55:19.514+00:00 Discord

Everything comes with a cost :/

fzyzcjy2022-09-26T22:55:41.363+00:00 Discord

Especially with the gain of "60fps, no matter how heavy your subtree is to build and layout"

Hixie2022-09-26T22:55:53.533+00:00 Discord

well, it doesn't really. it solves the "subset of your tree at 60fps" problem :-)

fzyzcjy2022-09-26T22:56:15.571+00:00 Discord

The "user seeing the UI at 60fps" 🙂

Hixie2022-09-26T22:56:24.635+00:00 Discord

sort of. parts will be 30fps :-)

Hixie2022-09-26T22:56:25.743+00:00 Discord

anyway

Hixie2022-09-26T22:56:35.747+00:00 Discord

is there a way to build this in a package?

fzyzcjy2022-09-26T22:56:39.655+00:00 Discord

parts that does not need change indeed so users never know it 🙂

Hixie2022-09-26T22:56:43.193+00:00 Discord

what does it actually need from the core framework?

fzyzcjy2022-09-26T22:56:54.318+00:00 Discord

(btw why you can type that face while mine will be a yellow face)

fzyzcjy2022-09-26T22:56:59.566+00:00 Discord

Yes and no, the details:

Hixie2022-09-26T22:57:08.356+00:00 Discord

i suppose you need to hook into every build/layout

fzyzcjy2022-09-26T22:57:18.532+00:00 Discord

image

fzyzcjy2022-09-26T22:57:23.047+00:00 Discord

The "needed code change" section

fzyzcjy2022-09-26T22:57:38.635+00:00 Discord

So maybe I should PR those framework changes, and then make a package? Or should I PR the whole thing?

Hixie2022-09-26T22:59:01.092+00:00 Discord

some of these changes are sort of fundamentally opposed to flutter's design philosophy like, for example, the widget system shouldn't need to know about tickers a developer should be able to build an entirely separate animation system and just layer it on top of the framework but if we do what this proposal suggests, we're really saying that tickers are very special in a core sense

gaaclarke2022-09-26T22:59:14.749+00:00 Discord

Or a way to preempt an isolate, creating a continuation that can be resumed, but that probably isn't going to happen.

Hixie2022-09-26T22:59:34.985+00:00 Discord

yeah that seems even more fragile

fzyzcjy2022-09-26T22:59:37.998+00:00 Discord

Btw, not sure what exact problem will they face? The widget in main tree see one ticker per frame. The widget in second tree see many ticker ticks per frame. And they see the same microtasks and event loop run.

Hixie2022-09-26T23:00:17.695+00:00 Discord

i dunno, it's hard to predict. that's what i mean by "fragile".

fzyzcjy2022-09-26T23:00:19.192+00:00 Discord

IIRC I have had some thoughts a bit similar to that before (see that github thread) also without success

fzyzcjy2022-09-26T23:00:42.206+00:00 Discord

I see. Just want to have one example in my mind, really cannot imagine.

fzyzcjy2022-09-26T23:00:55.767+00:00 Discord

If there are zero examples then just cannot know what to do/worry with it

Hixie2022-09-26T23:01:15.233+00:00 Discord

i totally understand that my concerns are unsatisfying

fzyzcjy2022-09-26T23:01:36.465+00:00 Discord

It's totally OK, I just need some input to know the concerns better

fzyzcjy2022-09-26T23:01:47.594+00:00 Discord

such that I can try to figure out a way to solve it if it is a problem

Hixie2022-09-26T23:02:01.511+00:00 Discord

let me see if i can think of a good example

BetoMan02022-09-26T23:02:33.416+00:00 Discord

I hope I'm not making an oot question: isn't the PreemptBuilder proposal trying to solve what Impller also aims to solve? (= remove jank on animations by pre-compiling stuff)

fzyzcjy2022-09-26T23:03:01.674+00:00 Discord

Well seems not. Impeller solves raster thread jank, PreemptBuilder solves build/layout jank in ui thread

gaaclarke2022-09-26T23:03:15.884+00:00 Discord

One piece of feedback that is actionable is imaging what it would take to make this a plugin.

BetoMan02022-09-26T23:04:04.521+00:00 Discord

Clear, thanks!

fzyzcjy2022-09-26T23:04:14.722+00:00 Discord

Yes I also think it is a feasible idea. The "Needed code change" section is about "what needs to change framework/engine"

fzyzcjy2022-09-26T23:04:24.415+00:00 Discord

Indeed not many changes to framework/engine

fzyzcjy2022-09-26T23:04:53.654+00:00 Discord

(That section, plus a few "make file-private function public but do not need to change any real functionality")

Hixie2022-09-26T23:05:07.907+00:00 Discord

@fzyzcjy so for example, suppose instead of using a Ticker i have an animation that's driven by a Timer. every time it triggers, it changes some global state. then i have many widgets that follow that global state. with your proposal, the sections inside the special widget would not animate fast.

fzyzcjy2022-09-26T23:05:37.748+00:00 Discord

Thanks for the example. However, IIRC "animation by timer" is discouraged?

fzyzcjy2022-09-26T23:05:50.512+00:00 Discord

So if a user uses a discouraged approach he gets bad results

Hixie2022-09-26T23:06:47.144+00:00 Discord

writing a widget tree that takes more than 16ms to build is also discouraged, but we're still trying to help people who do that :-)

fzyzcjy2022-09-26T23:07:10.502+00:00 Discord

Well that is indeed mandatory, because:

fzyzcjy2022-09-26T23:07:25.355+00:00 Discord

image

fzyzcjy2022-09-26T23:07:50.337+00:00 Discord

In other words, a dev trying the best to follow Flutter suggestions still may face the build/layout-more-than-16ms problem

fzyzcjy2022-09-26T23:08:01.106+00:00 Discord

Such as bytedance

fzyzcjy2022-09-26T23:08:14.234+00:00 Discord

And there are just so many really slow devices in the world indeed

Hixie2022-09-26T23:08:46.757+00:00 Discord

it's really important for us that flutter be something thet's predictable and easy to understand

fzyzcjy2022-09-26T23:08:51.625+00:00 Discord

It is just impossible to be smooth on all slow, slower, slower than slower devices (without the proposal). layout/build really needs some time, and on them it can exceed 16ms

fzyzcjy2022-09-26T23:08:57.013+00:00 Discord

I totally agree

fzyzcjy2022-09-26T23:09:10.938+00:00 Discord

for well-behaved users 😉

Hixie2022-09-26T23:09:14.557+00:00 Discord

for everyone

Hixie2022-09-26T23:09:27.63+00:00 Discord

because people don't necessarily know what best practices are

fzyzcjy2022-09-26T23:09:34.942+00:00 Discord

Hmm I see

Hixie2022-09-26T23:09:45.565+00:00 Discord

and they will never be able to learn if they get frustrated with a system that isn't helping them when they're new

Hixie2022-09-26T23:09:52.056+00:00 Discord

such that they just give up

fzyzcjy2022-09-26T23:09:55.316+00:00 Discord

totally agree

fzyzcjy2022-09-26T23:10:07.323+00:00 Discord

but I am not very sure will a new learner really use PreemptBuilder?

Hixie2022-09-26T23:10:27.927+00:00 Discord

yes, because they'll google around "how to help slow app" and they'll find a youtube video that talks about it and they'll try it

fzyzcjy2022-09-26T23:10:28.595+00:00 Discord

or should we add some doc to PreemptBuilder saying the care needed

fzyzcjy2022-09-26T23:10:40.798+00:00 Discord

Ah that is quite reasonable

fzyzcjy2022-09-26T23:11:00.139+00:00 Discord

or use the alternative approach you mentioned above - I publish a package about it

fzyzcjy2022-09-26T23:11:22.394+00:00 Discord

then in the package frontpage I can just have a big warning saying "hey don't use timers" and so on

Hixie2022-09-26T23:12:58.823+00:00 Discord

another example would be, suppose there's two RenderObjects and they each create a Layer and those Layers know they will always be used together because the RenderObjects are always used together. So they can rely on always existing as a pair and can always read the render object sizes and so on when the layer tree is walked. now suppose one of those RenderObjects is in the "expensive" part of the subtree and the other is in the "fast" part of the subtree. or suppose one is in the "expensive" part that got laid out before we interrupted layout, and the other is in the part that got laid out later.

fzyzcjy2022-09-26T23:14:32.852+00:00 Discord

That looks like a problem as well. But may I know how layers read RO sizes? IIRC layers do not remember ROs

Hixie2022-09-26T23:14:40.077+00:00 Discord

they can do whatevery they want

Hixie2022-09-26T23:14:42.073+00:00 Discord

they're just code

fzyzcjy2022-09-26T23:14:54.263+00:00 Discord

That sounds like a problem

fzyzcjy2022-09-26T23:15:35.368+00:00 Discord

For expert users who uses custom layers

Hixie2022-09-26T23:15:56.315+00:00 Discord

another example would be GlobalKey reparenting. Suppose the section of the subtree on the "fast" branch tries to move a widget from the section of the tree in the "expensive" branch using GlobalKeys.

fzyzcjy2022-09-26T23:16:18.486+00:00 Discord

I see, that will definitely not work.

fzyzcjy2022-09-26T23:16:33.121+00:00 Discord

But IMHO no "animation" requires moving it

fzyzcjy2022-09-26T23:16:41.366+00:00 Discord

But I agree we should be friendly to all edge cases

Hixie2022-09-26T23:16:49.464+00:00 Discord

fundamentally the problem is that your proposal violates some of the core assumptions of the framework

Hixie2022-09-26T23:17:07.502+00:00 Discord

such as, that we'll always do a single build/layout/paint/layer/semantics pass per frame

fzyzcjy2022-09-26T23:17:08.823+00:00 Discord

I agree, it is just the least violation I can find

fzyzcjy2022-09-26T23:17:41.675+00:00 Discord

I refine to: per tree per pseudo or real frame. fast tree it is 60fps pseudo "frame"

fzyzcjy2022-09-26T23:18:10.927+00:00 Discord

I have also tried other methods and there are previous thoughts about other methods as well, but seem they have drawbacks or cannot work

fzyzcjy2022-09-26T23:18:17.571+00:00 Discord

(See "Comparison" chapter)

Hixie2022-09-26T23:18:21.235+00:00 Discord

any time you violate core assumptions, you have to either very carefully think about what the new assumptions should be that we can make sure the entire model follows these new assumptions, or things will become fragile

fzyzcjy2022-09-26T23:18:38.793+00:00 Discord

totally agree

Hixie2022-09-26T23:18:44.994+00:00 Discord

(it's often extremely hard to do this)

fzyzcjy2022-09-26T23:18:50.858+00:00 Discord

think so :/

Hixie2022-09-26T23:19:37.767+00:00 Discord

the problem with a system like flutter's, which has many years of work already done on it, is that there's a lot of these assumptions and it's easy to just not know about some of them. e.g. i'm sure i don't know them all at this point.

fzyzcjy2022-09-26T23:19:57.458+00:00 Discord

totally agree about that

fzyzcjy2022-09-26T23:20:05.783+00:00 Discord

so should I just make a package and publish to pub.dev?

fzyzcjy2022-09-26T23:20:16.333+00:00 Discord

with minimal necessary modifications to framework and engine

Hixie2022-09-26T23:20:44.37+00:00 Discord

it would be interesting to examine what the minimal changes would need to be

fzyzcjy2022-09-26T23:20:59.796+00:00 Discord

mainly this image

Hixie2022-09-26T23:21:01.306+00:00 Discord

the list currently in the doc is probably not minimal enough to justify doing

fzyzcjy2022-09-26T23:21:14.525+00:00 Discord

Let me think whether it is possible to remove some

Hixie2022-09-26T23:21:15.601+00:00 Discord

for example, i definitely don't think we should elevate Tickers in this way

Hixie2022-09-26T23:21:38.752+00:00 Discord

and adding a hook to every build/layout step is going to be a cost everyone would pay even if they don't use the feature

Hixie2022-09-26T23:21:46.572+00:00 Discord

which is probably not something we'd want to do

Hixie2022-09-26T23:21:53.623+00:00 Discord

especially when the goal is to make things faster :-)

fzyzcjy2022-09-26T23:22:16.253+00:00 Discord

May I know why?

gaaclarke2022-09-26T23:22:25.485+00:00 Discord

we could potentially allow people to swap out the layout function with zero cost or one layer of indirection at the top of the tree

fzyzcjy2022-09-26T23:22:54.389+00:00 Discord

I am not expert in compiler, but will it introduce runtime cost if the hook is always null? (Will compiler throw it away)

fzyzcjy2022-09-26T23:23:47.88+00:00 Discord

Or maybe we can do what @gaaclarke suggests, say create a Renderer.layout(RenderObject ro) and I just class MyRenderer extends Renderer

Hixie2022-09-26T23:23:50.5+00:00 Discord

flutter's tried to follow a "layered" approach, where there are very few "core" components that one must use. For example, you can use RenderObjects without widgets. You can use widgets without material. You don't have to use WidgetsApp if you don't want to. You don't have to use Tickers if you don't want to. etc.

Hixie2022-09-26T23:24:33.281+00:00 Discord

if there's a way to do that that's zero cost during normal operations, that's worth considering

Hixie2022-09-26T23:24:40.639+00:00 Discord

i'm sure lots of packages could benefit from that kind of hook

fzyzcjy2022-09-26T23:24:46.759+00:00 Discord

Oh I see maybe I do not explain clearly. We do notexpose all tickers. We only expose those who are created inside SingleTickerProviderStateMixin

fzyzcjy2022-09-26T23:25:07.999+00:00 Discord

We already let SingleTickerProviderStateMixin to read TickerMode (inherited widget) so looks like we are just doing something mimicking the existing

fzyzcjy2022-09-26T23:25:43.261+00:00 Discord

Then maybe I should try that class Renderer and report some metrics later

Hixie2022-09-26T23:26:24.116+00:00 Discord

right my point is that TickerMode is just a widget. you don't have to use it. SingleTickerProviderStateMixin is just a tool, you don't have to use it. But if we say that SingleTickerProviderStateMixin now exposes a special API, we're saying it's special in a way that MyCustomSingleTickerProviderStateMixin is not, and can never be.

fzyzcjy2022-09-26T23:27:28.513+00:00 Discord

Sorry I do not quite get it. I am trying to modify SingleTickerProviderStateMixin in a ways that, originally it calls TickerMode (inherited widget), not it calls TickerMode + TickerRegistry (another inherited widget)

fzyzcjy2022-09-26T23:28:04.088+00:00 Discord

image

fzyzcjy2022-09-26T23:28:11.775+00:00 Discord

image

fzyzcjy2022-09-26T23:28:25.683+00:00 Discord

Just read an inherited widget, completely mimicking what we do to TickerMode

fzyzcjy2022-09-26T23:28:55.87+00:00 Discord

(code may not be exactly mimic though if merely looking at the screenshot; but digging down one or two function calls we see it is the same)

Hixie2022-09-26T23:29:20.952+00:00 Discord

what would TickerRegistry do?

fzyzcjy2022-09-26T23:29:45.511+00:00 Discord

It knows all tickers in the subtree and created by TickerProviderStateMixin

Hixie2022-09-26T23:30:03.585+00:00 Discord

so now if i want to make a new kind of Ticker, how do i register it?

fzyzcjy2022-09-26T23:30:06.172+00:00 Discord

Then in PreemptBuilder (e.g. in 3rd package), we just call all of those tickers's onTick

Hixie2022-09-26T23:30:17.073+00:00 Discord

MyCustomTicker, which has no interfaces in common with Ticker

fzyzcjy2022-09-26T23:30:17.93+00:00 Discord

Just like how you register it with TickerMode

Hixie2022-09-26T23:30:25.419+00:00 Discord

TickerMode doesn't know about Tickers

Hixie2022-09-26T23:30:38.099+00:00 Discord

it just reports a boolean

Hixie2022-09-26T23:30:53.159+00:00 Discord

and notifies you when it changes

fzyzcjy2022-09-26T23:31:15.661+00:00 Discord

Then what about making TickerRegistry receive an abstract class (interface) instead of the real Ticker class

fzyzcjy2022-09-26T23:31:23.166+00:00 Discord

Or , I know it:

fzyzcjy2022-09-26T23:31:32.112+00:00 Discord

Just do not let TickerRegistry know the tickers

Hixie2022-09-26T23:31:39.096+00:00 Discord

so then what does it do?

fzyzcjy2022-09-26T23:32:00.699+00:00 Discord

Instead, let it (maybe w/a rename) provide addListenerWhenOtherPartsOfSystemWantsToCallAnExtraOnTick

fzyzcjy2022-09-26T23:34:14.693+00:00 Discord

So now the MyCustomTicker (not extend/implement Ticker) is happy

fzyzcjy2022-09-26T23:36:23.436+00:00 Discord

e.g. named "ExtraOnTickProvider"

Hixie2022-09-26T23:37:00.42+00:00 Discord

basically this makes ExtraOnTickProvider a special class

fzyzcjy2022-09-26T23:37:21.476+00:00 Discord

May I know why it is special?

Hixie2022-09-26T23:37:44.763+00:00 Discord

anyone who wants to participate in this model has to make sure they can express their animation logic as an ExtraOnTickProvider

fzyzcjy2022-09-26T23:38:17.652+00:00 Discord

Well, again, is there real examples who create CustomTicker unrelated to real ticker logic...

fzyzcjy2022-09-26T23:38:29.86+00:00 Discord

If so he must be an expert

fzyzcjy2022-09-26T23:38:40.392+00:00 Discord

And experts should understand how PreemptBuilder works 😉

Hixie2022-09-26T23:38:53.998+00:00 Discord

as we talked about before, that's not a safe line of reasoning

Hixie2022-09-26T23:39:11.419+00:00 Discord

non-experts will be exposed to all APIs

fzyzcjy2022-09-26T23:39:15.756+00:00 Discord

I totally agree we should be friendly to new users watching a youtube video

Hixie2022-09-26T23:39:22.685+00:00 Discord

"only experts will use it" is never a valid way to design APIs

Hixie2022-09-26T23:39:30.851+00:00 Discord

because everyone is a non-expert the first time they use an API

fzyzcjy2022-09-26T23:39:32.534+00:00 Discord

Well, ok :/

fzyzcjy2022-09-26T23:39:42.074+00:00 Discord

I just mean not sure who will really need this

fzyzcjy2022-09-26T23:39:48.553+00:00 Discord

Ok then let me think about other ways to expose Tickers

Hixie2022-09-26T23:39:53.635+00:00 Discord

fwiw i think this is something we could have done from the beginning, basically instead of the animation phase which relies on scheduleMicrotask as the special magic, we would have a registry of OnTickProviders that provide the special magic

fzyzcjy2022-09-26T23:40:15.5+00:00 Discord

Totally agree. But seems we can never change it today? 😦

Hixie2022-09-26T23:40:29.6+00:00 Discord

but having now gone the former route, moving to the latter route either means having extra complexity (because we have both), or means taking on the rather large task of migrating the entire ecosystem to the new mechanism

Hixie2022-09-26T23:40:41.992+00:00 Discord

sometimes we have done that kind of thing

fzyzcjy2022-09-26T23:41:00.382+00:00 Discord

I can contribute

Hixie2022-09-26T23:41:10.65+00:00 Discord

e.g. at one point State.widget was called State.config and someone (i forget who?) renamed it and basically migrated the entire ecosystem to the new name

Hixie2022-09-26T23:41:20.665+00:00 Discord

but that was a long time ago and the ecosystem is much bigger now

fzyzcjy2022-09-26T23:41:26.066+00:00 Discord

that's true

fzyzcjy2022-09-26T23:41:33.741+00:00 Discord

(and congratulations!)

fzyzcjy2022-09-26T23:41:46.55+00:00 Discord

So what about this:

fzyzcjy2022-09-26T23:42:24.037+00:00 Discord

Change SingleTickerProviderStateMixin.createTicker . Do not ticker = Ticker(). But instead ticker = TickerProviderConfigInheritedWidget.of(context).createTicker()

fzyzcjy2022-09-26T23:42:36.305+00:00 Discord

In other words, we have a new TickerProviderConfigInheritedWidget

fzyzcjy2022-09-26T23:42:46.256+00:00 Discord

it just configures how TickerProviderStateMixin create tickers

Hixie2022-09-26T23:43:12.744+00:00 Discord

SingleTickerProviderStateMixin.createTicker is not the only way animation triggers are created

fzyzcjy2022-09-26T23:43:16.717+00:00 Discord

When there is no such inherited widget, just do the old logic: new Ticker()

fzyzcjy2022-09-26T23:43:27.704+00:00 Discord

May I know what are the others?

Hixie2022-09-26T23:43:35.709+00:00 Discord

Timer.periodic, for example

fzyzcjy2022-09-26T23:43:41.727+00:00 Discord

That just will not be supported

Hixie2022-09-26T23:43:48.307+00:00 Discord

Streams are another

fzyzcjy2022-09-26T23:44:06.729+00:00 Discord

Since these logic are in my 3rd party package I guess it is not a huge problem

fzyzcjy2022-09-26T23:44:16.665+00:00 Discord

just write down "Stream and Timer are not supported" in README

fzyzcjy2022-09-26T23:44:18.824+00:00 Discord

of the 3rd package

Hixie2022-09-26T23:44:24.246+00:00 Discord

yeah it's a lot easier if it's a package than the core framework

fzyzcjy2022-09-26T23:44:43.661+00:00 Discord

And also easier to upgrade as well (no need to wait 3mo for next stable flutter if someone sees a bug)

fzyzcjy2022-09-26T23:46:05.902+00:00 Discord

image

fzyzcjy2022-09-26T23:46:20.574+00:00 Discord

So looks like my next step is to create these 4 PRs to Flutter?

Hixie2022-09-26T23:53:23.351+00:00 Discord

i think for each one we should carefully consider if there are potentially better ways to approach it

fzyzcjy2022-09-26T23:53:34.034+00:00 Discord

Sure 🙂

Hixie2022-09-26T23:53:36.376+00:00 Discord

but that's definitely more tractable than the earlier list :-)

fzyzcjy2022-09-26T23:54:41.548+00:00 Discord

Feel free to raise any potential problems and I will try to fix them

Hixie2022-09-26T23:57:21.34+00:00 Discord

i haven't studied the engine changes yet

Hixie2022-09-26T23:57:29.494+00:00 Discord

i'm still looking at the ticker one :-)

fzyzcjy2022-09-26T23:57:46.971+00:00 Discord

I see, take your time 🙂

Hixie2022-09-26T23:57:57.944+00:00 Discord

the problem you're trying to solve is, how to cause CircularProgressIndicator to tick, even though we haven't had an animation phase, right?

fzyzcjy2022-09-26T23:58:08.466+00:00 Discord

yes

Hixie2022-09-26T23:58:46.237+00:00 Discord

and _CircularProgressIndicatorState uses an AnimationController with a Ticker created from a SingleTickerProviderStateMixin

fzyzcjy2022-09-26T23:59:01.397+00:00 Discord

yes just like that

Hixie2022-09-26T23:59:20.473+00:00 Discord

and ticker uses SchedulerBinding.instance.scheduleFrameCallback

Hixie2022-09-26T23:59:27.017+00:00 Discord

interesting

fzyzcjy2022-09-26T23:59:28.56+00:00 Discord

exactly

Hixie2022-09-27T00:00:29.559+00:00 Discord

and the problem is that after you render your interrupted frame, you want to rerun all the newly scheduled frame callbacks, so that when you repaint this widget, it ends up advanced a little...

fzyzcjy2022-09-27T00:00:43.358+00:00 Discord

not all indeed

fzyzcjy2022-09-27T00:00:50.621+00:00 Discord

only those in second tree

fzyzcjy2022-09-27T00:01:07.145+00:00 Discord

that's why I make inherited widget - I put one at root of second tree

Hixie2022-09-27T00:01:14.423+00:00 Discord

ah, even trickier

fzyzcjy2022-09-27T00:01:23.407+00:00 Discord

because I do not want to disturb the main tree

Hixie2022-09-27T00:05:08.892+00:00 Discord

i certainly see why you gravitate to a way to create and/or register tickers via inherited widget

Hixie2022-09-27T00:06:31.48+00:00 Discord

ironically it would be easier to solve if you wanted to just call the scheduled frame callbacks of every animation

fzyzcjy2022-09-27T00:06:58.215+00:00 Discord

I agree. But that will cause trouble since main tree will receive extra ontick

Hixie2022-09-27T00:08:04.235+00:00 Discord

because then you could just create a new binding...

Hixie2022-09-27T00:08:05.631+00:00 Discord

hmm

fzyzcjy2022-09-27T00:08:17.149+00:00 Discord

hmm looks hard to make two bindings and let things in second subtree smartly register to second binding

Hixie2022-09-27T00:09:24.143+00:00 Discord

yeah the binding logic knows nothing about the trees

Hixie2022-09-27T00:10:49.448+00:00 Discord

yeah i dunno how to do this efficiently. i don't think we want to change SingleTickerProviderStateMixin et al to register their tickers, that seems like a lot of cycles spent that most people would never get to benefit from. (That said, if you can find a way to hook into layout cheaply, maybe we can expose a hook for SingleTickerProviderStateMixin too?)

Hixie2022-09-27T00:10:54.328+00:00 Discord

tell me more about the engine changes?

fzyzcjy2022-09-27T00:13:17.028+00:00 Discord

a lot of cycles spent that most people would never get to benefit from. (That said, if you can find a way to hook into layout cheaply, maybe we can expose a hook for SingleTickerProviderStateMixin too?) I will experiment to see whether performance regresses

fzyzcjy2022-09-27T00:13:46.645+00:00 Discord

So, is it enough to see benchmarks built into flutter repository? If they do not regress are we safe

Hixie2022-09-27T00:14:06.415+00:00 Discord

i mean to be clear, performance will definitely regress if we do more work, even if we can't measure it

fzyzcjy2022-09-27T00:14:18.193+00:00 Discord

That's definitely true

Hixie2022-09-27T00:14:43.441+00:00 Discord

we can't just add code that doesn't measurably affect benchmarks because if we did that 100 times then we would have moved the benchmarks

fzyzcjy2022-09-27T00:15:21.256+00:00 Discord

totally agree. what about this: we just have a global flag, enableTickerConfig. If it is false, do the old thing. If it is true, read Inherited Widget

fzyzcjy2022-09-27T00:15:36.368+00:00 Discord

And in my 3rd party package, one setup step is "set enableTickerConfig=true"

fzyzcjy2022-09-27T00:15:43.082+00:00 Discord

and by default false

fzyzcjy2022-09-27T00:16:35.492+00:00 Discord

Or, we just do not call inherited widget at all

fzyzcjy2022-09-27T00:16:56.415+00:00 Discord

we call a function, say: Ticker Function(BuildContext context, VoidCallback onTick) tickerCreator = (_, onTick) => Ticker(onTick)

fzyzcjy2022-09-27T00:17:20.027+00:00 Discord

it by default is nothing but Ticker.new, and we can set it to read the inherited widget And I guess compilers may even inline it, if that createTheTicker is never setted?

fzyzcjy2022-09-27T00:17:46.809+00:00 Discord

It is in the screenshot. I can explain more if something is unclear

fzyzcjy2022-09-27T00:19:33.711+00:00 Discord

Indeed we are unconditionally reading inherited widget (b/c the TickerMode) whenever we create one Ticker.

fzyzcjy2022-09-27T00:19:50.035+00:00 Discord

And I guess reading inh widget is much much more expensive that a function call that can possibly be inlined

fzyzcjy2022-09-27T00:19:59.357+00:00 Discord

Though I know a little accumulates to a lot

fzyzcjy2022-09-27T00:21:41.877+00:00 Discord

Or, for absolutely zero overhead: Maybe enable it by bool.fromEnvironement flag. IIRC those are compile time constants, and compilers will just handle them at compile time. for example, kDebugMode ? heavy_work : cheap_work, the heavy_work seems even not in the final binary

fzyzcjy2022-09-27T00:22:17.516+00:00 Discord

Users of the 3rd party package will need --dart-define=enableTheTickerConfig=true or something like that. And other users have exactly zero overhead.

fzyzcjy2022-09-27T00:27:49.254+00:00 Discord

Hope this is a bit clearer:

fzyzcjy2022-09-27T00:29:18.604+00:00 Discord

image

Hixie2022-09-27T01:27:53.684+00:00 Discord

i meant tell me more about why those specific changes

Hixie2022-09-27T01:28:28.038+00:00 Discord

it sounds like what you are trying to do is allow frames to be rendered from a different callback than the one that asked for it, right?

Hixie2022-09-27T01:29:24.747+00:00 Discord

hm, this is another one of those cases where there's some pretty fundamental assumptions built into the system that we would be breaking here, and need to think very carefully about

Hixie2022-09-27T01:30:06.995+00:00 Discord

that has the problem of being hard to test (we would need to run all the tests for every combination of these flags, which gets exponentially expensive)

fzyzcjy2022-09-27T01:36:04.001+00:00 Discord

I see the problem. What about this:

bool? debugOverrideTheFlag;
bool get theFlag {
var ans;
assert(() => ans = debugOverrideTheFlag);
return ans ?? bool.fromEnvironment('the.flag');
}

It has zero overhead in release (given it is just a compile time constant). And it has testability (just debugOverrideTheFlag)

fzyzcjy2022-09-27T01:40:22.847+00:00 Discord

image

fzyzcjy2022-09-27T01:40:24.579+00:00 Discord

"Why" added to the doc now

fzyzcjy2022-09-27T01:40:42.262+00:00 Discord

I guess no? i.e. I do not violate it?

fzyzcjy2022-09-27T01:41:36.713+00:00 Discord

I am still calling window.render inside the BeginFrame callback, because the preemptRender is a function called from build/layout functions which is called from BeginFrame. I just call it multiple times (instead of one time).

fzyzcjy2022-09-27T01:41:57.164+00:00 Discord

So I guess I do not break this

Hixie2022-09-27T01:51:07.488+00:00 Discord

but then presumably we would not call it for the next actual BeginFrame, right? since we'd have already done it

fzyzcjy2022-09-27T01:51:34.018+00:00 Discord

May I know what is "it"?

fzyzcjy2022-09-27T01:52:09.828+00:00 Discord

If "it" is "Produce", then that still calls in next BeginFrame

fzyzcjy2022-09-27T01:52:29.676+00:00 Discord

The logic is, at Render, when we see no continuation (this happens when one BeginFrame has two window.render), originally we just halt early

fzyzcjy2022-09-27T01:52:54.141+00:00 Discord

But now, when this case, we add one extra continuation via Produce().

fzyzcjy2022-09-27T01:53:28.32+00:00 Discord

We will finish this Produce() just a few lines below. So in next BeginFrame we need to Produce() again (for the next frame's Render)

fzyzcjy2022-09-27T01:54:21.545+00:00 Discord

image

fzyzcjy2022-09-27T01:54:28.343+00:00 Discord

the blue-highlighted lines are "finish the Produce continuation" logic

Hixie2022-09-27T01:54:39.023+00:00 Discord

it=window.render

fzyzcjy2022-09-27T01:54:51.04+00:00 Discord

Oh, then we still call it in the next frame

fzyzcjy2022-09-27T01:55:12.227+00:00 Discord

Because in my proposal, one plain-old frame will have zero to many extra smooth window.render

fzyzcjy2022-09-27T01:55:21.81+00:00 Discord

But that plain-old frame is just there. It runs normal full pipeline

fzyzcjy2022-09-27T01:55:28.563+00:00 Discord

including window.render inside that pipeline

Hixie2022-09-27T01:56:25.155+00:00 Discord

oh I see, we just skip the BeginFrame for "missed" frames that this would partially render

fzyzcjy2022-09-27T01:57:09.875+00:00 Discord

If a frame is missed, the whole pipeline just do not execute, and the proposed PreemptBuilder etc also do not execute

Hixie2022-09-27T01:57:18.475+00:00 Discord

how can we know the right timestamp for the "fast" frames if we don't get the BeginFrame call?

fzyzcjy2022-09-27T01:57:34.875+00:00 Discord

via the last PR among the four: Get latest vsync data

fzyzcjy2022-09-27T01:57:48.681+00:00 Discord

Indeed only need to read "what is the latest vsync data" once per 16ms

fzyzcjy2022-09-27T01:58:21.39+00:00 Discord

Btw I have confirmed the time is correct in the experiment analysis section

fzyzcjy2022-09-27T01:58:29.558+00:00 Discord

image

fzyzcjy2022-09-27T01:58:54.572+00:00 Discord

in this (photos from a camera mp4 video), we can see the animation is of equal distance

fzyzcjy2022-09-27T01:59:10.282+00:00 Discord

i.e. the arrow shifts the same distance in each frame

fzyzcjy2022-09-27T01:59:38.453+00:00 Discord

If we have the wrong vsync info (or use something like DateTime.now), then we will see the distance not equal at all

Hixie2022-09-27T02:09:29.495+00:00 Discord

how would the system be notified that it had changed?

fzyzcjy2022-09-27T02:09:42.659+00:00 Discord

Just no notification

fzyzcjy2022-09-27T02:09:49.554+00:00 Discord

preemptRender read it and it works well

fzyzcjy2022-09-27T02:10:11.356+00:00 Discord

Indeed there cannot be notifications - because UI thread is fully busy and PostTask to UI thread will not work at all

fzyzcjy2022-09-27T02:13:24.711+00:00 Discord

If I understand correctly, compiler explorer says this works well with zero overhead:

fzyzcjy2022-09-27T02:13:34.879+00:00 Discord

image

fzyzcjy2022-09-27T02:13:37.61+00:00 Discord

https://godbolt.org/z/bGoePGqYb

fzyzcjy2022-09-27T02:14:18.364+00:00 Discord

In the first example (with proposed Dart code), the heavyFunction is even not compiled into the binary. In the second example (just as a comparison), the heavyFunction is compiled and conditionally called.

fzyzcjy2022-09-27T02:14:42.597+00:00 Discord

In other words, my proposed Dart writing seems to be (1) zero overhead (2) easily testable.

fzyzcjy2022-09-27T02:37:40.924+00:00 Discord

For the RenderObject.layout thing

fzyzcjy2022-09-27T02:37:45.751+00:00 Discord

image

fzyzcjy2022-09-27T02:37:48.761+00:00 Discord

https://godbolt.org/z/r433E75Tx

fzyzcjy2022-09-27T02:38:35.404+00:00 Discord
  1. I do see one extra function call (from RenderObject.layout to Renderer.layout)
  2. If that is acceptable we are done; otherwise, we may enable it conditionally via zero-overhead flag just like mentioned above, then we have exactly zero cost
fzyzcjy2022-09-27T03:32:04Z GitHub

Exposing hook about tickers with zero overhead

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This PR is a part for implementing the 60fps smooth rendering (#101227).

Some discussions can be seen in Discord, such as around https://discordapp.com/channels/608014603317936148/608021234516754444/1024141682377236500.

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/101227

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

P.S. Not sure what naming do you like, so just put a very long (temporary) variable name here :)

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-27T03:53:22Z GitHub

Exposing hook for RenderObject.layout with zero overhead

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

Remark: This PR has less priority compared with the other two (https://github.com/flutter/engine/pull/36438, https://github.com/flutter/flutter/pull/112436), because this one can be workaround, while the other two are really mandatory to implement PreemptBuilder.


This PR is a part for implementing the 60fps smooth rendering (#101227).

Some discussions can be seen in Discord, such as around https://discordapp.com/channels/608014603317936148/608021234516754444/1024141682377236500.

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/101227

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-09-27T03:54:31.168+00:00 Discord

P.S. Two PRs about the framework change is created: https://github.com/flutter/flutter/pull/112436

fzyzcjy2022-09-27T03:54:42.036+00:00 Discord

https://github.com/flutter/flutter/pull/112437

fzyzcjy2022-09-27T05:49:30Z GitHub

Allow render to be called multiple times for one BeginFrame

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This PR is a part for implementing the 60fps smooth rendering (#101227).

List which issues are fixed by this PR. You must list at least one issue. https://github.com/flutter/flutter/issues/101227

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

The only change is an "if" as follows (all else are just tests)

image

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

By the way, the tests and code does work: If I comment out the code, the tests fail.

image image

fzyzcjy2022-09-27T05:50:24.155+00:00 Discord

... and https://github.com/flutter/engine/pull/36438

fzyzcjy2022-09-27T13:38:33.735Z Google Doc

Ah sorry I did not see all your questions! I only see the last, and when viewing the second-last I see my avatar so wrongly think all things below have been answered...

fzyzcjy2022-09-27T13:39:08.205Z Google Doc

We do not resume or retry. We just call preemptRender function as any normal function call, and just return from it. So zero cost.

fzyzcjy2022-09-27T13:41:58.686Z Google Doc

I have not done thorough experiments (e.g. ListView scrolling) so cannot give a conclusion now. But it seems this package will cover all cases with less drawbacks and better performance, by solving the problem in a different approach.

fzyzcjy2022-09-27T13:48:09.411+00:00 Discord

@gaaclarke I replied to all your questions now in google doc (Sorry I did not see all your questions this morning... I only see the last, and when viewing the second-last I see my reply there so wrongly think all things below have been answered)

Callum2022-09-27T15:31:32.672+00:00 Discord

Any sliver expert: Is it expected to provide a custom SliverChildDelegate for good performance? Since with the default SliverChildBuilderDelegate, all the children will be rebuilt if the parent is rebuilt.

Hixie2022-09-27T16:04:39.49+00:00 Discord

@Callum only the visible children, iirc, right?

Callum2022-09-27T17:38:57.801+00:00 Discord

Yeah that's true. For reasons, I have both pages rebuild during page pop, so the frame drop was quite noticeable. Both lists didn't need to rebuild as no change in the content, it's a good feature that was not immediately obvious.

jonahwilliams2022-09-27T17:49:02.882+00:00 Discord

I'd be really surprised if there is a significant framedrop from just rebuilding widgets. Do you have some sample code I could look at? I've been looking at scrolling performance issues the last couple of weeks, usually what goes wrong is folks accidentally making the entire list render (even offscreen) and that can be quite slow

Callum2022-09-27T18:31:00.711+00:00 Discord

The list items are quite complex paragraphs, during scrolling it can be okay to have 1-2ms build times. But if 24 of them build at once (12 on each page) during the page pop, it's going to drop frames.

jonahwilliams2022-09-27T18:32:52.526+00:00 Discord

what sort of device are you running on?

jonahwilliams2022-09-27T18:35:59.205+00:00 Discord

also, if its the same paragraph, the layout should be cached in the engine

Callum2022-09-27T19:31:07.203+00:00 Discord

Just looked into it, my paragraphs aren't getting cached because of my use of WidgetSpan, unless it's the exact same Widget, the paragraph gets re-laid-out.

jonahwilliams2022-09-27T19:40:52.294+00:00 Discord

oh, well that seems like a footgun

jonahwilliams2022-09-27T19:41:02.89+00:00 Discord

do you mind filling a bug on that? We should figure out how to make that work...

goderbauer2022-09-27T22:15:42Z GitHub

@fzyzcjy I believe this just needs a test (or probably just a test exemption) to land.

fzyzcjy2022-09-27T22:37:19Z GitHub

@goderbauer I agree. Not sure how it can be tested though, so I will ask a test-exempt.

I will do it maybe a few days later since I am working on https://github.com/flutter/flutter/issues/101227 (and on discord), and do not want to have too many things on discord in one day :)

Thanks for your reply!

fzyzcjy2022-09-27T22:43:50.226+00:00 Discord

Good morning/evening friends! Three (small) PRs are created yesterday, with tests and green CI: https://github.com/flutter/flutter/pull/112436, https://github.com/flutter/flutter/pull/112437, https://github.com/flutter/engine/pull/36438. May I get a little bit review 🙂

fzyzcjy2022-09-28T05:10:55Z GitHub

Not sure whether I should "@" some people here, maybe @dnfield @jonahwilliams @gaaclarke @flar engine experts?

May I get a code review, thanks :)

fzyzcjy2022-09-28T05:43:21Z GitHub

@dnfield Hi, thanks for the quick reply :)

The documentation on FlutterView.render specifies when it is safe/allowed to call render.

I will change that doc accordingly (probably after we come to a conclusion what should be done for this PR)

I'm not quite clear on how this will affect the pipeline - it seems like it will now be trivial for a dart:ui application to flood the pipeline if we remove guardrails around when you can call render. Today the contract is that the application can expect that it's time to call render because it got a call to onBeginFrame. In this world, the application calls render whenever it thinks it has been working too long and might want to give an update. But the application doesn't know about vsync and it will be very hard to reason about why render is getting called if we make this change. I don't think we should make this change. It too easily allows wasted work to happen.

Firstly, IMHO, a normal flutter app calls window.render once per frame, so no problem at all for all existing app.

Secondly, in my proposal (Preempt for 60 FPS), I do observe vsync (using VsyncAwaiter class), and only submit one window.render per vsync. Therefore, "But the application doesn't know about vsync" seems not to be the situation, and thus "it will be very hard to reason about why render is getting called" is also no problem.

That said, I do agree that, if the rasterizer thread takes too long (e.g. takes 50ms for one rasterize), it is a waste to submit a Scene per 16.6ms (but should submit per 50ms).

If this is still a problem for you, can I change as follows: Add a flag to window.render, say, window.render({bool onlyRenderOncePerBeginFrame = true}). Then the behavior will be exactly the same, except for someone who really needs this (e.g. the Preempt proposal).

In addition, given it is a so low-level API that most people will never touch, it seems reasonable to provide some flexibility to it.

fzyzcjy2022-09-28T10:06:35Z GitHub

@dnfield Another possibility for Dart code to understand the queue is full so it do not do anything more:

Add this 4-line function:

// return: whether it is prepared successfully. If return false, it means pipeline is full, 
// and thus the user should not really compute the Scene to avoid unnecessary work.
bool Animator::PrepareExtraRender() {
if (!producer_continuation_) {
producer_continuation_ = layer_tree_pipeline_->Produce();
}
return static_cast<bool>(producer_continuation_);
}

usage:

realize_next_vsync_comes; // see the design for details https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit
var prepared = window.prepareExtraRender();
if (prepared) {
ui.Scene scene = compute_the_scene();
window.render(scene);
} else {
// do not do anything since the rasterizer queue is already so full
// this mimic the behavior of Animator::BeginFrame, where we skip the current frame if it is full
}
fzyzcjy2022-09-28T10:15:48Z GitHub

@dnfield ... be trivial for a dart:ui application to flood the pipeline ...

IMHO the pipeline seems not to be flooded - it has depth 2. In other words, even if we call window.render(scene) a million times within a frame, only the first two scenes will be in the queue, and the rest 999998 will just be thrown away (suppose rasterizer has not processed any). So we are still safe.

For a dart:ui application, if needed, it can use the window.prepareExtraRender extra call to see whether the queue is already full, to avoid generating scene etc (just like example above).

Today the contract is that the application can expect that it's time to call render because it got a call to onBeginFrame. In this world, the application calls render whenever it thinks it has been working too long and might want to give an update.

Just as mentioned above, adding a flag like window.render({bool onlyRenderOncePerBeginFrame = true}) seems to solve the "contract" problem.

fzyzcjy2022-09-28T10:16:30Z GitHub

Oops sorry @chinmaygarde and @iskakaushik I just clicked the "Icons.refresh" on dnfield and do not know why github remove review requests to you...

dnfield2022-09-28T17:36:04.263+00:00 Discord

Probably we can avoid it if we figure out the widget span has the same dimensions as last time... But If it won't or we can't figure it out the text layout may have changed.

dnfield2022-09-28T17:37:50.001+00:00 Discord

Reviews typically happen once per week during triage meetings. I've looked at some of these PRs already though and there seems to be some missing context here. These changes don't seem quite safe on their own, and it's still not clear to me what's the bigger picture app that they fix. I think we've talked about having a sample application or benchmark that shows what you're improving - is that available (even if it requires some special patches to the engine or framework to run, that's ok)

dnfield2022-09-28T17:38:10.321+00:00 Discord

I'll say right now though that, in their current form and without extra support, these patches are unlikely to land anytime soon.

fzyzcjy2022-09-28T23:07:06.604+00:00 Discord

Hi, for sample app, with video + full code + brief code + benchmark + analysis, please have a look at https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit#, the new "Experiments" chapter

fzyzcjy2022-09-28T23:10:08Z GitHub

Thanks, I did discussed with Hixie on discord and wrongly thought that public discussion was enough. I will fill all those contents in a few hours.

fzyzcjy2022-09-28T23:11:30.566+00:00 Discord

I will let it not be "in their current form" by providing extra support doc now 🙂 Will tell you when finished (probably in a few hours)

fzyzcjy2022-09-28T23:20:31.161+00:00 Discord

By the way, those two PRs are the most important (i.e. package cannot exist without them), so if you are busy please ignore my other PRs currently

dnfield2022-09-28T23:21:09.174Z Google Doc

Other things need time on this thread, for example GC.

dnfield2022-09-28T23:21:57.199Z Google Doc

I'm saying that a single layout function might take too long and your preempt call will come too late.

dnfield2022-09-28T23:27:19.384Z Google Doc

https://github.com/fzyzcjy/flutter_smooth/blob/master/packages/smooth/example/lib/main.dart#L165 is a good chunk of the hard part. I'm not really clear from this document how that would automatically get inserted in meaningful places without breaking a lot of things.

dnfield2022-09-28T23:27:39.927+00:00 Discord

Added a couple more comments.

fzyzcjy2022-09-28T23:28:24Z GitHub

@dnfield

What is the big picture that it fixes?

The ultimate goal is, let the app run smoothly at 60FPS, even if it has heavy subtree that is very slow to build/layout. In other words, the design doc: https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko

It already has a working demo. See the (new) "experiments" chapter, with a video, screenshots, full code, brief code, benchmark, analysis.

What's the main goal you're trying to achieve with this particular change?

In order to solve that big goal, we must let animation callbacks run at 60FPS even if the whole tree is very slow to build/layout. Otherwise, even if we refresh a subtree by 60FPS, anything like CircularProgressIndicator, FooTransition, or manual AnimationController will all never be smooth, because they do not see new timestamp at 60FPS.

Then, to fire (extra) animation callbacks, a natural solution is to work with the Tickers. Originally, Tickers are fired once per frame. But now, we also extra fire it (with proper timestamp) in each 60FPS smooth extra frame.

Lastly, to fire extra events to Tickers , we must know the existence of Tickers in the auxiliary widget subtree (no need for Tickers in the main subtree since they should not be fired at 60FPS). That is why I added a callback when Tickers are created - then I can record it such that to fire extra ticks.

Why is it doing it this way?

Why it is a compile time flag FLUTTER_ENABLE_TICKER_PROVIDER_STATE_MIXIN_CREATOR: Because Hixie is worried about performance loss (in Discord hackers-framework). Making it a compile time flag, then nobody will have any even tiny bit of performance loss, if they do not need this feature.

Why there is debugOverrideEnableTickerProviderStateMixinTickerCreator in addition to compile time flag: Because Hixie said compile time flags are hard to test. By using this debugOverride... flag we can test it easily (indeed I have written tests there).

Why a context must be passed to the callback: Because as mentioned above, I need to determine whether it is in the second subtree or main subtree.

Btw the name is temporary, just suggest any name you like :)

Why can't we used existing mechanisms to achieve the same thing?

Well, Hixie and I have tried, but cannot come up with a solution :/ Feel free to suggest solutions! Microscopic speaking, seems that cannot know a Ticker in a TickerProviderMixin is created so cannot gather them. Macroscopic speaking, did not find other ways to let it be smooth.

Why do we need this mechanism to achieve the larger goal of having incremental/progressive layout?

Hope this question is clear with above ;)

fzyzcjy2022-09-28T23:28:45.777+00:00 Discord

Reply done to GitHub "hooks" PR: https://github.com/flutter/flutter/pull/112436#issuecomment-1261568241

fzyzcjy2022-09-28T23:30:46.105Z Google Doc

I agree theoretically. But during my experiments, I see about 39% of the UI thread time is idle, and I guess GC does not need that much time. This screenshot: https://user-images.githubusercontent.com/5236035/190553863-5a373dcb-75ba-468d-8118-66e7a393070b.png

dnfield2022-09-28T23:30:57.852+00:00 Discord

I really appreciate your enthusiasm for this topic! But I'm still not sure I understand the big picture purpose of this method. You've explained some of the specifics about why you're guarding certain things the way you are, which isn't really what's unclear to me. What's unclear to me is why we want tickers to have an artificial way to fire an extra tick.

fzyzcjy2022-09-28T23:31:54.998Z Google Doc

Just add maybePreemptRender to that single layout function. For example:

class VeryHeavySingleLayout extends RenderObject { void performLayout() { compute_heavy_things_part_1; maybePreemptRender(); compute_heavy_things_part_2; maybePreemptRender(); ... compute_heavy_things_part_5; maybePreemptRender(); } }

dnfield2022-09-28T23:32:18.592+00:00 Discord

This is related to the engine PR concerns - it seems like we're struggling a bit to find the right way to express the concept of vsync/animation frame. The platform gives us a very clear signal, and I would like to avoid adding methods to the engine or framework to override that.

fzyzcjy2022-09-28T23:34:25.287Z Google Doc
  1. Insert a maybePreemptRender to RenderObject.layout function seems enough, without breaking anything if I think correctly.
  2. I also think about another possibility, just do it the way now it is. In other words, let the user manually specify preempt points via SmoothPreemptPoint. Then one less PR to framework, and user has more flexibility.
fzyzcjy2022-09-28T23:43:09.622+00:00 Discord

I really appreciate your enthusiasm for this topic! Thanks! 🙂 What's unclear to me is why we want tickers to have an artificial way to fire an extra tick. Because tickers originally fire once per full pipeline, in the animation phase. But now we want it to run extra ticks in the 60fps smooth extra frame. it seems like we're struggling a bit to find the right way to express the concept of vsync/animation frame. I do respect vsync, just using a way other than "be fired by onBeginFrame" (because when we are busy running dart code, the callback can never be fired again). Shortly speaking, I let VsyncAwaiter set a thread-shared variable about the last vsync data. Then in maybePreemptRender, I read that data. (Briefly speaking) if it is a new vsync, I realize it is time to create Scene and submit via window.render. Thus I submit once per vsync and respect vsync well. The platform gives us a very clear signal, and I would like to avoid adding methods to the engine or framework to override that. I would also like to make as few changes as possible, if it is possible 🙂 Not sure what "clear signal" mean, but if it means "the vsync signal", then hope my explanaions above solve the problem - I do respect the clear signal as well in another way

fzyzcjy2022-09-28T23:45:08.208+00:00 Discord

Btw, I did not add replies to https://github.com/flutter/engine/pull/36438 today since yesterday already add some and some questions seem also overlap with today.

Feel free to ask if there is anything missing!

dnfield2022-09-28T23:45:08.783Z Google Doc

In real applications under real workloads there is more need for GC time - for example, if your application is creating a lot of objects to understand data it fetched from the network or SQLite.

fzyzcjy2022-09-28T23:57:11Z GitHub

If we are worried that users may submit multiple window.render inside one vsync, another possibility: We may add some code in the C++ layer (or Dart layer), such that we check current vsync status, and only Produce() if it is a new vsync that has not been produced before.

fzyzcjy2022-09-28T23:57:12.678+00:00 Discord

If we are worried that users may submit multiple window.render inside one vsync, another possibility: We may add some code in the C++ layer (or Dart layer), such that we check current vsync status, and only Produce() if it is a new vsync that has not been produced before.

dnfield2022-09-28T23:58:18.8+00:00 Discord

Which patch is updating a vsync ready signal for dart:ui or the framework?

dnfield2022-09-28T23:58:23.85+00:00 Discord

The patches I've seen so far don't seem to do that.

fzyzcjy2022-09-28T23:58:44.408+00:00 Discord

No patch yet, because I was thinking to submit as few as possible

fzyzcjy2022-09-28T23:58:57.173+00:00 Discord

If you like it I can submit one, but that may not be tiny

fzyzcjy2022-09-29T00:00:42.639+00:00 Discord
  • Set a variable ("lastVsyncInfo") when VsyncAwaiter callback is fired
  • Dart can read that variable
fzyzcjy2022-09-29T00:02:35.051+00:00 Discord

The current possibly hard part for that potential PR: in android sdk>=29 and ios, seems that the callback of VsyncWaiter is fired on ui thread. But our Dart code is occupying the UI thread (for a long time) so that vsync callback may not be fired. For old android it does work well because it is fired in platform thread (my example is based on that)

fzyzcjy2022-09-29T00:03:03.188+00:00 Discord

To create the PR, I may need to move new-android and ios VsyncWaiter to platform thread as well.

fzyzcjy2022-09-29T00:06:53.439Z Google Doc

I agree. But seems that we need to compare two cases:

  1. Without this new proposal: Suppose one frame is 100ms, then we have busy UI thread for 100ms without idle. And then frame ends so we have idle.
  2. With this proposal: We still be busy for 100ms (+3% overhead so indeed 103ms), and then get idle. In addition, we have 6 extra smooth frames which may generate some object.

Thus, the difference with this proposal is that, the objects we create inside extra smooth frame do give GC extra pressure. But I hope that is small - they are just animations.

fzyzcjy2022-09-29T00:09:30.460Z Google Doc

Moreover, IMHO, my approach allows GC to happen, as long as it is less than 16ms stop-the-world and is not very unlucky.

For example, suppose GC happens during 17ms to 20ms. Then I can still build the layer tree and submit window.render at around 33.33ms. That stop-the-world GC does not cause any problem like visible jank. As long as we have about 0.5ms per 16.667ms, because 0.5ms is what we need (in experiments below) to produce an extra smooth frame.

On the contrary, existing methods may have jank in such cases. Because if GC runs for 3ms, they have 3ms less to compute the next scene.

dnfield2022-09-29T00:10:01.577+00:00 Discord

I'm not suggesting you create that PR right now, but having a working patch that shows what would need to be done, with some details about why this approach is being taken would help.

dnfield2022-09-29T00:10:16.462+00:00 Discord

For example, I'd expect your document to have a section on this explaining how it will work and what threading considerations are being made etc.

fzyzcjy2022-09-29T00:28:56.229+00:00 Discord

I see. I will add that (probably within a few hours) and come back when I am done.

fzyzcjy2022-09-29T01:19:09.325+00:00 Discord

That section is now written under "Get last vsync time information" doc section, with psuedo-code, threading concerns etc I will make a runnable code if the proposal about this vsync change looks interesting

fzyzcjy2022-09-29T02:44:43Z GitHub

AutomatedTestWidgetsFlutterBinding.pump provides wrong pump time stamp, probably because of forgetting the precision

The fix is just one line:

image

I have git blame and find the bug exist since the first version 7yr ago, and no special comments about why this is introduced so I guess it is but not feature.

IMHO the bug may be written like, the programmer wants to convert DateTime (the clock.now()) into a Duration. But then it is forgotten that both are microseconds precision instead of milliseconds precision, and the milliseconds approach is used.

List which issues are fixed by this PR. You must list at least one issue. Close #112610

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.


p.s. test fails with old code, confirming that the test is effective.

image image

fzyzcjy2022-09-29T06:21:11Z GitHub

Export elapseBlocking to test binding, so slow sync work can be simulated such as a slow widget build

There are needs to simulate sync heavy work, such as a slow widget build, in flutter widget tests. This method simply expose that.

As a remark, this cannot be replaced by runAsync. Consider the following example:

    testWidgets('can use to simulate slow build', (WidgetTester tester) async {
final DateTime beforeTime = binding.clock.now();

await tester.pumpWidget(Builder(builder: (_) {
bool timerCalled = false;
Timer.run(() => timerCalled = true);

binding.elapseBlocking(const Duration(seconds: 1));

// if we use `delayed` instead of `elapseBlocking`, such as
// binding.delayed(const Duration(seconds: 1));
// the timer will be called here. Surely, that violates how
// a flutter widget build works
expect(timerCalled, false);

return Container();
}));

expect(binding.clock.now(), beforeTime.add(const Duration(seconds: 1)));
binding.idle();
});

As is discussed in the comments in the example, if we use delayed, timers will be fired when executing half of a build function, which is totally wrong.

List which issues are fixed by this PR. You must list at least one issue. Close https://github.com/flutter/flutter/issues/112620

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

dnfield2022-09-30T18:11:41Z GitHub

Fizzling like that would be expensive, and we'd be giving developers a button to push that actually makes things slower. We should avoid that.

fzyzcjy2022-09-30T22:42:21Z GitHub

@dnfield If speed is a concern, maybe the original PR is ok: For a normal usage, it only adds if (!producer_continuation_) (and that if will return false immediately). Given that explicit operator bool() const { return continuation_ != nullptr; }, this if will only check whether a pointer is nullptr, so I guess it is only a few CPU cycles (per 16667 microseconds, i.e. maybe 10000000 cycles). In addition, for a normal usage, the branch will always be false, so IMHO the cpu branch predictor will be quite correct about the prediction.

fzyzcjy2022-09-30T22:51:53Z GitHub

@dnfield giving developers a button to push that actually makes things slower

There seems to be another way that is not very slower:

Firstly, the onlyRenderOncePerBeginFrame should not be window.render(onlyRenderOncePerBeginFrame: true), but be window.onlyRenderOncePerBeginFrame = true; window.render(). In other words, it should be a flag that is set once. Then the code is:

...
if (!onlyRenderOncePerBeginFrame && !producer_continuation_) {
producer_continuation_ = layer_tree_pipeline_->Produce();
}
...

void SetOnlyRenderOncePerBeginFrame(bool value) { this->onlyRenderOncePerBeginFrame = value; }
class Animator { ... bool onlyRenderOncePerBeginFrame; ... }

(no need for locks, since all on UI thread.)

Then there comes the concern that if (!onlyRenderOncePerBeginFrame && !producer_continuation_) can cost CPU cycles, even when onlyRenderOncePerBeginFrame is always true (for a classical user). Firstly, for a classical user, we only pay extra cost of if(boolean) (because && is short-circuited), so only a few cycles.

Secondly, seems we can use the [[likely]] (c++20), or LIKELY (a lot of c++ library write their own version for that, e.g. see [how linux])(https://stackoverflow.com/questions/109710/how-do-the-likely-unlikely-macros-in-the-linux-kernel-work-and-what-is-their-ben) does that), to further hint compiler about this case to speed up.

fzyzcjy2022-09-30T22:55:53Z GitHub

@dnfield And for zero speed decrease if you like:

The Animator::PrepareExtraRender proposal seems to cause zero speed loss for a classical user. Because a classical user never calls that function, and only call Animator::Render. But Animator::Render is not modified in this proposal. For a smooth user, there does exist overhead, because need to call one extra C++ function - the PrepareExtraRender.

fzyzcjy2022-09-30T23:01:48Z GitHub

Quick update (still WIP, just provide some progress): I am working on the gesture system. Jonah Williams has thought that, it was bad that my old proposal did not let the pointer data packet go through Flutter's gesture system. Now, the new method just calls the classical gestureBinding.handlePointerEvent to dispatch PointerMoveEvents.

fzyzcjy2022-10-01T01:18:23Z GitHub

Fix logic error in markNeedsPaint

The original code comment says:

If we're the root of the render tree (probably a RenderView), then we have to paint ourselves, since nobody else can paint us. We don't add ourselves to _nodesNeedingPaint in this case, because the root is always told to paint regardless.

However, IMHO it is wrong in two aspects.

Problem 1: RenderView does not come to this branch

Firstly, for a RenderView, it will not go into this branch, but instead go into the first branch (the if (isRepaintBoundary && _wasRepaintBoundary)). This is because RenderView.isRepaintBoundary is defined to be true, which can be seen in the code.

The experiment also confirms this. Click to expand below:

Details

Add a few logs:

image

Code:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('hello', (tester) async {
debugPrintBeginFrameBanner = debugPrintEndFrameBanner = true;

final dummy = ValueNotifier(0);
await tester.pumpWidget(ValueListenableBuilder(
valueListenable: dummy,
builder: (_, dummy, __) => _DummyInner(dummy: dummy),
));

dummy.value++;
await tester.pump();

debugPrintBeginFrameBanner = debugPrintEndFrameBanner = false;
});
}

class _DummyInner extends SingleChildRenderObjectWidget {
final int dummy;

const _DummyInner({
super.key,
required this.dummy,
super.child,
});


_RenderDummy createRenderObject(BuildContext context) =>
_RenderDummy(dummy: dummy);


void updateRenderObject(BuildContext context, _RenderDummy renderObject) {
renderObject.dummy = dummy;
}
}

class _RenderDummy extends RenderProxyBox {
_RenderDummy({
required int dummy,
RenderBox? child,
}) : _dummy = dummy,
super(child);

// not mark repaint yet
int get dummy => _dummy;
int _dummy;

set dummy(int value) {
if (_dummy == value) return;
_dummy = value;
print('hi ${describeIdentity(this)} set dummy thus markNeedsPaint START');
markNeedsPaint();
print('hi ${describeIdentity(this)} set dummy thus markNeedsPaint END');
}


void paint(PaintingContext context, Offset offset) {
print('hi ${describeIdentity(this)}.paint SUPPOSE THIS IS THE REAL PAINT');
super.paint(context, offset);
}
}

output

/Volumes/MyExternal/ExternalRefCode/flutter/bin/flutter --no-color test --machine --start-paused --plain-name hello --local-engine-src-path=/Volumes/MyExternal/ExternalRefCode/engine/src --local-engine=host_debug_unopt test/hello.dart
Testing started at 09:21 ...

hi RenderParagraph#d9227.markNeedsPaint start _needsPaint=true
hi RenderPositionedBox#d9bb5.markNeedsPaint start _needsPaint=true
hi RenderView#fab3a.markNeedsPaint start _needsPaint=true
hi flushPaint PipelineOwner#89028 node=RenderView#fab3a NEEDS-PAINT _needsPaint=true owner=PipelineOwner#89028 node._layerHandle.layer!.attached=true
hi TransformLayer#64092.buildScene
hi PictureLayer#149d1._addToSceneWithRetainedRendering _needsAddToScene=true
▄▄▄▄▄▄▄▄ Frame 2 0ms ▄▄▄▄▄▄▄▄
hi _RenderDummy#a17bc.markNeedsPaint start _needsPaint=true
hi RenderView#fab3a.markNeedsPaint start _needsPaint=false
hi RenderView#fab3a.markNeedsPaint case-repaintboundary owner=PipelineOwner#89028
hi flushPaint PipelineOwner#89028 node=RenderView#fab3a NEEDS-PAINT _needsPaint=true owner=PipelineOwner#89028 node._layerHandle.layer!.attached=true
hi _RenderDummy#a17bc.paint SUPPOSE THIS IS THE REAL PAINT
hi TransformLayer#64092.buildScene
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
▄▄▄▄▄▄▄▄ Frame 3 0ms ▄▄▄▄▄▄▄▄
hi _RenderDummy#a17bc set dummy thus markNeedsPaint START
hi _RenderDummy#a17bc.markNeedsPaint start _needsPaint=false
hi _RenderDummy#a17bc.markNeedsPaint case-parent parent=RenderView#fab3a
hi RenderView#fab3a.markNeedsPaint start _needsPaint=false
hi RenderView#fab3a.markNeedsPaint case-repaintboundary owner=PipelineOwner#89028
hi _RenderDummy#a17bc set dummy thus markNeedsPaint END
hi flushPaint PipelineOwner#89028 node=RenderView#fab3a NEEDS-PAINT _needsPaint=true owner=PipelineOwner#89028 node._layerHandle.layer!.attached=true
hi _RenderDummy#a17bc.paint SUPPOSE THIS IS THE REAL PAINT
hi TransformLayer#64092.buildScene
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
hi RenderParagraph#c63b7.markNeedsPaint start _needsPaint=true
hi RenderPositionedBox#2fb99.markNeedsPaint start _needsPaint=true
hi RenderView#fab3a.markNeedsPaint start _needsPaint=false
hi RenderView#fab3a.markNeedsPaint case-repaintboundary owner=PipelineOwner#89028
hi flushPaint PipelineOwner#89028 node=RenderView#fab3a NEEDS-PAINT _needsPaint=true owner=PipelineOwner#89028 node._layerHandle.layer!.attached=true
hi TransformLayer#64092.buildScene
hi PictureLayer#18f7f._addToSceneWithRetainedRendering _needsAddToScene=true

By looking at the experiment above, we see that, the RenderView goes to the case-repaintboundary which is the first branch, instead of the third branch, so the comments seem incorrect.

Problem 2: Root is not always told to paint indeed

Theoretically, I do not find clues why "root is always told to paint" indeed. Experimentically, this is also confirmed as below.

We change the branching condition as follows, so RenderView is forced to go to the 3rd branch (the branch with comments), instead of the 1st branch.

-     if (isRepaintBoundary && _wasRepaintBoundary) {
+ if (isRepaintBoundary && _wasRepaintBoundary && /*HACK!!!*/(this is! RenderView)) {

Then we run the test code same as above (only with a few more logging), and get:

Details
/Volumes/MyExternal/ExternalRefCode/flutter/bin/flutter --no-color test --machine --start-paused --plain-name hello --local-engine-src-path=/Volumes/MyExternal/ExternalRefCode/engine/src --local-engine=host_debug_unopt test/hello.dart
Testing started at 09:25 ...

hi RenderParagraph#c9872.markNeedsPaint start _needsPaint=true
hi RenderPositionedBox#f6896.markNeedsPaint start _needsPaint=true
hi RenderView#0fb85.markNeedsPaint start _needsPaint=true
hi flushPaint PipelineOwner#ce901 node=RenderView#0fb85 NEEDS-PAINT _needsPaint=true owner=PipelineOwner#ce901 node._layerHandle.layer!.attached=true
hi RenderView#0fb85.paint
hi TransformLayer#d7668.buildScene
hi PictureLayer#335b7._addToSceneWithRetainedRendering _needsAddToScene=true
▄▄▄▄▄▄▄▄ Frame 2 0ms ▄▄▄▄▄▄▄▄
hi _RenderDummy#3427b.markNeedsPaint start _needsPaint=true
hi RenderView#0fb85.markNeedsPaint start _needsPaint=false
hi RenderView#0fb85.markNeedsPaint case-else owner=PipelineOwner#ce901
hi TransformLayer#d7668.buildScene
hi PictureLayer#335b7._addToSceneWithRetainedRendering _needsAddToScene=false
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
▄▄▄▄▄▄▄▄ Frame 3 0ms ▄▄▄▄▄▄▄▄
hi _RenderDummy#3427b set dummy thus markNeedsPaint START
hi _RenderDummy#3427b.markNeedsPaint start _needsPaint=true
hi _RenderDummy#3427b set dummy thus markNeedsPaint END
hi TransformLayer#d7668.buildScene
hi PictureLayer#335b7._addToSceneWithRetainedRendering _needsAddToScene=false
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
hi RenderParagraph#66d6b.markNeedsPaint start _needsPaint=true
hi RenderPositionedBox#537a5.markNeedsPaint start _needsPaint=true
hi RenderView#0fb85.markNeedsPaint start _needsPaint=true
hi TransformLayer#d7668.buildScene
hi PictureLayer#335b7._addToSceneWithRetainedRendering _needsAddToScene=false

As we can see, RenderView.paint and RenderDummy.paint is only called once, even though we clearly call RenderDummy.markNeedsPaint. That is indeed a bug, and at least shows that the code comment is wrong - root is not always told to paint.


Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue. close #112736

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-01T02:05:20Z GitHub

[WIP][Do not merge this PR] Tentative experiment to see how to fix logic error about skippedPaintingOnLayer

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-01T02:21:09Z GitHub

All right, this should not be the fix

fzyzcjy2022-10-02T01:11:34Z GitHub

Add warning that RenderRepaintBoundary.toImage and OffsetLayer.toImage is slow

Scene.toImage has doc saying: "This is a slow operation that is performed on a background thread". However, people may use RenderRepaintBoundary.toImage and OffsetLayer.toImage and never read that comment, so they are unaware of the slowness. This PR simply adds the warning to them.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-02T01:33:27Z GitHub

Update comments that seem to contradict the code and may confuse the reader

Original comment:

... So we flatten the layer tree into a picture and use that as the thread transport mechanism.

However, looking at the whole code:

Details
Dart_Handle Picture::RasterizeToImage(sk_sp<DisplayList> display_list,
std::shared_ptr<LayerTree> layer_tree,
uint32_t width,
uint32_t height,
Dart_Handle raw_image_callback) {
if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) {
return tonic::ToDart("Image callback was invalid");
}

if (width == 0 || height == 0) {
return tonic::ToDart("Image dimensions for scene were invalid.");
}

auto* dart_state = UIDartState::Current();
auto image_callback = std::make_unique<tonic::DartPersistentValue>(
dart_state, raw_image_callback);
auto unref_queue = dart_state->GetSkiaUnrefQueue();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner();
auto snapshot_delegate = dart_state->GetSnapshotDelegate();

// We can't create an image on this task runner because we don't have a
// graphics context. Even if we did, it would be slow anyway. Also, this
// thread owns the sole reference to the layer tree. So we flatten the layer
// tree into a picture and use that as the thread transport mechanism.

auto picture_bounds = SkISize::Make(width, height);

auto ui_task =
// The static leak checker gets confused by the use of fml::MakeCopyable.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
fml::MakeCopyable([image_callback = std::move(image_callback),
unref_queue](sk_sp<DlImage> image) mutable {
auto dart_state = image_callback->dart_state().lock();
if (!dart_state) {
// The root isolate could have died in the meantime.
return;
}
tonic::DartState::Scope scope(dart_state);

if (!image) {
tonic::DartInvoke(image_callback->Get(), {Dart_Null()});
return;
}

if (image->skia_image()) {
image =
DlImageGPU::Make({image->skia_image(), std::move(unref_queue)});
}

auto dart_image = CanvasImage::Create();
dart_image->set_image(image);
auto* raw_dart_image = tonic::ToDart(std::move(dart_image));

// All done!
tonic::DartInvoke(image_callback->Get(), {raw_dart_image});

// image_callback is associated with the Dart isolate and must be
// deleted on the UI thread.
image_callback.reset();
});

// Kick things off on the raster rask runner.
fml::TaskRunner::RunNowOrPostTask(
raster_task_runner,
[ui_task_runner, snapshot_delegate, display_list, picture_bounds, ui_task,
layer_tree = std::move(layer_tree)] {
sk_sp<DlImage> image;
if (layer_tree) {
auto display_list = layer_tree->Flatten(
SkRect::MakeWH(picture_bounds.width(), picture_bounds.height()),
snapshot_delegate->GetTextureRegistry(),
snapshot_delegate->GetGrContext());

image = snapshot_delegate->MakeRasterSnapshot(display_list,
picture_bounds);
} else {
image = snapshot_delegate->MakeRasterSnapshot(display_list,
picture_bounds);
}

fml::TaskRunner::RunNowOrPostTask(
ui_task_runner, [ui_task, image]() { ui_task(image); });
});

return Dart_Null();
}

It seems that, the layer_tree is directly moved into raster_task_runner callbacks. Then, inside the raster thread, layer_tree->Flatten is called and it is converted to a DisplayList. In other words, the "thread transport mechanism" seems to be the layer_tree (ui -> raster thread) and DlImage (raster -> ui thread), instead of the "flatten the layer tree into a picture and use that" (the flattened layer tree, i.e. the picture).

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-02T01:33:30Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-02T01:34:31Z GitHub

This only updates comments so seems no need for tests

fzyzcjy2022-10-02T01:46:48Z GitHub

@jonahwilliams Hi thanks for the reply.

  • If it is slow, IMHO the users need to know it, otherwise users may abuse it because they may think it is just a normal function.
  • If it is fast, then we need to remove the original comment (because it is outdated).
  • As for impl specific, if it is slow but in the future it becomes fast (hopefully!), seems that we can update comments at that time.
fzyzcjy2022-10-02T01:50:25Z GitHub

Anyway this is just a small doc change and it does not matter whether it is changed or not for myself (since I already know it is slow and should be careful). I have spent some making this PR simply because I hope other users works correctly with the api :)

fzyzcjy2022-10-03T03:53:35Z GitHub

Minor change type nullability

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

dnfield2022-10-03T16:24:41Z GitHub

You're discounting the time it takes to actually make a native call from Dart.

On top of that, we should not expose an API that might or might not do something and developers have no good way to reason about whether they're really supposed to call it or not.

This proposal is fundamentally changing the invariants around render/onBeginFrame, but it's not providing any way for developers to know if they're using the new invariants correctly or not. Even if the new potentially useless API is relatively cheap, it adds up when developers (and packages they use) start doing it multiple times per frame. And those developers/packages will have no way to know whether they're doing it correctly or not, so it will definitely get misused.

Why, for example, shouldn't the framework just call render and schedule a new frame when its hit its potential limit?

fzyzcjy2022-10-03T23:26:28Z GitHub

@dnfield Hi thanks for the reply.

You're discounting the time it takes to actually make a native call from Dart.

Originally I thought that is small just like a normal function call... Ok now I learn it.

On top of that, we should not expose an API that might or might not do something and developers have no good way to reason about whether they're really supposed to call it or not.

Indeed they have a way to reason: look at time or vsync info. They should not submit twice inside one vsync interval.

This proposal is fundamentally changing the invariants around render/onBeginFrame, but it's not providing any way for developers to know if they're using the new invariants correctly or not.

I am not sure, if I expose the vsync info and ensure only one call is made per vsync interval (16.67ms), does this satisfy your requirement?

Even if the new potentially useless API is relatively cheap, it adds up when developers (and packages they use) start doing it multiple times per frame.

Again, as mentioned above, dev should not call it multiple times per frame. A naive dev may use DateTime.now() - last_vsync_time > 15ms etc to check, and a more sophisticated way may be read the vsync info (exposed from engine) to really ensure we never call twice per vsync interval.

And those developers/packages will have no way to know whether they're doing it correctly or not, so it will definitely get misused.

Then maybe we should return bool to indicate whether it is really scheduled. If they see a lot of false they are doing it wrong (call too many times that are useless).

Why, for example, shouldn't the framework just call render and schedule a new frame when its hit its potential limit?

Because of the fundamental design of the preempt proposal (https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit), mainly "The flow chart" section.

Indeed, window.render is called per vsync interval (16.67ms). The main difference from classical code is that, it may be called multiple times per onBeginFrame (when onBeginFrame is super slow).

dnfield2022-10-03T23:52:26Z GitHub

Devices do not always have 60fps vsync - sometimes it's 90 or 120 or more or less (at one point we had a customer looking at 240hz devices, and it's likely there are customers out there looking at 30hz use cases). There is no way currently in dart:ui to know what the current refresh rate is, and on some platforms it's not even possible to implement because the vendors don't provide an API for it (e.g. some Android vendors), and it can change from frame to frame.

In other words, a developer must not assume that 16.67ms is the right interval for a frame in all circumstances. And the query of DateTime.now is almost certain to not match the actual vsync start time, so if you assume you have roughly 16ms from onBeginFrame you might actually overshoot vsync.

fzyzcjy2022-10-03T23:57:18Z GitHub

Devices do not always have 60fps vsync - sometimes it's 90 or 120 or more or less (at one point we had a customer looking at 240hz devices, and it's likely there are customers out there looking at 30hz use cases). There is no way currently in dart:ui to know what the current refresh rate is, and on some platforms it's not even possible to implement because the vendors don't provide an API for it (e.g. some Android vendors), and it can change from frame to frame.

Definitely! That's why I also propose to expose vsync-related information to the dev. Last week you asked me to describe it and it was at "Get last vsync time information" section of google doc.

And the query of DateTime.now is almost certain to not match the actual vsync start time, so if you assume you have roughly 16ms from onBeginFrame you might actually overshoot vsync.

Totally agree. Indeed in my (previous) implementation, I let the C++ side expose the timeStamp (i.e. vsync target time we provide to dart tonBeginFrame) both a "Duration" and a "DateTime-compatible time". If you like I can add that back (deleted it b/c want to make PR small).

fzyzcjy2022-10-03T23:58:56Z GitHub

P.S. I am also considering relaxing when to start a onBeginFrame which seems to reduce unnecessary idle time and improve performance. That may be related to the big picture you are concerned - how vsync and code are interacted. I will add it to design doc and reply here maybe in an hour.

fzyzcjy2022-10-04T00:34:41Z GitHub

@dnfield Here it goes: "Relax onBeginFrame starting criterion" section in https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit

dnfield2022-10-04T16:23:59Z GitHub

I think it would be easier to start with a patch that exposes more about vsync timings to the developer, because that will be critical to whether this one makes sense.

fzyzcjy2022-10-04T23:05:16Z GitHub

Thanks, I will do that.

fzyzcjy2022-10-05T00:07:09Z GitHub

Expose vsync information to developer

This PR tries to expose vsync information to the developer, so they can know when it is proper to call the more un-restricted window.render proposed in #36438.

Currently only the API is shown, because IMHO the implementation details is unrelated to thoughts about #36438, and the API (and therefore implementations) are subject to changes. I will continue working on it, once the API is approved.

For detailed design about this API and its implementation, please have a look at "Get last vsync time information" section of https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit.

Sample usage:

final info = SchedulerBinding.instance.lastVsyncInfo();

List of work:

  • code the (draft) Dart API
  • discuss whether the API is acceptable
  • implement the C++ part on SDK<=29 Android
  • implement the C++ part on new android, ios, and other platforms
  • create a wrapper function in flutter/flutter repo, probably in SchedulerBinding

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-05T00:16:04Z GitHub

@dnfield Hi, PR is here: https://github.com/flutter/engine/pull/36607

Only the API is there currently, because IMHO the implementation details is unrelated to thoughts about this issue, and the API (and therefore implementations) are subject to changes.

fzyzcjy2022-10-06T00:16:57Z GitHub
| 00:15 +36: /b/s/w/ir/x/t/flutter_customer_testing.flutter_packages.RNUBSQ/tests/packages/animations/test/open_container_test.dart: Container closes - Fade (by default)
| ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
| The following TestFailure was thrown running a test:
| Expected: 1.0 (±1e-10)
| Actual: <0.9999833333333332>
| Which: 0.9999833333333332 is not in the range of 1.0 (±1e-10).
|
| When the exception was thrown, this was the stack:
| #4 main.<anonymous closure> (file:///b/s/w/ir/x/t/flutter_customer_testing.flutter_packages.RNUBSQ/tests/packages/animations/test/open_container_test.dart:273:7)
| <asynchronous suspension>
| <asynchronous suspension>
| (elided one frame from package:stack_trace)
|
| This was caught by the test expectation on the following line:
| file:///b/s/w/ir/x/t/flutter_customer_testing.flutter_packages.RNUBSQ/tests/packages/animations/test/open_container_test.dart line 273
| The test description was:
| Container closes - Fade (by default)
| ════════════════════════════════════════════════════════════════════════════════════════════════════
|
| 00:15 +37 -1: /b/s/w/ir/x/t/flutter_customer_testing.flutter_packages.RNUBSQ/tests/packages/animations/test/fade_scale_transition_test.dart: FadeScaleTransitionConfiguration builds a new route
| 00:15 +37 -1: /b/s/w/ir/x/t/flutter_customer_testing.flutter_packages.RNUBSQ/tests/packages/animations/test/open_container_test.dart: Container closes - Fade (by default) [E]
| Test failed. See exception logs above.
| The test description was: Container closes - Fade (by default)
fzyzcjy2022-10-06T00:17:28Z GitHub

@pdblasi-google I guess maybe need to update the custom testing configurations to point to the latest tests?

pdblasi-google2022-10-06T18:20:51Z GitHub

@fzyzcjy Yup, you called it. Apologies, I forgot to point the flutter/tests repo to the latest tests. PR is up for that now, I'll ping here when it goes through.

flutter-dashboard2022-10-06T20:44:21Z GitHub

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #112609 at sha 02ebd54c3343d0c3aaabcba423b5db13b2bfaadb

fzyzcjy2022-10-06T23:22:52Z GitHub

Thanks and 🎉 !

goderbauer2022-10-10T17:51:11Z GitHub

@fzyzcjy Did you have a chance to look into testing this or applying for the test exception?

goderbauer2022-10-10T18:29:29Z GitHub

Any reason this is still marked as Draft?

This will need a testing exception.

goderbauer2022-10-10T18:31:24Z GitHub

I agree that this comment alone is not particularly useful. To be useful it would need more context so developers can actually make an informed decision of whether they want to use this or not. Let's close this for now.

goderbauer2022-10-10T18:35:45Z GitHub

This patch is missing a lot of context information. To quote Dan from the other patch:

This patch needs a lot more context. What's the main goal you're trying to achieve with this particular change? Why is it doing it this way? Why can't we used existing mechanisms to achieve the same thing? Why do we need this mechanism to achieve the larger goal of having incremental/progressive layout?

Regardless, this doesn't seem like a great API to provide a single static callback that gets called on every layout. What if multiple implementations are trying to set this? Also, this gets called for the layout of every single RenderObject. This doesn't sound great for performance. I know, the title claims it has no overhead, but I find that hard to believe. What's the basis for that claim?

fzyzcjy2022-10-10T23:08:38Z GitHub

Any reason this is still marked as Draft?

No, I just forgot it :)

This will need a testing exception.

I think so, thanks

Hixie2022-10-10T23:11:41Z GitHub

test-exempt: API refactor

fzyzcjy2022-10-10T23:14:18Z GitHub

@Hixie So shall I relax the code visibility of the widgets and render objects (i.e. change from private to public) in order to have a test? If so I will add it.

fzyzcjy2022-10-10T23:15:27Z GitHub

@goderbauer Ah I forgot it completely. Here is the exemption request a few seconds ago: https://discordapp.com/channels/608014603317936148/608018585025118217/1029170238190784704

fzyzcjy2022-10-10T23:18:30Z GitHub

Please ignore this PR for now, since in the https://github.com/fzyzcjy/flutter_smooth (i.e. impl of https://docs.google.com/document/d/1FuNcBvAPghUyjeqQCOYxSt6lGDAQ1YxsNlOvrUx0Gko/edit#), I am trying to use manual widgets as a workaround. So skip it is you are busy :)

This doesn't sound great for performance. I know, the title claims it has no overhead, but I find that hard to believe. What's the basis for that claim?

I mean zero overhead when it is disabled (which IIRC is what hixie(?) cares about a lot).

Compiler explorer says it is zero overhead b/c the compiler just correctly understands it and eliminate the dead code: https://discord.com/channels/608014603317936148/608021234516754444/1024141725024923688

fzyzcjy2022-10-11T12:33:06.463+00:00 Discord

I am still working on the "Preemption for 60FPS", i.e. flutter_smooth, currently. Just having this (very rough) idea and want to share it here https://github.com/flutter/flutter/issues/113281

fzyzcjy2022-10-11T12:40:20.881+00:00 Discord

(Spoiler: It tries to solve the hot update problem)

Hixie2022-10-11T18:19:00Z GitHub

test-exempt: code refactor with no semantic change

fzyzcjy2022-10-12T14:04:46Z GitHub

Quick update: ListView scrolling at 60FPS with heavy build/layout

Highlights:

  • It is 60FPS (check via splitting video into frames, and by my script to examine timeline tracing data; not checked this demo video though; you can find the script in my repo)
  • The list shifting is (roughly) uniform speed (up to error from OS pointer events) (check via script to examine timeline tracing data; again script is in my repo)
  • The system uses gestureBinding.handlePointerEvent to dispatch PointerMoveEvents

Experiment setup: Slow build/layout when new item comes in. Full code can be seen in https://github.com/fzyzcjy/flutter_smooth.

May still contain (a lot of) bugs, since it is still WIP :)

Video (firstly raw case, then use-flutter_smooth case):

https://user-images.githubusercontent.com/5236035/195363841-240fa44c-c471-412e-9c3d-3314cf6ed8ea.mp4

Sample screenshots from tracing and my script:

image image

fzyzcjy2022-10-12T14:06:16.429+00:00 Discord

Hi guys, quick update:

fzyzcjy2022-10-12T14:06:27.301+00:00 Discord

ListView scrolling at 60FPS with heavy build/layout

Highlights:

  • It is 60FPS (check via splitting video into frames, and by my script to examine timeline tracing data; not checked this demo video though; you can find the script in my repo)
  • The list shifting is (roughly) uniform speed (up to error from OS pointer events) (check via script to examine timeline tracing data; again script is in my repo)
  • The system uses gestureBinding.handlePointerEvent to dispatch PointerMoveEvents

Experiment setup: Slow build/layout when new item comes in. Full code can be seen in https://github.com/fzyzcjy/flutter_smooth.

May still contain (a lot of) bugs, since it is still WIP 🙂

fzyzcjy2022-10-12T14:07:54.564+00:00 Discord

Video (click to see):

https://github.com/flutter/flutter/issues/101227#issuecomment-1276239303

fzyzcjy2022-10-12T14:08:12.932+00:00 Discord

image

CaseyHillers2022-10-14T01:40:05Z GitHub

@fzyzcjy @goderbauer this is a breaking change. Can a migration guide be written on how developers can migrate their code with this change? I'm not sure what's needed on my end as a developer, and my animation tests are now very flaky.

fzyzcjy2022-10-14T01:44:06Z GitHub

@CaseyHillers Hi,

Can a migration guide be written on how developers can migrate their code with this change? ...and my animation tests are now very flaky.

Could you please share some flaky test minimal reproducible samples? IMHO this should not make any problem so want to see reproductions in order to know what happens

CaseyHillers2022-10-14T01:57:31Z GitHub

Here's an example now that is flaky:

      await tester.pumpWidget(myAnimatedWidget);
await tester.pumpAndSettle();

await tester.sendSelectEvent();

await tester.pumpFrames(scene, Duration(milliseconds: 100));
await expectLater(
find.byType(MyAnimatedWidget),
matchesGoldenFile(
'animated_widget'));

The resulting goldens are changing. When I change pumpFrames to microseconds, I am still seeing the same flakiness. I'm unsure if it's because the earlier clocks are still in millisecond mode.

fzyzcjy2022-10-14T02:00:52Z GitHub

@CaseyHillers Weird. Some possible ideas:

  1. Do you have anything that depends on e.g. a real clock? If so, it will be flaky. (I guess no)
  2. Could you please change MyAnimatedWidget to something like AnimatedBuilder(builder: (_, value) => Text('the value is: $value'). Then, when the golden is changing, we can know what value indeed it is having.

I'm unsure if it's because the earlier clocks are still in millisecond mode.

Do you mean the new golden (i.e. after the PR) are different from old golden, and the new golden is itself stable? If so, looks like it is possible. Indeed the animation controller is fed with a changed animation time.

CaseyHillers2022-10-14T02:22:06Z GitHub

My animated widgets are just an animated builder that has a custom animation controller. @goderbauer or @pdblasi-google can help with reproducing the issue here.

My understanding is I need to change every possible clock to be in microseconds instead of milliseconds. This seems to be a breaking change for any other customers, and I'm having a difficult time tracking all the various clocks in my codebase.

I haven't found the discord threads, but I wonder if this is something that should be in the framework. There could be a tester field added for high precision or this can be added directly to your package. I assume most Flutter tests aren't needing high precision, and this is going to cause a lot of pain once it's in beta/stable.

fzyzcjy2022-10-14T02:24:29Z GitHub

@CaseyHillers

I need to change every possible clock to be in microseconds instead of milliseconds

Btw I am curious why a clock can in milliseconds in codebase - clock package, Duration, DateTime, etc are all in microseconds.

I haven't found the discord threads, but I wonder if this is something that should be in the framework. There could be a tester field added for high precision or this can be added directly to your package. I assume most Flutter tests aren't needing high precision, and this is going to cause a lot of pain once it's in beta/stable.

I agree that an alternative solution is to use a bool flag to enable high-accuracy (I have done that indeed - https://github.com/flutter/flutter/pull/112609/commits/e0b5882b87bc8fe025d55d626ec663c8587522b7).

CaseyHillers2022-10-14T11:38:10Z GitHub

I agree that an alternative solution is to use a bool flag to enable high-accuracy (I have done that indeed - https://github.com/flutter/flutter/commit/e0b5882b87bc8fe025d55d626ec663c8587522b7).

Thanks, that sounds like it would work for me! Are there plans to upstream that change?

fzyzcjy2022-10-14T11:39:13Z GitHub

@CaseyHillers I am ok with that, just not sure what other reviewers think? (Since that was my original design and later a reviewer suggests me to change to what is current merged.) If you guys are OK I will PR it.

fzyzcjy2022-10-14T11:46:19Z GitHub

Reland AutomatedTestWidgetsFlutterBinding.pump provides wrong pump time stamp, probably because of forgetting the precision, via optional flag

This relands https://github.com/flutter/flutter/pull/112609, but with a flag that is off by default. The reason why it is designed like this can be found in discussions around https://github.com/flutter/flutter/pull/112609#issuecomment-1278889389.

Close #112610

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-14T11:48:14Z GitHub

@CaseyHillers Here is the PR: https://github.com/flutter/flutter/pull/113433

pdblasi-google2022-10-14T17:25:00Z GitHub

@fzyzcjy @CaseyHillers @goderbauer

I still think that this change should be made without the boolean flag. The underlying issue with the goldens is that pumpFrames defines interval with microsecond precision, but the AutomatedTestWidgetsFlutterBinding didn't support microsecond precision. @Piinks and I went over a couple of golden changes in the flutter repos test as well and accepted the changes to those goldens as they were a change to a correct state (which is why the goldens are current hanging for this PR).

The secondary issue that makes this difficult to break cleanly is that LiveWidgetsFlutterBinding does support microsecond precision, so we can't just update pumpFrames' interval parameter to be a clean 16 milliseconds by default, as that would break other tests.

To start with, the first paragraph in the breaking changes process says:

Sometimes, however, doing this is necessary for the greater good. We want our APIs to be intuitive; if being backwards-compatible requires making an API into something that we would never have designed that way unless forced to by circumstances, then we should instead break the API and make it good.

Adding a boolean to make the correct behavior happen is not an API that we would have designed on purpose. It's also not a change that we can easily guide people to using, as there's no "new API" we can drive them towards with deprecations or data driven fixes. It'd be something we introduce for a period of time, hope people read the blog, then end up running into the same "breaking change" issues when we eventually remove the boolean or default it to true.

From there, digging into the process, the preferred process is the three step process:

  1. Add new API and opt in to the new API
  2. Remove the old API
  3. Remove the opt in

I think the only way we can get that to happen is to introduce a new version of pumpFrames that would support the correct behavior, deprecate the current pumpFrames, then eventually remove pumpFrames. Here's what I'd propose:

  • Fix AutomatedTestWidgetsFlutterBinding without the flag
  • Introduce a new method with the exact contents that pumpFrames currently has:
pumpFramesFor(
Widget target,
Duration duration, [
Duration interval = const Duration(milliseconds: 16, microseconds: 683),
])
  • Update pumpFrames to pass through to the new pumpFramesFor method:
    • Check if (binding is AutomatedTestWidgetsFlutterBinding)
    • If it is, then truncate the microseconds off of interval before passing through to maintain the current incorrect behavior

My biggest concern with this approach is that pumpFramesFor isn't as clean a name as just pumpFrames, but it's the best I can come up with. Names aside, introducing a new method and deprecating the old is the only way I can think of to keep the existing behavior and actually be able to drive users to the new api before landing the correct behavior.

fzyzcjy2022-10-14T23:08:45Z GitHub

This is an artifact of what the code used to do, but it has since been refactored to not do that :)

I guess so :) That is why I update it - otherwise it will mislead future readers

fzyzcjy2022-10-14T23:13:36Z GitHub

@pdblasi-google @CaseyHillers @goderbauer I agree with both sides of the opinion, both looks very reasonable to me. So just ping me when googlers reach a conclusion that what I should do!

fzyzcjy2022-10-15T02:11:34Z GitHub

Fix wrong VSYNC event

Firstly, we know VSYNC event in timeline is special - chrome://tracing tool will show "zebra" colors (gray and white) at every VSYNC. Therefore, it is critical to let this event have correct timing, otherwise every user is doing reasoning with the wrong vsync time.

In the image below, the "a" shows the new VSYNC with this PR, while the "b" shows the old VSYNC interval before this PR. As we can see, the left side of "a" and "b" does not coincide. In other words, before this PR (where we have "b" as the VSYNC and there is no "a"), we consider the wrong time as the vsync time.

The cause is quite simple: Before this PR, the left edge of "VSYNC" event is (for example) the call time of VsyncWaiterAndroid::OnVsyncFromJava. However, there exist "frame delay" (e.g. frameDelayNanos argument in OnVsyncFromJava), so the real vsync time should minus that delay.

As a side remark, in the image below the difference is not very much, but in real scenarios, I have seen once in a while it has large differences. Then you know, the visualization goes wild, and it took me some time before I realized, it is not a bug in code anywhere, but a bug of the VSYNC event time.

image

Close #113475

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-15T02:26:29Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-17T11:55:09Z GitHub

Allow disable report timing in profile build since it takes not-negligible amount of time

Flutter does say the time cost is "less than 0.1ms every 1 second to report the timings measured on iPhone6S". However, not every mobile phone is as high-end as iPhone6S. For example, on my testing device (TRT-AL00, indeed not the lowest-end device!), I measured that it takes about 20-30ms per second. Then we have a problem. When having https://github.com/fzyzcjy/flutter_smooth, we know a big janky frame (say, takes 200ms) will never let user really feel janky, but instead user will see the app being 60FPS smooth. However, this is based on the assumption that misc work such as report timings should not block the UI thread for a continuous period of time - which is not true if report timings happens. After the 200ms janky frame, we see about 6ms of report timing. Among with other things such as dispatch touch events, they easily take up more than ~16ms and we get one jank. Then flutter_smooth is no longer smooth due to the jank.

Except for the case of flutter_smooth, IMHO this PR is also useful for normal Flutter users. It takes 2-3% of CPU time, which is not negligible and may be measured. In addition, this is not a critical feature. Surely, when this is disabled, the DevTool will not show the frame ui/rasterizer time at all. However, not everyone needs to read that timing data, since they may either do not open DevTool, or use the tracing timeline instead (which contains more than enough information to know the frame timing data). Therefore, it looks reasonable to at least give users a chance (i.e. a flag) to disable it.

The code is deliberately written by reading a const bool environment variable. Therefore, it has completely zero overhead. I have confirmed that by using compiler explorer before - https://discordapp.com/channels/608014603317936148/608021234516754444/1024141682377236500.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-17T12:33:38Z GitHub

Expose NotifyIdle from RuntimeController to Dart, allowing flutter_smooth to get 60FPS, even if GC needs to run for 14ms per 16.67ms

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

The PR simply exposes RuntimeController::NotifyIdle to the dart layer.

It is necessary for https://github.com/fzyzcjy/flutter_smooth because of the following commonly seen scenario: Suppose we are in a janky frame (say it takes 200ms). Then, NotifyIdle is never called at all, because it is usually called at the end of DrawFrame (indeed, more exactly, in the Animator::AwaitVSync). Then, during the 200ms, garbage accumulates, and at one time the young generation is full, then Dart VM must stop the world and make a GC. From my experiments, such GC can even take 20ms on my testing device. Stop the world for 20ms - then we must miss one frames, causing non-60FPS. Even if the stop-the-world GC is fast, say, 5ms, it can still cause a jank. For example, when it happens at 97.5ms-102.5ms, then the preemptRender which should originally be done near 98-100ms can only be done at 105ms, so it calls window.render too late, thus the rasterizer may fail to rasterize the frame before the 116.67ms vsync, so there is a jank. (If needed, I can draw a figure).

However, with this PR, there is no such problem at all. The flutter_smooth will call NotifyIdle immediately after each and every preemptRender, with a deadline of roughly 14ms (16.67ms minus a few ms). By doing this, there are two benefits. Firstly, since the heap is not that full, GC can finish its work sooner instead of the 20ms bad case when the heap is really full. This avoids the 20ms-long-GC problem above. Secondly, since we actively tell Dart VM that it can start a GC at this time, GC can run for a time duration as long as ~14ms without causing any jank. This is contrary to the discussion above, where even a 5ms GC can cause a frame jank. As for why it can run 14ms without causing trouble, it is because, suppose we start it at 100ms and it runs 14ms, then we are now at 114ms, and we start preemptRender. Since preemptRender is really fast (e.g. 2ms), we will submit window.render at 116ms. In other words, we submit window.render with sufficient time left for rasterizer to finish its job - as long as rasterizer finishes its job before 133.33ms, no jank will happen.

Therefore, the title is explained well: It allows flutter_smooth to get 60FPS, even if GC needs to run for 14ms per 16.67ms. (That extreme GC will not happen in real world, I just want to say this proposal works even for that.)

I have already done that for my engine branch and ran experiments on flutter_smooth. It works pretty well - originally I observe GC-caused janks and then they disappear after this fix. If you are interested I can present some data.

As for tests: Have not found a way to add tests for this very simple calling, may need a test exempt.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-17T12:33:41Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Hixie2022-10-17T22:40:09Z GitHub

Tests can actually get to the private classes, they just can't use the private class name. You can cast the render object to dynamic and can call things on it blindly.

fzyzcjy2022-10-17T23:02:26Z GitHub

@Hixie Thanks, but the problem is I cannot construct a _CupertinoDialogRenderWidget object. All its current usages does not cause this bug (but future usages may do), while this is a logical bug indeed (since by definition updateRenderObject should update the fields).

fzyzcjy2022-10-17T23:04:19Z GitHub

So... what should I do?

Hixie2022-10-17T23:06:40Z GitHub

Ah, I see. Yeah, that's unfortunate. Probably fine to skip the test for now then.

test-exempt: not technically changing actual behaviour.

fzyzcjy2022-10-17T23:19:06Z GitHub

Fix 1-char typo

Well, just 1-char typo that I come across...

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-17T23:19:09Z GitHub

This pull request was opened against a branch other than main. Since Flutter pull requests should not normally be opened against branches other than main, I have changed the base to main. If this was intended, you may modify the base back to master. See the Release Process for information about how other branches get updated.

Reviewers: Use caution before merging pull requests to branches other than main, unless this is an intentional hotfix/cherrypick.

fzyzcjy2022-10-17T23:41:12Z GitHub

Eliminate duplicated code when dealing with pointer data

Hope the PR is self-explanatory :) If needed I can explain it.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

pdblasi-google2022-10-17T23:41:19Z GitHub

@fzyzcjy

If you're alright with it, I'd like to grab the issue from you to wrap it up. We'll need to make some changes internally and do some extra documentation to release this due to the internal test failures. If you'd like, I'll guide you through the extra docs and just handle the internal stuff myself, but I think it'll be easier with just one person working to get this landed.

fzyzcjy2022-10-17T23:42:16Z GitHub

@pdblasi-google Sure :) so shall I close this issue now?

pdblasi-google2022-10-17T23:43:11Z GitHub

This PR yes. I'll still work off of the original issue so we can keep the wonderful history of this surprisingly complex issue! 😛

fzyzcjy2022-10-17T23:44:15Z GitHub

@pdblasi-google Looking forward to see it landed!

fzyzcjy2022-10-18T02:52:56Z GitHub

Speed up pointer data packet dispatching by roughly 2x when multiple packets come

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

Benefits for Flutter (without considering flutter_smooth)

Multiple pointer data packets often arrive in one vsync interval. Currently, each of them requires a PostTask, C++-to-Dart-call, etc. However, when there is already one pending PostTask and a second data packet arrives, we can optimize it - no need to schedule a second PostTask and C++-to-Dart call, but instead utilize the pending PostTask and submit more data inside one call.

How much speed up does it give: Consider the following screenshot (It happens after a long janky frame, but serves pretty well for us to compute numbers because it contains a lot of items - the average measure error will be much smaller). As we can see,

  • total wall time: ~8.2ms
  • total Dart time (measured by the _handlePointerDataPacket time, which is the 4th purple row. I made an extra Timeline event to measure that): ~3.2ms

Therefore, if we merge multiple into one, we can get roughly 2x speed up, because it removes those idle periods between them, as well as some of the big overhead between Engine::DispatchPointerDataPacket and the real Dart code execution.

image image

Concrete cases when this happens:

  1. When it janks, this PR helps speed up. For example, many Android devices provide two data packets per vsync interval. So suppose somehow the UI thread took 30ms to compute a frame, then this approach will merge 3-4 packet deliver into one.
  2. In some devices, speedup due to this PR happens in each and every time. For example, below is a tracing on a test phone. As you can see, it delivers 4 pointer data packets per frame. Consider what will happen when UI thread needs (e.g.) 15ms to compute (unlike what is in the screenshot which is very lightweight workload indeed). Then, the first 3 out of 4 packets will be able to be merged by this PR.

image

Benefits for flutter_smooth

The analysis is similar to above. However, since there are a lot of big janky frames in flutter_smooth, it is common to see a dozen of pointer event dispatching after that long janky frame. Therefore, this PR makes that part much much faster.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing. --- Note: I will fix CI later after hearing some review feedbacks, because after review feedback the code itself may change a lot, so I do not want to waste time to fix to-be-thrown code :)

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-18T12:27:06Z GitHub

Make deadline of NotifyIdle configurable, allowing flutter_smooth to get 60FPS, even if GC needs to run for 14ms per 16.67ms

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This PR is similar to https://github.com/flutter/engine/pull/36797. However, it addresses another portion of the GC-caused-jank problem.

Consider the following case: For each frame, UI thread needs to run for 16.00ms. Then:

Without this PR and without flutter_smooth: We know NotifyIdle will be called after the frame ends (more specifically, at AwaitVSync), and the "deadline" argument of NotifyIdle is set to "next_vsync_time - current_time". In other words, it is 16.67-16=0.67ms in our scenario. When DartVM receives this NotifyIdle call, it estimates how long a young GC needs, and realize it needs more than 0.67ms, so it do not call any young GC here. Therefore, garbage starts to accumulate. Finally, at one time, (young) GC must happen because the heap is full. At that time, Dart VM will stop the world for (e.g.) 10ms. Given that the UI thread needs 16.00ms to compute the content of one frame, the 10ms stop-the-world means it must miss at least one deadline. Thus, it janks whenever GC comes.

With this PR and flutter_smooth: No such problem at all. Let's consider one specific frame. Suppose the UI thread runs from 0.00-16.00ms and finished computing the content. Then, when calling NotifyIdle, I will deliberately set the "deadline" to be "next_vsync_time - current_time + 14ms". In other words, DartVM is now notified that, it has 14.67ms (instead of 0.67ms as before). Given this loose deadline, Dart VM happily executes a young GC (when it feels needed) using (e.g.) 10ms. Now we are at 26.00ms and the next frame begins. Given that we are using flutter_smooth, we can easily deliver an extra smooth frame when needed near 33.33ms, even though the plain-old frame needs 16.00ms to compute. Therefore, GC is triggered at proper time that does not cause any jank. And since NotifyIdle is triggered per 16.67ms with sufficient deadline (>14ms deadline duration), Dart VM will do GC at these period, so there will be no GC mentioned in the previous case which happens at random location causing UI to jank.

In conslusion, this PR allows flutter_smooth to get 60FPS, even if GC needs to run for (e.g.) 14ms per 16.67ms.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on ------ I deliberately do not add any tests yet, because the test are trivial and I want to listen to some feedbacks first (e.g. changing the PR). After feedbacks I will definitely add tests, no worries :) writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-18T13:42:58Z GitHub

Fix jank and large-jumping frame by controlling rasterizer ending time

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

Consider the problem - what will happen, when the computation latency becomes lower temporarily? Looks like it is a good thing, since faster means better; many FPS monitors also do not think this is a problem. Spoiler: It is a bad thing - pay a jank.

Detailed analysis is as follows. To begin with, let us define "latency" as the number of frames it takes from starting drawing frame to ending rasterization. Now, what happens when latency temporarily drops to 1 for one or some frames, while it is 2 in other frames? This is separted to two parts: latency decrease (2->1) and increase (1->2).

The decrease itself does not introduce jank, but causes a uncomfortable "jumping" feeling from the user (will be discussed in the "linearlity" section later). For example, say frame a (0.00-16.67ms) has latency 2, frame b (16.67-33.33ms) has latency 2, and frame c (33.33-50.00ms) has latency 1. Then, at 33.33ms, content of frame a is displayed. However, at 50.00ms, both the content from frame b and frame c wants to be displayed to screen, so frame b will never be shown and only frame c is shown. If it is a linear moving animation with 1px per millisecond, we will see offset being 0 (frame a) at 33.33ms and offset being 33.33px (frame c) at 50.00ms, while we know all other frames will introduce an offset of 16.67px per frame. Thus a big jump happens.

As for the increase (1->2), it will introduce one jank. Suppose frame 0.00-16.67ms has latency 1, and 16.67-33.33ms has latency 2. Then, the rasterizer will provide new content to screen only at 16.67ms and 50.00ms, not at 33.33ms, and there is a jank.

Similar analysis holds for any latency change. For example, "1->2->1" latency change will cause a jank and then a uncomfortable big-jump.

Does this happen in real world? Yes, and quite frequently! I do observe it a lot of times in my tracing timeline. For example, the UI+rasterizer time may be near 16.67ms with fluctuation, then we do see a lot of 1->2 / 2->1 latency change. As another example, sometimes a frame may be much faster or slower to compute.

That is what this PR solves. Let's discuss by concrete numbers. Suppose latency is always 2 for a lot of frames, and suddenly in this frame latency drop to 1. Then, this PR will delay the rasterizer ending by sleeping (or can be changed to signaling or whatever you like). It will sleep (shortly speaking) to the next vsync, such that after the sleep, this frame has latency 2. No worries if the sleep happens to be a bit longer - it is still latency 2 if that happens.

Related: https://cjycode.com/flutter_smooth/benchmark/pitfall/latency-change

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. --- I will add tests and refine code and enhance strategy etc after some code review - since review may request changing the code :)
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-18T13:43:03Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-18T14:19:18Z GitHub

flutter_smooth package is out now :)

https://github.com/fzyzcjy/flutter_smooth

(forgot to mention it here yesterday...)

fzyzcjy2022-10-19T00:56:17Z GitHub

Maybe another typo (2 char only)

Hope there exist a more lightweight way to fix typos...

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-19T00:57:58Z GitHub

My bad, it is correct :)

fzyzcjy2022-10-21T03:39:49Z GitHub

Fix janks caused by await vsync in classical Flutter

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This fixes the jank happened in classical Flutter, even without the existence of flutter_smooth

I will add tests and refine code etc after some code review - since review may request changing the code :)

This works pretty well in flutter_smooth, see https://github.com/fzyzcjy/engine/blob/flutter-smooth/shell/common/animator.cc for full code.

During experiments, I observe a phenomenon: Even when the UI thread finishes everything before the deadline (vsync) a few milliseconds, the next frame is scheduled one vsync later, causing one jank. For example, UI thread may run from 0-15ms, but the next frame starts from 33.33ms instead of the correct 16.67ms.

An example screenshot can be seen at the end of this proposal. I added a timeline event, Animator::AwaitVSync, so we can clearly see when vsync await is called. (This screenshot has roughly 3ms space; but more frequently, I see this bug when there is about 0.5-2ms space.)

Therefore, this PR tries to fix this problem. The main idea is that, when detecting we are very near the next vsync, we do not wait at all, but instead directly start the next frame.

image

zoom in:

image

further zoom in:

image

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. -- see above
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T03:39:52Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-21T03:47:57Z GitHub

Remove (3N-1) jank and big-jump when N rasterization misses deadline

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This fixes the jank happened in classical Flutter, even without the existence of flutter_smooth

This PR works with https://github.com/flutter/engine/pull/36438

This optimization holds for both classical Flutter and flutter_smooth - indeed the figure below is for classical Flutter.

In experiments, I do see rasterization takes longer time once in a while, instead of having the exact same duration. Experiments show that, a portion of rasterization ends a little bit later than the deadline (the vsync), while all others meet the deadline.

The following figure demonstrates the case. Given that this code change is unrelated to flutter_smooth, the scenario assumes UI is fast and no flutter_smooth exist at all. If using flutter_smooth, things are similar indeed. The first row is the case without code change to animator.cc, and the second row is the case with (1) this change (2) plus the https://github.com/flutter/engine/pull/36837 change.

image

Consider the frame starting at time 1. In the first row, when the rasterization misses the deadline a little bit (seen in time 2-3), there is nothing new to be shown to the screen, so time 2-3 yields a jank. This is inevitable and also holds for the second row - indeed the only jank in the second row.

Now consider the frame starting at time 2. It yields a big jump in classical Flutter, because the scene "1" (rasterized at about time 3.1) never has a chance to be shown to the screen. The second row does not have the problem because of the deliberate sleep.

Then comes the frame starting at time 3. In classical Flutter, the Animator::BeginFrame early returns, and thus no Dart pipeline is run, because it detects the pipeline is full. The pipeline is full because it is occupied with both the frame around 1-3.1 and the frame around 2-3.9. However, we are too pessimisitic about this - even though the pipeline is full at the beginning of BeginFrame, it may not be full at the end when we really need to call Animator::Render and enqueue a real scene to rasterizer. Thus, the classical Flutter (row 1) voluntarily give up a whole frame causing a jank, while the proposed solution runs the normal pipeline and produce a new scene.

Next is the frame starting at time 4, which we again assume its rasterization misses the deadline a little bit. All frames starting at this one indeed mimics the analysis above, so we do not repeate here. The interesting thing is that, the proposed solution no longer yields a jank anymore.

So, if we count the numbers, there are 3N janks in the first row (where N is the number of slightly missing deadline), and only 1 jank in the second row.

The drawback is that, the latency is increased by one frame, until the end of current frame chain (such as when animation finally finishes). However, when scrolling or touching, this seems better than having a large annoying jump in the UI - which is directly perceptible by human eyes easily. My test mobile phone has intrinsic (i.e. OS/hardware constraints) touch event latency of about 100ms, so adding 16ms to it looks almost non-distinguishable. Of course, if someone is developing a game, having low latency may be more important.

The same analysis also holds for any "latency changes from 2 to 3 to 2" scenario. For example, the "latency being 3" may last for more than one frame (contrary to the figure), with flutter_smooth.

As a remark, flutter_smooth is indeed implicitly doing something similar when in the middle of a plain jank frame. As we know, when a preempt render is about to start (analogy to "when Animator::BeginFrame is called"), we never skip it if pipeline is full (analyogy to the code change to BeginFrame). This works well in experiments.

P.S. Indeed, this is not something caused by flutter_smooth (since it is rasterizer slowness instead of build/layout slowness), but I have found a way trying to improve it.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. -- see above
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T03:47:59Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-21T06:21:51Z GitHub

Add peekPointerDataPacket to get pointer data packets earlier

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This is needed by flutter_smooth in order to handle events. More specifically, when we are in the middle of a long jank frame, flutter_smooth will do preempt render. When doing preempt render, it needs to read and dispatch the pointer events, otherwise the UI will not respond to user fingers at all.

Related: https://github.com/fzyzcjy/flutter_smooth/blob/feat%2Fdoc-insight/website/docs/design/infra/gesture/impl.md

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. -- see above
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-21T06:40:25Z GitHub

Provide fallback vsync target time for window.render

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This works together with a few other PRs: https://github.com/flutter/engine/pull/36438 (to support multi render), https://github.com/flutter/engine/pull/36837 (which consumes vsync tagret time).

In order to make https://github.com/flutter/engine/pull/36837 and other Flutter logic work, we need to provide the correct vsync target time. However, currently the fallback target time is filled as the current time, which is surely incorrect and caused bugs for things like https://github.com/flutter/engine/pull/36837.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. -- see above
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T06:40:28Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-21T06:43:30Z GitHub

Fix incorrect newline in pull request template

Just one char fix :)

Before this fix, it looks like the following on github, with a bug newline:

image

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-21T07:14:04Z GitHub

Fix errors when using multiple build/pipeline owners

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

Close https://github.com/flutter/flutter/issues/114002

We all know that, Flutter allows us to create our own BuildOwner and PipelineOwner, and here is even an official example: https://github.com/flutter/flutter/blob/master/examples/api/lib/widgets/framework/build_owner.0.dart. The doc also agrees with that. For example:

You can create other pipeline owners to manage off-screen objects, which can flush their pipelines independently of the on-screen render objects. (https://api.flutter.dev/flutter/rendering/PipelineOwner-class.html)

And

Additional build owners can be built to manage off-screen widget trees. (https://api.flutter.dev/flutter/widgets/BuildOwner-class.html)

Therefore, theoretically, we should be able to happily use our own BuildOwner and PipelineOwner anywhere freely. However, it has a bug as follows: If I call pipelineOwner.flushPaint(); (and sibling methods) inside the layout phase of the main PipelineOwner, then I get an assertion error in debug mode.

The root cause is that, even though the self-managed PipelineOwner is isolated from the flutter-managed PipelineOwner, the debug variable RenderObject.debugActiveLayout is shared. Therefore, when calling flushPaint on self-manged PipelineOwner within a call of flushLayout of flutter-managed PipelineOwner, the assertions get confused and wrongly throws.

My hack can be seen in https://github.com/fzyzcjy/flutter_smooth/blob/0c5db0ff270aa0c8cff28ea19055999627a8df6d/packages/smooth/lib/src/infra/auxiliary_tree_pack.dart#L214. Copy it here for completeness:

...
_temporarilyRemoveDebugActiveLayout(() {
pipelineOwner.flushPaint();
});
...

void _temporarilyRemoveDebugActiveLayout(VoidCallback f) {
// NOTE we have to temporarily remove debugActiveLayout
// b/c [SecondTreeRootView.paint] is called inside [preemptRender]
// which is inside main tree's build/layout.
// thus, if not set it to null we will see error
// https://github.com/fzyzcjy/yplusplus/issues/5783#issuecomment-1254974511
// In short, this is b/c [debugActiveLayout] is global variable instead
// of per-tree variable
// and also
// https://github.com/fzyzcjy/yplusplus/issues/5793#issuecomment-1256095858
final oldDebugActiveLayout = RenderObject.debugActiveLayout;
RenderObject.debugActiveLayout = null;
try {
f();
} finally {
RenderObject.debugActiveLayout = oldDebugActiveLayout;
}
}

However, for this to work, the debugActiveLayout setter must be public.

The most naive solution is to make it public, but that may violate encapsulation. Thus, in the proposed PR, I create a method to wrap that.

Reproduction code and output

Details
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('When call PipelineOwner.flushPaint inside another PipelineOwner.flushLayout', (tester) async {
int onPerformLayoutCount = 0;
await tester.pumpWidget(_SpyLayoutBuilder(onPerformLayout: () {
onPerformLayoutCount++;

const Widget widget = ColoredBox(color: Colors.green, child: SizedBox(width: 100, height: 100));

// mimic https://github.com/flutter/flutter/blob/master/examples/api/lib/widgets/framework/build_owner.0.dart
final PipelineOwner pipelineOwner = PipelineOwner();
final MeasurementView rootView = pipelineOwner.rootNode = MeasurementView();
final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
final RenderObjectToWidgetElement<RenderBox> element = RenderObjectToWidgetAdapter<RenderBox>(
container: rootView,
debugShortDescription: '[root]',
child: widget,
).attachToRenderTree(buildOwner);

rootView.scheduleInitialLayout();
rootView.scheduleInitialPaint(TransformLayer(transform: Matrix4.identity())..attach(rootView));
buildOwner.buildScope(element);
pipelineOwner.flushLayout();
pipelineOwner.flushPaint();
}));

expect(onPerformLayoutCount, 1);
});
}

class MeasurementView extends RenderBox with RenderObjectWithChildMixin<RenderBox> {

void performLayout() {
assert(child != null);
child!.layout(const BoxConstraints(), parentUsesSize: true);
size = child!.size;
}


void paint(PaintingContext context, Offset offset) {
print('hi ${describeIdentity(this)}.paint');
context.paintChild(child!, offset);
}


bool get isRepaintBoundary => true;


Rect get paintBounds => Offset.zero & size;


void debugAssertDoesMeetConstraints() => true;
}

class _SpyLayoutBuilder extends SingleChildRenderObjectWidget {
final VoidCallback onPerformLayout;

const _SpyLayoutBuilder({required this.onPerformLayout});


_RenderSpyLayoutBuilder createRenderObject(BuildContext context) => _RenderSpyLayoutBuilder(
onPerformLayout: onPerformLayout,
);


void updateRenderObject(BuildContext context, _RenderSpyLayoutBuilder renderObject) {
renderObject.onPerformLayout = onPerformLayout;
}
}

class _RenderSpyLayoutBuilder extends RenderProxyBox {
_RenderSpyLayoutBuilder({
required this.onPerformLayout,
RenderBox? child,
}) : super(child);

VoidCallback onPerformLayout;


void performLayout() {
super.performLayout();
onPerformLayout();
}
}

yields

Details
00:08 +0: When call PipelineOwner.flushPaint inside another PipelineOwner.flushLayout                                                  
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following assertion was thrown during performLayout():
RenderBox.size accessed beyond the scope of resize, layout, or permitted parent access. RenderBox
can always access its own size, otherwise, the only object that is allowed to read RenderBox.size is
its parent, if they have said they will. It you hit this assert trying to access a child's size,
pass "parentUsesSize: true" to that child's layout().
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2009 pos 13: 'debugDoingThisResize || debugDoingThisLayout ||
_computingThisDryLayout ||
(RenderObject.debugActiveLayout == parent && size._canBeUsedByParent)'

Either the assertion indicates an error in the framework itself, or we should provide substantially
more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.md

The relevant error-causing widget was:
_SpyLayoutBuilder
_SpyLayoutBuilder:file:///Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart:9:29

When the exception was thrown, this was the stack:
#2 RenderBox.size.<anonymous closure> (package:flutter/src/rendering/box.dart:2009:13)
#3 RenderBox.size (package:flutter/src/rendering/box.dart:2022:6)
#4 MeasurementView.paintBounds (file:///Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart:53:41)
#5 PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:154:56)
#6 PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:98:5)
#7 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1116:31)
#8 main.<anonymous closure>.<anonymous closure> (file:///Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart:28:21)
#9 _RenderSpyLayoutBuilder.performLayout (file:///Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart:86:20)
#10 RenderObject.layout (package:flutter/src/rendering/object.dart:2135:7)
#11 RenderBox.layout (package:flutter/src/rendering/box.dart:2418:11)
#12 RenderView.performLayout (package:flutter/src/rendering/view.dart:170:14)
#13 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1973:7)
#14 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:999:18)
#15 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1194:23)
#16 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:378:5)
#17 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
#18 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
#19 AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:1057:9)
#22 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#23 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1043:27)
#24 WidgetTester.pumpWidget.<anonymous closure> (package:flutter_test/src/widget_tester.dart:554:22)
#27 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#28 WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:551:27)
#29 main.<anonymous closure> (file:///Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart:9:18)
#30 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:171:29)
<asynchronous suspension>
<asynchronous suspension>
(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace)

The following RenderObject was being processed when the exception was fired: _RenderSpyLayoutBuilder#f19a8:
creator: _SpyLayoutBuilder ← [root]
parentData: <none>
constraints: BoxConstraints(w=800.0, h=600.0)
size: Size(800.0, 600.0)
This RenderObject has no descendants.
════════════════════════════════════════════════════════════════════════════════════════════════════
00:08 +0 -1: When call PipelineOwner.flushPaint inside another PipelineOwner.flushLayout [E]
Test failed. See exception logs above.
The test description was: When call PipelineOwner.flushPaint inside another PipelineOwner.flushLayout


To run this test again: /Users/tom/fvm/versions/3.3.5/bin/cache/dart-sdk/bin/dart test /Users/tom/Main/yplusplus/frontend/yplusplus/test/a.dart -p vm --plain-name 'When call PipelineOwner.flushPaint inside another PipelineOwner.flushLayout'
00:08 +0 -1: Some tests failed.

Performance overhead

Using compiler explorer, we can see that it does not generate worse assembly (as long as we use the prefer-inline pragma)

https://godbolt.org/z/EoznoWex7

image

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt. -- see above
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T07:14:08Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-21T10:40:45Z GitHub

Expose Ticker.startTime so users know the start time and the absolute time

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

Currently, the user of Ticker only knows the elapsed time. However, it looks reasonable to allow the user to know when the ticker thinks it starts ticking.

If this does not wanted to be widely used, maybe we can mark it as @protected or @visibleForTesting or @experimental.

As for where it is needed inside flutter_smooth, it is utilized to know the relative time between a few Tickers as well as the system. In other words, when Ticker A says it elapsed 1 second, flutter_smooth needs to know the startTime such that it knows what "1second" means in absolute time. Detailed code can be seen in https://github.com/fzyzcjy/flutter_smooth/blob/0c5db0ff270aa0c8cff28ea19055999627a8df6d/packages/smooth/lib/src/drop_in/list_view/shift.dart#L352-L356

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-21T11:53:27Z GitHub

Fix bug thattimeDilation is not reset, causing subsequent test errors, and add verifications to ensure such problem does not exist in the future

Some tests set the time dilation to be non-one, but is not reset after the test ends. Thus, every test after it will see very weird time. I find this bug because got trapped in https://github.com/flutter/flutter/pull/113828.

The added line, timeDilation = 1.0; // restore time dilation, or it will affect other tests, is copied from image_stream_test.dart's 'timeDilation affects animation frame timers' test.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-21T12:24:03Z GitHub

Allow GestureBinding subclasses to know hitTest information

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This PR allows the subclasses of GestureBinding to read the hitTest information. It is directly needed in flutter_smooth, because flutter_smooth has extra call to dispatchEvent and only execute those who are in auxiliary tree (and omit those in the main tree) during preempt render. I can copy-and-paste the content of dispatchEvent to mimic the behavior, but there is one missing piece: the hitTest information. By adding this PR, it can work.

An alternative solution, which the flutter-smooth branch is currently using (but more hacky), may be to add a filter to dispatchEvent. Then, we can utilize the filter to skip those RenderObjects in the main tree.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt. -- see above
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T12:24:06Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-21T12:48:32Z GitHub

Enable a frame to be scheduled immediately

  1. I will finish code details, refine code, add tests, make tests pass, etc, after a code review that thinks the rough idea is acceptable. It is because, from my past experience, reviews may request changing a lot. If the general idea is to be changed, all detailed implementation efforts are wasted :)
  2. The PR has an already-working counterpart, and it produces ~60FPS smooth experimental results. The benchmark results and detailed analysis is in chapter https://cjycode.com/flutter_smooth/benchmark/. All the source code is in https://github.com/fzyzcjy/engine/tree/flutter-smooth and https://github.com/fzyzcjy/flutter/tree/flutter-smooth.
  3. Possibly useful as a context to this PR, there is a whole chapter discussing the internals - how flutter_smooth is implemented. (Link: https://cjycode.com/flutter_smooth/design/)

This PR depends on the merging of https://github.com/flutter/engine/pull/36911.

This PR is needed by flutter_smooth, because of the "Brake" mechanism discussed in https://github.com/fzyzcjy/flutter_smooth/blob/feat%2Fdoc-insight/website/docs/design/infra/brake/intro.md (TODO@fzyzcjy: post website link when it is published). In short, for that mechanism to work without jank, a frame must be able to be started immediately instead of waiting for the next vsync (otherwise we must have a jank).

More specifically, let's analyze the figure in the Brake mechanism. The red arrow points where we need this PR. If this PR is not there, the frame cannot start at time=2.6, but have to start at time=3. Then, even though we have preempt render mechanism, we are not able to produce scene and rasterizer quick enough before time=4, so we will have a jank.

image

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests. -- see above
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-21T12:48:35Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-24T06:49:03Z GitHub

Fix wasted memory caused by debug fields - 16 bytes per object (when adding that should-be-removed field crosses double-word alignment)

Close #113940

Theoretical analysis

Consider the following example code. Code with "case A" occupies 2.5GB memory, while "case B" is 4.0GB memory.

Details
class C {
int? a;
int? b;

// int get computed => a.hashCode; // case A
int get computed => a.hashCode ^ b.hashCode; // case B
}

Future<void> main() async {
final arr = <C>[];
for (var i = 0; i < 100000000; ++i) {
arr.add(C()
..a = 42
..b = 100);
}
print('hash=${Object.hashAll(arr.map((e) => e.computed))}');

print('sleep...');
await Future<void>.delayed(const Duration(seconds: 10000));
}

Therefore, we know that, a field does not occupy memory if there is no read/write to it. For example, the field b in case A is never read, so Dart compiler seems so smart that it knows it can be eliminated and no memory is allocated for that. By the way, b is written even in case A, but seems memory is not allocated as long as it is not read. This agrees with common sense (but since I am not compiler expert, feel free to correct me if I am wrong!).

For fields inside Flutter that is merely used for debug, they are usually accessed inside an assert(() { ... }()); block. That is great, because if each and every field access are inside assert, those code will be eliminated in release build, and by discussions above, we will not pay memory for those debug fields.

However, AnimationController.debugLabel does not seem to follow this. Inside AnimationController.toStringDetails, it uses debugLabel field without being inside a assert block. Therefore, IMHO, we will be paying the memory of debugLabel even if we never use that in runtime.

As for why toStringDetails is guaranteed to be called, it is simple: Animation.toString calls that toStringDetails, and we know toString will not be tree shaken out.

Experimental analysis

Setup

Use these code:

Details

Need to add two dummy fields to AnimationController, such that it crosses the double-word alignment. (Yes, this PR does not affect memory for today's AnimationController, but who can guarantee it never have two more fields or two less fields, which this PR will reduce 16 bytes).

class AnimationController {
int? dummy1;
int? dummy2;
... old code ...
}

Use this code

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);


State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final arr = <AnimationController>[];


void initState() {
super.initState();
for (var i = 0; i < 2000000; ++i) arr.add(AnimationController(vsync: const TestVSync()));
print('${arr.first.dummy1} ${arr.first.dummy2}'); // ensure the dummy fields are not removed
arr.first
..duration = const Duration(seconds: 1)
..forward();
}


Widget build(BuildContext context) => Container();
}

class TestVSync implements TickerProvider {
const TestVSync();


Ticker createTicker(TickerCallback onTick) => Ticker(onTick);
}

Operations

  1. flutter build apk --extra-gen-snapshot-options='--print-object-layout-to=object_layout.json' and look at json to know memory layout
  2. flutter run --profile to know memory consumption at runtime

Results

Without PR

96 bytes per object

Details
  {
"class": "AnimationController",
"size": 96,
"fields": [
{
"field": "dummy1",
"offset": 20
},
{
"field": "dummy2",
"offset": 24
},
{
"field": "lowerBound",
"offset": 28
},
{
"field": "upperBound",
"offset": 36
},
{
"field": "debugLabel",
"offset": 44
},
{
"field": "animationBehavior",
"offset": 48
},
{
"field": "duration",
"offset": 52
},
{
"field": "reverseDuration",
"offset": 56
},
{
"field": "_ticker",
"offset": 60
},
{
"field": "_simulation",
"offset": 64
},
{
"field": "_value",
"offset": 68
},
{
"field": "_direction",
"offset": 72
},
{
"field": "_status",
"offset": 76
},
{
"field": "_lastReportedStatus",
"offset": 80
}
]
},

and

image

With this PR

80 bytes per object

Details
  {
"class": "AnimationController",
"size": 80,
"fields": [
{
"field": "dummy1",
"offset": 20
},
{
"field": "dummy2",
"offset": 24
},
{
"field": "lowerBound",
"offset": 28
},
{
"field": "upperBound",
"offset": 36
},
{
"field": "animationBehavior",
"offset": 44
},
{
"field": "duration",
"offset": 48
},
{
"field": "reverseDuration",
"offset": 52
},
{
"field": "_ticker",
"offset": 56
},
{
"field": "_simulation",
"offset": 60
},
{
"field": "_value",
"offset": 64
},
{
"field": "_direction",
"offset": 68
},
{
"field": "_status",
"offset": 72
},
{
"field": "_lastReportedStatus",
"offset": 76
}
]
},

and

image

Conclusion

16B reduce per object.

Without PR

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-24T06:49:06Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

flutter-dashboard2022-10-24T07:31:03Z GitHub

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

jonahwilliams2022-10-24T18:31:27Z GitHub

You'll need either a test or a test exception.

Hixie2022-10-24T23:15:36Z GitHub

test-exemption: optimization

auto-submit2022-10-24T23:39:30Z GitHub

auto label is removed for flutter/flutter, pr: 113927, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-10-24T23:39:31Z GitHub

auto label is removed for flutter/flutter, pr: 113927, due to Validations Fail.

fzyzcjy2022-10-25T01:05:22Z GitHub

Let _debugCheckNotUsedAsOldLayer provide hashcode in addition to runtime type

I see this assert fail when working on my own app today (unrelated to flutter_smooth; work on stable channel). With identity hashcode, we can surely know more about which layer violates.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • [
  • x] I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-25T01:05:24Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-25T03:56:49Z GitHub

Fix addToScene documentation

Its return type is void, but doc says "return the engine layer", so I guess the doc is outdated.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-25T04:04:31Z GitHub

[WIP] Fix Layer ... was previously used as oldLayer, caused by LeaderLayer addToScene bug

Close https://github.com/flutter/flutter/issues/113995 Please see the issue for a bug reproduction. Here I will discuss how it is solved and what caused it.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-10-25T07:08:30Z GitHub

Layer ... was previously used as oldLayer assertion error in debug mode, and page being blank in release mode, caused by LeaderLayer addToScene bug

Close https://github.com/flutter/flutter/issues/113995 Please see the issue for a bug reproduction. Here I will discuss how it is solved and what caused it.

The real world bug

My app has a part of it not rendered (i.e. page blank) sometimes, which is very weird. After I manage to reproduce it in debug environment, the error is:

Details
======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
Layer ClipRectEngineLayer was previously used as oldLayer.
Once a layer is used as oldLayer, it may not be used again. Instead, after calling one of the SceneBuilder.push* methods and passing an oldLayer to it, use the layer returned by the method as oldLayer in subsequent frames.
'dart:ui/compositing.dart':
Failed assertion: line 88 pos 9: '<optimized out>'


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.md

When the exception was thrown, this was the stack:
#2 _EngineLayerWrapper._debugCheckNotUsedAsOldLayer (dart:ui/compositing.dart:88:9)
#3 SceneBuilder.addRetained.<anonymous closure>.recursivelyCheckChildrenUsedOnce (dart:ui/compositing.dart:656:21)
#4 List.forEach (dart:core-patch/growable_array.dart:416:8)
#5 SceneBuilder.addRetained.<anonymous closure>.recursivelyCheckChildrenUsedOnce (dart:ui/compositing.dart:662:18)
#6 SceneBuilder.addRetained.<anonymous closure> (dart:ui/compositing.dart:665:7)
#7 SceneBuilder.addRetained (dart:ui/compositing.dart:668:6)
#8 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:643:15)
#9 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#10 OffsetLayer.addToScene (package:flutter/src/rendering/layer.dart:1378:5)
#11 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#12 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#13 OffsetLayer.addToScene (package:flutter/src/rendering/layer.dart:1378:5)
#14 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#15 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#16 EnhancedLeaderLayer.addToScene (package:flutter_portal/src/enhanced_composited_transform/flutter_src/rendering_layer.dart:159:5)
#17 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#18 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#19 EnhancedLeaderLayer.addToScene (package:flutter_portal/src/enhanced_composited_transform/flutter_src/rendering_layer.dart:159:5)
#20 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#21 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#22 EnhancedLeaderLayer.addToScene (package:flutter_portal/src/enhanced_composited_transform/flutter_src/rendering_layer.dart:159:5)
#23 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#24 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#25 EnhancedLeaderLayer.addToScene (package:flutter_portal/src/enhanced_composited_transform/flutter_src/rendering_layer.dart:159:5)
#26 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#27 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#28 OffsetLayer.addToScene (package:flutter/src/rendering/layer.dart:1378:5)
#29 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:646:5)
#30 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1241:13)
#31 TransformLayer.addToScene (package:flutter/src/rendering/layer.dart:1834:5)
#32 ContainerLayer.buildScene (package:flutter/src/rendering/layer.dart:1054:5)
#33 RenderView.compositeFrame (package:flutter/src/rendering/view.dart:231:37)
#34 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:517:18)
#35 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:884:13)
#36 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:378:5)
#37 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
#38 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
#39 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1015:5)
#40 _invoke (dart:ui/hooks.dart:148:13)
#41 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#42 _drawFrame (dart:ui/hooks.dart:115:31)
(elided 2 frames from class _AssertionError)
====================================================================================================

The minimal reproduction

(I will omit how I find out the root cause. If you are interested I can write down.)

Please see https://github.com/flutter/flutter/issues/113995 with minimal reproduction code

Why that causes bug

The first pumpWidget creates initial tree, and I deliberately set padding to non-zero, such that LeaderLayer.paint will see non-zero Offset, and thus will pushTransform.

In the second pumpWidget, I deliberately set padding to zero. Then, LeaderLayer.paint will not call pushTransform. Correct implementation should set engineLayer to null, but the old code just leave that variable unchanged.

Then comes the third pumpWidget. I change the color inside a RepaintBoundary which is the sibling of LeaderLayer (CompositedTransformTarget). This is carefully constructed (notice the sibling and the RepaintBoundary) to reproduce the following behavior in real-world complicated app: We should (1) ensure LeaderLayer. _addToSceneWithRetainedRendering is called, and (2) ensure LeaderLayer. _needsAddToScene = false. For example, if we do not add that RepaintBoundary, it will not construct the case, because CompositedTransformTarget's RO will repaint and thus _needsAddToScene becomes true.

Then, interesting thing happens. Look at the code:

  void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
if (!_needsAddToScene && _engineLayer != null) {
builder.addRetained(_engineLayer!);
return;
}
...

We have constructed a case, such that the if is true. Notice that, if we fix the bug (just as what we do in the PR), the engineLayer will be null so if condition will not be true. Thus, buggy code will call LeaderLayer.addRetained, while correct code will not.

Then the error happens. Notice that, this LeaderLayer._engineLayer indeed is the layer constructed in the second pumpWidget instead of the third (i.e. it is stale from last frame). Therefore, this EngineLayer's children are all from the stale previous frame. For example, the ClipRectLayer from previous frame (second pumpWidget). Since it has already been used as oldLayer in RenderClipRect, that old stale ClipRectLayer should never be used. However we are now using it in addRetained. Therefore, no wonder when addRetained is checking all subtree ensuring _debugCheckNotUsedAsOldLayer, it fails the assertions.

Why is the solution valid

With analysis above, we can clearly see this solves the bug. In addition, looking at ClipRectLayer etc, they also have such "engineLayer = null" logic, so even if only mimicking those we should do this.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-25T07:08:33Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-25T07:08:46Z GitHub

Hmm it gets auto closed after I rename branch. Anyway please see https://github.com/flutter/flutter/pull/113998

fzyzcjy2022-10-25T09:41:15Z GitHub

Introduce debugWithActiveLayoutCleared to avoid duplicated code

This is indeed a part of https://github.com/flutter/flutter/pull/113817. However, that one actually does two things in one PR: refactor internal code, and exposes an API. Thus, in order to follow the spirit mentioned in Flutter - one PR for only one thing - I make this refactor a separate PR, which should be easier to review.

Performance difference

As is discussed in https://github.com/flutter/flutter/pull/113817#issue-1417868505 via compiler explorer, the generate assembly is not worse as long as we use that prefer-inline annotation.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-25T12:00:24Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

goderbauer2022-10-25T22:16:37Z GitHub

TestWidgetsFlutterBinding._verifyInvariants could be the right place to check for this.

fzyzcjy2022-10-26T00:03:08Z GitHub

@Piinks Yes I think so, @goderbauer mentions the _verifyInvariants - the one I have seen a ton of times reporting I changed somthing (e.g. fake screen resolution) but do not change it back

I will update soon.

fzyzcjy2022-10-26T00:21:02Z GitHub

done

/cc @Piinks @goderbauer

fzyzcjy2022-10-26T00:25:06Z GitHub

Anyway this is not a big problem, so I will close it now since have a lot of other more important PRs remaining for me to improve :)

fzyzcjy2022-10-26T00:28:15Z GitHub

The CI failure, "infra failed", does not look like a problem of my code indeed

image

fzyzcjy2022-10-26T12:00:00Z GitHub

CI passes

goderbauer2022-10-26T20:39:34Z GitHub

Looks like this was changed in https://github.com/flutter/flutter/pull/36402, but we never updated the doc (cc @yjbanov).

goderbauer2022-10-26T21:05:48Z GitHub

Please ignore this PR for now

I am going to close this then for now to get it off the queue. Feel free to reopen when you can provide more context.

goderbauer2022-10-26T21:26:11Z GitHub

In isolation, the API this exposes doesn't make a lot of sense to me and it feels hacky.

If you chose to continue working on this PR, please also take another look at the flutter style guide, especially the sections around API documentation: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc.

dnfield2022-10-26T21:38:03Z GitHub

I'm still not quite clear on why this is a solution that should live in the framework.

Tickers have no means of interrupting work more often. Tickers have no special knowledge of when vsync happens. This seems to introduce a number of problems that will be difficult for developers to reason about. I think this could be solved in a package instead. I'm going to close this for now but let's discuss more on discord if you like.

dnfield2022-10-26T22:12:20Z GitHub

Without this, how do you measure frame timings in profile mode?

fzyzcjy2022-10-26T23:10:44Z GitHub

Looks like this was changed in https://github.com/flutter/flutter/pull/36402, but we never updated the doc (cc @yjbanov).

I agree. That's why the PR happens :)

fzyzcjy2022-10-26T23:20:03Z GitHub

Sure, thanks!

fzyzcjy2022-10-26T23:28:54Z GitHub

@dnfield Without this, how do you measure frame timings in profile mode?

I personally by the timeline data. You know, look at the GPURasterizer::Draw rectangle inside timeline, etc.

p.s. partially related https://github.com/flutter/devtools/issues/4522

fzyzcjy2022-10-26T23:31:40Z GitHub

@goderbauer I find adding this a bit dubios. The environment flag will be really hard to discover.

Hmm why are other env flags in flutter easy to discover, by adding doc or something else? I can do the same, then this is no problem.

Also, profile mode exists so you can get these kind of performance metrics out of your app.

As mentioned above, these perf metrics are already exposed via the timeline data, such as by looking at GPURasterizer::Draw.

If you don't want that, there's always release mode...

But we need to do profiling to get profile data, don't we :)

fzyzcjy2022-10-26T23:32:50Z GitHub

Rephrase the problem: We are providing redundant data (i.e. report timing, even if timeline tracing already has the data), and that redundancy is causing measurable speed drop compared with release mode. Then we are biasing the profiling result.

So, if this is not to be merged, maybe we should create another PR to the doc site, with something like: "Please do not believe the speed in profile mode. It will be measureably slower than release mode." But IMHO users will not like that sentence.

fzyzcjy2022-10-26T23:38:37Z GitHub

Thank you, I will reconsider the API and discuss on discord.

fzyzcjy2022-10-27T00:15:59Z GitHub

Look like I can remove the environment variable.

In Dart, if we have a normal variable that only has one constant value, then the field will be removed. https://github.com/dart-lang/sdk/issues/50287#issuecomment-1289027245 "Also AOT compiler is capable of removing unused fields of various kinds (the field is also effectively unused if it always contains the same constant value or is only written into, but never read)."

The experiment confirms this:

https://godbolt.org/z/Thd91YcPj

image

I will update the code shortly.

fzyzcjy2022-10-27T00:20:49Z GitHub

Fix incorrectly named "debug" prefix

Close #111874

Please read discussions there for why this PR is made :)

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-10-27T00:20:51Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-10-27T03:06:12Z GitHub

Avoid future bugs about wrong manipulation of engineLayer inside addToScene

In https://github.com/flutter/flutter/pull/113998, I have fixed the bug caused by wrongly using engineLayer. But that bug is so time consuming to locate, so I write this PR to add assertions so we will not add such bugs in the future anymore.

Question: In order to insert assertions, I have to wrap the addToScene function. IMHO the best thing is addToScene vs performAddToScene (like layout vs performLayout so everyone is familiar). However, that is a breaking change. I am not a googler so not sure whether you like it or not? IMHO the breaking change may be acceptable, because (1) it is merely a rename so simple to migrate (2) very few people write their own Layer class so this will affect few people. (Currently, I temporarily create a addToSceneWrapped method in order to see whether the code works - but I guess this should not be the final name)

This assertion does work, because https://github.com/flutter/flutter/pull/114124/checks?check_run_id=9132589021 reports the following error, which is exactly the error that #113998 finds and fixes.

══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════
The following assertion was thrown during a scheduler callback:
When addToScene previously configures the engineLayer, it should either update it in current
addToScene, or set it to null explicitly. Otherwise, Flutter framework may utilize that already
out-of-date engineLayer and thus cause problems. However, it is observed that
previousEngineLayer=TransformEngineLayer#38532153 while engineLayer=TransformEngineLayer#38532153.
This originates in LeaderLayer#204442042.
'package:flutter/src/rendering/layer.dart':
Failed assertion: line 673 pos 9: 'previousEngineLayer == null || previousEngineLayer !=
engineLayer'

Either the assertion indicates an error in the framework itself, or we should provide substantially
more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.md

When the exception was thrown, this was the stack:
#2 Layer.addToSceneWrapped.<anonymous closure> (package:flutter/src/rendering/layer.dart:673:9)
#3 Layer.addToSceneWrapped (package:flutter/src/rendering/layer.dart:683:6)
#4 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:701:5)
#5 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1311:13)
#6 OffsetLayer.addToScene (package:flutter/src/rendering/layer.dart:1448:5)
#7 Layer.addToSceneWrapped (package:flutter/src/rendering/layer.dart:669:5)
#8 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:701:5)
#9 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1311:13)
#10 OffsetLayer.addToScene (package:flutter/src/rendering/layer.dart:1448:5)
#11 Layer.addToSceneWrapped (package:flutter/src/rendering/layer.dart:669:5)
#12 Layer._addToSceneWithRetainedRendering (package:flutter/src/rendering/layer.dart:701:5)
#13 ContainerLayer.addChildrenToScene (package:flutter/src/rendering/layer.dart:1311:13)
#14 TransformLayer.addToScene (package:flutter/src/rendering/layer.dart:1941:5)
#15 Layer.addToSceneWrapped (package:flutter/src/rendering/layer.dart:669:5)
#16 ContainerLayer.buildScene (package:flutter/src/rendering/layer.dart:1124:5)
#17 RenderView.compositeFrame (package:flutter/src/rendering/view.dart:231:37)
#18 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1257:26)
#19 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:375:5)
#20 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1275:15)
#21 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1204:9)
#22 AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:1096:9)
#25 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#26 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1082:27)
#27 WidgetTester.pump.<anonymous closure> (package:flutter_test/src/widget_tester.dart:618:53)
#30 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#31 WidgetTester.pump (package:flutter_test/src/widget_tester.dart:618:27)
#32 main.<anonymous closure> (file:///b/s/w/ir/x/w/flutter/packages/flutter/test/widgets/autocomplete_test.dart:491:18)
<asynchronous suspension>
<asynchronous suspension>
(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace)
════════════════════════════════════════════════════════════════════════════════════════════════════

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

dnfield2022-10-27T16:22:20Z GitHub

@dnfield Without this, how do you measure frame timings in profile mode?

I personally by the timeline data. You know, look at the GPURasterizer::Draw rectangle inside timeline, etc.

p.s. partially related flutter/devtools#4522

This method is significantly cheaper than the timeline.

auto-submit2022-10-27T16:35:08Z GitHub

auto label is removed for flutter/flutter, pr: 113998, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-10-27T16:35:09Z GitHub

auto label is removed for flutter/flutter, pr: 113998, due to Validations Fail.

chinmaygarde2022-10-27T20:17:43Z GitHub

cc @dnfield

dnfield2022-10-27T20:18:50Z GitHub

Let's close this one out to get it off review queues until we have more consensus around the approach.

dnfield2022-10-27T20:20:30Z GitHub

I'm going to close this to remove it from review queues. As I'm mentioning in some other PRs, let's discuss further on discord.

auto-submit2022-10-27T21:07:46Z GitHub

auto label is removed for flutter/engine, pr: 36822, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-10-27T21:07:47Z GitHub

auto label is removed for flutter/engine, pr: 36822, due to Validations Fail.

dnfield2022-10-27T21:29:25Z GitHub

I started to review this, but then realized this seems to be at odds with some similar work that was done for iOS - see pointer_data_dispatcher.h and in particular the SmoothPointerDataDispatcher.

I think you're on to something here, but I wonder if we should be looking at making the SmoothPointerDataDispatcher work on more than just iOS at this point (as in, perhaps some Android phones are now doing what iOS was doing w.r.t. pointer events and screen vsync being different). This approach does not currently seem to be compatible with that, and I have some concerns about thread safety around it (you're capturing this and posting it to a different task runner in an unsafe manner).

I would suggest filing a specific bug for this issue with more details about which platform(s) you're observing this behavior on and how what you need is different from the SmoothPointerDataDispatcher (if it's different at all). If you're on Android, it would probably be worth doing an experiment where the smooth dispatcher is enabled there.

dnfield2022-10-27T21:32:39Z GitHub

This approach is not passing tests and doesn't seem to be ready for review. It would need substantial reworking to be ready for review. I'm going to close this to get it off of review queues.

dnfield2022-10-27T21:35:52Z GitHub

This seems very similar to https://github.com/flutter/engine/pull/29276

Similar concerns as to that one: we need some motivating benchmarks that clearly show what benefits would be gained here, and we need to see that changes to this won't negatively impact existing benchmarks/users.

dnfield2022-10-27T21:40:04Z GitHub

This removes backpressure from the rasterizer and will make the application run hotter than necessary in a lot of cases. It'd be nice to improve things here, but we can't completely remove that backpressure safely.

dnfield2022-10-27T21:41:34Z GitHub

This is not the only thing that would need peeking,a nd creating this kind of "pull" based model is a major architectural change we'd need more time to reason about. I'm going to close this PR to get it off of review queues for now - tests are not passing and it is not clear that this API is the desirable one to expose to developers to achieve the larger goals you're discussing.

dnfield2022-10-27T21:44:56Z GitHub

Also re-kicked the infra failure :)

Hixie2022-10-27T21:47:18Z GitHub

This would ideally be tested on the framework side, to make sure the output matches what we expect from other messages (we have a special matcher that knows how to normalize this kind of hash code).

That can't be in this PR, though.

test-exempt: test needs to be in another repo

dnfield2022-10-27T21:50:28Z GitHub

Same comment as on the linked PR applies.

In particular, it's really hard to reason about how to use this parameter correctly. A lot of the work you're doing seems to be geared towards ignoring/overriding the vsync that the system gives us instead of cooperating with it more.

In particular, vsync is an important source of backpressure that we must respect. We cannot just keep shoving frames at the system compositor, we will overwhelm it and it will start dropping more frames than necessary/run into long GPU stalls. This will be really bad on older hardware, and still be bad on newer hardware.

dnfield2022-10-27T21:52:33Z GitHub

Closing to remove from review queues.

auto-submit2022-10-27T21:53:44Z GitHub

auto label is removed for flutter/engine, pr: 36985, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-10-27T21:53:44Z GitHub

auto label is removed for flutter/engine, pr: 36985, due to Validations Fail.

dnfield2022-10-27T21:57:31Z GitHub

@iskakaushik can you provide a secondary review here?

dnfield2022-10-27T21:59:32Z GitHub

Sorry, this should probably be closed. There is usage of this API internally in google that needs some reworking before this can be updated. Thank you for the effort!

fzyzcjy2022-10-27T22:20:39Z GitHub

You are welcome!

fzyzcjy2022-10-27T22:44:52Z GitHub

You are welcome!

fzyzcjy2022-10-27T22:51:43Z GitHub

Hmm it still fails. I have seen this test failing across my PRs recently without reasons. let me bump ci

fzyzcjy2022-10-27T22:57:45Z GitHub

@dnfield This method is significantly cheaper than the timeline.

I originally thought so. But interestingly, seems no in a few reasons:

  • timeline is usually there (or even cannot be disabled? not find a flag to disable), while report timings is an extra thing
  • timeline makes app slower evenly, while report timings is a big burst. thus, flutter_smooth can handle timline slowness very easily (and still gets 60fps and smooth user feeling), while report timings will cause a jank (because it itself takes some long time and during that time flutter_smooth cannot perform any job to submit extra frames).

For disable tracing: Not see any flag to disable IMHO

flutter run --help | grep trace
--trace-startup Trace application startup, then exit, saving the trace to a file. By default, this will be saved in the "build" directory. If the FLUTTER_TEST_OUTPUTS_DIR environment variable is set, the file will be written there instead.
--endless-trace-buffer Enable tracing to an infinite buffer, instead of a ring buffer. This is useful when recording large traces. To use an endless buffer to record startup traces, combine this with "--trace-startup".
--trace-systrace Enable tracing to the system tracer. This is only useful on platforms where such a tracer is available (Android, iOS, macOS and Fuchsia).
--trace-skia Enable tracing of Skia code. This is useful when debugging the raster thread (formerly known as the GPU thread). By default, Flutter will not log Skia code, as it introduces significant overhead that may affect recorded performance metrics in a misleading way.
--[no-]await-first-frame-when-tracing Whether to wait for the first frame when tracing startup ("--trace-startup"), or just dump the trace as soon as the application is running. The first frame is detected by looking for a Timeline event with the name "Rasterized first useful frame". By default, the widgets library's binding takes care of sending this event.
--[no-]hot Run with support for hot reloading. Only available for debug mode. Not available with "--trace-startup".
fzyzcjy2022-10-27T23:00:18Z GitHub

@dnfield Sure.

Btw if you guys think the cost paid by CHECK is ok, I will change DCHECK to CHECK (pretty easy to change)

fzyzcjy2022-10-27T23:03:46Z GitHub

All right...

fzyzcjy2022-10-27T23:04:18Z GitHub

Thanks, I will discuss on discord about this

fzyzcjy2022-10-27T23:05:16Z GitHub

Thanks for the reply, I will ask on discord later

fzyzcjy2022-10-27T23:05:51Z GitHub

thanks, will discuss on discord further

fzyzcjy2022-10-27T23:09:17Z GitHub

but then realized this seems to be at odds with some similar work that was done for iOS - see pointer_data_dispatcher.h and in particular the SmoothPointerDataDispatcher.

Looking at https://github.com/flutter/engine/blob/6368dee0d78f4345e6cfdc4541754acb0891d845/shell/common/pointer_data_dispatcher.h#L157, seems that SmoothPointerDataDispatcher is just delaying one packet. On the other hand, this PR tries to merge multiple packets into one bigger packet, so we do not pay extra cost of PostTask, call Dart, etc.

then realized this seems to be at odds with some similar work that was done for iOS - see pointer_data_dispatcher.h and in particular the SmoothPointerDataDispatcher.

That one seems to be queueing

(you're capturing this and posting it to a different task runner in an unsafe manner).

This is just minor details and I will fix them definitely

I would suggest filing a specific bug for this issue with more details about which platform(s) you're observing this behavior on and how what you need is different from the SmoothPointerDataDispatcher (if it's different at all). If you're on Android, it would probably be worth doing an experiment where the smooth dispatcher is enabled there.

Thanks I will do that

dnfield2022-10-27T23:09:34Z GitHub

It'd be really helpful if you could share some measurements (including device/platform/runtime mode).

dnfield2022-10-27T23:10:03Z GitHub

CHECK will cause a crash at runtime. DCHECK is probably fine here to make tests fail.

fzyzcjy2022-10-27T23:12:04Z GitHub

Thanks for pointing out the related PR.

Similar concerns as to that one: we need some motivating benchmarks that clearly show what benefits would be gained here, and we need to see that changes to this won't negatively impact existing benchmarks/users.

Sure. I will try to make one.

we need to see that changes to this won't negatively impact existing benchmarks/users.

May I know how to do this? does not see benchmark data on CI IMHO

dnfield2022-10-27T23:13:08Z GitHub

flutter-flutter-perf.skia.org and flutter-engine-perf.skia.org has benchmark data

fzyzcjy2022-10-27T23:16:37Z GitHub

@dnfield Btw I have replied to your review comments (not sure whether you can see that so also reply here)

fzyzcjy2022-10-27T23:18:08Z GitHub

@dnfield Thanks, may I know some doc, or how to see perf benchmark of a PR? Click "help" gives http://go/perf-user-doc which cannot be opened (google internal only?)

fzyzcjy2022-10-27T23:19:53Z GitHub

@dnfield Let me find an old screenshot from my past experiment (https://github.com/fzyzcjy/yplusplus/issues/6124#issuecomment-1272830057)

image image image image

after fix

image image

fzyzcjy2022-10-27T23:21:01Z GitHub

@dnfield I agree. Will try to figure out some methods.

fzyzcjy2022-10-27T23:21:36Z GitHub

Ok I will discuss the pull model later maybe on discord

fzyzcjy2022-10-27T23:22:00Z GitHub

Btw tests are deliberately not passed since I want to have a quick rough review first before working into details

fzyzcjy2022-10-27T23:23:50Z GitHub

@dnfield I see

We cannot just keep shoving frames at the system compositor, we will overwhelm it and it will start dropping more frames than necessary/run into long GPU stalls. This will be really bad on older hardware, and still be bad on newer hardware.

May I know a bit more details?

By the way, this PR does not shoving too many frames. Indeed, it has one and exactly one rasterization ending (i.e. submit data to OS) in each vsync interval. Thus, it behaves exactly the same as a super-smooth app, which also provide one and exactly one data to system per vsync interval.

fzyzcjy2022-10-27T23:24:31Z GitHub

I see, will try to work towards the target

dnfield2022-10-28T16:35:25Z GitHub

What happens when an application decicdes to just repeatedly call this method? How does it know that it's only called it once per vsync?

fzyzcjy2022-10-28T23:18:17Z GitHub

@chunhtai sure, done

fzyzcjy2022-10-28T23:26:00Z GitHub

@dnfield I see your point: If repeatedly call this method, and at the same time the whole UI thread pipeline is much faster than 16ms, then we end up running multiple UI thread pipeline inside one vsync interval. In other words, multi window.render per vsync interval. I admit is a waste - but it is the user who is doing the wrong thing ;) Just like, users can put a ton of Opacity and see rasterizer jank, or they can run sync operations on ui thread and observe ui jank, etc. Nobody can stop them from doing the wrong thing and observe bad outcome.

fzyzcjy2022-10-28T23:27:02Z GitHub

@cbracken done https://discord.com/channels/608014603317936148/608018585025118217/1035696084963577957

Btw, is it polite to ask for test exemption directly when creating this PR or I should wait for a few days?

Hixie2022-10-28T23:27:34Z GitHub

test-exemption: code refactor with no semantic change

chunhtai2022-10-28T23:33:39Z GitHub

Btw, is it polite to ask for test exemption directly when creating this PR or I should wait for a few days?

If you think the PR should be exempted, I don't think there is an inappropriate grace period to ask for NTE as far as I know.

fzyzcjy2022-10-28T23:37:49Z GitHub

@chunhtai I see, thank you

auto-submit2022-10-29T01:43:38Z GitHub

auto label is removed for flutter/flutter, pr: 114117, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

fzyzcjy2022-10-29T13:41:43Z GitHub

Minor code cleanup: remove redundant return

Find this when reading source code. Maybe next time it can be caught by a linter :)

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Hixie2022-10-31T18:41:49Z GitHub

test-exemption: code refactor with no semantic change

That said, it would be great to file an issue on the linter to ask for a lint to catch this kind of thing.

auto-submit2022-10-31T22:20:08Z GitHub

auto label is removed for flutter/flutter, pr: 114290, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

fzyzcjy2022-10-31T22:47:12Z GitHub

That said, it would be great to file an issue on the linter to ask for a lint to catch this kind of thing.

Totally agree, here it is: https://github.com/dart-lang/linter/issues/3804

fzyzcjy2022-11-01T03:58:28Z GitHub

Tiny fix about outdated message

The message is in _ensureOutputIsNotJsonRpcError, and that method is called both in runSkia and runRasterizer. Thus, it is not reasonable to say "... skia output ..." because it can also be a rasterizer output.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

jiahaog2022-11-01T06:53:52Z GitHub

auto label is removed for flutter/flutter, pr: 114290, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

This seems to be some sort of infrastructure issue. I've overridden the "Google testing" status check and this PR should be ok to land. Googlers, please see b/256753114 for more details.

fzyzcjy2022-11-01T07:39:12Z GitHub

Incorrect rendering of SnapshotWidget

Close https://github.com/flutter/flutter/issues/114398

Problem description

In short, the SnapshotWidget is rendered differently when enabled vs disabled.

Reproduction:

Details
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
final binding = TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('SnapshotWidget should have same result when enabled', (tester) async {
binding.window
..physicalSizeTestValue = const Size(10, 10)
..devicePixelRatioTestValue = 1;
addTearDown(() => binding.window
..clearPhysicalSizeTestValue()
..clearDevicePixelRatioTestValue());

final controller = SnapshotController(allowSnapshotting: false);
await tester.pumpWidget(MaterialApp(
debugShowCheckedModeBanner: false,
home: Container(
color: Colors.black,
padding: const EdgeInsets.only(right: 0.6, bottom: 0.6),
child: SnapshotWidget(
controller: controller,
child: Container(
margin: const EdgeInsets.only(right: 0.4, bottom: 0.4),
color: Colors.blue,
),
),
),
));

final imageWhenDisabled = await _captureImage(tester.element(find.byType(MaterialApp)));

controller.allowSnapshotting = true;
await tester.pump();

final imageWhenEnabled = await _captureImage(tester.element(find.byType(MaterialApp)));
await expectLater(imageWhenEnabled, matchesReferenceImage(imageWhenDisabled));
});
}

Future<ui.Image> _captureImage(Element element) {
assert(element.renderObject != null);
RenderObject renderObject = element.renderObject!;
while (!renderObject.isRepaintBoundary) {
renderObject = renderObject.parent! as RenderObject;
}
assert(!renderObject.debugNeedsPaint);
final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer;
return layer.toImage(renderObject.paintBounds);
}

If you dump the images, will see:

imageWhenDisabled image

imageWhenEnabled image

How PR solves it

It seems that this is caused by integer rounding. For example, suppose our SnapshotWidget (and thus its Layer) is 9.4 pixels, then after toImageSync will get a 10x10 (or 9x9?) ui.Image instead of a 9.4x9.4 image - since image size must be integer. Then, next time we paint this ui.Image, the original code will paint the 10x10 rectangle area into the 9.4x9.4 Canvas, thus causing resizing of the content. The PR will remember the real size is 9.4 instead of 10, so next time when painting the ui.Image we will paint the 9.4x9.4 rectangle area into the 9.4x9.4 canvas, so no resize of content.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

goderbauer2022-11-01T22:26:45Z GitHub

Can you update this PR with the latest master to make sure what is actually changed by this?

fzyzcjy2022-11-01T23:36:33Z GitHub

Done merging (wait for ci though, but the idea is clear)

fzyzcjy2022-11-01T23:40:38Z GitHub

@jonahwilliams done

fzyzcjy2022-11-02T00:02:37Z GitHub

You are welcome!

(I will update code soon)

goderbauer2022-11-02T00:53:49Z GitHub

Sorry for the trouble, but you'll also have to rebase this to the latest master to make the "ci.yaml validation" check happy.

fzyzcjy2022-11-02T01:24:21Z GitHub

Done

fzyzcjy2022-11-02T01:44:28Z GitHub

Refactor usages of physicalSizeTestValue to simplify code and improve DX

Just a small refactor. Using:

  set physicalSizeCurrentTestValue(ui.Size value) {
physicalSizeTestValue = value;
addTearDown(clearPhysicalSizeTestValue);
}

We can make setting physical size test values a bit better in the following two aspects:

  1. Code is less duplicated. Originally need to specify set value + clear value, now only need one call
  2. Writing new tests are less error-prone, especially for new learners of Flutter, thus increasing DX. When I firstly learn Flutter, I often wrongly write down set test value calls, without the clear value calls. Then the tests works pretty well when isolated, but you know, it fails weirdly when run sequentially because the later tests have wrong physical size. As a new learner of Flutter (years ago), it took me some time before realizing it is this bug. Thus, it would be great to avoid the possibility of such problem from the beginning.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-02T01:52:26Z GitHub

Fix error when resetting configurations in tear down phase

(This is WIP, since I want to see whether this change will make regression test fail or not) ready for review

Consider this simple example

import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('addTearDown should work', (tester) async { // <-- THIS FAILS
timeDilation = 2;
addTearDown(() => timeDilation = 1);
});

testWidgets('directly reset should work', (tester) async { // <-- this is ok
timeDilation = 2;
timeDilation = 1;
});
}

It yields:

Details
/Volumes/MyExternal/ExternalRefCode/flutter/bin/flutter --no-color test --machine --start-paused test/a.dart
Testing started at 09:50 ...

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown running a test:
The timeDilation was changed and not reset by the test.

When the exception was thrown, this was the stack:
#0 SchedulerBinding.debugAssertNoTimeDilation.<anonymous closure> (package:flutter/src/scheduler/binding.dart:662:9)
#1 SchedulerBinding.debugAssertNoTimeDilation (package:flutter/src/scheduler/binding.dart:665:6)
#2 TestWidgetsFlutterBinding._verifyInvariants (package:flutter_test/src/binding.dart:968:12)
#3 AutomatedTestWidgetsFlutterBinding._verifyInvariants (package:flutter_test/src/binding.dart:1433:11)
#4 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:952:7)
<asynchronous suspension>

The test description was:
addTearDown should work
════════════════════════════════════════════════════════════════════════════════════════════════════

Test failed. See exception logs above.
The test description was: addTearDown should work

In other words, we do not allow resetting configurations in addTearDown (or tearDown). Instead, we must do it at the end of the closure.

IMHO resetting things in addTearDown/tearDown is a commonly seen practice. For example, window.physicalSize can be reset in a tear-down function, and even Flutter test code inside the framework does so a lot of times. A quick search also shows that, such as this example, resetting in tear down holds even for other tests such as Python.

By disallowing so, Flutter devs may have a bit more friction when learning Flutter, since they may firstly write down code that follows common practice, realizing it does not work, and change it.

It is also inconsistent with other parts of the Flutter. As mentioned above, window.physicalSize can be reset in a tear-down function, but things like timeDilation cannot.

The PR can also make code a bit simpler. Originally, whenever writing setup code (e.g. timeDilation=2), we must put tear down code at the end of the function, and wrap with a try-finally. But with the PR, it can be put near the setup code, so it is a bit cleaner for code readers. By the way, this is also a bit like the "defer" keyword in go - something like configure(); defer reset(); other_functions() will let the reset be executed last.

In some cases, this seems to simplify code a lot. For example, in https://github.com/flutter/flutter/pull/113830#discussion_r1005887028, I have to introduce a weird timeDilation reset that seems to have no pairing timeDilation modification (and cause confusion of readers - even code reviewers). With this PR, the reset will be put to the next line of modification, so it is clear.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-02T02:05:52Z GitHub

ping :) @caseyhillers, since

If you still see failures, feel free to ping a Googler for help. You're welcome to ping me on any PRs (my GitHub is @caseyhillers) and I'll take a look. There's an internal switch to mark it as passing as if rebasing isn't working, it likely indicates a separate PR is causing the failures. https://discord.com/channels/608014603317936148/608018585025118217/1037051780443414628

CaseyHillers2022-11-02T02:16:00Z GitHub

ping :) @CaseyHillers

You can ignore this failure for now. Google Testing can only run if a Flutter hacker approves your PR. Internally, it says there's no LGTMs, and it marks the status as failed (there's an internal tracking bug for making this status show pending instead of failing)

fzyzcjy2022-11-02T02:18:49Z GitHub

@CaseyHillers Thanks, get it

jonahwilliams2022-11-02T03:00:23Z GitHub

FYI @dnfield / @goderbauer as secondary review

fzyzcjy2022-11-02T05:31:06Z GitHub

Tiny improvement of RouteSettings display

When name is null, originally it prints something like: RouteSettings("null", null). But we know "null" looks like a string instead of a real null. So I change to RouteSettings(null, null) when that is null.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-02T08:04:03Z GitHub

Tiny code cleanup: remove unnecessary comparisons

The flags themselves are normal variables, so seems no need to check equality before assigning.

This PR is so small that I dare not ask for a test exemption or a review :P (Anyway I will continue working on flutter_smooth and those big PRs in a few days when I am not that busy)

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-11-02T08:04:06Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-11-02T13:39:44Z GitHub

Ensure methods like dispose are not async by accident

Tiny PR, just ensure nobody will accidentially write sth like Future<void> didChangeDependencies() or Future<void> dispose

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-02T13:56:28Z GitHub

Clarify what AnimatedBuilder really speeds up when using child

When I was reading AnimatedBuilder doc (when not that familiar w/ Flutter), I wrongly have the following conclusion: Suppose I have the following, and MyWidget constructs a huge tree:

return AnimatedBuilder(
builder: () => AnimatedOpacity(child: Padding(padding: ..., child: MyWidget())),
);

vs

return AnimatedBuilder(
builder: (child) => AnimatedOpacity(child: child),
child: Padding(padding: ..., child: MyWidget()),
);

Then, reading the doc, I think this will be very little performance boost when using this child. It is because,

If your builder function contains a subtree that does not depend on the animation, it's more efficient to build that subtree once instead of rebuilding it on every animation tick.

So I thought: Yes, I do have a subtree (Padding + MyWidget) that does not change, and this trick can make them built once instead of many times. But it is very fast, because it just creates two objects! (Here, I understood the "a subtree" in the doc as the Padding+MyWidget two objects, while in reality we know it should mean the whole subtree that MyWidget and its descedent widgets build.)

Thus, I add a few lines to clarify this. Hope future flutter new learners do not get confused as me!

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-04T23:32:41Z GitHub

Secondly, I think having two similarly named setters, one that cleans up after itself and one that doesn't, is still pretty unintuitive for developers. There is still a lot of potential for making a mistake, say by accidentally setting physicalSizeTestValue instead of physicalSizeCurrentTestValue and expecting it to automatically do clearPhysicalSizeTestValue.

What about renaming it to: physicalSizeTestValue vs physicalSizeTestValueAutoClear? Then nobody can use it by mistake since the latter says "auto clear"

fzyzcjy2022-11-04T23:41:53Z GitHub

ThemeData does not respect ColorScheme for TabBar, CheckBox, etc

Close https://github.com/flutter/flutter/issues/114601

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

fzyzcjy2022-11-05T03:00:01Z GitHub

@dnfield

(content of this comment is moved to: https://github.com/flutter/engine/pull/37341)

fzyzcjy2022-11-05T03:04:13Z GitHub

(content is also moved to #37341)

Details

@dnfield and we need to see that changes to this won't negatively impact existing benchmarks/users

Could you please give some hints? I guess I have no permission to execute skia perf as it seems only run on master or other flutter branches. Thus, maybe I can do nothing except for creating a PR and see skia perf after it is merged...

fzyzcjy2022-11-05T03:28:15Z GitHub

Fix jank and large-jumping frame by controlling rasterizer ending time (V2)

Previous: https://github.com/flutter/engine/pull/36837

fzyzcjy2022-11-05T05:38:58Z GitHub

Fix janks caused by await vsync in classical Flutter (V2)

Previous: https://github.com/flutter/engine/pull/36911

(Since the previous one is already closed and I have no permission to reopen, I guess I should create a new issue such that it can be put in the review queue)

As @dnfield points out in https://github.com/flutter/engine/pull/36911#issuecomment-1294089965:

we need some motivating benchmarks that clearly show what benefits would be gained here, and we need to see that changes to this won't negatively impact existing benchmarks/users.

Thus, reproduction is shown in the section below. As for existing benchmarks, could you please give some hints? I guess I have no permission to execute skia perf as it seems only run on master or other flutter branches. Thus, maybe I can do nothing except for creating a PR and see skia perf after it is merged...

Reproduction

Setup and analysis

In the benchmark, I deliberately make a dead loop to mimic the case when ui thread runs for smaller than but very close to 16.67ms. This is quite hacky and may not work on your device, so please modify the hardcoded time in the code if you cannot reproduce it. But hopefully this is ok as a "motivating" benchmark you mentioned :)

I also realize this seems hard to reproduce if the await vsync is called per 16ms. Instead, when I make ui thread deliberately janky (e.g. ~100ms), so that await vsync is not called for ~100ms, this is reproduced. Not sure why, but anyway regardless of why this happens, the PR can solve it.

In the screenshots below, I add red vertical lines manually by mimicking the VSYNC interval (this is the correct interval that I have fixed previously, so can use it). As we can see, the AwaitVsync is called before the vsync (which is imaginary and not shown in figure), but we miss it for a whole 16.67ms.

Data

Code: https://github.com/fzyzcjy/flutter/tree/feat/await-vsync-directly-call + standard engine build (because this is reproduction not bugfix) Run it: /path/to/flutter drive --profile -t test_driver/run_app.dart --driver test_driver/near_full_tim_perf_test.dart

Sample result timeline json: near_full_time_perf.timeline.json.zip

Sample screenshot:

image image

flutter-dashboard2022-11-05T05:39:01Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-11-05T05:47:02Z GitHub

...I would suggest filing a specific bug

done: https://github.com/flutter/flutter/issues/114738

fzyzcjy2022-11-05T05:48:33Z GitHub

Hi @iskakaushik may I know what previous work you have done? Thanks

fzyzcjy2022-11-05T05:52:18Z GitHub

So... What should I do now? I have already replied to all questions and it has been more than a week :)

fzyzcjy2022-11-05T06:20:26Z GitHub

@dnfield What about changing like this:

https://github.com/flutter/engine/pull/37343

fzyzcjy2022-11-05T06:37:08Z GitHub

Remove (3N-1) jank and big-jump when N rasterization misses deadline (V2)

This PR is just a rough sketch. If it looks OK, I will polish (e.g. add tests, refine code etc)

Previous: https://github.com/flutter/engine/pull/36912

Background: The original proposal completely removes back-pressure. Thus, suppose a rasterzation takes 1000ms, then we will be running ui thread unnecessarily for 60 times.

Proposed new change: Maybe we should conditionally remove back-pressure. More specifically, when we see PipelineFull, we only ignore it and continue running ui thread under the following condition: The current ongoing rasterization has not been running for a long time. This condition holds for the figure in my previous comment, so the main scenario that this PR wants to solve is happy. On the other hand, for the "1000ms-long-rasterization" case, we will only waste one single ui pipeline computation, instead of 60 computations, so the waste does not look like a lot. I agree one waste is indeed waste, but it is the cost to pay (user battery consumes a tiny bit more) if we want to make the UI that user feels completely smooth at 60FPS (which is one of the goals of Flutter IMHO).

Remark: Code comments also describe this as well.

fzyzcjy2022-11-05T07:17:20Z GitHub

We don't want to expose this kind of internal of the VM to users.

Hmm NotifyIdle seems very generic - there seems no VM internals, but only "hey I am idle for a period of time". It is not something like NotifyDartToDoYoungGC which is internals :) But instead, it is a well-defined interface so dev can tell flutter it is free.

Perhaps it would be more appropriate to expose a different API around whether an animation is occurring currently, although I think @iskakaushik has already done some work on that.

Sorry but I do not quite get it. In flutter_smooth (detailed design: https://cjycode.com/flutter_smooth/design/), a janky frame can last for a long time (for convenience of description, suppose 100ms). Then, during this whole period, the Dart in UI thread will be busy, so it is impossible to execute any C++ code unless Dart code explicitly do so. With this PR, flutter_smooth will explictly call notifyIdle with an explicit deadline of ~14ms (much longer than classical Flutter). This is a bit like the pull vs push model problem.

fzyzcjy2022-11-05T07:29:01Z GitHub

Same problems as the other NotifyIdle PR - these are details we don't want to expose to users.

Same confusion as in https://github.com/flutter/engine/pull/36797. Still think "dev tells system it can be idle for a period of time" is not something internal but a reasonable thing.

NotifyIdle might not do anything, or it might do a lot more than the user expects.

Totally agree - I have checked Dart NotifyIdle source code and saw it uses something like history data to guess how long GC will happen now, but it can run for longer or shorter time.

As for this specific case of GC: If it does not do anything, there is surely no problem. If it do a lot more, it is still no problem. This is because, a much longer execution means that, even if we do not NotifyIdle, GC will happen somewhere in the future with at least this time duration (because garbage only accumulates).

It is also like what we do with sleep. Dart (and all lang) have a sleep function and can specify how long to sleep. But as you have pointed out earlier, the thread may not be woken up at that time because of OS schedule. In other words, sleep may sleep a lot more than the user expects. Or, even consider this: Each and every line of Dart code (indeed Java/... as well), will have the possibility that, it takes much much longer time to finish! For example, needs 10ms to finish a single i++. This is just fact, not something that I make up - because we have the stop-the-world GC. When STW happens, the code just stuck. Furthermore, we know operating systems which Flutter targets schedule threads with preemption. Thus, it is totally possible that any line of code executes much longer than the user expects even if using C/C++/Rust/etc. So, shall we ban sleep from users if the same logic holds, or ban any language with GC, or ban any operating system that is not real-time ;) (Surely I am joking!)

In all honesty, it would be nice to get rid of it entirely since it is hard to reason about and seems to come out wrong very frequently. If users can change this arbitrarily it will make it much harder to reason about what's going on in an application.

Sorry but do not get it.

May I know a bit more details why "wrong very frequently"? IMHO it just "notifies it is idle" and the only way to be wrong is the user forgets it can run for a much longer time - then my discussions about sleep and STW GC holds.

And I also wonder why "much harder to reason about what's going on in an application"? IMHO it is shown in the timeline, so it is quite clear what is going on. And this is indeed a Dart VM specific thing, so it is even not that related to Flutter framework and engine.

fzyzcjy2022-11-05T07:52:10Z GitHub

https://github.com/flutter/engine/pull/36921#issuecomment-1294116970

Same comment as on the linked PR applies.

I have made a v2 of that PR: https://github.com/flutter/engine/pull/37341

In particular, it's really hard to reason about how to use this parameter correctly. A lot of the work you're doing seems to be geared towards ignoring/overriding the vsync that the system gives us instead of cooperating with it more.

I agree, it is overriding the system vsync. However, this is used for flutter_smooth, which really has to override some things in the engine...

Does it help if we mark it as @experimental? Then we can freely change it in the future without worrying potential users excluding flutter_smooth.

P.S. Why this is needed: https://cjycode.com/flutter_smooth/design/infra/brake/

In particular, vsync is an important source of backpressure that we must respect. We cannot just keep shoving frames at the system compositor, we will overwhelm it and it will start dropping more frames than necessary/run into long GPU stalls. This will be really bad on older hardware, and still be bad on newer hardware.

Replied above - IMHO seems not a problem?

fzyzcjy2022-11-05T08:00:22Z GitHub

allowing the developer to specify the vsync time is a big footgun, the developer is very likely to get this wrong and overwhelm the system.

If this API is for normal dev then I totally agree. So same as in the other reply - shall we mark it as "not suitable for normal dev" and mainly for flutter_smooth (and others who knows deeply about flutter)?

The direction of this information has to go in the opposite direction: we need to improve or make it easier for developers to understand the target vsync time and how much time they have left to do work rather than letting developers tell the system what they think vsync should be.

That's perfect for the standard flutter, but IMHO may be impossible for flutter_smooth (e.g. https://cjycode.com/flutter_smooth/design/infra/preempt/idea). In flutter_smooth, there are just tons of long janky frame (e.g. say one frame takes 100ms), and flutter_smooth trigger a lot of extra window.render.

Related: https://github.com/flutter/engine/pull/36438 - one onBeginFrame with multiple window.render.

fzyzcjy2022-11-05T08:04:22Z GitHub

It's not clear to me how this is any different from observing the Duration you get in onBeginFrame.

In flutter_smooth, one janky frame can be (e.g.) 100ms, and we need to submit window.render per vsync interval. When is the next vsync interval? This question is answered by this PR.

In other words, in the 100ms janky frame, onBeginFrame only provides first vsync target time, while we need to know the rest.

As I'm mentioning in some other PRs, let's discuss further on discord.

Sure, will discuss when you have time (guess you are on weekends so reply here firstly)

fzyzcjy2022-11-05T08:19:36Z GitHub

I'm still not quite clear on why this is a solution that should live in the framework. Tickers have no means of interrupting work more often. Tickers have no special knowledge of when vsync happens. This seems to introduce a number of problems that will be difficult for developers to reason about. I think this could be solved in a package instead.

Totally agree. However, I cannot find out a method to let it live in a package while making flutter_smooth implementable. flutter_smooth need to support AnimationController and everything built on it (CircularProgressIndicator, SlideTransition, ...), so it seems impossible to create a 3rd party package. Otherwise, at least users cannot use any builtin widget such as CircularProgressIndicator and higher-level widgets, as long as a widget directly or indirectly depends on AnimationController/TickerProviderMixin. Users also cannot use most of the other 3rd party packages, because they use Flutter's AnimationController/TickerProviderMixin as well.

I'm going to close this for now but let's discuss more on discord if you like.

(Firstly reply here before discussing on Discord since I guess you are on weekends)

dnfield2022-11-05T19:43:59Z GitHub

Part of the work to make this benchmark would be coming up with an at least somewhat realistic case where UI thread work goes to just over vsync budget. Yes, you can construct a benchmark that does exactly that, but what's an example of a real application that actually behaves that way on a real device? IME, it's more common to see an application that far exceeds budget on some frames and then is under budget on many other frames, rather than one that is consistently just slightly over budget. In fact, if you had one that was consistently over budget, you should instead look to optimize your consistent workload to be less - it's an application problem rather than an engine one. But right now, we don't have good ways to fix the problem where some frames over budget despite the fact that you're not doing anything all that unreasonble - for example, oyu're showing a screen full of text that can't all do its initial layout within frame budget on your target device.

You should be able to run the devicelab benchmarks locally and do experiments before and after -see https://github.com/flutter/flutter/blob/master/dev/devicelab/README.md

I'm closing this PR for the same reasons as the previous PR right now - it's not in a state that is ready for review, and much of this content might be more appropriate for an issue right now.

dnfield2022-11-05T19:45:57Z GitHub

No, conditionally removing back-pressure is still bad: it will cause other problems for sure, since we're conditionally letting the CPU get ahead of the GPU. It'll just be harder to figure out when that's happening and fix it.

fzyzcjy2022-11-05T21:58:20Z GitHub

@dnfield

Yes, you can construct a benchmark that does exactly that, but what's an example of a real application that actually behaves that way on a real device? IME, it's more common to see an application that far exceeds budget on some frames and then is under budget on many other frames, rather than one that is consistently just slightly over budget.

IMHO it is more like a math/statistics problem than a programming problem. Using my observation from previous issue (this problem happens as long as 2ms before deadline), and a vsync interval is 16.67ms. Then, for a very rough estimation, suppose the real time used is uniform from shortest time to longest time possible - this is very rough, but it somehow makes sense because an app targets from highest-end to lowest-end devices. Then, we have 2/16.67 = 12% probability. Surely that does not sound huge, but it does happen and affect user feeling. Indeed, my flutter_smooth's goal is 60FPS (on 60FPS machine) and this single problem drops 7.2FPS if using the above very rough analysis - and in reality I roughly see more drops.

So, answering "it's more common to see..." - surely yes, but Flutter is a high-performance framework and wants to be 60FPS, instead of a framework that allows jank from time to time ;)

In fact, if you had one that was consistently over budget, you should instead look to optimize your consistent workload to be less - it's an application problem rather than an engine one

By the way, this bug is not only about "over budget" - if ui thread runs over 16.67ms (on 60hz machine) one can never get 60FPS (but can still be 60FPS with flutter_smooth - that's another story). In addition, a frame that is computed quicker than 16.67ms in one phone may be slower than 16.67ms on another lower end phone.

But right now, we don't have good ways to fix the problem where some frames over budget despite the fact that you're not doing anything all that unreasonble - for example, oyu're showing a screen full of text that can't all do its initial layout within frame budget on your target device.

Indeed we have - flutter_smooth :) That example is indeed the first example that I have solved when prototyping (btw thanks for providing that example as a canonical test base!). (As long as all PRs are merged after discussions and modifications)

You should be able to run the devicelab benchmarks locally and do experiments before and after -see flutter/flutter@master/dev/devicelab/README.md I'm closing this PR for the same reasons as the previous PR right now - it's not in a state that is ready for review, and much of this content might be more appropriate for an issue right now.

I see, thanks (originally I used macrobenchmark folder directly)

fzyzcjy2022-11-05T21:59:41Z GitHub

@dnfield

it will cause other problems for sure, since we're conditionally letting the CPU get ahead of the GPU. It'll just be harder to figure out when that's happening and fix it.

May I know a bit more about "other problems"? Because without knowing more, I have no idea what they are thus how to solve it.

pdblasi-google2022-11-07T19:51:34Z GitHub

I like the goal of this (easier tests, less likely to make mistakes), but I have a couple of concerns with this as implemented:

  1. Does this hide a problem, rather than addressing it?
    • It's pretty common that addTearDown and tearDown calls are missed as you pointed out in your description. This PR makes it easier to not miss those calls for a few specific cases, but it doesn't really address why those calls are missed or drive users to write better tests in other cases.
  2. Does adding these methods here set a standard that the testing framework will be expected to stick to elsewhere?
    • How do we determine which properties or fields should/shouldn't have versions that auto clear?
    • How should adding those auto clear versions be prioritized?
    • If/when those versions break, should it be considered a breaking change and should the breaking change process be followed for just the auto clear version, or for auto clear and standard versions?
  3. Will this cause unintuitive behavior in other use cases?
    • Specifically I'm thinking of someone trying to use this for setting up a group of tests. Since this makes use of addTearDown, it'll only behave as expected for individual tests. If someone tries to use the "AutoClear" version in a group, it'll throw an exception.
    • In combination with number 2, would any "AutoClear" versions also need an "AutoClearGroup" version?

Personally, I'd lean towards pushing the convenience method part to a separate package where the expectations for support, maintenance, and coverage won't be as high. It'd also allow for more community contributions for different conveniences and extensions to be added without needing to go through as much process as this repo requires. As for the fixes to our own tests, it's a great catch and should be included, but IMO without using the convenience method.

fzyzcjy2022-11-07T23:24:50Z GitHub

@Renzo-Olivares @justinmc done :)

fzyzcjy2022-11-07T23:31:54Z GitHub

@pdblasi-google

Does this hide a problem, rather than addressing it? It's pretty common that addTearDown and tearDown calls are missed as you pointed out in your description. This PR makes it easier to not miss those calls for a few specific cases, but it doesn't really address why those calls are missed or drive users to write better tests in other cases.

So what do you think is the reason "why those calls are missed"? I indeed am not sure - maybe because they just forget?

Does adding these methods here set a standard that the testing framework will be expected to stick to elsewhere? How do we determine which properties or fields should/shouldn't have versions that auto clear?

For fields that have a set somethingTestValue and clearSomethingTestValue I guess?

How should adding those auto clear versions be prioritized?

IMHO this is quite trivial to implement (if you like I can submit more)

If/when those versions break, should it be considered a breaking change and should the breaking change process be followed for just the auto clear version, or for auto clear and standard versions?

Sorry, not sure about the question

Will this cause unintuitive behavior in other use cases? Specifically I'm thinking of someone trying to use this for setting up a group of tests. Since this makes use of addTearDown, it'll only behave as expected for individual tests. If someone tries to use the "AutoClear" version in a group, it'll throw an exception. In combination with number 2, would any "AutoClear" versions also need an "AutoClearGroup" version?

Luckily, not a problem! If you like I can put the following code which automatically uses addTearDown vs tearDown and is used internally in my app:

void autoSetUpTearDownOrAddTearDownSync(
void Function() setUpBody,
void Function() tearDownBody,
) {
// ref: test_api :: test_structure.dart :: addTearDown
// `Invoker.current==null` will lead to a message `addTearDown() may only be called within a test.`
// so use this to determine whether in test
if (Invoker.current == null) {
setUp(setUpBody);
tearDown(tearDownBody);
} else {
setUpBody();
addTearDown(tearDownBody);
}
}
pdblasi-google2022-11-08T21:31:57Z GitHub

@fzyzcjy

Personally, I think those calls are missed because they aren't made obvious to users through documentation and examples. As such, I'm not sure hiding those calls behind convenience methods promotes better usage of those methods.

Adding the convenience methods also increases our API surface, which increases our maintenance burden, and sets up expectations for other parts of the testing framework that don't actually solve the problem of those methods being missed, but instead makes it even less likely that users will run into an example using them correctly.

Long story short, I don't think the convenience methods are worth the long term cost of introducing that pattern to the framework. Hence suggesting creating a package for them, where the maintenance costs and coverage expectations are going to be lower.

It's also worth noting that there's work at the moment to move from a single window to multiple views (to support desktop better), which may be a great opportunity to improve on these particular fields' APIs from the ground up. I believe @goderbauer is driving that initiative, though he's out until next Tuesday.

fzyzcjy2022-11-08T22:25:29Z GitHub

@pdblasi-google

Personally, I think those calls are missed because they aren't made obvious to users through documentation and examples.

Not sure how other people do, but I am not this case personally. I have this simply because I forgot to call the clear :/ I do vaguely remember the clear, and if you test me in a questionaire I will remember it, but when programming I just set the value - doing the minimal necessary work - while forgetting the clear.

Hence suggesting creating a package for them

Then what should we do for the flutter framework itself? Or shall we mark them @internal so flutter framework can use it?

It's also worth noting that there's work at the moment to move from a single window to multiple views (to support desktop better), which may be a great opportunity to improve on these particular fields' APIs from the ground up. I believe @goderbauer is driving that initiative, though he's out until next Tuesday.

Looks interesting!

pdblasi-google2022-11-08T22:37:43Z GitHub

If the convenience methods are moved to a package, I would suggest just not using them in the flutter repo and updating the tests here to use the addTearDown methods where appropriate to clear the values. That way, those tests can serve as an example of how to use the standard addTearDown pattern.

fzyzcjy2022-11-08T22:43:07Z GitHub

not using them in the flutter repo*

Then the tests are not DRY ;)

pdblasi-google2022-11-08T22:58:52Z GitHub

Then the tests are not DRY ;)

I mean, if you want to get the tests really DRY, then the setup/teardown would be moved out to the group level and done either once for the entire group, or set up to happen before and after each test with the group level methods.

Since we're working on a public API though, we don't necessarily want to be DRY on everything. We have to balance trying to keep our internal stuff as clean as we can with not making the API too specific to our internal use cases.

fzyzcjy2022-11-08T23:01:58Z GitHub

then the setup/teardown would be moved out to the group level and done either once for the entire group

Well, those tests are not in the same file, so IMHO they cannot be solved by group.

Since we're working on a public API though, we don't necessarily want to be DRY on everything. We have to balance trying to keep our internal stuff as clean as we can with not making the API too specific to our internal use cases.

I see. Btw, it is not "too specific to our internal use cases" as it is used everyday by external users

dnfield2022-11-09T00:16:32.941+00:00 Discord
dnfield2022-11-09T00:16:33.499+00:00 Discord

Yes! I think there are a number of things (sorry, I've been in meetings)

dnfield2022-11-09T00:17:03.341+00:00 Discord

Changed the channel name.

dnfield2022-11-09T00:17:30.113+00:00 Discord

So there are a few things that I think are needed: we need to make sure we don't make it "easy" for developers to ignore backpressure from the GPU

dnfield2022-11-09T00:18:01.518+00:00 Discord

We need to make sure that we're not relying on the OS to schedule us exactly when we'd like if we choose to deschedule, and that we're not spending significant amounts of time spinning to avoid descheduling

fzyzcjy2022-11-09T00:43:01.177+00:00 Discord

It's okay

fzyzcjy2022-11-09T00:43:22.507+00:00 Discord

Definitely

fzyzcjy2022-11-09T00:45:12.891+00:00 Discord

Btw I have replied to your questions in most PRs previously

fzyzcjy2022-11-09T00:45:42.673+00:00 Discord

(just a reminder in case github does not send notification)

fzyzcjy2022-11-09T00:47:58.683+00:00 Discord

we don't make it "easy" for developers to ignore backpressure from the GPU So continuing from https://github.com/flutter/engine/pull/37343 - may I know a bit more about why conditionally removing back-pressure is bad?

fzyzcjy2022-11-09T00:48:40.905+00:00 Discord

We need to make sure that we're not relying on the OS to schedule us exactly when we'd like if we choose to deschedule, and that we're not spending significant amounts of time spinning to avoid descheduling Hope my thoughts here help a little bit: https://github.com/flutter/engine/pull/37341#issuecomment-1304649243

dnfield2022-11-09T00:50:16.496+00:00 Discord

The purpose of GPU backpressure is to help make sure we don't end up outrunning the GPU on the CPU, which will lead to jank because the GPU can't give us buffers fast enough. We've had issues with this for example even when we tried to use extensions on Android wher it would tell the GPU to drop all but the latest buffer submitted per vsync (led to jittering sometimes which looked worse/as bad as jank).

fzyzcjy2022-11-09T00:53:12.21+00:00 Discord

we don't end up outrunning the GPU on the CPU, which will lead to jank because the GPU can't give us buffers fast enough. A bit confused... Let's say GPU is super janky and takes 100ms + CPU outrunning it and compute 6 scenes (though my proposal will not be this bad). Then, IMHO the main problem is CPU waste battery, because the scene is thrown away. However, seems that the jank will be exactly the same, no matter whether CPU computes 6 scenes or 0 scenes, because the bottleneck is GPU?

dnfield2022-11-09T00:55:11.278+00:00 Discord

The problem is more like: the GPU has 3 or 4 buffers to work with. If you keep asking it to give you buffers to fill when it's busy with them, you're taking time from it doing the work it needs to do to show pixels on the screen, and because you're distracting it from the work it needs to be doing now you're making everything worse for subsequent frames (until you finally stop and let it finish its work queue).

fzyzcjy2022-11-09T00:55:39.552+00:00 Discord

Ah interesting insight!

dnfield2022-11-09T00:56:38.149+00:00 Discord

So you could write a program that just continually asks the GPU to hand you a buffer to draw into, but you'll overwhelm the GPU and won't be able to draw as many frames. The solution is to listen to vsync and do your best to only ask for/submit a buffer once per vsync.

fzyzcjy2022-11-09T00:57:23.428+00:00 Discord

asks the GPU to hand you a buffer to draw into May I know where the code in flutter does this? Is it happened during the rasterization, or happen when ui thread creates a scene tree? (I guess former?)

fzyzcjy2022-11-09T00:58:23.313+00:00 Discord

If the former, then imho, whether ui thread is computing extra scenes is not a problem.

fzyzcjy2022-11-09T00:58:50.366+00:00 Discord

because ui thread does not consume GPU buffers, and also do not disturb rasterizer from doing anything fancy

dnfield2022-11-09T00:59:43.075+00:00 Discord

It happens implicitly when the raster task runner asks for a surface

dnfield2022-11-09T01:00:12.147+00:00 Discord

And we tell the rasterizer to do that when the animator is done getting a frame from the framework

fzyzcjy2022-11-09T01:00:35.551+00:00 Discord

when the animator is done getting a frame from the framework so, a window.render?

fzyzcjy2022-11-09T01:00:51.678+00:00 Discord

i.e. Animator::Render

dnfield2022-11-09T01:01:27.145+00:00 Discord

yes

fzyzcjy2022-11-09T01:01:41.443+00:00 Discord

Then imho it is no problem, at least for https://github.com/flutter/engine/pull/37343

fzyzcjy2022-11-09T01:01:53.332+00:00 Discord

because imho we just do extra onBeginFrame, not extra window.render

fzyzcjy2022-11-09T01:02:17.074+00:00 Discord

hmm wait a bit

fzyzcjy2022-11-09T01:05:06.429+00:00 Discord

my reasoning is: in this pr, what I did is, BeginFrame will run ui thread pipeline, even if it sees rasterizer queue is full. when ui thread finishes the pipeline, it calls window.render thus Animator::Render. then, if rasterizer queue is still full, it will drop the Scene (ensured by pipeline.h implementation); if not full, it will enqueue the Scene. Thus, IMHO from rasterizer's eyes it is not different.

dnfield2022-11-09T01:05:15.434+00:00 Discord

once you call onBeginFrame, the framework will eventually call window.render right?

fzyzcjy2022-11-09T01:05:26.365+00:00 Discord

sure, but that window.render may be useless

fzyzcjy2022-11-09T01:05:38.292+00:00 Discord

that will cause a waste, but no harm to rasterizer

fzyzcjy2022-11-09T01:06:05.066+00:00 Discord

a waste because the work ui thread has done (the Scene) is not used

fzyzcjy2022-11-09T01:06:42.771+00:00 Discord

(if you are worried about this waste I can explain more - shortly speaking it may be worthwhile; but anyway we are discussing rasterizer and gpu now so I do not want to distract)

dnfield2022-11-09T01:07:58.925+00:00 Discord

If we have a benchmark that shows improvement from this it's probably worth exploring. Right now I'm a bit skewed towards saying this will be bad because it will consume more CPU/power, and on lower end devices that will hurt a lot

dnfield2022-11-09T01:08:57.58+00:00 Discord

The other thing to consider with that is what happens when you succeed: does the new frame that gets added to the pipeline look like it "fits" well with the previous and next frames, or does it get rendered in an unexpected vsync and end up looking slightly off/jittery

fzyzcjy2022-11-09T01:09:25.177+00:00 Discord

will be bad because it will consume more CPU/power, and on lower end devices that will hurt a lot That happens only if it is really wasted. However, looking at figure in https://github.com/flutter/engine/pull/36912 - which is the main case the proposal is to solve - ui thread work is not wasted at all

fzyzcjy2022-11-09T01:10:23.043+00:00 Discord

If we have a benchmark that shows improvement from this it's probably worth exploring Is it OK to be a benchmark like https://github.com/flutter/engine/pull/37341, i.e. a one with tons of deliberately blank loops?

dnfield2022-11-09T01:10:30.054+00:00 Discord

Sure, but this is not the typical problem that I see when working on real applications on lower end devices 🙂

fzyzcjy2022-11-09T01:10:50.084+00:00 Discord

Btw what is the typical problems?

dnfield2022-11-09T01:11:08.05+00:00 Discord

The more typical problem is a frame that just blows way through frame budget (e.g. 40-50ms) to build.

fzyzcjy2022-11-09T01:11:09.048+00:00 Discord

Indeed I aim at 60FPS (not 55FPS etc) so each corner case I face needs to be solved 🙂

fzyzcjy2022-11-09T01:11:20.096+00:00 Discord

That's just https://github.com/fzyzcjy/flutter_smooth solves! 😄

fzyzcjy2022-11-09T01:12:17.079+00:00 Discord

Indeed, I do not realize PR 36912 is even a problem, before I have finished the main part of flutter_smooth

fzyzcjy2022-11-09T01:12:45.259+00:00 Discord

In other words: flutter_smooth solves the main type of problem, then the problems that are less likely to happen now become main problems that makes me about 50-55FPS but not ~60FPS

dnfield2022-11-09T01:12:52.502+00:00 Discord

That draws something like a contacts list. On a low end Android device, building that scene can easily take 27-30ms, mostly in text layout

fzyzcjy2022-11-09T01:13:15.485+00:00 Discord

IIRC my earliest prototype of flutter smooth have already solved that 🙂

fzyzcjy2022-11-09T01:13:54.441+00:00 Discord

(at that time it is not called flutter_smooth)

dnfield2022-11-09T01:13:57.711+00:00 Discord

I've yet to see a full, runnable solution that solves it though. My recollection (which may be wrong) is that the proposals tended to have a lot of comments like "fix this later"

fzyzcjy2022-11-09T01:14:29.481+00:00 Discord

Ah, sure, I should make a full runnable app now (but with custom framework and engine code)

fzyzcjy2022-11-09T01:15:47.605+00:00 Discord

Smooth animation (material page route) when entering a page, and that page is really heavy to build (using ComplexWidget to simulate, can be seen in code)

fzyzcjy2022-11-09T01:16:35.389+00:00 Discord

is that sound interesting to you? or I need to make an exact reproduction again for list_text_layout

dnfield2022-11-09T01:16:42.325+00:00 Discord

I am not convinced that it is actually the same. And even if it is the same problem, it would need to be adopted pretty widely before we could land changes to the engine that only make sense if you're using it.

dnfield2022-11-09T01:17:00.772+00:00 Discord

I'd like to see what it looks like to use your solution to fix the list_text_layout to be faster

dnfield2022-11-09T01:17:10.546+00:00 Discord

(even if it requires a custom engine or framework build to achieve)

fzyzcjy2022-11-09T01:18:46.983+00:00 Discord

(forget to reply this) It looks well IMHO. But if you are having a rasterization that is longer than 16ms, then it is inevitable to have one jank feeling. see the main figure of PR for more details. And, as for flutter_smooth (note: the PR also holds for classical flutter), an extra doc page: https://cjycode.com/flutter_smooth/benchmark/analyze/linearity/ (I made up a term called linearity, which is what you mention as jittery I guess)

fzyzcjy2022-11-09T01:19:36.661+00:00 Discord

Sure. https://github.com/fzyzcjy/flutter_smooth/issues/173

fzyzcjy2022-11-09T01:20:58.2+00:00 Discord

A bit confused: Do you mean flutter_smooth should be adopted widely before flutter engine can change?

fzyzcjy2022-11-09T01:21:32.062+00:00 Discord

But imho most people (including me) will never use something that is not in flutter stable for production app

fzyzcjy2022-11-09T01:22:44.209+00:00 Discord

But the stars (>700) may show that many are interested in it

dnfield2022-11-09T01:22:49.192+00:00 Discord

So I said that a patch you opened in the engine will fail to help some common sources of jank and actually potentially make them worse on low end devices. If I understand, you're saying those common sources go away with flutter_smooth so that's not a problem, and you're moving on to other sources of jank once you solve those common problems.

fzyzcjy2022-11-09T01:23:50.994+00:00 Discord

image

fzyzcjy2022-11-09T01:23:55.407+00:00 Discord

4th popular in this year

dnfield2022-11-09T01:23:56.228+00:00 Discord

Put more concretely: flutter has some very large customers who don't use flutter_smooth. We can't make changes to the engine that will make their applications worse. If flutter_smooth helps those applications, we should try to integrate it into the framework

dnfield2022-11-09T01:24:00.108+00:00 Discord

That's very cool 🙂

fzyzcjy2022-11-09T01:24:35.639+00:00 Discord

and even get to GitHub trending main board twice (I really did not expect that - you know github trending main board contains all languages, so almost impossible for a Flutter/Dart project to be there)

fzyzcjy2022-11-09T01:25:11.637+00:00 Discord

https://github.com/search?q=flutter_smooth&type=code can see some spiders get that trending data - 10.30 and 10.21

fzyzcjy2022-11-09T01:25:54.708+00:00 Discord

imho maybe not "make them worse".

If I understand, you're saying those common sources go away with flutter_smooth so that's not a problem, and you're moving on to other sources of jank once you solve those common problems. Yes

fzyzcjy2022-11-09T01:26:26.282+00:00 Discord

definitely, it is also very interesting to be integrated to flutter

fzyzcjy2022-11-09T01:26:52.132+00:00 Discord

so... what makes it worse if having the PR on low end device?

fzyzcjy2022-11-09T01:27:12.393+00:00 Discord

except that I do not provide a benchmark yet (since you requested just a few minutes ago 🙂 )

fzyzcjy2022-11-09T01:27:33.56+00:00 Discord

the rasterizer problem seems not to be a problem as said above

fzyzcjy2022-11-09T01:27:36.836+00:00 Discord

or I understand wrongly

dnfield2022-11-09T01:27:37.825+00:00 Discord

Doing more work will make things worse

fzyzcjy2022-11-09T01:27:45.76+00:00 Discord

I see

dnfield2022-11-09T01:27:48.4+00:00 Discord

If you're on a slow device and it's taking a lot of time to build every frame

fzyzcjy2022-11-09T01:28:20.104+00:00 Discord

taking a lot of time to build every frame yes, that happens for users without flutter_smooth

dnfield2022-11-09T01:29:28.207+00:00 Discord

There are many more users who don't have flutter_smooth than do 🙂 FWIW, internally at google applications are built relatively close to head of master

dnfield2022-11-09T01:29:41.913+00:00 Discord

(usually about a week or so behind it)

fzyzcjy2022-11-09T01:29:52.393+00:00 Discord

you guys are so brave 🙂

fzyzcjy2022-11-09T01:29:57.975+00:00 Discord

even not built with beta?

dnfield2022-11-09T01:30:11.17+00:00 Discord

We'll get yelled at by all those customers when their benchmarks go south, and telling them "oh well if you use flutter_smooth it'll be fixed" won't be good enough

fzyzcjy2022-11-09T01:30:19.085+00:00 Discord

definitely

dnfield2022-11-09T01:30:30.21+00:00 Discord

It's a little more complicated than what I'm saying but no, for the most part they're newer than beta

fzyzcjy2022-11-09T01:31:23.111+00:00 Discord

so I guess the main priority is like this:

  1. maybe we can firstly PR for a partial-flutter_smooth, i.e. a version for only maybe 50FPS not 60FPS (because all those edge cases are not solved). then the PR merged into flutter, and people can try it. then, if many people find it quite helpful etc, maybe we can consider the rest prs or integrate into framework?
  2. or alternatively, shall we just consider integrating flutter_smooth to framework now? so many PRs that solely wants to make flutter expose some low-level things are never a problem now
fzyzcjy2022-11-09T01:32:10Z GitHub

For future readers: This is discussed in https://discord.com/channels/608014603317936148/1039667632342835250

dnfield2022-11-09T01:32:38.179+00:00 Discord

yeah, making incremental improvements is a good way to go.

dnfield2022-11-09T01:33:06.234+00:00 Discord

And having some compelling benchmarks would help ideally without requiring major surgery on the application side to achieve)

fzyzcjy2022-11-09T01:33:28.094+00:00 Discord

totally agree

fzyzcjy2022-11-09T01:34:06.911+00:00 Discord

Btw flutter_smooth does have (hopefully) compelling bench: (1) 3-second video in https://github.com/fzyzcjy/flutter_smooth (2) a chapter in https://cjycode.com/flutter_smooth/benchmark/

fzyzcjy2022-11-09T01:34:39.009+00:00 Discord

so, one of the main blocker before partial-flutter_smooth can land, maybe: https://github.com/flutter/engine/pull/36438

fzyzcjy2022-11-09T01:35:12.53+00:00 Discord

also, pull-based model (e.g. https://github.com/flutter/engine/pull/36438) - otherwise ListView scrolling cannot be smooth because we have no way to read touch events, though your "list of text" can be

dnfield2022-11-09T01:37:16.43+00:00 Discord

I think ideally we have a solution that doesn't require calling render more than once per onBeginFrame

fzyzcjy2022-11-09T01:37:38.961+00:00 Discord

I have tried to think about it, but that seems fundamental for flutter_smooth

fzyzcjy2022-11-09T01:37:47.867+00:00 Discord

whole design details: https://cjycode.com/flutter_smooth/design/

fzyzcjy2022-11-09T01:39:40.405+00:00 Discord

so we may not have ideal things 😐 but considering the benefits of flutter_smooth, I hope it is worthwhile

fzyzcjy2022-11-09T01:40:05.116+00:00 Discord

(benefits, including future benefits if it is merged into flutter)

justinmc2022-11-10T21:36:13Z GitHub

I have to agree with @pdblasi-google, I'm not certain that this is better than the existing API, so I don't think we should make this change. Instead, here's what I think we can do about this problem:

  1. Clearing should happen immediately after setting. This makes it obvious to future readers of the test that these values need to be cleared, and it makes it less likely the test will end without the values being cleared. I think this could be done in this PR or in a new PR.

Old way:

tester.binding.window.devicePixelRatioTestValue = 1.2;
tester.binding.window.physicalSizeTestValue = const Size(250, 300);

... // Rest of test

tester.binding.window.clearPhysicalSizeTestValue();
tester.binding.window.clearDevicePixelRatioTestValue();

Should be changed to:

tester.binding.window.devicePixelRatioTestValue = 1.2;
addTearDown(clearDevicePixelRatioTestValue);
tester.binding.window.physicalSizeTestValue = const Size(250, 300);
addTearDown(clearPhysicalSizeTestValue);

... // Rest of test
  1. devicePixelRatioTestValueAutoClear and physicalSizeTestValueAutoClear could be released as a Pub package if desired.

  2. Like @pdblasi-google said, ideas about autoclear and other patterns that avoid this problem should be directed at @goderbauer's work to move from TestWindow to TestView, since we have the opportunity to start fresh here and change the API. He has a design doc, which it doesn't specifically mention this about testing so it might be worth bringing it up in a comment there.

fzyzcjy2022-11-10T23:30:27Z GitHub

@christopherfujino Sure, will do this soon

fzyzcjy2022-11-10T23:35:09Z GitHub

Hmm I see. Then maybe I will refactor like what you said.

P.S Did a comment there just now.

fzyzcjy2022-11-10T23:36:20Z GitHub

All right, I will try to squeeze some time to make a benchmark later :)

fzyzcjy2022-11-11T23:17:06Z GitHub

@Piinks Sure, will do it later. Btw may I get some hints about how to add test for this feature? Not very familiar with registerBoolServiceExtension etc

auto-submit2022-11-14T23:03:06Z GitHub

auto label is removed for flutter/flutter, pr: 114400, due to - The status or check suite Mac tool_tests_commands has failed. Please fix the issues identified (or deflake) before re-applying this label.

jonahwilliams2022-11-14T23:04:40Z GitHub

@fzyzcjy could you update to ToT so we can land this?

fzyzcjy2022-11-14T23:25:04Z GitHub

@jonahwilliams sure, done (waiting for ci now)

goderbauer2022-11-15T23:12:53Z GitHub

I am going to close this as it doesn't add any new information to the doc.

goderbauer2022-11-15T23:16:45Z GitHub

(This is WIP, since I want to see whether this change will make regression test fail or not)

Do you still have further plans for this? Or did it achieve its goal and can be closed?

fzyzcjy2022-11-15T23:23:54Z GitHub

Oh it is ready for review

flutter-dashboard2022-11-15T23:24:05Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

goderbauer2022-11-15T23:28:28Z GitHub

@fzyzcjy Can you rebase this to trigger the Google testing check again? Looks like it got stuck...

fzyzcjy2022-11-15T23:31:57Z GitHub

@goderbauer done

goderbauer2022-11-16T00:08:22Z GitHub

This is internal state that nobody should depend on. If you have a use case for this, I suggest filing an issue that describes what you want to achieve instead of describing a solution.

fzyzcjy2022-11-16T00:10:19Z GitHub

Ok, I will explain why this is needed in a separate issue later.

goderbauer2022-11-16T16:58:38Z GitHub

I don't think we should be exposing random implementation details like that. I would suggest filing an issue discussing the problem you're trying to solve without tying it to a particular solution. We can then see if this fixes that particular problem.

dnfield2022-11-16T17:04:42Z GitHub

You can use systracing instead of timeline by doing --trace-systrace. That is less expensive than timeline based tracing.

Unfortunately, I'm having trouble from the images telling where the extra time is being spent. I'm also confused why the traces are looking at pointer data packet events when this bug is about frame timings.

I think we should try to look at ways to make the frame timings measurements cheaper on representative hardware rather than disabling them. Timeline adds more overhead on every frame. Maybe you could file an issue with some of your findings?

fzyzcjy2022-11-16T22:50:39Z GitHub

Have no bandwidth now, will reply to review soon, thanks :)

fzyzcjy2022-11-16T22:51:47Z GitHub

I see, same as before, I will file soon when having time. Thanks!

fzyzcjy2022-11-16T22:53:51Z GitHub

Unfortunately, I'm having trouble from the images telling where the extra time is being spent. I'm also confused why the traces are looking at pointer data packet events when this bug is about frame timings.

Ah sorry! I must paste the wrong image here - at the time I wrote the reply I may be thinking about my another PR about pointer events.

I think we should try to look at ways to make the frame timings measurements cheaper on representative hardware rather than disabling them. Timeline adds more overhead on every frame. Maybe you could file an issue with some of your findings?

Sure. I will do that soon (have no time now so possibly within a few days)

fzyzcjy2022-11-17T00:16:58Z GitHub

Thanks :) I will modify and update it soon (no time now though)

pdblasi-google2022-11-17T21:33:34Z GitHub

Followup on moving the invariant verifications into an addTearDown (came to me at about 3AM last night...).

I think if we move the invariant verifications into an addTearDown, we may also need to move the storing of those variables (for checking the end state) that get checked into a setUp call. I think as it stands right now, if someone were to use setUp to change an invariant, it'll have a couple problems:

a) It won't validate that the invariant is reset after the test, since it's actually set before the test b) If (after this issue is fixed) the invariant is reset via an addTearDown or tearDown, then the verifications will fail because they'll have the changed value saved to check against, since it was changed before the test.

fzyzcjy2022-11-17T23:16:05Z GitHub

Good point! I will check that later.

auto-submit2022-11-17T23:46:28Z GitHub

auto label is removed for flutter/flutter, pr: 114481, due to - Please get at least one approved review if you are already a member or two member reviews if you are not a member before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

auto-submit2022-11-17T23:46:29Z GitHub

auto label is removed for flutter/flutter, pr: 114481, due to Validations Fail.

Piinks2022-11-22T23:04:27Z GitHub

Hm, I am not really sure how to test this. Have you asked Hixie on Discord if it qualifies for a no test exemption?

fzyzcjy2022-11-22T23:14:59Z GitHub

@Piinks Sure, I will ask soon ask sent

fzyzcjy2022-11-23T01:01:47Z GitHub

As discussed earlier in Discord, maybe I will focus on the main part of flutter_smooth now (quite busy recently), and restart this PR after the critical PRs of flutter_smooth has been merged.

(For future readers of this thread: Recent progress of the critical PRs are not only in flutter repositories and discord, but also somewhere such as https://github.com/fzyzcjy/flutter_smooth/issues/173)

fzyzcjy2022-11-23T01:22:41Z GitHub

As discussed in Discord with @dnfield previously, I will firstly focus on the PRs that are critical to the main part of flutter_smooth, and deal with these PRs (which improves performance roughly several FPS per PR) later.

(For future readers of this thread: Recent progress of the critical PRs are not only in flutter repositories and discord, but also somewhere such as https://github.com/fzyzcjy/flutter_smooth/issues/173)

Hixie2022-11-23T01:49:21Z GitHub

test-exempt: code refactor with no semantic change

fzyzcjy2022-11-23T05:55:26Z GitHub

Reland fix wrong VSYNC event

Reland #36775 Previously reverted by https://github.com/flutter/engine/pull/37589 Close https://github.com/flutter/flutter/issues/115161

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

flutter-dashboard2022-11-23T05:55:30Z GitHub

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

fzyzcjy2022-11-23T06:35:42Z GitHub

Issue created describing this: https://github.com/flutter/flutter/issues/115899

fzyzcjy2022-11-23T06:45:11Z GitHub

The requested issue is created and described the details: https://github.com/flutter/flutter/issues/115901

fzyzcjy2022-11-23T07:02:17Z GitHub

Well, tearDown does not seem to work. I guess it is because, our addTearDown(verifyInvariants) are called before this user-added tearDown. Thus, even if user resets value in this tearDown, our verifications run before that so is not aware of it.

However, IMHO this PR is still useful, because even if tearDown is not supported, it adds support for addTearDown, which is very frequently used.


tests:

    group('tearDown does not work yet', () {
tearDown(() {
timeDilation = 1;
});

testWidgets('main test inside group', (WidgetTester tester) async {
timeDilation = 2;
});
});

yields

package:flutter/src/scheduler/binding.dart 662:9  SchedulerBinding.debugAssertNoTimeDilation.<fn>
package:flutter/src/scheduler/binding.dart 665:6 SchedulerBinding.debugAssertNoTimeDilation
package:flutter_test/src/binding.dart 971:12 TestWidgetsFlutterBinding._verifyInvariants
package:flutter_test/src/binding.dart 1436:11 AutomatedTestWidgetsFlutterBinding._verifyInvariants
package:flutter_test/src/binding.dart 950:9 TestWidgetsFlutterBinding._runTestBody.<fn>
===== asynchronous gap ===========================
dart:async _CustomZone.registerUnaryCallback
package:flutter_test/src/binding.dart 941:9 TestWidgetsFlutterBinding._runTestBody.<fn>

The timeDilation was changed and not reset by the test.
fzyzcjy2022-11-23T07:41:52Z GitHub

Hmm CI fails. I will firstly revert now