diff --git a/examples/jsm/loaders/TaskManager.js b/examples/jsm/loaders/TaskManager.js index 6ce03a7cf57e4c..12d98e9d8ed6c0 100644 --- a/examples/jsm/loaders/TaskManager.js +++ b/examples/jsm/loaders/TaskManager.js @@ -16,14 +16,11 @@ class TaskManager { */ constructor ( maximumWorkerCount ) { - this.types = new Map(); + this.taskTypes = new Map(); this.verbose = false; - this.maximumWorkerCount = 4 - if ( maximumWorkerCount ) { - - this.maximumWorkerCount = maximumWorkerCount; - - } + this.maximumWorkerCount = maximumWorkerCount ? maximumWorkerCount : 4; + this.actualExecutionCount = 0; + this.storedPromises = []; } @@ -54,18 +51,18 @@ class TaskManager { /** * Returns true if support for the given task type is available. - * @param {string} type The type as string + * @param {string} taskType The task type as string * @return boolean */ - supportsType ( type ) { + supportsTaskType ( taskType ) { - return this.types.has( type ); + return this.taskTypes.has( taskType ); } /** * Registers functionality for a new task type. - * @param {string} type The name to be used for registration. + * @param {string} taskType The name to be used for registration. * @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 * @param {function} comRoutingFunction The function that should handle communication, leave undefined for default behavior @@ -73,43 +70,43 @@ class TaskManager { * @param {String[]} [dependencyUrls] * @return {TaskManager} */ - registerType ( type, initFunction, executeFunction, comRoutingFunction, fallback, dependencyUrls ) { + registerTaskType ( taskType, initFunction, executeFunction, comRoutingFunction, fallback, dependencyUrls ) { - let workerTypeDefinition = new WorkerTypeDefinition( type, this.maximumWorkerCount, fallback, this.verbose ); + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maximumWorkerCount, fallback, this.verbose ); workerTypeDefinition.setFunctions( initFunction, executeFunction, comRoutingFunction ); workerTypeDefinition.setDependencyUrls( dependencyUrls ); - this.types.set( type, workerTypeDefinition ); + this.taskTypes.set( taskType, workerTypeDefinition ); return this; } /** * Registers functionality for a new task type based on module file. - * @param {string} type The name to be used for registration. - * @param {string} workerJsmUrl The URL to be used for the Worker. Module must provide logic to handle "init" and "execute" messages. + * @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} */ - registerTypeJsm ( type, workerJsmUrl ) { + registerTaskTypeModule ( taskType, workerModuleUrl ) { - let workerTypeDefinition = new WorkerTypeDefinition( type, this.maximumWorkerCount, false, this.verbose ); - workerTypeDefinition.setWorkerJsm( workerJsmUrl ); - this.types.set( type, workerTypeDefinition ); + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maximumWorkerCount, false, this.verbose ); + workerTypeDefinition.setWorkerModule( workerModuleUrl ); + this.taskTypes.set( taskType, workerTypeDefinition ); return this; } /** * Provides initialization configuration and transferable objects. - * @param {string} type The name of the registered task type. + * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer}. */ - async initType ( type, config, transferables ) { + async initTaskType ( taskType, config, transferables ) { - let workerTypeDefinition = this.types.get( type ); - if ( workerTypeDefinition.isWorkerJsm() ) { + let workerTypeDefinition = this.taskTypes.get( taskType ); + if ( workerTypeDefinition.isWorkerModule() ) { - return await workerTypeDefinition.createWorkersJsm() + return await workerTypeDefinition.createWorkerModules() .then( instances => workerTypeDefinition.initWorkers( instances, config, transferables ) ); } @@ -126,70 +123,79 @@ class TaskManager { /** * Queues a new task of the given type. Task will not execute until initialization completes. - * @param {string} type The name of the registered task type. + * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer}. * @return {Promise} */ - async addTask ( type, config, transferables ) { + async enqueueForExecution ( taskType, config, transferables ) { - let workerTypeDefinition = this.types.get( type ); - let taskWorker = workerTypeDefinition.getAvailableTask(); - return new Promise( ( resolveUser, rejectUser ) => { + let localPromise = new Promise( ( resolveUser, rejectUser ) => { - /** - * Function wrapping worker execution. It binds resolve and reject to onmessage and onerror. - * - * @param {TaskWorker} taskWorkerExecute - * @param {function} resolveExecute - * @param {function} rejectExecute - */ - function executeWorker( taskWorkerExecute, resolveExecute, rejectExecute ) { + this.storedPromises.push( { + taskType: taskType, + config: config, + transferables: transferables, + resolve: resolveUser, + reject: rejectUser + } ); - let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { + } ) - taskWorkerExecute.onmessage = resolveWorker; - taskWorkerExecute.onerror = rejectWorker; + this._verify(); - taskWorkerExecute.postMessage( { - cmd: "execute", - id: taskWorkerExecute.getId(), - config: config - }, transferables ); + return localPromise; - } ); - promiseWorker.then( ( e ) => { + } - resolveExecute( e.data ); - workerTypeDefinition.returnAvailableTask( taskWorkerExecute ); + _verify () { - } ).catch( ( e ) => { + while ( this.actualExecutionCount < this.maximumWorkerCount && this.storedPromises.length > 0 ) { - rejectExecute( "Execution error: " + e ); + let storedExec = this.storedPromises.shift(); + if ( storedExec ) { - } ); + let workerTypeDefinition = this.taskTypes.get( storedExec.taskType ); + let taskWorker = workerTypeDefinition.getAvailableTask(); + if ( taskWorker ) { - } + this.actualExecutionCount++; + let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { - if ( taskWorker ) { + taskWorker.onmessage = resolveWorker; + taskWorker.onerror = rejectWorker; - executeWorker( taskWorker, resolveUser, rejectUser ); + taskWorker.postMessage( { + cmd: "execute", + id: taskWorker.getId(), + config: storedExec.config + }, storedExec.transferables ); - } - else { + } ); + promiseWorker.then( ( e ) => { - // store promises that can not directly executed as the limit has been reached. - // storedPromises are checked when returnAvailableTask is called. - workerTypeDefinition.workers.storedPromises.push( { - exec: executeWorker, - resolve: resolveUser, - reject: rejectUser - } ); + workerTypeDefinition.returnAvailableTask( taskWorker ); + storedExec.resolve( e.data ); + this.actualExecutionCount --; + this._verify(); - } + } ).catch( ( e ) => { - } ) + storedExec.reject( "Execution error: " + e ); + + } ); + + } + else { + + // try later again, add at the end for now + this.storedPromises.push( storedExec ); + + } + + } + } } /** @@ -198,7 +204,7 @@ class TaskManager { */ dispose () { - for ( let workerTypeDefinition of this.types.values() ) { + for ( let workerTypeDefinition of this.taskTypes.values() ) { workerTypeDefinition.dispose(); @@ -225,8 +231,8 @@ class WorkerTypeDefinition { /** */ - constructor ( type, maximumCount, fallback, verbose ) { - this.type = type; + constructor ( taskType, maximumCount, fallback, verbose ) { + this.taskType = taskType; this.maximumCount = maximumCount; this.fallback = fallback; this.verbose = verbose === true; @@ -250,22 +256,21 @@ class WorkerTypeDefinition { /** * @type {URL} */ - workerJsmUrl: null + workerModuleUrl: null }; this.workers = { code: [], instances: [], - available: [], - storedPromises: [] + available: [] }; } - getType () { + getTaskType () { - return this.type; + return this.taskType; } @@ -304,7 +309,7 @@ class WorkerTypeDefinition { } /** - * Set the url of all dependent libraries (only used in non-jsm case). + * Set the url of all dependent libraries (only used in non-module case). * * @param {String[]} dependencyUrls URLs of code init and execute functions rely on. */ @@ -319,24 +324,24 @@ class WorkerTypeDefinition { } /** - * Set the url of the jsm worker. + * Set the url of the module worker. * - * @param {string} workerJsmUrl The URL is created from this string. + * @param {string} workerModuleUrl The URL is created from this string. */ - setWorkerJsm ( workerJsmUrl ) { + setWorkerModule ( workerModuleUrl ) { - this.functions.workerJsmUrl = new URL( workerJsmUrl, window.location.href ); + this.functions.workerModuleUrl = new URL( workerModuleUrl, window.location.href ); } /** - * Is it a jsm worker? + * Is it a module worker? * * @return {boolean} True or false */ - isWorkerJsm () { + isWorkerModule () { - return ( this.functions.workerJsmUrl !== null ); + return ( this.functions.workerModuleUrl !== null ); } @@ -351,11 +356,11 @@ class WorkerTypeDefinition { fileLoader.setResponseType( 'arraybuffer' ); for ( let url of this.functions.dependencies.urls ) { - let dep = await fileLoader.loadAsync( url.href, report => { if ( this.verbose ) console.log( report.detail.text ); } ) + let dep = await fileLoader.loadAsync( url.href, report => { if ( this.verbose ) console.log( report ); } ) this.functions.dependencies.code.push( dep ); } - if ( this.verbose ) console.log( 'Task: ' + this.getType() + ': Waiting for completion of loading of all dependencies.'); + if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of loading of all dependencies.'); return await Promise.all( this.functions.dependencies.code ); } @@ -390,13 +395,14 @@ class WorkerTypeDefinition { */ async createWorkers ( code ) { - let worker, workerBlob; + let worker; if ( !this.fallback ) { + let workerBlob = new Blob( this.functions.dependencies.code.concat( this.workers.code ), { type: 'application/javascript' } ); + let objectURL = window.URL.createObjectURL( workerBlob ); for ( let i = 0; i < this.maximumCount; i ++ ) { - workerBlob = new Blob( this.functions.dependencies.code.concat( this.workers.code ), { type: 'application/javascript' } ); - worker = new TaskWorker( i, window.URL.createObjectURL( workerBlob ) ); + worker = new TaskWorker( i, objectURL ); this.workers.instances[ i ] = worker; } @@ -404,8 +410,12 @@ class WorkerTypeDefinition { } else { - worker = new FakeTaskWorker( 0, this.functions.init.ref, this.functions.execute.ref ); - this.workers.instances[ 0 ] = worker; + for ( let i = 0; i < this.maximumCount; i ++ ) { + + worker = new FakeTaskWorker( i, this.functions.init.ref, this.functions.execute.ref ); + this.workers.instances[ i ] = worker; + + } } return this.workers.instances; @@ -416,11 +426,11 @@ class WorkerTypeDefinition { * * @return {Promise} */ - async createWorkersJsm () { + async createWorkerModules () { for ( let worker, i = 0; i < this.maximumCount; i++ ) { - worker = new TaskWorker( i, this.functions.workerJsmUrl.href, { type: "module" } ); + worker = new TaskWorker( i, this.functions.workerModuleUrl.href, { type: "module" } ); this.workers.instances[ i ] = worker; } @@ -430,7 +440,7 @@ class WorkerTypeDefinition { /** * - * @param {TaskWorker[]} instances + * @param {TaskWorker[]|FakeTaskWorker[]} instances * @param {object} config * @param {Transferable[]} transferables * @return {Promise} @@ -454,15 +464,15 @@ class WorkerTypeDefinition { this.workers.available.push( taskWorker ); } - if ( this.verbose ) console.log( 'Task: ' + this.getType() + ': Waiting for completion of initialization of all workers.'); + if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of initialization of all workers.'); return await Promise.all( this.workers.available ); } /** - * Returns the first {@link TaskWorker} from array of available workers. + * Returns the first {@link TaskWorker} or {@link FakeTaskWorker} from array of available workers. * - * @return {TaskWorker|null} + * @return {TaskWorker|FakeTaskWorker|undefined} */ getAvailableTask () { @@ -472,17 +482,11 @@ class WorkerTypeDefinition { /** * - * @param {TaskWorker} taskWorker + * @param {TaskWorker|FakeTaskWorker} taskWorker */ returnAvailableTask ( taskWorker ) { this.workers.available.push( taskWorker ); - let storedExec = this.workers.storedPromises.shift(); - if ( storedExec ) { - - storedExec.exec( this.getAvailableTask(), storedExec.resolve, storedExec.reject ); - - } } @@ -534,7 +538,7 @@ class TaskWorker extends Worker { /** * */ -class FakeTaskWorker extends TaskWorker { +class FakeTaskWorker { /** * @@ -544,7 +548,7 @@ class FakeTaskWorker extends TaskWorker { */ constructor( id, initFunction, executeFunction ) { - super( id, null ) + this.id = id; this.functions = { init: initFunction, execute: executeFunction @@ -552,6 +556,16 @@ class FakeTaskWorker extends TaskWorker { } + /** + * + * @return {*} + */ + getId() { + + return this.id; + + } + /** * * @param message diff --git a/examples/jsm/taskmanager/tmJsmExample.js b/examples/jsm/taskmanager/tmModuleExample.js similarity index 100% rename from examples/jsm/taskmanager/tmJsmExample.js rename to examples/jsm/taskmanager/tmModuleExample.js diff --git a/examples/jsm/taskmanager/tmJsmExampleNoThree.js b/examples/jsm/taskmanager/tmModuleExampleNoThree.js similarity index 100% rename from examples/jsm/taskmanager/tmJsmExampleNoThree.js rename to examples/jsm/taskmanager/tmModuleExampleNoThree.js diff --git a/examples/webgl_loader_taskmanager.html b/examples/webgl_loader_taskmanager.html index fab479009b5674..b7775a26b485cd 100644 --- a/examples/webgl_loader_taskmanager.html +++ b/examples/webgl_loader_taskmanager.html @@ -86,7 +86,7 @@ uvs: uvFA, }, materials: { - materialNames: ['meshNormalMaterial'] + materialNames: [ 'meshNormalMaterial' ] } }, [vertexFA.buffer], @@ -120,6 +120,8 @@ this.maximumWorkerCount = 4; this.taskManager = new TaskManager(); + this.taskManager.setVerbose( true ); + let materialHandler = new MaterialHandler(); materialHandler.createDefaultMaterials( true ); let meshNormalMaterial = new THREE.MeshNormalMaterial(); @@ -129,8 +131,8 @@ this.taskNames = new Map (); this.taskNames.set( 'tmProtoExample', { use: true, fallback: false } ); - this.taskNames.set( 'tmProtoExampleJsm', { use: true, fallback: false } ); - this.taskNames.set( 'tmProtoExampleJsmNoThree', { use: true, fallback: false} ); + this.taskNames.set( 'tmProtoExampleModule', { use: true, fallback: false } ); + this.taskNames.set( 'tmProtoExampleModuleNoThree', { use: true, fallback: false} ); this.taskNames.set( 'tmProtoExampleMain', { use: false, fallback: true } ); this.tasksToUse = []; @@ -140,10 +142,12 @@ this.removeCount = 50; this.numberOfMeshesToKeep = 750; + this.overallExecutionCount = 1000000; + // overall executions: maxPerLoop * loopCount this.maxPerLoop = 1000; // number of Promises kept in one go - this.loopCount = 1000; + this.loopCount = this.overallExecutionCount / this.maxPerLoop; this.abort = false; @@ -194,28 +198,28 @@ if ( valueObj.use ) { this.tasksToUse.push( taskName ); - this.taskManager.registerType( taskName, workerInitFunction, workerExecFunction, null, false, ["../build/three.js"] ); - awaiting.push( this.taskManager.initType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + this.taskManager.registerTaskType( taskName, workerInitFunction, workerExecFunction, null, false, ["../build/three.js"] ); + awaiting.push( this.taskManager.initTaskType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); } - taskName = 'tmProtoExampleJsm'; + taskName = 'tmProtoExampleModule'; valueObj = this.taskNames.get( taskName ); if ( valueObj.use ) { this.tasksToUse.push( taskName ); - this.taskManager.registerTypeJsm( taskName, './jsm/taskmanager/tmJsmExample.js' ); - awaiting.push( this.taskManager.initType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + this.taskManager.registerTaskTypeModule( taskName, './jsm/taskmanager/tmModuleExample.js' ); + awaiting.push( this.taskManager.initTaskType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); } - taskName = 'tmProtoExampleJsmNoThree'; + taskName = 'tmProtoExampleModuleNoThree'; valueObj = this.taskNames.get( taskName ); if ( valueObj.use ) { let torus = new THREE.TorusBufferGeometry( 25, 8, 16, 100 ); let torusPayload = TransferableUtils.packageBufferGeometry( torus, 'torus', 0 ); this.tasksToUse.push( taskName ); - this.taskManager.registerTypeJsm( taskName, './jsm/taskmanager/tmJsmExampleNoThree.js' ); - awaiting.push( this.taskManager.initType( taskName, torusPayload, torusPayload.transferables ).catch( e => console.error( e ) ) ); + this.taskManager.registerTaskTypeModule( taskName, './jsm/taskmanager/tmModuleExampleNoThree.js' ); + awaiting.push( this.taskManager.initTaskType( taskName, torusPayload, torusPayload.transferables ).catch( e => console.error( e ) ) ); } taskName = 'tmProtoExampleMain'; @@ -223,8 +227,8 @@ if ( valueObj.use ) { this.tasksToUse.push( taskName ); - this.taskManager.registerType( taskName, workerInitFunction, workerExecFunction, null, true ); - awaiting.push( this.taskManager.initType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + this.taskManager.registerTaskType( taskName, workerInitFunction, workerExecFunction, null, true ); + awaiting.push( this.taskManager.initTaskType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); } return Promise.all( awaiting ); @@ -263,7 +267,7 @@ _storeAddTaskPromise( taskNameIndex, globalCount ) { - this.executions.push( this.taskManager.addTask( this.tasksToUse[ taskNameIndex ], { count: globalCount } ).then( data => this._processMessage( data ) ).catch( e => console.error( e ) ) ); + this.executions.push( this.taskManager.enqueueForExecution( this.tasksToUse[ taskNameIndex ], { count: globalCount } ).then( data => this._processMessage( data ) ).catch( e => console.error( e ) ) ); } @@ -381,10 +385,13 @@ let controlStop; let tmControls = { tmProtoExampleName: app.taskNames.get( 'tmProtoExample' ).use, - tmProtoExampleJsm: app.taskNames.get( 'tmProtoExampleJsm' ).use, - tmProtoExampleJsmNoThree: app.taskNames.get( 'tmProtoExampleJsmNoThree' ).use, + tmProtoExampleModule: app.taskNames.get( 'tmProtoExampleModule' ).use, + tmProtoExampleModuleNoThree: app.taskNames.get( 'tmProtoExampleModuleNoThree' ).use, tmProtoExampleMain: app.taskNames.get( 'tmProtoExampleMain' ).use, maximumWorkerCount: app.maximumWorkerCount, + overallExecutionCount: app.overallExecutionCount, + numberOfMeshesToKeep: app.numberOfMeshesToKeep, + maxPerLoop: app.maxPerLoop, blockEvent ( event ) { event.stopPropagation(); @@ -400,8 +407,9 @@ executeLoading () { for ( let control of controls ) this.disableElement( control ); + console.time( 'All tasks have been initialized' ); app.initContent().then( x => { - console.log( 'All tasks have been initialized.' ); + console.timeEnd( 'All tasks have been initialized' ); app.executeWorkers(); } ); @@ -424,12 +432,12 @@ controls[ 0 ] = gui.add( tmControls, taskName0 + 'Name' ).name( 'Worker Legacy + three' ); controls[ 0 ].onChange( value => { app.taskNames.get( taskName0 ).use = value; } ); - let taskName1 = 'tmProtoExampleJsm'; - controls[ 1 ] = gui.add( tmControls, taskName1 ).name( 'Worker Jsm + three' ); + let taskName1 = 'tmProtoExampleModule'; + controls[ 1 ] = gui.add( tmControls, taskName1 ).name( 'Worker Module + three' ); controls[ 1 ].onChange( value => { app.taskNames.get( taskName1 ).use = value; } ); - let taskName2 = 'tmProtoExampleJsmNoThree'; - controls[ 2 ] = gui.add( tmControls, taskName2 ).name( 'Worker Jsm solo' ); + let taskName2 = 'tmProtoExampleModuleNoThree'; + controls[ 2 ] = gui.add( tmControls, taskName2 ).name( 'Worker Module solo' ); controls[ 2 ].onChange( value => { app.taskNames.get( taskName2 ).use = value; } ); let taskName3 = 'tmProtoExampleMain'; @@ -439,8 +447,17 @@ controls[ 4 ] = gui.add( tmControls, 'maximumWorkerCount', 1, 16 ).step( 1 ).name( 'Maximum worker count' ); controls[ 4 ].onChange( value => { app.taskManager.setMaximumWorkerCount( value ) } ); - controls[ 5 ] = gui.add( tmControls, 'executeLoading' ).name( 'Engage' ); - controls[ 5 ].domElement.id = 'startButton'; + controls[ 5 ] = gui.add( tmControls, 'overallExecutionCount', 0, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); + controls[ 5 ].onChange( value => { app.overallExecutionCount = value } ); + + controls[ 6 ] = gui.add( tmControls, 'maxPerLoop', 0, 10000 ).step( 100 ).name( 'Max Executions Peer loop' ); + controls[ 6 ].onChange( value => { app.maxPerLoop = value } ); + + controls[ 7 ] = gui.add( tmControls, 'numberOfMeshesToKeep', 100, 10000 ).step( 25 ).name( 'Keep N Meshes' ); + controls[ 7 ].onChange( value => { app.numberOfMeshesToKeep = value } ); + + controls[ 8 ] = gui.add( tmControls, 'executeLoading' ).name( 'Engage' ); + controls[ 8 ].domElement.id = 'startButton'; controlStop = gui.add( tmControls, 'stopExecution' ).name( 'Stop' );