Skip to content

Commit

Permalink
feat(geo-layers): Add .debounceTime option to Tileset2D, TileLayer (#…
Browse files Browse the repository at this point in the history
…8589)

* TileLayer: Add .debounceTime option
* Tileset2D: Restore unit test coverage
  • Loading branch information
donmccurdy committed Mar 11, 2024
1 parent 375ef98 commit 401e7a6
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 17 deletions.
10 changes: 10 additions & 0 deletions docs/api-reference/geo-layers/tile-layer.md
Expand Up @@ -220,6 +220,16 @@ If the web server supports HTTP/2 (Open Chrome dev tools and look for "h2" in th

- Default: `6`

##### `debounceTime` (Number, optional) {#debouncetime}

Queue tile requests until no new tiles have been added for at least `debounceTime` milliseconds.

If `debounceTime == 0`, tile requests are issued as quickly as the `maxRequests` concurrent request limit allows.

If `debounceTime > 0`, tile requests are queued until a period of at least `debounceTime` milliseconds has passed without any new tiles being added to the queue. May reduce bandwidth usage and total loading time during interactive view transitions.

- Default: `0`

### Render Options

##### `renderSubLayers` (Function, optional) {#rendersublayers}
Expand Down
10 changes: 10 additions & 0 deletions modules/geo-layers/src/tile-layer/tile-layer.ts
Expand Up @@ -44,6 +44,7 @@ const defaultProps: DefaultProps<TileLayerProps> = {
refinementStrategy: STRATEGY_DEFAULT,
zRange: null,
maxRequests: 6,
debounceTime: 0,
zoomOffset: 0
};

Expand Down Expand Up @@ -128,6 +129,13 @@ type _TileLayerProps<DataT> = {
*/
maxRequests?: number;

/**
* Queue tile requests until no new tiles have been requested for at least `debounceTime` milliseconds.
*
* @default 0
*/
debounceTime?: number;

/**
* This offset changes the zoom level at which the tiles are fetched.
*
Expand Down Expand Up @@ -221,6 +229,7 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
maxZoom,
minZoom,
maxRequests,
debounceTime,
zoomOffset
} = this.props;

Expand All @@ -233,6 +242,7 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
refinementStrategy,
extent,
maxRequests,
debounceTime,
zoomOffset,

getTileData: this.getTileData.bind(this),
Expand Down
11 changes: 7 additions & 4 deletions modules/geo-layers/src/tileset-2d/tileset-2d.ts
Expand Up @@ -73,6 +73,8 @@ export type Tileset2DProps<DataT = any> = {
zRange?: ZRange | null;
/** The maximum number of concurrent getTileData calls. @default 6 */
maxRequests?: number;
/** Queue tile requests until no new tiles have been requested for at least `debounceTime` milliseconds. @default 0 */
debounceTime?: number;
/** Changes the zoom level at which the tiles are fetched. Needs to be an integer. @default 0 */
zoomOffset?: number;

Expand Down Expand Up @@ -101,6 +103,7 @@ export const DEFAULT_TILESET2D_PROPS: Omit<Required<Tileset2DProps>, 'getTileDat
refinementStrategy: 'best-available',
zRange: null,
maxRequests: 6,
debounceTime: 0,
zoomOffset: 0,

// onTileLoad: (tile: Tile2DHeader) => void, // onTileUnload: (tile: Tile2DHeader) => void, // onTileError: (error: any, tile: Tile2DHeader) => void, /** Called when all tiles in the current viewport are loaded. */
Expand Down Expand Up @@ -140,6 +143,7 @@ export class Tileset2D {
*/
constructor(opts: Tileset2DProps) {
this.opts = {...DEFAULT_TILESET2D_PROPS, ...opts};
this.setOptions(this.opts);

this.onTileLoad = tile => {
this.opts.onTileLoad?.(tile);
Expand All @@ -150,8 +154,9 @@ export class Tileset2D {
};

this._requestScheduler = new RequestScheduler({
maxRequests: opts.maxRequests,
throttleRequests: Boolean(opts.maxRequests && opts.maxRequests > 0)
throttleRequests: this.opts.maxRequests > 0 || this.opts.debounceTime > 0,
maxRequests: this.opts.maxRequests,
debounceTime: this.opts.debounceTime
});

// Maps tile id in string {z}-{x}-{y} to a Tile object
Expand All @@ -168,8 +173,6 @@ export class Tileset2D {

this._modelMatrix = new Matrix4();
this._modelMatrixInverse = new Matrix4();

this.setOptions(opts);
}

/* Public API */
Expand Down
4 changes: 2 additions & 2 deletions modules/test-utils/src/generate-layer-tests.ts
Expand Up @@ -46,8 +46,8 @@ export function generateLayerTests<LayerT extends Layer>({
*/
sampleProps?: Partial<LayerT['props']>;
assert?: (condition: any, comment: string) => void;
onBeforeUpdate: LayerTestCase<LayerT>['onBeforeUpdate'];
onAfterUpdate: LayerTestCase<LayerT>['onAfterUpdate'];
onBeforeUpdate?: LayerTestCase<LayerT>['onBeforeUpdate'];
onAfterUpdate?: LayerTestCase<LayerT>['onAfterUpdate'];

/**
* Test some typical assumptions after layer updates
Expand Down
38 changes: 37 additions & 1 deletion test/modules/geo-layers/tile-layer/tile-layer.spec.ts
Expand Up @@ -71,6 +71,7 @@ test('TileLayer', async t => {

const testCases = [
{
title: 'default',
props: {
data: DUMMY_DATA
},
Expand All @@ -87,6 +88,7 @@ test('TileLayer', async t => {
}
},
{
title: 'show z=2',
props: {
getTileData,
renderSubLayers
Expand All @@ -109,6 +111,7 @@ test('TileLayer', async t => {
}
},
{
title: 'show z=3',
viewport: testViewport2,
onAfterUpdate: ({layer, subLayers}) => {
if (layer.isLoaded) {
Expand All @@ -124,6 +127,7 @@ test('TileLayer', async t => {
}
},
{
title: 'hide z=3',
viewport: testViewport1,
onAfterUpdate: ({layer, subLayers}) => {
if (layer.isLoaded) {
Expand All @@ -139,6 +143,7 @@ test('TileLayer', async t => {
}
},
{
title: 'cached sublayers',
updateProps: {
renderSubLayers: renderNestedSubLayers
},
Expand All @@ -147,6 +152,7 @@ test('TileLayer', async t => {
}
},
{
title: 'cached sublayers (invalidate)',
updateProps: {
minWidthPixels: 1
},
Expand All @@ -155,6 +161,7 @@ test('TileLayer', async t => {
}
},
{
title: 'cached sublayers (refetch)',
updateProps: {
updateTriggers: {
getTileData: 1
Expand Down Expand Up @@ -186,10 +193,11 @@ test('TileLayer#MapView:repeat', async t => {
repeat: true
});

t.is(testViewport.subViewports.length, 3, 'Viewport has more than one sub viewports');
t.is(testViewport.subViewports!.length, 3, 'Viewport has more than one sub viewports');

const testCases = [
{
title: 'repeat',
props: {
data: DUMMY_DATA,
renderSubLayers
Expand Down Expand Up @@ -224,6 +232,7 @@ test('TileLayer#AbortRequestsOnUpdateTrigger', async t => {

const testCases = [
{
title: 'case-1',
props: {
getTileData: () => sleep(10)
},
Expand All @@ -232,6 +241,7 @@ test('TileLayer#AbortRequestsOnUpdateTrigger', async t => {
}
},
{
title: 'case-2',
updateProps: {
updateTriggers: {
getTileData: 1
Expand Down Expand Up @@ -265,6 +275,7 @@ test('TileLayer#AbortRequestsOnNewLayer', async t => {

const testCases = [
{
title: 'case-1',
props: {
getTileData: () => sleep(10)
},
Expand All @@ -273,6 +284,7 @@ test('TileLayer#AbortRequestsOnNewLayer', async t => {
}
},
{
title: 'case-2',
props: {
id: 'new-layer'
},
Expand All @@ -291,6 +303,30 @@ test('TileLayer#AbortRequestsOnNewLayer', async t => {
t.end();
});

test('TileLayer#debounceTime', async t => {
const testViewport = new WebMercatorViewport({width: 1200, height: 400, zoom: 8});
const testCases = [
{
title: 'debounceTime = 0',
props: {debounceTime: 0, getTileData: () => sleep(10)},
onAfterUpdate: ({layer}) => {
t.is(layer.state.tileset.opts.debounceTime, 0, 'assigns tileset#debounceTime = 0');
}
},
{
title: 'debounceTime = 25',
props: {debounceTime: 25, getTileData: () => sleep(10)},
onAfterUpdate: ({layer}) => {
t.is(layer.state.tileset.opts.debounceTime, 25, 'assigns tileset#debounceTime = 25');
}
}
];

testLayer({Layer: TileLayer, viewport: testViewport, testCases, onError: t.fail});

t.end();
});

function sleep(ms) {
return new Promise(resolve => {
/* global setTimeout */
Expand Down
3 changes: 1 addition & 2 deletions test/modules/geo-layers/tileset-2d/index.ts
@@ -1,4 +1,3 @@
import './utils.spec';
// TODO disabled for v9, restore ASAP
// import './tileset-2d.spec';
import './tileset-2d.spec';
import './tile-2d-header.spec';
46 changes: 38 additions & 8 deletions test/modules/geo-layers/tileset-2d/tileset-2d.spec.ts
@@ -1,6 +1,6 @@
import test from 'tape-promise/tape';
import {_Tileset2D as Tileset2D} from '@deck.gl/geo-layers';
import {WebMercatorViewport, OrthographicView} from '@deck.gl/core';
import {WebMercatorViewport, OrthographicView, Viewport} from '@deck.gl/core';
import {Matrix4} from '@math.gl/core';

const testViewState = {
Expand Down Expand Up @@ -67,7 +67,7 @@ test('Tileset2D#updateOnModelMatrix', t => {
target: [100, 100],
zoom: 4
}
});
}) as Viewport;
tileset.update(testOrthoraphicViewport, {modelMatrix: null});
t.is(tileset._cache.size, 4, 'null model matrix updates');

Expand All @@ -81,14 +81,17 @@ test('Tileset2D#updateOnModelMatrix', t => {
});

test('Tileset2D#finalize', async t => {
const tileDataPending = sleep(50);
const tileset = new Tileset2D({
getTileData: () => sleep(50),
getTileData: () => tileDataPending,
onTileLoad: () => {}
});

tileset.update(testViewport);
const tile1 = tileset.selectedTiles[0];
const tile1 = tileset.selectedTiles![0];

await sleep(60);
await tileDataPending;
await sleep(10);

tileset.update(
new WebMercatorViewport(
Expand All @@ -98,7 +101,7 @@ test('Tileset2D#finalize', async t => {
})
)
);
const tile2 = tileset.selectedTiles[0];
const tile2 = tileset.selectedTiles![0];

tileset.finalize();
t.notOk(tileset._cache.size, 'cache is purged');
Expand Down Expand Up @@ -144,7 +147,8 @@ test('Tileset2D#maxCacheSize', t => {
t.end();
});

test('Tileset2D#maxCacheByteSize', async t => {
// TODO v9
test.skip('Tileset2D#maxCacheByteSize', async t => {
const tileset = new Tileset2D({
getTileData: () => Promise.resolve({byteLength: 100}),
maxCacheByteSize: 150,
Expand Down Expand Up @@ -176,6 +180,31 @@ test('Tileset2D#maxCacheByteSize', async t => {
t.end();
});

test('Tileset2D#debounceTime', async t => {
const tileset = new Tileset2D({
getTileData: () => sleep(2),
maxRequests: 1000,
debounceTime: 25,
onTileLoad: () => {}
});

tileset.update(new WebMercatorViewport(Object.assign({}, testViewState, {zoom: 20})));

await sleep(10);

for (const tile of tileset.tiles) {
t.false(tile.isLoaded, `tile ${tile.id} pending`);
}

await sleep(50);

for (const tile of tileset.tiles) {
t.true(tile.isLoaded, `tile ${tile.id} loaded`);
}

t.end();
});

test('Tileset2D#over-zoomed', t => {
const tileset = new Tileset2D({
getTileData,
Expand Down Expand Up @@ -306,7 +335,8 @@ test('Tileset2D#traversal', async t => {
const tileset = new Tileset2D({
getTileData: () => sleep(10),
onTileLoad: () => {},
onTileError: () => {}
onTileError: () => {},
maxRequests: 0
});

/*
Expand Down

0 comments on commit 401e7a6

Please sign in to comment.