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

debounce rounding position on Marker #11167

Merged
merged 15 commits into from Dec 17, 2021

Conversation

malekeym
Copy link
Contributor

@malekeym malekeym commented Oct 22, 2021

Launch Checklist

In this PR we debounce rounding position for cases that we need to animate Marker
Close #11135

  • 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
  • document any changes to public APIs
  • post benchmark scores
  • manually test the debug page
  • tagged @mapbox/map-design-team @mapbox/static-apis if this PR includes style spec API or visual changes
  • tagged @mapbox/gl-native if this PR includes shader changes or needs a native port
  • apply changelog label ('bug', 'feature', 'docs', etc) or use the label 'skip changelog'
  • add an entry inside this element for inclusion in the mapbox-gl-js changelog: <changelog>Animated markers are smoother in most browsers (h/t [@malekeym](https://github.com/malekeym))</changelog>

src/ui/marker.js Outdated Show resolved Hide resolved
Copy link
Contributor

@SnailBones SnailBones left a comment

Choose a reason for hiding this comment

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

The unit tests look great to me!

I just tested this out in browser, and unfortunately the bug still isn't fixed.
https://user-images.githubusercontent.com/14878684/141167735-a2739059-b954-409e-9082-e4cd513fb15a.mov

I think this is because of the map._forceMarkerUpdate(), which I added to fix another marker bug here. The intention was to have markers update in response to map.setTerrain(), but it seems to be calling excess updates as well. I think that fixing this will require checking if terrain actually changes on the call to painter.updateTerrain() and only updating the markers if so.
EDIT: I'm wrong, this isn't the cause of the bug.

@malekeym
Copy link
Contributor Author

The unit tests look great to me!

I just tested this out in browser, and unfortunately the bug still isn't fixed. https://user-images.githubusercontent.com/14878684/141167735-a2739059-b954-409e-9082-e4cd513fb15a.mov

I think this is because of the map._forceMarkerUpdate(), which I added to fix another marker bug here. The intention was to have markers update in response to map.setTerrain(), but it seems to be calling excess updates as well. I think that fixing this will require checking if terrain actually changes on the call to painter.updateTerrain() and only updating the markers if so.

2021-11-10.23-27-53.mov

@SnailBones good point, I think I catch it. but when i test it in my browser all thing is fine.
could you provide a code that demonstrates this bug.

@SnailBones
Copy link
Contributor

@SnailBones good point, I think I catch it. but when i test it in my browser all thing is fine. could you provide a code that demonstrates this bug.

I've copied the code from this example into a local html file, and tested in both Firefox and Chrome.

Your video unfortunately doesn't seem to be working for me.

@malekeym
Copy link
Contributor Author

@SnailBones good point, I think I catch it. but when i test it in my browser all thing is fine. could you provide a code that demonstrates this bug.

I've copied the code from this example into a local html file, and tested in both Firefox and Chrome.

Your video unfortunately doesn't seem to be working for me.

actually i copied that code...
https://user-images.githubusercontent.com/70053533/141190959-299cad85-3bcc-4622-9787-6ef5c25e95e9.mov
Could you check this link?

@SnailBones
Copy link
Contributor

actually i copied that code... https://user-images.githubusercontent.com/70053533/141190959-299cad85-3bcc-4622-9787-6ef5c25e95e9.mov Could you check this link?

Still not working for me. I see this:
image

@mourner
Copy link
Member

mourner commented Nov 10, 2021

@SnailBones here's a better format:

marker.mp4

@SnailBones
Copy link
Contributor

SnailBones commented Nov 10, 2021

Thanks for the conversion @mourner!
@malekeym It's hard for me to tell if the marker is snapping in that video. I've found that the bug is most visible when a marker is moving very slowly, like this:

Screen.Recording.2021-11-10.at.1.59.58.PM.mov

Here's the code I used to create this:

<!DOCTYPE html>
<html>
<head>
    <title>Marker snapping test</title>
    <meta charset='utf-8'>
    <link rel='stylesheet' href='../dist/mapbox-gl.css' />
    <style>
        body { margin: 0; padding: 0; }
        html, body, #map { height: 100%; }
    </style>
</head>

<body>
<div id='map'></div>
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    zoom: 0
});

const marker = new mapboxgl.Marker({color: '#F84C4C'});

function animateMarker(timestamp) {
    const radius = 5;
    marker.setLngLat([
        Math.cos(timestamp / 1000) * radius,
        Math.sin(timestamp / 1000) * radius
    ]);
    marker.addTo(map);
    requestAnimationFrame(animateMarker);
}

requestAnimationFrame(animateMarker);

</script>
</body>
</html>

src/ui/marker.js Outdated
@@ -282,7 +283,7 @@ export default class Marker extends Evented {
remove() {
if (this._map) {
this._map.off('click', this._onMapClick);
this._map.off('move', this._update);
this._map.off('move', () => this._update(true));
Copy link
Member

Choose a reason for hiding this comment

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

This won't actually remove the listener that was added previously, because this is a different function. You need to pass the exact reference — e.g. by creating a separate _updateMoving that internally calls _update(true).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@malekeym
Copy link
Contributor Author

malekeym commented Nov 10, 2021

Thanks for the conversion @mourner! @malekeym It's hard for me to tell if the marker is snapping in that video. I've found that the bug is most visible when a marker is moving very slowly, like this:

Screen.Recording.2021-11-10.at.1.59.58.PM.mov
Here's the code I used to create this:

<!DOCTYPE html>
<html>
<head>
    <title>Marker snapping test</title>
    <meta charset='utf-8'>
    <link rel='stylesheet' href='../dist/mapbox-gl.css' />
    <style>
        body { margin: 0; padding: 0; }
        html, body, #map { height: 100%; }
    </style>
</head>

<body>
<div id='map'></div>
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    zoom: 0
});

const marker = new mapboxgl.Marker({color: '#F84C4C'});

function animateMarker(timestamp) {
    const radius = 5;
    marker.setLngLat([
        Math.cos(timestamp / 1000) * radius,
        Math.sin(timestamp / 1000) * radius
    ]);
    marker.addTo(map);
    requestAnimationFrame(animateMarker);
}

requestAnimationFrame(animateMarker);

</script>
</body>
</html>

Thank you @SnailBones , I check the code. if you see in function animateMarker call addTo after setLngLat so the result is that _pos is rounded, as we discussed before we decided to round position immediately in addTo and moveend. am i right?
please check this code

<!DOCTYPE html>
<html>
<head>
    <title>Marker snapping test</title>
    <meta charset='utf-8'>
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v2.4.1/mapbox-gl.css"
      rel="stylesheet"
    />    <style>
        body { margin: 0; padding: 0; }
        html, body, #map { height: 100%; }
    </style>
</head>

<body>
<div id='map'></div>
<script src='./mapbox-gl-unminified.js'></script>
<script>
      mapboxgl.accessToken =
        "ACCESS_TOKEN";
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    zoom: 0
});

const marker = new mapboxgl.Marker({color: '#F84C4C'}).setLngLat([0,0]).addTo(map);

function animateMarker(timestamp) {
    const radius = 5;
    marker.setLngLat([
        Math.cos(timestamp / 10000) * radius,
        Math.sin(timestamp / 10000) * radius
    ]);
    requestAnimationFrame(animateMarker);
}

requestAnimationFrame(animateMarker);

</script>
</body>
</html>

@SnailBones
Copy link
Contributor

Thank you @SnailBones , I check the code. if you see in function animateMarker call addTo after setLngLat so the result is that _pos is rounded, as we discussed before we decided to round position immediately in addTo and moveend. am i right?

Good catch @malekeym! That works for me, you're correct that the issue results from the repeated calls to marker.addTo. One last suggestion before I approve this: as a way to fix this specific case, can we add an early exit condition to the addTo()? Maybe something like:

        if (map === this._map) { return this; }

@malekeym
Copy link
Contributor Author

malekeym commented Nov 11, 2021

Thank you @SnailBones , I check the code. if you see in function animateMarker call addTo after setLngLat so the result is that _pos is rounded, as we discussed before we decided to round position immediately in addTo and moveend. am i right?

Good catch @malekeym! That works for me, you're correct that the issue results from the repeated calls to marker.addTo. One last suggestion before I approve this: as a way to fix this specific case, can we add an early exit condition to the addTo()? Maybe something like:

        if (map === this._map) { return this; }

@SnailBones very good idea, i changed the code, but I find a bug that is related to _forceUpdateMarker :)) if you want to reproduce it, load terrain Map and just add a Marker then move the map you can see the marker is jagging. (this is not happening in street Map), could you help me to fix this?
I found the bug that is related to @mourner Review, I don't know why but when using _updateMoving the behaviour is changed on the move event. To demonstrate the issue first create a map then just add a Marker and move the map. Is it clear?

@SnailBones
Copy link
Contributor

I found the bug that is related to @mourner Review, I don't know why but when using _updateMoving the behaviour is changed on the move event. To demonstrate the issue first create a map then just add a Marker and move the map. Is it clear?

I'm glad you noticed that issue @malekeym! The cause is that this is bound to the map in updateMoving, so it's actually calling map._update If you can find a way to correctly call marker._update the bug should be fixed.

@malekeym
Copy link
Contributor Author

I found the bug that is related to @mourner Review, I don't know why but when using _updateMoving the behaviour is changed on the move event. To demonstrate the issue first create a map then just add a Marker and move the map. Is it clear?

I'm glad you noticed that issue @malekeym! The cause is that this is bound to the map in updateMoving, so it's actually calling map._update If you can find a way to correctly call marker._update the bug should be fixed.

Thank you @SnailBones for finding the reason that bug happened I made changes and add a unit test to check move events and the issue is fixed but I have a problem with flow test, I can fix it with define property _updateMove instead of method and use Arrow function, or I should use wild card for disabling flow on reassigning Method. I think the first solution is better what do you think?

@SnailBones
Copy link
Contributor

I think you have the right idea with assigning a property with an arrow function! Because besides the flow error, I think the current approach runs into the same issue that @mourner mentioned above because .bind(); creates a new method.

@malekeym
Copy link
Contributor Author

malekeym commented Nov 12, 2021

I think you have the right idea with assigning a property with an arrow function! Because besides the flow error, I think the current approach runs into the same issue that @mourner mentioned above because .bind(); creates a new method.

I use the arrow function, but just one question, if we use bind and then assign the new function to a method, so the references are the same, so the @mourner issue is fixed, am I right?

Copy link
Contributor

@SnailBones SnailBones left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@malekeym
Copy link
Contributor Author

@SnailBones any blocker for this merge request? should I change anything?

Copy link
Member

@mourner mourner left a comment

Choose a reason for hiding this comment

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

This looks great to me now!

src/ui/marker.js Outdated
this._updateFrameId = window.requestAnimationFrame(() => {
if (this._element && this._pos && this._anchor) {
this._pos = this._pos.round();
DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I wonder if we could deduplicate this long line of code that's duplicated below, but not a big deal.

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, but I don't have any idea, do you have?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe seperate that line into something like:

function _updateDOM(){   DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`);}

If you move the pitch and location calculating code into this function, then it can live in the marker's scope.

Copy link
Contributor Author

@malekeym malekeym Dec 15, 2021

Choose a reason for hiding this comment

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

I separate some methods, I appreciate reviewing changes and giving your feedback

@CLAassistant
Copy link

CLAassistant commented Dec 14, 2021

CLA assistant check
All committers have signed the CLA.

@malekeym
Copy link
Contributor Author

@SnailBones and @mourner , when I merge the main branch to my branch, I see that DOM is removed and used js for add style so I changed my code, too.

src/ui/marker.js Outdated
@@ -446,41 +452,59 @@ export default class Marker extends Evented {
position.y >= 0 && position.y < tr.height;
}

_update(e?: {type: 'move' | 'moveend'}) {
if (!this._map) return;
_updateDOM(pos: Point) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Great work @malekeym!
One more tiny nit: Why does this function take in the position as an argument? Given it's directly accessing class variables like this._anchor, I think that it would be more consistent and prevent potential confusion to either remove this argument, or else pass in the other variables as arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@SnailBones Done!

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks great!

@SnailBones
Copy link
Contributor

Merging it in, thanks for your work on this @malekeym! 🙏

@SnailBones SnailBones merged commit 5058720 into mapbox:main Dec 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add isHighAccurate property to Marker
4 participants