Skip to content

Commit 59be754

Browse files
consolidate, optimize, add functionality, tests
1 parent cae43e5 commit 59be754

File tree

10 files changed

+598
-494
lines changed

10 files changed

+598
-494
lines changed

src/source/vector_tile_worker_source.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Protobuf from 'pbf';
22
import {VectorTile, type VectorTileLayer} from '@mapbox/vector-tile';
33
import {type ExpiryData, getArrayBuffer} from '../util/ajax';
44
import {WorkerTile} from './worker_tile';
5-
import {BoundedLRUCache} from '../tile/tile_cache';
5+
import {BoundedLRUCache} from '../tile/bounded_lru_cache';
66
import {extend} from '../util/util';
77
import {RequestPerformance} from '../util/performance';
88
import {VectorTileOverzoomed, sliceVectorTileLayer, toVirtualVectorTile} from './vector_tile_overzoomed';
@@ -60,7 +60,7 @@ export class VectorTileWorkerSource implements WorkerSource {
6060
this.fetching = {};
6161
this.loading = {};
6262
this.loaded = {};
63-
this.overzoomedTileResultCache = new BoundedLRUCache<string, LoadVectorTileResult>(1000);
63+
this.overzoomedTileResultCache = new BoundedLRUCache<string, LoadVectorTileResult>({maxEntries: 1000});
6464
}
6565

6666
/**
@@ -69,8 +69,8 @@ export class VectorTileWorkerSource implements WorkerSource {
6969
async loadVectorTile(params: WorkerTileParameters, abortController: AbortController): Promise<LoadVectorTileResult> {
7070
const response = await getArrayBuffer(params.request, abortController);
7171
try {
72-
const vectorTile = params.encoding !== 'mlt'
73-
? new VectorTile(new Protobuf(response.data))
72+
const vectorTile = params.encoding !== 'mlt'
73+
? new VectorTile(new Protobuf(response.data))
7474
: new MLTVectorTile(response.data);
7575
return {
7676
vectorTile,
@@ -171,7 +171,7 @@ export class VectorTileWorkerSource implements WorkerSource {
171171

172172
const cacheKey = `${maxZoomTileID.key}_${tileID.key}`;
173173
const cachedOverzoomTile = this.overzoomedTileResultCache.get(cacheKey);
174-
174+
175175
if (cachedOverzoomTile) {
176176
return cachedOverzoomTile;
177177
}

src/style/style.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2050,7 +2050,7 @@ export class Style extends Evented {
20502050
}
20512051
tileManager._tiles = {};
20522052
}
2053-
tileManager._cache.reset();
2053+
tileManager._cache.clear();
20542054
tileManager.onRemove(this.map);
20552055
}
20562056
this.tileManagers = {};

src/tile/bounded_lru_cache.test.ts

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
import {describe, test, expect} from 'vitest';
2+
import {type Tile} from './tile';
3+
import {BoundedLRUCache} from './bounded_lru_cache';
4+
import {OverscaledTileID} from './tile_id';
5+
6+
const _idA = new OverscaledTileID(10, 0, 10, 0, 1);
7+
const _idB = new OverscaledTileID(10, 0, 10, 0, 2);
8+
const _idC = new OverscaledTileID(10, 0, 10, 0, 3);
9+
const _idD = new OverscaledTileID(10, 0, 10, 0, 4);
10+
const idA = _idA.key;
11+
const idB = _idB.key;
12+
const idC = _idC.key;
13+
const idD = _idD.key;
14+
const tileA = {tileID: _idA} as Tile;
15+
const tileA2 = {tileID: _idA} as Tile;
16+
const tileB = {tileID: _idB} as Tile;
17+
const tileC = {tileID: _idC} as Tile;
18+
const tileD = {tileID: _idD} as Tile;
19+
20+
function keysExpected(cache: BoundedLRUCache<string, Tile>, ids: string[]): void {
21+
expect(cache.getKeys()).toEqual(ids);
22+
}
23+
24+
describe('BoundedLRUCache', () => {
25+
test('complex flow', () => {
26+
const cache = new BoundedLRUCache<string, Tile>({
27+
maxEntries: 10,
28+
onRemove: (removed) => expect(removed).toBe(idC)
29+
});
30+
expect(cache.take(idC)).toBeUndefined();
31+
cache.set(idA, tileA);
32+
keysExpected(cache, [idA]);
33+
expect(cache.get(idA)).toBe(tileA);
34+
expect(cache.take(idA)).toBe(tileA);
35+
expect(cache.take(idA)).toBeUndefined();
36+
expect(cache.get(idA)).toBe(undefined);
37+
keysExpected(cache, []);
38+
});
39+
40+
test('get', () => {
41+
let removeCount = 0;
42+
const cache = new BoundedLRUCache<string, Tile>({
43+
maxEntries: 10,
44+
onRemove: () => removeCount++
45+
});
46+
cache.set(idA, tileA);
47+
expect(cache.get(idA)).toBe(tileA);
48+
keysExpected(cache, [idA]);
49+
expect(cache.get(idA)).toBe(tileA);
50+
expect(removeCount).toBe(0);
51+
});
52+
53+
test('get re-ordering and getKeys order', () => {
54+
const cache = new BoundedLRUCache<string, Tile>({
55+
maxEntries: 10,
56+
});
57+
cache.set(idA, tileA);
58+
cache.set(idB, tileB);
59+
cache.set(idC, tileC);
60+
expect(cache.getKeys()).toEqual([idA, idB, idC]);
61+
expect(cache.get(idA)).toBe(tileA);
62+
expect(cache.getKeys()).toEqual([idB, idC, idA]);
63+
});
64+
65+
test('take without calling onRemove', () => {
66+
let removeCount = 0;
67+
const cache = new BoundedLRUCache<string, Tile>({
68+
maxEntries: 10,
69+
onRemove: () => removeCount++
70+
});
71+
cache.set(idA, tileA);
72+
keysExpected(cache, [idA]);
73+
74+
const tile = cache.take(idA);
75+
expect(tile).toBe(tileA);
76+
expect(cache.get(idA)).toBeUndefined();
77+
keysExpected(cache, []);
78+
expect(removeCount).toBe(0);
79+
});
80+
81+
test('remove tile calls onRemove', () => {
82+
let removeCount = 0;
83+
const cache = new BoundedLRUCache<string, Tile>({
84+
maxEntries: 10,
85+
onRemove: () => removeCount++
86+
});
87+
cache.set(idA, tileA);
88+
cache.remove(idA);
89+
expect(removeCount).toBe(1);
90+
});
91+
92+
test('duplicate add', () => {
93+
let removeCount = 0;
94+
const cache = new BoundedLRUCache<string, Tile>({
95+
maxEntries: 10,
96+
onRemove: () => removeCount++
97+
});
98+
cache.set(idA, tileA);
99+
cache.set(idA, tileA2);
100+
101+
keysExpected(cache, [idA]);
102+
expect(cache.get(idA)).toBe(tileA2);
103+
expect(cache.take(idA)).toBe(tileA2);
104+
expect(cache.get(idA)).toBeUndefined();
105+
expect(cache.take(idA)).toBeUndefined();
106+
keysExpected(cache, []);
107+
expect(removeCount).toBe(1);
108+
});
109+
110+
test('set of same tile id using the same tile retains tile and moves to end without calling remove', () => {
111+
let removeCount = 0;
112+
const cache = new BoundedLRUCache<string, Tile>({
113+
maxEntries: 10,
114+
onRemove: () => removeCount++
115+
});
116+
cache.set(idA, tileA);
117+
cache.set(idB, tileB);
118+
keysExpected(cache, [idA, idB]);
119+
120+
// Set the same tile reference again, but it should not call remove.
121+
cache.set(idA, tileA);
122+
expect(removeCount).toBe(0);
123+
keysExpected(cache, [idB, idA]);
124+
expect(cache.get(idA)).toBe(tileA);
125+
});
126+
127+
test('set of same tile id using a new tile removes previous tile reference and calls remove', () => {
128+
let removeCount = 0;
129+
const cache = new BoundedLRUCache<string, Tile>({
130+
maxEntries: 10,
131+
onRemove: () => removeCount++
132+
133+
});
134+
cache.set(idA, tileA);
135+
cache.set(idB, tileB);
136+
keysExpected(cache, [idA, idB]);
137+
138+
// Set the same tile id but with new tile, remove should be called
139+
cache.set(idA, tileA2);
140+
expect(removeCount).toBe(1);
141+
keysExpected(cache, [idB, idA]);
142+
expect(cache.get(idA)).toBe(tileA2);
143+
});
144+
145+
test('duplicate set of same tile id updates entry to most recently added tile', () => {
146+
const cache = new BoundedLRUCache<string, Tile>({maxEntries: 10});
147+
cache.set(idA, tileA);
148+
cache.set(idA, tileA2);
149+
150+
keysExpected(cache, [idA]);
151+
expect(cache.get(idA)).toBe(tileA2);
152+
});
153+
154+
test('set of tile over max entries trims the cache', () => {
155+
let removeCount = 0;
156+
const cache = new BoundedLRUCache<string, Tile>({
157+
maxEntries: 1,
158+
onRemove: () => removeCount++
159+
160+
});
161+
cache.set(idA, tileA);
162+
cache.set(idB, tileB);
163+
keysExpected(cache, [idB]);
164+
expect(cache.get(idB)).toBe(tileB);
165+
166+
cache.set(idA, tileA2);
167+
keysExpected(cache, [idA]);
168+
expect(cache.get(idA)).toBe(tileA2);
169+
170+
expect(removeCount).toBe(2);
171+
});
172+
173+
test('removes tiles', () => {
174+
const cache = new BoundedLRUCache<string, Tile>({
175+
maxEntries: 10
176+
});
177+
178+
cache.set(idA, tileA);
179+
cache.set(idB, tileB);
180+
cache.set(idC, tileC);
181+
182+
keysExpected(cache, [idA, idB, idC]);
183+
expect(cache.get(idB)).toBe(tileB);
184+
cache.remove(idB);
185+
186+
keysExpected(cache, [idA, idC]);
187+
expect(cache.get(idB)).toBeUndefined();
188+
expect(() => cache.remove(idB)).not.toThrow();
189+
});
190+
191+
test('filters tiles using provided function', () => {
192+
let removeCount = 0;
193+
const cache = new BoundedLRUCache<string, Tile>({
194+
maxEntries: 10,
195+
onRemove: () => removeCount++
196+
});
197+
cache.set(idA, tileA);
198+
cache.set(idB, tileB);
199+
cache.set(idC, tileC);
200+
cache.set(idD, tileD);
201+
202+
cache.filter(tile => tile.tileID.canonical.y === 2 || tile.tileID.canonical.y === 3);
203+
keysExpected(cache, [idB, idC]);
204+
expect(removeCount).toBe(2);
205+
206+
removeCount = 0;
207+
cache.set(idA, tileA);
208+
cache.set(idB, tileB);
209+
cache.set(idC, tileC);
210+
cache.set(idD, tileD);
211+
cache.filter(tile => tile.tileID.canonical.y === 1 || tile.tileID.canonical.y === 4);
212+
keysExpected(cache, [idA, idD]);
213+
expect(removeCount).toBe(2);
214+
});
215+
216+
test('clear tiles call onRemove on all tiles', () => {
217+
let removeCount = 0;
218+
const cache = new BoundedLRUCache<string, Tile>({
219+
maxEntries: 10,
220+
onRemove: () => removeCount++
221+
});
222+
223+
cache.set(idA, tileA);
224+
cache.set(idB, tileB);
225+
cache.set(idC, tileC);
226+
227+
cache.clear();
228+
keysExpected(cache, []);
229+
expect(removeCount).toBe(3);
230+
});
231+
232+
test('.clear removes all tiles', () => {
233+
let called;
234+
const cache = new BoundedLRUCache<string, Tile>({
235+
maxEntries: 10,
236+
onRemove: (removed) => {
237+
expect(removed).toBe(tileA);
238+
called = true;
239+
}
240+
});
241+
cache.set(idA, tileA);
242+
cache.clear();
243+
expect(cache.get(idA)).toBeUndefined();
244+
expect(called).toBeTruthy();
245+
});
246+
247+
test('overflow automatically evicts', () => {
248+
const cache = new BoundedLRUCache<string, Tile>({
249+
maxEntries: 1,
250+
onRemove: (removed) => expect(removed).toBe(tileA)
251+
});
252+
cache.set(idA, tileA);
253+
cache.set(idB, tileB);
254+
255+
expect(cache.get(idB)).toBeTruthy();
256+
expect(cache.get(idA)).toBeFalsy();
257+
});
258+
259+
test('.setMaxSize trims tile count', () => {
260+
let numRemoved = 0;
261+
const cache = new BoundedLRUCache<string, Tile>({
262+
maxEntries: 10,
263+
onRemove: () => numRemoved++
264+
});
265+
cache.set(idA, tileA);
266+
cache.set(idB, tileB);
267+
cache.set(idC, tileC);
268+
expect(numRemoved).toBe(0);
269+
cache.setMaxSize(15);
270+
expect(numRemoved).toBe(0);
271+
cache.setMaxSize(1);
272+
expect(numRemoved).toBe(2);
273+
keysExpected(cache, [idC]);
274+
cache.set(idD, tileD);
275+
expect(numRemoved).toBe(3);
276+
keysExpected(cache, [idD]);
277+
});
278+
279+
test('evicts least-recently-used item when capacity exceeded', () => {
280+
const cache = new BoundedLRUCache<string, number>({maxEntries: 2});
281+
282+
cache.set('a', 1);
283+
cache.set('b', 2);
284+
285+
// Access 'a' to make it most-recently-used
286+
expect(cache.get('a')).toBe(1);
287+
288+
// Insert 'c' -> should evict 'b' (the least recently used)
289+
cache.set('c', 3);
290+
291+
expect(cache.get('b')).toBeUndefined();
292+
expect(cache.get('a')).toBe(1);
293+
expect(cache.get('c')).toBe(3);
294+
});
295+
296+
test('setting an existing key updates value and makes it most-recently-used', () => {
297+
const cache = new BoundedLRUCache<string, number>({maxEntries: 2});
298+
299+
cache.set('a', 1);
300+
cache.set('b', 2);
301+
302+
// Update 'a' value and it should become most-recently-used
303+
cache.set('a', 10);
304+
// Insert 'c' -> should evict 'b'
305+
cache.set('c', 3);
306+
307+
expect(cache.get('b')).toBeUndefined();
308+
expect(cache.get('a')).toBe(10);
309+
expect(cache.get('c')).toBe(3);
310+
});
311+
312+
test('capacity 1 evicts previous entry on new set', () => {
313+
const cache = new BoundedLRUCache<string, string>({maxEntries: 1});
314+
315+
cache.set('x', 'first');
316+
expect(cache.get('x')).toBe('first');
317+
318+
cache.set('y', 'second');
319+
expect(cache.get('x')).toBeUndefined();
320+
expect(cache.get('y')).toBe('second');
321+
});
322+
323+
test('clear removes all entries', () => {
324+
const cache = new BoundedLRUCache<number, string>({maxEntries: 3});
325+
cache.set(1, 'one');
326+
cache.set(2, 'two');
327+
328+
expect(cache.get(1)).toBe('one');
329+
expect(cache.get(2)).toBe('two');
330+
331+
cache.clear();
332+
333+
expect(cache.get(1)).toBeUndefined();
334+
expect(cache.get(2)).toBeUndefined();
335+
});
336+
});

0 commit comments

Comments
 (0)