From 15f1bed58a884dea2f4b59d6eeae81f0e17d823a Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Mon, 16 Jun 2025 17:34:43 +0200 Subject: [PATCH 1/8] Loader: Add `abort()`. --- src/loaders/FileLoader.js | 23 +++++++++++++- src/loaders/ImageBitmapLoader.js | 22 ++++++++++++++ src/loaders/Loader.js | 22 ++++++++++++++ src/loaders/LoadingManager.js | 52 +++++++++++++++++++++++++++++++- 4 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/loaders/FileLoader.js b/src/loaders/FileLoader.js index 97061a61ea000d..b73007619b10d8 100644 --- a/src/loaders/FileLoader.js +++ b/src/loaders/FileLoader.js @@ -55,6 +55,14 @@ class FileLoader extends Loader { */ this.responseType = ''; + /** + * Used for aborting requests. + * + * @private + * @type {AbortController} + */ + this._abortController = new AbortController(); + } /** @@ -121,7 +129,7 @@ class FileLoader extends Loader { const req = new Request( url, { headers: new Headers( this.requestHeader ), credentials: this.withCredentials ? 'include' : 'same-origin', - // An abort controller could be added within a future PR + signal: this._abortController.signal } ); // record states ( avoid data race ) @@ -338,6 +346,19 @@ class FileLoader extends Loader { } + /** + * Aborts ongoing fetch requests. + * + * @return {FileLoader} A reference to this instance. + */ + abort() { + + this._abortController.abort(); + + return this; + + } + } diff --git a/src/loaders/ImageBitmapLoader.js b/src/loaders/ImageBitmapLoader.js index a8cca17f8bdbf1..b801947620a31c 100644 --- a/src/loaders/ImageBitmapLoader.js +++ b/src/loaders/ImageBitmapLoader.js @@ -66,6 +66,14 @@ class ImageBitmapLoader extends Loader { */ this.options = { premultiplyAlpha: 'none' }; + /** + * Used for aborting requests. + * + * @private + * @type {AbortController} + */ + this._abortController = new AbortController(); + } /** @@ -154,6 +162,7 @@ class ImageBitmapLoader extends Loader { const fetchOptions = {}; fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; fetchOptions.headers = this.requestHeader; + fetchOptions.signal = this._abortController.signal; const promise = fetch( url, fetchOptions ).then( function ( res ) { @@ -191,6 +200,19 @@ class ImageBitmapLoader extends Loader { } + /** + * Aborts ongoing fetch requests. + * + * @return {ImageBitmapLoader} A reference to this instance. + */ + abort() { + + this._abortController.abort(); + + return this; + + } + } export { ImageBitmapLoader }; diff --git a/src/loaders/Loader.js b/src/loaders/Loader.js index ecd3bdcf2351fb..84f9ead2f67124 100644 --- a/src/loaders/Loader.js +++ b/src/loaders/Loader.js @@ -61,12 +61,21 @@ class Loader { */ this.requestHeader = {}; + // + + if ( this.manager.enableAbortManagement === true ) { + + this.manager.addEventListener( 'abort', () => this.abort() ); + + } + } /** * This method needs to be implemented by all concrete loaders. It holds the * logic for loading assets from the backend. * + * @abstract * @param {string} url - The path/URL of the file to be loaded. * @param {Function} onLoad - Executed when the loading process has been finished. * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. @@ -97,6 +106,7 @@ class Loader { * This method needs to be implemented by all concrete loaders. It holds the * logic for parsing the asset into three.js entities. * + * @abstract * @param {any} data - The data to parse. */ parse( /* data */ ) {} @@ -171,6 +181,18 @@ class Loader { } + /** + * This method can be implemented in loaders for aborting ongoing requests. + * + * @abstract + * @return {Loader} A reference to this instance. + */ + abort() { + + return this; + + } + } /** diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index bae742a457c878..f49b6488889418 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -1,3 +1,13 @@ +import { EventDispatcher } from '../core/EventDispatcher.js'; + +/** + * Fires when {@link LoadingManager#abort} is called. + * + * @event LoadingManager#abort + * @type {Object} + */ +const _abort = { type: 'abort' }; + /** * Handles and keeps track of loaded and pending data. A default global * instance of this class is created and used by loaders if not supplied @@ -15,7 +25,7 @@ * const loader2 = new ColladaLoader( manager ); * ``` */ -class LoadingManager { +class LoadingManager extends EventDispatcher { /** * Constructs a new loading manager. @@ -26,6 +36,8 @@ class LoadingManager { */ constructor( onLoad, onProgress, onError ) { + super(); + const scope = this; let isLoading = false; @@ -69,6 +81,14 @@ class LoadingManager { */ this.onError = onError; + /** + * Whether loading requests can be aborted with {@link LoadingManager#abort} or not. + * + * @type {boolean} + * @default false + */ + this.enableAbortManagement = false; + /** * This should be called by any loader using the manager when the loader * starts loading an item. @@ -269,6 +289,36 @@ class LoadingManager { }; + /** + * Can be used to abort ongoing loading requests in loaders using this manager. + * The abort only works if {@link LoadingManager#enableAbortManagement} is set + * to `true` and the loaders implement {@link Loader#abort}. + * + * @fires LoadingManager#abort + * @return {LoadingManager} A reference to this loading manager. + */ + this.abort = function () { + + this.dispatchEvent( _abort ); + + return this; + + }; + + /** + * Frees internal resources. Call this method whenever this instance is no + * longer used in your app. + * + * @return {LoadingManager} A reference to this loading manager. + */ + this.dispose = function () { + + this._listeners = {}; // remove abort handler + + return this; + + }; + } } From 995564c9e6ea3b379cda25cfd540f66715d099b6 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Tue, 17 Jun 2025 10:33:36 +0200 Subject: [PATCH 2/8] EventDispatcher: Add `removeEventListeners()`. --- src/core/EventDispatcher.js | 15 +++++++++++++++ src/loaders/LoadingManager.js | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/core/EventDispatcher.js b/src/core/EventDispatcher.js index 0ef63080706364..b445aa0707d707 100644 --- a/src/core/EventDispatcher.js +++ b/src/core/EventDispatcher.js @@ -93,6 +93,21 @@ class EventDispatcher { } + /** + * Removes all event listeners for the given event type. + * + * @param {string} type - The type of event. + */ + removeEventListeners( type ) { + + const listeners = this._listeners; + + if ( listeners === undefined ) return; + + delete this._listeners[ type ]; + + } + /** * Dispatches an event object. * diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index f49b6488889418..14abdd131ecfee 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -313,7 +313,9 @@ class LoadingManager extends EventDispatcher { */ this.dispose = function () { - this._listeners = {}; // remove abort handler + handlers.length = 0; + + this.removeEventListeners( 'abort' ); return this; From 22b330afdb5abd96537675438a9e4afe0b86a6f3 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Tue, 17 Jun 2025 12:32:04 +0200 Subject: [PATCH 3/8] LoadingManager: Remove `dispose()`. --- src/core/EventDispatcher.js | 15 --------------- src/loaders/LoadingManager.js | 16 ---------------- 2 files changed, 31 deletions(-) diff --git a/src/core/EventDispatcher.js b/src/core/EventDispatcher.js index b445aa0707d707..0ef63080706364 100644 --- a/src/core/EventDispatcher.js +++ b/src/core/EventDispatcher.js @@ -93,21 +93,6 @@ class EventDispatcher { } - /** - * Removes all event listeners for the given event type. - * - * @param {string} type - The type of event. - */ - removeEventListeners( type ) { - - const listeners = this._listeners; - - if ( listeners === undefined ) return; - - delete this._listeners[ type ]; - - } - /** * Dispatches an event object. * diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index 14abdd131ecfee..6224676e182f10 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -305,22 +305,6 @@ class LoadingManager extends EventDispatcher { }; - /** - * Frees internal resources. Call this method whenever this instance is no - * longer used in your app. - * - * @return {LoadingManager} A reference to this loading manager. - */ - this.dispose = function () { - - handlers.length = 0; - - this.removeEventListeners( 'abort' ); - - return this; - - }; - } } From a802e6442b068dca96ec92d28b9443914b670763 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Thu, 19 Jun 2025 12:02:55 +0200 Subject: [PATCH 4/8] Loaders: Recreate `AbortController`. --- src/loaders/FileLoader.js | 1 + src/loaders/ImageBitmapLoader.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/loaders/FileLoader.js b/src/loaders/FileLoader.js index b73007619b10d8..9f186c55c6d369 100644 --- a/src/loaders/FileLoader.js +++ b/src/loaders/FileLoader.js @@ -354,6 +354,7 @@ class FileLoader extends Loader { abort() { this._abortController.abort(); + this._abortController = new AbortController(); return this; diff --git a/src/loaders/ImageBitmapLoader.js b/src/loaders/ImageBitmapLoader.js index b801947620a31c..78aa613b1f05d3 100644 --- a/src/loaders/ImageBitmapLoader.js +++ b/src/loaders/ImageBitmapLoader.js @@ -208,6 +208,7 @@ class ImageBitmapLoader extends Loader { abort() { this._abortController.abort(); + this._abortController = new AbortController(); return this; From 1e53f0523098289b11f8d142ca0816085e335824 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Fri, 20 Jun 2025 11:23:59 +0200 Subject: [PATCH 5/8] LoadingManager: New abort approach. --- src/loaders/FileLoader.js | 2 +- src/loaders/ImageBitmapLoader.js | 2 +- src/loaders/Loader.js | 8 -------- src/loaders/LoadingManager.js | 27 +++++++-------------------- 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/loaders/FileLoader.js b/src/loaders/FileLoader.js index 9f186c55c6d369..ecd8e8a565fbb5 100644 --- a/src/loaders/FileLoader.js +++ b/src/loaders/FileLoader.js @@ -129,7 +129,7 @@ class FileLoader extends Loader { const req = new Request( url, { headers: new Headers( this.requestHeader ), credentials: this.withCredentials ? 'include' : 'same-origin', - signal: this._abortController.signal + signal: AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) } ); // record states ( avoid data race ) diff --git a/src/loaders/ImageBitmapLoader.js b/src/loaders/ImageBitmapLoader.js index 78aa613b1f05d3..8eb1637ae7d4b4 100644 --- a/src/loaders/ImageBitmapLoader.js +++ b/src/loaders/ImageBitmapLoader.js @@ -162,7 +162,7 @@ class ImageBitmapLoader extends Loader { const fetchOptions = {}; fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; fetchOptions.headers = this.requestHeader; - fetchOptions.signal = this._abortController.signal; + fetchOptions.signal = AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ); const promise = fetch( url, fetchOptions ).then( function ( res ) { diff --git a/src/loaders/Loader.js b/src/loaders/Loader.js index 84f9ead2f67124..c18ae7c83a83c3 100644 --- a/src/loaders/Loader.js +++ b/src/loaders/Loader.js @@ -61,14 +61,6 @@ class Loader { */ this.requestHeader = {}; - // - - if ( this.manager.enableAbortManagement === true ) { - - this.manager.addEventListener( 'abort', () => this.abort() ); - - } - } /** diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index 6224676e182f10..016c29f0fc5d8d 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -1,13 +1,3 @@ -import { EventDispatcher } from '../core/EventDispatcher.js'; - -/** - * Fires when {@link LoadingManager#abort} is called. - * - * @event LoadingManager#abort - * @type {Object} - */ -const _abort = { type: 'abort' }; - /** * Handles and keeps track of loaded and pending data. A default global * instance of this class is created and used by loaders if not supplied @@ -25,7 +15,7 @@ const _abort = { type: 'abort' }; * const loader2 = new ColladaLoader( manager ); * ``` */ -class LoadingManager extends EventDispatcher { +class LoadingManager { /** * Constructs a new loading manager. @@ -36,8 +26,6 @@ class LoadingManager extends EventDispatcher { */ constructor( onLoad, onProgress, onError ) { - super(); - const scope = this; let isLoading = false; @@ -82,12 +70,11 @@ class LoadingManager extends EventDispatcher { this.onError = onError; /** - * Whether loading requests can be aborted with {@link LoadingManager#abort} or not. + * Used for aborting ongoing requests in loaders using this manager. * - * @type {boolean} - * @default false + * @type {AbortController} */ - this.enableAbortManagement = false; + this.abortController = new AbortController(); /** * This should be called by any loader using the manager when the loader @@ -291,15 +278,15 @@ class LoadingManager extends EventDispatcher { /** * Can be used to abort ongoing loading requests in loaders using this manager. - * The abort only works if {@link LoadingManager#enableAbortManagement} is set - * to `true` and the loaders implement {@link Loader#abort}. + * The abort only works if the loaders implement {@link Loader#abort}. * * @fires LoadingManager#abort * @return {LoadingManager} A reference to this loading manager. */ this.abort = function () { - this.dispatchEvent( _abort ); + this.abortController.abort(); + this.abortController = new AbortController(); return this; From 32c2aa8b886c591880d6791162c232e5d80a5139 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Fri, 20 Jun 2025 11:24:47 +0200 Subject: [PATCH 6/8] Clean up. --- src/loaders/LoadingManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index 016c29f0fc5d8d..8b55b86ecf9a59 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -280,7 +280,6 @@ class LoadingManager { * Can be used to abort ongoing loading requests in loaders using this manager. * The abort only works if the loaders implement {@link Loader#abort}. * - * @fires LoadingManager#abort * @return {LoadingManager} A reference to this loading manager. */ this.abort = function () { From a0cbbfac20831909c79932d0c494696613a3d5d2 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Mon, 23 Jun 2025 10:31:40 +0200 Subject: [PATCH 7/8] Loaders: Add `AbortSignal.any()` fallback. --- src/loaders/FileLoader.js | 2 +- src/loaders/ImageBitmapLoader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/loaders/FileLoader.js b/src/loaders/FileLoader.js index ecd8e8a565fbb5..886ae3d335e9e9 100644 --- a/src/loaders/FileLoader.js +++ b/src/loaders/FileLoader.js @@ -129,7 +129,7 @@ class FileLoader extends Loader { const req = new Request( url, { headers: new Headers( this.requestHeader ), credentials: this.withCredentials ? 'include' : 'same-origin', - signal: AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) + signal: ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal } ); // record states ( avoid data race ) diff --git a/src/loaders/ImageBitmapLoader.js b/src/loaders/ImageBitmapLoader.js index 8eb1637ae7d4b4..8151df27d4a29d 100644 --- a/src/loaders/ImageBitmapLoader.js +++ b/src/loaders/ImageBitmapLoader.js @@ -162,7 +162,7 @@ class ImageBitmapLoader extends Loader { const fetchOptions = {}; fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; fetchOptions.headers = this.requestHeader; - fetchOptions.signal = AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ); + fetchOptions.signal = ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal; const promise = fetch( url, fetchOptions ).then( function ( res ) { From f7ddaaaea324d4ad7e718d136e25120bd8525352 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Mon, 23 Jun 2025 10:33:32 +0200 Subject: [PATCH 8/8] LoadingManager: Add note about `AbortSignal.any()`. --- src/loaders/LoadingManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index 8b55b86ecf9a59..69339f44ff2ed0 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -278,7 +278,8 @@ class LoadingManager { /** * Can be used to abort ongoing loading requests in loaders using this manager. - * The abort only works if the loaders implement {@link Loader#abort}. + * The abort only works if the loaders implement {@link Loader#abort} and `AbortSignal.any()` + * is supported in the browser. * * @return {LoadingManager} A reference to this loading manager. */