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

Refactor CustomSource #12063

Merged
merged 12 commits into from
Sep 19, 2022
Merged

Refactor CustomSource #12063

merged 12 commits into from
Sep 19, 2022

Conversation

stepankuzmin
Copy link
Contributor

@stepankuzmin stepankuzmin commented Jul 6, 2022

This PR makes 3 changes:

  1. Drops the prepareTile as it was complicating more than helping
  2. Adds the clearTiles method that allows an implementation to remove all existing tiles in SourceCaches
  3. Denotes the "missing" vs "empty" tile behavior in CustomSource by the undefined vs null value returned by the loadTile. More context is below.

Before the fix, we draw nothing (an empty tile) if loadTile throws or returns no data (blank space on the first screenshot). After the fix, if loadTile returns undefined, such tiles are marked as errored, and a map draws an overscaled parent tile if there is any (red tile on the second screenshot).

If prepareTile returns null, such tiles are marked as loading, and a map draws nothing. (blank space on the first screenshot). If prepareTile returns undefined, a map draws an overscaled parent tile if there is any (red tile on the second screenshot).

loadTile returns null

Screen Shot 2022-07-08 at 15 41 51

loadTile returns undefined

Screen Shot 2022-07-08 at 15 41 57

Related to #11608
Closes #11687 #12159

Launch Checklist

  • briefly describe the changes in this PR
  • manually test the debug page
  • apply changelog label ('bug', 'feature', 'docs', etc) or use the label 'skip changelog'

@stepankuzmin stepankuzmin added bug 🐞 skip changelog Used for PRs that do not need a changelog entry labels Jul 6, 2022
@stepankuzmin stepankuzmin requested a review from ansis July 6, 2022 15:31
@stepankuzmin stepankuzmin self-assigned this Jul 6, 2022
@ansis
Copy link
Contributor

ansis commented Jul 7, 2022

Documenting conversation from elsewhere: Erroring the tile will cause the parent tile to be shown, if it is available. If we need this in some cases but not in other we'll need to support both a way to error the tile and a way to make it empty.

@stepankuzmin stepankuzmin changed the title remove empty tiles caching in CustomSource Draw overscaled parent tiles if loadTile returns no data in CustomSource Jul 8, 2022
@stepankuzmin
Copy link
Contributor Author

After some feedback, it looks like we should support both ways — to render nothing and to show an overscaled parent tile in the tile’s space.

I see two possible solutions: we can either add a boolean flag to the CustomSource constructor or use the undefined vs null returned value to denote the behavior.

// If the implementation returned `undefined` as tile data,
// mark the tile as `errored` to indicate that we have no data for it.
// A map will render an overscaled parent tile in the tile’s space.
if (data === undefined) {
    tile.state = 'errored';
    return callback(null);
}

// If the implementation returned `null` as tile data,
// mark the tile as `loaded` and use an an empty image as tile data.
// A map will render nothing in the tile’s space.
if (data === null) {
    const emptyImage = {width: this.tileSize, height: this.tileSize, data: null};
    this.loadTileData(tile, (emptyImage: any));
    tile.state = 'loaded';
    return callback(null);
}

@ansis @mourner, what do you think of these options?

@ansis
Copy link
Contributor

ansis commented Jul 11, 2022

Another option would be to catch rejected promises and treat those as errors. Error/rejection == missing tile. Undefined/null === blank tile.

@stepankuzmin
Copy link
Contributor Author

@ansis yes, that's possible too. It's probably a matter of taste, but since the use-case implies that the user might want to return a missing tile explicitly, I'd prefer not to use throw to denote the missing tile and go like this: Undefined/Error/rejection == missing tile. Null === blank tile. Is that okay?

@stepankuzmin stepankuzmin force-pushed the fix-custom-source-caching branch 2 times, most recently from ddc4a21 to a88f0cd Compare July 12, 2022 12:57
@stepankuzmin stepankuzmin changed the title Draw overscaled parent tiles if loadTile returns no data in CustomSource Denote the "missing" vs "empty" tile behavior in CustomSource by the undefined vs null value returned by loadTile Jul 12, 2022
@stepankuzmin stepankuzmin force-pushed the fix-custom-source-caching branch 2 times, most recently from 6d4726f to 3f409c6 Compare July 15, 2022 14:16
@stepankuzmin stepankuzmin changed the title Denote the "missing" vs "empty" tile behavior in CustomSource by the undefined vs null value returned by loadTile Denote the "missing" vs "empty" tile behavior in CustomSource using the undefined vs null return value Jul 19, 2022
src/source/custom_source.js Outdated Show resolved Hide resolved
@stepankuzmin stepankuzmin changed the title Denote the "missing" vs "empty" tile behavior in CustomSource using the undefined vs null return value Refactor CustomSource Aug 23, 2022
@stepankuzmin stepankuzmin requested a review from a team as a code owner September 8, 2022 09:57
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.

Love how this simplifies the code significantly!

src/source/source.js Outdated Show resolved Hide resolved
@rreusser
Copy link
Contributor

rreusser commented Sep 8, 2022

I'm not sure if it's related to this PR or not, but this is an odd behavior I observe on the custom-source debug page, in which high-zoom tiles continue to be displayed as you zoom out and until you zoom in elsewhere. I wonder if this is related to needing to manually implement your own unload function or something? I do not observe this behavior for regular tiles on the debug/debug.html page with tile debug turned on.

unloading.mov

Copy link
Contributor

@rreusser rreusser left a comment

Choose a reason for hiding this comment

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

I really like the changes!! 👏 I think it's definitely headed in the right direction, but I observed what appears to be a bug in the debug page, a possible bug in tile unloading, and had a question about a possible alternative API design.

debug/custom-source.html Show resolved Hide resolved
debug/custom-source.html Outdated Show resolved Hide resolved
debug/custom-source.html Show resolved Hide resolved
@SergeiMelman
Copy link

A little comment about mapboxgl.CustomSourceInterface<T> in version "2.10.0"
This T is expected to be HTMLImageElement | ImageData | ImageBitmap as it is declared in

export type AnySourceData =
        ......
        | CustomSourceInterface<HTMLImageElement | ImageData | ImageBitmap>;

but raster is checked as

function isRaster(data: any): boolean {
    return data instanceof window.ImageData ||
        data instanceof window.ImageBitmap ||
        data instanceof window.HTMLCanvasElement;
}

HTMLCanvasElement is what I am after, but I have to tweak return types as I use TS.

Thank you for the CustomSource in general :)

@stepankuzmin
Copy link
Contributor Author

Hey @rreusser,

…high-zoom tiles continue to be displayed as you zoom out and until you zoom in elsewhere.

I did some digging, and it's a bug related to the retaining raster tiles when raster-fade-duration is set to 0 #12241

@stepankuzmin
Copy link
Contributor Author

Good catch, @SergeiMelman!

I've updated the JSDoc for loadTile.

Copy link
Contributor

@rreusser rreusser 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 good to me, thanks for the changes! My strategy now is to use an LRU cache to store canvases by z/x/y and separately use a Set to track whether particular tiles need to be redraw. This happens since there are two cases:

  1. the tile's canvas is cached and can be re-used as-is
  2. the tile's canvas is cached but needs to be repainted

I think this is reasonable and matches expectations, and I'm able to use it to accomplish the rendering I was after!

(Also, "raster-fade-duration": 1 solved my other problem regarding the retained tiles, thanks for tracking down the source of that issue!)

@stepankuzmin stepankuzmin merged commit 1be5682 into main Sep 19, 2022
@stepankuzmin stepankuzmin deleted the fix-custom-source-caching branch September 19, 2022 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐞 skip changelog Used for PRs that do not need a changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants