Browse Source

OBJLoader2 V2.2.0
- WorkerRunnerRefImpl changes: No longer required `THREE.LoaderSupport.Validator` and `THREE.LoaderSupport.ConsoleLogger` which reduces size of worker. Default workers only include WorkerRunnerRefImpl for establishing communication and no longer need the Validator or the ConsoleLogger.
- LoaderWorkerSupport changes: It always ensures logConfig parameters are properly initialized before being passed to worker. Fixed logging problems.
- Issue #25: OBJLoader2 now logs an error if `THREE.LoaderSupport` is not included as script in HTML. The same is true for missing `THRE.MTLLoader`, but only if method `loadMtl` is used.
- Issue #26: `WorkerSuport` now contains a inner private class `LoaderWorker` that encapsulates the native worker. This separates the runtime functionality from the setup and interaction. Workers are now terminated when immediately when they are not running otherwise `LoaderWorker.terminateRequested` ensures termination when final execution status is reached. `WorkerDirector` now properly handles shutdown of workers. Evaluation of status is always performed in `WorkerDirector.processQueue`. `WorkerDirector.callbackOnFinishedProcessing` is called when processing is completed and all workers are terminated. This allows to clear all meshes, for example.

Kai Salmen 7 years ago
parent
commit
6dee6d14de

+ 12 - 11
docs/examples/loaders/LoaderSupport.html

@@ -156,10 +156,9 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null validate] ( [page:Function functionCodeBuilder], [page:Boolean forceWorkerReload], Array of [page:String libLocations], [page:String libPath], [page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] )</h3>
+		<h3>[method:null validate] ( [page:Function functionCodeBuilder], Array of [page:String libLocations], [page:String libPath], [page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] )</h3>
 		<div>
 			[page:Function functionCodeBuilder] - Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.<br>
-			[page:Boolean forceWorkerReload] - Force re-build of the worker code.<br>
 			Array of [page:String libLocations] - URL of libraries that shall be added to worker code relative to libPath.<br>
 			[page:String libPath] - Base path used for loading libraries.<br>
 			[page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] - The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
@@ -178,12 +177,6 @@
 		</div>
 
 
-		<h3>[method:null terminateWorker] ()</h3>
-		<div>
-			Terminate the worker and the code.
-		</div>
-
-
 		<h3>[method:null setCallbacks] ( [page:Function builder], [page:Function onLoad] )</h3>
 		<div>
 			[page:Function builder] - The builder function. Default is [page:LoaderSupport.Builder].<br>
@@ -251,7 +244,7 @@
 			- prepareWorkers<br>
 			- enqueueForRun<br>
 			- processQueue<br>
-			- deregister
+			- tearDown
 		</div>
 
 
@@ -283,7 +276,10 @@
 		</div>
 
 
-		<h3>[method:null deregister]()</h3>
+		<h3>[method:null tearDown]( [page:Function callbackOnFinishedProcessing] )</h3>
+		<div>
+			[page:Function callbackOnFinishedProcessing] - Function called once all workers finished processing.
+		</div>
 		<div>
 			Terminate all workers.
 		</div>
@@ -300,6 +296,11 @@
 			Returns the maximum number of workers.
 		</div>
 
+		<h3>[method:Boolean isRunning]()</h3>
+		<div>
+			Returns if any workers are running.
+		</div>
+
 
 		<h3>[method:null setCrossOrigin]( [page:String crossOrigin] )</h3>
 		<div>
@@ -543,7 +544,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null isValid]( [page:Object input] )</h3>
+		<h3>[method:Boolean isValid]( [page:Object input] )</h3>
 		<div>
 			[page:Object input] - Can be anything
 		</div>

+ 289 - 228
examples/js/loaders/LoaderSupport.js

@@ -941,13 +941,8 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
 	 */
 	WorkerRunnerRefImpl.prototype.processMessage = function ( payload ) {
-		var logger = new ConsoleLogger();
-		if ( Validator.isValid( payload.logger ) ) {
-
-			logger.setEnabled( payload.logger.enabled );
-			logger.setDebug( payload.logger.debug );
-
-		}
+		var logEnabled = payload.logger.enabled;
+		var logDebug = payload.logger.enabled;
 		if ( payload.cmd === 'run' ) {
 
 			var callbacks = {
@@ -955,19 +950,20 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 					self.postMessage( payload );
 				},
 				callbackProgress: function ( text ) {
-					logger.logDebug( 'WorkerRunner: progress: ' + text );
+					if ( logEnabled && logDebug ) console.debug( 'WorkerRunner: progress: ' + text );
 				}
 			};
 
 			// Parser is expected to be named as such
-			var parser = new Parser( logger );
+			var parser = new Parser();
+			if ( typeof parser[ 'setLogConfig' ] === 'function' ) parser.setLogConfig( logEnabled, logDebug );
 			this.applyProperties( parser, payload.params );
 			this.applyProperties( parser, payload.materials );
 			this.applyProperties( parser, callbacks );
 			parser.workerScope = self;
 			parser.parse( payload.data.input, payload.data.options );
 
-			logger.logInfo( 'WorkerRunner: Run complete!' );
+			if ( logEnabled ) console.log( 'WorkerRunner: Run complete!' );
 
 			callbacks.callbackBuilder( {
 				cmd: 'complete',
@@ -976,7 +972,7 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 
 		} else {
 
-			logger.logError( 'WorkerRunner: Received unknown command: ' + payload.cmd );
+			console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
 
 		}
 	};
@@ -993,170 +989,226 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
  */
 THREE.LoaderSupport.WorkerSupport = (function () {
 
-	var WORKER_SUPPORT_VERSION = '1.1.1';
+	var WORKER_SUPPORT_VERSION = '2.0.0';
 
 	var Validator = THREE.LoaderSupport.Validator;
 
-	function WorkerSupport( logger ) {
-		this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
-		this.logger.logInfo( 'Using THREE.LoaderSupport.WorkerSupport version: ' + WORKER_SUPPORT_VERSION );
+	var LoaderWorker = (function () {
 
-		// check worker support first
-		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
-		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
-		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
+		function LoaderWorker( logger ) {
+			this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
+			this._reset();
+		}
+
+		LoaderWorker.prototype._reset = function () {
+			this.worker = null;
+			this.runnerImplName = null;
+			this.callbacks = {
+				builder: null,
+				onLoad: null
+			};
+			this.terminateRequested = false;
+			this.queuedMessage = null;
+			this.started = false;
+		};
+
+		LoaderWorker.prototype.initWorker = function ( code, runnerImplName ) {
+			this.runnerImplName = runnerImplName;
+			var blob = new Blob( [ code ], { type: 'application/javascript' } );
+			this.worker = new Worker( window.URL.createObjectURL( blob ) );
+			this.worker.onmessage = this._receiveWorkerMessage;
 
-		this.worker = null;
-		this.workerCode = null;
-		this.loading = true;
-		this.queuedMessage = null;
-		this.running = false;
-		this.terminateRequested = false;
+			// set referemce to this, then processing in worker scope within "_receiveWorkerMessage" can access members
+			this.worker.runtimeRef = this;
 
-		this.callbacks = {
-			builder: null,
-			onLoad: null
+			// process stored queuedMessage
+			this._postMessage();
 		};
-	}
 
-	/**
-	 * Validate the status of worker code and the derived worker.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {Function} functionCodeBuilder Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.
-	 * @param {boolean} forceWorkerReload Force re-build of the worker code.
-	 * @param {String[]} libLocations URL of libraries that shall be added to worker code relative to libPath
-	 * @param {String} libPath Base path used for loading libraries
-	 * @param {THREE.LoaderSupport.WorkerRunnerRefImpl} runnerImpl The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
-	 */
-	WorkerSupport.prototype.validate = function ( functionCodeBuilder, forceWorkerReload, libLocations, libPath, runnerImpl ) {
-		this.running = false;
-		if ( forceWorkerReload ) {
+		/**
+		 * Executed in worker scope
+ 		 */
+		LoaderWorker.prototype._receiveWorkerMessage = function ( e ) {
+			var payload = e.data;
+			switch ( payload.cmd ) {
+				case 'meshData':
+				case 'materialData':
+				case 'imageData':
+					this.runtimeRef.callbacks.builder( payload );
+					break;
 
-			this.worker = null;
-			this.workerCode = null;
-			this.loading = true;
-			this.queuedMessage = null;
-			this.callbacks.builder = null;
-			this.callbacks.onLoad = null;
+				case 'complete':
+					this.runtimeRef.queuedMessage = null;
+					this.started = false;
+					this.runtimeRef.callbacks.onLoad( payload.msg );
 
-		}
+					if ( this.runtimeRef.terminateRequested ) {
+
+						this.runtimeRef.logger.logInfo( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Run is complete. Terminating application on request!' );
+						this.runtimeRef._terminate();
+
+					}
+					break;
+
+				case 'error':
+					this.runtimeRef.logger.logError( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Reported error: ' + payload.msg );
+					this.runtimeRef.queuedMessage = null;
+					this.started = false;
+					this.runtimeRef.callbacks.onLoad( payload.msg );
 
-		if ( ! Validator.isValid( this.worker ) ) {
+					if ( this.runtimeRef.terminateRequested ) {
 
-			this.logger.logInfo( 'WorkerSupport: Building worker code...' );
-			this.logger.logTimeStart( 'buildWebWorkerCode' );
+						this.runtimeRef.logger.logInfo( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Run reported error. Terminating application on request!' );
+						this.runtimeRef._terminate();
 
-			var workerRunner;
-			if ( Validator.isValid( runnerImpl ) ) {
+					}
+					break;
 
-				this.logger.logInfo( 'WorkerSupport: Using "' + runnerImpl.name + '" as Runncer class for worker.' );
-				workerRunner = runnerImpl;
+				default:
+					this.runtimeRef.logger.logError( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Received unknown command: ' + payload.cmd );
+					break;
+
+			}
+		};
+
+		LoaderWorker.prototype.setCallbacks = function ( builder, onLoad ) {
+			this.callbacks.builder = Validator.verifyInput( builder, this.callbacks.builder );
+			this.callbacks.onLoad = Validator.verifyInput( onLoad, this.callbacks.onLoad );
+		};
+
+		LoaderWorker.prototype.run = function( payload ) {
+			if ( Validator.isValid( this.queuedMessage ) ) {
+
+				console.warn( 'Already processing message. Rejecting new run instruction' );
+				return;
 
 			} else {
 
-				this.logger.logInfo( 'WorkerSupport: Using DEFAULT "THREE.LoaderSupport.WorkerRunnerRefImpl" as Runncer class for worker.' );
-				workerRunner = THREE.LoaderSupport.WorkerRunnerRefImpl;
+				this.queuedMessage = payload;
+				this.started = true;
 
 			}
+			if ( ! Validator.isValid( this.callbacks.builder ) ) throw 'Unable to run as no "builder" callback is set.';
+			if ( ! Validator.isValid( this.callbacks.onLoad ) ) throw 'Unable to run as no "onLoad" callback is set.';
+			if ( payload.cmd !== 'run' ) payload.cmd = 'run';
+			if ( Validator.isValid( payload.logger ) ) {
 
-			var scope = this;
-			var buildWorkerCode = function ( baseWorkerCode ) {
-				scope.workerCode = baseWorkerCode;
-				if ( workerRunner == THREE.LoaderSupport.WorkerRunnerRefImpl ) {
+				payload.logger.enabled = Validator.verifyInput( payload.logger.enabled, true );
+				payload.logger.debug = Validator.verifyInput( payload.logger.debug, false );
 
-					scope.workerCode += buildObject( 'Validator', THREE.LoaderSupport.Validator );
-					scope.workerCode += buildSingelton( 'ConsoleLogger', 'ConsoleLogger', THREE.LoaderSupport.ConsoleLogger );
+			} else {
 
+				payload.logger = {
+					enabled: true,
+					debug: false
 				}
-				scope.workerCode += functionCodeBuilder( buildObject, buildSingelton );
-				scope.workerCode += buildSingelton( workerRunner.name, workerRunner.name, workerRunner );
-				scope.workerCode += 'new ' + workerRunner.name + '();\n\n';
 
-				var blob = new Blob( [ scope.workerCode ], { type: 'application/javascript' } );
-				scope.worker = new Worker( window.URL.createObjectURL( blob ) );
-				scope.logger.logTimeEnd( 'buildWebWorkerCode' );
+			}
+			this._postMessage();
+		};
 
-				var receiveWorkerMessage = function ( e ) {
-					var payload = e.data;
+		LoaderWorker.prototype._postMessage = function () {
+			if ( Validator.isValid( this.queuedMessage ) && Validator.isValid( this.worker ) ) {
 
-					switch ( payload.cmd ) {
-						case 'meshData':
-						case 'materialData':
-						case 'imageData':
-							scope.callbacks.builder( payload );
-							break;
+				this.worker.postMessage( this.queuedMessage );
 
-						case 'complete':
-							scope.callbacks.onLoad( payload.msg );
-							scope.running = false;
+			}
+		};
 
-							if ( scope.terminateRequested ) {
+		LoaderWorker.prototype.setTerminateRequested = function ( terminateRequested ) {
+			this.terminateRequested = terminateRequested === true;
+			if ( this.terminateRequested && Validator.isValid( this.worker ) && ! Validator.isValid( this.queuedMessage ) && this.started ) {
 
-								scope.logger.logInfo( 'WorkerSupport [' + workerRunner + ']: Run is complete. Terminating application on request!' );
-								scope.terminateWorker();
+				this.logger.logInfo( 'Worker is terminated immediately as it is not running!' );
+				this._terminate();
 
-							}
-							break;
+			}
+		};
 
-						case 'error':
-							scope.logger.logError( 'WorkerSupport [' + workerRunner + ']: Reported error: ' + payload.msg );
-							break;
+		LoaderWorker.prototype._terminate = function () {
+			this.worker.terminate();
+			this._reset();
+		};
 
-						default:
-							scope.logger.logError( 'WorkerSupport [' + workerRunner + ']: Received unknown command: ' + payload.cmd );
-							break;
+		return LoaderWorker;
 
-					}
-				};
-				scope.worker.addEventListener( 'message', receiveWorkerMessage, false );
-				scope.loading = false;
-				scope._postMessage();
-			};
+	})();
 
-			if ( Validator.isValid( libLocations ) && libLocations.length > 0 ) {
+	function WorkerSupport( logger ) {
+		this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
+		this.logger.logInfo( 'Using THREE.LoaderSupport.WorkerSupport version: ' + WORKER_SUPPORT_VERSION );
 
-				var libsContent = '';
-				var loadAllLibraries = function ( path, locations ) {
-					if ( locations.length === 0 ) {
+		// check worker support first
+		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
+		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
+		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
 
-						buildWorkerCode( libsContent );
+		this.loaderWorker = new LoaderWorker( this.logger );
+	}
 
-					} else {
+	/**
+	 * Validate the status of worker code and the derived worker.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {Function} functionCodeBuilder Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.
+	 * @param {String[]} libLocations URL of libraries that shall be added to worker code relative to libPath
+	 * @param {String} libPath Base path used for loading libraries
+	 * @param {THREE.LoaderSupport.WorkerRunnerRefImpl} runnerImpl The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
+	 */
+	WorkerSupport.prototype.validate = function ( functionCodeBuilder, libLocations, libPath, runnerImpl ) {
+		if ( Validator.isValid( this.loaderWorker.worker ) ) return;
 
-						var loadedLib = function ( contentAsString ) {
-							libsContent += contentAsString;
-							loadAllLibraries( path, locations );
-						};
+		this.logger.logInfo( 'WorkerSupport: Building worker code...' );
+		this.logger.logTimeStart( 'buildWebWorkerCode' );
 
-						var fileLoader = new THREE.FileLoader();
-						fileLoader.setPath( path );
-						fileLoader.setResponseType( 'text' );
-						fileLoader.load( locations[ 0 ], loadedLib );
-						locations.shift();
+		if ( Validator.isValid( runnerImpl ) ) {
 
-					}
-				};
-				loadAllLibraries( libPath, libLocations );
+			this.logger.logInfo( 'WorkerSupport: Using "' + runnerImpl.name + '" as Runncer class for worker.' );
 
-			} else {
+		} else {
 
-				buildWorkerCode( '' );
+			runnerImpl = THREE.LoaderSupport.WorkerRunnerRefImpl;
+			this.logger.logInfo( 'WorkerSupport: Using DEFAULT "THREE.LoaderSupport.WorkerRunnerRefImpl" as Runncer class for worker.' );
 
-			}
 		}
-	};
 
-	/**
-	 * Terminate the worker and the code.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 */
-	WorkerSupport.prototype.terminateWorker = function () {
-		if ( Validator.isValid( this.worker ) ) {
-			this.worker.terminate();
+		var userWorkerCode = functionCodeBuilder( buildObject, buildSingelton );
+		userWorkerCode += buildSingelton( runnerImpl.name, runnerImpl.name, runnerImpl );
+		userWorkerCode += 'new ' + runnerImpl.name + '();\n\n';
+
+		var scope = this;
+		if ( Validator.isValid( libLocations ) && libLocations.length > 0 ) {
+
+			var libsContent = '';
+			var loadAllLibraries = function ( path, locations ) {
+				if ( locations.length === 0 ) {
+
+					scope.loaderWorker.initWorker( libsContent + userWorkerCode, scope.logger, runnerImpl.name );
+					scope.logger.logTimeEnd( 'buildWebWorkerCode' );
+
+				} else {
+
+					var loadedLib = function ( contentAsString ) {
+						libsContent += contentAsString;
+						loadAllLibraries( path, locations );
+					};
+
+					var fileLoader = new THREE.FileLoader();
+					fileLoader.setPath( path );
+					fileLoader.setResponseType( 'text' );
+					fileLoader.load( locations[ 0 ], loadedLib );
+					locations.shift();
+
+				}
+			};
+			loadAllLibraries( libPath, libLocations );
+
+		} else {
+
+			this.loaderWorker.initWorker( userWorkerCode, this.logger, runnerImpl.name );
+			this.logger.logTimeEnd( 'buildWebWorkerCode' );
+
 		}
-		this.worker = null;
-		this.workerCode = null;
 	};
 
 	/**
@@ -1167,10 +1219,27 @@ THREE.LoaderSupport.WorkerSupport = (function () {
 	 * @param {Function} onLoad The function that is called when parsing is complete.
 	 */
 	WorkerSupport.prototype.setCallbacks = function ( builder, onLoad ) {
-		this.callbacks = {
-			builder: builder,
-			onLoad: onLoad
-		};
+		this.loaderWorker.setCallbacks( builder, onLoad );
+	};
+
+	/**
+	 * Runs the parser with the provided configuration.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
+	 */
+	WorkerSupport.prototype.run = function ( payload ) {
+		this.loaderWorker.run( payload );
+	};
+
+	/**
+	 * Request termination of worker once parser is finished.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {boolean} terminateRequested True or false.
+	 */
+	WorkerSupport.prototype.setTerminateRequested = function ( terminateRequested ) {
+		this.loaderWorker.setTerminateRequested( terminateRequested );
 	};
 
 	var buildObject = function ( fullName, object ) {
@@ -1229,41 +1298,8 @@ THREE.LoaderSupport.WorkerSupport = (function () {
 		return objectString;
 	};
 
-	/**
-	 * Request termination of worker once parser is finished.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {boolean} terminateRequested True or false.
-	 */
-	WorkerSupport.prototype.setTerminateRequested = function ( terminateRequested ) {
-		this.terminateRequested = terminateRequested === true;
-	};
-
-	/**
-	 * Runs the parser with the provided configuration.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
-	 */
-	WorkerSupport.prototype.run = function ( payload ) {
-		if ( ! Validator.isValid( this.callbacks.builder ) ) throw 'Unable to run as no "builder" callback is set.';
-		if ( ! Validator.isValid( this.callbacks.onLoad ) ) throw 'Unable to run as no "onLoad" callback is set.';
-		if ( Validator.isValid( this.worker ) || this.loading ) {
-			if ( payload.cmd !== 'run' ) payload.cmd = 'run';
-			this.queuedMessage = payload;
-			this.running = true;
-			this._postMessage();
-
-		}
-	};
-
-	WorkerSupport.prototype._postMessage = function () {
-		if ( ! this.loading && Validator.isValid( this.queuedMessage ) ) {
-			this.worker.postMessage( this.queuedMessage );
-		}
-	};
-
 	return WorkerSupport;
+
 })();
 
 /**
@@ -1272,7 +1308,7 @@ THREE.LoaderSupport.WorkerSupport = (function () {
  *   prepareWorkers
  *   enqueueForRun
  *   processQueue
- *   deregister
+ *   tearDown (to force stop)
  *
  * @class
  *
@@ -1281,7 +1317,7 @@ THREE.LoaderSupport.WorkerSupport = (function () {
  */
 THREE.LoaderSupport.WorkerDirector = (function () {
 
-	var LOADER_WORKER_DIRECTOR_VERSION = '2.0.0';
+	var LOADER_WORKER_DIRECTOR_VERSION = '2.1.0';
 
 	var Validator = THREE.LoaderSupport.Validator;
 
@@ -1301,10 +1337,13 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		this.workerDescription = {
 			classDef: classDef,
 			globalCallbacks: {},
-			workerSupports: []
+			workerSupports: {}
 		};
 		this.objectsCompleted = 0;
 		this.instructionQueue = [];
+		this.instructionQueuePointer = 0;
+
+		this.callbackOnFinishedProcessing = null;
 	}
 
 	/**
@@ -1349,30 +1388,21 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		if ( Validator.isValid( globalCallbacks ) ) this.workerDescription.globalCallbacks = globalCallbacks;
 		this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE );
 		this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER );
+		this.maxWebWorkers = Math.min( this.maxWebWorkers, this.maxQueueSize );
 		this.objectsCompleted = 0;
 		this.instructionQueue = [];
+		this.instructionQueuePointer = 0;
 
-		var start = this.workerDescription.workerSupports.length;
-		var i;
-		if ( start < this.maxWebWorkers ) {
-
-			for ( i = start; i < this.maxWebWorkers; i++ ) {
-
-				this.workerDescription.workerSupports[ i ] = {
-					workerSupport: new THREE.LoaderSupport.WorkerSupport( this.logger ),
-					loader: null
-				};
-
-			}
-
-		} else {
+		for ( var instanceNo = 0; instanceNo < this.maxWebWorkers; instanceNo++ ) {
 
-			for ( i = start - 1; i >= this.maxWebWorkers; i-- ) {
+			this.workerDescription.workerSupports[ instanceNo ] = {
+				instanceNo: instanceNo,
+				inUse: false,
+				terminateRequested: false,
+				workerSupport: new THREE.LoaderSupport.WorkerSupport( this.logger ),
+				loader: null
+			};
 
-				this.workerDescription.workerSupports[ i ].workerSupport.setRequestTerminate( true );
-				this.workerDescription.workerSupports.pop();
-
-			}
 		}
 	};
 
@@ -1388,47 +1418,68 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		}
 	};
 
+	/**
+	 * Returns if any workers are running.
+	 *
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 * @returns {boolean}
+	 */
+	WorkerDirector.prototype.isRunning = function () {
+		var wsKeys = Object.keys( this.workerDescription.workerSupports );
+		return ( ( this.instructionQueue.length > 0 && this.instructionQueuePointer < this.instructionQueue.length ) || wsKeys.length > 0 );
+	};
+
 	/**
 	 * Process the instructionQueue until it is depleted.
 	 * @memberOf THREE.LoaderSupport.WorkerDirector
 	 */
 	WorkerDirector.prototype.processQueue = function () {
-		if ( this.instructionQueue.length === 0 ) return;
+		var prepData, supportDesc;
+		for ( var instanceNo in this.workerDescription.workerSupports ) {
 
-		var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
-		for ( var i = 0; i < length; i++ ) {
+			supportDesc = this.workerDescription.workerSupports[ instanceNo ];
+			if ( ! supportDesc.inUse ) {
 
-			this._kickWorkerRun( this.instructionQueue[ 0 ], i );
-			this.instructionQueue.shift();
+				if ( this.instructionQueuePointer < this.instructionQueue.length ) {
 
-		}
-	};
+					prepData = this.instructionQueue[ this.instructionQueuePointer ];
+					this._kickWorkerRun( prepData, supportDesc );
+					this.instructionQueuePointer++;
 
-	WorkerDirector.prototype._kickWorkerRun = function( prepData, workerInstanceNo ) {
-		var scope = this;
-		var directorOnLoad = function ( event ) {
-			scope.objectsCompleted++;
+				} else {
 
-			var nextPrepData = scope.instructionQueue[ 0 ];
-			if ( Validator.isValid( nextPrepData ) ) {
+					this._deregister( supportDesc );
 
-				scope.instructionQueue.shift();
-				scope.logger.logInfo( '\nAssigning next item from queue to worker (queue length: ' + scope.instructionQueue.length + ')\n\n' );
-				scope._kickWorkerRun( nextPrepData, event.detail.instanceNo );
+				}
 
-			} else if ( scope.instructionQueue.length === 0 ) {
+			}
 
-				scope.deregister();
+		}
 
-			}
-		};
+		if ( ! this.isRunning() && this.callbackOnFinishedProcessing !== null ) {
+
+			this.callbackOnFinishedProcessing();
+			this.callbackOnFinishedProcessing = null;
 
+		}
+	};
+
+	WorkerDirector.prototype._kickWorkerRun = function( prepData, supportDesc ) {
+		supportDesc.inUse = true;
+		supportDesc.workerSupport.setTerminateRequested( supportDesc.terminateRequested );
+
+		this.logger.logInfo( '\nAssigning next item from queue to worker (queue length: ' + this.instructionQueue.length + ')\n\n' );
+
+		var scope = this;
 		var prepDataCallbacks = prepData.getCallbacks();
 		var globalCallbacks = this.workerDescription.globalCallbacks;
 		var wrapperOnLoad = function ( event ) {
 			if ( Validator.isValid( globalCallbacks.onLoad ) ) globalCallbacks.onLoad( event );
 			if ( Validator.isValid( prepDataCallbacks.onLoad ) ) prepDataCallbacks.onLoad( event );
-			directorOnLoad( event );
+			scope.objectsCompleted++;
+			supportDesc.inUse = false;
+
+			scope.processQueue();
 		};
 
 		var wrapperOnProgress = function ( event ) {
@@ -1441,8 +1492,7 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 			if ( Validator.isValid( prepDataCallbacks.onMeshAlter ) ) prepDataCallbacks.onMeshAlter( event );
 		};
 
-		var supportTuple = this.workerDescription.workerSupports[ workerInstanceNo ];
-		supportTuple.loader = this._buildLoader( workerInstanceNo );
+		supportDesc.loader = this._buildLoader( supportDesc.instanceNo );
 
 		var updatedCallbacks = new THREE.LoaderSupport.Callbacks();
 		updatedCallbacks.setCallbackOnLoad( wrapperOnLoad );
@@ -1450,13 +1500,13 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		updatedCallbacks.setCallbackOnMeshAlter( wrapperOnMeshAlter );
 		prepData.callbacks = updatedCallbacks;
 
-		supportTuple.loader.run( prepData, supportTuple.workerSupport );
+		supportDesc.loader.run( prepData, supportDesc.workerSupport );
 	};
 
 	WorkerDirector.prototype._buildLoader = function ( instanceNo ) {
 		var classDef = this.workerDescription.classDef;
 		var loader = Object.create( classDef.prototype );
-		this.workerDescription.classDef.call( loader, null, this.logger );
+		this.workerDescription.classDef.call( loader, THREE.DefaultLoadingManager, this.logger );
 
 		// verify that all required functions are implemented
 		if ( ! loader.hasOwnProperty( 'instanceNo' ) ) throw classDef.name + ' has no property "instanceNo".';
@@ -1466,36 +1516,47 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 
 			throw classDef.name + ' has no property "workerSupport".';
 
-		} else if ( ! classDef.workerSupport instanceof THREE.LoaderSupport.WorkerSupport ) {
-
-			throw classDef.name + '.workerSupport is not of type "THREE.LoaderSupport.WorkerSupport".';
-
 		}
 		if ( typeof loader.run !== 'function'  ) throw classDef.name + ' has no function "run".';
+		if ( ! loader.hasOwnProperty( 'callbacks' ) || ! Validator.isValid( loader.callbacks ) ) {
+
+			this.logger.logWarn( classDef.name + ' has an invalid property "callbacks". Will change to "THREE.LoaderSupport.Callbacks"' );
+			loader.callbacks = new THREE.LoaderSupport.Callbacks();
 
+		}
 		return loader;
 	};
 
+	WorkerDirector.prototype._deregister = function ( supportDesc ) {
+		if ( Validator.isValid( supportDesc ) ) {
+
+			supportDesc.workerSupport.setTerminateRequested( true );
+			this.logger.logInfo( 'Requested termination of worker #' + supportDesc.instanceNo + '.' );
+
+			var loaderCallbacks = supportDesc.loader.callbacks;
+			if ( Validator.isValid( loaderCallbacks.onProgress ) ) loaderCallbacks.onProgress( { detail: { text: '' } } );
+			delete this.workerDescription.workerSupports[ supportDesc.instanceNo ];
+
+		}
+	};
+
 	/**
 	 * Terminate all workers.
 	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @param {callback} callbackOnFinishedProcessing Function called once all workers finished processing.
 	 */
-	WorkerDirector.prototype.deregister = function () {
+	WorkerDirector.prototype.tearDown = function ( callbackOnFinishedProcessing ) {
 		this.logger.logInfo( 'WorkerDirector received the deregister call. Terminating all workers!' );
 
-		for ( var i = 0, length = this.workerDescription.workerSupports.length; i < length; i++ ) {
+		this.instructionQueuePointer = this.instructionQueue.length;
+		this.callbackOnFinishedProcessing = Validator.verifyInput( callbackOnFinishedProcessing, null );
 
-			var supportTuple = this.workerDescription.workerSupports[ i ];
-			supportTuple.workerSupport.setTerminateRequested( true );
-			this.logger.logInfo( 'Requested termination of worker.' );
+		for ( var name in this.workerDescription.workerSupports ) {
 
-			var loaderCallbacks = supportTuple.loader.callbacks;
-			if ( Validator.isValid( loaderCallbacks.onProgress ) ) loaderCallbacks.onProgress( { detail: { text: '' } } );
+			this.workerDescription.workerSupports[ name ].terminateRequested = true;
 
 		}
-
-		this.workerDescription.workerSupports = [];
-		this.instructionQueue = [];
 	};
 
 	return WorkerDirector;

+ 19 - 9
examples/js/loaders/OBJLoader2.js

@@ -7,6 +7,8 @@
 
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
 
+if ( THREE.LoaderSupport === undefined ) console.error( '"THREE.LoaderSupport" is not available. "THREE.OBJLoader2" requires it. Please include "LoaderSupport.js" in your HTML.' );
+
 /**
  * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
  * @class
@@ -16,9 +18,10 @@ if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
  */
 THREE.OBJLoader2 = (function () {
 
-	var OBJLOADER2_VERSION = '2.1.2';
+	var OBJLOADER2_VERSION = '2.2.0';
 	var LoaderBase = THREE.LoaderSupport.LoaderBase;
 	var Validator = THREE.LoaderSupport.Validator;
+	var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
 
 	OBJLoader2.prototype = Object.create( THREE.LoaderSupport.LoaderBase.prototype );
 	OBJLoader2.prototype.constructor = OBJLoader2;
@@ -126,10 +129,6 @@ THREE.OBJLoader2 = (function () {
 			this.workerSupport = workerSupportExternal;
 			this.logger = workerSupportExternal.logger;
 
-		} else {
-
-			this.terminateWorkerOnLoad = true;
-
 		}
 		var scope = this;
 		var onMaterialsLoaded = function ( materials ) {
@@ -176,7 +175,8 @@ THREE.OBJLoader2 = (function () {
 	OBJLoader2.prototype.parse = function ( content ) {
 		this.logger.logTimeStart( 'OBJLoader2 parse: ' + this.modelName );
 
-		var parser = new Parser( this.logger );
+		var parser = new Parser();
+		parser.setLogConfig( this.logger.enabled, this.logger.debug );
 		parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
 		parser.setUseIndices( this.useIndices );
 		parser.setDisregardNormals( this.disregardNormals );
@@ -239,7 +239,6 @@ THREE.OBJLoader2 = (function () {
 					}
 				}
 			);
-			if ( scope.terminateWorkerOnLoad ) scope.workerSupport.terminateWorker();
 			scope.logger.logTimeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
 		};
 		var scopedOnMeshLoaded = function ( payload ) {
@@ -257,6 +256,8 @@ THREE.OBJLoader2 = (function () {
 			workerCode += '/**\n';
 			workerCode += '  * This code was constructed by OBJLoader2 buildCode.\n';
 			workerCode += '  */\n\n';
+			workerCode += funcBuildObject( 'Validator', Validator );
+			workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
 			workerCode += funcBuildSingelton( 'LoaderBase', 'LoaderBase', LoaderBase );
 			workerCode += funcBuildObject( 'Consts', Consts );
 			workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
@@ -267,6 +268,7 @@ THREE.OBJLoader2 = (function () {
 		};
 		this.workerSupport.validate( buildCode, false );
 		this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
+		if ( scope.terminateWorkerOnLoad ) this.workerSupport.setTerminateRequested( true );
 
 		var materialNames = {};
 		var materials = this.builder.getMaterials();
@@ -330,7 +332,9 @@ THREE.OBJLoader2 = (function () {
 	 */
 	var Parser = (function () {
 
-		function Parser( logger ) {
+		var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
+
+		function Parser() {
 			this.callbackProgress = null;
 			this.callbackBuilder = null;
 
@@ -348,7 +352,7 @@ THREE.OBJLoader2 = (function () {
 				faces: 0,
 				doubleIndicesCount: 0
 			};
-			this.logger = logger;
+			this.logger = new ConsoleLogger();
 			this.totalBytes = 0;
 			this.reachedFaces = false;
 		};
@@ -383,6 +387,11 @@ THREE.OBJLoader2 = (function () {
 			this.callbackProgress = callbackProgress;
 		};
 
+		Parser.prototype.setLogConfig = function ( enabled, debug ) {
+			this.logger.setEnabled( enabled );
+			this.logger.setDebug( debug );
+		};
+
 		Parser.prototype.configure = function () {
 			this.rawMesh = new RawMesh( this.materialPerSmoothingGroup, this.useIndices, this.disregardNormals );
 
@@ -1359,6 +1368,7 @@ THREE.OBJLoader2 = (function () {
 	 * @param {string} [crossOrigin] CORS value
 	 */
 	OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
+		if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' );
 		if ( Validator.isValid( resource ) ) this.logger.logTimeStart( 'Loading MTL: ' + resource.name );
 
 		var materials = [];

+ 19 - 10
examples/webgl_loader_obj2_meshspray.html

@@ -142,12 +142,14 @@
 						workerCode += '/**\n';
 						workerCode += '  * This code was constructed by MeshSpray buildCode.\n';
 						workerCode += '  */\n\n';
+						workerCode += funcBuildObject( 'Validator', Validator );
+						workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
 						workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
 
 						return workerCode;
 					};
 					var libs2Load = [ 'build/three.min.js' ];
-					this.workerSupport.validate( buildCode, false, libs2Load, '../' );
+					this.workerSupport.validate( buildCode, libs2Load, '../' );
 					this.workerSupport.setCallbacks( scopeBuilderFunc, scopeFuncComplete );
 					this.workerSupport.run(
 						{
@@ -173,7 +175,9 @@
 
 				var Parser  = ( function () {
 
-					function Parser( logger ) {
+					var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
+
+					function Parser() {
 						this.sizeFactor = 0.5;
 						this.localOffsetFactor = 1.0;
 						this.globalObjectCount = 0;
@@ -181,10 +185,16 @@
 						this.dimension = 200;
 						this.quantity = 1;
 						this.callbackBuilder = null;
-						this.logger = logger;
+						this.callbackProgress = null;
+						this.logger = new ConsoleLogger();
 						this.serializedMaterials = null;
 					};
 
+					Parser.prototype.setLogConfig = function ( enabled, debug ) {
+						this.logger.setEnabled( enabled );
+						this.logger.setDebug( debug );
+					};
+
 					Parser.prototype.parse = function () {
 						var baseTriangle = [ 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 1.0 ];
 						var vertices = [];
@@ -389,12 +399,11 @@
 					var maxWebWorkers = 4;
 					var radius = 640;
 					var logger = new THREE.LoaderSupport.ConsoleLogger( false );
-					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray, logger );
-					this.workerDirector.setCrossOrigin( 'anonymous' );
+					var workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray, logger );
+					workerDirector.setCrossOrigin( 'anonymous' );
 
-					var scope = this;
 					var callbackOnLoad = function ( event ) {
-						logger.logInfo( 'Worker #' + event.detail.instanceNo + ': Completed loading. (#' + scope.workerDirector.objectsCompleted + ')' );
+						logger.logInfo( 'Worker #' + event.detail.instanceNo + ': Completed loading. (#' + workerDirector.objectsCompleted + ')' );
 					};
 					var reportProgress = function( event ) {
 						document.getElementById( 'feedback' ).innerHTML = event.detail.text;
@@ -416,7 +425,7 @@
 					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 					callbacks.setCallbackOnLoad( callbackOnLoad );
 					callbacks.setCallbackOnProgress( reportProgress );
-					this.workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
+					workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
 
 					var prepData;
 					var pivot;
@@ -440,9 +449,9 @@
 						prepData.dimension = Math.max( Math.random() * 500, 100 );
 						prepData.globalObjectCount = globalObjectCount++;
 
-						this.workerDirector.enqueueForRun( prepData );
+						workerDirector.enqueueForRun( prepData );
 					}
-					this.workerDirector.processQueue();
+					workerDirector.processQueue();
 				};
 
 				MeshSprayApp.prototype.resizeDisplayGL = function () {

+ 4 - 1
examples/webgl_loader_obj2_options.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - WWOBJLoader2</title>
+		<title>three.js webgl - OBJLoader2 usage options</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 
@@ -243,6 +243,8 @@
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var callbackOnLoad = function ( event ) {
+						objLoader.workerSupport.setTerminateRequested( true );
+
 						var local = new THREE.Object3D();
 						local.name = 'Pivot_WaltHead';
 						local.position.set( -125, 50, 0 );
@@ -257,6 +259,7 @@
 					var onLoadMtl = function ( materials ) {
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
+						objLoader.terminateWorkerOnLoad = false;
 						objLoader.load( 'obj/walt/WaltHead.obj', callbackOnLoad, null, null, null, true );
 					};
 					objLoader.loadMtl( 'obj/walt//WaltHead.mtl', 'WaltHead.mtl', null, onLoadMtl );

+ 20 - 4
examples/webgl_loader_obj2_run_director.html

@@ -113,7 +113,7 @@
 
 					this.logger = new THREE.LoaderSupport.ConsoleLogger();
 					this.logger.setEnabled( false );
-					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2, this.logger  );
+					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2, this.logger );
 					this.workerDirector.setCrossOrigin( 'anonymous' );
 
 					this.controls = null;
@@ -380,7 +380,24 @@
 				};
 
 				WWParallels.prototype.terminateManager = function () {
-					this.workerDirector.deregister();
+					this.workerDirector.tearDown();
+					this.running = false;
+				};
+
+				WWParallels.prototype.terminateManagerAndClearScene = function () {
+					var scope = this;
+					var scopedClearAllAssests = function (){
+						scope.clearAllAssests();
+					};
+					if ( this.workerDirector.isRunning() ) {
+
+						this.workerDirector.tearDown( scopedClearAllAssests );
+
+					} else {
+
+						scopedClearAllAssests();
+					}
+
 					this.running = false;
 				};
 
@@ -402,8 +419,7 @@
 					app.terminateManager();
 				},
 				clearAllAssests: function () {
-					app.terminateManager();
-					app.clearAllAssests();
+					app.terminateManagerAndClearScene();
 				}
 			};
 			var gui = new dat.GUI( {