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

port CollisionIndex and Placement changes from -native #5890

Merged
merged 3 commits into from
Jan 9, 2018

Conversation

ansis
Copy link
Contributor

@ansis ansis commented Dec 19, 2017

This ports changes to CrossTileSymbolIndex and Placement from -native (mapbox/mapbox-gl-native#10436) back to -js.


Previously symbol opacities were stored directly on SymbolInstances. Whenever a tile was added or removed CrossTileSymbolIndex would do a bunch of calculations and copy these opacities directly between SymbolInstances, which it would hold references to. The approach was really stateful. The new approach untangles things into four more distinct steps:

  • match symbols across tiles and assign matching crossTileIDs to matching symbols (CrossTileIndex)
  • calculate boolean placement values for each crossTileID in the current set of tiles (Placement.placeLayer)
  • combine these boolean placement values with the previous values to calculate opacities (Placement.commit)
  • write these placement and opacity values to the buffers of current tiles by matching based on crossTileID (Placement.updateLayerOpacities)

A stale Placement can be used to update newly loaded buckets. This lets us avoid doing a full, expensive placement on every frame where a tile gets loaded. It uses the old values and updates them after a new Placement (split over several frames) has been produced.


Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • document any changes to public APIs
  • post benchmark scores https://bl.ocks.org/anonymous/raw/f5ce771d18384774bead7f8a06f1af7f/
    The most critical benchmark for these changes is Paint and the results look good.
  • manually test the debug page
  • manually test a setData symbol example

I still need to finish writing new tests but the pr is at the point where it could be reviewed.

@ChrisLoer
Copy link
Contributor

Awesome! I'm just starting to work through the PR, should get more comments in tomorrow.

It appears to me that this fixes part of #5887 (in that tiles never "lose" their collision index because they don't hold references to the collision index any more), but maybe makes more of a problem in the case when a FeatureIndex changes during tile reload? Because we may now keep using the old collision index for many frames (until the next placement happens), it could be more problematic if the indices in the collision index don't match up with the feature index. I wonder if it might even trigger an error if the new feature index has fewer features and filterMatching indexes off the end of the array...

@ansis
Copy link
Contributor Author

ansis commented Dec 19, 2017

I wonder if it might even trigger an error if the new feature index has fewer features and filterMatching indexes off the end of the array...

Yes... I think so. Would adding a bucketInstanceId check be good enough? In the case of a bucket update and stale placement it would return nothing.

@@ -1,25 +1,26 @@
// @flow
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this file to pauseable_placement.js so the name doesn't collide with src/symbol/placement.js?

@ChrisLoer
Copy link
Contributor

Haven't figured out the problem yet, but just panning around I see sometimes placement doesn't seem to finish (?) until some later map interaction:

missing_placement

Also on zooming out I'll see symbols disappear:

spurious_duplicates

(This is running against 7bb3d45)


let placeText = false;
let placeIcon = false;
//let offscreen = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the plan to hook this back up later?


import type {OverscaledTileID} from '../source/tile_id';
import type {SymbolInstance} from '../data/bucket/symbol_bucket';
import type SymbolBucket from '../data/bucket/symbol_bucket';
import type StyleLayer from '../style/style_layer';
import type Tile from '../source/tile';

/*
The CrossTileSymbolIndex generally works on the assumption that
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment needs updating.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated in 8e20dd3

@ansis
Copy link
Contributor Author

ansis commented Dec 19, 2017

70eb215 should fix the missing labels

}

const tileIndex = new TileLayerIndex(tileID, symbolInstances);

// make all higher-res child tiles block duplicate labels in this tile
Copy link
Contributor

Choose a reason for hiding this comment

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

These two loops (child index and parent index) still have structure and comments from when child and parent indexes had different blocking logic. Now that the logic is really just "run findMatches across all zoom levels", maybe we could express this more clearly with one loop that just has a branch for looking up child vs. parent indices?

.sort((a, b) => (b.tileID.overscaledZ - a.tileID.overscaledZ) || (a.tileID.isLessThan(b.tileID) ? -1 : 1));
}

if (this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source])) symbolBucketsChanged = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

How about symbolBucketsChanged |= addLayer(... instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think a regular if is clearer than cleverly using a bitwise or

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and flow expects numbers not booleans for |=

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, how about just bringing the symbolBucketsChanged = true down to its own line? My reasoning is that when someone is scanning the logic looking for where assignments happen, they're easiest (most common) to see when they start the line they're on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, would this alternative be better than a separate line?

const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source]);
symbolBucketsChanged = symbolBucketsChanged || layerBucketsChanged;

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that's good.

@ChrisLoer
Copy link
Contributor

ChrisLoer commented Dec 19, 2017

70eb215 should fix the missing labels

👍 Looking good now.

@ChrisLoer
Copy link
Contributor

Would adding a bucketInstanceId check be good enough? In the case of a bucket update and stale placement it would return nothing.

It would work to prevent errors, but wouldn't that still exhibit the behavior from #5716 (comment)? Ideally, I think the results of queryRenderedFeatures should only change when a new placement is committed. For this PR, though, it's probably OK to just make sure we don't error out.

@ChrisLoer
Copy link
Contributor

It looks like getting rid of full placement on tile loading is working to make the ten-second pitched rotation animation case significantly smoother (looks like 2 instances of stutter after vs. about 15 before):

fps_meter_during_rotation.zip

🎉 !

Copy link
Contributor

@ChrisLoer ChrisLoer left a comment

Choose a reason for hiding this comment

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

OK, I think the "don't error when querying tiles with stale placement" solution is good enough for now. This should fix #5716 but leave #5887 for later.

@ansis
Copy link
Contributor Author

ansis commented Jan 8, 2018

@ChrisLoer can you review c47acaa, d44241b, ba0dc8a, 98dbc7f which are meant to fix flickering bugs related to data and style updates?

this.collisionIndex = this.placement.collisionIndex;
this.placement.setRecent(browser.now());
} else {
this.placement.setRecent(browser.now());
Copy link
Contributor

Choose a reason for hiding this comment

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

This was tricky for me to read -- it looks like setRecent is just being duplicated, it's easy to miss that setRecent is going to set stale to false, and confusing that we want to undo it right away (because we want to update _recentUntil for throttling, right?).

Could we just save something like inProgressPlacementStale at the top and set it at the bottom? It might be clearer.

Or would it make sense to simplify this just by always committing the pausable placement? Do we expect there's some performance win from keeping the original placement object unmodified in the case nothing changes?

class JointPlacement {
text: boolean;
icon: boolean;
// offscreen = outside viewport, but within CollisionIndex::viewportPadding px of the edge
Copy link
Contributor

Choose a reason for hiding this comment

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

By the time it gets here "offscreen" now means something like "skip fade animation" since it's the combination of "offscreen" and "justReloaded". We should come up with a new name to account for that once the two concepts are combined.

The index relied on a source layer and index to identify a feature. If
a data layer gets updated a new bucket is formed where these identifiers
probably don't match the previous bucket. Since there is a gap between a
bucket being loaded and the index being updated (during the next
placement) it was possible for a query to return wrong results.

This prevents it from returning incorrect features but it does not
return the correct features. This might cause it to return empty
results.
@ansis ansis merged commit 3963d94 into master Jan 9, 2018
@jfirebaugh jfirebaugh deleted the collision-port-back branch February 22, 2018 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants