@woosh/meep-engine 2.73.0 → 2.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/meep.cjs +4 -4
  3. package/build/meep.min.js +1 -1
  4. package/build/meep.module.js +4 -4
  5. package/package.json +1 -1
  6. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +2 -1
  7. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +4 -5
  8. package/src/engine/asset/loaders/image/png/PNGReader.js +23 -19
  9. package/src/engine/graphics/generate_halton_jitter.js +21 -0
  10. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js +3 -20
  11. package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +129 -20
  12. package/src/engine/graphics/texture/virtual/v2/TileLoader.js +24 -7
  13. package/src/engine/graphics/texture/virtual/v2/UsageMetadata.js +73 -21
  14. package/src/engine/graphics/texture/virtual/v2/VirtualTextureManager.js +60 -5
  15. package/src/engine/graphics/texture/virtual/v2/debug/ResidencyDebugView.js +58 -0
  16. package/src/engine/graphics/texture/virtual/v2/debug/UsageDebugView.js +63 -0
  17. package/src/engine/graphics/texture/virtual/v2/{UsagePyramidDebugView.js → debug/UsagePyramidDebugView.js} +30 -39
  18. package/src/engine/graphics/texture/virtual/v2/prototype.js +46 -21
  19. package/src/engine/graphics/texture/virtual/v2/{TextureTile.js → tile/TextureTile.js} +1 -1
  20. package/src/engine/graphics/texture/virtual/v2/tile/compose_finger_print.js +23 -0
  21. package/src/engine/graphics/texture/virtual/v2/tile/compose_tile_address.js +22 -0
  22. package/src/engine/graphics/texture/virtual/v2/tile/decompose_finger_print.js +12 -0
  23. package/src/engine/graphics/texture/virtual/v2/tile/finger_print_to_tile_address.js +16 -0
  24. package/src/engine/graphics/texture/virtual/v2/{tile_index_to_finger_print.js → tile/tile_address_to_finger_print.js} +7 -3
  25. package/src/view/CSS_ABSOLUTE_POSITIONING.js +5 -0
  26. package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +0 -64
  27. package/src/engine/graphics/texture/virtual/v2/compose_finger_print.js +0 -13
  28. package/src/engine/graphics/texture/virtual/v2/finger_print_to_tile_index.js +0 -37
@@ -69586,9 +69586,8 @@ class ThreadedImageDecoder extends Codec {
69586
69586
  return true;
69587
69587
  }
69588
69588
 
69589
- async decode(data) {
69590
- const result = await this.worker.request('decode', [data.buffer]);
69591
- return result;
69589
+ decode(data) {
69590
+ return this.worker.request('decode', [data.buffer]);
69592
69591
  }
69593
69592
  }
69594
69593
 
@@ -69621,7 +69620,8 @@ class NativeImageDecoder extends Codec {
69621
69620
  const image = new Image();
69622
69621
 
69623
69622
  // convert binary data to image URL that we can load
69624
- const url = URL.createObjectURL(new Blob([data]));
69623
+ const blob = new Blob([data]);
69624
+ const url = URL.createObjectURL(blob);
69625
69625
 
69626
69626
  image.src = url;
69627
69627
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "description": "Fully featured ECS game engine written in JavaScript",
6
6
  "type": "module",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.73.0",
8
+ "version": "2.74.0",
9
9
  "main": "build/meep.module.js",
10
10
  "module": "build/meep.module.js",
11
11
  "exports": {
@@ -29,7 +29,8 @@ export class NativeImageDecoder extends Codec {
29
29
  const image = new Image();
30
30
 
31
31
  // convert binary data to image URL that we can load
32
- const url = URL.createObjectURL(new Blob([data]));
32
+ const blob = new Blob([data]);
33
+ const url = URL.createObjectURL(blob);
33
34
 
34
35
  image.src = url;
35
36
 
@@ -1,7 +1,7 @@
1
- import { Codec } from "./Codec.js";
2
- import WorkerBuilder from "../../../../../core/process/worker/WorkerBuilder.js";
3
1
  import { OnDemandWorkerManager } from "../../../../../core/process/worker/OnDemandWorkerManager.js";
2
+ import WorkerBuilder from "../../../../../core/process/worker/WorkerBuilder.js";
4
3
  import { PNG_HEADER_BYTES } from "../png/PNG_HEADER_BYTES.js";
4
+ import { Codec } from "./Codec.js";
5
5
 
6
6
  export class ThreadedImageDecoder extends Codec {
7
7
  constructor() {
@@ -35,8 +35,7 @@ export class ThreadedImageDecoder extends Codec {
35
35
  return true;
36
36
  }
37
37
 
38
- async decode(data) {
39
- const result = await this.worker.request('decode', [data.buffer]);
40
- return result;
38
+ decode(data) {
39
+ return this.worker.request('decode', [data.buffer]);
41
40
  }
42
41
  }
@@ -2,13 +2,13 @@
2
2
  "use strict";
3
3
 
4
4
  import zlib from 'pako';
5
-
6
- import { PNG } from './PNG.js';
7
5
  import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
8
6
  import { isArrayEqualStrict } from "../../../../../core/collection/array/isArrayEqualStrict.js";
9
- import { PNG_HEADER_BYTES } from "./PNG_HEADER_BYTES.js";
10
7
  import { crc } from "./crc.js";
11
8
 
9
+ import { PNG } from './PNG.js';
10
+ import { PNG_HEADER_BYTES } from "./PNG_HEADER_BYTES.js";
11
+
12
12
  /**
13
13
  *
14
14
  * @param {Uint8Array} buffer
@@ -217,7 +217,7 @@ PNGReader.prototype.decodeiEXt = function (chunk) {
217
217
  * Interlace method 1 byte
218
218
  */
219
219
  PNGReader.prototype.decodeIHDR = function (chunk) {
220
- var png = this.png;
220
+ const png = this.png;
221
221
 
222
222
  png.setWidth(readUInt32(chunk, 0));
223
223
  png.setHeight(readUInt32(chunk, 4));
@@ -263,7 +263,7 @@ PNGReader.prototype.decodeIEND = function () {
263
263
  * Uncompress IDAT chunks
264
264
  */
265
265
  PNGReader.prototype.decodePixels = function () {
266
- var png = this.png;
266
+ const png = this.png;
267
267
 
268
268
  const inflator = new zlib.Inflate();
269
269
 
@@ -292,21 +292,25 @@ PNGReader.prototype.decodePixels = function () {
292
292
 
293
293
  PNGReader.prototype.interlaceNone = function (data) {
294
294
 
295
- var png = this.png;
295
+ const png = this.png;
296
296
 
297
- // bytes per pixel
298
- var bytes_per_pixel = Math.max(1, png.colors * png.bitDepth / 8);
297
+ const bits_per_pixel = png.bitDepth;
298
+ const bits_per_line = png.colors * bits_per_pixel;
299
+ const bytes_per_pixel = Math.max(1, bits_per_line / 8);
300
+
301
+ const width = png.width;
302
+ const height = png.height;
299
303
 
300
304
  // color bytes per row
301
- const color_bytes_per_row = bytes_per_pixel * png.width;
305
+ const color_bytes_per_row = bytes_per_pixel * width;
306
+
307
+ const pixels = new Uint8Array(bytes_per_pixel * width * height);
302
308
 
303
- var pixels = new Uint8Array(bytes_per_pixel * png.width * png.height);
304
- // var scanline;
305
- var offset = 0;
309
+ let offset = 0;
306
310
 
307
311
  const data_length = data.length;
308
312
 
309
- for (var i = 0; i < data_length; i += color_bytes_per_row + 1) {
313
+ for (let i = 0; i < data_length; i += color_bytes_per_row + 1) {
310
314
 
311
315
  const scanline_address = i + 1;
312
316
 
@@ -367,7 +371,7 @@ PNGReader.prototype.unFilterNone = function (scanline, scanline_offset, pixels,
367
371
  * Sub(x) = Raw(x) + Raw(x - bpp)
368
372
  */
369
373
  PNGReader.prototype.unFilterSub = function (scanline, scanline_offset, pixels, bpp, of, length) {
370
- var i = 0;
374
+ let i = 0;
371
375
 
372
376
  for (; i < bpp; i++) {
373
377
  pixels[of + i] = scanline[i + scanline_offset];
@@ -387,7 +391,7 @@ PNGReader.prototype.unFilterSub = function (scanline, scanline_offset, pixels, b
387
391
  * Up(x) = Raw(x) + Prior(x)
388
392
  */
389
393
  PNGReader.prototype.unFilterUp = function (scanline, scanline_offset, pixels, bpp, of, length) {
390
- var i = 0, byte, prev;
394
+ let i = 0, byte, prev;
391
395
  // Prior(x) is 0 for all x on the first scanline
392
396
  if ((of - length) < 0) for (; i < length; i++) {
393
397
  pixels[of + i] = scanline[i + scanline_offset];
@@ -406,7 +410,7 @@ PNGReader.prototype.unFilterUp = function (scanline, scanline_offset, pixels, bp
406
410
  * Average(x) = Raw(x) + floor((Raw(x-bpp)+Prior(x))/2)
407
411
  */
408
412
  PNGReader.prototype.unFilterAverage = function (scanline, scanline_offset, pixels, bpp, of, length) {
409
- var i = 0, byte, prev, prior;
413
+ let i = 0, byte, prev, prior;
410
414
  if ((of - length) < 0) {
411
415
  // Prior(x) == 0 && Raw(x - bpp) == 0
412
416
  for (; i < bpp; i++) {
@@ -460,7 +464,7 @@ PNGReader.prototype.unFilterAverage = function (scanline, scanline_offset, pixel
460
464
  * end
461
465
  */
462
466
  PNGReader.prototype.unFilterPaeth = function (scanline, scanline_offset, pixels, bpp, of, length) {
463
- var i = 0, raw, a, b, c, p, pa, pb, pc, pr;
467
+ let i = 0, raw, a, b, c, p, pa, pb, pc, pr;
464
468
  if ((of - length) < 0) {
465
469
  // Prior(x) == 0 && Raw(x - bpp) == 0
466
470
  for (; i < bpp; i++) {
@@ -525,7 +529,7 @@ PNGReader.prototype.parse = function () {
525
529
  this.decodeHeader();
526
530
 
527
531
  while (this.buffer.position < this.buffer.length) {
528
- var type = this.decodeChunk();
532
+ const type = this.decodeChunk();
529
533
  // stop after IHDR chunk, or after IEND
530
534
  // if (type === 'IHDR' && options.data === false || type === 'IEND') break;
531
535
  if (type === 'IEND') {
@@ -533,7 +537,7 @@ PNGReader.prototype.parse = function () {
533
537
  }
534
538
  }
535
539
 
536
- var png = this.png;
540
+ const png = this.png;
537
541
 
538
542
  this.decodePixels();
539
543
 
@@ -0,0 +1,21 @@
1
+ import { assert } from "../../core/assert.js";
2
+ import { halton_sequence } from "../../core/math/statistics/halton_sequence.js";
3
+
4
+ /**
5
+ * Generates a number of 2d jitter offsets in range -1...1
6
+ * @param {number} point_count
7
+ * @return {Float32Array}
8
+ */
9
+ export function generate_halton_jitter(point_count) {
10
+ assert.isNonNegativeInteger(point_count, 'point_count');
11
+
12
+ const result = new Float32Array(point_count * 2);
13
+
14
+ for (let i = 0; i < point_count; i++) {
15
+ const i2 = i * 2;
16
+ result[i2] = halton_sequence(2, i) * 2 - 1;
17
+ result[i2 + 1] = halton_sequence(3, i) * 2 - 1;
18
+ }
19
+
20
+ return result;
21
+ }
@@ -1,23 +1,6 @@
1
- import { EnginePlugin } from "../../../../../plugin/EnginePlugin.js";
2
- import { halton_sequence } from "../../../../../../core/math/statistics/halton_sequence.js";
3
1
  import { array_copy } from "../../../../../../core/collection/array/array_copy.js";
4
-
5
- /**
6
- * Generates a number of 2d jitter offsets in range -1...1
7
- * @param {number} point_count
8
- * @return {Float32Array}
9
- */
10
- function generate_jitter(point_count) {
11
- const result = new Float32Array(point_count * 2);
12
-
13
- for (let i = 0; i < point_count; i++) {
14
- const i2 = i * 2;
15
- result[i2] = halton_sequence(2, i) * 2 - 1;
16
- result[i2 + 1] = halton_sequence(3, i) * 2 - 1;
17
- }
18
-
19
- return result;
20
- }
2
+ import { EnginePlugin } from "../../../../../plugin/EnginePlugin.js";
3
+ import { generate_halton_jitter } from "../../../../generate_halton_jitter.js";
21
4
 
22
5
  export class TemporalSupersamplingRenderPlugin extends EnginePlugin {
23
6
  constructor() {
@@ -31,7 +14,7 @@ export class TemporalSupersamplingRenderPlugin extends EnginePlugin {
31
14
  this.__frame_index = 0;
32
15
 
33
16
  this.__sample_count = 16;
34
- this.__jitter_offset = generate_jitter(this.__sample_count);
17
+ this.__jitter_offset = generate_halton_jitter(this.__sample_count);
35
18
 
36
19
 
37
20
  this.__projection_modifier = {
@@ -3,7 +3,9 @@ import { BitSet } from "../../../../../core/binary/BitSet.js";
3
3
  import { Cache } from "../../../../../core/cache/Cache.js";
4
4
  import { array_copy } from "../../../../../core/collection/array/array_copy.js";
5
5
  import { passThrough, strictEquals } from "../../../../../core/function/Functions.js";
6
- import { compose_finger_print } from "./compose_finger_print.js";
6
+ import { max2 } from "../../../../../core/math/max2.js";
7
+ import { min2 } from "../../../../../core/math/min2.js";
8
+ import { tile_address_to_finger_print } from "./tile/tile_address_to_finger_print.js";
7
9
  import { TileLoader } from "./TileLoader.js";
8
10
 
9
11
  /**
@@ -17,18 +19,48 @@ export class SparseTexture {
17
19
 
18
20
  #page_texture_size = [1, 1];
19
21
 
22
+ #version = 0;
23
+
24
+ /**
25
+ * Maximum number of new page assignments per single update cycle
26
+ * This helps prevent thrashing and keep performance overhead of updating pages predictable
27
+ * @type {number}
28
+ */
29
+ #cycle_assignment_limit = 2;
30
+
31
+
32
+ get page_texture_resolution() {
33
+ return this.#page_texture_size;
34
+ }
35
+
20
36
  /**
21
37
  *
22
38
  * @type {TextureTile[]}
23
39
  */
24
40
  #resident_tiles = [];
25
41
 
42
+ get resident_tiles() {
43
+ return this.#resident_tiles;
44
+ }
45
+
26
46
  #page_slot_occupancy = new BitSet();
27
47
 
28
48
  #tile_resolution = 128;
29
49
 
50
+ get tile_resolution() {
51
+ return this.#tile_resolution;
52
+ }
53
+
30
54
  #residency_tile_capacity = 0;
31
55
 
56
+ /**
57
+ * Must be >=0
58
+ * When 0 - tiles that would not fit onto the page will not be loaded
59
+ * When >0 - same fraction of extra tiles will be fetching and put into cache, resulting in prefetching
60
+ * @type {number}
61
+ */
62
+ #prefetch_factor = 0.33;
63
+
32
64
  #loader = new TileLoader();
33
65
 
34
66
  /**
@@ -37,6 +69,34 @@ export class SparseTexture {
37
69
  */
38
70
  #asset_manager = null;
39
71
 
72
+ constructor() {
73
+ this.#loader.on.loaded.add(this.#handle_tile_loaded, this);
74
+ }
75
+
76
+ /**
77
+ *
78
+ * @param {TextureTile} tile
79
+ */
80
+ #handle_tile_loaded(tile) {
81
+ const finderPrint = tile.finder_print;
82
+
83
+ if (this.#tile_cache.has(finderPrint)) {
84
+ // already loaded
85
+ return;
86
+ }
87
+
88
+ this.#tile_cache.put(finderPrint, tile);
89
+
90
+ }
91
+
92
+ /**
93
+ *
94
+ * @param {string} v
95
+ */
96
+ set path(v) {
97
+ this.#loader.path = v;
98
+ }
99
+
40
100
  set asset_manager(v) {
41
101
  this.#asset_manager = v
42
102
 
@@ -51,6 +111,8 @@ export class SparseTexture {
51
111
  const y = size[1] / tile_resolution;
52
112
 
53
113
  this.#residency_tile_capacity = x * y;
114
+
115
+ this.#loader.queue_limit = max2(16, this.#residency_tile_capacity);
54
116
  }
55
117
 
56
118
 
@@ -81,19 +143,19 @@ export class SparseTexture {
81
143
  const tiles = this.#resident_tiles;
82
144
  const count = tiles.length;
83
145
 
84
- let max = -1;
85
- let max_index = -1;
146
+ let min = Infinity;
147
+ let min_index = -1;
86
148
 
87
149
  for (let i = 0; i < count; i++) {
88
150
  const tile = tiles[i];
89
151
 
90
- if (tile.last_used_time > max) {
91
- max = tile.last_used_time;
92
- max_index = i;
152
+ if (tile.last_used_time < min) {
153
+ min = tile.last_used_time;
154
+ min_index = i;
93
155
  }
94
156
  }
95
157
 
96
- return max_index;
158
+ return min_index;
97
159
  }
98
160
 
99
161
  #evict_one() {
@@ -119,12 +181,40 @@ export class SparseTexture {
119
181
  // mark slot as empty for next tile
120
182
  this.#page_slot_occupancy.clear(pageSlot);
121
183
 
184
+ removed.page_slot = -1;
185
+
122
186
  // push into cache
123
187
  this.#tile_cache.put(removed.finder_print, removed);
124
188
 
125
189
  return true;
126
190
  }
127
191
 
192
+ /**
193
+ *
194
+ * @param {TextureTile} tile
195
+ */
196
+ #make_resident(tile) {
197
+
198
+ if (this.#residency_tile_capacity === 0) {
199
+ throw new Error('Capacity is 0');
200
+ }
201
+
202
+ let slot = this.#page_slot_occupancy.nextClearBit(0);
203
+
204
+ while (slot >= this.#residency_tile_capacity) {
205
+ this.#evict_one();
206
+
207
+ slot = this.#page_slot_occupancy.nextClearBit(0);
208
+ }
209
+
210
+ tile.page_slot = slot;
211
+
212
+ this.#page_slot_occupancy.set(slot, true);
213
+ this.#resident_tiles.push(tile);
214
+
215
+ return true;
216
+ }
217
+
128
218
  /**
129
219
  *
130
220
  * @param {number[]} v
@@ -137,7 +227,7 @@ export class SparseTexture {
137
227
  );
138
228
 
139
229
  this.#page_texture.dispose();
140
- this.#page_texture.setSize(this.#page_texture[0], this.#page_texture[1]);
230
+ this.#page_texture.setSize(this.#page_texture_size[0], this.#page_texture_size[1]);
141
231
 
142
232
  this.#update_residency_capacity();
143
233
  }
@@ -147,31 +237,50 @@ export class SparseTexture {
147
237
  * @param {UsageMetadata} usage
148
238
  */
149
239
  update_usage(usage) {
150
- const max_mip = usage.max_mip_level;
240
+ const usage_occupancy = usage.occupancy;
241
+ const usage_occupancy_count = usage.occupancy_count;
151
242
 
152
- for (let mip = 0; mip < max_mip; mip++) {
153
243
 
154
- const resolution = 1 << mip;
244
+ // Usage occupancy is already sorted by priority, so there's no point is trying to fetch more than t
245
+ let fetch_limit = this.#residency_tile_capacity * (1 + this.#prefetch_factor);
155
246
 
156
- for (let y = 0; y < resolution; y++) {
157
- for (let x = 0; x < resolution; x++) {
247
+ let writes_remaining = min2(
248
+ max2(1, this.#cycle_assignment_limit),
249
+ this.#residency_tile_capacity
250
+ );
158
251
 
159
- const count = usage.getCountBy(mip, x, y);
252
+ let remaining_slots = this.#residency_tile_capacity;
160
253
 
161
- if (count === 0) {
162
- continue;
163
- }
254
+ for (let i = 0; i < usage_occupancy_count && fetch_limit > 0; i++) {
255
+ const tile_address = usage_occupancy[i];
164
256
 
165
- const fingerprint = compose_finger_print(mip, x, y);
257
+ const fingerPrint = tile_address_to_finger_print(tile_address);
166
258
 
167
- this.#loader.enqueue(fingerprint);
259
+ const tile = this.#tile_cache.get(fingerPrint);
260
+ if (tile === null) {
261
+ this.#loader.enqueue(fingerPrint);
262
+ } else if (remaining_slots > 0) {
263
+ // found in cache, and we have some slots remaining to write into
168
264
 
265
+
266
+ // touch to prevent eviction
267
+ tile.last_used_time = this.#version;
268
+
269
+ if (tile.page_slot === -1 && writes_remaining > 0) {
270
+ // tile is not active, and we can write it
271
+ writes_remaining--;
272
+ this.#make_resident(tile);
169
273
  }
170
- }
171
274
 
275
+ fetch_limit--;
276
+ remaining_slots--;
277
+ }
172
278
  }
173
279
 
280
+
174
281
  this.#loader.update_usage(usage);
282
+
283
+ this.#version++;
175
284
  }
176
285
 
177
286
  dispose() {
@@ -3,9 +3,9 @@ import { arrayQuickSort } from "../../../../../core/collection/array/arrayQuickS
3
3
  import Signal from "../../../../../core/events/signal/Signal.js";
4
4
  import { AssetManager } from "../../../../asset/AssetManager.js";
5
5
  import { GameAssetType } from "../../../../asset/GameAssetType.js";
6
- import { compose_finger_print } from "./compose_finger_print.js";
7
- import { decompose_finger_print } from "./finger_print_to_tile_index.js";
8
- import { TextureTile } from "./TextureTile.js";
6
+ import { compose_finger_print } from "./tile/compose_finger_print.js";
7
+ import { decompose_finger_print } from "./tile/decompose_finger_print.js";
8
+ import { TextureTile } from "./tile/TextureTile.js";
9
9
 
10
10
  export class TileLoader {
11
11
  /**
@@ -14,13 +14,23 @@ export class TileLoader {
14
14
  */
15
15
  #root_path = "";
16
16
 
17
+ set path(v) {
18
+ this.#root_path = v;
19
+ }
20
+
21
+ /**
22
+ * When queue gets larger than this, we start discarding elements
23
+ * @type {number}
24
+ */
25
+ queue_limit = Infinity;
26
+
17
27
  /**
18
28
  *
19
29
  * @type {AssetManager}
20
30
  */
21
31
  #asset_manager = null;
22
32
 
23
- set asset_manager(v){
33
+ set asset_manager(v) {
24
34
  this.#asset_manager = v;
25
35
  }
26
36
 
@@ -28,7 +38,7 @@ export class TileLoader {
28
38
  * How many tiles can be loaded at the same time
29
39
  * @type {number}
30
40
  */
31
- #concurrency = 4;
41
+ #concurrency = 2;
32
42
 
33
43
  /**
34
44
  * Tiles that are currently being loaded
@@ -95,7 +105,6 @@ export class TileLoader {
95
105
  return false;
96
106
  }
97
107
 
98
- // TODO limit queue size, if the queue grows too large we may end up with completely irrelevant tiles being loaded minutes after they were actually relevant
99
108
  if (this.#queue_add(fingerprint) === false) {
100
109
  return false;
101
110
  }
@@ -142,6 +151,12 @@ export class TileLoader {
142
151
  */
143
152
  update_usage(usage) {
144
153
  this.#sort_queue_by_usage(usage);
154
+
155
+ // truncate the queue if necessary
156
+ const queue_overflow = this.#queue.length - this.queue_limit;
157
+ if (queue_overflow > 0) {
158
+ this.#queue.splice(this.queue_limit, queue_overflow);
159
+ }
145
160
  }
146
161
 
147
162
  /**
@@ -165,7 +180,7 @@ export class TileLoader {
165
180
  */
166
181
  #prod() {
167
182
  while (this.#in_flight.length < this.#concurrency && this.#queue.length > 0) {
168
- const fingerprint = this.#queue.pop();
183
+ const fingerprint = this.#queue[0];
169
184
  const { mip, x, y } = decompose_finger_print(fingerprint);
170
185
 
171
186
  this.#load(mip, x, y);
@@ -201,6 +216,8 @@ export class TileLoader {
201
216
 
202
217
 
203
218
  return tile;
219
+ }, (reason) => {
220
+ console.error(`Failed to load tile ${file_name}, reason:${reason}`);
204
221
  })
205
222
  .finally(() => {
206
223