@needle-tools/gltf-progressive 3.6.0 → 3.6.2

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.
package/lib/extension.js CHANGED
@@ -796,6 +796,8 @@ export class NEEDLE_progressive {
796
796
  }
797
797
  }
798
798
  else {
799
+ // Requests already waiting for a queue slot must not populate a new cache lifetime.
800
+ this.cacheGeneration++;
799
801
  this.lodInfos.clear();
800
802
  for (const [, entryRef] of this.lowresCache) {
801
803
  const entry = entryRef.deref();
@@ -840,6 +842,7 @@ export class NEEDLE_progressive {
840
842
  else {
841
843
  // Promise — may be in-flight or already resolved.
842
844
  // Attach disposal to run after resolution.
845
+ this.disposedRequests.add(entry);
843
846
  entry.then(resource => {
844
847
  if (resource) {
845
848
  if (Array.isArray(resource)) {
@@ -938,8 +941,11 @@ export class NEEDLE_progressive {
938
941
  }
939
942
  }
940
943
  static workers = [];
944
+ static cacheGeneration = 0;
945
+ static disposedRequests = new WeakSet();
941
946
  static _workersIndex = 0;
942
947
  static async getOrLoadLOD(current, level, options) {
948
+ const generation = this.cacheGeneration;
943
949
  const debugverbose = debug == "verbose";
944
950
  /** this key is used to lookup the LOD information */
945
951
  const LOD = this.getAssignedLODInformation(current);
@@ -1010,6 +1016,8 @@ export class NEEDLE_progressive {
1010
1016
  const KEY = lod_url + "_" + lodInfo.guid;
1011
1017
  // check if the requested file is currently being loaded or was previously loaded
1012
1018
  const cached = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
1019
+ if (generation !== this.cacheGeneration)
1020
+ return null;
1013
1021
  if (cached.found)
1014
1022
  return cached.value;
1015
1023
  if (options?.isCurrent?.() === false) {
@@ -1018,6 +1026,8 @@ export class NEEDLE_progressive {
1018
1026
  return null;
1019
1027
  }
1020
1028
  const slot = await this.queue.slot(lod_url);
1029
+ if (generation !== this.cacheGeneration)
1030
+ return null;
1021
1031
  if (options?.isCurrent?.() === false) {
1022
1032
  if (debugverbose)
1023
1033
  console.log(`Skipping stale LOD ${level} request after queue: ${lod_url}`);
@@ -1026,8 +1036,20 @@ export class NEEDLE_progressive {
1026
1036
  // Another request can fill the cache while this one waits for a queue slot.
1027
1037
  // Re-checking here avoids duplicate loads for heavily instanced assets.
1028
1038
  const cachedAfterQueue = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
1039
+ if (generation !== this.cacheGeneration)
1040
+ return null;
1029
1041
  if (cachedAfterQueue.found)
1030
1042
  return cachedAfterQueue.value;
1043
+ // Even a cache miss above yields a microtask. Another slot holder can
1044
+ // start this same request before that continuation resumes.
1045
+ const startedMeanwhile = this.cache.get(KEY);
1046
+ if (startedMeanwhile !== undefined) {
1047
+ const cached = await this.tryResolveLODCacheEntry(startedMeanwhile, KEY, lod_url, current, level, debugverbose);
1048
+ if (generation !== this.cacheGeneration)
1049
+ return null;
1050
+ if (cached.found)
1051
+ return cached.value;
1052
+ }
1031
1053
  // #region loading
1032
1054
  if (!slot.use) {
1033
1055
  if (debug)
@@ -1175,6 +1197,10 @@ export class NEEDLE_progressive {
1175
1197
  this.cache.set(KEY, request);
1176
1198
  slot.use(request);
1177
1199
  const res = await request;
1200
+ // dispose() owns cleanup of removed pending entries. Do not return their
1201
+ // disposed resources to callers or overwrite a newer request for this key.
1202
+ if (generation !== this.cacheGeneration || this.cache.get(KEY) !== request)
1203
+ return null;
1178
1204
  // Optimize cache entry: replace loading promise with lightweight reference.
1179
1205
  // This releases closure variables captured during the loading function.
1180
1206
  if (res != null) {
@@ -1212,7 +1238,7 @@ export class NEEDLE_progressive {
1212
1238
  console.log("Load texture from uri: " + lod_url);
1213
1239
  const loader = new TextureLoader();
1214
1240
  const tex = await loader.loadAsync(lod_url);
1215
- if (options?.isCurrent?.() === false) {
1241
+ if (generation !== this.cacheGeneration || options?.isCurrent?.() === false) {
1216
1242
  tex?.dispose();
1217
1243
  return null;
1218
1244
  }
@@ -1269,6 +1295,8 @@ export class NEEDLE_progressive {
1269
1295
  console.error(`Error loading LOD ${level} from ${lodUrl}\n`, err);
1270
1296
  return null;
1271
1297
  });
1298
+ if (this.disposedRequests.has(existing))
1299
+ return { found: true, value: null };
1272
1300
  let resourceIsDisposed = false;
1273
1301
  if (res == null) {
1274
1302
  // Failed loads stay cached as null so we don't retry the same missing resource forever.
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { version as VERSION } from "./version.js";
2
2
  export * from "./extension.js";
3
3
  export * from "./plugins/index.js";
4
4
  export { LODsManager, calculateMeshLODLevel, getLODColor, lodDebugColors, type LOD_Results, type MeshLODSelectionOptions, type MeshLODSelectionResult } from "./lods.manager.js";
5
- export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
5
+ export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, disposeLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
6
6
  export { getRaycastMesh, registerRaycastMesh, useRaycastMeshes } from "./utils.js";
7
7
  import { WebGLRenderer } from "three";
8
8
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ export { version as VERSION } from "./version.js";
2
2
  export * from "./extension.js";
3
3
  export * from "./plugins/index.js";
4
4
  export { LODsManager, calculateMeshLODLevel, getLODColor, lodDebugColors } from "./lods.manager.js";
5
- export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
5
+ export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, disposeLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
6
6
  export { getRaycastMesh, registerRaycastMesh, useRaycastMeshes } from "./utils.js";
7
7
  import { addDracoAndKTX2Loaders, configureLoader, createLoaders } from "./loaders.js";
8
8
  import { NEEDLE_progressive } from "./extension.js";
package/lib/loaders.d.ts CHANGED
@@ -37,6 +37,8 @@ export declare function createLoaders(renderer: WebGLRenderer | null): {
37
37
  decodeGltfBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, mode: string, filter?: string) => void;
38
38
  };
39
39
  };
40
+ /** Release the shared Draco and KTX2 workers when no engine context uses them. */
41
+ export declare function disposeLoaders(): void;
40
42
  export declare function addDracoAndKTX2Loaders(loader: GLTFLoader): void;
41
43
  /**
42
44
  * Smart loading hints can be used by needle infrastructure to deliver assets optimized for a specific usecase.
package/lib/loaders.js CHANGED
@@ -40,7 +40,8 @@ fetch(_remoteDracoDecoderUrl, {
40
40
  }
41
41
  })
42
42
  .finally(() => {
43
- prepareLoaders();
43
+ if (preloadLoaders)
44
+ prepareLoaders();
44
45
  });
45
46
  // #region Loader Configuration
46
47
  /**
@@ -89,15 +90,25 @@ export function setKTX2TranscoderLocation(location) {
89
90
  * @returns The loaders/decoders.
90
91
  */
91
92
  export function createLoaders(renderer) {
92
- prepareLoaders();
93
+ preloadLoaders = true;
94
+ const loaders = prepareLoaders();
93
95
  if (renderer) {
94
- ktx2Loader.detectSupport(renderer);
96
+ loaders.ktx2Loader.detectSupport(renderer);
95
97
  }
96
98
  else if (renderer !== null)
97
99
  console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail");
98
- return { dracoLoader, ktx2Loader, meshoptDecoder };
100
+ return loaders;
101
+ }
102
+ /** Release the shared Draco and KTX2 workers when no engine context uses them. */
103
+ export function disposeLoaders() {
104
+ preloadLoaders = false;
105
+ dracoLoader?.dispose();
106
+ ktx2Loader?.dispose();
107
+ dracoLoader = undefined;
108
+ ktx2Loader = undefined;
99
109
  }
100
110
  export function addDracoAndKTX2Loaders(loader) {
111
+ const { dracoLoader, ktx2Loader, meshoptDecoder } = prepareLoaders();
101
112
  if (!loader.dracoLoader)
102
113
  loader.setDRACOLoader(dracoLoader);
103
114
  if (!loader.ktx2Loader)
@@ -107,6 +118,7 @@ export function addDracoAndKTX2Loaders(loader) {
107
118
  }
108
119
  // #region internal
109
120
  const $dracoDecoderPath = Symbol("dracoDecoderPath");
121
+ let preloadLoaders = true;
110
122
  let dracoLoader;
111
123
  let meshoptDecoder;
112
124
  let ktx2Loader;
@@ -127,6 +139,7 @@ function prepareLoaders() {
127
139
  if (!meshoptDecoder) {
128
140
  meshoptDecoder = MeshoptDecoder;
129
141
  }
142
+ return { dracoLoader, ktx2Loader, meshoptDecoder };
130
143
  }
131
144
  const gltfLoaderConfigurations = new WeakMap();
132
145
  export function configureLoader(loader, opts) {
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // replaced at build time
2
- export const version = "3.6.0";
2
+ export const version = "3.6.2";
3
3
  globalThis["GLTF_PROGRESSIVE_VERSION"] = version;
4
4
  console.debug(`[gltf-progressive] version ${version || "-"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-progressive",
3
- "version": "3.6.0",
3
+ "version": "3.6.2",
4
4
  "description": "three.js support for loading glTF or GLB files that contain progressive loading data",
5
5
  "homepage": "https://needle.tools",
6
6
  "author": {