From bd37870b6fcc613a021c523d4eb5aa8638d71382 Mon Sep 17 00:00:00 2001 From: Taras Vozniuk Date: Sun, 6 Aug 2023 04:23:56 +0800 Subject: [PATCH] VectorTileWorkerSource: fix reload for original's load parse would not pass the rawTileData and meta (#2941) * fix reload for original's load parse would not pass the rawTileData * remove fetching state after base load * add changelog * update the tests to check for rawTileData to be passed on reload that cancels load * remove unused import * nit: change interface to type, test the case when reparsing is done after mocked load callback is called --- CHANGELOG.md | 1 + src/source/vector_tile_worker_source.test.ts | 79 ++++++++++++++++++++ src/source/vector_tile_worker_source.ts | 27 ++++++- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 468df5130b..1e25ae7b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 🐞 Bug fixes +- VectorTileWorkerSource: fix reload for original's load parse would not pass the rawTileData and meta. ([#2941](https://github.com/maplibre/maplibre-gl-js/pull/2941)) - _...Add new stuff here..._ ## 3.2.1 diff --git a/src/source/vector_tile_worker_source.test.ts b/src/source/vector_tile_worker_source.test.ts index c4bcf6327d..2958b2dc79 100644 --- a/src/source/vector_tile_worker_source.test.ts +++ b/src/source/vector_tile_worker_source.test.ts @@ -87,6 +87,85 @@ describe('vector tile worker source', () => { }); test('VectorTileWorkerSource#loadTile reparses tile if the reloadTile has been called during parsing', (done) => { + const rawTileData = new Uint8Array([]); + function loadVectorData(params, callback) { + return callback(null, { + vectorTile: { + layers: { + test: { + version: 2, + name: 'test', + extent: 8192, + length: 1, + feature: (featureIndex: number) => ({ + extent: 8192, + type: 1, + id: featureIndex, + properties: { + name: 'test' + }, + loadGeometry () { + return [[{x: 0, y: 0}]]; + } + }) + } + } + } as any as vt.VectorTile, + rawData: rawTileData + }); + } + + const layerIndex = new StyleLayerIndex([{ + id: 'test', + source: 'source', + 'source-layer': 'test', + type: 'symbol', + layout: { + 'icon-image': 'hello', + 'text-font': ['StandardFont-Bold'], + 'text-field': '{name}' + } + }]); + + const send = jest.fn().mockImplementation((type: string, data: unknown, callback: Function) => { + const res = setTimeout(() => callback(null, + type === 'getImages' ? + {'hello': {width: 1, height: 1, data: new Uint8Array([0])}} : + {'StandardFont-Bold': {width: 1, height: 1, data: new Uint8Array([0])}} + )); + + return { + cancel: () => clearTimeout(res) + }; + }); + + const actor = { + send + } as unknown as Actor; + const source = new VectorTileWorkerSource(actor, layerIndex, ['hello'], loadVectorData); + source.loadTile({ + source: 'source', + uid: 0, + tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}}, + request: {url: 'http://localhost:2900/faketile.pbf'} + } as any as WorkerTileParameters, () => { + done.fail('should not be called'); + }); + + source.reloadTile({ + source: 'source', + uid: '0', + tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}}, + } as any as WorkerTileParameters, (err, res) => { + expect(err).toBeFalsy(); + expect(res).toBeDefined(); + expect(res.rawTileData).toBeDefined(); + expect(res.rawTileData).toStrictEqual(rawTileData); + done(); + }); + }); + + test('VectorTileWorkerSource#loadTile reparses tile if reloadTile is called during reparsing', (done) => { const rawTileData = new Uint8Array([]); function loadVectorData(params, callback) { return callback(null, { diff --git a/src/source/vector_tile_worker_source.ts b/src/source/vector_tile_worker_source.ts index b694aa00d8..1a51a73a79 100644 --- a/src/source/vector_tile_worker_source.ts +++ b/src/source/vector_tile_worker_source.ts @@ -24,6 +24,12 @@ export type LoadVectorTileResult = { resourceTiming?: Array; } & ExpiryData; +type FetchingState = { + rawTileData: ArrayBuffer; + cacheControl: ExpiryData; + resourceTiming: any; +} + /** * The callback when finished loading vector data */ @@ -66,6 +72,7 @@ export class VectorTileWorkerSource implements WorkerSource { layerIndex: StyleLayerIndex; availableImages: Array; loadVectorData: LoadVectorData; + fetching: {[_: string]: FetchingState }; loading: {[_: string]: WorkerTile}; loaded: {[_: string]: WorkerTile}; @@ -80,6 +87,7 @@ export class VectorTileWorkerSource implements WorkerSource { this.layerIndex = layerIndex; this.availableImages = availableImages; this.loadVectorData = loadVectorData || loadVectorTile; + this.fetching = {}; this.loading = {}; this.loaded = {}; } @@ -124,6 +132,7 @@ export class VectorTileWorkerSource implements WorkerSource { workerTile.vectorTile = response.vectorTile; workerTile.parse(response.vectorTile, this.layerIndex, this.availableImages, this.actor, (err, result) => { + delete this.fetching[uid]; if (err || !result) return callback(err); // Transferring a copy of rawTileData because the worker needs to retain its copy. @@ -132,6 +141,8 @@ export class VectorTileWorkerSource implements WorkerSource { this.loaded = this.loaded || {}; this.loaded[uid] = workerTile; + // keep the original fetching state so that reload tile can pick it up if the original parse is cancelled by reloads' parse + this.fetching[uid] = {rawTileData, cacheControl, resourceTiming}; }) as AbortVectorData; } @@ -145,7 +156,21 @@ export class VectorTileWorkerSource implements WorkerSource { const workerTile = loaded[uid]; workerTile.showCollisionBoxes = params.showCollisionBoxes; if (workerTile.status === 'parsing') { - workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, callback); + workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, (err, result) => { + if (err || !result) return callback(err, result); + + // if we have cancelled the original parse, make sure to pass the rawTileData from the original fetch + let parseResult; + if (this.fetching[uid]) { + const {rawTileData, cacheControl, resourceTiming} = this.fetching[uid]; + delete this.fetching[uid]; + parseResult = extend({rawTileData: rawTileData.slice(0)}, result, cacheControl, resourceTiming); + } else { + parseResult = result; + } + + callback(null, parseResult); + }); } else if (workerTile.status === 'done') { // if there was no vector tile data on the initial load, don't try and re-parse tile if (workerTile.vectorTile) {