From 05a07fbba83b890769813cca1fadaf34809a0522 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 24 Jun 2019 14:37:04 +0200 Subject: [PATCH] OBJLoader2Parallel: isolated build function and added code docs WorkerExecutionSupport: buildWorker uses CodeBuilderInstructions to decide how to internally build the worker (standard or jsm based) --- examples/jsm/loaders/OBJLoader2Parallel.js | 105 ++++++++++++------ .../worker/main/WorkerExecutionSupport.js | 91 +++++++++++---- examples/webgl_loader_obj2_options.html | 4 +- 3 files changed, 141 insertions(+), 59 deletions(-) diff --git a/examples/jsm/loaders/OBJLoader2Parallel.js b/examples/jsm/loaders/OBJLoader2Parallel.js index f2e763e8b6..0071bb1be8 100644 --- a/examples/jsm/loaders/OBJLoader2Parallel.js +++ b/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 ) ); } }; diff --git a/examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.js b/examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.js index 164f87faae..3854064b0e 100644 --- a/examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.js +++ b/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 ) ); diff --git a/examples/webgl_loader_obj2_options.html b/examples/webgl_loader_obj2_options.html index 4ca851dbb1..e0412c56f4 100644 --- a/examples/webgl_loader_obj2_options.html +++ b/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 ); -- GitLab