Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix view annotations triggering excessive onMeasure [MAPSAND-516] #1744

Merged
merged 12 commits into from
Oct 13, 2022

Conversation

yunikkk
Copy link
Contributor

@yunikkk yunikkk commented Oct 10, 2022

Problem was caused calling bringToFront() on every update of the annotations, that happens on any map animation or gesture.

bringToFront() was needed to guarantee that :

  • view annotations are displayed in the correct order (same as items in the onViewAnnotationPositionsUpdate(@NonNull List<ViewAnnotationPositionDescriptor> positions); callback). This order changes if some ViewAnnotation is being selected, for example
  • plugins (compass, ruler etc) are displayed on top of ViewAnnotations

Also I've added separate FrameLayout to the MapView on top of the actual surface/texture view but below any other plugin - so that removing or adding new view annotation does not interfere with the plugins at all.
MapView adds a SurfaceView/TextureView that actually displays map at the index 0 here, then additional views are added on top of it, thus before the fix the layout was looking the following way :

FrameLayout MapView : 
 - SurfaceView/TextureView
 - ViewAnnotation1
 - ViewAnnotation2
 - ...
 - CompassPlugin
 - LogoPlugin

After the fix it becomes :

FrameLayout MapView : 
 - SurfaceView/TextureView
 - FrameLayout for view annotations
   - ViewAnnotation1
   - ViewAnnotation2
   - ...
 - CompassPlugin
 - LogoPlugin

Summary of changes

User impact (optional)

Before changes, simple map layout with 1 view annotation, browsing map (so onMeasure is called every frame):
image

onMeasure calls take 117.767 nanos out of 1.409.936 that choreographer.doFrame spent, roughly 8.3%.

After changes, same layout and map :
image

Tested on the Android emulator.
No onMeasure calls at all.
The difference will be more noticeable the more complex layout is used.

Pull request checklist:

  • Briefly describe the changes in this PR.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality. If tests were not written, please explain why.
  • Optimize code for java consumption (@JvmOverloads, @file:JvmName, etc).
  • Add example if relevant.
  • Document any changes to public APIs.
  • Run make update-api to update generated api files, if there's public API changes, otherwise the verify-api-* CI steps might fail.
  • Update CHANGELOG.md or use the label 'skip changelog', otherwise check changelog CI step will fail.
  • If this PR is a v10.[version] release branch fix / enhancement, merge it to main firstly and then port to v10.[version] release branch.

Fixes: < Link to related issues that will be fixed by this pull request, if they exist >

PRs must be submitted under the terms of our Contributor License Agreement CLA.

@yunikkk yunikkk requested a review from kiryldz October 10, 2022 17:05
sdk/build.gradle.kts Outdated Show resolved Hide resolved
currentViewsDrawnMap.keys.forEach { id ->
if (positionDescriptorCoreList.indexOfFirst { it.identifier == id } == -1) {
currentlyDrawnViewIdSet.forEach { id ->
if (positionDescriptorUpdatedList.indexOfFirst { it.identifier == id } == -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I understand correctly in worst case it can be O(N)?
maybe save somehow id + position?

// pos : [identifier: 42, width: 282, height: 196, leftTopCoordinate: [x: 99.99999999998583, y: 368.9999999999887]]
// pos : [identifier: 44, width: 282, height: 196, leftTopCoordinate: [x: 148.00000000000566, y: 616.9999999999659]]
// pos : [identifier: 45, width: 282, height: 196, leftTopCoordinate: [x: 483.0000000000149, y: 566.9999999999759]]
val wrapContentWithAllowOverlapFalseViewAdded = positionDescriptorUpdatedList.size == 1 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Suggested change
val wrapContentWithAllowOverlapFalseViewAdded = positionDescriptorUpdatedList.size == 1 &&
val wrapContentWithAllowOverlapFalseViewAdded = positionDescriptorUpdatedList.size >= 1 &&

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we can do that, workaround specifically handles case when there's single view. I'll also create a ticket to gl-native to fix the root issue

CHANGELOG.md Outdated Show resolved Hide resolved
Co-authored-by: Kiryl Dzehtsiarenka <kiryl.dzehtsiarenka@mapbox.com>
@yunikkk yunikkk marked this pull request as ready for review October 12, 2022 10:24
@yunikkk yunikkk requested a review from a team as a code owner October 12, 2022 10:24
@kiryldz kiryldz added the bug 🪲 Something isn't working label Oct 12, 2022
@yunikkk yunikkk changed the title Fix view annotations triggering excessive onMeasure Fix view annotations triggering excessive onMeasure [MAPSAND-516] Oct 12, 2022
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
// place the view annotations above the map (index 0) but below the compass, ruler and other plugin views
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that view annotation should be placed below the compass/scalebar? Do we want to expose some configurations to adjust the z-order of the view annotations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was not possible before that and in order to control Z index we have the concept of selected state for view annotatotions so it seems that we're totally OK here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peng this kind of customisations makes sense to me, actually, though outside the scope of this PR I guess

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@kiryldz kiryldz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing before merging - will you be able to record CPU load and update PR description before / after this PR? I believe improvement should be visible.

CHANGELOG.md Show resolved Hide resolved
Comment on lines +10 to +12
internal fun FrameLayout.hasChildView(view: View) = children.any { it == view }

internal fun MapView.getChildViewIndex(view: View) = children.indexOf(view)
internal fun FrameLayout.getChildViewIndex(view: View) = children.indexOf(view)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in the last kotlin group session, it's generally a bad practice to extend Android system APIs, but I guess it's less of a problem for internal methods, and in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, not a problem here - since it's test utils, but a good example of something that could be dangerous in the sdk code indeed!

Copy link
Member

@pengdev pengdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM % nits

@yunikkk
Copy link
Contributor Author

yunikkk commented Oct 13, 2022

One last thing before merging - will you be able to record CPU load and update PR description before / after this PR? I believe improvement should be visible.

Added simple run of view annotation activity before/after change, onMeasure was 8.3 percent of all the time doFrame spent on the main thread.

@yunikkk yunikkk merged commit 9e882dc into main Oct 13, 2022
@yunikkk yunikkk deleted the yds-fix-view-annotations-exsessive-on-measure branch October 13, 2022 14:54
yunikkk added a commit that referenced this pull request Oct 17, 2022
* Fix view annotations triggering onMeasure by doing bringToFront() on every update.

* Implemented algorithm to detect if bringToFront is needed instead of using DiffUtil.

* ktlint.

* Bring back view hiding until its positioned.

* Changelog.

* Update CHANGELOG.md

Co-authored-by: Kiryl Dzehtsiarenka <kiryl.dzehtsiarenka@mapbox.com>

* Fixed tests and review comments.

* Fix ui test.

* ktlint fix.

* Add 10.9.0 section to changelog.

* Fix layout name.

* Fix incorrectly compared descriptors.

Co-authored-by: Kiryl Dzehtsiarenka <kiryl.dzehtsiarenka@mapbox.com>
kiryldz pushed a commit that referenced this pull request Aug 2, 2023
* update stylegen to main

* update-api

* expose lights3d public

* update apis and tests

* publish_android_snapshot

* publish_android_snapshot

* rebase develop and update api

---------

Co-authored-by: Tobrun Van Nuland <tobrun.van.nuland@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🪲 Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants