Skip to content

Commit c22c129

Browse files
authored
Improved file caching (#762)
Removed cache-busting query parameters from all file requests to allow browsers to cache files properly.
1 parent 30cf807 commit c22c129

10 files changed

Lines changed: 516 additions & 56 deletions

File tree

common/webapp/.eslintrc.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/* eslint-env node */
22
module.exports = {
33
root: true,
4+
env: {
5+
es2022: true
6+
},
47
'extends': [
58
'plugin:vue/vue3-essential',
69
'eslint:recommended'

common/webapp/src/js/BlueMapApp.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ import "./BlueMap";
2626
import {MapViewer} from "./MapViewer";
2727
import {MapControls} from "./controls/map/MapControls";
2828
import {FreeFlightControls} from "./controls/freeflight/FreeFlightControls";
29-
import {FileLoader, MathUtils, Vector3} from "three";
29+
import {MathUtils, Vector3} from "three";
3030
import {Map as BlueMapMap} from "./map/Map";
31-
import {alert, animate, EasingFunctions, generateCacheHash} from "./util/Utils";
31+
import {alert, animate, EasingFunctions} from "./util/Utils";
3232
import {MainMenu} from "./MainMenu";
3333
import {PopupMarker} from "./PopupMarker";
3434
import {MarkerSet} from "./markers/MarkerSet";
3535
import {getLocalStorage, round, setLocalStorage} from "./Utils";
36+
import {RevalidatingFileLoader} from "./util/RevalidatingFileLoader";
3637
import {i18n, setLanguage} from "../i18n";
3738
import {PlayerMarkerManager} from "./markers/PlayerMarkerManager";
3839
import {NormalMarkerManager} from "./markers/NormalMarkerManager";
@@ -310,7 +311,7 @@ export class BlueMapApp {
310311
let map = new BlueMapMap(mapId, settings.mapDataRoot + "/" + mapId, settings.liveDataRoot + "/" + mapId, this.loadBlocker, this.mapViewer.events);
311312
maps.push(map);
312313

313-
return map.loadSettings(this.mapViewer.tileCacheHash)
314+
return map.loadSettings(this.mapViewer.revalidatedUrls)
314315
.catch(error => {
315316
alert(this.events, `Failed to load settings for map '${map.data.id}':` + error, "warning");
316317
});
@@ -366,9 +367,10 @@ export class BlueMapApp {
366367
*/
367368
loadSettings() {
368369
return new Promise((resolve, reject) => {
369-
let loader = new FileLoader();
370+
let loader = new RevalidatingFileLoader();
371+
loader.setRevalidatedUrls(new Set()); // force no-cache requests
370372
loader.setResponseType("json");
371-
loader.load("settings.json?" + generateCacheHash(),
373+
loader.load("settings.json",
372374
resolve,
373375
() => {},
374376
() => reject("Failed to load the settings.json!")
@@ -382,9 +384,10 @@ export class BlueMapApp {
382384
*/
383385
loadPlayerData(map) {
384386
return new Promise((resolve, reject) => {
385-
let loader = new FileLoader();
387+
let loader = new RevalidatingFileLoader();
388+
loader.setRevalidatedUrls(new Set()); // force no-cache requests
386389
loader.setResponseType("json");
387-
loader.load(map.data.liveDataRoot + "/live/players.json?" + generateCacheHash(),
390+
loader.load(map.data.liveDataRoot + "/live/players.json",
388391
fileData => {
389392
if (!fileData) reject(`Failed to parse '${this.fileUrl}'!`);
390393
else resolve(fileData);
@@ -636,11 +639,11 @@ export class BlueMapApp {
636639
return;
637640
}
638641

639-
// Only reuse the user's tile cash hash if the current browser navigation event is not a reload.
640-
// If it's a reload, we assume the user is troubleshooting and actually wants to refresh the map.
642+
// If it's a reload, we assume the user is troubleshooting and actually
643+
// wants to fully refresh the map.
641644
const [entry] = performance.getEntriesByType("navigation");
642-
if (entry.type != "reload") {
643-
this.mapViewer.clearTileCache(this.loadUserSetting("tileCacheHash", this.mapViewer.tileCacheHash));
645+
if (entry.type === "reload") {
646+
this.mapViewer.clearTileCache();
644647
}
645648

646649
this.mapViewer.superSampling = this.loadUserSetting("superSampling", this.mapViewer.data.superSampling);
@@ -665,7 +668,6 @@ export class BlueMapApp {
665668
if (!this.settings.useCookies) return;
666669

667670
this.saveUserSetting("resetSettings", false);
668-
this.saveUserSetting("tileCacheHash", this.mapViewer.tileCacheHash);
669671

670672
this.saveUserSetting("superSampling", this.mapViewer.data.superSampling);
671673
this.saveUserSetting("hiresViewDistance", this.mapViewer.data.loadedHiresViewDistance);

common/webapp/src/js/MapViewer.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {Map} from "./map/Map";
2727
import {SkyboxScene} from "./skybox/SkyboxScene";
2828
import {ControlsManager} from "./controls/ControlsManager";
2929
import Stats from "./util/Stats";
30-
import {alert, dispatchEvent, elementOffset, generateCacheHash, htmlToElement, softClamp} from "./util/Utils";
30+
import {alert, dispatchEvent, elementOffset, htmlToElement, softClamp} from "./util/Utils";
3131
import {TileManager} from "./map/TileManager";
3232
import {HIRES_VERTEX_SHADER} from "./map/hires/HiresVertexShader";
3333
import {HIRES_FRAGMENT_SHADER} from "./map/hires/HiresFragmentShader";
@@ -79,7 +79,12 @@ export class MapViewer {
7979
loadedLowresViewDistance: 2000,
8080
});
8181

82-
this.tileCacheHash = generateCacheHash();
82+
/** @import { RevalidatingFileLoader } from "./util/RevalidatingFileLoader" */
83+
/**
84+
* Used by {@link RevalidatingFileLoader}.
85+
* @type {Set<string> | undefined}
86+
*/
87+
this.revalidatedUrls = undefined;
8388

8489
this.stats = new Stats();
8590
this.stats.hide();
@@ -405,7 +410,7 @@ export class MapViewer {
405410
this.map = map;
406411

407412
if (this.map && this.map.isMap) {
408-
return map.load(HIRES_VERTEX_SHADER, HIRES_FRAGMENT_SHADER, LOWRES_VERTEX_SHADER, LOWRES_FRAGMENT_SHADER, this.data.uniforms, this.tileCacheHash)
413+
return map.load(HIRES_VERTEX_SHADER, HIRES_FRAGMENT_SHADER, LOWRES_VERTEX_SHADER, LOWRES_FRAGMENT_SHADER, this.data.uniforms, this.revalidatedUrls)
409414
.then(() => {
410415
for (let texture of this.map.loadedTextures){
411416
this.renderer.initTexture(texture);
@@ -462,15 +467,13 @@ export class MapViewer {
462467
}
463468
}
464469

465-
clearTileCache(newTileCacheHash) {
466-
if (!newTileCacheHash) newTileCacheHash = generateCacheHash();
467-
468-
this.tileCacheHash = newTileCacheHash;
470+
clearTileCache() {
471+
this.revalidatedUrls = new Set();
469472
if (this.map) {
470473
for (let i = 0; i < this.map.lowresTileManager.length; i++) {
471-
this.map.lowresTileManager[i].tileLoader.tileCacheHash = this.tileCacheHash;
474+
this.map.lowresTileManager[i].tileLoader.revalidatedUrls = this.revalidatedUrls;
472475
}
473-
this.map.hiresTileManager.tileLoader.tileCacheHash = this.tileCacheHash;
476+
this.map.hiresTileManager.tileLoader.revalidatedUrls = this.revalidatedUrls;
474477
}
475478
}
476479

common/webapp/src/js/map/LowresTileLoader.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
import {pathFromCoords} from "../util/Utils";
2626
import {
27-
TextureLoader,
2827
Mesh,
2928
PlaneGeometry,
3029
FrontSide,
@@ -34,23 +33,25 @@ import {
3433
NearestMipMapLinearFilter,
3534
Vector2
3635
} from "three";
36+
import {RevalidatingTextureLoader} from "../util/RevalidatingTextureLoader";
3737

3838
export class LowresTileLoader {
3939

40-
constructor(tilePath, tileSettings, lod, vertexShader, fragmentShader, uniforms, loadBlocker = () => Promise.resolve(), tileCacheHash = 0) {
40+
constructor(tilePath, tileSettings, lod, vertexShader, fragmentShader, uniforms, loadBlocker = () => Promise.resolve(), revalidatedUrls) {
4141
Object.defineProperty( this, 'isLowresTileLoader', { value: true } );
4242

4343
this.tilePath = tilePath;
4444
this.tileSettings = tileSettings;
4545
this.lod = lod;
4646
this.loadBlocker = loadBlocker;
47-
this.tileCacheHash = tileCacheHash;
47+
this.revalidatedUrls = revalidatedUrls;
4848

4949
this.vertexShader = vertexShader;
5050
this.fragmentShader = fragmentShader;
5151
this.uniforms = uniforms;
5252

53-
this.textureLoader = new TextureLoader();
53+
this.textureLoader = new RevalidatingTextureLoader();
54+
this.textureLoader.setRevalidatedUrls(this.revalidatedUrls);
5455
this.geometry = new PlaneGeometry(
5556
tileSettings.tileSize.x + 1, tileSettings.tileSize.z + 1,
5657
Math.ceil(100 / (lod * 2)), Math.ceil(100 / (lod * 2))
@@ -66,7 +67,8 @@ export class LowresTileLoader {
6667

6768
//await this.loadBlocker();
6869
return new Promise((resolve, reject) => {
69-
this.textureLoader.load(tileUrl + '?' + this.tileCacheHash,
70+
this.textureLoader.setRevalidatedUrls(this.revalidatedUrls);
71+
this.textureLoader.load(tileUrl,
7072
async texture => {
7173
texture.anisotropy = 1;
7274
texture.generateMipmaps = false;

common/webapp/src/js/map/Map.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import {
2626
ClampToEdgeWrapping,
2727
Color,
28-
FileLoader,
2928
FrontSide,
3029
NearestFilter,
3130
NearestMipMapLinearFilter,
@@ -34,6 +33,7 @@ import {
3433
Texture,
3534
Vector3
3635
} from "three";
36+
import {RevalidatingFileLoader} from "../util/RevalidatingFileLoader";
3737
import {alert, dispatchEvent, getPixel, hashTile, stringToImage, vecArrToObj} from "../util/Utils";
3838
import {TileManager} from "./TileManager";
3939
import {TileLoader} from "./TileLoader";
@@ -110,14 +110,14 @@ export class Map {
110110
* @param lowresVertexShader {string}
111111
* @param lowresFragmentShader {string}
112112
* @param uniforms {object}
113-
* @param tileCacheHash {number}
113+
* @param revalidatedUrls {Set<string> | undefined}
114114
* @returns {Promise<void>}
115115
*/
116-
load(hiresVertexShader, hiresFragmentShader, lowresVertexShader, lowresFragmentShader, uniforms, tileCacheHash = 0) {
116+
load(hiresVertexShader, hiresFragmentShader, lowresVertexShader, lowresFragmentShader, uniforms, revalidatedUrls) {
117117
this.unload()
118118

119-
let settingsPromise = this.loadSettings(tileCacheHash);
120-
let textureFilePromise = this.loadTexturesFile(tileCacheHash);
119+
let settingsPromise = this.loadSettings(revalidatedUrls);
120+
let textureFilePromise = this.loadTexturesFile(revalidatedUrls);
121121

122122
this.lowresMaterial = this.createLowresMaterial(lowresVertexShader, lowresFragmentShader, uniforms);
123123

@@ -133,7 +133,7 @@ export class Map {
133133
this.hiresMaterial,
134134
this.data.hires,
135135
this.loadBlocker,
136-
tileCacheHash
136+
revalidatedUrls
137137
), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events);
138138
this.hiresTileManager.scene.matrixWorldAutoUpdate = false;
139139

@@ -147,7 +147,7 @@ export class Map {
147147
lowresFragmentShader,
148148
uniforms,
149149
async () => {},
150-
tileCacheHash
150+
revalidatedUrls
151151
), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events);
152152
this.lowresTileManager[i].scene.matrixWorldAutoUpdate = false;
153153
}
@@ -160,8 +160,8 @@ export class Map {
160160
* Loads the settings of this map
161161
* @returns {Promise<void>}
162162
*/
163-
loadSettings(tileCacheHash) {
164-
return this.loadSettingsFile(tileCacheHash)
163+
loadSettings(revalidatedUrls) {
164+
return this.loadSettingsFile(revalidatedUrls)
165165
.then(worldSettings => {
166166
this.data.name = worldSettings.name ? worldSettings.name : this.data.name;
167167

@@ -259,13 +259,14 @@ export class Map {
259259
* Loads the settings.json file for this map
260260
* @returns {Promise<Object>}
261261
*/
262-
loadSettingsFile(tileCacheHash) {
262+
loadSettingsFile(revalidatedUrls) {
263263
return new Promise((resolve, reject) => {
264264
alert(this.events, `Loading settings for map '${this.data.id}'...`, "fine");
265265

266-
let loader = new FileLoader();
266+
let loader = new RevalidatingFileLoader();
267+
loader.setRevalidatedUrls(revalidatedUrls);
267268
loader.setResponseType("json");
268-
loader.load(this.data.settingsUrl + "?" + tileCacheHash,
269+
loader.load(this.data.settingsUrl,
269270
resolve,
270271
() => {},
271272
() => reject(`Failed to load the settings.json for map: ${this.data.id}`)
@@ -277,13 +278,14 @@ export class Map {
277278
* Loads the textures.json file for this map
278279
* @returns {Promise<Object>}
279280
*/
280-
loadTexturesFile(tileCacheHash) {
281+
loadTexturesFile(revalidatedUrls) {
281282
return new Promise((resolve, reject) => {
282283
alert(this.events, `Loading textures for map '${this.data.id}'...`, "fine");
283284

284-
let loader = new FileLoader();
285+
let loader = new RevalidatingFileLoader();
286+
loader.setRevalidatedUrls(revalidatedUrls);
285287
loader.setResponseType("json");
286-
loader.load(this.data.texturesUrl + "?" + tileCacheHash,
288+
loader.load(this.data.texturesUrl,
287289
resolve,
288290
() => {},
289291
() => reject(`Failed to load the textures.json for map: ${this.data.id}`)

common/webapp/src/js/map/TileLoader.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
* THE SOFTWARE.
2424
*/
2525
import {pathFromCoords} from "../util/Utils";
26-
import {BufferGeometryLoader, FileLoader, Mesh, Material} from "three";
26+
import {BufferGeometryLoader, Mesh, Material} from "three";
2727
import {PRBMLoader} from "./hires/PRBMLoader";
28+
import {RevalidatingFileLoader} from "../util/RevalidatingFileLoader";
2829

2930
export class TileLoader {
3031

@@ -37,21 +38,22 @@ export class TileLoader {
3738
* translate: {x: number, z: number}
3839
* }}
3940
* @param loadBlocker {function: Promise}
40-
* @param tileCacheHash {number}
41+
* @param revalidatedUrls {Set<string> | undefined}
4142
*/
42-
constructor(tilePath, material, tileSettings, loadBlocker = () => Promise.resolve(), tileCacheHash = 0) {
43+
constructor(tilePath, material, tileSettings, loadBlocker = () => Promise.resolve(), revalidatedUrls) {
4344
Object.defineProperty( this, 'isTileLoader', { value: true } );
4445

4546
this.tilePath = tilePath;
4647
this.material = material;
4748
this.tileSettings = tileSettings;
4849

49-
this.tileCacheHash = tileCacheHash;
50+
this.revalidatedUrls = revalidatedUrls;
5051

5152
this.loadBlocker = loadBlocker;
5253

53-
this.fileLoader = new FileLoader();
54+
this.fileLoader = new RevalidatingFileLoader();
5455
this.fileLoader.setResponseType('arraybuffer');
56+
this.fileLoader.setRevalidatedUrls(this.revalidatedUrls);
5557

5658
this.bufferGeometryLoader = new PRBMLoader();
5759
}
@@ -60,7 +62,8 @@ export class TileLoader {
6062
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.prbm';
6163

6264
return new Promise((resolve, reject) => {
63-
this.fileLoader.load(tileUrl + '?' + this.tileCacheHash,
65+
this.fileLoader.setRevalidatedUrls(this.revalidatedUrls);
66+
this.fileLoader.load(tileUrl,
6467
async data => {
6568

6669
await this.loadBlocker();

common/webapp/src/js/markers/MarkerManager.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
* THE SOFTWARE.
2424
*/
25-
import {FileLoader} from "three";
2625
import {MarkerSet} from "./MarkerSet";
27-
import {alert, generateCacheHash} from "../util/Utils";
26+
import {alert} from "../util/Utils";
27+
import {RevalidatingFileLoader} from "../util/RevalidatingFileLoader";
2828

2929
/**
3030
* A manager for loading and updating markers from a file
@@ -116,9 +116,10 @@ export class MarkerManager {
116116
*/
117117
loadMarkerFile() {
118118
return new Promise((resolve, reject) => {
119-
let loader = new FileLoader();
119+
let loader = new RevalidatingFileLoader();
120+
loader.setRevalidatedUrls(new Set()); // force no-cache requests
120121
loader.setResponseType("json");
121-
loader.load(this.fileUrl + "?" + generateCacheHash(),
122+
loader.load(this.fileUrl,
122123
markerFileData => {
123124
if (!markerFileData) reject(`Failed to parse '${this.fileUrl}'!`);
124125
else resolve(markerFileData);
@@ -129,4 +130,4 @@ export class MarkerManager {
129130
});
130131
}
131132

132-
}
133+
}

0 commit comments

Comments
 (0)