From fc24ca6311285e282cd114dc3fb541617c97a0cc Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 17 Aug 2020 23:04:09 +0200 Subject: [PATCH] TaskManager: - Is now able to properly handle multiple requests for initTaskType. If one is ongoing the other requests are not answered until the first one is complete. This prevents that parsing starts in any sequentially triggered loaders before init is really complete. - Introduced execution loop OBJLoader2Parser - Fix: Was not properly reset when reused in Worker. General: - Updated typescript definitions --- examples/jsm/loaders/OBJLoader2.d.ts | 1 - examples/jsm/loaders/OBJLoader2.js | 1 - examples/jsm/loaders/OBJLoader2Parallel.js | 50 ++- examples/jsm/loaders/obj2/OBJLoader2Parser.js | 132 +++---- .../loaders/obj2/shared/MaterialHandler.d.ts | 24 -- .../loaders/obj2/shared/MaterialHandler.js | 19 +- examples/jsm/taskmanager/TaskManager.d.ts | 39 +-- examples/jsm/taskmanager/TaskManager.js | 322 +++++++++--------- .../jsm/taskmanager/worker/tmOBJLoader2.js | 3 + examples/webgl_loader_obj2_options.html | 10 +- examples/webgl_loader_taskmanager.html | 3 +- 11 files changed, 282 insertions(+), 322 deletions(-) diff --git a/examples/jsm/loaders/OBJLoader2.d.ts b/examples/jsm/loaders/OBJLoader2.d.ts index e0b66095f59ff8..f55e3d18c6eaea 100644 --- a/examples/jsm/loaders/OBJLoader2.d.ts +++ b/examples/jsm/loaders/OBJLoader2.d.ts @@ -13,7 +13,6 @@ export class OBJLoader2 extends Loader { constructor( manager?: LoadingManager ); parser: OBJLoader2Parser; modelName: string; - instanceNo: number; path: string; resourcePath: string; baseObject3d: Object3D; diff --git a/examples/jsm/loaders/OBJLoader2.js b/examples/jsm/loaders/OBJLoader2.js index 1fbe343b2fb024..1764c626bda9bc 100644 --- a/examples/jsm/loaders/OBJLoader2.js +++ b/examples/jsm/loaders/OBJLoader2.js @@ -26,7 +26,6 @@ const OBJLoader2 = function ( manager ) { this.parser = new OBJLoader2Parser(); this.modelName = ''; - this.instanceNo = 0; this.baseObject3d = new Object3D(); this.materialHandler = new MaterialHandler(); diff --git a/examples/jsm/loaders/OBJLoader2Parallel.js b/examples/jsm/loaders/OBJLoader2Parallel.js index 487162dbc3a273..d3db441a561404 100644 --- a/examples/jsm/loaders/OBJLoader2Parallel.js +++ b/examples/jsm/loaders/OBJLoader2Parallel.js @@ -24,12 +24,11 @@ const OBJLoader2Parallel = function ( manager ) { OBJLoader2.call( this, manager ); this.preferJsmWorker = false; - this.initPerformed = false; this.jsmWorkerUrl = null; this.executeParallel = true; - this.taskManager = new TaskManager(); + this.taskManager = null; this.taskName = 'tmOBJLoader2'; }; @@ -101,6 +100,12 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp */ _buildWorkerCode: async function () { + if ( ! this.taskManager instanceof TaskManager ) { + + if ( this.parser.logging.debug ) console.log( 'Needed to create new TaskManager' ); + this.taskManager = new TaskManager(); + + } if ( ! this.taskManager.supportsTaskType( this.taskName ) ) { if ( this.preferJsmWorker ) { @@ -110,19 +115,13 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp } else { let obj2ParserDep = 'const OBJLoader2Parser = ' + OBJLoader2Parser.toString() + ';\n\n'; - this.taskManager.registerTaskType( this.taskName, OBJ2LoaderWorker.init, OBJ2LoaderWorker.execute, null, false, [{ code: obj2ParserDep }] ); + this.taskManager.registerTaskType( this.taskName, OBJ2LoaderWorker.init, OBJ2LoaderWorker.execute, null, false, + [{ code: obj2ParserDep }] ); } - await this.taskManager.initTaskType( this.taskName, {} ).catch( e => console.error( e ) ); - - } - else { - - await new Promise( resolve => resolve( true ) ); + await this.taskManager.initTaskType( this.taskName, {} ); } - this.initPerformed = true; - return this.initPerformed; }, @@ -149,7 +148,6 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp } } - OBJLoader2.prototype.load.call( this, content, interceptOnLoad, onFileLoadProgress, onError, onMeshAlter ); }, @@ -163,21 +161,14 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp if ( this.executeParallel ) { - // check if worker has been initialize before. If yes, skip init - if ( ! this.initPerformed ) { - - this._buildWorkerCode() - .then( x => { + this._buildWorkerCode() + .then( + x => { if ( this.parser.logging.debug ) console.log( 'OBJLoader2Parallel init was performed: ' + x ); - this._executeWorkerParse( content ) - } ); + this._executeWorkerParse( content ); + } + ).catch( e => console.error( e ) ); - } - else { - - this._executeWorkerParse( content ); - - } let dummy = new Object3D(); dummy.name = 'OBJLoader2ParallelDummy'; return dummy; @@ -196,23 +187,22 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp this.materialHandler.createDefaultMaterials( false ); let config = { - id: 42, + id: Math.floor( Math.random() * Math.floor( 65536 ) ), + buffer: content, params: { modelName: this.modelName, - instanceNo: this.instanceNo, useIndices: this.parser.useIndices, disregardNormals: this.parser.disregardNormals, materialPerSmoothingGroup: this.parser.materialPerSmoothingGroup, useOAsMesh: this.parser.useOAsMesh, materials: this.materialHandler.getMaterialsJSON() }, - buffer: content, logging: { enabled: this.parser.logging.enabled, debug: this.parser.logging.debug } }; - this.taskManager.enqueueForExecution( this.taskName, config,data => this._onAssetAvailable( data ), { buffer: content.buffer } ) + this.taskManager.enqueueForExecution( this.taskName, config, data => this._onAssetAvailable( data ), { buffer: content } ) .then( data => { this._onAssetAvailable( data ); this.parser.callbacks.onLoad( this.baseObject3d, 'finished' ); @@ -222,8 +212,6 @@ OBJLoader2Parallel.prototype = Object.assign( Object.create( OBJLoader2.prototyp } - - } ); export { OBJLoader2Parallel }; diff --git a/examples/jsm/loaders/obj2/OBJLoader2Parser.js b/examples/jsm/loaders/obj2/OBJLoader2Parser.js index 9b53f1731049c9..c28da78862606d 100644 --- a/examples/jsm/loaders/obj2/OBJLoader2Parser.js +++ b/examples/jsm/loaders/obj2/OBJLoader2Parser.js @@ -1,4 +1,5 @@ /** + * @author Kai Salmen / https://kaisalmen.de * Development repository: https://github.com/kaisalmen/WWOBJLoader */ @@ -7,11 +8,6 @@ */ const OBJLoader2Parser = function () { - this.logging = { - enabled: false, - debug: false - }; - const scope = this; this.callbacks = { @@ -60,54 +56,65 @@ const OBJLoader2Parser = function () { } }; - this.contentRef = null; - this.legacyMode = false; - - this.materials = {}; - this.materialPerSmoothingGroup = false; - this.useOAsMesh = false; - this.useIndices = false; - this.disregardNormals = false; - - this.vertices = []; - this.colors = []; - this.normals = []; - this.uvs = []; - this.objectId = 0; - - this.rawMesh = { - objectName: '', - groupName: '', - activeMtlName: '', - mtllibName: '', - - // reset with new mesh - faceType: - 1, - subGroups: [], - subGroupInUse: null, - smoothingGroup: { - splitMaterials: false, - normalized: - 1, - real: - 1 - }, - counts: { + + this._init = function () { + + this.logging = { + enabled: false, + debug: false + }; + + this.contentRef = null; + this.legacyMode = false; + + this.materials = {}; + this.materialPerSmoothingGroup = false; + this.useOAsMesh = false; + this.useIndices = false; + this.disregardNormals = false; + + this.vertices = []; + this.colors = []; + this.normals = []; + this.uvs = []; + this.objectId = 0; + + this.rawMesh = { + objectName: '', + groupName: '', + activeMtlName: '', + mtllibName: '', + + // reset with new mesh + faceType: - 1, + subGroups: [], + subGroupInUse: null, + smoothingGroup: { + splitMaterials: false, + normalized: - 1, + real: - 1 + }, + counts: { + doubleIndicesCount: 0, + faceCount: 0, + mtlCount: 0, + smoothingGroupCount: 0 + } + }; + + this.inputObjectCount = 1; + this.outputObjectCount = 1; + this.globalCounts = { + vertices: 0, + faces: 0, doubleIndicesCount: 0, - faceCount: 0, - mtlCount: 0, - smoothingGroupCount: 0 - } - }; + lineByte: 0, + currentByte: 0, + totalBytes: 0 + }; - this.inputObjectCount = 1; - this.outputObjectCount = 1; - this.globalCounts = { - vertices: 0, - faces: 0, - doubleIndicesCount: 0, - lineByte: 0, - currentByte: 0, - totalBytes: 0 - }; + } + this._init(); this._resetRawMesh = function () { @@ -842,12 +849,6 @@ const OBJLoader2Parser = function () { this._resetRawMesh(); } - this.callbacks.onAssetAvailable( - { - cmd: 'execComplete', - type: 'void' - } ); - return haveMesh; } @@ -940,6 +941,7 @@ const OBJLoader2Parser = function () { const payload = { cmd: 'assetAvailable', type: 'material', + id: this.objectId, materials: { materialCloneInstructions: materialCloneInstructions } @@ -1070,11 +1072,11 @@ const OBJLoader2Parser = function () { uvs: uvFA } }, - [ vertexFA.buffer ], - indexUA !== null ? [ indexUA.buffer ] : null, - colorFA !== null ? [ colorFA.buffer ] : null, - normalFA !== null ? [ normalFA.buffer ] : null, - uvFA !== null ? [ uvFA.buffer ] : null + [ vertexFA.buffer, + indexUA !== null ? indexUA.buffer : null, + colorFA !== null ? colorFA.buffer : null, + normalFA !== null ? normalFA.buffer : null, + uvFA !== null ? uvFA.buffer : null ] ); } @@ -1091,6 +1093,12 @@ const OBJLoader2Parser = function () { console.info( parserFinalReport ); } + this.callbacks.onAssetAvailable( + { + cmd: 'execComplete', + type: 'void', + id: this.objectId + } ); } }; diff --git a/examples/jsm/loaders/obj2/shared/MaterialHandler.d.ts b/examples/jsm/loaders/obj2/shared/MaterialHandler.d.ts index 134441c0967b48..ded53965ee33b8 100644 --- a/examples/jsm/loaders/obj2/shared/MaterialHandler.d.ts +++ b/examples/jsm/loaders/obj2/shared/MaterialHandler.d.ts @@ -1,26 +1,2 @@ -import { - Material -} from '../../../../../src/Three'; - export class MaterialHandler { - - constructor(); - logging: { - enabled: boolean; - debug: boolean; - }; - callbacks: { - onLoadMaterials: Function; - }; - materials: object; - - createDefaultMaterials( overrideExisting: boolean ): void; - addMaterials( materials: object, overrideExisting: boolean, newMaterials?: object ): object; - addPayloadMaterials( materialPayload: object ): object; - setLogging( enabled: boolean, debug: boolean ): void; - getMaterials(): object; - getMaterial( materialName: string ): Material; - getMaterialsJSON(): object; - clearMaterials(): void; - } diff --git a/examples/jsm/loaders/obj2/shared/MaterialHandler.js b/examples/jsm/loaders/obj2/shared/MaterialHandler.js index 443965c0e91f74..5e610336c74242 100644 --- a/examples/jsm/loaders/obj2/shared/MaterialHandler.js +++ b/examples/jsm/loaders/obj2/shared/MaterialHandler.js @@ -1,4 +1,5 @@ /** + * @author Kai Salmen / https://kaisalmen.de * Development repository: https://github.com/kaisalmen/WWOBJLoader */ @@ -6,7 +7,8 @@ import { LineBasicMaterial, MaterialLoader, MeshStandardMaterial, - PointsMaterial + PointsMaterial, + VertexColors } from "../../../../../build/three.module.js"; @@ -63,7 +65,7 @@ MaterialHandler.prototype = { const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; - defaultVertexColorMaterial.vertexColors = true; + defaultVertexColorMaterial.vertexColors = VertexColors; const defaultLineMaterial = new LineBasicMaterial(); defaultLineMaterial.name = 'defaultLineMaterial'; @@ -123,26 +125,21 @@ MaterialHandler.prototype = { } let materials = materialPayload.materials.serializedMaterials; - if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) { const loader = new MaterialLoader(); let materialJson; - for ( materialName in materials ) { materialJson = materials[ materialName ]; - if ( materialJson !== undefined && materialJson !== null ) { material = loader.parse( materialJson ); - if ( this.logging.enabled ) { console.info( 'De-serialized material with name "' + materialName + '" will be added.' ); } - this.materials[ materialName ] = material; newMaterials[ materialName ] = material; @@ -151,7 +148,6 @@ MaterialHandler.prototype = { } } - materials = materialPayload.materials.runtimeMaterials; newMaterials = this.addMaterials( materials, true, newMaterials ); @@ -173,32 +169,27 @@ MaterialHandler.prototype = { newMaterials = {}; } - if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) { let material; let existingMaterial; let add; - for ( const materialName in materials ) { material = materials[ materialName ]; add = overrideExisting === true; - if ( ! add ) { existingMaterial = this.materials[ materialName ]; add = ( existingMaterial === null || existingMaterial === undefined ); } - if ( add ) { this.materials[ materialName ] = material; newMaterials[ materialName ] = material; } - if ( this.logging.enabled && this.logging.debug ) { console.info( 'Material with name "' + materialName + '" was added.' ); @@ -214,7 +205,6 @@ MaterialHandler.prototype = { this.callbacks.onLoadMaterials( newMaterials ); } - return newMaterials; }, @@ -250,7 +240,6 @@ MaterialHandler.prototype = { const materialsJSON = {}; let material; - for ( const materialName in this.materials ) { material = this.materials[ materialName ]; diff --git a/examples/jsm/taskmanager/TaskManager.d.ts b/examples/jsm/taskmanager/TaskManager.d.ts index 788e4354e3d2f1..b5551fabb23b61 100644 --- a/examples/jsm/taskmanager/TaskManager.d.ts +++ b/examples/jsm/taskmanager/TaskManager.d.ts @@ -5,15 +5,17 @@ export class TaskManager { maxParallelExecutions: number; actualExecutionCount: number; storedExecutions: StoredExecution[]; + teardown: boolean; setVerbose(verbose: boolean): TaskManager; setMaxParallelExecutions(maxParallelExecutions: number): TaskManager; getMaxParallelExecutions(): number; supportsTaskType(taskType: string): boolean; - registerTaskType(taskType: string, initFunction: Function, executeFunction: Function, comRoutingFunction: Function, fallback: boolean, dependencyDescriptions?: any[]): TaskManager; - registerTaskTypeModule(taskType: string, workerModuleUrl: string): TaskManager; - initTaskType(taskType: string, config: object, transferables?: any): Promise; + registerTaskType(taskType: string, initFunction: Function, executeFunction: Function, comRoutingFunction: Function, fallback: boolean, dependencyDescriptions?: any[]): boolean; + registerTaskTypeModule(taskType: string, workerModuleUrl: string): boolean; + initTaskType(taskType: string, config: object, transferables?: any): Promise; + wait(milliseconds: any): Promise; enqueueForExecution(taskType: string, config: object, assetAvailableFunction: Function, transferables?: any): Promise; - _kickExecutions(): void; + _kickExecutions(): Promise; dispose(): TaskManager; } declare class WorkerTypeDefinition { @@ -21,19 +23,11 @@ declare class WorkerTypeDefinition { taskType: string; fallback: boolean; verbose: boolean; + initialised: boolean; functions: { - init: { - ref: Function; - code: string; - }; - execute: { - ref: Function; - code: string; - }; - comRouting: { - ref: Function; - code: string; - }; + init: Function; + execute: Function; + comRouting: Function; dependencies: { descriptions: any[]; code: string[]; @@ -45,16 +39,19 @@ declare class WorkerTypeDefinition { instances: TaskWorker[] | MockedTaskWorker[]; available: TaskWorker[] | MockedTaskWorker[]; }; + status: { + initStarted: boolean; + initComplete: boolean; + }; getTaskType(): string; setFunctions(initFunction: Function, executeFunction: Function, comRoutingFunction?: Function): void; setDependencyDescriptions(dependencyDescriptions: any[]): void; setWorkerModule(workerModuleUrl: string): void; isWorkerModule(): boolean; - loadDependencies(): Promise; - generateWorkerCode(dependencies: ArrayBuffer[]): Promise; - createWorkers(code: string): Promise; - createWorkerModules(): Promise; - initWorkers(instances: TaskWorker[] | MockedTaskWorker[], config: object, transferables: any): Promise; + loadDependencies(): () => []; + createWorkers(): Promise; + createWorkerModules(): Promise; + initWorkers(config: object, transferables: any): Promise; getAvailableTask(): TaskWorker | MockedTaskWorker | undefined; hasTask(): boolean; returnAvailableTask(taskWorker: TaskWorker | MockedTaskWorker): void; diff --git a/examples/jsm/taskmanager/TaskManager.js b/examples/jsm/taskmanager/TaskManager.js index ac2040991604b3..a1de986776e1a7 100644 --- a/examples/jsm/taskmanager/TaskManager.js +++ b/examples/jsm/taskmanager/TaskManager.js @@ -4,6 +4,7 @@ */ import { FileLoader } from "../../../build/three.module.js"; +import { TaskManagerDefaultRouting } from "./worker/tmDefaultComRouting.js"; /** * Register one to many tasks type to the TaskManager. Then init and enqueue a worker based execution by passing @@ -29,6 +30,8 @@ class TaskManager { * @type {StoredExecution[]} */ this.storedExecutions = []; + this.teardown = false; + this._kickExecutions().then( x => console.log( 'Teared down: ' + x ) ); } @@ -89,15 +92,20 @@ class TaskManager { * @param {function} comRoutingFunction The function that should handle communication, leave undefined for default behavior * @param {boolean} fallback Set to true if execution should be performed in main * @param {Object[]} [dependencyDescriptions] - * @return {TaskManager} + * @return {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) */ registerTaskType ( taskType, initFunction, executeFunction, comRoutingFunction, fallback, dependencyDescriptions ) { - let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, fallback, this.verbose ); - workerTypeDefinition.setFunctions( initFunction, executeFunction, comRoutingFunction ); - workerTypeDefinition.setDependencyDescriptions( dependencyDescriptions ); - this.taskTypes.set( taskType, workerTypeDefinition ); - return this; + let allowedToRegister = ! this.supportsTaskType( taskType ); + if ( allowedToRegister ) { + + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, fallback, this.verbose ); + workerTypeDefinition.setFunctions( initFunction, executeFunction, comRoutingFunction ); + workerTypeDefinition.setDependencyDescriptions( dependencyDescriptions ); + this.taskTypes.set( taskType, workerTypeDefinition ); + + } + return allowedToRegister; } @@ -106,14 +114,19 @@ class TaskManager { * * @param {string} taskType The name to be used for registration. * @param {string} workerModuleUrl The URL to be used for the Worker. Module must provide logic to handle "init" and "execute" messages. - * @return {TaskManager} + * @return {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) */ registerTaskTypeModule ( taskType, workerModuleUrl ) { - let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, false, this.verbose ); - workerTypeDefinition.setWorkerModule( workerModuleUrl ); - this.taskTypes.set( taskType, workerTypeDefinition ); - return this; + let allowedToRegister = ! this.supportsTaskType( taskType ); + if ( allowedToRegister ) { + + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, false, this.verbose ); + workerTypeDefinition.setWorkerModule( workerModuleUrl ); + this.taskTypes.set( taskType, workerTypeDefinition ); + + } + return allowedToRegister; } @@ -127,21 +140,51 @@ class TaskManager { async initTaskType ( taskType, config, transferables ) { let workerTypeDefinition = this.taskTypes.get( taskType ); - if ( workerTypeDefinition.isWorkerModule() ) { + if ( workerTypeDefinition ) { - return await workerTypeDefinition.createWorkerModules() - .then( instances => workerTypeDefinition.initWorkers( instances, config, transferables ) ); + if ( ! workerTypeDefinition.status.initStarted ) { - } - else { + workerTypeDefinition.status.initStarted = true; + if ( workerTypeDefinition.isWorkerModule() ) { + + await workerTypeDefinition.createWorkerModules() + .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) + .then( workerTypeDefinition.status.initComplete ) + .catch( x => console.error( x ) ); + + } else { + + await workerTypeDefinition.loadDependencies() + .then( code => workerTypeDefinition.createWorkers() ) + .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) + .then( workerTypeDefinition.status.initComplete ) + .catch( x => console.error( x ) ); + + } + + } + else { + + while ( ! workerTypeDefinition.status.initComplete ) { - return await workerTypeDefinition.loadDependencies() - .then( buffers => workerTypeDefinition.generateWorkerCode( buffers ) ) - .then( code => workerTypeDefinition.createWorkers( code ) ) - .then( instances => workerTypeDefinition.initWorkers( instances, config, transferables ) ) - .catch( x => console.error( x ) ); + await this.wait( 10 ); + + } + + } } + + } + + async wait ( milliseconds ) { + + return new Promise(resolve => { + + setTimeout( resolve, milliseconds ); + + } ); + } /** @@ -160,77 +203,86 @@ class TaskManager { this.storedExecutions.push( new StoredExecution( taskType, config, assetAvailableFunction, resolveUser, rejectUser, transferables ) ); } ); - this._kickExecutions(); return localPromise; } - _kickExecutions () { + async _kickExecutions () { - while ( this.actualExecutionCount < this.maxParallelExecutions && this.storedExecutions.length > 0 ) { + while ( ! this.teardown ) { - let storedExecution = this.storedExecutions.shift(); - if ( storedExecution ) { + let counter = 0; + while ( this.storedExecutions.length > 0 ) { - let workerTypeDefinition = this.taskTypes.get( storedExecution.taskType ); - if ( workerTypeDefinition.hasTask() ) { + if ( this.actualExecutionCount < this.maxParallelExecutions && counter < this.storedExecutions.length ) { + // TODO: storedExecutions and results from worker seem to get mixed up + let storedExecution = this.storedExecutions[ counter ]; + let workerTypeDefinition = this.taskTypes.get( storedExecution.taskType ); let taskWorker = workerTypeDefinition.getAvailableTask(); - this.actualExecutionCount++; - let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { + if ( taskWorker ) { + + this.storedExecutions.splice( counter, 1 ); + this.actualExecutionCount ++; + let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { + + taskWorker.onmessage = function ( e ) { + + // allow intermediate asset provision before flagging execComplete + if ( e.data.cmd === 'assetAvailable' ) { - taskWorker.onmessage = function ( e ) { + if ( storedExecution.assetAvailableFunction instanceof Function ) { - // allow intermediate asset provision before flagging execComplete - if ( e.data.cmd === 'assetAvailable' ) { + storedExecution.assetAvailableFunction( e.data ); - if ( storedExecution.assetAvailableFunction instanceof Function ) { + } - storedExecution.assetAvailableFunction( e.data ); + } else { + + resolveWorker( e ); } - } - else { + }; + taskWorker.onerror = rejectWorker; - resolveWorker( e ); + taskWorker.postMessage( { + cmd: "execute", + workerId: taskWorker.getId(), + config: storedExecution.config + }, storedExecution.transferables ); - } + } ); + promiseWorker.then( ( e ) => { - }; - taskWorker.onerror = rejectWorker; + workerTypeDefinition.returnAvailableTask( taskWorker ); + storedExecution.resolve( e.data ); + this.actualExecutionCount --; - taskWorker.postMessage( { - cmd: "execute", - workerId: taskWorker.getId(), - config: storedExecution.config - }, storedExecution.transferables ); + } ).catch( ( e ) => { - } ); - promiseWorker.then( ( e ) => { + storedExecution.reject( "Execution error: " + e ); - workerTypeDefinition.returnAvailableTask( taskWorker ); - storedExecution.resolve( e.data ); - this.actualExecutionCount --; - this._kickExecutions(); + } ); - } ).catch( ( e ) => { + } else { - storedExecution.reject( "Execution error: " + e ); + counter ++; - } ); + } - } - else { + } else { - // try later again, add at the end for now - this.storedExecutions.push( storedExecution ); + counter = 0; + await this.wait( 1 ); } } + await this.wait( 1 ); } + } /** @@ -239,6 +291,7 @@ class TaskManager { */ dispose () { + this.teardown = true; for ( let workerTypeDefinition of this.taskTypes.values() ) { workerTypeDefinition.dispose(); @@ -267,25 +320,14 @@ class WorkerTypeDefinition { this.taskType = taskType; this.fallback = fallback; this.verbose = verbose === true; + this.initialised = false; this.functions = { - init: { - /** @type {function} */ - ref: null, - /** @type {string} */ - code: null - }, - execute: { - /** @type {function} */ - ref: null, - /** @type {string} */ - code: null - }, - comRouting: { - /** @type {function} */ - ref: null, - /** @type {string} */ - code: null - }, + /** @type {function} */ + init: null, + /** @type {function} */ + execute: null, + /** @type {function} */ + comRouting: null, dependencies: { /** @type {Object[]} */ descriptions: [], @@ -308,6 +350,11 @@ class WorkerTypeDefinition { available: [] }; + this.status = { + initStarted: false, + initComplete: false + } + } getTaskType () { @@ -318,6 +365,7 @@ class WorkerTypeDefinition { /** * Set the three functions. A default comRouting function is used if it is not passed here. + * Then it creates the code fr. * * @param {function} initFunction The function to be called when the worker is initialised * @param {function} executeFunction The function to be called when the worker is executed @@ -325,29 +373,19 @@ class WorkerTypeDefinition { */ setFunctions ( initFunction, executeFunction, comRoutingFunction ) { - this.functions.init.ref = initFunction; - this.functions.execute.ref = executeFunction; - this.functions.comRouting.ref = comRoutingFunction; - - if ( this.fallback && this.functions.comRouting.ref === undefined || this.functions.comRouting.ref === null ) { - - let comRouting = function ( message, init, execute ) { - - let payload = message.data; - if ( payload.cmd === 'init' ) { - - init( self, payload.id, payload.config ); - - } else if ( payload.cmd === 'execute' ) { + this.functions.init = initFunction; + this.functions.execute = executeFunction; + this.functions.comRouting = comRoutingFunction; + if ( this.functions.comRouting === undefined || this.functions.comRouting === null ) { - execute( self, payload.id, payload.config ); - - } - - } - this.functions.comRouting.ref = comRouting; + this.functions.comRouting = TaskManagerDefaultRouting.comRouting; } + this.workers.code.push( 'const init = ' + this.functions.init.toString() + ';\n\n' ); + this.workers.code.push( 'const execute = ' + this.functions.execute.toString() + ';\n\n' ); + this.workers.code.push( 'const comRouting = ' + this.functions.comRouting.toString() + ';\n\n' ); + this.workers.code.push( 'self.addEventListener( "message", message => comRouting( self, message, null, init, execute ), false );' ); + } /** @@ -390,65 +428,38 @@ class WorkerTypeDefinition { /** * Loads all dependencies and stores each as {@link ArrayBuffer} into the array. Returns if all loading is completed. * - * @return {Promise} + * @return {} */ async loadDependencies () { + let promises = []; let fileLoader = new FileLoader(); fileLoader.setResponseType( 'arraybuffer' ); for ( let description of this.functions.dependencies.descriptions ) { - let dep; if ( description.url ) { let url = new URL( description.url, window.location.href ); - dep = await fileLoader.loadAsync( url.href, report => { if ( this.verbose ) console.log( report ); } ) + promises.push( fileLoader.loadAsync( url.href, report => { if ( this.verbose ) console.log( report ); } ) ); } if ( description.code ) { - dep = description.code; + promises.push( new Promise( resolve => resolve( description.code ) ) ); } - this.functions.dependencies.code.push( dep ); } if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of loading of all dependencies.'); - return await Promise.all( this.functions.dependencies.code ); - - } - - /** - * Uses the configured values for init, execute and comRouting and embeds it in necessary glue code. - * - * @param {ArrayBuffer[]} dependencies - * @return {Promise} - */ - async generateWorkerCode ( dependencies ) { - - this.functions.init.code = 'const init = ' + this.functions.init.ref.toString() + ';\n\n'; - this.functions.execute.code = 'const execute = ' + this.functions.execute.ref.toString() + ';\n\n'; - if ( this.functions.comRouting.ref !== null ) { - - this.functions.comRouting.code = "const comRouting = " + this.functions.comRouting.ref.toString() + ";\n\n"; - - } - this.workers.code.push( this.functions.init.code ); - this.workers.code.push( this.functions.execute.code ); - this.workers.code.push( this.functions.comRouting.code ); - this.workers.code.push( 'self.addEventListener( "message", message => comRouting( message, init, execute ), false );' ); - - return this.workers.code; + this.functions.dependencies.code = await Promise.all( promises ); } /** * Creates workers based on the configured function and dependency strings. * - * @param {string} code - * @return {Promise} */ - async createWorkers ( code ) { + async createWorkers () { let worker; if ( !this.fallback ) { @@ -467,20 +478,18 @@ class WorkerTypeDefinition { for ( let i = 0; i < this.workers.instances.length; i ++ ) { - worker = new MockedTaskWorker( i, this.functions.init.ref, this.functions.execute.ref ); + worker = new MockedTaskWorker( i, this.functions.init, this.functions.execute ); this.workers.instances[ i ] = worker; } } - return this.workers.instances; } /** * Creates module workers. * - * @return {Promise} */ async createWorkerModules () { @@ -490,23 +499,21 @@ class WorkerTypeDefinition { this.workers.instances[ i ] = worker; } - return this.workers.instances; } /** * Initialises all workers with common configuration data. * - * @param {TaskWorker[]|MockedTaskWorker[]} instances * @param {object} config * @param {Object} transferables - * @return {Promise} */ - async initWorkers ( instances, config, transferables ) { + async initWorkers ( config, transferables ) { - for ( let taskWorker of instances ) { + let promises = []; + for ( let taskWorker of this.workers.instances ) { - await new Promise( ( resolveWorker, rejectWorker ) => { + let taskWorkerPromise = new Promise( ( resolveWorker, rejectWorker ) => { taskWorker.onmessage = resolveWorker; taskWorker.onerror = rejectWorker; @@ -527,11 +534,12 @@ class WorkerTypeDefinition { }, transferablesToWorker ); } ); - this.workers.available.push( taskWorker ); + promises.push( taskWorkerPromise ); } if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of initialization of all workers.'); - return await Promise.all( this.workers.available ); + await Promise.all( promises ); + this.workers.available = this.workers.instances; } @@ -542,7 +550,13 @@ class WorkerTypeDefinition { */ getAvailableTask () { - return this.workers.available.shift(); + let task = undefined; + if ( this.hasTask() ) { + + task = this.workers.available.shift(); + + } + return task; } @@ -682,26 +696,12 @@ class MockedTaskWorker { postMessage( message, transfer ) { let scope = this; - let comRouting = function ( message ) { - let self = { - postMessage: function ( m ) { - scope.onmessage( { data: m } ) - }, + let self = { + postMessage: function ( m ) { + scope.onmessage( { data: m } ) } - - let payload = message.data; - if ( payload.cmd === 'init' ) { - - scope.functions.init( self, payload.id, payload.config ); - - } - else if ( payload.cmd === 'execute' ) { - - scope.functions.execute( self, payload.id, payload.config ); - - } - }; - comRouting( { data: message } ) + } + TaskManagerDefaultRouting.comRouting( self, { data: message }, null, scope.functions.init, scope.functions.execute ) } diff --git a/examples/jsm/taskmanager/worker/tmOBJLoader2.js b/examples/jsm/taskmanager/worker/tmOBJLoader2.js index f63fe135805084..643d2d5404f16a 100644 --- a/examples/jsm/taskmanager/worker/tmOBJLoader2.js +++ b/examples/jsm/taskmanager/worker/tmOBJLoader2.js @@ -38,6 +38,9 @@ const OBJ2LoaderWorker = { ObjectManipulator.applyProperties( objParser, payload.config, false ); ObjectManipulator.applyProperties( objParser, callbacks, false ); */ + context.obj2.objParser._init(); + + if ( config.buffer !== undefined && config.buffer !== null ) context.obj2.buffer = config.buffer; context.obj2.objParser.objectId = config.id; diff --git a/examples/webgl_loader_obj2_options.html b/examples/webgl_loader_obj2_options.html index 905fcdd6d36e3b..7ed293013158ef 100644 --- a/examples/webgl_loader_obj2_options.html +++ b/examples/webgl_loader_obj2_options.html @@ -93,7 +93,7 @@ this.cube = null; this.pivot = null; - this.taskManager = new TaskManager( 2 ); + this.taskManager = new TaskManager( 1 ); }; WWOBJLoader2Example.prototype = { @@ -192,7 +192,7 @@ }; let objLoader2Parallel = new OBJLoader2Parallel() -// .setTaskManager( this.taskManager ) + .setTaskManager( this.taskManager ) .setModelName( modelName ) .setJsmWorker( this.useJsmWorker, new URL( OBJLoader2Parallel.DEFAULT_JSM_WORKER_PATH, window.location.href ) ) .setCallbackOnLoad( callbackOnLoad ) @@ -249,7 +249,7 @@ this.pivot.add( local ); let objLoader2Parallel = new OBJLoader2Parallel() -// .setTaskManager( this.taskManager ) + .setTaskManager( this.taskManager ) .setModelName( modelName ) .setJsmWorker( this.useJsmWorker, new URL( OBJLoader2Parallel.DEFAULT_JSM_WORKER_PATH, window.location.href ) ); @@ -277,7 +277,7 @@ this.pivot.add( local ); let objLoader2Parallel = new OBJLoader2Parallel() -// .setTaskManager( this.taskManager ) + .setTaskManager( this.taskManager ) .setModelName( local.name ) .setExecuteParallel( false ); @@ -298,7 +298,7 @@ this.pivot.add( local ); let objLoader2Parallel = new OBJLoader2Parallel() -// .setTaskManager( this.taskManager ) + .setTaskManager( this.taskManager ) .setModelName( local.name ) .setJsmWorker( this.useJsmWorker, new URL( OBJLoader2Parallel.DEFAULT_JSM_WORKER_PATH, window.location.href ) ) .setBaseObject3d( local ); diff --git a/examples/webgl_loader_taskmanager.html b/examples/webgl_loader_taskmanager.html index 23c5288438ad46..659eb8ca3b697c 100644 --- a/examples/webgl_loader_taskmanager.html +++ b/examples/webgl_loader_taskmanager.html @@ -266,7 +266,7 @@ } if ( awaiting.length > 0 ) { - return Promise.all( awaiting ); + return await Promise.all( awaiting ); } else { @@ -348,6 +348,7 @@ case 'material': this.materialHandler.addPayloadMaterials( payload ); + break; case 'void': break;