diff --git a/examples/jsm/loaders/TaskManager.js b/examples/jsm/loaders/TaskManager.js index 12d98e9d8ed6c0..5c608eb7920380 100644 --- a/examples/jsm/loaders/TaskManager.js +++ b/examples/jsm/loaders/TaskManager.js @@ -6,19 +6,21 @@ import { FileLoader } from "../../../build/three.module.js"; /** - * + * Register one to many tasks type to the TaskManager. Then init and enqueue a worker based execution by passing + * configuration and buffers. The TaskManager allows to execute a maximum number of executions in parallel. */ class TaskManager { /** + * Creates a new TaskManager instance. * - * @param {number} [maximumWorkerCount] How many workers are allows. Set 0 for execution on Main ({@link FakeTaskWorker}) + * @param {number} [maxParallelExecutions] How many workers are allowed to be executed in parallel. */ - constructor ( maximumWorkerCount ) { + constructor ( maxParallelExecutions ) { this.taskTypes = new Map(); this.verbose = false; - this.maximumWorkerCount = maximumWorkerCount ? maximumWorkerCount : 4; + this.maxParallelExecutions = maxParallelExecutions ? maxParallelExecutions : 4; this.actualExecutionCount = 0; this.storedPromises = []; @@ -38,19 +40,31 @@ class TaskManager { } /** - * @param {number} maximumWorkerCount How many workers are allows. Set 0 for execution on Main ({@link FakeTaskWorker}) + * Set the maximum number of parallel executions. + * + * @param {number} maxParallelExecutions How many workers are allowed to be executed in parallel. * @return {TaskManager} */ - setMaximumWorkerCount ( maximumWorkerCount ) { + setMaxParallelExecutions ( maxParallelExecutions ) { - this.maximumWorkerCount = maximumWorkerCount; + this.maxParallelExecutions = maxParallelExecutions; return this; } + /** + * Returns the maximum number of parallel executions. + * @return {number} + */ + getMaxParallelExecutions () { + + return this.maxParallelExecutions; + + } /** * Returns true if support for the given task type is available. + * * @param {string} taskType The task type as string * @return boolean */ @@ -61,7 +75,8 @@ class TaskManager { } /** - * Registers functionality for a new task type. + * Registers functions and dependencies for a new task type. + * * @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 @@ -72,7 +87,7 @@ class TaskManager { */ registerTaskType ( taskType, initFunction, executeFunction, comRoutingFunction, fallback, dependencyUrls ) { - let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maximumWorkerCount, fallback, this.verbose ); + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, fallback, this.verbose ); workerTypeDefinition.setFunctions( initFunction, executeFunction, comRoutingFunction ); workerTypeDefinition.setDependencyUrls( dependencyUrls ); this.taskTypes.set( taskType, workerTypeDefinition ); @@ -82,13 +97,14 @@ class TaskManager { /** * Registers functionality for a new task type based on module file. + * * @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} */ registerTaskTypeModule ( taskType, workerModuleUrl ) { - let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maximumWorkerCount, false, this.verbose ); + let workerTypeDefinition = new WorkerTypeDefinition( taskType, this.maxParallelExecutions, false, this.verbose ); workerTypeDefinition.setWorkerModule( workerModuleUrl ); this.taskTypes.set( taskType, workerTypeDefinition ); return this; @@ -97,6 +113,7 @@ class TaskManager { /** * Provides initialization configuration and transferable objects. + * * @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}. @@ -123,6 +140,7 @@ class TaskManager { /** * Queues a new task of the given type. Task will not execute until initialization completes. + * * @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}. @@ -142,23 +160,23 @@ class TaskManager { } ) - this._verify(); + this._kickExecutions(); return localPromise; } - _verify () { + _kickExecutions () { - while ( this.actualExecutionCount < this.maximumWorkerCount && this.storedPromises.length > 0 ) { + while ( this.actualExecutionCount < this.maxParallelExecutions && this.storedPromises.length > 0 ) { let storedExec = this.storedPromises.shift(); if ( storedExec ) { let workerTypeDefinition = this.taskTypes.get( storedExec.taskType ); - let taskWorker = workerTypeDefinition.getAvailableTask(); - if ( taskWorker ) { + if ( workerTypeDefinition.hasTask() ) { + let taskWorker = workerTypeDefinition.getAvailableTask(); this.actualExecutionCount++; let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { @@ -177,7 +195,7 @@ class TaskManager { workerTypeDefinition.returnAvailableTask( taskWorker ); storedExec.resolve( e.data ); this.actualExecutionCount --; - this._verify(); + this._kickExecutions(); } ).catch( ( e ) => { @@ -233,7 +251,6 @@ class WorkerTypeDefinition { */ constructor ( taskType, maximumCount, fallback, verbose ) { this.taskType = taskType; - this.maximumCount = maximumCount; this.fallback = fallback; this.verbose = verbose === true; this.functions = { @@ -262,7 +279,7 @@ class WorkerTypeDefinition { this.workers = { code: [], - instances: [], + instances: new Array ( maximumCount ), available: [] }; @@ -400,7 +417,7 @@ class WorkerTypeDefinition { 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 ++ ) { + for ( let i = 0; i < this.workers.instances.length; i ++ ) { worker = new TaskWorker( i, objectURL ); this.workers.instances[ i ] = worker; @@ -410,9 +427,9 @@ class WorkerTypeDefinition { } else { - for ( let i = 0; i < this.maximumCount; i ++ ) { + for ( let i = 0; i < this.workers.instances.length; i ++ ) { - worker = new FakeTaskWorker( i, this.functions.init.ref, this.functions.execute.ref ); + worker = new MockedTaskWorker( i, this.functions.init.ref, this.functions.execute.ref ); this.workers.instances[ i ] = worker; } @@ -428,7 +445,7 @@ class WorkerTypeDefinition { */ async createWorkerModules () { - for ( let worker, i = 0; i < this.maximumCount; i++ ) { + for ( let worker, i = 0; i < this.workers.instances.length; i++ ) { worker = new TaskWorker( i, this.functions.workerModuleUrl.href, { type: "module" } ); this.workers.instances[ i ] = worker; @@ -440,7 +457,7 @@ class WorkerTypeDefinition { /** * - * @param {TaskWorker[]|FakeTaskWorker[]} instances + * @param {TaskWorker[]|MockedTaskWorker[]} instances * @param {object} config * @param {Transferable[]} transferables * @return {Promise} @@ -470,9 +487,9 @@ class WorkerTypeDefinition { } /** - * Returns the first {@link TaskWorker} or {@link FakeTaskWorker} from array of available workers. + * Returns the first {@link TaskWorker} or {@link MockedTaskWorker} from array of available workers. * - * @return {TaskWorker|FakeTaskWorker|undefined} + * @return {TaskWorker|MockedTaskWorker|undefined} */ getAvailableTask () { @@ -481,8 +498,19 @@ class WorkerTypeDefinition { } /** + * Returns if a task is available or not. * - * @param {TaskWorker|FakeTaskWorker} taskWorker + * @return {boolean} + */ + hasTask () { + + return this.workers.available.length > 0; + + } + + /** + * + * @param {TaskWorker|MockedTaskWorker} taskWorker */ returnAvailableTask ( taskWorker ) { @@ -506,15 +534,16 @@ class WorkerTypeDefinition { } /** - * + * Extends the {@link Worker} with an id. */ class TaskWorker extends Worker { /** + * Creates a new instance. * - * @param id - * @param aURL - * @param options + * @param {number} id Numerical id of the task. + * @param {string} aURL + * @param {object} [options] */ constructor( id, aURL, options ) { @@ -536,13 +565,15 @@ class TaskWorker extends Worker { } /** - * + * This is a mock of a worker to be used on Main. It defines necessary functions, so it can be handled like + * a regular {@link TaskWorker}. */ -class FakeTaskWorker { +class MockedTaskWorker { /** + * Creates a new instance. * - * @param id + * @param {number} id * @param {function} initFunction * @param {function} executeFunction */ @@ -567,7 +598,7 @@ class FakeTaskWorker { } /** - * + * Delegates the message to the registered functions * @param message */ postMessage( message ) { diff --git a/examples/webgl_loader_taskmanager.html b/examples/webgl_loader_taskmanager.html index b7775a26b485cd..fda8775a2f69d6 100644 --- a/examples/webgl_loader_taskmanager.html +++ b/examples/webgl_loader_taskmanager.html @@ -101,6 +101,7 @@ class TaskManagerPrototypeExample { constructor ( elementToBindTo ) { + this.renderer = null; this.canvas = elementToBindTo; this.aspectRatio = 1; @@ -115,9 +116,36 @@ }; this.camera = null; this.cameraTarget = this.cameraDefaults.posCameraTarget; - this.controls = null; - this.maximumWorkerCount = 4; + + this.taskManager = null; + this.meshReceiver = null; + + this.taskNames = new Map (); + this.tasksToUse = []; + this.executions = []; + this.meshCount = 0; + this.lastDeletedMesh = 0; + 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 = this.overallExecutionCount / this.maxPerLoop; + this.abort = false; + this.reset = null; + + // sphere positions + this.baseFactor = 750; + this.baseVectorX = new THREE.Vector3( 1, 0, 0 ); + this.baseVectorY = new THREE.Vector3( 0, 1, 0 ); + this.baseVectorZ = new THREE.Vector3( 0, 0, 1 ); + + } + + resetAppContext () { this.taskManager = new TaskManager(); this.taskManager.setVerbose( true ); @@ -129,7 +157,7 @@ materialHandler.addMaterials( { meshNormalMaterial: meshNormalMaterial }, true ) this.meshReceiver = new MeshReceiver( materialHandler ); - this.taskNames = new Map (); + this.taskNames.clear(); this.taskNames.set( 'tmProtoExample', { use: true, fallback: false } ); this.taskNames.set( 'tmProtoExampleModule', { use: true, fallback: false } ); this.taskNames.set( 'tmProtoExampleModuleNoThree', { use: true, fallback: false} ); @@ -137,6 +165,14 @@ this.tasksToUse = []; this.executions = []; + + if ( this.reset !== null ) { + + this._deleteMeshRange( this.lastDeletedMesh, this.meshCount ); + this.reset(); + this.reset = null; + + } this.meshCount = 0; this.lastDeletedMesh = 0; this.removeCount = 50; @@ -148,7 +184,6 @@ this.maxPerLoop = 1000; // number of Promises kept in one go this.loopCount = this.overallExecutionCount / this.maxPerLoop; - this.abort = false; // sphere positions @@ -231,7 +266,16 @@ awaiting.push( this.taskManager.initTaskType( taskName, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); } - return Promise.all( awaiting ); + if ( awaiting.length > 0 ) { + + return Promise.all( awaiting ); + + } + else { + + return new Promise( ( resolve, reject ) => { reject ( 'No task type has been configured' ) } ) + + } } @@ -263,6 +307,12 @@ this.taskManager.dispose(); console.timeEnd( 'start' ); + if ( this.reset ) { + + this.resetAppContext(); + + } + } _storeAddTaskPromise( taskNameIndex, globalCount ) { @@ -322,24 +372,30 @@ if ( this.meshCount > this.numberOfMeshesToKeep && this.meshCount - this.lastDeletedMesh >= this.numberOfMeshesToKeep ) { let deleteRange = this.lastDeletedMesh + this.removeCount; - for ( let toBeRemoved, i = this.lastDeletedMesh; i < deleteRange; i++ ) { + this._deleteMeshRange( this.lastDeletedMesh, deleteRange ); + this.lastDeletedMesh = deleteRange; + + } - toBeRemoved = this.scene.getObjectByName( 'tmProto' + i ); - if ( toBeRemoved ) { + } - toBeRemoved.geometry.dispose(); - toBeRemoved.material.dispose(); - this.scene.remove( toBeRemoved ); + _deleteMeshRange ( firstMesh, lastMesh ) { - } - else { + for ( let toBeRemoved, i = firstMesh; i < lastMesh; i++ ) { - console.log( 'Unable to remove: tmProto' + i ); + toBeRemoved = this.scene.getObjectByName( 'tmProto' + i ); + if ( toBeRemoved ) { - } + toBeRemoved.geometry.dispose(); + toBeRemoved.material.dispose(); + this.scene.remove( toBeRemoved ); + + } + else { + + console.log( 'Unable to remove: tmProto' + i ); } - this.lastDeletedMesh = deleteRange; } @@ -380,18 +436,40 @@ } let app = new TaskManagerPrototypeExample( document.getElementById( 'example' ) ); + app.resetAppContext(); - let controls = []; - let controlStop; let tmControls = { - tmProtoExampleName: app.taskNames.get( 'tmProtoExample' ).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, + controls: [], + controlStop: null, + controlReset: null, + started: false, + tmProtoExampleName: false, + tmProtoExampleModule: false, + tmProtoExampleModuleNoThree: false, + tmProtoExampleMain: false, + maxParallelExecutions: 0, + overallExecutionCount: 0, + numberOfMeshesToKeep: 0, + maxPerLoop: 0, + resetContent () { + + this.tmProtoExampleName = app.taskNames.get( 'tmProtoExample' ).use; + this.tmProtoExampleModule = app.taskNames.get( 'tmProtoExampleModule' ).use; + this.tmProtoExampleModuleNoThree = app.taskNames.get( 'tmProtoExampleModuleNoThree' ).use; + this.tmProtoExampleMain = app.taskNames.get( 'tmProtoExampleMain' ).use; + this.maxParallelExecutions = app.taskManager.getMaxParallelExecutions(); + this.overallExecutionCount = app.overallExecutionCount; + this.numberOfMeshesToKeep = app.numberOfMeshesToKeep; + this.maxPerLoop = app.maxPerLoop; + for ( let control of this.controls ) { + + this.enableElement( control ); + control.updateDisplay(); + + } + this.disableElement( this.controlStop ); + + }, blockEvent ( event ) { event.stopPropagation(); @@ -403,21 +481,51 @@ elementHandle.domElement.parentElement.style.pointerEvents = 'none'; elementHandle.domElement.parentElement.style.opacity = 0.5; + }, + enableElement ( elementHandle ) { + + elementHandle.domElement.removeEventListener( 'click', this.blockEvent, true ); + elementHandle.domElement.parentElement.style.pointerEvents = 'auto'; + elementHandle.domElement.parentElement.style.opacity = 1.0; + }, executeLoading () { - for ( let control of controls ) this.disableElement( control ); + this.started = true; + for ( let control of this.controls ) this.disableElement( control ); + this.enableElement( this.controlStop ); console.time( 'All tasks have been initialized' ); app.initContent().then( x => { console.timeEnd( 'All tasks have been initialized' ); app.executeWorkers(); - } ); + } ).catch( x => alert( x ) ); }, stopExecution () { + this.started = false; app.abort = true; + }, + resetExecution () { + + let scope = this; + function scopeReset() { + scope.resetContent(); + } + app.reset = scopeReset; + if ( this.started ) { + + this.stopExecution(); + + } + else { + + app.resetAppContext(); + this.resetContent(); + + } + } }; @@ -429,37 +537,40 @@ menuDiv.appendChild( gui.domElement ); let taskName0 = 'tmProtoExample'; - controls[ 0 ] = gui.add( tmControls, taskName0 + 'Name' ).name( 'Worker Legacy + three' ); - controls[ 0 ].onChange( value => { app.taskNames.get( taskName0 ).use = value; } ); + tmControls.controls[ 0 ] = gui.add( tmControls, taskName0 + 'Name' ).name( 'Worker Legacy + three' ); + tmControls.controls[ 0 ].onChange( value => { app.taskNames.get( taskName0 ).use = value; } ); let taskName1 = 'tmProtoExampleModule'; - controls[ 1 ] = gui.add( tmControls, taskName1 ).name( 'Worker Module + three' ); - controls[ 1 ].onChange( value => { app.taskNames.get( taskName1 ).use = value; } ); + tmControls.controls[ 1 ] = gui.add( tmControls, taskName1 ).name( 'Worker Module + three' ); + tmControls.controls[ 1 ].onChange( value => { app.taskNames.get( taskName1 ).use = value; } ); let taskName2 = 'tmProtoExampleModuleNoThree'; - controls[ 2 ] = gui.add( tmControls, taskName2 ).name( 'Worker Module solo' ); - controls[ 2 ].onChange( value => { app.taskNames.get( taskName2 ).use = value; } ); + tmControls.controls[ 2 ] = gui.add( tmControls, taskName2 ).name( 'Worker Module solo' ); + tmControls.controls[ 2 ].onChange( value => { app.taskNames.get( taskName2 ).use = value; } ); let taskName3 = 'tmProtoExampleMain'; - controls[ 3 ] = gui.add( tmControls, taskName3 ).name( 'Worker Legacy Main' ); - controls[ 3 ].onChange( value => { app.taskNames.get( taskName3 ).use = value;} ); + tmControls.controls[ 3 ] = gui.add( tmControls, taskName3 ).name( 'Worker Legacy Main' ); + tmControls.controls[ 3 ].onChange( value => { app.taskNames.get( taskName3 ).use = value;} ); + + tmControls.controls[ 4 ] = gui.add( tmControls, 'maxParallelExecutions', 1, 16 ).step( 1 ).name( 'Maximum Parallel Executions' ); + tmControls.controls[ 4 ].onChange( value => { app.taskManager.setMaxParallelExecutions( value ) } ); - controls[ 4 ] = gui.add( tmControls, 'maximumWorkerCount', 1, 16 ).step( 1 ).name( 'Maximum worker count' ); - controls[ 4 ].onChange( value => { app.taskManager.setMaximumWorkerCount( value ) } ); + tmControls.controls[ 5 ] = gui.add( tmControls, 'overallExecutionCount', 0, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); + tmControls.controls[ 5 ].onChange( value => { app.overallExecutionCount = value } ); - controls[ 5 ] = gui.add( tmControls, 'overallExecutionCount', 0, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); - controls[ 5 ].onChange( value => { app.overallExecutionCount = value } ); + tmControls.controls[ 6 ] = gui.add( tmControls, 'maxPerLoop', 0, 10000 ).step( 100 ).name( 'Loop executions' ); + tmControls.controls[ 6 ].onChange( value => { app.maxPerLoop = value } ); - controls[ 6 ] = gui.add( tmControls, 'maxPerLoop', 0, 10000 ).step( 100 ).name( 'Max Executions Peer loop' ); - controls[ 6 ].onChange( value => { app.maxPerLoop = value } ); + tmControls.controls[ 7 ] = gui.add( tmControls, 'numberOfMeshesToKeep', 100, 10000 ).step( 25 ).name( 'Keep N Meshes' ); + tmControls.controls[ 7 ].onChange( value => { app.numberOfMeshesToKeep = value } ); - controls[ 7 ] = gui.add( tmControls, 'numberOfMeshesToKeep', 100, 10000 ).step( 25 ).name( 'Keep N Meshes' ); - controls[ 7 ].onChange( value => { app.numberOfMeshesToKeep = value } ); + tmControls.controls[ 8 ] = gui.add( tmControls, 'executeLoading' ).name( 'Engage' ); + tmControls.controls[ 8 ].domElement.id = 'startButton'; - controls[ 8 ] = gui.add( tmControls, 'executeLoading' ).name( 'Engage' ); - controls[ 8 ].domElement.id = 'startButton'; + tmControls.controlStop = gui.add( tmControls, 'stopExecution' ).name( 'Stop' ); + tmControls.controlReset = gui.add( tmControls, 'resetExecution' ).name( 'Reset' ); - controlStop = gui.add( tmControls, 'stopExecution' ).name( 'Stop' ); + tmControls.resetContent(); let resizeWindow = function () { app.resizeDisplayGL();