瀏覽代碼

Added WorkerExecutionSupport and OBJLoader2Parallel.js and related modules.
WorkerExecutionSupport allows to run runtime code or pre-defined modules (if browser support is available) in a web worker.

Kai Salmen 6 年之前
父節點
當前提交
9dcd1a8d16

+ 27 - 25
examples/jsm/loaders/OBJLoader2.js

@@ -1,17 +1,16 @@
 /**
- * @author Kai Salmen / https://kaisalmen.de
- * Development repository: https://github.com/kaisalmen/WWOBJLoader
+ * @author Kai Salmen / www.kaisalmen.de
  */
 
 import {
 	DefaultLoadingManager,
 	FileLoader,
 	Group
-} from "../../../build/three.module.js";
+} from "../../node_modules/three/build/three.module.js";
 
-import { Parser } from "./obj2/worker/parallel/OBJLoader2Parser.js";
-import { MeshReceiver } from "./obj2/shared/MeshReceiver.js";
-import { MaterialHandler } from "./obj2/shared/MaterialHandler.js";
+import { OBJLoader2Parser } from "./worker/parallel/OBJLoader2Parser.js";
+import { MeshReceiver } from "./shared/MeshReceiver.js";
+import { MaterialHandler } from "./shared/MaterialHandler.js";
 
 /**
  * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
@@ -47,7 +46,6 @@ const OBJLoader2 = function ( manager ) {
 OBJLoader2.OBJLOADER2_VERSION = '3.0.0-beta';
 console.info( 'Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION );
 
-
 OBJLoader2.prototype = {
 
 	constructor: OBJLoader2,
@@ -320,7 +318,7 @@ OBJLoader2.prototype = {
 			console.time( 'OBJLoader parse: ' + this.modelName );
 
 		}
-		let parser = new Parser();
+		let parser = new OBJLoader2Parser();
 		parser.setLogging( this.logging.enabled, this.logging.debug );
 		parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
 		parser.setUseOAsMesh( this.useOAsMesh );
@@ -330,22 +328,8 @@ OBJLoader2.prototype = {
 		parser.setMaterials( this.materialHandler.getMaterials() );
 
 		let scope = this;
-		let onMeshLoaded = function ( payload ) {
-
-			if ( payload.cmd !== 'data' ) return;
-
-			if ( payload.type === 'mesh' ) {
-
-				let meshes = scope.meshReceiver.buildMeshes( payload );
-				for ( let mesh of meshes ) {
-					scope.baseObject3d.add( mesh );
-				}
-
-			} else if ( payload.type === 'material' ) {
-
-				scope.materialHandler.addPayloadMaterials( payload );
-
-			}
+		let scopedOnAssetAvailable = function ( payload ) {
+			scope._onAssetAvailable( payload );
 		};
 		let onProgressScoped = function ( text, numericalValue ) {
 			scope._onProgress( 'progressParse', text, numericalValue );
@@ -353,7 +337,7 @@ OBJLoader2.prototype = {
 		let onErrorScoped = function ( message ) {
 			scope._onError( message );
 		};
-		parser.setCallbackOnAssetAvailable( onMeshLoaded );
+		parser.setCallbackOnAssetAvailable( scopedOnAssetAvailable );
 		parser.setCallbackOnProgress( onProgressScoped );
 		parser.setCallbackOnError( onErrorScoped );
 		if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
@@ -377,6 +361,24 @@ OBJLoader2.prototype = {
 
 		}
 		return this.baseObject3d;
+	},
+
+	_onAssetAvailable: function ( payload ) {
+
+		if ( payload.cmd !== 'assetAvailable' ) return;
+
+		if ( payload.type === 'mesh' ) {
+
+			let meshes = this.meshReceiver.buildMeshes( payload );
+			for ( let mesh of meshes ) {
+				this.baseObject3d.add( mesh );
+			}
+
+		} else if ( payload.type === 'material' ) {
+
+			this.materialHandler.addPayloadMaterials( payload );
+
+		}
 	}
 };
 

+ 151 - 0
examples/jsm/loaders/OBJLoader2Parallel.js

@@ -0,0 +1,151 @@
+/**
+ * @author Kai Salmen / www.kaisalmen.de
+ */
+
+// Imports only related to wrapper
+import {
+	CodeBuilderInstructions,
+	WorkerExecutionSupport
+} from "./obj2/worker/main/WorkerExecutionSupport.js";
+import { CodeSerializer } from "./obj2/worker/main/CodeSerializer.js";
+import { OBJLoader2 } from "./OBJLoader2.js";
+
+// Imports only related to worker (when standard workers (modules aren't supported) are used)
+import { OBJLoader2Parser } from "./obj2/worker/parallel/OBJLoader2Parser.js";
+import { ObjectManipulator } from "./obj2/utils/ObjectManipulator.js";
+import {
+	WorkerRunner,
+	DefaultWorkerPayloadHandler
+} from "./obj2/worker/parallel/WorkerRunner.js";
+
+/**
+ *
+ * @param [LoadingManager] manager
+
+ * @constructor
+ */
+const OBJLoader2Parallel = function ( manager ) {
+	OBJLoader2.call( this, manager );
+	this.useJsmWorker = false;
+
+	this.callbackOnLoad = null;
+	this.executeParallel = true;
+	this.workerExecutionSupport = new WorkerExecutionSupport();
+};
+
+OBJLoader2Parallel.prototype = Object.create( OBJLoader2.prototype );
+OBJLoader2Parallel.prototype.constructor = OBJLoader2Parallel;
+
+OBJLoader2Parallel.prototype.setUseJsmWorker = function ( useJsmWorker ) {
+	this.useJsmWorker = useJsmWorker === true;
+	return this;
+};
+
+OBJLoader2Parallel.prototype.setCallbackOnLoad = function ( callbackOnLoad ) {
+	if ( callbackOnLoad !== undefined && callbackOnLoad !== null ) {
+		this.callbackOnLoad = callbackOnLoad;
+	}
+	else {
+
+		throw "No callbackOnLoad was provided! Aborting!"
+
+	}
+	return this;
+};
+
+OBJLoader2Parallel.prototype.setExecuteParallel = function ( executeParallel ) {
+	this.executeParallel = executeParallel === true;
+	return this;
+};
+
+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 ) {
+
+		codeBuilderInstructions.setJsmWorkerFile( '../../src/loaders/worker/parallel/jsm/OBJLoader2Worker.js' );
+		jsmSuccess = this.workerExecutionSupport.buildWorkerJsm( codeBuilderInstructions );
+	}
+
+	if ( ! jsmSuccess ) {
+
+		let codeOBJLoader2Parser = CodeSerializer.serializeClass( 'OBJLoader2Parser', OBJLoader2Parser );
+		let codeObjectManipulator = CodeSerializer.serializeObject( 'ObjectManipulator', ObjectManipulator );
+		let codeParserPayloadHandler = CodeSerializer.serializeClass( 'DefaultWorkerPayloadHandler', DefaultWorkerPayloadHandler );
+		let codeWorkerRunner = CodeSerializer.serializeClass( 'WorkerRunner', WorkerRunner );
+
+		codeBuilderInstructions.addCodeFragment( codeOBJLoader2Parser );
+		codeBuilderInstructions.addCodeFragment( codeObjectManipulator );
+		codeBuilderInstructions.addCodeFragment( codeParserPayloadHandler );
+		codeBuilderInstructions.addCodeFragment( codeWorkerRunner );
+
+//		codeBuilderInstructions.addLibraryImport( '../../node_modules/three/build/three.js' );
+		codeBuilderInstructions.addStartCode( 'new WorkerRunner( new DefaultWorkerPayloadHandler( new OBJLoader2Parser() ) );' );
+
+		this.workerExecutionSupport.buildWorkerStandard( codeBuilderInstructions );
+
+	}
+	let scope = this;
+	let scopedOnAssetAvailable = function ( payload ) {
+		scope._onAssetAvailable( payload );
+	};
+
+	this.workerExecutionSupport.updateCallbacks( scopedOnAssetAvailable, this.callbackOnLoad );
+};
+
+/**
+ * Load is intercepted from OBJLoader2.
+ * @inheritDoc
+ */
+OBJLoader2Parallel.prototype.load = function( content, onLoad, onFileLoadProgress, onError, onMeshAlter ) {
+	this.setCallbackOnLoad( onLoad );
+
+	OBJLoader2.prototype.load.call( this, content, function () {}, onFileLoadProgress, onError, onMeshAlter );
+
+};
+
+/**
+ * @inheritDoc
+ */
+OBJLoader2Parallel.prototype.parse = function( content ) {
+	if ( this.executeParallel ) {
+
+		this._configure();
+
+		this.workerExecutionSupport.executeParallel(
+			{
+				params: {
+					modelName: this.modelName,
+					instanceNo: this.instanceNo,
+					useIndices: this.useIndices,
+					disregardNormals: this.disregardNormals,
+					materialPerSmoothingGroup: this.materialPerSmoothingGroup,
+					useOAsMesh: this.useOAsMesh,
+				},
+				materials: this.materialHandler.getMaterialsJSON(),
+				data: {
+					input: content,
+					options: null
+				},
+				logging: {
+					enabled: this.logging.enabled,
+					debug: this.logging.debug
+				}
+			} );
+
+	} else {
+
+		this.callbackOnLoad( OBJLoader2.prototype.parse.call( this, content ) );
+
+	}
+};
+
+export { OBJLoader2Parallel }

+ 3 - 3
examples/jsm/loaders/obj2/shared/MaterialHandler.js

@@ -85,9 +85,9 @@ MaterialHandler.prototype = {
 		if ( materialCloneInstructions !== undefined && materialCloneInstructions !== null ) {
 
 			let materialNameOrg = materialCloneInstructions.materialNameOrg;
-			if ( materialNameOrg !== undefined && materialNameOrg !== null ) {
-
-				let materialOrg = this.materials[ materialNameOrg ];
+			materialNameOrg = (materialNameOrg !== undefined && materialNameOrg !== null) ? materialNameOrg : "";
+			let materialOrg = this.materials[ materialNameOrg ];
+			if ( materialOrg ) {
 				material = materialOrg.clone();
 
 				materialName = materialCloneInstructions.materialName;

+ 178 - 0
examples/jsm/loaders/obj2/utils/CodeSerializer.js

@@ -0,0 +1,178 @@
+/**
+ * @author Kai Salmen / https://kaisalmen.de
+ * Development repository: https://github.com/kaisalmen/WWOBJLoader
+ */
+
+const CodeSerializer = {
+
+	/**
+	 *
+	 * @param fullName
+	 * @param object
+	 * @returns {string}
+	 */
+	serializeObject: function ( fullName, object ) {
+		let objectString = fullName + ' = {\n\n';
+		let part;
+		for ( let name in object ) {
+
+			part = object[ name ];
+			if ( typeof( part ) === 'string' || part instanceof String ) {
+
+				part = part.replace( '\n', '\\n' );
+				part = part.replace( '\r', '\\r' );
+				objectString += '\t' + name + ': "' + part + '",\n';
+
+			} else if ( part instanceof Array ) {
+
+				objectString += '\t' + name + ': [' + part + '],\n';
+
+			} else if ( typeof part === 'object' ) {
+
+				// TODO: Short-cut for now. Recursion required?
+				objectString += '\t' + name + ': {},\n';
+
+			} else {
+
+				objectString += '\t' + name + ': ' + part + ',\n';
+
+			}
+
+		}
+		objectString += '}\n\n';
+
+		return objectString;
+	},
+
+	/**
+	 *
+	 * @param fullName
+	 * @param object
+	 * @param basePrototypeName
+	 * @param ignoreFunctions
+	 * @returns {string}
+	 */
+	serializeClass: function ( fullName, object, constructorName, basePrototypeName, ignoreFunctions, includeFunctions, overrideFunctions ) {
+		let valueString, objectPart, constructorString, i, funcOverride;
+		let prototypeFunctions = [];
+		let objectProperties = [];
+		let objectFunctions = [];
+		let isExtended = ( basePrototypeName !== null && basePrototypeName !== undefined );
+
+		if ( ! Array.isArray( ignoreFunctions ) ) ignoreFunctions = [];
+		if ( ! Array.isArray( includeFunctions ) ) includeFunctions = null;
+		if ( ! Array.isArray( overrideFunctions ) ) overrideFunctions = [];
+
+		for ( let name in object.prototype ) {
+
+			objectPart = object.prototype[ name ];
+			valueString = objectPart.toString();
+			if ( name === 'constructor' ) {
+
+				constructorString = fullName + ' = ' + valueString + ';\n\n';
+
+			} else if ( typeof objectPart === 'function' ) {
+
+				if ( ignoreFunctions.indexOf( name ) < 0 && ( includeFunctions === null || includeFunctions.indexOf( name ) >= 0 ) ) {
+
+					funcOverride = overrideFunctions[ name ];
+					if ( funcOverride && funcOverride.fullName === fullName + '.prototype.' + name ) {
+
+						valueString = funcOverride.code;
+
+					}
+					if ( isExtended ) {
+
+						prototypeFunctions.push( fullName + '.prototype.' + name + ' = ' + valueString + ';\n\n' );
+
+					} else {
+
+						prototypeFunctions.push( '\t' + name + ': ' + valueString + ',\n\n' );
+
+					}
+				}
+
+			}
+
+		}
+		for ( let name in object ) {
+
+			objectPart = object[ name ];
+
+			if ( typeof objectPart === 'function' ) {
+
+				if ( ignoreFunctions.indexOf( name ) < 0 && ( includeFunctions === null || includeFunctions.indexOf( name ) >= 0 ) ) {
+
+					funcOverride = overrideFunctions[ name ];
+					if ( funcOverride && funcOverride.fullName === fullName + '.' + name ) {
+
+						valueString = funcOverride.code;
+
+					} else {
+
+						valueString = objectPart.toString();
+
+					}
+					objectFunctions.push( fullName + '.' + name + ' = ' + valueString + ';\n\n' );
+
+				}
+
+			} else {
+
+				if ( typeof( objectPart ) === 'string' || objectPart instanceof String) {
+
+					valueString = '\"' + objectPart.toString() + '\"';
+
+				} else if ( typeof objectPart === 'object' ) {
+
+					// TODO: Short-cut for now. Recursion required?
+					valueString = "{}";
+
+				} else {
+
+					valueString = objectPart;
+
+				}
+				objectProperties.push( fullName + '.' + name + ' = ' + valueString + ';\n' );
+
+			}
+
+		}
+		if ( ( constructorString === undefined || constructorString === null ) && typeof object.prototype.constructor === 'function' ) {
+
+			constructorString = fullName + ' = ' + object.prototype.constructor.toString().replace( constructorName, '' );
+
+		}
+		let objectString = constructorString + '\n\n';
+		if ( isExtended ) {
+
+			objectString += fullName + '.prototype = Object.create( ' + basePrototypeName + '.prototype );\n';
+
+		}
+		objectString += fullName + '.prototype.constructor = ' + fullName + ';\n';
+		objectString += '\n\n';
+
+		for ( i = 0; i < objectProperties.length; i ++ ) objectString += objectProperties[ i ];
+		objectString += '\n\n';
+
+		for ( i = 0; i < objectFunctions.length; i ++ ) objectString += objectFunctions[ i ];
+		objectString += '\n\n';
+
+		if ( isExtended ) {
+
+			for ( i = 0; i < prototypeFunctions.length; i ++ ) objectString += prototypeFunctions[ i ];
+
+		} else {
+
+			objectString += fullName + '.prototype = {\n\n';
+			for ( i = 0; i < prototypeFunctions.length; i ++ ) objectString += prototypeFunctions[ i ];
+			objectString += '\n};';
+
+		}
+		objectString += '\n\n';
+
+		return objectString;
+	},
+};
+
+export { CodeSerializer }

+ 36 - 0
examples/jsm/loaders/obj2/utils/ObjectManipulator.js

@@ -0,0 +1,36 @@
+/**
+ * @author Kai Salmen / https://kaisalmen.de
+ * Development repository: https://github.com/kaisalmen/WWOBJLoader
+ */
+
+const ObjectManipulator = {
+
+	/**
+	 * Applies values from parameter object via set functions or via direct assignment.
+	 *
+	 * @param {Object} objToAlter The objToAlter instance
+	 * @param {Object} params The parameter object
+	 */
+	applyProperties: function ( objToAlter, params, forceCreation ) {
+		// fast-fail
+		if ( objToAlter === undefined || objToAlter === null || params === undefined || params === null ) return;
+
+		var property, funcName, values;
+		for ( property in params ) {
+			funcName = 'set' + property.substring( 0, 1 ).toLocaleUpperCase() + property.substring( 1 );
+			values = params[ property ];
+
+			if ( typeof objToAlter[ funcName ] === 'function' ) {
+
+				objToAlter[ funcName ]( values );
+
+			} else if ( objToAlter.hasOwnProperty( property ) || forceCreation ) {
+
+				objToAlter[ property ] = values;
+
+			}
+		}
+	}
+};
+
+export { ObjectManipulator }

+ 397 - 0
examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.js

@@ -0,0 +1,397 @@
+/**
+ * @author Kai Salmen / https://kaisalmen.de
+ * Development repository: https://github.com/kaisalmen/WWOBJLoader
+ */
+
+const CodeBuilderInstructions = function () {
+	this.startCode = '';
+	this.codeFragments = [];
+	this.importStatements = [];
+	this.jsmWorkerFile;
+	this.jsmWorker = false;
+	this.defaultGeometryType = 0;
+};
+
+CodeBuilderInstructions.prototype = {
+
+	constructor: CodeBuilderInstructions,
+
+	setJsmWorkerFile: function ( jsmWorkerFile ) {
+		this.jsmWorkerFile = jsmWorkerFile;
+	},
+
+	setJsmWorker: function ( jsmWorker ) {
+		this.jsmWorker = jsmWorker;
+	},
+
+	isJsmWorker: function () {
+		return this.jsmWorker;
+	},
+
+	addStartCode: function ( startCode ) {
+		this.startCode = startCode;
+	},
+
+	addCodeFragment: function ( code ) {
+		this.codeFragments.push( code );
+	},
+
+	addLibraryImport: function ( libraryPath ) {
+		let libraryUrl = new URL( libraryPath, window.location.href ).href;
+		let code = 'importScripts( "' + libraryUrl + '" );';
+		this.importStatements.push(	code );
+	},
+
+	getImportStatements: function () {
+		return this.importStatements;
+	},
+
+	getCodeFragments: function () {
+		return this.codeFragments;
+	},
+
+	getStartCode: function () {
+		return this.startCode;
+	}
+
+};
+/**
+ * This class provides means to transform existing parser code into a web worker. It defines a simple communication protocol
+ * which allows to configure the worker and receive raw mesh data during execution.
+ * @class
+ */
+const WorkerExecutionSupport = 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!";
+
+	this._reset();
+};
+WorkerExecutionSupport.WORKER_SUPPORT_VERSION = '3.0.0-preview';
+console.info( 'Using WorkerSupport version: ' + WorkerExecutionSupport.WORKER_SUPPORT_VERSION );
+
+
+WorkerExecutionSupport.prototype = {
+
+	constructor: WorkerExecutionSupport,
+
+	_reset: function () {
+		this.logging = {
+			enabled: true,
+			debug: false
+		};
+
+		let scope = this;
+		let scopeTerminate = function (  ) {
+			scope._terminate();
+		};
+		this.worker = {
+			native: null,
+			jsmWorker: false,
+			logging: true,
+			workerRunner: {
+				name: 'WorkerRunner',
+				usesMeshDisassembler: false,
+				defaultGeometryType: 0
+			},
+			terminateWorkerOnLoad: false,
+			forceWorkerDataCopy: false,
+			started: false,
+			queuedMessage: null,
+			callbacks: {
+				onAssetAvailable: null,
+				onLoad: null,
+				terminate: scopeTerminate
+			}
+		};
+	},
+
+	/**
+	 * Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
+	 *
+	 * @param {boolean} enabled True or false.
+	 * @param {boolean} debug True or false.
+	 */
+	setLogging: function ( enabled, debug ) {
+		this.logging.enabled = enabled === true;
+		this.logging.debug = debug === true;
+		this.worker.logging = enabled === true;
+		return this;
+	},
+
+	/**
+	 * Forces all ArrayBuffers to be transferred to worker to be copied.
+	 *
+	 * @param {boolean} forceWorkerDataCopy True or false.
+	 */
+	setForceWorkerDataCopy: function ( forceWorkerDataCopy ) {
+		this.worker.forceWorkerDataCopy = forceWorkerDataCopy === true;
+		return this;
+	},
+
+	/**
+	 * Request termination of worker once parser is finished.
+	 *
+	 * @param {boolean} terminateWorkerOnLoad True or false.
+	 */
+	setTerminateWorkerOnLoad: function ( terminateWorkerOnLoad ) {
+		this.worker.terminateWorkerOnLoad = terminateWorkerOnLoad === true;
+		if ( this.worker.terminateWorkerOnLoad && this.isWorkerLoaded( this.worker.jsmWorker ) &&
+				this.worker.queuedMessage === null && this.worker.started ) {
+
+			if ( this.logging.enabled ) {
+
+				console.info( 'Worker is terminated immediately as it is not running!' );
+
+			}
+			this._terminate();
+
+		}
+		return this;
+	},
+
+	/**
+	 * Update all callbacks.
+	 *
+	 * @param {Function} onAssetAvailable The function for processing the data, e.g. {@link MeshReceiver}.
+	 * @param {Function} [onLoad] The function that is called when parsing is complete.
+	 */
+	updateCallbacks: function ( onAssetAvailable, onLoad ) {
+		if ( onAssetAvailable !== undefined && onAssetAvailable !== null ) {
+
+			this.worker.callbacks.onAssetAvailable = onAssetAvailable;
+
+		}
+		if ( onLoad !== undefined && onLoad !== null ) {
+
+			this.worker.callbacks.onLoad = onLoad;
+
+		}
+		this._verifyCallbacks();
+	},
+
+	_verifyCallbacks: function () {
+		if ( this.worker.callbacks.onAssetAvailable === undefined || this.worker.callbacks.onAssetAvailable === null ) {
+
+			throw 'Unable to run as no "onAssetAvailable" callback is set.';
+
+		}
+	},
+
+	buildWorkerJsm: function ( codeBuilderInstructions ) {
+		let jsmSuccess = true;
+		this._buildWorkerCheckPreconditions( true, 'buildWorkerJsm' );
+
+		let workerFileUrl = new URL( codeBuilderInstructions.jsmWorkerFile, window.location.href ).href;
+		try {
+
+			let worker = new Worker( workerFileUrl, { type: "module" } );
+			codeBuilderInstructions.setJsmWorker( true );
+
+			this._configureWorkerCommunication( worker, codeBuilderInstructions, 'buildWorkerJsm' );
+
+		}
+		catch ( e ) {
+
+			jsmSuccess = false;
+			if ( e instanceof TypeError || e instanceof  SyntaxError ) {
+
+				console.error( "Modules are not supported in workers." );
+
+			}
+		}
+
+		return jsmSuccess;
+	},
+
+	/**
+	 * Validate the status of worker code and the derived worker and specify functions that should be build when new raw mesh data becomes available and when the parser is finished.
+	 *
+	 * @param {CodeBuilderIns} buildWorkerCode The function that is invoked to create the worker code of the parser.
+	 */
+	buildWorkerStandard: function ( codeBuilderInstructions ) {
+		this._buildWorkerCheckPreconditions( false,'buildWorkerStandard' );
+
+		let concatenateCode = '';
+		codeBuilderInstructions.getImportStatements().forEach( function ( element ) {
+			concatenateCode += element + '\n';
+		} );
+		concatenateCode += '\n';
+		codeBuilderInstructions.getCodeFragments().forEach( function ( element ) {
+			concatenateCode += element+ '\n';
+		} );
+		concatenateCode += '\n';
+		concatenateCode += codeBuilderInstructions.getStartCode();
+
+		let blob = new Blob( [ concatenateCode ], { type: 'application/javascript' } );
+		let worker = new Worker( window.URL.createObjectURL( blob ) );
+		codeBuilderInstructions.setJsmWorker( false );
+
+		this._configureWorkerCommunication( worker, codeBuilderInstructions, 'buildWorkerStandard' );
+	},
+
+	_buildWorkerCheckPreconditions: function ( requireJsmWorker, timeLabel ) {
+		if ( this.isWorkerLoaded( requireJsmWorker ) ) return;
+
+		if ( this.logging.enabled ) {
+
+			console.info( 'WorkerExecutionSupport: Building ' + ( requireJsmWorker ? 'jsm' : 'standard' ) + ' worker code...' );
+			console.time( timeLabel );
+
+		}
+	},
+
+	_configureWorkerCommunication: function ( worker, codeBuilderInstructions, timeLabel ) {
+		this.worker.native = worker;
+		this.worker.jsmWorker = codeBuilderInstructions.isJsmWorker();
+
+		let scope = this;
+		let scopedReceiveWorkerMessage = function ( event ) {
+			scope._receiveWorkerMessage( event );
+		};
+		this.worker.native.onmessage = scopedReceiveWorkerMessage;
+		if ( codeBuilderInstructions.defaultGeometryType !== undefined && codeBuilderInstructions.defaultGeometryType !== null ) {
+
+			this.worker.workerRunner.defaultGeometryType = codeBuilderInstructions.defaultGeometryType;
+		}
+
+		if ( this.logging.enabled ) {
+
+			console.timeEnd( timeLabel );
+
+		}
+	},
+
+	isWorkerLoaded: function ( requireJsmWorker ) {
+		return this.worker.native !== null &&
+			( ( requireJsmWorker && this.worker.jsmWorker ) || ( ! requireJsmWorker && ! this.worker.jsmWorker ) );
+	},
+
+	/**
+	 * Executed in worker scope
+	 */
+	_receiveWorkerMessage: function ( event ) {
+		let payload = event.data;
+		let workerRunnerName = this.worker.workerRunner.name;
+
+		switch ( payload.cmd ) {
+			case 'assetAvailable':
+				this.worker.callbacks.onAssetAvailable( payload );
+				break;
+
+			case 'completeOverall':
+				this.worker.queuedMessage = null;
+				this.worker.started = false;
+				if ( this.worker.callbacks.onLoad !== null ) {
+
+					this.worker.callbacks.onLoad( payload.msg );
+
+				}
+				if ( this.worker.terminateWorkerOnLoad ) {
+
+					if ( this.worker.logging.enabled ) console.info( 'WorkerSupport [' + workerRunnerName + ']: Run is complete. Terminating application on request!' );
+					this.worker.callbacks.terminate();
+
+				}
+				break;
+
+			case 'error':
+				console.error( 'WorkerSupport [' + workerRunnerName + ']: Reported error: ' + payload.msg );
+				this.worker.queuedMessage = null;
+				this.worker.started = false;
+				if ( this.worker.callbacks.onLoad !== null ) {
+
+					this.worker.callbacks.onLoad( payload.msg );
+
+				}
+				if ( this.worker.terminateWorkerOnLoad ) {
+
+					if ( this.worker.logging.enabled ) console.info( 'WorkerSupport [' + workerRunnerName + ']: Run reported error. Terminating application on request!' );
+					this.worker.callbacks.terminate();
+
+				}
+				break;
+
+			default:
+				console.error( 'WorkerSupport [' + workerRunnerName + ']: Received unknown command: ' + payload.cmd );
+				break;
+
+		}
+	},
+
+	/**
+	 * Runs the parser with the provided configuration.
+	 *
+	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
+	 */
+	executeParallel: function( payload, transferables ) {
+		payload.cmd = 'parse';
+		payload.usesMeshDisassembler = this.worker.workerRunner.usesMeshDisassembler;
+		payload.defaultGeometryType = this.worker.workerRunner.defaultGeometryType;
+		if ( ! this._verifyWorkerIsAvailable( payload, transferables ) ) return;
+
+		this._postMessage();
+	},
+
+	_verifyWorkerIsAvailable: function ( payload, transferables ) {
+		this._verifyCallbacks();
+		let ready = true;
+		if ( this.worker.queuedMessage !== null ) {
+
+			console.warn( 'Already processing message. Rejecting new run instruction' );
+			ready = false;
+
+		} else {
+
+			this.worker.queuedMessage = {
+				payload: payload,
+				transferables: ( transferables === undefined || transferables === null ) ? [] : transferables
+			};
+			this.worker.started = true;
+
+		}
+		return ready;
+	},
+
+	_postMessage: function () {
+		if ( this.worker.queuedMessage !== null && this.isWorkerLoaded( this.worker.jsmWorker ) ) {
+
+			if ( this.worker.queuedMessage.payload.data.input instanceof ArrayBuffer ) {
+
+				let transferables = [];
+				if ( this.worker.forceWorkerDataCopy ) {
+
+					transferables.push( this.worker.queuedMessage.payload.data.input.slice( 0 ) );
+
+				} else {
+
+					transferables.push( this.worker.queuedMessage.payload.data.input );
+
+				}
+				if ( this.worker.queuedMessage.transferables.length > 0 ) {
+
+					transferables = transferables.concat( this.worker.queuedMessage.transferables );
+
+				}
+				this.worker.native.postMessage( this.worker.queuedMessage.payload, transferables );
+
+			} else {
+
+				this.worker.native.postMessage( this.worker.queuedMessage.payload );
+
+			}
+
+		}
+	},
+
+	_terminate: function () {
+		this.worker.native.terminate();
+		this._reset();
+	}
+};
+
+export {
+	CodeBuilderInstructions,
+	WorkerExecutionSupport
+}

+ 7 - 8
examples/jsm/loaders/obj2/worker/parallel/OBJLoader2Parser.js

@@ -1,13 +1,12 @@
 /**
- * @author Kai Salmen / https://kaisalmen.de
- * Development repository: https://github.com/kaisalmen/WWOBJLoader
+ * @author Kai Salmen / www.kaisalmen.de
  */
 
 /**
  * Parse OBJ data either from ArrayBuffer or string
  * @class
  */
-const Parser = function() {
+const OBJLoader2Parser = function() {
 	this.callbacks = {
 		onProgress: null,
 		onAssetAvailable: null,
@@ -67,9 +66,9 @@ const Parser = function() {
 	};
 };
 
-Parser.prototype = {
+OBJLoader2Parser.prototype = {
 
-	constructor: Parser,
+	constructor: OBJLoader2Parser,
 
 	resetRawMesh: function () {
 		// faces are stored according combined index of group, material and smoothingGroup (0 or not)
@@ -764,7 +763,7 @@ Parser.prototype = {
 					}
 				};
 				let payload = {
-					cmd: 'data',
+					cmd: 'assetAvailable',
 					type: 'material',
 					materials: {
 						materialCloneInstructions: materialCloneInstructions
@@ -863,7 +862,7 @@ Parser.prototype = {
 		this.outputObjectCount ++;
 		this.callbacks.onAssetAvailable(
 			{
-				cmd: 'data',
+				cmd: 'assetAvailable',
 				type: 'mesh',
 				progress: {
 					numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes
@@ -908,4 +907,4 @@ Parser.prototype = {
 	}
 };
 
-export { Parser };
+export { OBJLoader2Parser };

+ 128 - 0
examples/jsm/loaders/obj2/worker/parallel/WorkerRunner.js

@@ -0,0 +1,128 @@
+/**
+ * @author Kai Salmen / www.kaisalmen.de
+ */
+
+import { ObjectManipulator } from "../../utils/ObjectManipulator.js";
+
+const DefaultWorkerPayloadHandler = function ( parser ) {
+	this.parser = parser;
+	this.logging = {
+		enabled: false,
+		debug: false
+	};
+};
+
+DefaultWorkerPayloadHandler.prototype = {
+
+	constructor: DefaultWorkerPayloadHandler,
+
+	handlePayload: function ( payload ) {
+		if ( payload.logging ) {
+			this.logging.enabled = payload.logging.enabled === true;
+			this.logging.debug = payload.logging.debug === true;
+		}
+		if ( payload.cmd === 'parse' ) {
+
+			let scope = this;
+			let callbacks = {
+				callbackOnAssetAvailable: function ( payload ) {
+					self.postMessage( payload );
+				},
+				callbackOnProgress: function ( text ) {
+					if ( scope.logging.enabled && scope.logging.debug ) console.debug( 'WorkerRunner: progress: ' + text );
+				}
+			};
+
+			let parser = this.parser;
+			if ( typeof parser[ 'setLogging' ] === 'function' ) {
+
+				parser.setLogging( this.logging.enabled, this.logging.debug );
+
+			}
+			ObjectManipulator.applyProperties( parser, payload.params );
+			ObjectManipulator.applyProperties( parser, payload.materials );
+			ObjectManipulator.applyProperties( parser, callbacks );
+
+			let arraybuffer;
+			if ( payload.params && payload.params.index !== undefined && payload.params.index !== null) {
+
+				arraybuffer = this.resourceDescriptors[ payload.params.index ].content;
+
+			} else {
+
+				arraybuffer = payload.data.input;
+
+			}
+
+			let parseFunctionName = 'parse';
+			if ( typeof parser.getParseFunctionName === 'function' ) parseFunctionName = parser.getParseFunctionName();
+			if ( payload.usesMeshDisassembler ) {
+
+				// TODO: Allow to plug and use generic MeshDisassembler
+
+			} else {
+
+				parser[ parseFunctionName ] ( arraybuffer, payload.data.options );
+
+			}
+			if ( this.logging.enabled ) console.log( 'WorkerRunner: Run complete!' );
+
+			self.postMessage( {
+				cmd: 'completeOverall',
+				msg: 'WorkerRunner completed run.'
+			} );
+
+		} else {
+
+			console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
+
+		}
+
+	}
+};
+
+
+/**
+ * Default implementation of the WorkerRunner responsible for creation and configuration of the parser within the worker.
+ * @constructor
+ */
+const WorkerRunner = function ( payloadHandler ) {
+	this.resourceDescriptors = [];
+	this.payloadHandler = payloadHandler;
+
+	let scope = this;
+	let scopedRunner = function( event ) {
+		scope.processMessage( event.data );
+	};
+	self.addEventListener( 'message', scopedRunner, false );
+};
+
+WorkerRunner.prototype = {
+
+	constructor: WorkerRunner,
+
+	/**
+	 * Configures the Parser implementation according the supplied configuration object.
+	 *
+	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
+	 */
+	processMessage: function ( payload ) {
+		if ( payload.data.resourceDescriptors && this.resourceDescriptors.length === 0 ) {
+
+			for ( let name in payload.data.resourceDescriptors ) {
+
+				this.resourceDescriptors.push( payload.data.resourceDescriptors[ name ] );
+
+			}
+
+		}
+
+		this.payloadHandler.handlePayload( payload );
+	}
+
+};
+
+export {
+	WorkerRunner,
+	DefaultWorkerPayloadHandler
+}

+ 11 - 0
examples/jsm/loaders/obj2/worker/parallel/jsm/OBJLoader2Worker.js

@@ -0,0 +1,11 @@
+/**
+ * @author Kai Salmen / www.kaisalmen.de
+ */
+
+import { OBJLoader2Parser } from "../OBJLoader2Parser.js";
+import {
+	WorkerRunner,
+	DefaultWorkerPayloadHandler
+} from "../WorkerRunner.js";
+
+new WorkerRunner( new DefaultWorkerPayloadHandler( new OBJLoader2Parser() ) );