diff --git a/packages/fiber/src/core/hooks.tsx b/packages/fiber/src/core/hooks.tsx index f5df469fe2..aab78856a1 100644 --- a/packages/fiber/src/core/hooks.tsx +++ b/packages/fiber/src/core/hooks.tsx @@ -91,15 +91,15 @@ export type LoaderResult = T extends { scene: THREE.Object3D } ? T & ObjectMa export type Extensions = (loader: Loader) => void const memoizedLoaders = new WeakMap, Loader>() +const memoizedResults = new WeakMap, Map>>() const isConstructor = (value: unknown): value is LoaderProto => typeof value === 'function' && value?.prototype?.constructor === value function loadingFn(extensions?: Extensions, onProgress?: (event: ProgressEvent) => void) { - return async function (Proto: Loader | LoaderProto, ...input: string[]) { - let loader: Loader - + return async function (Proto: Loader | LoaderProto, ...urls: string[]) { // Construct and cache loader if constructor was passed + let loader!: Loader if (isConstructor(Proto)) { loader = memoizedLoaders.get(Proto)! if (!loader) { @@ -110,22 +110,37 @@ function loadingFn(extensions?: Extensions, onProgress?: (event: ProgressE loader = Proto } + // Prime per-loader cache + let results: Map> = memoizedResults.get(loader)! + if (!results) { + results = new Map() + memoizedResults.set(loader, results) + } + // Apply loader extensions if (extensions) extensions(loader) // Go through the urls and load them return Promise.all( - input.map( - (input) => - new Promise>((res, reject) => - loader.load( - input, - (data) => res(isObject3D(data?.scene) ? Object.assign(data, buildGraph(data.scene)) : data), - onProgress, - (error) => reject(new Error(`Could not load ${input}: ${(error as ErrorEvent)?.message}`)), - ), - ), - ), + urls.map((url) => { + // Load url and cache the result on first try + if (!results.has(url)) { + try { + results.set( + url, + loader + .loadAsync(url, onProgress) + .then((data) => (isObject3D(data?.scene) ? Object.assign(data, buildGraph(data.scene)) : data)), + ) + } catch (error) { + // TODO: merge errors for multiple urls + throw new Error(`Could not load ${url}: ${(error as ErrorEvent)?.message}`) + } + } + + // Return cached pending or completed result + return results.get(url) + }), ) } }