ソースを参照

OBJLoader2Parallel: isolated build function and added code docs
WorkerExecutionSupport: buildWorker uses CodeBuilderInstructions to decide how to internally build the worker (standard or jsm based)

Kai Salmen 6 年 前
コミット
05a07fbba8

+ 70 - 35
examples/jsm/loaders/OBJLoader2Parallel.js

@@ -20,69 +20,83 @@ import {
 } from "./obj2/worker/parallel/WorkerRunner.js";
 
 /**
+ * Extends {OBJLoader2} with the capability to run the parser {OBJLoader2Parser} in web worker
+ * with help of {WorkerExecutionSupport}.
  *
  * @param [LoadingManager] manager
-
  * @constructor
  */
 const OBJLoader2Parallel = function ( manager ) {
 	OBJLoader2.call( this, manager );
-	this.useJsmWorker = false;
+	this.preferJsmWorker = false;
 
-	this.callbackOnLoad = null;
+	this.callbacks.onParseComplete = null;
 	this.executeParallel = true;
 	this.workerExecutionSupport = new WorkerExecutionSupport();
 };
-OBJLoader2.OBJLOADER2_PARALLEL_VERSION = '3.0.0-beta2';
-console.info( 'Using OBJLoader2Parallel version: ' + OBJLoader2.OBJLOADER2PARALLEL_VERSION );
-
 OBJLoader2Parallel.prototype = Object.create( OBJLoader2.prototype );
 OBJLoader2Parallel.prototype.constructor = OBJLoader2Parallel;
 
-OBJLoader2Parallel.prototype.setUseJsmWorker = function ( useJsmWorker ) {
-	this.useJsmWorker = useJsmWorker === true;
+OBJLoader2.OBJLOADER2_PARALLEL_VERSION = '3.0.0-beta2';
+console.info( 'Using OBJLoader2Parallel version: ' + OBJLoader2.OBJLOADER2_PARALLEL_VERSION );
+
+
+OBJLoader2Parallel.prototype.setPreferJsmWorker = function ( preferJsmWorker ) {
+	this.preferJsmWorker = preferJsmWorker === true;
 	return this;
 };
 
-OBJLoader2Parallel.prototype.setCallbackOnLoad = function ( callbackOnLoad ) {
-	if ( callbackOnLoad !== undefined && callbackOnLoad !== null ) {
-		this.callbackOnLoad = callbackOnLoad;
+/**
+ * If this call back is not set, then the completion message from worker will not be received.
+ *
+ * @param {function} onParseComplete
+ * @return {OBJLoader2Parallel}
+ */
+OBJLoader2Parallel.prototype.setCallbackOnParseComplete = function ( onParseComplete ) {
+	if ( onParseComplete !== undefined && onParseComplete !== null ) {
+		this.callbacks.onParseComplete = onParseComplete;
 	}
 	else {
 
-		throw "No callbackOnLoad was provided! Aborting!"
+		throw "No callbackOnLoad was provided! Aborting!";
 
 	}
 	return this;
 };
 
+/**
+ * Execution of parse in parallel via Worker is default, but normal {OBJLoader2} parsing can be enforced via false here.
+ *
+ * @param executeParallel
+ * @return {OBJLoader2Parallel}
+ */
 OBJLoader2Parallel.prototype.setExecuteParallel = function ( executeParallel ) {
 	this.executeParallel = executeParallel === true;
 	return this;
 };
 
+/**
+ * Allow to get hold of {WorkerExecutionSupport} for configuratin purposes
+ *
+ * @return {WorkerExecutionSupport|WorkerExecutionSupport}
+ */
 OBJLoader2Parallel.prototype.getWorkerExecutionSupport = function () {
 	return this.workerExecutionSupport;
 };
 
-OBJLoader2Parallel.prototype._configure = function () {
-	if ( this.callbackOnLoad === null ) {
-		"No callbackOnLoad was provided! Aborting!"
-	}
-
-	// check if worker is already available and if so, then fast-fail
-	if ( this.workerExecutionSupport.isWorkerLoaded( this.useJsmWorker ) ) return;
-
-	let codeBuilderInstructions = new CodeBuilderInstructions();
-
-	let jsmSuccess = false;
-	if ( this.useJsmWorker ) {
+/**
+ * Provides instructions on what is to be contained in the worker
+ *
+ * @return {CodeBuilderInstructions}
+ */
+OBJLoader2Parallel.prototype.buildWorkerCode = function () {
+	let codeBuilderInstructions = new CodeBuilderInstructions( true, true, this.preferJsmWorker );
+	if ( codeBuilderInstructions.isSupportsJsmWorker() ) {
 
 		codeBuilderInstructions.setJsmWorkerFile( '../../src/loaders/worker/parallel/jsm/OBJLoader2Worker.js' );
-		jsmSuccess = this.workerExecutionSupport.buildWorkerJsm( codeBuilderInstructions );
-	}
 
-	if ( ! jsmSuccess ) {
+	}
+	if ( codeBuilderInstructions.isSupportsStandardWorker() ) {
 
 		let codeOBJLoader2Parser = CodeSerializer.serializeClass( 'OBJLoader2Parser', OBJLoader2Parser );
 		let codeObjectManipulator = CodeSerializer.serializeObject( 'ObjectManipulator', ObjectManipulator );
@@ -94,33 +108,54 @@ OBJLoader2Parallel.prototype._configure = function () {
 		codeBuilderInstructions.addCodeFragment( codeParserPayloadHandler );
 		codeBuilderInstructions.addCodeFragment( codeWorkerRunner );
 
+		// allows to include full libraries as importScripts
 //		codeBuilderInstructions.addLibraryImport( '../../node_modules/three/build/three.js' );
 		codeBuilderInstructions.addStartCode( 'new WorkerRunner( new DefaultWorkerPayloadHandler( new OBJLoader2Parser() ) );' );
 
-		this.workerExecutionSupport.buildWorkerStandard( codeBuilderInstructions );
+	}
+	return codeBuilderInstructions;
+};
 
+/**
+ * @private
+ */
+OBJLoader2Parallel.prototype._configure = function () {
+	if ( this.callbacks.onParseComplete === null ) {
+		"No callbackOnLoad was provided! Aborting!"
 	}
+	// check if worker is already available and if so, then fast-fail
+	if ( this.workerExecutionSupport.isWorkerLoaded( this.preferJsmWorker ) ) return;
+
+	this.workerExecutionSupport.buildWorker( this.buildWorkerCode() );
+
 	let scope = this;
 	let scopedOnAssetAvailable = function ( payload ) {
 		scope._onAssetAvailable( payload );
 	};
 
-	this.workerExecutionSupport.updateCallbacks( scopedOnAssetAvailable, this.callbackOnLoad );
+	this.workerExecutionSupport.updateCallbacks( scopedOnAssetAvailable, this.callbacks.onParseComplete );
 };
 
 /**
- * Load is intercepted from OBJLoader2.
- * @inheritDoc
+ * Load is intercepted from {OBJLoader2}. It replaces the regular onLoad callback as the final worker result will be
+ * returned later by its own callbackOnLoad.
+ *
+ * @param {string}  url A string containing the path/URL of the file to be loaded.
+ * @param {function} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument.
+ * @param {function} [onFileLoadProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
+ * @param {function} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
+ * @param {function} [onMeshAlter] Called after worker successfully delivered a single mesh
  */
 OBJLoader2Parallel.prototype.load = function( content, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
-	this.setCallbackOnLoad( onLoad );
+	this.setCallbackOnParseComplete( onLoad );
 
 	OBJLoader2.prototype.load.call( this, content, function () {}, onFileLoadProgress, onError, onMeshAlter );
-
 };
 
 /**
- * @inheritDoc
+ * Parses OBJ data in parallel with web worker.
+ *
+ * @param {arraybuffer} content OBJ data as Uint8Array or String
  */
 OBJLoader2Parallel.prototype.parse = function( content ) {
 	if ( this.executeParallel ) {
@@ -150,7 +185,7 @@ OBJLoader2Parallel.prototype.parse = function( content ) {
 
 	} else {
 
-		this.callbackOnLoad( OBJLoader2.prototype.parse.call( this, content ) );
+		this.callbacks.onParseComplete( OBJLoader2.prototype.parse.call( this, content ) );
 
 	}
 };

+ 69 - 22
examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.js

@@ -3,12 +3,22 @@
  * Development repository: https://github.com/kaisalmen/WWOBJLoader
  */
 
-const CodeBuilderInstructions = function () {
+/**
+ * These instructions are used by {WorkerExecutionSupport} to build code for the web worker or to assign code
+ *
+ * @param {boolean} supportsStandardWorker
+ * @param {boolean} supportsJsmWorker
+ * @constructor
+ */
+const CodeBuilderInstructions = function ( supportsStandardWorker, supportsJsmWorker, preferJsmWorker ) {
+	this.supportsStandardWorker = supportsStandardWorker;
+	this.supportsJsmWorker = supportsJsmWorker;
+	this.preferJsmWorker = preferJsmWorker;
 	this.startCode = '';
 	this.codeFragments = [];
 	this.importStatements = [];
+
 	this.jsmWorkerFile;
-	this.jsmWorker = false;
 	this.defaultGeometryType = 0;
 };
 
@@ -16,16 +26,20 @@ CodeBuilderInstructions.prototype = {
 
 	constructor: CodeBuilderInstructions,
 
-	setJsmWorkerFile: function ( jsmWorkerFile ) {
-		this.jsmWorkerFile = jsmWorkerFile;
+	isSupportsStandardWorker: function () {
+		return this.supportsStandardWorker;
+	},
+
+	isSupportsJsmWorker: function () {
+		return this.supportsJsmWorker;
 	},
 
-	setJsmWorker: function ( jsmWorker ) {
-		this.jsmWorker = jsmWorker;
+	isPreferJsmWorker: function () {
+		return this.preferJsmWorker;
 	},
 
-	isJsmWorker: function () {
-		return this.jsmWorker;
+	setJsmWorkerFile: function ( jsmWorkerFile ) {
+		this.jsmWorkerFile = jsmWorkerFile;
 	},
 
 	addStartCode: function ( startCode ) {
@@ -68,7 +82,7 @@ const WorkerExecutionSupport = function () {
 
 	this._reset();
 };
-WorkerExecutionSupport.WORKER_SUPPORT_VERSION = '3.0.0-beta2';
+WorkerExecutionSupport.WORKER_SUPPORT_VERSION = '3.0.0-preview';
 console.info( 'Using WorkerSupport version: ' + WorkerExecutionSupport.WORKER_SUPPORT_VERSION );
 
 
@@ -95,7 +109,7 @@ WorkerExecutionSupport.prototype = {
 				usesMeshDisassembler: false,
 				defaultGeometryType: 0
 			},
-			terminateWorkerOnLoad: false,
+			terminateWorkerOnLoad: true,
 			forceWorkerDataCopy: false,
 			started: false,
 			queuedMessage: null,
@@ -179,7 +193,33 @@ WorkerExecutionSupport.prototype = {
 		}
 	},
 
-	buildWorkerJsm: function ( codeBuilderInstructions ) {
+	/**
+	 * Builds the worker code according the provided Instructions.
+	 * If jsm worker code shall be built, then function may fall back to standard if lag is set
+	 *
+ 	 * @param {CodeBuilderInstructions} codeBuilderInstructions
+	 */
+	buildWorker: function ( codeBuilderInstructions ) {
+		let jsmSuccess = false;
+
+		if ( codeBuilderInstructions.isSupportsJsmWorker() && codeBuilderInstructions.isPreferJsmWorker() ) {
+			jsmSuccess = this._buildWorkerJsm( codeBuilderInstructions );
+		}
+
+		if ( ! jsmSuccess && codeBuilderInstructions.isSupportsStandardWorker() ) {
+
+			this._buildWorkerStandard( codeBuilderInstructions );
+
+		}
+	},
+
+	/**
+	 *
+	 * @param {CodeBuilderInstructions} codeBuilderInstructions
+	 * @return {boolean} Whether loading of jsm worker was successful
+	 * @private
+	 */
+	_buildWorkerJsm: function ( codeBuilderInstructions ) {
 		let jsmSuccess = true;
 		this._buildWorkerCheckPreconditions( true, 'buildWorkerJsm' );
 
@@ -187,9 +227,7 @@ WorkerExecutionSupport.prototype = {
 		try {
 
 			let worker = new Worker( workerFileUrl, { type: "module" } );
-			codeBuilderInstructions.setJsmWorker( true );
-
-			this._configureWorkerCommunication( worker, codeBuilderInstructions, 'buildWorkerJsm' );
+			this._configureWorkerCommunication( worker, true, codeBuilderInstructions.defaultGeometryType, 'buildWorkerJsm' );
 
 		}
 		catch ( e ) {
@@ -201,7 +239,6 @@ WorkerExecutionSupport.prototype = {
 
 			}
 		}
-
 		return jsmSuccess;
 	},
 
@@ -210,7 +247,13 @@ WorkerExecutionSupport.prototype = {
 	 *
 	 * @param {CodeBuilderIns} buildWorkerCode The function that is invoked to create the worker code of the parser.
 	 */
-	buildWorkerStandard: function ( codeBuilderInstructions ) {
+
+	/**
+	 *
+	 * @param {CodeBuilderInstructions} codeBuilderInstructions
+	 * @private
+	 */
+	_buildWorkerStandard: function ( codeBuilderInstructions ) {
 		this._buildWorkerCheckPreconditions( false,'buildWorkerStandard' );
 
 		let concatenateCode = '';
@@ -226,9 +269,8 @@ WorkerExecutionSupport.prototype = {
 
 		let blob = new Blob( [ concatenateCode ], { type: 'application/javascript' } );
 		let worker = new Worker( window.URL.createObjectURL( blob ) );
-		codeBuilderInstructions.setJsmWorker( false );
 
-		this._configureWorkerCommunication( worker, codeBuilderInstructions, 'buildWorkerStandard' );
+		this._configureWorkerCommunication( worker, false, codeBuilderInstructions.defaultGeometryType, 'buildWorkerStandard' );
 	},
 
 	_buildWorkerCheckPreconditions: function ( requireJsmWorker, timeLabel ) {
@@ -242,18 +284,18 @@ WorkerExecutionSupport.prototype = {
 		}
 	},
 
-	_configureWorkerCommunication: function ( worker, codeBuilderInstructions, timeLabel ) {
+	_configureWorkerCommunication: function ( worker, haveJsmWorker, defaultGeometryType, timeLabel ) {
 		this.worker.native = worker;
-		this.worker.jsmWorker = codeBuilderInstructions.isJsmWorker();
+		this.worker.jsmWorker = haveJsmWorker;
 
 		let scope = this;
 		let scopedReceiveWorkerMessage = function ( event ) {
 			scope._receiveWorkerMessage( event );
 		};
 		this.worker.native.onmessage = scopedReceiveWorkerMessage;
-		if ( codeBuilderInstructions.defaultGeometryType !== undefined && codeBuilderInstructions.defaultGeometryType !== null ) {
+		if ( defaultGeometryType !== undefined && defaultGeometryType !== null ) {
 
-			this.worker.workerRunner.defaultGeometryType = codeBuilderInstructions.defaultGeometryType;
+			this.worker.workerRunner.defaultGeometryType = defaultGeometryType;
 		}
 
 		if ( this.logging.enabled ) {
@@ -263,6 +305,11 @@ WorkerExecutionSupport.prototype = {
 		}
 	},
 
+	/**
+	 * Returns if Worker code is available and complies with expectation.
+	 * @param {boolean} requireJsmWorker
+	 * @return {boolean|*}
+	 */
 	isWorkerLoaded: function ( requireJsmWorker ) {
 		return this.worker.native !== null &&
 			( ( requireJsmWorker && this.worker.jsmWorker ) || ( ! requireJsmWorker && ! this.worker.jsmWorker ) );

+ 2 - 2
examples/webgl_loader_obj2_options.html

@@ -284,8 +284,8 @@
 					.setModelName( local.name )
 					.setBaseObject3d( local );
 
-					// Configure WorkerExecutionSupport to disregard worker after execution
-					objLoader2Parallel.getWorkerExecutionSupport().setTerminateWorkerOnLoad( true );
+					// Configure WorkerExecutionSupport to not disregard worker after execution
+					objLoader2Parallel.getWorkerExecutionSupport().setTerminateWorkerOnLoad( false );
 
 					function callbackMeshAlter ( event ) {
 						let override = new LoadedMeshUserOverride( false, true );