From f5ce379be6738d460e85e42c56d934ceffef6aaf Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Tue, 12 Jan 2021 14:14:25 +0100 Subject: [PATCH 01/12] Squashed all commits due to remvoal of OBJLoader2. WorkerTaskManager: - Remove need for execution loop requiring wait - Rename TaskManager To WorkerTaskManager and move it to directory WorkerTaskManager below loaders - 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. - All context (legacy, module or main) use TaskManagerDefaultRouting.comRouting if no other routing function is provided - Introduced execution loop - Dependency loading allows to provide strings along with to be loaded urls - Adjusted naming (taskType and module) - Reorganize execution flow and renamed addTask to enqueueForExecution - Align FakeWorker main execution behaviuor with TaskWorkers: The maximum no of workers is created for all types - Moved maximumWorkerCount from WorkerTypeDefinition to TaskManager. Maximum number of workers for every type are created, but only maximum number is executed. - Removed the need for FileLoaderBufferAsync - Fixed init bugs due to broken async await - Added default comRouting function - Added TaskWorker (extension of Worker) and FakeTaskWorker (used on main) - Added jsm worker where mesh is created on main and is then shipped to worker which uses buffers as base for new meshes webgl_loader_taskmanager.html - OBJLoader replaces OBJLoader2 in example - now features mtl loading for female and male obj models - uses code from existing modules for export to legacy - Added Reset Button that can reset the selected parameters and the complete scene without reload - Allow to define overall execution, loop count and meshes to keep visible - Adjusted naming (taskType and module) - Add gui controls which allow selection of workers and quantity of instances + abort - Added fake worker that runs on main and not in worker. Prepare code for future UI selection - jsm workers rely on imported comRouting - Add a second jsm based worker to the example - Add and remove meshes no longer synched with renderer as otherwise sphere don't get cleaned on tab change (no rendering) - One million executions with stable resource usage TransferableUtils: - split into TransferableUtils and MeshMessageStructure to have a now have a formal definition of what should is transferred. General: - Transformed all other relevant files to classes and fixed broken examples - Updated typescript definitions - Completed documentation and typescript definition files. TypeScript compiler is used to generate d.ts files from jsdoc descriptions - default comRouting function (jsm or legacy case) takes init and exec functions as input - Make init and excute fully async - All workers in experiment example load three in Worker - Experiment with transferable content generated in Worker - Created TaskManager and WorkerDefinition derived from proposal of Don McCurdy #18234 - addTask returns Promises for every added task, but enqueues them when maximum count is reached - example adds 1000 task, worker loops from 1 to 10^8 on every worker execution, 8 parallel workers are available --- examples/files.json | 3 +- .../workerTaskManager/WorkerTaskManager.d.ts | 85 ++ .../workerTaskManager/WorkerTaskManager.js | 703 ++++++++++++++++ .../shared/MaterialHandler.d.ts | 19 + .../shared/MaterialHandler.js | 273 ++++++ .../shared/MeshReceiver.d.ts | 25 + .../workerTaskManager/shared/MeshReceiver.js | 327 ++++++++ .../utils/TransferableUtils.d.ts | 50 ++ .../utils/TransferableUtils.js | 344 ++++++++ .../worker/tmDefaultComRouting.d.ts | 3 + .../worker/tmDefaultComRouting.js | 39 + .../worker/tmModuleExample.js | 46 ++ .../worker/tmModuleExampleNoThree.js | 43 + .../workerTaskManager/worker/tmOBJLoader.js | 65 ++ examples/webgl_loader_workertaskmanager.html | 775 ++++++++++++++++++ 15 files changed, 2799 insertions(+), 1 deletion(-) create mode 100644 examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js create mode 100644 examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js create mode 100644 examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js create mode 100644 examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js create mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js create mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js create mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js create mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js create mode 100644 examples/webgl_loader_workertaskmanager.html diff --git a/examples/files.json b/examples/files.json index b7cc2fcd104b7c..91d6e56e43cc15 100644 --- a/examples/files.json +++ b/examples/files.json @@ -1,4 +1,4 @@ -{ +{ "webgl": [ "webgl_animation_cloth", "webgl_animation_keyframes", @@ -123,6 +123,7 @@ "webgl_loader_vrm", "webgl_loader_vrml", "webgl_loader_vtk", + "webgl_loader_workertaskmanager", "webgl_loader_xyz", "webgl_lod", "webgl_marchingcubes", diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts new file mode 100644 index 00000000000000..6967a38393db73 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts @@ -0,0 +1,85 @@ +export class WorkerTaskManager { + constructor(maxParallelExecutions?: number); + taskTypes: Map; + verbose: boolean; + maxParallelExecutions: number; + actualExecutionCount: number; + storedExecutions: StoredExecution[]; + teardown: boolean; + setVerbose(verbose: boolean): WorkerTaskManager; + setMaxParallelExecutions(maxParallelExecutions: number): WorkerTaskManager; + getMaxParallelExecutions(): number; + supportsTaskType(taskType: string): boolean; + 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; + _depleteExecutions(): Promise; + dispose(): WorkerTaskManager; +} +declare class WorkerTypeDefinition { + constructor(taskType: string, maximumCount: number, fallback: boolean, verbose?: boolean); + taskType: string; + fallback: boolean; + verbose: boolean; + initialised: boolean; + functions: { + init: Function; + execute: Function; + comRouting: Function; + dependencies: { + descriptions: any[]; + code: string[]; + }; + workerModuleUrl: URL; + }; + workers: { + code: string[]; + 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(): () => []; + createWorkers(): Promise; + createWorkerModules(): Promise; + initWorkers(config: object, transferables: any): Promise; + getAvailableTask(): TaskWorker | MockedTaskWorker | undefined; + hasTask(): boolean; + returnAvailableTask(taskWorker: TaskWorker | MockedTaskWorker): void; + dispose(): void; +} +declare class StoredExecution { + constructor(taskType: string, config: object, assetAvailableFunction: Function, resolve: Function, reject: Function, transferables?: Transferable[]); + taskType: string; + config: any; + assetAvailableFunction: Function; + resolve: Function; + reject: Function; + transferables: Transferable[]; +} +declare class TaskWorker extends Worker { + constructor(id: number, aURL: string, options?: object); + id: number; + getId(): number; +} +declare class MockedTaskWorker { + constructor(id: number, initFunction: Function, executeFunction: Function); + id: number; + functions: { + init: Function; + execute: Function; + }; + getId(): number; + postMessage(message: string, transfer?: Transferable[]): void; + terminate(): void; +} +export {}; diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js new file mode 100644 index 00000000000000..69adf36582e4dc --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js @@ -0,0 +1,703 @@ +/** + * @author Don McCurdy / https://www.donmccurdy.com + * @author Kai Salmen / https://kaisalmen.de + */ + +import { FileLoader } from "../../../../build/three.module.js"; +import { WorkerTaskManagerDefaultRouting } from "./worker/tmDefaultComRouting.js"; + +/** + * Register one to many tasks type to the WorkerTaskManager. Then init and enqueue a worker based execution by passing + * configuration and buffers. The WorkerTaskManager allows to execute a maximum number of executions in parallel. + */ +class WorkerTaskManager { + + /** + * Creates a new WorkerTaskManager instance. + * + * @param {number} [maxParallelExecutions] How many workers are allowed to be executed in parallel. + */ + constructor ( maxParallelExecutions ) { + + /** + * @type {Map} + */ + this.taskTypes = new Map(); + this.verbose = false; + this.maxParallelExecutions = maxParallelExecutions ? maxParallelExecutions : 4; + this.actualExecutionCount = 0; + /** + * @type {StoredExecution[]} + */ + this.storedExecutions = []; + this.teardown = false; + + } + + /** + * Set if logging should be verbose + * + * @param {boolean} verbose + * @return {WorkerTaskManager} + */ + setVerbose ( verbose ) { + + this.verbose = verbose; + return this; + + } + + /** + * Set the maximum number of parallel executions. + * + * @param {number} maxParallelExecutions How many workers are allowed to be executed in parallel. + * @return {WorkerTaskManager} + */ + setMaxParallelExecutions ( maxParallelExecutions ) { + + 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 + */ + supportsTaskType ( taskType ) { + + return this.taskTypes.has( taskType ); + + } + + /** + * 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 + * @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 {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) + */ + registerTaskType ( taskType, initFunction, executeFunction, comRoutingFunction, fallback, dependencyDescriptions ) { + + 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; + + } + + /** + * 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 {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) + */ + registerTaskTypeModule ( taskType, workerModuleUrl ) { + + 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; + + } + + /** + * 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 {Object} [transferables] Any optional {@link ArrayBuffer} encapsulated in object.. + */ + async initTaskType ( taskType, config, transferables ) { + + let workerTypeDefinition = this.taskTypes.get( taskType ); + if ( workerTypeDefinition ) { + + if ( ! workerTypeDefinition.status.initStarted ) { + + 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 ) { + + await this._wait( 10 ); + + } + + } + + } + + } + + async _wait ( milliseconds ) { + + return new Promise(resolve => { + + setTimeout( resolve, milliseconds ); + + } ); + + } + + /** + * 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 {Function} assetAvailableFunction Invoke this function if an asset become intermediately available + * @param {Object} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. + * @return {Promise} + */ + async enqueueForExecution ( taskType, config, assetAvailableFunction, transferables ) { + + let localPromise = new Promise( ( resolveUser, rejectUser ) => { + + this.storedExecutions.push( new StoredExecution( taskType, config, assetAvailableFunction, resolveUser, rejectUser, transferables ) ); + this._depleteExecutions(); + + } ); + return localPromise; + + } + + _depleteExecutions () { + + let counter = 0; + while ( 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(); + 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' ) { + + if ( storedExecution.assetAvailableFunction instanceof Function ) { + + storedExecution.assetAvailableFunction( e.data ); + + } + + } else { + + resolveWorker( e ); + + } + + }; + taskWorker.onerror = rejectWorker; + + taskWorker.postMessage( { + cmd: "execute", + workerId: taskWorker.getId(), + config: storedExecution.config + }, storedExecution.transferables ); + + } ); + promiseWorker.then( ( e ) => { + + workerTypeDefinition.returnAvailableTask( taskWorker ); + storedExecution.resolve( e.data ); + this.actualExecutionCount --; + this._depleteExecutions(); + + } ).catch( ( e ) => { + + storedExecution.reject( "Execution error: " + e ); + + } ); + + } else { + + counter ++; + + } + + } + + } + + /** + * Destroys all workers and associated resources. + * @return {WorkerTaskManager} + */ + dispose () { + + this.teardown = true; + for ( let workerTypeDefinition of this.taskTypes.values() ) { + + workerTypeDefinition.dispose(); + + } + return this; + + } + +} + +/** + * Defines a worker type: functions, dependencies and runtime information once it was created. + */ +class WorkerTypeDefinition { + + /** + * Creates a new instance of {@link WorkerTypeDefinition}. + * + * @param {string} taskType The name of the registered task type. + * @param {Number} maximumCount Maximum worker count + * @param {boolean} fallback Set to true if execution should be performed in main + * @param {boolean} [verbose] Set if logging should be verbose + */ + constructor ( taskType, maximumCount, fallback, verbose ) { + this.taskType = taskType; + this.fallback = fallback; + this.verbose = verbose === true; + this.initialised = false; + this.functions = { + /** @type {function} */ + init: null, + /** @type {function} */ + execute: null, + /** @type {function} */ + comRouting: null, + dependencies: { + /** @type {Object[]} */ + descriptions: [], + /** @type {string[]} */ + code: [] + }, + /** + * @type {URL} + */ + workerModuleUrl: null + }; + + + this.workers = { + /** @type {string[]} */ + code: [], + /** @type {TaskWorker[]|MockedTaskWorker[]} */ + instances: new Array ( maximumCount ), + /** @type {TaskWorker[]|MockedTaskWorker[]} */ + available: [] + }; + + this.status = { + initStarted: false, + initComplete: false + } + + } + + getTaskType () { + + return this.taskType; + + } + + /** + * 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 + * @param {function} [comRoutingFunction] The function that should handle communication, leave undefined for default behavior + */ + setFunctions ( initFunction, executeFunction, comRoutingFunction ) { + + this.functions.init = initFunction; + this.functions.execute = executeFunction; + this.functions.comRouting = comRoutingFunction; + if ( this.functions.comRouting === undefined || this.functions.comRouting === null ) { + + this.functions.comRouting = WorkerTaskManagerDefaultRouting.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 );' ); + + } + + /** + * Set the url of all dependent libraries (only used in non-module case). + * + * @param {Object[]} dependencyDescriptions URLs of code init and execute functions rely on. + */ + setDependencyDescriptions ( dependencyDescriptions ) { + + if ( dependencyDescriptions ) { + + dependencyDescriptions.forEach( description => { this.functions.dependencies.descriptions.push( description ) } ); + + } + + } + + /** + * Set the url of the module worker. + * + * @param {string} workerModuleUrl The URL is created from this string. + */ + setWorkerModule ( workerModuleUrl ) { + + this.functions.workerModuleUrl = new URL( workerModuleUrl, window.location.href ); + + } + + /** + * Is it a module worker? + * + * @return {boolean} True or false + */ + isWorkerModule () { + + return ( this.functions.workerModuleUrl !== null ); + + } + + /** + * Loads all dependencies and stores each as {@link ArrayBuffer} into the array. Returns if all loading is completed. + * + * @return {} + */ + async loadDependencies () { + + let promises = []; + let fileLoader = new FileLoader(); + fileLoader.setResponseType( 'arraybuffer' ); + for ( let description of this.functions.dependencies.descriptions ) { + + if ( description.url ) { + + let url = new URL( description.url, window.location.href ); + promises.push( fileLoader.loadAsync( url.href, report => { if ( this.verbose ) console.log( report ); } ) ); + + } + if ( description.code ) { + + promises.push( new Promise( resolve => resolve( description.code ) ) ); + + } + + } + if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of loading of all dependencies.'); + this.functions.dependencies.code = await Promise.all( promises ); + + } + + /** + * Creates workers based on the configured function and dependency strings. + * + */ + async createWorkers () { + + 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.workers.instances.length; i ++ ) { + + worker = new TaskWorker( i, objectURL ); + this.workers.instances[ i ] = worker; + + } + + } + else { + + for ( let i = 0; i < this.workers.instances.length; i ++ ) { + + worker = new MockedTaskWorker( i, this.functions.init, this.functions.execute ); + this.workers.instances[ i ] = worker; + + } + + } + + } + + /** + * Creates module workers. + * + */ + async createWorkerModules () { + + 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; + + } + + } + + /** + * Initialises all workers with common configuration data. + * + * @param {object} config + * @param {Object} transferables + */ + async initWorkers ( config, transferables ) { + + let promises = []; + for ( let taskWorker of this.workers.instances ) { + + let taskWorkerPromise = new Promise( ( resolveWorker, rejectWorker ) => { + + taskWorker.onmessage = resolveWorker; + taskWorker.onerror = rejectWorker; + + // ensure all transferables are copies to all workers on int! + let transferablesToWorker; + if ( transferables ) { + transferablesToWorker = {}; + for ( let [ key, transferable ] of Object.entries( transferables ) ) { + transferablesToWorker[ key ] = transferable !== null ? transferable.slice( 0 ) : null; + } + } + + taskWorker.postMessage( { + cmd: "init", + workerId: taskWorker.getId(), + config: config + }, transferablesToWorker ); + + } ); + promises.push( taskWorkerPromise ); + + } + if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of initialization of all workers.'); + await Promise.all( promises ); + this.workers.available = this.workers.instances; + + } + + /** + * Returns the first {@link TaskWorker} or {@link MockedTaskWorker} from array of available workers. + * + * @return {TaskWorker|MockedTaskWorker|undefined} + */ + getAvailableTask () { + + let task = undefined; + if ( this.hasTask() ) { + + task = this.workers.available.shift(); + + } + return task; + + } + + /** + * Returns if a task is available or not. + * + * @return {boolean} + */ + hasTask () { + + return this.workers.available.length > 0; + + } + + /** + * + * @param {TaskWorker|MockedTaskWorker} taskWorker + */ + returnAvailableTask ( taskWorker ) { + + this.workers.available.push( taskWorker ); + + } + + /** + * Dispose all worker instances. + */ + dispose () { + + for ( let taskWorker of this.workers.instances ) { + + taskWorker.terminate(); + + } + + } + +} + +/** + * Contains all things required for later executions of Worker. + */ +class StoredExecution { + + /** + * Creates a new instance. + * + * @param {string} taskType + * @param {object} config + * @param {Function} assetAvailableFunction + * @param {Function} resolve + * @param {Function} reject + * @param {Transferable[]} [transferables] + */ + constructor( taskType, config, assetAvailableFunction, resolve, reject, transferables ) { + + this.taskType = taskType; + this.config = config; + this.assetAvailableFunction = assetAvailableFunction; + this.resolve = resolve; + this.reject = reject; + this.transferables = transferables; + + } + +} + +/** + * Extends the {@link Worker} with an id. + */ +class TaskWorker extends Worker { + + /** + * Creates a new instance. + * + * @param {number} id Numerical id of the task. + * @param {string} aURL + * @param {object} [options] + */ + constructor( id, aURL, options ) { + + super( aURL, options ); + this.id = id; + + } + + /** + * Returns the id. + * @return {number} + */ + getId() { + + return this.id; + + } + +} + +/** + * 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 MockedTaskWorker { + + /** + * Creates a new instance. + * + * @param {number} id + * @param {function} initFunction + * @param {function} executeFunction + */ + constructor( id, initFunction, executeFunction ) { + + this.id = id; + this.functions = { + init: initFunction, + execute: executeFunction + } + + } + + /** + * Returns the id. + * @return {number} + */ + getId() { + + return this.id; + + } + + /** + * Delegates the message to the registered functions + * @param {String} message + * @param {Transferable[]} [transfer] + */ + postMessage( message, transfer ) { + + let scope = this; + let self = { + postMessage: function ( m ) { + scope.onmessage( { data: m } ) + } + } + WorkerTaskManagerDefaultRouting.comRouting( self, { data: message }, null, scope.functions.init, scope.functions.execute ) + + } + + /** + * Mocking termination + */ + terminate () {} + +} + + +export { WorkerTaskManager }; diff --git a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts new file mode 100644 index 00000000000000..96ef5e3f12509b --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts @@ -0,0 +1,19 @@ +export class MaterialHandler { + logging: { + enabled: boolean; + debug: boolean; + }; + callbacks: { + onLoadMaterials: any; + }; + materials: {}; + setLogging(enabled: boolean, debug: boolean): void; + _setCallbacks(onLoadMaterials: any): void; + createDefaultMaterials(overrideExisting: any): void; + addPayloadMaterials(materialPayload: any): any; + addMaterials(materials: any, overrideExisting: any, newMaterials: any): any; + getMaterials(): any; + getMaterial(materialName: string): any; + getMaterialsJSON(): any; + clearMaterials(): void; +} diff --git a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js new file mode 100644 index 00000000000000..e8a9d4f233f74e --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js @@ -0,0 +1,273 @@ +/** + * @author Kai Salmen / https://kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader + */ + +import { + LineBasicMaterial, + MaterialLoader, + MeshStandardMaterial, + PointsMaterial, + VertexColors +} from '../../../../../build/three.module.js'; + + +class MaterialHandler { + + constructor() { + + this.logging = { + enabled: false, + debug: false + }; + + this.callbacks = { + onLoadMaterials: null + }; + this.materials = {}; + + } + + /** + * 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 ( enabled, debug ) { + + this.logging.enabled = enabled === true; + this.logging.debug = debug === true; + + } + + _setCallbacks ( onLoadMaterials ) { + + if ( onLoadMaterials !== undefined && onLoadMaterials !== null && onLoadMaterials instanceof Function ) { + + this.callbacks.onLoadMaterials = onLoadMaterials; + + } + + } + + /** + * Creates default materials and adds them to the materials object. + * + * @param overrideExisting boolean Override existing material + */ + createDefaultMaterials ( overrideExisting ) { + + const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultMaterial.name = 'defaultMaterial'; + + const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; + defaultVertexColorMaterial.vertexColors = VertexColors; + + const defaultLineMaterial = new LineBasicMaterial(); + defaultLineMaterial.name = 'defaultLineMaterial'; + + const defaultPointMaterial = new PointsMaterial( { size: 0.1 } ); + defaultPointMaterial.name = 'defaultPointMaterial'; + + const runtimeMaterials = {}; + runtimeMaterials[ defaultMaterial.name ] = defaultMaterial; + runtimeMaterials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial; + runtimeMaterials[ defaultLineMaterial.name ] = defaultLineMaterial; + runtimeMaterials[ defaultPointMaterial.name ] = defaultPointMaterial; + + this.addMaterials( runtimeMaterials, overrideExisting ); + + } + + /** + * Updates the materials with contained material objects (sync) or from alteration instructions (async). + * + * @param {Object} materialPayload Material update instructions + * @returns {Object} Map of {@link Material} + */ + addPayloadMaterials ( materialPayload ) { + + let material, materialName; + const materialCloneInstructions = materialPayload.materials.materialCloneInstructions; + let newMaterials = {}; + + if ( materialCloneInstructions !== undefined && materialCloneInstructions !== null ) { + + let materialNameOrg = materialCloneInstructions.materialNameOrg; + materialNameOrg = ( materialNameOrg !== undefined && materialNameOrg !== null ) ? materialNameOrg : ''; + const materialOrg = this.materials[ materialNameOrg ]; + if ( materialOrg ) { + + material = materialOrg.clone(); + + materialName = materialCloneInstructions.materialName; + material.name = materialName; + + Object.assign( material, materialCloneInstructions.materialProperties ); + + this.materials[ materialName ] = material; + newMaterials[ materialName ] = material; + + } else { + + if ( this.logging.enabled ) { + + console.info( 'Requested material "' + materialNameOrg + '" is not available!' ); + + } + + } + + } + + 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; + + } + + } + + } + materials = materialPayload.materials.runtimeMaterials; + newMaterials = this.addMaterials( materials, true, newMaterials ); + + return newMaterials; + + } + + /** + * Set materials loaded by any supplier of an Array of {@link Material}. + * + * @param materials Object with named {@link Material} + * @param forceOverrideExisting boolean Override existing material + * @param newMaterials [Object] with named {@link Material} + */ + addMaterials ( materials, forceOverrideExisting, newMaterials ) { + + if ( newMaterials === undefined || newMaterials === null ) { + + newMaterials = {}; + + } + if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) { + + let material; + let existingMaterial; + let force; + for ( const materialName in materials ) { + + material = materials[ materialName ]; + force = forceOverrideExisting === true; + if ( ! force ) { + + existingMaterial = this.materials[ materialName ]; + if ( existingMaterial ) { + + if ( existingMaterial.uuid !== material.uuid ) { + + console.log( 'Same material name "' + material.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']' ); + } + + } else { + + this.materials[ materialName ] = material; + + } + + } else { + + this.materials[ materialName ] = material; + newMaterials[ materialName ] = material; + + } + if ( this.logging.enabled && this.logging.debug ) { + + console.info( 'Material with name "' + materialName + '" was added.' ); + + } + + } + + } + + if ( this.callbacks.onLoadMaterials ) { + + this.callbacks.onLoadMaterials( newMaterials ); + + } + return newMaterials; + + } + + /** + * Returns the mapping object of material name and corresponding material. + * + * @returns {Object} Map of {@link Material} + */ + getMaterials () { + + return this.materials; + + } + + /** + * + * @param {String} materialName + * @returns {Material} + */ + getMaterial ( materialName ) { + + return this.materials[ materialName ]; + + } + + /** + * Returns the mapping object of material name and corresponding jsonified material. + * + * @returns {Object} Map of Materials in JSON representation + */ + getMaterialsJSON () { + + const materialsJSON = {}; + let material; + for ( const materialName in this.materials ) { + + material = this.materials[ materialName ]; + materialsJSON[ materialName ] = material.toJSON(); + + } + + return materialsJSON; + + } + + /** + * Removes all materials + */ + clearMaterials () { + + this.materials = {}; + + } + +} + +export { MaterialHandler }; diff --git a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts new file mode 100644 index 00000000000000..8357e46e256fb4 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts @@ -0,0 +1,25 @@ +export class MeshReceiver { + constructor(materialHandler: any); + logging: { + enabled: boolean; + debug: boolean; + }; + callbacks: { + onProgress: any; + onMeshAlter: any; + }; + materialHandler: any; + setLogging(enabled: boolean, debug: boolean): void; + private _setCallbacks; + buildMeshes(meshPayload: any): Mesh[]; +} +export class LoadedMeshUserOverride { + constructor(disregardMesh: any, alteredMesh: any); + disregardMesh: boolean; + alteredMesh: boolean; + meshes: any[]; + addMesh(mesh: Mesh): void; + isDisregardMesh(): boolean; + providesAlteredMeshes(): boolean; +} +import { Mesh } from "../../../../../build/three.module.js"; diff --git a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js new file mode 100644 index 00000000000000..b57ac378a31a0f --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js @@ -0,0 +1,327 @@ +/** + * @author Kai Salmen / https://kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader + */ + +import { + BufferAttribute, + BufferGeometry, + LineSegments, + Mesh, + Points, + MaterialLoader +} from '../../../../../build/three.module.js'; + + +/** + * + * @param {MaterialHandler} materialHandler + * @constructor + */ +class MeshReceiver { + + constructor( materialHandler ) { + + this.logging = { + enabled: false, + debug: false + }; + + this.callbacks = { + onProgress: null, + onMeshAlter: null + }; + this.materialHandler = materialHandler; + + } + + /** + * 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 ( enabled, debug ) { + + this.logging.enabled = enabled === true; + this.logging.debug = debug === true; + + } + + /** + * + * @param {Function} onProgress + * @param {Function} onMeshAlter + * @private + */ + _setCallbacks ( onProgress, onMeshAlter ) { + + if ( onProgress !== null && onProgress !== undefined && onProgress instanceof Function ) { + + this.callbacks.onProgress = onProgress; + + } + if ( onMeshAlter !== null && onMeshAlter !== undefined && onMeshAlter instanceof Function ) { + + this.callbacks.onMeshAlter = onMeshAlter; + + } + + } + + /** + * Builds one or multiple meshes from the data described in the payload (buffers, params, material info). + * + * @param {Object} meshPayload Raw mesh description (buffers, params, materials) used to build one to many meshes. + * @returns {Mesh[]} mesh Array of {@link Mesh} + */ + buildMeshes ( meshPayload ) { + + const buffers = meshPayload.buffers; + const bufferGeometry = new BufferGeometry(); + if ( buffers.vertices !== undefined && buffers.vertices !== null ) { + + bufferGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( buffers.vertices ), 3 ) ); + + } + if ( buffers.indices !== undefined && buffers.indices !== null ) { + + bufferGeometry.setIndex( new BufferAttribute( new Uint32Array( buffers.indices ), 1 ) ); + + } + if ( buffers.colors !== undefined && buffers.colors !== null ) { + + bufferGeometry.setAttribute( 'color', new BufferAttribute( new Float32Array( buffers.colors ), 3 ) ); + + } + if ( buffers.normals !== undefined && buffers.normals !== null ) { + + bufferGeometry.setAttribute( 'normal', new BufferAttribute( new Float32Array( buffers.normals ), 3 ) ); + + } else { + + bufferGeometry.computeVertexNormals(); + + } + if ( buffers.uvs !== undefined && buffers.uvs !== null ) { + + bufferGeometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( buffers.uvs ), 2 ) ); + + } + if ( buffers.skinIndex !== undefined && buffers.skinIndex !== null ) { + + bufferGeometry.setAttribute( 'skinIndex', new BufferAttribute( new Uint16Array( buffers.skinIndex ), 4 ) ); + + } + if ( buffers.skinWeight !== undefined && buffers.skinWeight !== null ) { + + bufferGeometry.setAttribute( 'skinWeight', new BufferAttribute( new Float32Array( buffers.skinWeight ), 4 ) ); + + } + + let meshName = 'none'; + if ( meshPayload.meshName ) meshName = meshPayload.meshName; + + let material; + let multiMaterials = []; + let materialNames = [] + let createMultiMaterial = false; + let materialGroups = []; + if ( meshPayload.materials ) { + + if ( meshPayload.materials.json === undefined || meshPayload.materials.json === null ) { + + if ( meshPayload.materials.materialNames ) materialNames = meshPayload.materials.materialNames; + if ( meshPayload.materials.multiMaterial ) createMultiMaterial = meshPayload.materials.multiMaterial; + if ( meshPayload.materials.materialGroups ) materialGroups = meshPayload.materials.materialGroups; + + } else { + + const materialFromJson = new MaterialLoader().parse( meshPayload.materials.json ); + const loadedMaterials = {}; + loadedMaterials[ materialFromJson.name ] = materialFromJson; + this.materialHandler.addMaterials( loadedMaterials, false ); + material = this.materialHandler.getMaterial( materialFromJson.name ); + + } + + } + if ( ! material ) { + + if ( createMultiMaterial ) { + + for ( let key in materialNames ) { + + let materialName = materialNames[ key ]; + multiMaterials.push( this.materialHandler.getMaterial( materialName ).clone() ); + + } + material = multiMaterials; + for ( let key in materialGroups ) { + + let materialGroup = materialGroups[ key ]; + bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index ); + + } + + } else { + + let materialName = materialNames[ 0 ]; + if ( materialName ) material = this.materialHandler.getMaterial( materialName ).clone(); + + } + + } + const meshes = []; + let mesh; + let callbackOnMeshAlterResult; + let useOrgMesh = true; + const geometryType = meshPayload.params.geometryType ? meshPayload.params.geometryType : 0; + + if ( this.callbacks.onMeshAlter ) { + + callbackOnMeshAlterResult = this.callbacks.onMeshAlter( + { + detail: { + meshName: meshName, + bufferGeometry: bufferGeometry, + material: material, + geometryType: geometryType + } + } + ); + + } + + // here LoadedMeshUserOverride is required to be provided by the callback used to alter the results + if ( callbackOnMeshAlterResult ) { + + if ( callbackOnMeshAlterResult.isDisregardMesh() ) { + + useOrgMesh = false; + + } else if ( callbackOnMeshAlterResult.providesAlteredMeshes() ) { + + for ( const i in callbackOnMeshAlterResult.meshes ) { + + meshes.push( callbackOnMeshAlterResult.meshes[ i ] ); + + } + useOrgMesh = false; + + } + + } + if ( useOrgMesh ) { + + if ( meshPayload.computeBoundingSphere ) bufferGeometry.computeBoundingSphere(); + if ( geometryType === 0 ) { + + mesh = new Mesh( bufferGeometry, material ); + + } else if ( geometryType === 1 ) { + + mesh = new LineSegments( bufferGeometry, material ); + + } else { + + mesh = new Points( bufferGeometry, material ); + + } + mesh.name = meshName; + meshes.push( mesh ); + + } + + let progressMessage = meshName; + let progressNumericalValue = 0; + if ( meshPayload.progress ) { + + if ( meshPayload.progress.numericalValue ) progressNumericalValue = meshPayload.progress.numericalValue; + + } + if ( meshes.length > 0 ) { + + const meshNames = []; + for ( const i in meshes ) { + + mesh = meshes[ i ]; + meshNames[ i ] = mesh.name; + + } + progressMessage += ': Adding mesh(es) (' + meshNames.length + ': ' + meshNames + ') from input mesh: ' + meshName; + progressMessage += ' (' + ( progressNumericalValue * 100 ).toFixed( 2 ) + '%)'; + + } else { + + progressMessage += ': Not adding mesh: ' + meshName; + progressMessage += ' (' + ( progressNumericalValue * 100 ).toFixed( 2 ) + '%)'; + + } + if ( this.callbacks.onProgress ) { + + this.callbacks.onProgress( 'progress', progressMessage, progressNumericalValue ); + + } + return meshes; + + } + +} + +/** + * Object to return by callback onMeshAlter. Used to disregard a certain mesh or to return one to many meshes. + * @class + * + * @param {boolean} disregardMesh=false Tell implementation to completely disregard this mesh + * @param {boolean} disregardMesh=false Tell implementation that mesh(es) have been altered or added + */ +class LoadedMeshUserOverride { + + constructor ( disregardMesh, alteredMesh ) { + + this.disregardMesh = disregardMesh === true; + this.alteredMesh = alteredMesh === true; + this.meshes = []; + + } + + /** + * Add a mesh created within callback. + * + * @param {Mesh} mesh + */ + addMesh ( mesh ) { + + this.meshes.push( mesh ); + this.alteredMesh = true; + + } + + /** + * Answers if mesh shall be disregarded completely. + * + * @returns {boolean} + */ + isDisregardMesh () { + + return this.disregardMesh; + + } + + /** + * Answers if new mesh(es) were created. + * + * @returns {boolean} + */ + providesAlteredMeshes () { + + return this.alteredMesh; + + } +} + +export { + MeshReceiver, + LoadedMeshUserOverride +}; diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts new file mode 100644 index 00000000000000..0f38bec6622ffd --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts @@ -0,0 +1,50 @@ +export class TransferableUtils { + static walkMesh(rootNode: Object3D, callback: Function): void; + static packageBufferGeometry(bufferGeometry: BufferGeometry, id: string, meshName: string, geometryType: number, materialNames?: string[]): MeshMessageStructure; +} +export class MeshMessageStructure { + static cloneMessageStructure(input: object | MeshMessageStructure): MeshMessageStructure; + static copyTypedArray(arrayIn: ArrayBuffer, arrayOut: ArrayBuffer): void; + constructor(cmd: string, id: string, meshName: string); + main: { + cmd: string; + type: string; + id: string; + meshName: string; + progress: { + numericalValue: number; + }; + params: { + geometryType: number; + }; + materials: { + multiMaterial: boolean; + materialNames: string[]; + materialGroups: object[]; + }; + buffers: { + vertices: ArrayBuffer; + indices: ArrayBuffer; + colors: ArrayBuffer; + normals: ArrayBuffer; + uvs: ArrayBuffer; + skinIndex: ArrayBuffer; + skinWeight: ArrayBuffer; + }; + }; + transferables: { + vertex: ArrayBuffer[]; + index: ArrayBuffer[]; + color: ArrayBuffer[]; + normal: ArrayBuffer[]; + uv: ArrayBuffer[]; + skinIndex: ArrayBuffer[]; + skinWeight: ArrayBuffer[]; + }; + postMessage(postMessageImpl: object): void; +} +export class ObjectManipulator { + static applyProperties(objToAlter: any, params: any, forceCreation: boolean): void; +} +import { Object3D } from "../../../../../build/three.module.js"; +import { BufferGeometry } from "../../../../../build/three.module.js"; diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js new file mode 100644 index 00000000000000..52bebd0f65b6e8 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js @@ -0,0 +1,344 @@ +/** + * @author Kai Salmen / https://kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader + */ + +import { + BufferGeometry, + Object3D +} from "../../../../../build/three.module.js"; + +/** + * Define a fixed structure that is used to ship data in between main and workers. + */ +class MeshMessageStructure { + + /** + * Creates a new {@link MeshMessageStructure}. + * + * @param {string} cmd + * @param {string} id + * @param {string} meshName + */ + constructor( cmd, id, meshName ) { + this.main = { + cmd: cmd, + type: 'mesh', + id: id, + meshName: meshName, + progress: { + numericalValue: 0 + }, + params: { + // 0: mesh, 1: line, 2: point + geometryType: 0 + }, + materials: { + /** @type {string|null} */ + json: null, + multiMaterial: false, + /** @type {string[]} */ + materialNames: [], + /** @type {object[]} */ + materialGroups: [] + }, + buffers: { + /** @type {ArrayBuffer} */ + vertices: null, + /** @type {ArrayBuffer} */ + indices: null, + /** @type {ArrayBuffer} */ + colors: null, + /** @type {ArrayBuffer} */ + normals: null, + /** @type {ArrayBuffer} */ + uvs: null, + /** @type {ArrayBuffer} */ + skinIndex: null, + /** @type {ArrayBuffer} */ + skinWeight: null + } + }; + this.transferables = { + /** @type {ArrayBuffer[]} */ + vertex: null, + /** @type {ArrayBuffer[]} */ + index: null, + /** @type {ArrayBuffer[]} */ + color: null, + /** @type {ArrayBuffer[]} */ + normal: null, + /** @type {ArrayBuffer[]} */ + uv: null, + /** @type {ArrayBuffer[]} */ + skinIndex: null, + /** @type {ArrayBuffer[]} */ + skinWeight: null + }; + + } + + /** + * Clone the input which can be a complete {@link MeshMessageStructure} or a config following the structure to a + * {@link MeshMessageStructure}. + * + * @param {object|MeshMessageStructure} input + * @return {MeshMessageStructure} + */ + static cloneMessageStructure( input ) { + let output = new MeshMessageStructure( input.main.cmd, input.main.meshName ); + output.main.type = input.main.type; + output.main.progress.numericalValue = input.main.progress.numericalValue; + output.main.params.geometryType = input.main.params.geometryType; + output.main.materials.multiMaterial = input.main.materials.multiMaterial; + output.main.materials.materialNames = input.main.materials.materialNames; + output.main.materials.materialGroups = input.main.materials.materialGroups; + + if ( input.main.buffers.vertices !== null ) { + + output.main.buffers.vertices = input.main.buffers.vertices; + let arrayOut = new Float32Array( input.main.buffers.vertices.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.vertices, arrayOut ) + output.transferables.vertex = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.indices !== null ) { + + output.main.buffers.indices = input.main.buffers.indices; + let arrayOut = new Uint32Array( input.main.buffers.indices ); + MeshMessageStructure.copyTypedArray( input.main.buffers.indices, arrayOut ); + output.transferables.index = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.colors !== null ) { + + output.main.buffers.colors = input.main.buffers.colors; + let arrayOut = new Float32Array( input.main.buffers.colors.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.colors, arrayOut ) + output.transferables.color = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.normals !== null ) { + + output.main.buffers.normals = input.main.buffers.normals; + let arrayOut = new Float32Array( input.main.buffers.normals.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.normals, arrayOut ) + output.transferables.normal = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.uvs !== null ) { + + output.main.buffers.uvs = input.main.buffers.uvs; + let arrayOut = new Float32Array( input.main.buffers.uvs.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.uvs, arrayOut ) + output.transferables.uv = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.skinIndex !== null ) { + + output.main.buffers.skinIndex = input.main.buffers.skinIndex; + let arrayOut = new Float32Array( input.main.buffers.skinIndex.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.skinIndex, arrayOut ) + output.transferables.skinIndex = [ arrayOut.buffer ]; + + } + if ( input.main.buffers.skinWeight !== null ) { + + output.main.buffers.skinWeight = input.main.buffers.skinWeight; + let arrayOut = new Float32Array( input.main.buffers.skinWeight.length ); + MeshMessageStructure.copyTypedArray( input.main.buffers.skinWeight, arrayOut ) + output.transferables.skinWeight = [ arrayOut.buffer ]; + + } + return output; + + } + + /** + * Copies all values of input {@link ArrayBuffer} to output {@link ArrayBuffer}. + * @param {ArrayBuffer} arrayIn + * @param {ArrayBuffer} arrayOut + */ + static copyTypedArray ( arrayIn, arrayOut ) { + + for ( let i = 0; i < arrayIn.length; i++ ) arrayOut[ i ] = arrayIn[ i ]; + + } + + /** + * Posts a message by invoking the method on the provided object. + * + * @param {object} postMessageImpl + */ + postMessage( postMessageImpl ) { + + postMessageImpl.postMessage( this.main, this.transferables ); + + } + +} + +/** + * Utility class that helps to transform meshes and especially {@link BufferGeometry} to message with transferables. + * Structure that is used to ship data in between main and workers is defined {@link MeshMessageStructure}. + */ +class TransferableUtils { + + /** + * Walk a mesh and on ever geometry call the callback function. + * + * @param {Object3D} rootNode + * @param {Function} callback + */ + static walkMesh( rootNode, callback ) { + let scope = this; + let _walk_ = function ( object3d ) { + console.info( 'Walking: ' + object3d.name ); + + if ( object3d.hasOwnProperty( 'geometry' ) && object3d[ 'geometry' ] instanceof BufferGeometry ) { + let payload = TransferableUtils.packageBufferGeometry( object3d[ 'geometry' ], rootNode, object3d.name, 0,['TBD'] ); + callback( payload.main, payload.transferables ); + + } + if ( object3d.hasOwnProperty( 'material' ) ) { + + let mat = object3d.material; + if ( mat.hasOwnProperty( 'materials' ) ) { + + let materials = mat.materials; + for ( let name in materials ) { + + if ( materials.hasOwnProperty( name ) ) { + + console.log( materials[ name ] ); + + } + + } + + } else { + + console.log( mat.name ); + + } + + } + }; + rootNode.traverse( _walk_ ); + + } + + + /** + * Package {@link BufferGeometry} into {@link MeshMessageStructure} + * + * @param {BufferGeometry} bufferGeometry + * @param {string} id + * @param {string} meshName + * @param {number} geometryType + * @param {string[]} [materialNames] + * @return {MeshMessageStructure} + */ + static packageBufferGeometry( bufferGeometry, id, meshName, geometryType, materialNames ) { + let vertexBA = bufferGeometry.getAttribute( 'position' ); + let indexBA = bufferGeometry.getIndex(); + let colorBA = bufferGeometry.getAttribute( 'color' ); + let normalBA = bufferGeometry.getAttribute( 'normal' ); + let uvBA = bufferGeometry.getAttribute( 'uv' ); + let skinIndexBA = bufferGeometry.getAttribute( 'skinIndex' ); + let skinWeightBA = bufferGeometry.getAttribute( 'skinWeight' ); + let vertexFA = (vertexBA !== null && vertexBA !== undefined) ? vertexBA.array : null; + let indexUA = (indexBA !== null && indexBA !== undefined) ? indexBA.array : null; + let colorFA = (colorBA !== null && colorBA !== undefined) ? colorBA.array : null; + let normalFA = (normalBA !== null && normalBA !== undefined) ? normalBA.array : null; + let uvFA = (uvBA !== null && uvBA !== undefined) ? uvBA.array : null; + let skinIndexFA = (skinIndexBA !== null && skinIndexBA !== undefined) ? skinIndexBA.array : null; + let skinWeightFA = (skinWeightBA !== null && skinWeightBA !== undefined) ? skinWeightBA.array : null; + + + let payload = new MeshMessageStructure( 'execComplete', id, meshName ); + payload.main.params.geometryType = geometryType; + payload.main.materials.materialNames = materialNames; + if ( vertexFA !== null ) { + + payload.main.buffers.vertices = vertexFA; + payload.transferables.vertex = [ vertexFA.buffer ]; + + } + if ( indexUA !== null ) { + + payload.main.buffers.indices = indexUA; + payload.transferables.index = [ indexUA.buffer ]; + + } + if ( colorFA !== null ) { + + payload.main.buffers.colors = colorFA; + payload.transferables.color = [ colorFA.buffer ]; + + } + if ( normalFA !== null ) { + + payload.main.buffers.normals = normalFA; + payload.transferables.normal = [ normalFA.buffer ]; + + } + if ( uvFA !== null ) { + + payload.main.buffers.uvs = uvFA; + payload.transferables.uv = [ uvFA.buffer ]; + + } + if ( skinIndexFA !== null ) { + + payload.main.buffers.skinIndex = skinIndexFA; + payload.transferables.skinIndex = [ skinIndexFA.buffer ]; + + } + if ( skinWeightFA !== null ) { + + payload.main.buffers.skinWeight = skinWeightFA; + payload.transferables.skinWeight = [ skinWeightFA.buffer ]; + + } + return payload; + } + +} + +class 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 + * @param {boolean} forceCreation Force the creation of a property + */ + static applyProperties ( objToAlter, params, forceCreation ) { + + // fast-fail + if ( objToAlter === undefined || objToAlter === null || params === undefined || params === null ) return; + + let 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 { TransferableUtils, MeshMessageStructure, ObjectManipulator } diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts b/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts new file mode 100644 index 00000000000000..d16628bb9789af --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts @@ -0,0 +1,3 @@ +export namespace WorkerTaskManagerDefaultRouting { + export function comRouting(context: any, message: any, object: any, initFunction: any, executeFunction: any): void; +} diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js b/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js new file mode 100644 index 00000000000000..4b32f5001a977c --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js @@ -0,0 +1,39 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +const WorkerTaskManagerDefaultRouting = { + + comRouting: function ( context, message, object, initFunction, executeFunction ) { + + let payload = message.data; + if ( payload.cmd === 'init' ) { + + if ( object !== undefined && object !== null ) { + + object[ initFunction ]( context, payload.workerId, payload.config ); + } else { + + initFunction( context, payload.workerId, payload.config ); + + } + + } else if ( payload.cmd === 'execute' ) { + + if ( object !== undefined && object !== null ) { + + object[ executeFunction ]( context, payload.workerId, payload.config ); + + } else { + + executeFunction( context, payload.workerId, payload.config ); + + } + + } + + } + +}; + +export { WorkerTaskManagerDefaultRouting } diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js new file mode 100644 index 00000000000000..93c3aa7965b7d7 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js @@ -0,0 +1,46 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { TorusKnotBufferGeometry } from "../../../../../build/three.module.js"; +import { TransferableUtils } from "../utils/TransferableUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; + + +function init ( context, id, config ) { + context.storage = { + whoami: id, + }; + + context.postMessage( { + cmd: "init", + id: id + } ); + +} + +function execute ( context, id, config ) { + + let bufferGeometry = new TorusKnotBufferGeometry( 20, 3, 100, 64 ); + + let vertexBA = bufferGeometry.getAttribute( 'position' ) ; + let vertexArray = vertexBA.array; + for ( let i = 0; i < vertexArray.length; i++ ) { + + vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); + + } + let payload = TransferableUtils.packageBufferGeometry( bufferGeometry, config.id, 'tmProto' + config.id, 2,[ 'defaultPointMaterial' ] ); + + let randArray = new Uint8Array( 3 ); + context.crypto.getRandomValues( randArray ); + payload.main.params.color = { + r: randArray[ 0 ] / 255, + g: randArray[ 1 ] / 255, + b: randArray[ 2 ] / 255 + }; + payload.postMessage( context ); + +} + +self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js new file mode 100644 index 00000000000000..0d99283724718a --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js @@ -0,0 +1,43 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { MeshMessageStructure } from "../utils/TransferableUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; + + +function init ( context, id, config ) { + + context.config = config; + context.postMessage( { + cmd: "init", + id: id + } ); + +} + +function execute ( context, id, config ) { + + let payload = MeshMessageStructure.cloneMessageStructure( context.config ); + let vertexArray = payload.main.buffers.vertices.buffer; + for ( let i = 0; i < vertexArray.length; i++ ) { + + vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); + + } + payload.main.meshName = 'tmProto' + config.id; + payload.main.id = config.id; + payload.main.params.geometryType = 1; + payload.main.materials.materialNames = [ 'defaultLineMaterial' ]; + let randArray = new Uint8Array( 3 ); + context.crypto.getRandomValues( randArray ); + payload.main.params.color = { + r: randArray[ 0 ] / 255, + g: randArray[ 1 ] / 255, + b: randArray[ 2 ] / 255 + }; + payload.postMessage( context ); + +} + +self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js new file mode 100644 index 00000000000000..3a1821127f40e1 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js @@ -0,0 +1,65 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { MaterialLoader } from "../../../../../src/loaders/MaterialLoader.js"; +import { OBJLoader } from "../../OBJLoader.js"; +import { TransferableUtils } from "../utils/TransferableUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; + +const OBJLoaderWorker = { + + init: function ( context, id, config ) { + + context.objLoader = { + loader: null, + buffer: null + } + context.postMessage( { + cmd: "init", + id: id + } ); + if ( config.buffer !== undefined && config.buffer !== null ) context.objLoader.buffer = config.buffer; + + }, + + execute: function ( context, id, config ) { + + context.objLoader.loader = new OBJLoader(); + context.objLoader.loader.objectId = config.id; + const materialLoader = new MaterialLoader(); + let material, materialJson; + let materialsIn = config.params.materials; + let materialsOut = {}; + for ( let materialName in materialsIn ) { + + materialJson = materialsIn[ materialName ]; + if ( materialJson !== undefined && materialJson !== null ) { + + material = materialLoader.parse( materialJson ); +// console.info( 'De-serialized material with name "' + materialName + '" will be added.' ); + materialsOut[ materialName ] = material; + + } + + } + context.objLoader.loader.setMaterials( materialsOut ); + + const enc = new TextDecoder("utf-8"); + let meshes = context.objLoader.loader.parse( enc.decode( context.objLoader.buffer ) ); + for ( let mesh, i = 0; i < meshes.children.length; i ++ ) { + + mesh = meshes.children[ i ]; + let payload = TransferableUtils.packageBufferGeometry( mesh.geometry, config.id, mesh.name + config.id, 0 ); + payload.main.materials.json = mesh.material.toJSON(); + payload.postMessage( context ); + + } + + } + +}; + +self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, OBJLoaderWorker, 'init', 'execute' ), false ); + +export { OBJLoaderWorker }; diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html new file mode 100644 index 00000000000000..2387ea7c2ee0d4 --- /dev/null +++ b/examples/webgl_loader_workertaskmanager.html @@ -0,0 +1,775 @@ + + + + three.js webgl - WorkerTaskManager prototype + + + + + + + +
+ three.js - WorkerTaskManager prototype +
+
+
+ + + + From 631903c050fc289620193eff476a43d5041885dc Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Wed, 10 Feb 2021 22:43:34 +0100 Subject: [PATCH 02/12] webgl_loader_workertaskmanager.html is now only using TransferableUtils in addition to WorkerTaskManager. All other utilities have been removed or transformed into TransferableUtils. TransferableUtils: - Provide simple means to package and reconstruct mesh related data bi-directionally - Contains: TransportBase, DataTransport, GeometryTransport, MaterialsTransport, MaterialUtils, MaterialCloneInstruction, MeshTransport, CodeUtils, ObjectManipulator) - Allow arraybuffer copy if needed (e.g init or geometry re-usage) - Add MaterialStore: Perform all required tasks MaterialHandler did and remove the overhead. WorkerTaskManager: - Fixed transferables being transferred as such Removed MeshReceiver and MaterialHandler. --- .../worker/tmDefaultComRouting.js | 0 .../jsm/loaders/worker/tmModuleExample.js | 63 ++ .../worker/tmModuleExampleNoThree.js | 24 +- examples/jsm/loaders/worker/tmOBJLoader.js | 91 ++ .../workerTaskManager/WorkerTaskManager.d.ts | 85 -- .../workerTaskManager/WorkerTaskManager.js | 20 +- .../comm/worker/defaultRouting.js | 39 + .../shared/MaterialHandler.d.ts | 19 - .../shared/MaterialHandler.js | 273 ----- .../shared/MeshReceiver.d.ts | 25 - .../workerTaskManager/shared/MeshReceiver.js | 327 ------ .../utils/TransferableUtils.d.ts | 50 - .../utils/TransferableUtils.js | 974 ++++++++++++++---- .../worker/tmDefaultComRouting.d.ts | 3 - .../worker/tmModuleExample.js | 46 - .../workerTaskManager/worker/tmOBJLoader.js | 65 -- examples/webgl_loader_workertaskmanager.html | 202 ++-- 17 files changed, 1082 insertions(+), 1224 deletions(-) rename examples/jsm/loaders/{workerTaskManager => }/worker/tmDefaultComRouting.js (100%) create mode 100644 examples/jsm/loaders/worker/tmModuleExample.js rename examples/jsm/loaders/{workerTaskManager => }/worker/tmModuleExampleNoThree.js (52%) create mode 100644 examples/jsm/loaders/worker/tmOBJLoader.js delete mode 100644 examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts create mode 100644 examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js delete mode 100644 examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts delete mode 100644 examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js delete mode 100644 examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts delete mode 100644 examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js delete mode 100644 examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts delete mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts delete mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js delete mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js b/examples/jsm/loaders/worker/tmDefaultComRouting.js similarity index 100% rename from examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.js rename to examples/jsm/loaders/worker/tmDefaultComRouting.js diff --git a/examples/jsm/loaders/worker/tmModuleExample.js b/examples/jsm/loaders/worker/tmModuleExample.js new file mode 100644 index 00000000000000..e32d12832dec6b --- /dev/null +++ b/examples/jsm/loaders/worker/tmModuleExample.js @@ -0,0 +1,63 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { + TorusKnotBufferGeometry, + Color, + MeshPhongMaterial +} from "../../../../build/three.module.js"; +import { + MeshTransport, + MaterialsTransport, + MaterialUtils +} from "../workerTaskManager/utils/TransferableUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; + + +function init ( context, id, config ) { + context.storage = { + whoami: id, + }; + + context.postMessage( { + cmd: "init", + id: id + } ); + +} + +function execute ( context, id, config ) { + + let bufferGeometry = new TorusKnotBufferGeometry( 20, 3, 100, 64 ); + bufferGeometry.name = 'tmProto' + config.id; + + let vertexBA = bufferGeometry.getAttribute( 'position' ) ; + let vertexArray = vertexBA.array; + for ( let i = 0; i < vertexArray.length; i++ ) { + + vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); + + } + + const randArray = new Uint8Array( 3 ); + context.crypto.getRandomValues( randArray ); + const color = new Color(); + color.r = randArray[ 0 ] / 255; + color.g = randArray[ 1 ] / 255; + color.b = randArray[ 2 ] / 255; + const material = new MeshPhongMaterial( { color: color } ); + + const materialsTransport = new MaterialsTransport(); + MaterialUtils.addMaterial( materialsTransport.main.materials, material, 'randomColor' + config.id, false, false ); + materialsTransport.cleanMaterials(); + + new MeshTransport( 'execComplete', config.id ) + .setGeometry( bufferGeometry, 2 ) + .setMaterialsTransport( materialsTransport ) + .package( false ) + .postMessage( context ); + +} + +self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/worker/tmModuleExampleNoThree.js similarity index 52% rename from examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js rename to examples/jsm/loaders/worker/tmModuleExampleNoThree.js index 0d99283724718a..8c0a2c3eff38ee 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js +++ b/examples/jsm/loaders/worker/tmModuleExampleNoThree.js @@ -2,9 +2,8 @@ * @author Kai Salmen / www.kaisalmen.de */ -import { MeshMessageStructure } from "../utils/TransferableUtils.js"; -import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; - +import { GeometryTransport } from "../workerTaskManager/utils/TransferableUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; function init ( context, id, config ) { @@ -18,25 +17,28 @@ function init ( context, id, config ) { function execute ( context, id, config ) { - let payload = MeshMessageStructure.cloneMessageStructure( context.config ); - let vertexArray = payload.main.buffers.vertices.buffer; + const geometry = new GeometryTransport().loadData( context.config ).reconstruct( true ).getBufferGeometry(); + geometry.name = 'tmProto' + config.id; + let vertexArray = geometry.getAttribute( 'position' ).array; for ( let i = 0; i < vertexArray.length; i++ ) { vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); } - payload.main.meshName = 'tmProto' + config.id; - payload.main.id = config.id; - payload.main.params.geometryType = 1; - payload.main.materials.materialNames = [ 'defaultLineMaterial' ]; + + const sender = new GeometryTransport( 'execComplete', config.id ) + .setGeometry( geometry, 1 ) + .package( false ); + let randArray = new Uint8Array( 3 ); context.crypto.getRandomValues( randArray ); - payload.main.params.color = { + sender.main.params.color = { r: randArray[ 0 ] / 255, g: randArray[ 1 ] / 255, b: randArray[ 2 ] / 255 }; - payload.postMessage( context ); + + sender.postMessage( context ); } diff --git a/examples/jsm/loaders/worker/tmOBJLoader.js b/examples/jsm/loaders/worker/tmOBJLoader.js new file mode 100644 index 00000000000000..86fcc047d52728 --- /dev/null +++ b/examples/jsm/loaders/worker/tmOBJLoader.js @@ -0,0 +1,91 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { + TransportBase, + DataTransport, + MaterialsTransport, + MaterialUtils, + GeometryTransport, + MeshTransport, + CodeUtils, +} from "../workerTaskManager/utils/TransferableUtils.js"; +import { OBJLoader } from "../OBJLoader.js"; +import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; + +const OBJLoaderWorker = { + + buildStandardWorkerDependencies: function ( threeJsLocation, objLoaderLocation ) { + return [ + { url: threeJsLocation }, + { code: '\n\n' }, + { code: 'const MaterialLoader = THREE.MaterialLoader;\n' }, + { code: 'const Material = THREE.Material;\n' }, + { code: 'const Texture = THREE.Texture;\n' }, + { code: 'const BufferGeometry = THREE.BufferGeometry;\n' }, + { code: '\n\n' }, + { url: objLoaderLocation }, + { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, + { code: '\n\n' }, + { code: CodeUtils.serializeClass( TransportBase ) }, + { code: CodeUtils.serializeClass( DataTransport ) }, + { code: CodeUtils.serializeClass( MaterialsTransport ) }, + { code: CodeUtils.serializeClass( MaterialUtils ) }, + { code: CodeUtils.serializeClass( GeometryTransport ) }, + { code: CodeUtils.serializeClass( MeshTransport ) } + ] + }, + + init: function ( context, id, config ) { + + const materialsTransport = new MaterialsTransport().loadData( config ); + context.objLoader = { + loader: null, + buffer: null, + materials: materialsTransport.getMaterials() + } + + const buffer = materialsTransport.getBuffer( 'data' ) + if ( buffer !== undefined && buffer !== null ) context.objLoader.buffer = buffer; + + context.postMessage( { + cmd: "init", + id: id + } ); + }, + + execute: function ( context, id, config ) { + + context.objLoader.loader = new OBJLoader(); + context.objLoader.loader.objectId = config.id; + context.objLoader.loader.setMaterials( context.objLoader.materials ); + + const enc = new TextDecoder("utf-8"); + let meshes = context.objLoader.loader.parse( enc.decode( context.objLoader.buffer ) ); + for ( let mesh, i = 0; i < meshes.children.length; i ++ ) { + + mesh = meshes.children[ i ]; + mesh.name = mesh.name + config.id; + + const materialsTransport = new MaterialsTransport(); + const material = mesh.material; + MaterialUtils.addMaterial( materialsTransport.main.materials, material, material.name, false, false ); + new MeshTransport( 'assetAvailable', config.id ) + .setMesh( mesh, 0 ) + .setMaterialsTransport( materialsTransport ) + .package( false ) + .postMessage( context ); + + } + + // signal complete + new TransportBase( 'execComplete' ).postMessage( context ); + + } + +}; + +self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, OBJLoaderWorker, 'init', 'execute' ), false ); + +export { OBJLoaderWorker }; diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts deleted file mode 100644 index 6967a38393db73..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -export class WorkerTaskManager { - constructor(maxParallelExecutions?: number); - taskTypes: Map; - verbose: boolean; - maxParallelExecutions: number; - actualExecutionCount: number; - storedExecutions: StoredExecution[]; - teardown: boolean; - setVerbose(verbose: boolean): WorkerTaskManager; - setMaxParallelExecutions(maxParallelExecutions: number): WorkerTaskManager; - getMaxParallelExecutions(): number; - supportsTaskType(taskType: string): boolean; - 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; - _depleteExecutions(): Promise; - dispose(): WorkerTaskManager; -} -declare class WorkerTypeDefinition { - constructor(taskType: string, maximumCount: number, fallback: boolean, verbose?: boolean); - taskType: string; - fallback: boolean; - verbose: boolean; - initialised: boolean; - functions: { - init: Function; - execute: Function; - comRouting: Function; - dependencies: { - descriptions: any[]; - code: string[]; - }; - workerModuleUrl: URL; - }; - workers: { - code: string[]; - 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(): () => []; - createWorkers(): Promise; - createWorkerModules(): Promise; - initWorkers(config: object, transferables: any): Promise; - getAvailableTask(): TaskWorker | MockedTaskWorker | undefined; - hasTask(): boolean; - returnAvailableTask(taskWorker: TaskWorker | MockedTaskWorker): void; - dispose(): void; -} -declare class StoredExecution { - constructor(taskType: string, config: object, assetAvailableFunction: Function, resolve: Function, reject: Function, transferables?: Transferable[]); - taskType: string; - config: any; - assetAvailableFunction: Function; - resolve: Function; - reject: Function; - transferables: Transferable[]; -} -declare class TaskWorker extends Worker { - constructor(id: number, aURL: string, options?: object); - id: number; - getId(): number; -} -declare class MockedTaskWorker { - constructor(id: number, initFunction: Function, executeFunction: Function); - id: number; - functions: { - init: Function; - execute: Function; - }; - getId(): number; - postMessage(message: string, transfer?: Transferable[]): void; - terminate(): void; -} -export {}; diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js index 69adf36582e4dc..d5662199754fde 100644 --- a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js @@ -4,7 +4,7 @@ */ import { FileLoader } from "../../../../build/three.module.js"; -import { WorkerTaskManagerDefaultRouting } from "./worker/tmDefaultComRouting.js"; +import { WorkerTaskManagerDefaultRouting } from "./comm/worker/defaultRouting.js"; /** * Register one to many tasks type to the WorkerTaskManager. Then init and enqueue a worker based execution by passing @@ -134,7 +134,7 @@ class WorkerTaskManager { * * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. - * @param {Object} [transferables] Any optional {@link ArrayBuffer} encapsulated in object.. + * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. */ async initTaskType ( taskType, config, transferables ) { @@ -148,7 +148,7 @@ class WorkerTaskManager { await workerTypeDefinition.createWorkerModules() .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) - .then( workerTypeDefinition.status.initComplete ) + .then( y => workerTypeDefinition.status.initComplete = true ) .catch( x => console.error( x ) ); } else { @@ -156,7 +156,7 @@ class WorkerTaskManager { await workerTypeDefinition.loadDependencies() .then( code => workerTypeDefinition.createWorkers() ) .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) - .then( workerTypeDefinition.status.initComplete ) + .then( y => workerTypeDefinition.status.initComplete = true ) .catch( x => console.error( x ) ); } @@ -192,7 +192,7 @@ class WorkerTaskManager { * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. * @param {Function} assetAvailableFunction Invoke this function if an asset become intermediately available - * @param {Object} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. + * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. * @return {Promise} */ async enqueueForExecution ( taskType, config, assetAvailableFunction, transferables ) { @@ -493,7 +493,7 @@ class WorkerTypeDefinition { * Initialises all workers with common configuration data. * * @param {object} config - * @param {Object} transferables + * @param {Transferable[]} transferables */ async initWorkers ( config, transferables ) { @@ -505,12 +505,12 @@ class WorkerTypeDefinition { taskWorker.onmessage = resolveWorker; taskWorker.onerror = rejectWorker; - // ensure all transferables are copies to all workers on int! + // ensure all transferables are copies to all workers on init! let transferablesToWorker; if ( transferables ) { - transferablesToWorker = {}; - for ( let [ key, transferable ] of Object.entries( transferables ) ) { - transferablesToWorker[ key ] = transferable !== null ? transferable.slice( 0 ) : null; + transferablesToWorker = []; + for ( let i = 0; i < transferables.length; i++ ) { + transferablesToWorker.push( transferables[ i ].slice( 0 ) ); } } diff --git a/examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js b/examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js new file mode 100644 index 00000000000000..4b32f5001a977c --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js @@ -0,0 +1,39 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +const WorkerTaskManagerDefaultRouting = { + + comRouting: function ( context, message, object, initFunction, executeFunction ) { + + let payload = message.data; + if ( payload.cmd === 'init' ) { + + if ( object !== undefined && object !== null ) { + + object[ initFunction ]( context, payload.workerId, payload.config ); + } else { + + initFunction( context, payload.workerId, payload.config ); + + } + + } else if ( payload.cmd === 'execute' ) { + + if ( object !== undefined && object !== null ) { + + object[ executeFunction ]( context, payload.workerId, payload.config ); + + } else { + + executeFunction( context, payload.workerId, payload.config ); + + } + + } + + } + +}; + +export { WorkerTaskManagerDefaultRouting } diff --git a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts deleted file mode 100644 index 96ef5e3f12509b..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -export class MaterialHandler { - logging: { - enabled: boolean; - debug: boolean; - }; - callbacks: { - onLoadMaterials: any; - }; - materials: {}; - setLogging(enabled: boolean, debug: boolean): void; - _setCallbacks(onLoadMaterials: any): void; - createDefaultMaterials(overrideExisting: any): void; - addPayloadMaterials(materialPayload: any): any; - addMaterials(materials: any, overrideExisting: any, newMaterials: any): any; - getMaterials(): any; - getMaterial(materialName: string): any; - getMaterialsJSON(): any; - clearMaterials(): void; -} diff --git a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js b/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js deleted file mode 100644 index e8a9d4f233f74e..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/shared/MaterialHandler.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * @author Kai Salmen / https://kaisalmen.de - * Development repository: https://github.com/kaisalmen/WWOBJLoader - */ - -import { - LineBasicMaterial, - MaterialLoader, - MeshStandardMaterial, - PointsMaterial, - VertexColors -} from '../../../../../build/three.module.js'; - - -class MaterialHandler { - - constructor() { - - this.logging = { - enabled: false, - debug: false - }; - - this.callbacks = { - onLoadMaterials: null - }; - this.materials = {}; - - } - - /** - * 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 ( enabled, debug ) { - - this.logging.enabled = enabled === true; - this.logging.debug = debug === true; - - } - - _setCallbacks ( onLoadMaterials ) { - - if ( onLoadMaterials !== undefined && onLoadMaterials !== null && onLoadMaterials instanceof Function ) { - - this.callbacks.onLoadMaterials = onLoadMaterials; - - } - - } - - /** - * Creates default materials and adds them to the materials object. - * - * @param overrideExisting boolean Override existing material - */ - createDefaultMaterials ( overrideExisting ) { - - const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); - defaultMaterial.name = 'defaultMaterial'; - - const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); - defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; - defaultVertexColorMaterial.vertexColors = VertexColors; - - const defaultLineMaterial = new LineBasicMaterial(); - defaultLineMaterial.name = 'defaultLineMaterial'; - - const defaultPointMaterial = new PointsMaterial( { size: 0.1 } ); - defaultPointMaterial.name = 'defaultPointMaterial'; - - const runtimeMaterials = {}; - runtimeMaterials[ defaultMaterial.name ] = defaultMaterial; - runtimeMaterials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial; - runtimeMaterials[ defaultLineMaterial.name ] = defaultLineMaterial; - runtimeMaterials[ defaultPointMaterial.name ] = defaultPointMaterial; - - this.addMaterials( runtimeMaterials, overrideExisting ); - - } - - /** - * Updates the materials with contained material objects (sync) or from alteration instructions (async). - * - * @param {Object} materialPayload Material update instructions - * @returns {Object} Map of {@link Material} - */ - addPayloadMaterials ( materialPayload ) { - - let material, materialName; - const materialCloneInstructions = materialPayload.materials.materialCloneInstructions; - let newMaterials = {}; - - if ( materialCloneInstructions !== undefined && materialCloneInstructions !== null ) { - - let materialNameOrg = materialCloneInstructions.materialNameOrg; - materialNameOrg = ( materialNameOrg !== undefined && materialNameOrg !== null ) ? materialNameOrg : ''; - const materialOrg = this.materials[ materialNameOrg ]; - if ( materialOrg ) { - - material = materialOrg.clone(); - - materialName = materialCloneInstructions.materialName; - material.name = materialName; - - Object.assign( material, materialCloneInstructions.materialProperties ); - - this.materials[ materialName ] = material; - newMaterials[ materialName ] = material; - - } else { - - if ( this.logging.enabled ) { - - console.info( 'Requested material "' + materialNameOrg + '" is not available!' ); - - } - - } - - } - - 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; - - } - - } - - } - materials = materialPayload.materials.runtimeMaterials; - newMaterials = this.addMaterials( materials, true, newMaterials ); - - return newMaterials; - - } - - /** - * Set materials loaded by any supplier of an Array of {@link Material}. - * - * @param materials Object with named {@link Material} - * @param forceOverrideExisting boolean Override existing material - * @param newMaterials [Object] with named {@link Material} - */ - addMaterials ( materials, forceOverrideExisting, newMaterials ) { - - if ( newMaterials === undefined || newMaterials === null ) { - - newMaterials = {}; - - } - if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) { - - let material; - let existingMaterial; - let force; - for ( const materialName in materials ) { - - material = materials[ materialName ]; - force = forceOverrideExisting === true; - if ( ! force ) { - - existingMaterial = this.materials[ materialName ]; - if ( existingMaterial ) { - - if ( existingMaterial.uuid !== material.uuid ) { - - console.log( 'Same material name "' + material.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']' ); - } - - } else { - - this.materials[ materialName ] = material; - - } - - } else { - - this.materials[ materialName ] = material; - newMaterials[ materialName ] = material; - - } - if ( this.logging.enabled && this.logging.debug ) { - - console.info( 'Material with name "' + materialName + '" was added.' ); - - } - - } - - } - - if ( this.callbacks.onLoadMaterials ) { - - this.callbacks.onLoadMaterials( newMaterials ); - - } - return newMaterials; - - } - - /** - * Returns the mapping object of material name and corresponding material. - * - * @returns {Object} Map of {@link Material} - */ - getMaterials () { - - return this.materials; - - } - - /** - * - * @param {String} materialName - * @returns {Material} - */ - getMaterial ( materialName ) { - - return this.materials[ materialName ]; - - } - - /** - * Returns the mapping object of material name and corresponding jsonified material. - * - * @returns {Object} Map of Materials in JSON representation - */ - getMaterialsJSON () { - - const materialsJSON = {}; - let material; - for ( const materialName in this.materials ) { - - material = this.materials[ materialName ]; - materialsJSON[ materialName ] = material.toJSON(); - - } - - return materialsJSON; - - } - - /** - * Removes all materials - */ - clearMaterials () { - - this.materials = {}; - - } - -} - -export { MaterialHandler }; diff --git a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts deleted file mode 100644 index 8357e46e256fb4..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -export class MeshReceiver { - constructor(materialHandler: any); - logging: { - enabled: boolean; - debug: boolean; - }; - callbacks: { - onProgress: any; - onMeshAlter: any; - }; - materialHandler: any; - setLogging(enabled: boolean, debug: boolean): void; - private _setCallbacks; - buildMeshes(meshPayload: any): Mesh[]; -} -export class LoadedMeshUserOverride { - constructor(disregardMesh: any, alteredMesh: any); - disregardMesh: boolean; - alteredMesh: boolean; - meshes: any[]; - addMesh(mesh: Mesh): void; - isDisregardMesh(): boolean; - providesAlteredMeshes(): boolean; -} -import { Mesh } from "../../../../../build/three.module.js"; diff --git a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js b/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js deleted file mode 100644 index b57ac378a31a0f..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/shared/MeshReceiver.js +++ /dev/null @@ -1,327 +0,0 @@ -/** - * @author Kai Salmen / https://kaisalmen.de - * Development repository: https://github.com/kaisalmen/WWOBJLoader - */ - -import { - BufferAttribute, - BufferGeometry, - LineSegments, - Mesh, - Points, - MaterialLoader -} from '../../../../../build/three.module.js'; - - -/** - * - * @param {MaterialHandler} materialHandler - * @constructor - */ -class MeshReceiver { - - constructor( materialHandler ) { - - this.logging = { - enabled: false, - debug: false - }; - - this.callbacks = { - onProgress: null, - onMeshAlter: null - }; - this.materialHandler = materialHandler; - - } - - /** - * 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 ( enabled, debug ) { - - this.logging.enabled = enabled === true; - this.logging.debug = debug === true; - - } - - /** - * - * @param {Function} onProgress - * @param {Function} onMeshAlter - * @private - */ - _setCallbacks ( onProgress, onMeshAlter ) { - - if ( onProgress !== null && onProgress !== undefined && onProgress instanceof Function ) { - - this.callbacks.onProgress = onProgress; - - } - if ( onMeshAlter !== null && onMeshAlter !== undefined && onMeshAlter instanceof Function ) { - - this.callbacks.onMeshAlter = onMeshAlter; - - } - - } - - /** - * Builds one or multiple meshes from the data described in the payload (buffers, params, material info). - * - * @param {Object} meshPayload Raw mesh description (buffers, params, materials) used to build one to many meshes. - * @returns {Mesh[]} mesh Array of {@link Mesh} - */ - buildMeshes ( meshPayload ) { - - const buffers = meshPayload.buffers; - const bufferGeometry = new BufferGeometry(); - if ( buffers.vertices !== undefined && buffers.vertices !== null ) { - - bufferGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( buffers.vertices ), 3 ) ); - - } - if ( buffers.indices !== undefined && buffers.indices !== null ) { - - bufferGeometry.setIndex( new BufferAttribute( new Uint32Array( buffers.indices ), 1 ) ); - - } - if ( buffers.colors !== undefined && buffers.colors !== null ) { - - bufferGeometry.setAttribute( 'color', new BufferAttribute( new Float32Array( buffers.colors ), 3 ) ); - - } - if ( buffers.normals !== undefined && buffers.normals !== null ) { - - bufferGeometry.setAttribute( 'normal', new BufferAttribute( new Float32Array( buffers.normals ), 3 ) ); - - } else { - - bufferGeometry.computeVertexNormals(); - - } - if ( buffers.uvs !== undefined && buffers.uvs !== null ) { - - bufferGeometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( buffers.uvs ), 2 ) ); - - } - if ( buffers.skinIndex !== undefined && buffers.skinIndex !== null ) { - - bufferGeometry.setAttribute( 'skinIndex', new BufferAttribute( new Uint16Array( buffers.skinIndex ), 4 ) ); - - } - if ( buffers.skinWeight !== undefined && buffers.skinWeight !== null ) { - - bufferGeometry.setAttribute( 'skinWeight', new BufferAttribute( new Float32Array( buffers.skinWeight ), 4 ) ); - - } - - let meshName = 'none'; - if ( meshPayload.meshName ) meshName = meshPayload.meshName; - - let material; - let multiMaterials = []; - let materialNames = [] - let createMultiMaterial = false; - let materialGroups = []; - if ( meshPayload.materials ) { - - if ( meshPayload.materials.json === undefined || meshPayload.materials.json === null ) { - - if ( meshPayload.materials.materialNames ) materialNames = meshPayload.materials.materialNames; - if ( meshPayload.materials.multiMaterial ) createMultiMaterial = meshPayload.materials.multiMaterial; - if ( meshPayload.materials.materialGroups ) materialGroups = meshPayload.materials.materialGroups; - - } else { - - const materialFromJson = new MaterialLoader().parse( meshPayload.materials.json ); - const loadedMaterials = {}; - loadedMaterials[ materialFromJson.name ] = materialFromJson; - this.materialHandler.addMaterials( loadedMaterials, false ); - material = this.materialHandler.getMaterial( materialFromJson.name ); - - } - - } - if ( ! material ) { - - if ( createMultiMaterial ) { - - for ( let key in materialNames ) { - - let materialName = materialNames[ key ]; - multiMaterials.push( this.materialHandler.getMaterial( materialName ).clone() ); - - } - material = multiMaterials; - for ( let key in materialGroups ) { - - let materialGroup = materialGroups[ key ]; - bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index ); - - } - - } else { - - let materialName = materialNames[ 0 ]; - if ( materialName ) material = this.materialHandler.getMaterial( materialName ).clone(); - - } - - } - const meshes = []; - let mesh; - let callbackOnMeshAlterResult; - let useOrgMesh = true; - const geometryType = meshPayload.params.geometryType ? meshPayload.params.geometryType : 0; - - if ( this.callbacks.onMeshAlter ) { - - callbackOnMeshAlterResult = this.callbacks.onMeshAlter( - { - detail: { - meshName: meshName, - bufferGeometry: bufferGeometry, - material: material, - geometryType: geometryType - } - } - ); - - } - - // here LoadedMeshUserOverride is required to be provided by the callback used to alter the results - if ( callbackOnMeshAlterResult ) { - - if ( callbackOnMeshAlterResult.isDisregardMesh() ) { - - useOrgMesh = false; - - } else if ( callbackOnMeshAlterResult.providesAlteredMeshes() ) { - - for ( const i in callbackOnMeshAlterResult.meshes ) { - - meshes.push( callbackOnMeshAlterResult.meshes[ i ] ); - - } - useOrgMesh = false; - - } - - } - if ( useOrgMesh ) { - - if ( meshPayload.computeBoundingSphere ) bufferGeometry.computeBoundingSphere(); - if ( geometryType === 0 ) { - - mesh = new Mesh( bufferGeometry, material ); - - } else if ( geometryType === 1 ) { - - mesh = new LineSegments( bufferGeometry, material ); - - } else { - - mesh = new Points( bufferGeometry, material ); - - } - mesh.name = meshName; - meshes.push( mesh ); - - } - - let progressMessage = meshName; - let progressNumericalValue = 0; - if ( meshPayload.progress ) { - - if ( meshPayload.progress.numericalValue ) progressNumericalValue = meshPayload.progress.numericalValue; - - } - if ( meshes.length > 0 ) { - - const meshNames = []; - for ( const i in meshes ) { - - mesh = meshes[ i ]; - meshNames[ i ] = mesh.name; - - } - progressMessage += ': Adding mesh(es) (' + meshNames.length + ': ' + meshNames + ') from input mesh: ' + meshName; - progressMessage += ' (' + ( progressNumericalValue * 100 ).toFixed( 2 ) + '%)'; - - } else { - - progressMessage += ': Not adding mesh: ' + meshName; - progressMessage += ' (' + ( progressNumericalValue * 100 ).toFixed( 2 ) + '%)'; - - } - if ( this.callbacks.onProgress ) { - - this.callbacks.onProgress( 'progress', progressMessage, progressNumericalValue ); - - } - return meshes; - - } - -} - -/** - * Object to return by callback onMeshAlter. Used to disregard a certain mesh or to return one to many meshes. - * @class - * - * @param {boolean} disregardMesh=false Tell implementation to completely disregard this mesh - * @param {boolean} disregardMesh=false Tell implementation that mesh(es) have been altered or added - */ -class LoadedMeshUserOverride { - - constructor ( disregardMesh, alteredMesh ) { - - this.disregardMesh = disregardMesh === true; - this.alteredMesh = alteredMesh === true; - this.meshes = []; - - } - - /** - * Add a mesh created within callback. - * - * @param {Mesh} mesh - */ - addMesh ( mesh ) { - - this.meshes.push( mesh ); - this.alteredMesh = true; - - } - - /** - * Answers if mesh shall be disregarded completely. - * - * @returns {boolean} - */ - isDisregardMesh () { - - return this.disregardMesh; - - } - - /** - * Answers if new mesh(es) were created. - * - * @returns {boolean} - */ - providesAlteredMeshes () { - - return this.alteredMesh; - - } -} - -export { - MeshReceiver, - LoadedMeshUserOverride -}; diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts deleted file mode 100644 index 0f38bec6622ffd..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -export class TransferableUtils { - static walkMesh(rootNode: Object3D, callback: Function): void; - static packageBufferGeometry(bufferGeometry: BufferGeometry, id: string, meshName: string, geometryType: number, materialNames?: string[]): MeshMessageStructure; -} -export class MeshMessageStructure { - static cloneMessageStructure(input: object | MeshMessageStructure): MeshMessageStructure; - static copyTypedArray(arrayIn: ArrayBuffer, arrayOut: ArrayBuffer): void; - constructor(cmd: string, id: string, meshName: string); - main: { - cmd: string; - type: string; - id: string; - meshName: string; - progress: { - numericalValue: number; - }; - params: { - geometryType: number; - }; - materials: { - multiMaterial: boolean; - materialNames: string[]; - materialGroups: object[]; - }; - buffers: { - vertices: ArrayBuffer; - indices: ArrayBuffer; - colors: ArrayBuffer; - normals: ArrayBuffer; - uvs: ArrayBuffer; - skinIndex: ArrayBuffer; - skinWeight: ArrayBuffer; - }; - }; - transferables: { - vertex: ArrayBuffer[]; - index: ArrayBuffer[]; - color: ArrayBuffer[]; - normal: ArrayBuffer[]; - uv: ArrayBuffer[]; - skinIndex: ArrayBuffer[]; - skinWeight: ArrayBuffer[]; - }; - postMessage(postMessageImpl: object): void; -} -export class ObjectManipulator { - static applyProperties(objToAlter: any, params: any, forceCreation: boolean): void; -} -import { Object3D } from "../../../../../build/three.module.js"; -import { BufferGeometry } from "../../../../../build/three.module.js"; diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js index 52bebd0f65b6e8..e4d22c32473a95 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js @@ -5,305 +5,871 @@ import { BufferGeometry, - Object3D + BufferAttribute, + Box3, + Sphere, + Texture, + Material, + MeshStandardMaterial, + LineBasicMaterial, + PointsMaterial, + VertexColors, + MaterialLoader } from "../../../../../build/three.module.js"; /** - * Define a fixed structure that is used to ship data in between main and workers. + * Define a base structure that is used to ship data in between main and workers. */ -class MeshMessageStructure { +class TransportBase { /** - * Creates a new {@link MeshMessageStructure}. + * Creates a new {@link TransportBase}. * - * @param {string} cmd - * @param {string} id - * @param {string} meshName + * @param {string} [cmd] + * @param {string} [id] */ - constructor( cmd, id, meshName ) { + constructor( cmd, id ) { + this.main = { - cmd: cmd, - type: 'mesh', - id: id, - meshName: meshName, - progress: { - numericalValue: 0 - }, + cmd: ( cmd !== undefined ) ? cmd : 'unknown', + id: ( id !== undefined ) ? id : 0, + type: 'TransportBase', + /** @type {number} */ + progress: 0, params: { - // 0: mesh, 1: line, 2: point - geometryType: 0 - }, - materials: { - /** @type {string|null} */ - json: null, - multiMaterial: false, - /** @type {string[]} */ - materialNames: [], - /** @type {object[]} */ - materialGroups: [] - }, - buffers: { - /** @type {ArrayBuffer} */ - vertices: null, - /** @type {ArrayBuffer} */ - indices: null, - /** @type {ArrayBuffer} */ - colors: null, - /** @type {ArrayBuffer} */ - normals: null, - /** @type {ArrayBuffer} */ - uvs: null, - /** @type {ArrayBuffer} */ - skinIndex: null, - /** @type {ArrayBuffer} */ - skinWeight: null } }; - this.transferables = { - /** @type {ArrayBuffer[]} */ - vertex: null, - /** @type {ArrayBuffer[]} */ - index: null, - /** @type {ArrayBuffer[]} */ - color: null, - /** @type {ArrayBuffer[]} */ - normal: null, - /** @type {ArrayBuffer[]} */ - uv: null, - /** @type {ArrayBuffer[]} */ - skinIndex: null, - /** @type {ArrayBuffer[]} */ - skinWeight: null - }; + this.transferables = []; + + } + + /** + * + * @param {object} transportObject + * + * @return {TransportBase} + */ + loadData( transportObject ) { + this.main.cmd = transportObject.cmd; + this.main.id = transportObject.id; + this.main.type = 'TransportBase'; + this.setProgress( transportObject.progress ); + this.setParams( transportObject.params ); + return this; + } + + /** + * + * @param {object.} params + * @return {TransportBase} + */ + setParams( params ) { + + if ( params !== null && params !== undefined ) { + this.main.params = params; + } + return this; } + getParams() { + return this.main.params; + } + /** - * Clone the input which can be a complete {@link MeshMessageStructure} or a config following the structure to a - * {@link MeshMessageStructure}. * - * @param {object|MeshMessageStructure} input - * @return {MeshMessageStructure} + * @return {*|{cmd: string, type: string, progress: {numericalValue: number}, params: {}}|{progress: number, cmd: (string|string), id: (string|number), type: string, params: {}}} */ - static cloneMessageStructure( input ) { - let output = new MeshMessageStructure( input.main.cmd, input.main.meshName ); - output.main.type = input.main.type; - output.main.progress.numericalValue = input.main.progress.numericalValue; - output.main.params.geometryType = input.main.params.geometryType; - output.main.materials.multiMaterial = input.main.materials.multiMaterial; - output.main.materials.materialNames = input.main.materials.materialNames; - output.main.materials.materialGroups = input.main.materials.materialGroups; + getMain() { - if ( input.main.buffers.vertices !== null ) { + return this.main; - output.main.buffers.vertices = input.main.buffers.vertices; - let arrayOut = new Float32Array( input.main.buffers.vertices.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.vertices, arrayOut ) - output.transferables.vertex = [ arrayOut.buffer ]; + } + + /** + * + * @return {[]|any[]|*} + */ + getTransferables() { + + return this.transferables; + + } + + /** + * + * @param {number} numericalValue + * + * @return {TransportBase} + */ + setProgress( numericalValue ) { + + this.main.progress = numericalValue; + return this; + + } + + /** + * Posts a message by invoking the method on the provided object. + * + * @param {object} postMessageImpl + * + * @return {TransportBase} + */ + postMessage( postMessageImpl ) { + postMessageImpl.postMessage( this.main, this.transferables ); + return this; + + } +} + +class DataTransport extends TransportBase { + + /** + * Creates a new {@link DataTransport}. + * + * @param {string} [cmd] + * @param {string} [id] + */ + constructor( cmd, id ) { + super( cmd, id ); + this.main.type = 'DataTransport'; + this.main.buffers = {}; + } + + /** + * + * @param {object} transportObject + * + * @return {TransportBase} + */ + loadData( transportObject ) { + super.loadData( transportObject ); + + if ( transportObject.buffers ) { + Object.entries( transportObject.buffers ).forEach( ( [name, buffer] ) => { + this.main.buffers[ name ] = buffer; + } ); } - if ( input.main.buffers.indices !== null ) { + return this; + } + + /** + * + * @param name + * @param buffer + * @return {DataTransport} + */ + addBuffer ( name, buffer ) { + this.main.buffers[ name ] = buffer; + return this; + } + + getBuffer( name ) { + return this.main.buffers[ name]; + } - output.main.buffers.indices = input.main.buffers.indices; - let arrayOut = new Uint32Array( input.main.buffers.indices ); - MeshMessageStructure.copyTypedArray( input.main.buffers.indices, arrayOut ); - output.transferables.index = [ arrayOut.buffer ]; + setParams( params ) { + super.setParams( params ); + return this; + } + /** + * Package all data buffers + * + * @param {boolean} cloneBuffers + * + * @return {DataTransport} + */ + package( cloneBuffers ) { + for ( let buffer of Object.values( this.main.buffers ) ) { + this.addArrayBufferToTransferable( buffer, cloneBuffers ); } - if ( input.main.buffers.colors !== null ) { + return this; + } - output.main.buffers.colors = input.main.buffers.colors; - let arrayOut = new Float32Array( input.main.buffers.colors.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.colors, arrayOut ) - output.transferables.color = [ arrayOut.buffer ]; + /** + * + * @param buffer + * @param cloneBuffer + * + * @return {DataTransport} + */ + addArrayBufferToTransferable( buffer, cloneBuffer ) { + if ( buffer !== null && buffer !== undefined ) { + + const potentialClone = cloneBuffer ? buffer.slice( 0 ) : buffer; + this.transferables.push( potentialClone ); } - if ( input.main.buffers.normals !== null ) { + return this; + } +} - output.main.buffers.normals = input.main.buffers.normals; - let arrayOut = new Float32Array( input.main.buffers.normals.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.normals, arrayOut ) - output.transferables.normal = [ arrayOut.buffer ]; +/** + * Define a structure that is used to ship materials data between main and workers. + */ +class MaterialsTransport extends DataTransport { + + /** + * Creates a new {@link MeshMessageStructure}. + * + * @param {string} [cmd] + * @param {string} [id] + */ + constructor( cmd, id ) { + super( cmd, id ); + this.main.type = 'MaterialsTransport'; + this.main.materials = {}; + this.main.multiMaterials = {}; + this.main.cloneInstructions = {}; + } + + /** + * + * @param {object} transportObject + * + * @return {MaterialsTransport} + */ + loadData( transportObject ) { + super.loadData( transportObject ); + this.main.type = 'MaterialsTransport'; + Object.assign( this.main, transportObject ); + + const materialLoader = new MaterialLoader(); + Object.entries( this.main.materials ).forEach( ( [ name, materialObject ] ) => { + + this.main.materials[ name ] = materialLoader.parse( materialObject ) + + } ); + return this; + } + + _cleanMaterial ( material ) { + Object.entries( material ).forEach( ( [key, value] ) => { + if ( value instanceof Texture || value === null ) { + material[ key ] = undefined; + } + } ); + return material; + } + + /** + * + * @param name + * @param buffer + * @return {DataTransport} + */ + addBuffer( name, buffer ) { + return super.addBuffer( name, buffer ); + } + + setParams( params ) { + super.setParams( params ); + return this; + } + + /** + * + * @param materials + */ + setMaterials ( materials ) { + if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) this.main.materials = materials; + return this; + } + + getMaterials () { + return this.main.materials; + } + + /** + * Removes all textures and null values from all materials + */ + cleanMaterials () { + let clonedMaterials = {}; + let clonedMaterial; + for ( let material of Object.values( this.main.materials ) ) { + + if ( material instanceof Material ) { + + clonedMaterial = material.clone(); + clonedMaterials[ clonedMaterial.name ] = this._cleanMaterial( clonedMaterial ); + + } } - if ( input.main.buffers.uvs !== null ) { + this.setMaterials( clonedMaterials ); + return this; + } + + package ( cloneBuffers) { + + super.package( cloneBuffers ); + + this.main.materials = MaterialUtils.getMaterialsJSON( this.main.materials ); + return this; + + } - output.main.buffers.uvs = input.main.buffers.uvs; - let arrayOut = new Float32Array( input.main.buffers.uvs.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.uvs, arrayOut ) - output.transferables.uv = [ arrayOut.buffer ]; + hasMultiMaterial () { + return ( Object.keys( this.main.multiMaterials ).length > 0 ); + + } + + getSingleMaterial () { + + if ( Object.keys( this.main.materials ).length > 0 ) { + return Object.entries( this.main.materials )[ 0 ][ 1 ]; + } + else { + return new MeshStandardMaterial( { color: 0xFF0000 } ); } - if ( input.main.buffers.skinIndex !== null ) { - output.main.buffers.skinIndex = input.main.buffers.skinIndex; - let arrayOut = new Float32Array( input.main.buffers.skinIndex.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.skinIndex, arrayOut ) - output.transferables.skinIndex = [ arrayOut.buffer ]; + } + + /** + * Updates the materials with contained material objects (sync) or from alteration instructions (async). + * + * @param {Object.} materials + * @param {boolean} log + * + * @return {Material|Material[]} + */ + processMaterialTransport ( materials, log ) { + + Object.entries( this.main.cloneInstructions ).forEach( ( [ materialName, materialCloneInstructions ] ) => { + if ( materialCloneInstructions ) { + + let materialNameOrg = materialCloneInstructions.materialNameOrg; + materialNameOrg = (materialNameOrg !== undefined && materialNameOrg !== null) ? materialNameOrg : ''; + const materialOrg = materials[ materialNameOrg ]; + if ( materialOrg ) { + + let material = materialOrg.clone(); + Object.assign( material, materialCloneInstructions.materialProperties ); + MaterialUtils.addMaterial( materials, material, materialName, true ); + + } + else { + + if ( log ) console.info( 'Requested material "' + materialNameOrg + '" is not available!' ); + + } + + } + + } ); + + let outputMaterial; + if ( this.hasMultiMaterial() ) { + + // multi-material + outputMaterial = []; + Object.entries( this.main.multiMaterials ).forEach( ( [ index, materialName ] ) => { + + outputMaterial[ index ] = materials[ materialName ]; + + } ); } - if ( input.main.buffers.skinWeight !== null ) { + else { - output.main.buffers.skinWeight = input.main.buffers.skinWeight; - let arrayOut = new Float32Array( input.main.buffers.skinWeight.length ); - MeshMessageStructure.copyTypedArray( input.main.buffers.skinWeight, arrayOut ) - output.transferables.skinWeight = [ arrayOut.buffer ]; + const singleMaterial = this.getSingleMaterial(); + if (singleMaterial !== null ) { + outputMaterial = materials[ singleMaterial.name ]; + if ( !outputMaterial ) outputMaterial = singleMaterial; + } } - return output; + return outputMaterial; } +} + +/** + * Define a structure that is used to send geometry data between main and workers. + */ +class GeometryTransport extends TransportBase { /** - * Copies all values of input {@link ArrayBuffer} to output {@link ArrayBuffer}. - * @param {ArrayBuffer} arrayIn - * @param {ArrayBuffer} arrayOut + * Creates a new {@link GeometrySender}. + * + * @param {string} [cmd] + * @param {string} [id] */ - static copyTypedArray ( arrayIn, arrayOut ) { + constructor( cmd, id ) { + super( cmd, id ); + this.main.type = 'GeometryTransport'; + /** + * @type {number} + * 0: mesh, 1: line, 2: point + */ + this.main.geometryType = 0; + /** @type {object} */ + this.main.geometry = {}; + /** @type {BufferGeometry} */ + this.main.bufferGeometry = null; + } - for ( let i = 0; i < arrayIn.length; i++ ) arrayOut[ i ] = arrayIn[ i ]; + /** + * + * @param {object} transportObject + * + * @return {GeometryTransport} + */ + loadData( transportObject ) { + super.loadData( transportObject ); + this.main.type = 'GeometryTransport'; + return this.setGeometry( transportObject.geometry, transportObject.geometryType ); + } + setParams( params ) { + super.setParams( params ); + return this; } /** - * Posts a message by invoking the method on the provided object. + * Only add the {@link BufferGeometry} * - * @param {object} postMessageImpl + * @param {BufferGeometry} geometry + * @param {number} geometryType + * + * @return {GeometryTransport} */ - postMessage( postMessageImpl ) { + setGeometry( geometry, geometryType ) { + this.main.geometry = geometry; + this.main.params.geometryType = geometryType; + if ( geometry instanceof BufferGeometry ) this.main.bufferGeometry = geometry; - postMessageImpl.postMessage( this.main, this.transferables ); + return this; + } + /** + * Package {@link BufferGeometry} + * + * @param {boolean} cloneBuffers + * + * @return {GeometryTransport} + */ + package( cloneBuffers ) { + const vertexBA = this.main.geometry.getAttribute( 'position' ); + const normalBA = this.main.geometry.getAttribute( 'normal' ); + const uvBA = this.main.geometry.getAttribute( 'uv' ); + const colorBA = this.main.geometry.getAttribute( 'color' ); + const skinIndexBA = this.main.geometry.getAttribute( 'skinIndex' ); + const skinWeightBA = this.main.geometry.getAttribute( 'skinWeight' ); + const indexBA = this.main.geometry.getIndex(); + + this.addBufferAttributeToTransferable( vertexBA, cloneBuffers ); + this.addBufferAttributeToTransferable( normalBA, cloneBuffers ); + this.addBufferAttributeToTransferable( uvBA, cloneBuffers ); + this.addBufferAttributeToTransferable( colorBA, cloneBuffers ); + this.addBufferAttributeToTransferable( skinIndexBA, cloneBuffers ); + this.addBufferAttributeToTransferable( skinWeightBA, cloneBuffers ); + + this.addBufferAttributeToTransferable( indexBA, cloneBuffers ); + + return this; } -} + /** + * @param {boolean} cloneBuffers + * + * @return {GeometryTransport} + */ + reconstruct( cloneBuffers ) { + if ( this.main.bufferGeometry instanceof BufferGeometry ) return this; + this.main.bufferGeometry = new BufferGeometry(); -/** - * Utility class that helps to transform meshes and especially {@link BufferGeometry} to message with transferables. - * Structure that is used to ship data in between main and workers is defined {@link MeshMessageStructure}. - */ -class TransferableUtils { + const transferredGeometry = this.main.geometry; + this.assignAttribute( transferredGeometry.attributes.position, 'position', cloneBuffers ); + this.assignAttribute( transferredGeometry.attributes.normal, 'normal', cloneBuffers ); + this.assignAttribute( transferredGeometry.attributes.uv, 'uv', cloneBuffers ); + this.assignAttribute( transferredGeometry.attributes.color, 'color', cloneBuffers ); + this.assignAttribute( transferredGeometry.attributes.skinIndex, 'skinIndex', cloneBuffers ); + this.assignAttribute( transferredGeometry.attributes.skinWeight, 'skinWeight', cloneBuffers ); + + const index = transferredGeometry.index; + if ( index !== null && index !== undefined ) { + + const indexBuffer = cloneBuffers ? index.array.slice( 0 ) : index.array; + this.main.bufferGeometry.setIndex( new BufferAttribute( indexBuffer, index.itemSize, index.normalized ) ); + + } + const boundingBox = transferredGeometry.boundingBox; + if ( boundingBox !== null ) this.main.bufferGeometry.boundingBox = Object.assign( new Box3(), boundingBox ); + + const boundingSphere = transferredGeometry.boundingSphere; + if ( boundingSphere !== null ) this.main.bufferGeometry.boundingSphere = Object.assign( new Sphere(), boundingSphere ); + + this.main.bufferGeometry.uuid = transferredGeometry.uuid; + this.main.bufferGeometry.name = transferredGeometry.name; + this.main.bufferGeometry.type = transferredGeometry.type; + this.main.bufferGeometry.groups = transferredGeometry.groups; + this.main.bufferGeometry.drawRange = transferredGeometry.drawRange; + this.main.bufferGeometry.userData = transferredGeometry.userData; + + return this; + } /** - * Walk a mesh and on ever geometry call the callback function. * - * @param {Object3D} rootNode - * @param {Function} callback + * @return {BufferGeometry|null} */ - static walkMesh( rootNode, callback ) { - let scope = this; - let _walk_ = function ( object3d ) { - console.info( 'Walking: ' + object3d.name ); + getBufferGeometry() { + return this.main.bufferGeometry + } - if ( object3d.hasOwnProperty( 'geometry' ) && object3d[ 'geometry' ] instanceof BufferGeometry ) { - let payload = TransferableUtils.packageBufferGeometry( object3d[ 'geometry' ], rootNode, object3d.name, 0,['TBD'] ); - callback( payload.main, payload.transferables ); + /** + * + * @param input + * @param cloneBuffer + * + * @return {GeometryTransport} + */ + addBufferAttributeToTransferable( input, cloneBuffer ) { + if ( input !== null && input !== undefined ) { - } - if ( object3d.hasOwnProperty( 'material' ) ) { + const arrayBuffer = cloneBuffer ? input.array.slice( 0 ) : input.array; + this.transferables.push( arrayBuffer.buffer ); - let mat = object3d.material; - if ( mat.hasOwnProperty( 'materials' ) ) { + } + return this; + } - let materials = mat.materials; - for ( let name in materials ) { + /** + * + * @param attr + * @param attrName + * @param cloneBuffer + * + * @return {GeometryTransport} + */ + assignAttribute( attr, attrName, cloneBuffer ) { + if ( attr ) { + const arrayBuffer = cloneBuffer ? attr.array.slice( 0 ) : attr.array; + this.main.bufferGeometry.setAttribute( attrName, new BufferAttribute( arrayBuffer, attr.itemSize, attr.normalized ) ); + } + return this; + } + +} - if ( materials.hasOwnProperty( name ) ) { +class MeshTransport extends GeometryTransport { - console.log( materials[ name ] ); + /** + * Creates a new {@link MeshTransport}. + * + * @param {string} [cmd] + * @param {string} [id] + */ + constructor( cmd, id ) { + super( cmd, id ); - } + this.main.type = 'MeshTransport'; + // needs to be added as we cannot inherit from both materials and geometry + this.main.materialsTransport = new MaterialsTransport(); + } - } + /** + * + * @param {object} transportObject + * + * @return {MeshTransport} + */ + loadData( transportObject ) { + super.loadData( transportObject ); + this.main.type = 'MeshTransport'; + this.main.meshName = transportObject.meshName; + this.main.materialsTransport = new MaterialsTransport().loadData( transportObject.materialsTransport.main ); - } else { + return this; + } - console.log( mat.name ); + setParams( params ) { + super.setParams( params ); + return this; + } - } + /** + * Only set the material. + * + * @param {MaterialsTransport} materialsTransport + * + * @return {MeshTransport} + */ + setMaterialsTransport( materialsTransport ) { - } - }; - rootNode.traverse( _walk_ ); + if ( materialsTransport instanceof MaterialsTransport ) this.main.materialsTransport = materialsTransport; + return this; } + /** + * @return {MaterialsTransport} + */ + getMaterialsTransport() { + + return this.main.materialsTransport; + + } /** - * Package {@link BufferGeometry} into {@link MeshMessageStructure} * - * @param {BufferGeometry} bufferGeometry - * @param {string} id - * @param {string} meshName + * @param {Mesh} mesh * @param {number} geometryType - * @param {string[]} [materialNames] - * @return {MeshMessageStructure} - */ - static packageBufferGeometry( bufferGeometry, id, meshName, geometryType, materialNames ) { - let vertexBA = bufferGeometry.getAttribute( 'position' ); - let indexBA = bufferGeometry.getIndex(); - let colorBA = bufferGeometry.getAttribute( 'color' ); - let normalBA = bufferGeometry.getAttribute( 'normal' ); - let uvBA = bufferGeometry.getAttribute( 'uv' ); - let skinIndexBA = bufferGeometry.getAttribute( 'skinIndex' ); - let skinWeightBA = bufferGeometry.getAttribute( 'skinWeight' ); - let vertexFA = (vertexBA !== null && vertexBA !== undefined) ? vertexBA.array : null; - let indexUA = (indexBA !== null && indexBA !== undefined) ? indexBA.array : null; - let colorFA = (colorBA !== null && colorBA !== undefined) ? colorBA.array : null; - let normalFA = (normalBA !== null && normalBA !== undefined) ? normalBA.array : null; - let uvFA = (uvBA !== null && uvBA !== undefined) ? uvBA.array : null; - let skinIndexFA = (skinIndexBA !== null && skinIndexBA !== undefined) ? skinIndexBA.array : null; - let skinWeightFA = (skinWeightBA !== null && skinWeightBA !== undefined) ? skinWeightBA.array : null; - - - let payload = new MeshMessageStructure( 'execComplete', id, meshName ); - payload.main.params.geometryType = geometryType; - payload.main.materials.materialNames = materialNames; - if ( vertexFA !== null ) { - - payload.main.buffers.vertices = vertexFA; - payload.transferables.vertex = [ vertexFA.buffer ]; + * + * @return {MeshTransport} + */ + setMesh( mesh, geometryType ) { + this.main.meshName = mesh.name; + super.setGeometry( mesh.geometry, geometryType ); + + return this; + } + + /** + * Package {@link Mesh} + * + * @param {boolean} cloneBuffers + * + * @return {MeshTransport} + */ + package( cloneBuffers ) { + super.package( cloneBuffers ); + if ( this.main.materialsTransport !== null ) this.main.materialsTransport.package(); + + return this; + } + + /** + * @param {boolean} cloneBuffers + * + * @return {MeshTransport} + */ + reconstruct( cloneBuffers ) { + super.reconstruct( cloneBuffers ); + + // so far nothing needs to be done for material + + return this; + } + +} + +class MaterialCloneInstruction { + + /** + * + * @param {string} materialNameOrg + * @param {string} newMaterialName + * @param {boolean} haveVertexColors + * @param {number} smoothingGroup + */ + constructor ( materialNameOrg, newMaterialName, haveVertexColors, smoothingGroup ) { + this.materialNameOrg = materialNameOrg; + this.materialProperties = { + name: newMaterialName, + vertexColors: haveVertexColors ? 2 : 0, + flatShading: smoothingGroup === 0 + }; + } + +} + +class MaterialUtils { + + /** + * + * @param {object} materialsObject + * @param {Material|MaterialCloneInstruction} material + * @param {string} materialName + * @param {boolean} force + * @param {boolena} [log] + */ + static addMaterial( materialsObject, material, materialName, force, log ) { + let existingMaterial; + // ensure materialName is set + material.name = materialName; + if ( ! force ) { + + existingMaterial = materialsObject[ materialName ]; + if ( existingMaterial ) { + + if ( existingMaterial.uuid !== existingMaterial.uuid ) { + + if ( log ) console.log( 'Same material name "' + existingMaterial.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']' ); + + } + + } else { + + materialsObject[ materialName ] = material; + if ( log ) console.info( 'Material with name "' + materialName + '" was added.' ); + + } + + } else { + + materialsObject[ materialName ] = material; + if ( log ) console.info( 'Material with name "' + materialName + '" was forcefully overridden.' ); } - if ( indexUA !== null ) { + } - payload.main.buffers.indices = indexUA; - payload.transferables.index = [ indexUA.buffer ]; + /** + * Returns the mapping object of material name and corresponding jsonified material. + * + * @param {object.} + * @returns {Object} Map of Materials in JSON representation + */ + static getMaterialsJSON ( materialsObject ) { + + const materialsJSON = {}; + let material; + for ( const materialName in materialsObject ) { + + material = materialsObject[ materialName ]; + if ( material instanceof Material ) materialsJSON[ materialName ] = material.toJSON(); } - if ( colorFA !== null ) { + return materialsJSON; + + } + +} + +class MaterialStore { + + constructor( createDefaultMaterials ) { - payload.main.buffers.colors = colorFA; - payload.transferables.color = [ colorFA.buffer ]; + this.materials = {}; + if ( createDefaultMaterials ) { + + const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultMaterial.name = 'defaultMaterial'; + + const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; + defaultVertexColorMaterial.vertexColors = VertexColors; + + const defaultLineMaterial = new LineBasicMaterial(); + defaultLineMaterial.name = 'defaultLineMaterial'; + + const defaultPointMaterial = new PointsMaterial( { size: 0.1 } ); + defaultPointMaterial.name = 'defaultPointMaterial'; + + this.materials[ defaultMaterial.name ] = defaultMaterial; + this.materials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial; + this.materials[ defaultLineMaterial.name ] = defaultLineMaterial; + this.materials[ defaultPointMaterial.name ] = defaultPointMaterial; } - if ( normalFA !== null ) { - payload.main.buffers.normals = normalFA; - payload.transferables.normal = [ normalFA.buffer ]; + } + + /** + * Set materials loaded by any supplier of an Array of {@link Material}. + * + * @param newMaterials Object with named {@link Material} + * @param forceOverrideExisting boolean Override existing material + */ + addMaterials ( newMaterials, forceOverrideExisting ) { + + if ( newMaterials === undefined || newMaterials === null ) newMaterials = {}; + if ( Object.keys( newMaterials ).length > 0 ) { + + let material; + for ( const materialName in newMaterials ) { + + material = newMaterials[ materialName ]; + MaterialUtils.addMaterial( this.materials, material, materialName, forceOverrideExisting === true ); + + } } - if ( uvFA !== null ) { - payload.main.buffers.uvs = uvFA; - payload.transferables.uv = [ uvFA.buffer ]; + } + + /** + * Returns the mapping object of material name and corresponding material. + * + * @returns {Object} Map of {@link Material} + */ + getMaterials () { + + return this.materials; + + } + + /** + * + * @param {String} materialName + * @returns {Material} + */ + getMaterial ( materialName ) { + + return this.materials[ materialName ]; + + } + + /** + * Removes all materials + */ + clearMaterials () { + + this.materials = {}; + } + +} + +class CodeUtils { + + static serializePrototype ( targetClass, targetPrototype, fullObjectName, processPrototype ) { + + let prototypeFunctions = []; + let objectString = ''; + let target; + if ( processPrototype ) { + objectString = targetClass.toString() + "\n\n" + target = targetPrototype; } - if ( skinIndexFA !== null ) { + else { + target = targetClass; + } + for ( let name in target ) { + + let objectPart = target[ name ]; + let code = objectPart.toString(); - payload.main.buffers.skinIndex = skinIndexFA; - payload.transferables.skinIndex = [ skinIndexFA.buffer ]; + if ( typeof objectPart === 'function' ) { + + prototypeFunctions.push( '\t' + name + ': ' + code + ',\n\n' ); + + } } - if ( skinWeightFA !== null ) { - payload.main.buffers.skinWeight = skinWeightFA; - payload.transferables.skinWeight = [ skinWeightFA.buffer ]; + let protoString = processPrototype ? '.prototype' : ''; + objectString += fullObjectName + protoString + ' = {\n\n'; + for ( let i = 0; i < prototypeFunctions.length; i ++ ) { + + objectString += prototypeFunctions[ i ]; } - return payload; + objectString += '\n}\n;'; + return objectString; + } + static serializeClass ( targetClass ) { + + return targetClass.toString() + "\n\n"; + + } } class ObjectManipulator { @@ -339,6 +905,18 @@ class ObjectManipulator { } } + } -export { TransferableUtils, MeshMessageStructure, ObjectManipulator } +export { + TransportBase, + DataTransport, + GeometryTransport, + MeshTransport, + MaterialsTransport, + MaterialUtils, + MaterialStore, + MaterialCloneInstruction, + CodeUtils, + ObjectManipulator +} diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts b/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts deleted file mode 100644 index d16628bb9789af..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/worker/tmDefaultComRouting.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export namespace WorkerTaskManagerDefaultRouting { - export function comRouting(context: any, message: any, object: any, initFunction: any, executeFunction: any): void; -} diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js deleted file mode 100644 index 93c3aa7965b7d7..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @author Kai Salmen / www.kaisalmen.de - */ - -import { TorusKnotBufferGeometry } from "../../../../../build/three.module.js"; -import { TransferableUtils } from "../utils/TransferableUtils.js"; -import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; - - -function init ( context, id, config ) { - context.storage = { - whoami: id, - }; - - context.postMessage( { - cmd: "init", - id: id - } ); - -} - -function execute ( context, id, config ) { - - let bufferGeometry = new TorusKnotBufferGeometry( 20, 3, 100, 64 ); - - let vertexBA = bufferGeometry.getAttribute( 'position' ) ; - let vertexArray = vertexBA.array; - for ( let i = 0; i < vertexArray.length; i++ ) { - - vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); - - } - let payload = TransferableUtils.packageBufferGeometry( bufferGeometry, config.id, 'tmProto' + config.id, 2,[ 'defaultPointMaterial' ] ); - - let randArray = new Uint8Array( 3 ); - context.crypto.getRandomValues( randArray ); - payload.main.params.color = { - r: randArray[ 0 ] / 255, - g: randArray[ 1 ] / 255, - b: randArray[ 2 ] / 255 - }; - payload.postMessage( context ); - -} - -self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js deleted file mode 100644 index 3a1821127f40e1..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @author Kai Salmen / www.kaisalmen.de - */ - -import { MaterialLoader } from "../../../../../src/loaders/MaterialLoader.js"; -import { OBJLoader } from "../../OBJLoader.js"; -import { TransferableUtils } from "../utils/TransferableUtils.js"; -import { WorkerTaskManagerDefaultRouting } from "./tmDefaultComRouting.js"; - -const OBJLoaderWorker = { - - init: function ( context, id, config ) { - - context.objLoader = { - loader: null, - buffer: null - } - context.postMessage( { - cmd: "init", - id: id - } ); - if ( config.buffer !== undefined && config.buffer !== null ) context.objLoader.buffer = config.buffer; - - }, - - execute: function ( context, id, config ) { - - context.objLoader.loader = new OBJLoader(); - context.objLoader.loader.objectId = config.id; - const materialLoader = new MaterialLoader(); - let material, materialJson; - let materialsIn = config.params.materials; - let materialsOut = {}; - for ( let materialName in materialsIn ) { - - materialJson = materialsIn[ materialName ]; - if ( materialJson !== undefined && materialJson !== null ) { - - material = materialLoader.parse( materialJson ); -// console.info( 'De-serialized material with name "' + materialName + '" will be added.' ); - materialsOut[ materialName ] = material; - - } - - } - context.objLoader.loader.setMaterials( materialsOut ); - - const enc = new TextDecoder("utf-8"); - let meshes = context.objLoader.loader.parse( enc.decode( context.objLoader.buffer ) ); - for ( let mesh, i = 0; i < meshes.children.length; i ++ ) { - - mesh = meshes.children[ i ]; - let payload = TransferableUtils.packageBufferGeometry( mesh.geometry, config.id, mesh.name + config.id, 0 ); - payload.main.materials.json = mesh.material.toJSON(); - payload.postMessage( context ); - - } - - } - -}; - -self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, OBJLoaderWorker, 'init', 'execute' ), false ); - -export { OBJLoaderWorker }; diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 2387ea7c2ee0d4..d0a213cd37f6e0 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -24,23 +24,24 @@ import { TrackballControls } from "./jsm/controls/TrackballControls.js"; import { WorkerTaskManager } from "./jsm/loaders/workerTaskManager/WorkerTaskManager.js"; - import { MeshReceiver } from "./jsm/loaders/workerTaskManager/shared/MeshReceiver.js"; import { - MeshMessageStructure, - ObjectManipulator + TransportBase, + GeometryTransport, + MeshTransport, + MaterialsTransport, + MaterialStore, + CodeUtils } from "./jsm/loaders/workerTaskManager/utils/TransferableUtils.js" - import { MaterialHandler } from "./jsm/loaders/workerTaskManager/shared/MaterialHandler.js"; - import { TransferableUtils } from "./jsm/loaders/workerTaskManager/utils/TransferableUtils.js"; import { FileLoader } from "../src/loaders/FileLoader.js"; import { MTLLoader } from "./jsm/loaders/MTLLoader.js"; - import { OBJLoaderWorker } from "./jsm/loaders/workerTaskManager/worker/tmOBJLoader.js"; + import { OBJLoaderWorker } from "./jsm/loaders/worker/tmOBJLoader.js"; /** * The aim of this example is to show all possible ways how to use the {@link WorkerTaskManager}: * - Standard Workers with dependency loading * - Module Workers with and without additional dependencies * - Main Fallback without dependencies - * - It also allows to use OBJLoader2Parser with and without modules. + * - It also allows to use OBJLoader with and without modules. * * Via dat.gui it is possible to control various parameters of the example: * - The quantity of workers created for each task (default: 4) @@ -51,7 +52,6 @@ * The tasks perform the same loading operation over and over again. This shall demonstrate: * - A good CPU utilization can be achieved permanently if the selected amount of workers match the logical CPUs available * - No memory is leaked - * - The exmaple is able */ class TaskManagerPrototypeExample { @@ -73,8 +73,6 @@ this.controls = null; this.workerTaskManager = null; - this.materialHandler = null; - this.meshReceiver = null; this.taskDescriptions = new Map (); this.tasksToUse = []; @@ -100,36 +98,39 @@ this.baseVectorZ = new THREE.Vector3( 0, 0, 1 ); } + _recalcExecutionNumbers () { + this.loopCount = this.overallExecutionCount / this.maxPerLoop; + } + resetAppContext () { this.workerTaskManager = new WorkerTaskManager(); this.workerTaskManager.setVerbose( true ); - this.materialHandler = new MaterialHandler(); - this.materialHandler.createDefaultMaterials( true ); - let meshNormalMaterial = new THREE.MeshNormalMaterial(); - meshNormalMaterial.name = 'meshNormalMaterial'; - this.materialHandler.addMaterials( { meshNormalMaterial: meshNormalMaterial }, true ) - this.meshReceiver = new MeshReceiver( this.materialHandler ); - this.taskDescriptions.clear(); this.taskDescriptions.set( 'tmProtoExample', { name: 'tmProtoExample', - use: false, + use: true, fallback: false, funcInit: WorkerFunctions.workerStandardInit, - funcExec: WorkerFunctions.workerStandardExec + funcExec: WorkerFunctions.workerStandardExec, + dependencies: [ + { url: "../build/three.js" }, + { code: CodeUtils.serializeClass( THREE.BufferGeometry ) }, + { code: CodeUtils.serializeClass( TransportBase ) }, + { code: CodeUtils.serializeClass( GeometryTransport ) } + ] } ); this.taskDescriptions.set( 'tmProtoExampleModule', { name: 'tmProtoExampleModule', - use: false, + use: true, fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmModuleExample.js' + module: './jsm/loaders/worker/tmModuleExample.js' } ); this.taskDescriptions.set( 'tmProtoExampleModuleNoThree', { name: 'tmProtoExampleModuleNoThree', - use: false, + use: true, fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js' + module: './jsm/loaders/worker/tmModuleExampleNoThree.js' } ); this.taskDescriptions.set( 'tmProtoExampleMain', { name: 'tmProtoExampleMain', @@ -142,10 +143,10 @@ name: 'tmOBJLoaderModule', use: true, fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmOBJLoader.js', + module: './jsm/loaders/worker/tmOBJLoader.js', filenameMtl: './models/obj/female02/female02.mtl', filenameObj: './models/obj/female02/female02.obj', - materials: {} + materialStore: new MaterialStore( true ), } ); this.taskDescriptions.set( 'tmOBJLoaderStandard', { name: 'tmOBJLoaderStandard', @@ -155,7 +156,8 @@ funcExec: OBJLoaderWorker.execute, filenameMtl: './models/obj/male02/male02.mtl', filenameObj: './models/obj/male02/male02.obj', - materials: {} + materialStore: new MaterialStore( true ), + dependencies: OBJLoaderWorker.buildStandardWorkerDependencies( '../build/three.js', '../examples/js/loaders/OBJLoader.js' ) } ); this.tasksToUse = []; @@ -227,11 +229,12 @@ async initContent () { let awaiting = []; this.tasksToUse = []; + let taskDescr = this.taskDescriptions.get( 'tmProtoExample' ); if ( taskDescr.use ) { this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, [ { url: "../build/three.js" } ] ); + this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); } @@ -247,10 +250,12 @@ if ( taskDescr.use ) { let torus = new THREE.TorusBufferGeometry( 25, 8, 16, 100 ); - let torusPayload = TransferableUtils.packageBufferGeometry( torus, '0', 'torus', 0 ); + torus.name = 'torus'; + + const sender = new GeometryTransport().setGeometry( torus, 0 ).package( false ); this.tasksToUse.push( taskDescr ); this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, torusPayload, torusPayload.transferables ).catch( e => console.error( e ) ) ); + awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, sender.getMain(), sender.getTransferables() ).catch( e => console.error( e ) ) ); } taskDescr = this.taskDescriptions.get( 'tmProtoExampleMain' ); @@ -267,32 +272,29 @@ this.tasksToUse.push( taskDescr ); this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); await this.loadObjMtl( taskDescr ) - .then( buffer => { - let config = { buffer: buffer } - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, config, { buffer: buffer } ).catch( e => console.error( e ) ) ); - } ); + .then( buffer => { + const mt = new MaterialsTransport() + .addBuffer( "data", buffer ) + .setMaterials( taskDescr.materialStore.getMaterials() ) + .cleanMaterials() + .package( false ); + awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); + } ); } taskDescr = this.taskDescriptions.get( 'tmOBJLoaderStandard' ); if ( taskDescr.use ) { this.tasksToUse.push( taskDescr ); - let transferableUtils = TransferableUtils.toString() + ';\n\n'; - let meshMessageStructure = MeshMessageStructure.toString() + ';\n\n'; - - this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, - [ - { url: '../build/three.js' }, - { code: '\n\nconst MaterialLoader = THREE.MaterialLoader;\n\n' }, - { url: '../examples/js/loaders/OBJLoader.js' }, - { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, - { code: transferableUtils }, - { code: meshMessageStructure } - ] ); + this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); await this.loadObjMtl( taskDescr ) .then( buffer => { - let config = { buffer: buffer } - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, config, { buffer: buffer } ).catch( e => console.error( e ) ) ); + const mt = new MaterialsTransport() + .addBuffer( "data", buffer ) + .setMaterials( taskDescr.materialStore.getMaterials() ) + .cleanMaterials() + .package( false ); + awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); } ); } @@ -322,8 +324,7 @@ await loadMtl.then( materialCreator => { materialCreator.preload(); - this.materialHandler.addMaterials( materialCreator.materials, false ); - taskDescr.materials = this.materialHandler.getMaterialsJSON() + taskDescr.materialStore.addMaterials( materialCreator.materials, false ); } ) ; return await fileLoader.loadAsync( taskDescr.filenameObj ); @@ -349,14 +350,11 @@ for ( let i = 0; i < this.maxPerLoop; i ++ ) { let taskDescr = this.tasksToUse[ taskToUseIndex ]; - let promise = this.workerTaskManager.enqueueForExecution( taskDescr.name, { - id: globalCount, - params: { - materials: taskDescr.materials - } - }, - data => this._processMessage( data ) ) - .then( data => this._processMessage( data ) ) + + const tb = new TransportBase( 'execute', globalCount ).setParams( { modelName: taskDescr.name } ); + let promise = this.workerTaskManager.enqueueForExecution( taskDescr.name, tb.getMain(), + data => this._processMessage( taskDescr, data ) ) + .then( data => this._processMessage( taskDescr, data ) ) .catch( e => console.error( e ) ) this.executions.push( promise ); @@ -383,7 +381,8 @@ } - _processMessage ( payload ) { + _processMessage ( taskDescr, payload ) { + let mesh, material; switch ( payload.cmd ) { case 'init': console.log( 'Init Completed: ' + payload.id ); @@ -393,16 +392,33 @@ case 'assetAvailable': switch ( payload.type ) { - case 'mesh': - let meshes = this.meshReceiver.buildMeshes( payload ); - this._addMesh( meshes[ 0 ], payload.id, payload.params ); + case 'GeometryTransport': + const geometryTransport = new GeometryTransport().loadData( payload ).reconstruct( payload.geometry ); + + let randArray = new Uint8Array( 3 ); + window.crypto.getRandomValues( randArray ); + const color = new THREE.Color(); + color.r = randArray[ 0 ] / 255; + color.g = randArray[ 1 ] / 255; + color.b = randArray[ 2 ] / 255; + material = new THREE.MeshPhongMaterial( { color: color } ); + + mesh = new THREE.Mesh( geometryTransport.getBufferGeometry(), material ); + this._addMesh( mesh, geometryTransport.main.id ); break; - case 'material': - this.materialHandler.addPayloadMaterials( payload ); + case 'MeshTransport' : + const meshTransport = new MeshTransport().loadData( payload ).reconstruct( false ); + + const materialsTransport = meshTransport.getMaterialsTransport(); + material = materialsTransport.processMaterialTransport( taskDescr.materialStore ? taskDescr.materialStore.getMaterials() : {}, true ); + + mesh = new THREE.Mesh( meshTransport.getBufferGeometry(), material ); + this._addMesh( mesh, meshTransport.main.id ); break; - case 'void': + case 'TransportBase': + if ( payload.cmd !== 'execComplete' ) console.log( 'TransportBase' ); break; default: @@ -419,13 +435,14 @@ } } - _addMesh( mesh, id, params ) { + _addMesh( mesh, id ) { let storedPos = this.objectsUsed.get( id ); let pos; if ( storedPos ) { pos = storedPos.pos; + } else { @@ -438,12 +455,6 @@ } mesh.position.set( pos.x, pos.y, pos.z ); mesh.name = id + '' + mesh.name; - if ( params.color !== undefined ) { - - mesh.material.color = new THREE.Color( params.color.r, params.color.g, params.color.b ); - - } - this.scene.add( mesh ); this.meshesAdded.push( mesh.name ); this.meshCount ++; @@ -544,50 +555,17 @@ workerStandardExec: function ( context, id, config ) { let bufferGeometry = new THREE.SphereBufferGeometry( 40, 64, 64 ); - - let vertexBA = bufferGeometry.getAttribute( 'position' ); - let indexBA = bufferGeometry.getIndex(); - let colorBA = bufferGeometry.getAttribute( 'color' ); - let normalBA = bufferGeometry.getAttribute( 'normal' ); - let uvBA = bufferGeometry.getAttribute( 'uv' ); - - let vertexArray = vertexBA.array; + bufferGeometry.name = 'tmProto' + config.id; + let vertexArray = bufferGeometry.getAttribute( 'position' ).array; for ( let i = 0; i < vertexArray.length; i ++ ) { vertexArray[ i ] = vertexArray[ i ] * Math.random() * 0.48; } - let vertexFA = vertexArray; - let indexUA = (indexBA !== null && indexBA !== undefined) ? indexBA.array : null; - let colorFA = (colorBA !== null && colorBA !== undefined) ? colorBA.array : null; - let normalFA = (normalBA !== null && normalBA !== undefined) ? normalBA.array : null; - let uvFA = (uvBA !== null && uvBA !== undefined) ? uvBA.array : null; - - context.postMessage( { - cmd: 'execComplete', - type: 'mesh', - meshName: 'tmProto' + config.id, - id: config.id, - params: { - geometryType: 0 - }, - buffers: { - vertices: vertexFA, - indices: indexUA, - colors: colorFA, - normals: normalFA, - uvs: uvFA, - }, - materials: { - materialNames: ['meshNormalMaterial'] - } - }, - [vertexFA.buffer], - indexUA !== null ? [indexUA.buffer] : null, - colorFA !== null ? [colorFA.buffer] : null, - normalFA !== null ? [normalFA.buffer] : null, - uvFA !== null ? [uvFA.buffer] : null - ); + new GeometryTransport( 'execComplete', config.id ) + .setGeometry( bufferGeometry, 0 ) + .package( false ) + .postMessage( context ); } @@ -734,11 +712,11 @@ index++; tmControls.controls[ index ] = gui.add( tmControls, 'overallExecutionCount', 0, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); - tmControls.controls[ index ].onChange( value => { app.overallExecutionCount = value } ); + tmControls.controls[ index ].onChange( value => { app.overallExecutionCount = value; app._recalcExecutionNumbers() } ); index++; tmControls.controls[ index ] = gui.add( tmControls, 'maxPerLoop', 0, 10000 ).step( 100 ).name( 'Loop executions' ); - tmControls.controls[ index ].onChange( value => { app.maxPerLoop = value } ); + tmControls.controls[ index ].onChange( value => { app.maxPerLoop = value; app._recalcExecutionNumbers() } ); index++; tmControls.controls[ index ] = gui.add( tmControls, 'numberOfMeshesToKeep', 100, 10000 ).step( 25 ).name( 'Keep N Meshes' ); From 51b85efb6ffbe4156380ba9d9df06514eb25e6a2 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Fri, 12 Feb 2021 23:13:41 +0100 Subject: [PATCH 03/12] Merged TransportBase and DataTransport (inheritance: DataTransport -> MaterialsTransport and DataTransport -> GeometryTransport -> MeshTransport) Aligned usage in tmOBJLoader.js and webgl_loader_workertaskmanager.html --- examples/jsm/loaders/worker/tmOBJLoader.js | 14 +- .../utils/TransferableUtils.js | 161 ++++++++---------- examples/webgl_loader_workertaskmanager.html | 18 +- 3 files changed, 88 insertions(+), 105 deletions(-) diff --git a/examples/jsm/loaders/worker/tmOBJLoader.js b/examples/jsm/loaders/worker/tmOBJLoader.js index 86fcc047d52728..74b84e990317e7 100644 --- a/examples/jsm/loaders/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/worker/tmOBJLoader.js @@ -3,7 +3,6 @@ */ import { - TransportBase, DataTransport, MaterialsTransport, MaterialUtils, @@ -28,7 +27,6 @@ const OBJLoaderWorker = { { url: objLoaderLocation }, { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, { code: '\n\n' }, - { code: CodeUtils.serializeClass( TransportBase ) }, { code: CodeUtils.serializeClass( DataTransport ) }, { code: CodeUtils.serializeClass( MaterialsTransport ) }, { code: CodeUtils.serializeClass( MaterialUtils ) }, @@ -46,7 +44,7 @@ const OBJLoaderWorker = { materials: materialsTransport.getMaterials() } - const buffer = materialsTransport.getBuffer( 'data' ) + const buffer = materialsTransport.getBuffer( 'modelData' ) if ( buffer !== undefined && buffer !== null ) context.objLoader.buffer = buffer; context.postMessage( { @@ -58,7 +56,9 @@ const OBJLoaderWorker = { execute: function ( context, id, config ) { context.objLoader.loader = new OBJLoader(); - context.objLoader.loader.objectId = config.id; + const dataTransport = new DataTransport().loadData( config ); + + context.objLoader.loader.objectId = dataTransport.getId(); context.objLoader.loader.setMaterials( context.objLoader.materials ); const enc = new TextDecoder("utf-8"); @@ -66,12 +66,12 @@ const OBJLoaderWorker = { for ( let mesh, i = 0; i < meshes.children.length; i ++ ) { mesh = meshes.children[ i ]; - mesh.name = mesh.name + config.id; + mesh.name = mesh.name + dataTransport.getId(); const materialsTransport = new MaterialsTransport(); const material = mesh.material; MaterialUtils.addMaterial( materialsTransport.main.materials, material, material.name, false, false ); - new MeshTransport( 'assetAvailable', config.id ) + new MeshTransport( 'assetAvailable', dataTransport.getId() ) .setMesh( mesh, 0 ) .setMaterialsTransport( materialsTransport ) .package( false ) @@ -80,7 +80,7 @@ const OBJLoaderWorker = { } // signal complete - new TransportBase( 'execComplete' ).postMessage( context ); + new DataTransport( 'execComplete' ).postMessage( context ); } diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js index e4d22c32473a95..b9d4a9fbdde92b 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js @@ -20,10 +20,10 @@ import { /** * Define a base structure that is used to ship data in between main and workers. */ -class TransportBase { +class DataTransport { /** - * Creates a new {@link TransportBase}. + * Creates a new {@link DataTransport}. * * @param {string} [cmd] * @param {string} [id] @@ -33,74 +33,76 @@ class TransportBase { this.main = { cmd: ( cmd !== undefined ) ? cmd : 'unknown', id: ( id !== undefined ) ? id : 0, - type: 'TransportBase', + type: 'DataTransport', /** @type {number} */ progress: 0, + buffers: {}, params: { } }; this.transferables = []; - } /** * * @param {object} transportObject * - * @return {TransportBase} + * @return {DataTransport} */ loadData( transportObject ) { this.main.cmd = transportObject.cmd; this.main.id = transportObject.id; - this.main.type = 'TransportBase'; + this.main.type = 'DataTransport'; this.setProgress( transportObject.progress ); this.setParams( transportObject.params ); + + if ( transportObject.buffers ) { + Object.entries( transportObject.buffers ).forEach( ( [name, buffer] ) => { + this.main.buffers[ name ] = buffer; + } ); + } return this; } /** * - * @param {object.} params - * @return {TransportBase} + * @return {string} */ - setParams( params ) { - - if ( params !== null && params !== undefined ) { - this.main.params = params; - } - return this; - - } - - getParams() { - return this.main.params; + getCmd () { + return this.main.cmd; } /** * - * @return {*|{cmd: string, type: string, progress: {numericalValue: number}, params: {}}|{progress: number, cmd: (string|string), id: (string|number), type: string, params: {}}} + * @return {number|string} */ - getMain() { - - return this.main; - + getId() { + return this.main.id; } /** * - * @return {[]|any[]|*} + * @param {object.} params + * @return {DataTransport} */ - getTransferables() { + setParams( params ) { - return this.transferables; + if ( params !== null && params !== undefined ) { + this.main.params = params; + } + return this; } + getParams() { + return this.main.params; + } + /** * * @param {number} numericalValue * - * @return {TransportBase} + * @return {DataTransport} */ setProgress( numericalValue ) { @@ -110,100 +112,78 @@ class TransportBase { } /** - * Posts a message by invoking the method on the provided object. * - * @param {object} postMessageImpl - * - * @return {TransportBase} + * @param name + * @param buffer + * @return {DataTransport} */ - postMessage( postMessageImpl ) { - - postMessageImpl.postMessage( this.main, this.transferables ); + addBuffer ( name, buffer ) { + this.main.buffers[ name ] = buffer; return this; - } -} - -class DataTransport extends TransportBase { /** - * Creates a new {@link DataTransport}. * - * @param {string} [cmd] - * @param {string} [id] + * @param name + * @return {ArrayBuffer} */ - constructor( cmd, id ) { - super( cmd, id ); - this.main.type = 'DataTransport'; - this.main.buffers = {}; + getBuffer( name ) { + return this.main.buffers[ name ]; } /** + * Package all data buffers * - * @param {object} transportObject + * @param {boolean} cloneBuffers * - * @return {TransportBase} + * @return {DataTransport} */ - loadData( transportObject ) { - super.loadData( transportObject ); + package( cloneBuffers ) { + for ( let buffer of Object.values( this.main.buffers ) ) { + + if ( buffer !== null && buffer !== undefined ) { + + const potentialClone = cloneBuffers ? buffer.slice( 0 ) : buffer; + this.transferables.push( potentialClone ); + + } - if ( transportObject.buffers ) { - Object.entries( transportObject.buffers ).forEach( ( [name, buffer] ) => { - this.main.buffers[ name ] = buffer; - } ); } return this; } /** - * - * @param name - * @param buffer - * @return {DataTransport} + * Return main data object + * @return {object} */ - addBuffer ( name, buffer ) { - this.main.buffers[ name ] = buffer; - return this; - } + getMain() { - getBuffer( name ) { - return this.main.buffers[ name]; - } + return this.main; - setParams( params ) { - super.setParams( params ); - return this; } /** - * Package all data buffers - * - * @param {boolean} cloneBuffers - * - * @return {DataTransport} + * Return all transferable in one array. + * @return {[]|any[]|*} */ - package( cloneBuffers ) { - for ( let buffer of Object.values( this.main.buffers ) ) { - this.addArrayBufferToTransferable( buffer, cloneBuffers ); - } - return this; + getTransferables() { + + return this.transferables; + } /** + * Posts a message by invoking the method on the provided object. * - * @param buffer - * @param cloneBuffer + * @param {object} postMessageImpl * * @return {DataTransport} */ - addArrayBufferToTransferable( buffer, cloneBuffer ) { - if ( buffer !== null && buffer !== undefined ) { - - const potentialClone = cloneBuffer ? buffer.slice( 0 ) : buffer; - this.transferables.push( potentialClone ); + postMessage( postMessageImpl ) { - } + postMessageImpl.postMessage( this.main, this.transferables ); return this; + } } @@ -391,7 +371,7 @@ class MaterialsTransport extends DataTransport { /** * Define a structure that is used to send geometry data between main and workers. */ -class GeometryTransport extends TransportBase { +class GeometryTransport extends DataTransport { /** * Creates a new {@link GeometrySender}. @@ -440,7 +420,7 @@ class GeometryTransport extends TransportBase { */ setGeometry( geometry, geometryType ) { this.main.geometry = geometry; - this.main.params.geometryType = geometryType; + this.main.geometryType = geometryType; if ( geometry instanceof BufferGeometry ) this.main.bufferGeometry = geometry; return this; @@ -454,6 +434,7 @@ class GeometryTransport extends TransportBase { * @return {GeometryTransport} */ package( cloneBuffers ) { + super.package( cloneBuffers ); const vertexBA = this.main.geometry.getAttribute( 'position' ); const normalBA = this.main.geometry.getAttribute( 'normal' ); const uvBA = this.main.geometry.getAttribute( 'uv' ); @@ -741,6 +722,9 @@ class MaterialUtils { } +/** + * Store materials in an object and create and store default materials optionally. + */ class MaterialStore { constructor( createDefaultMaterials ) { @@ -909,7 +893,6 @@ class ObjectManipulator { } export { - TransportBase, DataTransport, GeometryTransport, MeshTransport, diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index d0a213cd37f6e0..d6286bbab66223 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -25,7 +25,7 @@ import { TrackballControls } from "./jsm/controls/TrackballControls.js"; import { WorkerTaskManager } from "./jsm/loaders/workerTaskManager/WorkerTaskManager.js"; import { - TransportBase, + DataTransport, GeometryTransport, MeshTransport, MaterialsTransport, @@ -116,7 +116,7 @@ dependencies: [ { url: "../build/three.js" }, { code: CodeUtils.serializeClass( THREE.BufferGeometry ) }, - { code: CodeUtils.serializeClass( TransportBase ) }, + { code: CodeUtils.serializeClass( DataTransport ) }, { code: CodeUtils.serializeClass( GeometryTransport ) } ] } ); @@ -274,7 +274,7 @@ await this.loadObjMtl( taskDescr ) .then( buffer => { const mt = new MaterialsTransport() - .addBuffer( "data", buffer ) + .addBuffer( 'modelData', buffer ) .setMaterials( taskDescr.materialStore.getMaterials() ) .cleanMaterials() .package( false ); @@ -290,7 +290,7 @@ await this.loadObjMtl( taskDescr ) .then( buffer => { const mt = new MaterialsTransport() - .addBuffer( "data", buffer ) + .addBuffer( 'modelData', buffer ) .setMaterials( taskDescr.materialStore.getMaterials() ) .cleanMaterials() .package( false ); @@ -351,7 +351,7 @@ let taskDescr = this.tasksToUse[ taskToUseIndex ]; - const tb = new TransportBase( 'execute', globalCount ).setParams( { modelName: taskDescr.name } ); + const tb = new DataTransport( 'execute', globalCount ).setParams( { modelName: taskDescr.name } ); let promise = this.workerTaskManager.enqueueForExecution( taskDescr.name, tb.getMain(), data => this._processMessage( taskDescr, data ) ) .then( data => this._processMessage( taskDescr, data ) ) @@ -404,7 +404,7 @@ material = new THREE.MeshPhongMaterial( { color: color } ); mesh = new THREE.Mesh( geometryTransport.getBufferGeometry(), material ); - this._addMesh( mesh, geometryTransport.main.id ); + this._addMesh( mesh, geometryTransport.getId() ); break; case 'MeshTransport' : @@ -414,11 +414,11 @@ material = materialsTransport.processMaterialTransport( taskDescr.materialStore ? taskDescr.materialStore.getMaterials() : {}, true ); mesh = new THREE.Mesh( meshTransport.getBufferGeometry(), material ); - this._addMesh( mesh, meshTransport.main.id ); + this._addMesh( mesh, meshTransport.getId() ); break; - case 'TransportBase': - if ( payload.cmd !== 'execComplete' ) console.log( 'TransportBase' ); + case 'DataTransport': + if ( payload.cmd !== 'execComplete' ) console.log( 'DataTransport' ); break; default: From de4bbdfd37f76db94a30d59f69a7ed5c8951d28e Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 15 Feb 2021 23:55:08 +0100 Subject: [PATCH 04/12] Move MaterialUtils and MaterialStore to their own files. Renamed TransferableUtils to TransportUtils. Updated Typescript definitions. --- .../jsm/loaders/worker/tmModuleExample.js | 6 +- .../loaders/worker/tmModuleExampleNoThree.js | 2 +- examples/jsm/loaders/worker/tmOBJLoader.js | 18 +- .../workerTaskManager/utils/MaterialStore.js | 102 ++++++++ .../workerTaskManager/utils/MaterialUtils.js | 123 ++++++++++ ...TransferableUtils.js => TransportUtils.js} | 223 ++---------------- examples/webgl_loader_workertaskmanager.html | 12 +- 7 files changed, 263 insertions(+), 223 deletions(-) create mode 100644 examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js create mode 100644 examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js rename examples/jsm/loaders/workerTaskManager/utils/{TransferableUtils.js => TransportUtils.js} (73%) diff --git a/examples/jsm/loaders/worker/tmModuleExample.js b/examples/jsm/loaders/worker/tmModuleExample.js index e32d12832dec6b..aec22cb4af0191 100644 --- a/examples/jsm/loaders/worker/tmModuleExample.js +++ b/examples/jsm/loaders/worker/tmModuleExample.js @@ -9,9 +9,11 @@ import { } from "../../../../build/three.module.js"; import { MeshTransport, - MaterialsTransport, + MaterialsTransport +} from "../workerTaskManager/utils/TransportUtils.js"; +import { MaterialUtils -} from "../workerTaskManager/utils/TransferableUtils.js"; +} from '../workerTaskManager/utils/MaterialUtils.js'; import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; diff --git a/examples/jsm/loaders/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/worker/tmModuleExampleNoThree.js index 8c0a2c3eff38ee..d0f4c366dee944 100644 --- a/examples/jsm/loaders/worker/tmModuleExampleNoThree.js +++ b/examples/jsm/loaders/worker/tmModuleExampleNoThree.js @@ -2,7 +2,7 @@ * @author Kai Salmen / www.kaisalmen.de */ -import { GeometryTransport } from "../workerTaskManager/utils/TransferableUtils.js"; +import { GeometryTransport } from "../workerTaskManager/utils/TransportUtils.js"; import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; function init ( context, id, config ) { diff --git a/examples/jsm/loaders/worker/tmOBJLoader.js b/examples/jsm/loaders/worker/tmOBJLoader.js index 74b84e990317e7..29473c63df5d0d 100644 --- a/examples/jsm/loaders/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/worker/tmOBJLoader.js @@ -5,11 +5,13 @@ import { DataTransport, MaterialsTransport, - MaterialUtils, GeometryTransport, MeshTransport, - CodeUtils, -} from "../workerTaskManager/utils/TransferableUtils.js"; + ObjectUtils, +} from "../workerTaskManager/utils/TransportUtils.js"; +import { + MaterialUtils +} from '../workerTaskManager/utils/MaterialUtils.js'; import { OBJLoader } from "../OBJLoader.js"; import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; @@ -27,11 +29,11 @@ const OBJLoaderWorker = { { url: objLoaderLocation }, { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, { code: '\n\n' }, - { code: CodeUtils.serializeClass( DataTransport ) }, - { code: CodeUtils.serializeClass( MaterialsTransport ) }, - { code: CodeUtils.serializeClass( MaterialUtils ) }, - { code: CodeUtils.serializeClass( GeometryTransport ) }, - { code: CodeUtils.serializeClass( MeshTransport ) } + { code: ObjectUtils.serializeClass( DataTransport ) }, + { code: ObjectUtils.serializeClass( MaterialsTransport ) }, + { code: ObjectUtils.serializeClass( MaterialUtils ) }, + { code: ObjectUtils.serializeClass( GeometryTransport ) }, + { code: ObjectUtils.serializeClass( MeshTransport ) } ] }, diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js new file mode 100644 index 00000000000000..d421994ee25b86 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js @@ -0,0 +1,102 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { + Material, + MeshStandardMaterial, + LineBasicMaterial, + PointsMaterial, + VertexColors +} from "../../../../../build/three.module.js"; +import { MaterialUtils } from "./MaterialUtils.js"; + +/** + * Store materials in an object and create and store default materials optionally. + */ +class MaterialStore { + + constructor( createDefaultMaterials ) { + + this.materials = {}; + if ( createDefaultMaterials ) { + + const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultMaterial.name = 'defaultMaterial'; + + const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; + defaultVertexColorMaterial.vertexColors = VertexColors; + + const defaultLineMaterial = new LineBasicMaterial(); + defaultLineMaterial.name = 'defaultLineMaterial'; + + const defaultPointMaterial = new PointsMaterial( { size: 0.1 } ); + defaultPointMaterial.name = 'defaultPointMaterial'; + + this.materials[ defaultMaterial.name ] = defaultMaterial; + this.materials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial; + this.materials[ defaultLineMaterial.name ] = defaultLineMaterial; + this.materials[ defaultPointMaterial.name ] = defaultPointMaterial; + + } + + } + + /** + * Set materials loaded by any supplier of an Array of {@link Material}. + * + * @param newMaterials Object with named {@link Material} + * @param forceOverrideExisting boolean Override existing material + */ + addMaterials ( newMaterials, forceOverrideExisting ) { + + if ( newMaterials === undefined || newMaterials === null ) newMaterials = {}; + if ( Object.keys( newMaterials ).length > 0 ) { + + let material; + for ( const materialName in newMaterials ) { + + material = newMaterials[ materialName ]; + MaterialUtils.addMaterial( this.materials, material, materialName, forceOverrideExisting === true ); + + } + + } + + } + + /** + * Returns the mapping object of material name and corresponding material. + * + * @returns {Object} Map of {@link Material} + */ + getMaterials () { + + return this.materials; + + } + + /** + * + * @param {String} materialName + * @returns {Material} + */ + getMaterial ( materialName ) { + + return this.materials[ materialName ]; + + } + + /** + * Removes all materials + */ + clearMaterials () { + + this.materials = {}; + + } + +} + +export { MaterialStore } diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js new file mode 100644 index 00000000000000..5f4311149a33c7 --- /dev/null +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js @@ -0,0 +1,123 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +import { Material } from "../../../../../build/three.module.js"; + +class MaterialUtils { + + /** + * + * @param {object} materialsObject + * @param {Material|MaterialCloneInstruction} material + * @param {string} materialName + * @param {boolean} force + * @param {boolena} [log] + */ + static addMaterial( materialsObject, material, materialName, force, log ) { + let existingMaterial; + // ensure materialName is set + material.name = materialName; + if ( ! force ) { + + existingMaterial = materialsObject[ materialName ]; + if ( existingMaterial ) { + + if ( existingMaterial.uuid !== existingMaterial.uuid ) { + + if ( log ) console.log( 'Same material name "' + existingMaterial.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']' ); + + } + + } else { + + materialsObject[ materialName ] = material; + if ( log ) console.info( 'Material with name "' + materialName + '" was added.' ); + + } + + } else { + + materialsObject[ materialName ] = material; + if ( log ) console.info( 'Material with name "' + materialName + '" was forcefully overridden.' ); + + } + } + + /** + * Returns the mapping object of material name and corresponding jsonified material. + * + * @param {object.} + * @returns {Object} Map of Materials in JSON representation + */ + static getMaterialsJSON ( materialsObject ) { + + const materialsJSON = {}; + let material; + for ( const materialName in materialsObject ) { + + material = materialsObject[ materialName ]; + if ( material instanceof Material ) materialsJSON[ materialName ] = material.toJSON(); + + } + return materialsJSON; + + } + + /** + * + * @param {object.} materials + * @param {MaterialCloneInstruction} materialCloneInstruction + * @param {boolean} [log] + */ + static cloneMaterial ( materials, materialCloneInstruction, log ) { + + let material; + if ( materialCloneInstruction ) { + + let materialNameOrg = materialCloneInstruction.materialNameOrg; + materialNameOrg = ( materialNameOrg !== undefined && materialNameOrg !== null ) ? materialNameOrg : ''; + const materialOrg = materials[ materialNameOrg ]; + if ( materialOrg ) { + + material = materialOrg.clone(); + Object.assign( material, materialCloneInstruction.materialProperties ); + MaterialUtils.addMaterial( materials, material, materialCloneInstruction.materialProperties.name, true ); + + } + else { + + if ( log ) console.info( 'Requested material "' + materialNameOrg + '" is not available!' ); + + } + + } + return material; + + } +} + +class MaterialCloneInstruction { + + /** + * + * @param {string} materialNameOrg + * @param {string} materialNameNew + * @param {boolean} haveVertexColors + * @param {boolean} flatShading + */ + constructor ( materialNameOrg, materialNameNew, haveVertexColors, flatShading ) { + this.materialNameOrg = materialNameOrg; + this.materialProperties = { + name: materialNameNew, + vertexColors: haveVertexColors ? 2 : 0, + flatShading: flatShading + }; + } + +} + +export { + MaterialUtils, + MaterialCloneInstruction +} diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js similarity index 73% rename from examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js rename to examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js index b9d4a9fbdde92b..7101da90b1b0ee 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransferableUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js @@ -11,11 +11,11 @@ import { Texture, Material, MeshStandardMaterial, - LineBasicMaterial, - PointsMaterial, - VertexColors, MaterialLoader } from "../../../../../build/three.module.js"; +import { + MaterialUtils +} from './MaterialUtils.js'; /** * Define a base structure that is used to ship data in between main and workers. @@ -202,8 +202,8 @@ class MaterialsTransport extends DataTransport { super( cmd, id ); this.main.type = 'MaterialsTransport'; this.main.materials = {}; - this.main.multiMaterials = {}; - this.main.cloneInstructions = {}; + this.main.multiMaterialNames = {}; + this.main.cloneInstructions = []; } /** @@ -294,7 +294,7 @@ class MaterialsTransport extends DataTransport { hasMultiMaterial () { - return ( Object.keys( this.main.multiMaterials ).length > 0 ); + return ( Object.keys( this.main.multiMaterialNames ).length > 0 ); } @@ -319,37 +319,20 @@ class MaterialsTransport extends DataTransport { */ processMaterialTransport ( materials, log ) { - Object.entries( this.main.cloneInstructions ).forEach( ( [ materialName, materialCloneInstructions ] ) => { - if ( materialCloneInstructions ) { - - let materialNameOrg = materialCloneInstructions.materialNameOrg; - materialNameOrg = (materialNameOrg !== undefined && materialNameOrg !== null) ? materialNameOrg : ''; - const materialOrg = materials[ materialNameOrg ]; - if ( materialOrg ) { - - let material = materialOrg.clone(); - Object.assign( material, materialCloneInstructions.materialProperties ); - MaterialUtils.addMaterial( materials, material, materialName, true ); - - } - else { + for ( let i = 0; i < this.main.cloneInstructions.length; i ++ ) { - if ( log ) console.info( 'Requested material "' + materialNameOrg + '" is not available!' ); - - } - - } + MaterialUtils.cloneMaterial( materials, this.main.cloneInstructions[ i ], log ); - } ); + } let outputMaterial; if ( this.hasMultiMaterial() ) { // multi-material outputMaterial = []; - Object.entries( this.main.multiMaterials ).forEach( ( [ index, materialName ] ) => { + Object.entries( this.main.multiMaterialNames ).forEach( ( [ materialIndex, materialName ] ) => { - outputMaterial[ index ] = materials[ materialName ]; + outputMaterial[ materialIndex ] = materials[ materialName ]; } ); @@ -640,179 +623,10 @@ class MeshTransport extends GeometryTransport { } -class MaterialCloneInstruction { - - /** - * - * @param {string} materialNameOrg - * @param {string} newMaterialName - * @param {boolean} haveVertexColors - * @param {number} smoothingGroup - */ - constructor ( materialNameOrg, newMaterialName, haveVertexColors, smoothingGroup ) { - this.materialNameOrg = materialNameOrg; - this.materialProperties = { - name: newMaterialName, - vertexColors: haveVertexColors ? 2 : 0, - flatShading: smoothingGroup === 0 - }; - } - -} - -class MaterialUtils { - - /** - * - * @param {object} materialsObject - * @param {Material|MaterialCloneInstruction} material - * @param {string} materialName - * @param {boolean} force - * @param {boolena} [log] - */ - static addMaterial( materialsObject, material, materialName, force, log ) { - let existingMaterial; - // ensure materialName is set - material.name = materialName; - if ( ! force ) { - - existingMaterial = materialsObject[ materialName ]; - if ( existingMaterial ) { - - if ( existingMaterial.uuid !== existingMaterial.uuid ) { - - if ( log ) console.log( 'Same material name "' + existingMaterial.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']' ); - - } - - } else { - - materialsObject[ materialName ] = material; - if ( log ) console.info( 'Material with name "' + materialName + '" was added.' ); - - } - - } else { - - materialsObject[ materialName ] = material; - if ( log ) console.info( 'Material with name "' + materialName + '" was forcefully overridden.' ); - - } - } - - /** - * Returns the mapping object of material name and corresponding jsonified material. - * - * @param {object.} - * @returns {Object} Map of Materials in JSON representation - */ - static getMaterialsJSON ( materialsObject ) { - - const materialsJSON = {}; - let material; - for ( const materialName in materialsObject ) { - - material = materialsObject[ materialName ]; - if ( material instanceof Material ) materialsJSON[ materialName ] = material.toJSON(); - - } - return materialsJSON; - - } - -} - -/** - * Store materials in an object and create and store default materials optionally. - */ -class MaterialStore { - - constructor( createDefaultMaterials ) { - - this.materials = {}; - if ( createDefaultMaterials ) { - - const defaultMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); - defaultMaterial.name = 'defaultMaterial'; - - const defaultVertexColorMaterial = new MeshStandardMaterial( { color: 0xDCF1FF } ); - defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; - defaultVertexColorMaterial.vertexColors = VertexColors; - - const defaultLineMaterial = new LineBasicMaterial(); - defaultLineMaterial.name = 'defaultLineMaterial'; - - const defaultPointMaterial = new PointsMaterial( { size: 0.1 } ); - defaultPointMaterial.name = 'defaultPointMaterial'; - - this.materials[ defaultMaterial.name ] = defaultMaterial; - this.materials[ defaultVertexColorMaterial.name ] = defaultVertexColorMaterial; - this.materials[ defaultLineMaterial.name ] = defaultLineMaterial; - this.materials[ defaultPointMaterial.name ] = defaultPointMaterial; - - } - - } - - /** - * Set materials loaded by any supplier of an Array of {@link Material}. - * - * @param newMaterials Object with named {@link Material} - * @param forceOverrideExisting boolean Override existing material - */ - addMaterials ( newMaterials, forceOverrideExisting ) { - - if ( newMaterials === undefined || newMaterials === null ) newMaterials = {}; - if ( Object.keys( newMaterials ).length > 0 ) { - - let material; - for ( const materialName in newMaterials ) { - material = newMaterials[ materialName ]; - MaterialUtils.addMaterial( this.materials, material, materialName, forceOverrideExisting === true ); +class ObjectUtils { - } - - } - - } - - /** - * Returns the mapping object of material name and corresponding material. - * - * @returns {Object} Map of {@link Material} - */ - getMaterials () { - - return this.materials; - - } - - /** - * - * @param {String} materialName - * @returns {Material} - */ - getMaterial ( materialName ) { - - return this.materials[ materialName ]; - - } - - /** - * Removes all materials - */ - clearMaterials () { - - this.materials = {}; - - } - -} - -class CodeUtils { - - static serializePrototype ( targetClass, targetPrototype, fullObjectName, processPrototype ) { + static serializePrototype( targetClass, targetPrototype, fullObjectName, processPrototype ) { let prototypeFunctions = []; let objectString = ''; @@ -820,8 +634,7 @@ class CodeUtils { if ( processPrototype ) { objectString = targetClass.toString() + "\n\n" target = targetPrototype; - } - else { + } else { target = targetClass; } for ( let name in target ) { @@ -849,11 +662,12 @@ class CodeUtils { } - static serializeClass ( targetClass ) { + static serializeClass( targetClass ) { return targetClass.toString() + "\n\n"; } + } class ObjectManipulator { @@ -897,9 +711,6 @@ export { GeometryTransport, MeshTransport, MaterialsTransport, - MaterialUtils, - MaterialStore, - MaterialCloneInstruction, - CodeUtils, + ObjectUtils, ObjectManipulator } diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index d6286bbab66223..3b4e9c1436ac73 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -29,9 +29,9 @@ GeometryTransport, MeshTransport, MaterialsTransport, - MaterialStore, - CodeUtils - } from "./jsm/loaders/workerTaskManager/utils/TransferableUtils.js" + ObjectUtils + } from "./jsm/loaders/workerTaskManager/utils/TransportUtils.js" + import { MaterialStore } from "./jsm/loaders/workerTaskManager/utils/MaterialStore.js" import { FileLoader } from "../src/loaders/FileLoader.js"; import { MTLLoader } from "./jsm/loaders/MTLLoader.js"; import { OBJLoaderWorker } from "./jsm/loaders/worker/tmOBJLoader.js"; @@ -115,9 +115,9 @@ funcExec: WorkerFunctions.workerStandardExec, dependencies: [ { url: "../build/three.js" }, - { code: CodeUtils.serializeClass( THREE.BufferGeometry ) }, - { code: CodeUtils.serializeClass( DataTransport ) }, - { code: CodeUtils.serializeClass( GeometryTransport ) } + { code: ObjectUtils.serializeClass( THREE.BufferGeometry ) }, + { code: ObjectUtils.serializeClass( DataTransport ) }, + { code: ObjectUtils.serializeClass( GeometryTransport ) } ] } ); this.taskDescriptions.set( 'tmProtoExampleModule', { From 7570c71f9945f7585745fcadeca436e0a76ce26c Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Wed, 17 Feb 2021 23:29:53 +0100 Subject: [PATCH 05/12] Move all worker to a single folder below "workerTaskManager". Update TransportUtils documentation. --- .../jsm/loaders/worker/tmDefaultComRouting.js | 39 ----- .../workerTaskManager/WorkerTaskManager.js | 2 +- .../workerTaskManager/utils/TransportUtils.js | 146 +++++++++++++----- .../{comm => }/worker/defaultRouting.js | 0 .../worker/tmModuleExample.js | 8 +- .../worker/tmModuleExampleNoThree.js | 4 +- .../worker/tmOBJLoader.js | 8 +- examples/webgl_loader_workertaskmanager.html | 13 +- 8 files changed, 129 insertions(+), 91 deletions(-) delete mode 100644 examples/jsm/loaders/worker/tmDefaultComRouting.js rename examples/jsm/loaders/workerTaskManager/{comm => }/worker/defaultRouting.js (100%) rename examples/jsm/loaders/{ => workerTaskManager}/worker/tmModuleExample.js (85%) rename examples/jsm/loaders/{ => workerTaskManager}/worker/tmModuleExampleNoThree.js (85%) rename examples/jsm/loaders/{ => workerTaskManager}/worker/tmOBJLoader.js (91%) diff --git a/examples/jsm/loaders/worker/tmDefaultComRouting.js b/examples/jsm/loaders/worker/tmDefaultComRouting.js deleted file mode 100644 index 4b32f5001a977c..00000000000000 --- a/examples/jsm/loaders/worker/tmDefaultComRouting.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @author Kai Salmen / www.kaisalmen.de - */ - -const WorkerTaskManagerDefaultRouting = { - - comRouting: function ( context, message, object, initFunction, executeFunction ) { - - let payload = message.data; - if ( payload.cmd === 'init' ) { - - if ( object !== undefined && object !== null ) { - - object[ initFunction ]( context, payload.workerId, payload.config ); - } else { - - initFunction( context, payload.workerId, payload.config ); - - } - - } else if ( payload.cmd === 'execute' ) { - - if ( object !== undefined && object !== null ) { - - object[ executeFunction ]( context, payload.workerId, payload.config ); - - } else { - - executeFunction( context, payload.workerId, payload.config ); - - } - - } - - } - -}; - -export { WorkerTaskManagerDefaultRouting } diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js index d5662199754fde..e8705fa1aa9eb6 100644 --- a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js @@ -4,7 +4,7 @@ */ import { FileLoader } from "../../../../build/three.module.js"; -import { WorkerTaskManagerDefaultRouting } from "./comm/worker/defaultRouting.js"; +import { WorkerTaskManagerDefaultRouting } from "./worker/defaultRouting.js"; /** * Register one to many tasks type to the WorkerTaskManager. Then init and enqueue a worker based execution by passing diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js index 7101da90b1b0ee..55902c21d94734 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js @@ -10,7 +10,6 @@ import { Sphere, Texture, Material, - MeshStandardMaterial, MaterialLoader } from "../../../../../build/three.module.js"; import { @@ -24,7 +23,6 @@ class DataTransport { /** * Creates a new {@link DataTransport}. - * * @param {string} [cmd] * @param {string} [id] */ @@ -40,16 +38,18 @@ class DataTransport { params: { } }; + /** @type {ArrayBuffer[]} */ this.transferables = []; + } /** - * + * Populate this object with previously serialized data. * @param {object} transportObject - * * @return {DataTransport} */ loadData( transportObject ) { + this.main.cmd = transportObject.cmd; this.main.id = transportObject.id; this.main.type = 'DataTransport'; @@ -57,31 +57,40 @@ class DataTransport { this.setParams( transportObject.params ); if ( transportObject.buffers ) { + Object.entries( transportObject.buffers ).forEach( ( [name, buffer] ) => { + this.main.buffers[ name ] = buffer; + } ); + } return this; + } /** - * + * Returns the value of the command. * @return {string} */ getCmd () { + return this.main.cmd; + } /** - * - * @return {number|string} + * Returns the id. + * @return {string} */ getId() { + return this.main.id; + } /** - * + * Set a parameter object which is a map with string keys and strings or objects as values. * @param {object.} params * @return {DataTransport} */ @@ -94,14 +103,19 @@ class DataTransport { } + /** + * Return the parameter object + * @return {object.} + */ getParams() { + return this.main.params; + } /** - * + * Set the current progress (e.g. percentage of progress) * @param {number} numericalValue - * * @return {DataTransport} */ setProgress( numericalValue ) { @@ -112,33 +126,36 @@ class DataTransport { } /** - * - * @param name - * @param buffer + * Add a named {@link ArrayBuffer} + * @param {string} name + * @param {ArrayBuffer} buffer * @return {DataTransport} */ addBuffer ( name, buffer ) { + this.main.buffers[ name ] = buffer; return this; + } /** - * - * @param name + * Retrieve an {@link ArrayBuffer} by name + * @param {string} name * @return {ArrayBuffer} */ getBuffer( name ) { + return this.main.buffers[ name ]; + } /** - * Package all data buffers - * + * Package all data buffers into the transferable array. Clone if data needs to stay in current context. * @param {boolean} cloneBuffers - * * @return {DataTransport} */ package( cloneBuffers ) { + for ( let buffer of Object.values( this.main.buffers ) ) { if ( buffer !== null && buffer !== undefined ) { @@ -150,6 +167,7 @@ class DataTransport { } return this; + } /** @@ -164,7 +182,7 @@ class DataTransport { /** * Return all transferable in one array. - * @return {[]|any[]|*} + * @return {[]|ArrayBuffer[]} */ getTransferables() { @@ -174,9 +192,7 @@ class DataTransport { /** * Posts a message by invoking the method on the provided object. - * * @param {object} postMessageImpl - * * @return {DataTransport} */ postMessage( postMessageImpl ) { @@ -194,25 +210,28 @@ class MaterialsTransport extends DataTransport { /** * Creates a new {@link MeshMessageStructure}. - * * @param {string} [cmd] * @param {string} [id] */ constructor( cmd, id ) { + super( cmd, id ); this.main.type = 'MaterialsTransport'; + /** {object.} */ this.main.materials = {}; + /** {object.} */ this.main.multiMaterialNames = {}; this.main.cloneInstructions = []; + } /** - * + * See {@link DataTransport#loadData} * @param {object} transportObject - * * @return {MaterialsTransport} */ loadData( transportObject ) { + super.loadData( transportObject ); this.main.type = 'MaterialsTransport'; Object.assign( this.main, transportObject ); @@ -224,49 +243,74 @@ class MaterialsTransport extends DataTransport { } ); return this; + } _cleanMaterial ( material ) { + Object.entries( material ).forEach( ( [key, value] ) => { + if ( value instanceof Texture || value === null ) { material[ key ] = undefined; + } + } ); return material; + } /** - * - * @param name - * @param buffer - * @return {DataTransport} + * See {@link DataTransport#loadData} + * @param {string} name + * @param {ArrayBuffer} buffer + * @return {MaterialsTransport} */ addBuffer( name, buffer ) { - return super.addBuffer( name, buffer ); + + super.addBuffer( name, buffer ); + return this; + } + /** + * See {@link DataTransport#setParams} + * @param {object.} params + * @return {MaterialsTransport} + */ setParams( params ) { + super.setParams( params ); return this; + } /** - * - * @param materials + * Set an object containing named materials. + * @param {object.} materials */ setMaterials ( materials ) { + if ( materials !== undefined && materials !== null && Object.keys( materials ).length > 0 ) this.main.materials = materials; return this; + } + /** + * Returns all maerials + * @return {object.} + */ getMaterials () { + return this.main.materials; + } /** * Removes all textures and null values from all materials */ cleanMaterials () { + let clonedMaterials = {}; let clonedMaterial; for ( let material of Object.values( this.main.materials ) ) { @@ -281,30 +325,46 @@ class MaterialsTransport extends DataTransport { } this.setMaterials( clonedMaterials ); return this; + } + /** + * See {@link DataTransport#package} + * @param {boolean} cloneBuffers + * @return {DataTransport} + */ package ( cloneBuffers) { super.package( cloneBuffers ); - this.main.materials = MaterialUtils.getMaterialsJSON( this.main.materials ); return this; } + /** + * Tell whether a multi-material was defined + * @return {boolean} + */ hasMultiMaterial () { return ( Object.keys( this.main.multiMaterialNames ).length > 0 ); } + /** + * Returns a single material if it is defined or null. + * @return {Material|null} + */ getSingleMaterial () { if ( Object.keys( this.main.materials ).length > 0 ) { + return Object.entries( this.main.materials )[ 0 ][ 1 ]; - } - else { - return new MeshStandardMaterial( { color: 0xFF0000 } ); + + } else { + + return null; + } } @@ -363,6 +423,7 @@ class GeometryTransport extends DataTransport { * @param {string} [id] */ constructor( cmd, id ) { + super( cmd, id ); this.main.type = 'GeometryTransport'; /** @@ -374,6 +435,7 @@ class GeometryTransport extends DataTransport { this.main.geometry = {}; /** @type {BufferGeometry} */ this.main.bufferGeometry = null; + } /** @@ -383,14 +445,28 @@ class GeometryTransport extends DataTransport { * @return {GeometryTransport} */ loadData( transportObject ) { + super.loadData( transportObject ); this.main.type = 'GeometryTransport'; return this.setGeometry( transportObject.geometry, transportObject.geometryType ); + + } + + /** + * Returns + * @return {number} + */ + getGeometryType() { + + return this.main.geometryType; + } setParams( params ) { + super.setParams( params ); return this; + } /** diff --git a/examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js b/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js similarity index 100% rename from examples/jsm/loaders/workerTaskManager/comm/worker/defaultRouting.js rename to examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js diff --git a/examples/jsm/loaders/worker/tmModuleExample.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js similarity index 85% rename from examples/jsm/loaders/worker/tmModuleExample.js rename to examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js index aec22cb4af0191..dc9e44ebde1f4c 100644 --- a/examples/jsm/loaders/worker/tmModuleExample.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js @@ -6,15 +6,15 @@ import { TorusKnotBufferGeometry, Color, MeshPhongMaterial -} from "../../../../build/three.module.js"; +} from "../../../../../build/three.module.js"; import { MeshTransport, MaterialsTransport -} from "../workerTaskManager/utils/TransportUtils.js"; +} from "../utils/TransportUtils.js"; import { MaterialUtils -} from '../workerTaskManager/utils/MaterialUtils.js'; -import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; +} from '../utils/MaterialUtils.js'; +import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; function init ( context, id, config ) { diff --git a/examples/jsm/loaders/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js similarity index 85% rename from examples/jsm/loaders/worker/tmModuleExampleNoThree.js rename to examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js index d0f4c366dee944..e5d4b1cc2a7e1a 100644 --- a/examples/jsm/loaders/worker/tmModuleExampleNoThree.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js @@ -2,8 +2,8 @@ * @author Kai Salmen / www.kaisalmen.de */ -import { GeometryTransport } from "../workerTaskManager/utils/TransportUtils.js"; -import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; +import { GeometryTransport } from "../utils/TransportUtils.js"; +import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; function init ( context, id, config ) { diff --git a/examples/jsm/loaders/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js similarity index 91% rename from examples/jsm/loaders/worker/tmOBJLoader.js rename to examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js index 29473c63df5d0d..1baef3afc7cb91 100644 --- a/examples/jsm/loaders/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js @@ -8,12 +8,12 @@ import { GeometryTransport, MeshTransport, ObjectUtils, -} from "../workerTaskManager/utils/TransportUtils.js"; +} from "../utils/TransportUtils.js"; import { MaterialUtils -} from '../workerTaskManager/utils/MaterialUtils.js'; -import { OBJLoader } from "../OBJLoader.js"; -import { WorkerTaskManagerDefaultRouting } from "../workerTaskManager/comm/worker/defaultRouting.js"; +} from '../utils/MaterialUtils.js'; +import { OBJLoader } from "../../OBJLoader.js"; +import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; const OBJLoaderWorker = { diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 3b4e9c1436ac73..173220fed89f4b 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -34,7 +34,7 @@ import { MaterialStore } from "./jsm/loaders/workerTaskManager/utils/MaterialStore.js" import { FileLoader } from "../src/loaders/FileLoader.js"; import { MTLLoader } from "./jsm/loaders/MTLLoader.js"; - import { OBJLoaderWorker } from "./jsm/loaders/worker/tmOBJLoader.js"; + import { OBJLoaderWorker } from "./jsm/loaders/workerTaskManager/worker/tmOBJLoader.js"; /** * The aim of this example is to show all possible ways how to use the {@link WorkerTaskManager}: @@ -114,7 +114,7 @@ funcInit: WorkerFunctions.workerStandardInit, funcExec: WorkerFunctions.workerStandardExec, dependencies: [ - { url: "../build/three.js" }, + { url: "../build/three.min.js" }, { code: ObjectUtils.serializeClass( THREE.BufferGeometry ) }, { code: ObjectUtils.serializeClass( DataTransport ) }, { code: ObjectUtils.serializeClass( GeometryTransport ) } @@ -124,13 +124,13 @@ name: 'tmProtoExampleModule', use: true, fallback: false, - module: './jsm/loaders/worker/tmModuleExample.js' + module: './jsm/loaders/workerTaskManager/worker/tmModuleExample.js' } ); this.taskDescriptions.set( 'tmProtoExampleModuleNoThree', { name: 'tmProtoExampleModuleNoThree', use: true, fallback: false, - module: './jsm/loaders/worker/tmModuleExampleNoThree.js' + module: './jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js' } ); this.taskDescriptions.set( 'tmProtoExampleMain', { name: 'tmProtoExampleMain', @@ -143,7 +143,7 @@ name: 'tmOBJLoaderModule', use: true, fallback: false, - module: './jsm/loaders/worker/tmOBJLoader.js', + module: './jsm/loaders/workerTaskManager/worker/tmOBJLoader.js', filenameMtl: './models/obj/female02/female02.mtl', filenameObj: './models/obj/female02/female02.obj', materialStore: new MaterialStore( true ), @@ -157,7 +157,7 @@ filenameMtl: './models/obj/male02/male02.mtl', filenameObj: './models/obj/male02/male02.obj', materialStore: new MaterialStore( true ), - dependencies: OBJLoaderWorker.buildStandardWorkerDependencies( '../build/three.js', '../examples/js/loaders/OBJLoader.js' ) + dependencies: OBJLoaderWorker.buildStandardWorkerDependencies( '../build/three.min.js', '../examples/js/loaders/OBJLoader.js' ) } ); this.tasksToUse = []; @@ -412,6 +412,7 @@ const materialsTransport = meshTransport.getMaterialsTransport(); material = materialsTransport.processMaterialTransport( taskDescr.materialStore ? taskDescr.materialStore.getMaterials() : {}, true ); + if ( material === null ) material = new THREE.MeshStandardMaterial( { color: 0xFF0000 } ); mesh = new THREE.Mesh( meshTransport.getBufferGeometry(), material ); this._addMesh( mesh, meshTransport.getId() ); From 607fae789e48374e84fcf1d941779a137e8fb13c Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Thu, 18 Feb 2021 23:17:24 +0100 Subject: [PATCH 06/12] Updated documentation Updated Typescript definitions Removed MaterialCloneInstruction --- .../workerTaskManager/WorkerTaskManager.js | 4 +- .../workerTaskManager/utils/MaterialStore.js | 13 +- .../workerTaskManager/utils/MaterialUtils.js | 44 ++--- .../workerTaskManager/utils/TransportUtils.js | 154 ++++++++++-------- .../worker/defaultRouting.js | 2 +- .../worker/tmModuleExample.js | 2 +- .../worker/tmModuleExampleNoThree.js | 2 +- examples/webgl_loader_workertaskmanager.html | 2 +- 8 files changed, 116 insertions(+), 107 deletions(-) diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js index e8705fa1aa9eb6..fff8888fcb3848 100644 --- a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js @@ -1,6 +1,6 @@ /** - * @author Don McCurdy / https://www.donmccurdy.com - * @author Kai Salmen / https://kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader + * Proposed by Don McCurdy / https://www.donmccurdy.com */ import { FileLoader } from "../../../../build/three.module.js"; diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js index d421994ee25b86..2cff736dc290b9 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js @@ -1,5 +1,5 @@ /** - * @author Kai Salmen / www.kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader */ import { @@ -12,10 +12,15 @@ import { import { MaterialUtils } from "./MaterialUtils.js"; /** - * Store materials in an object and create and store default materials optionally. + * Helper class around an object storing materials by name. + * Optionally, create and store default materials. */ class MaterialStore { + /** + * Creates a new {@link MaterialStore}. + * @param {boolean} createDefaultMaterials + */ constructor( createDefaultMaterials ) { this.materials = {}; @@ -46,8 +51,8 @@ class MaterialStore { /** * Set materials loaded by any supplier of an Array of {@link Material}. * - * @param newMaterials Object with named {@link Material} - * @param forceOverrideExisting boolean Override existing material + * @param {object} newMaterials Object with named {@link Material} + * @param {boolean} forceOverrideExisting boolean Override existing material */ addMaterials ( newMaterials, forceOverrideExisting ) { diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js index 5f4311149a33c7..15e783f9f7a9b7 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js @@ -1,18 +1,23 @@ /** - * @author Kai Salmen / www.kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader */ import { Material } from "../../../../../build/three.module.js"; +/** + * Static functions useful in the context of handling materials. + */ class MaterialUtils { /** + * Adds the provided material to the provided materials object if the material does not exists. + * Use force override existing material. * - * @param {object} materialsObject - * @param {Material|MaterialCloneInstruction} material + * @param {object.} materialsObject + * @param {Material} material * @param {string} materialName * @param {boolean} force - * @param {boolena} [log] + * @param {boolean} [log] Log messages to the console */ static addMaterial( materialsObject, material, materialName, force, log ) { let existingMaterial; @@ -45,9 +50,9 @@ class MaterialUtils { } /** - * Returns the mapping object of material name and corresponding jsonified material. + * Transforms the named materials object to an object with named jsonified materials. * - * @param {object.} + * @param {object.} * @returns {Object} Map of Materials in JSON representation */ static getMaterialsJSON ( materialsObject ) { @@ -65,9 +70,10 @@ class MaterialUtils { } /** + * Clones a material according the provided instructions. * * @param {object.} materials - * @param {MaterialCloneInstruction} materialCloneInstruction + * @param {object} materialCloneInstruction * @param {boolean} [log] */ static cloneMaterial ( materials, materialCloneInstruction, log ) { @@ -95,29 +101,7 @@ class MaterialUtils { return material; } -} - -class MaterialCloneInstruction { - - /** - * - * @param {string} materialNameOrg - * @param {string} materialNameNew - * @param {boolean} haveVertexColors - * @param {boolean} flatShading - */ - constructor ( materialNameOrg, materialNameNew, haveVertexColors, flatShading ) { - this.materialNameOrg = materialNameOrg; - this.materialProperties = { - name: materialNameNew, - vertexColors: haveVertexColors ? 2 : 0, - flatShading: flatShading - }; - } } -export { - MaterialUtils, - MaterialCloneInstruction -} +export { MaterialUtils } diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js index 55902c21d94734..eba5e1fdd0ad09 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js @@ -1,5 +1,4 @@ /** - * @author Kai Salmen / https://kaisalmen.de * Development repository: https://github.com/kaisalmen/WWOBJLoader */ @@ -370,7 +369,7 @@ class MaterialsTransport extends DataTransport { } /** - * Updates the materials with contained material objects (sync) or from alteration instructions (async). + * Adds contained material or multi-material the provided materials object or it clones and adds new materials according clone instructions. * * @param {Object.} materials * @param {boolean} log @@ -417,8 +416,7 @@ class MaterialsTransport extends DataTransport { class GeometryTransport extends DataTransport { /** - * Creates a new {@link GeometrySender}. - * + * Creates a new {@link GeometryTransport}. * @param {string} [cmd] * @param {string} [id] */ @@ -439,9 +437,8 @@ class GeometryTransport extends DataTransport { } /** - * + * See {@link DataTransport#loadData} * @param {object} transportObject - * * @return {GeometryTransport} */ loadData( transportObject ) { @@ -453,7 +450,7 @@ class GeometryTransport extends DataTransport { } /** - * Returns + * Returns the geometry type [0=Mesh|1=LineSegments|2=Points] * @return {number} */ getGeometryType() { @@ -462,6 +459,11 @@ class GeometryTransport extends DataTransport { } + /** + * See {@link DataTransport#setParams} + * @param {object} params + * @return {GeometryTransport} + */ setParams( params ) { super.setParams( params ); @@ -470,11 +472,10 @@ class GeometryTransport extends DataTransport { } /** - * Only add the {@link BufferGeometry} + * Set the {@link BufferGeometry} and geometry type that can be used when a mesh is created. * * @param {BufferGeometry} geometry - * @param {number} geometryType - * + * @param {number} geometryType [0=Mesh|1=LineSegments|2=Points] * @return {GeometryTransport} */ setGeometry( geometry, geometryType ) { @@ -486,13 +487,13 @@ class GeometryTransport extends DataTransport { } /** - * Package {@link BufferGeometry} - * - * @param {boolean} cloneBuffers + * Package {@link BufferGeometry} and prepare it for transport. * + * @param {boolean} cloneBuffers Clone buffers if their content shall stay in the current context. * @return {GeometryTransport} */ package( cloneBuffers ) { + super.package( cloneBuffers ); const vertexBA = this.main.geometry.getAttribute( 'position' ); const normalBA = this.main.geometry.getAttribute( 'normal' ); @@ -502,34 +503,33 @@ class GeometryTransport extends DataTransport { const skinWeightBA = this.main.geometry.getAttribute( 'skinWeight' ); const indexBA = this.main.geometry.getIndex(); - this.addBufferAttributeToTransferable( vertexBA, cloneBuffers ); - this.addBufferAttributeToTransferable( normalBA, cloneBuffers ); - this.addBufferAttributeToTransferable( uvBA, cloneBuffers ); - this.addBufferAttributeToTransferable( colorBA, cloneBuffers ); - this.addBufferAttributeToTransferable( skinIndexBA, cloneBuffers ); - this.addBufferAttributeToTransferable( skinWeightBA, cloneBuffers ); - - this.addBufferAttributeToTransferable( indexBA, cloneBuffers ); - + this._addBufferAttributeToTransferable( vertexBA, cloneBuffers ); + this._addBufferAttributeToTransferable( normalBA, cloneBuffers ); + this._addBufferAttributeToTransferable( uvBA, cloneBuffers ); + this._addBufferAttributeToTransferable( colorBA, cloneBuffers ); + this._addBufferAttributeToTransferable( skinIndexBA, cloneBuffers ); + this._addBufferAttributeToTransferable( skinWeightBA, cloneBuffers ); + this._addBufferAttributeToTransferable( indexBA, cloneBuffers ); return this; } /** + * Reconstructs the {@link BufferGeometry} from the raw buffers. * @param {boolean} cloneBuffers - * * @return {GeometryTransport} */ reconstruct( cloneBuffers ) { + if ( this.main.bufferGeometry instanceof BufferGeometry ) return this; this.main.bufferGeometry = new BufferGeometry(); const transferredGeometry = this.main.geometry; - this.assignAttribute( transferredGeometry.attributes.position, 'position', cloneBuffers ); - this.assignAttribute( transferredGeometry.attributes.normal, 'normal', cloneBuffers ); - this.assignAttribute( transferredGeometry.attributes.uv, 'uv', cloneBuffers ); - this.assignAttribute( transferredGeometry.attributes.color, 'color', cloneBuffers ); - this.assignAttribute( transferredGeometry.attributes.skinIndex, 'skinIndex', cloneBuffers ); - this.assignAttribute( transferredGeometry.attributes.skinWeight, 'skinWeight', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.position, 'position', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.normal, 'normal', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.uv, 'uv', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.color, 'color', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.skinIndex, 'skinIndex', cloneBuffers ); + this._assignAttribute( transferredGeometry.attributes.skinWeight, 'skinWeight', cloneBuffers ); const index = transferredGeometry.index; if ( index !== null && index !== undefined ) { @@ -550,26 +550,22 @@ class GeometryTransport extends DataTransport { this.main.bufferGeometry.groups = transferredGeometry.groups; this.main.bufferGeometry.drawRange = transferredGeometry.drawRange; this.main.bufferGeometry.userData = transferredGeometry.userData; - return this; + } /** - * + * Returns the {@link BufferGeometry}. * @return {BufferGeometry|null} */ getBufferGeometry() { + return this.main.bufferGeometry + } - /** - * - * @param input - * @param cloneBuffer - * - * @return {GeometryTransport} - */ - addBufferAttributeToTransferable( input, cloneBuffer ) { + _addBufferAttributeToTransferable( input, cloneBuffer ) { + if ( input !== null && input !== undefined ) { const arrayBuffer = cloneBuffer ? input.array.slice( 0 ) : input.array; @@ -577,67 +573,73 @@ class GeometryTransport extends DataTransport { } return this; + } - /** - * - * @param attr - * @param attrName - * @param cloneBuffer - * - * @return {GeometryTransport} - */ - assignAttribute( attr, attrName, cloneBuffer ) { + _assignAttribute( attr, attrName, cloneBuffer ) { + if ( attr ) { + const arrayBuffer = cloneBuffer ? attr.array.slice( 0 ) : attr.array; this.main.bufferGeometry.setAttribute( attrName, new BufferAttribute( arrayBuffer, attr.itemSize, attr.normalized ) ); + } return this; + } } + +/** + * Define a structure that is used to send mesh data between main and workers. + */ class MeshTransport extends GeometryTransport { /** * Creates a new {@link MeshTransport}. - * * @param {string} [cmd] * @param {string} [id] */ constructor( cmd, id ) { - super( cmd, id ); + super( cmd, id ); this.main.type = 'MeshTransport'; // needs to be added as we cannot inherit from both materials and geometry this.main.materialsTransport = new MaterialsTransport(); + } /** - * + * See {@link GeometryTransport#loadData} * @param {object} transportObject - * * @return {MeshTransport} */ loadData( transportObject ) { + super.loadData( transportObject ); this.main.type = 'MeshTransport'; this.main.meshName = transportObject.meshName; this.main.materialsTransport = new MaterialsTransport().loadData( transportObject.materialsTransport.main ); - return this; + } + /** + * See {@link GeometryTransport#loadData} + * @param {object} params + * @return {MeshTransport} + */ setParams( params ) { + super.setParams( params ); return this; + } /** - * Only set the material. - * + * The {@link MaterialsTransport} wraps all info regarding the material for the mesh. * @param {MaterialsTransport} materialsTransport - * * @return {MeshTransport} */ setMaterialsTransport( materialsTransport ) { @@ -657,51 +659,60 @@ class MeshTransport extends GeometryTransport { } /** - * + * Sets the mesh and the geometry type [0=Mesh|1=LineSegments|2=Points] * @param {Mesh} mesh * @param {number} geometryType - * * @return {MeshTransport} */ setMesh( mesh, geometryType ) { + this.main.meshName = mesh.name; super.setGeometry( mesh.geometry, geometryType ); - return this; + } /** - * Package {@link Mesh} - * + * See {@link GeometryTransport#package} * @param {boolean} cloneBuffers - * * @return {MeshTransport} */ package( cloneBuffers ) { - super.package( cloneBuffers ); - if ( this.main.materialsTransport !== null ) this.main.materialsTransport.package(); + super.package( cloneBuffers ); + if ( this.main.materialsTransport !== null ) this.main.materialsTransport.package( cloneBuffers ); return this; } /** + * See {@link GeometryTransport#reconstruct} * @param {boolean} cloneBuffers - * * @return {MeshTransport} */ reconstruct( cloneBuffers ) { - super.reconstruct( cloneBuffers ); + super.reconstruct( cloneBuffers ); // so far nothing needs to be done for material - return this; + } } +/** + * Utility for serializing object in memory + */ class ObjectUtils { + /** + * Serializes a class with an optional prototype + * @param targetClass + * @param targetPrototype + * @param fullObjectName + * @param processPrototype + * @return {string} + */ static serializePrototype( targetClass, targetPrototype, fullObjectName, processPrototype ) { let prototypeFunctions = []; @@ -738,6 +749,11 @@ class ObjectUtils { } + /** + * Serializes a class. + * @param {object} targetClass An ES6+ class + * @return {string} + */ static serializeClass( targetClass ) { return targetClass.toString() + "\n\n"; @@ -746,6 +762,10 @@ class ObjectUtils { } + +/** + * Object manipulation utilities. + */ class ObjectManipulator { /** diff --git a/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js b/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js index 4b32f5001a977c..2f25c430d91744 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js +++ b/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js @@ -1,5 +1,5 @@ /** - * @author Kai Salmen / www.kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader */ const WorkerTaskManagerDefaultRouting = { diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js index dc9e44ebde1f4c..8948d9a1238876 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js @@ -1,5 +1,5 @@ /** - * @author Kai Salmen / www.kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader */ import { diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js index e5d4b1cc2a7e1a..8c39633349fdf9 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js @@ -1,5 +1,5 @@ /** - * @author Kai Salmen / www.kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader */ import { GeometryTransport } from "../utils/TransportUtils.js"; diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 173220fed89f4b..ae791c5a366d53 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -252,7 +252,7 @@ let torus = new THREE.TorusBufferGeometry( 25, 8, 16, 100 ); torus.name = 'torus'; - const sender = new GeometryTransport().setGeometry( torus, 0 ).package( false ); + const sender = new GeometryTransport().setGeometry( torus, 0 ).package( true ); this.tasksToUse.push( taskDescr ); this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, sender.getMain(), sender.getTransferables() ).catch( e => console.error( e ) ) ); From 2bd5698845d91ddfbbb3b0956317c58a93d52855 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Fri, 19 Feb 2021 13:57:16 +0100 Subject: [PATCH 07/12] Updated documentation of webgl_loader_workertaskmanager.html --- examples/webgl_loader_workertaskmanager.html | 63 ++++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index ae791c5a366d53..9f724f5007900e 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -41,17 +41,20 @@ * - Standard Workers with dependency loading * - Module Workers with and without additional dependencies * - Main Fallback without dependencies - * - It also allows to use OBJLoader with and without modules. + * - It also allows to use OBJLoader in wrapper (tmOBJLoader.js with and without modules. * * Via dat.gui it is possible to control various parameters of the example: - * - The quantity of workers created for each task (default: 4) - * - The absolute overall count of task executions (default: 1000000) - * - The maximum amount of task executions per loop (=number of promises returned, default: 1000) - * - How many meshes shall be kept as otherwise the continuous loading will (default: 750) + * - The quantity of workers created for each task (1-32, default: 4) + * - The absolute overall count of task executions (10^3-10^7, default: 10^6) + * - The maximum amount of task executions per loop (=number of promises returned, 1-10000, default: 1000) + * - How many meshes shall be kept as otherwise the continuous loading will (100-10000, default: 750) * - * The tasks perform the same loading operation over and over again. This shall demonstrate: + * The tasks perform the same loading operation over and over again. + * This is not what you want to do in a real-world loading scenario, + * but it is very helpful to demonstrate: * - A good CPU utilization can be achieved permanently if the selected amount of workers match the logical CPUs available - * - No memory is leaked + * - No memory is leaked, by the workers + * - It can be extended or altered to test new worker implementations */ class TaskManagerPrototypeExample { @@ -106,6 +109,7 @@ this.workerTaskManager = new WorkerTaskManager(); this.workerTaskManager.setVerbose( true ); + // configure all task that shall be usable on register to the WorkerTaskManager this.taskDescriptions.clear(); this.taskDescriptions.set( 'tmProtoExample', { name: 'tmProtoExample', @@ -223,10 +227,13 @@ /** * Registers any selected task at the {@link WorkerTaskManager} and initializes them. + * The initialization varies. Some need task only pass dummy params others need + * to init and send buffers to the workers * * @return {Promise} */ async initContent () { + let awaiting = []; this.tasksToUse = []; @@ -302,14 +309,15 @@ return await Promise.all( awaiting ); - } - else { + } else { return new Promise( ( resolve, reject ) => { reject ( 'No task type has been configured' ) } ) } + } + /** Only once needed for OBJ/MTL initialization */ async loadObjMtl ( taskDescr ) { let fileLoader = new FileLoader(); @@ -326,7 +334,7 @@ materialCreator.preload(); taskDescr.materialStore.addMaterials( materialCreator.materials, false ); - } ) ; + } ); return await fileLoader.loadAsync( taskDescr.filenameObj ); } @@ -334,7 +342,6 @@ /** * Once all tasks are initialized a number of tasks (maxPerLoop) are enqueued. * This is repeated a configured number of times (loopCount) or the abort flag is set. - * * @return {Promise} */ async executeWorkers () { @@ -373,14 +380,16 @@ this.workerTaskManager.dispose(); console.timeEnd( 'start' ); - if ( this.reset ) { - - this.resetAppContext(); - - } + if ( this.reset ) this.resetAppContext(); } + /** + * This method is invoked when {@link WorkerTaskManager} received a message from a worker. + * @param {object} taskDescr + * @param {object} payload Message received from worker + * @private + */ _processMessage ( taskDescr, payload ) { let mesh, material; switch ( payload.cmd ) { @@ -436,6 +445,9 @@ } } + /** + * Add mesh at random position, but keep sub-meshes of an object together + */ _addMesh( mesh, id ) { let storedPos = this.objectsUsed.get( id ); @@ -462,6 +474,10 @@ } + /** + * Ensures that only the configured amount of meshes stay in the scene + * @private + */ _cleanMeshes() { if ( this.meshesAdded.length >= this.numberOfMeshesToKeep ) { @@ -472,6 +488,11 @@ } + /** + * Perform the actual deletion of meshes from the scene. + * @param {number} deleteRange + * @private + */ _deleteMeshRange( deleteRange ) { let toBeRemoved; @@ -539,6 +560,9 @@ } + /** + * Simplest way to define a worker for {@link WorkerTaskManager} + */ const WorkerFunctions = { workerStandardInit: function ( context, id, config ) { @@ -575,6 +599,9 @@ let app = new TaskManagerPrototypeExample( document.getElementById( 'example' ) ); app.resetAppContext(); + /** + * DAT UI configuration and behaviour. + */ let tmControls = { controls: [], controlStop: null, @@ -712,11 +739,11 @@ tmControls.controls[ index ].onChange( value => { app.workerTaskManager.setMaxParallelExecutions( value ) } ); index++; - tmControls.controls[ index ] = gui.add( tmControls, 'overallExecutionCount', 0, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); + tmControls.controls[ index ] = gui.add( tmControls, 'overallExecutionCount', 1000, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); tmControls.controls[ index ].onChange( value => { app.overallExecutionCount = value; app._recalcExecutionNumbers() } ); index++; - tmControls.controls[ index ] = gui.add( tmControls, 'maxPerLoop', 0, 10000 ).step( 100 ).name( 'Loop executions' ); + tmControls.controls[ index ] = gui.add( tmControls, 'maxPerLoop', 1, 10000 ).step( 100 ).name( 'Loop executions' ); tmControls.controls[ index ].onChange( value => { app.maxPerLoop = value; app._recalcExecutionNumbers() } ); index++; From 687d35a434a434f4c4c615c7acffa5cc66fdaa6a Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 22 Feb 2021 23:33:50 +0100 Subject: [PATCH 08/12] Simplified example --- examples/webgl_loader_workertaskmanager.html | 690 ++++--------------- 1 file changed, 132 insertions(+), 558 deletions(-) diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 9f724f5007900e..59aed165c5ed95 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -20,7 +20,6 @@ 'use strict'; import * as THREE from '../build/three.module.js'; - import { GUI } from './jsm/libs/dat.gui.module.js'; import { TrackballControls } from "./jsm/controls/TrackballControls.js"; import { WorkerTaskManager } from "./jsm/loaders/workerTaskManager/WorkerTaskManager.js"; @@ -31,34 +30,24 @@ MaterialsTransport, ObjectUtils } from "./jsm/loaders/workerTaskManager/utils/TransportUtils.js" + import { MaterialUtils } from "./jsm/loaders/workerTaskManager/utils/MaterialUtils.js" import { MaterialStore } from "./jsm/loaders/workerTaskManager/utils/MaterialStore.js" import { FileLoader } from "../src/loaders/FileLoader.js"; - import { MTLLoader } from "./jsm/loaders/MTLLoader.js"; import { OBJLoaderWorker } from "./jsm/loaders/workerTaskManager/worker/tmOBJLoader.js"; /** - * The aim of this example is to show all possible ways how to use the {@link WorkerTaskManager}: - * - Standard Workers with dependency loading - * - Module Workers with and without additional dependencies - * - Main Fallback without dependencies - * - It also allows to use OBJLoader in wrapper (tmOBJLoader.js with and without modules. + * The aim of this example is to show two possible ways how to use the {@link WorkerTaskManager}: + * - Worker defined inline + * - Wrapper around OBJLoader, so it can be executed as worker * - * Via dat.gui it is possible to control various parameters of the example: - * - The quantity of workers created for each task (1-32, default: 4) - * - The absolute overall count of task executions (10^3-10^7, default: 10^6) - * - The maximum amount of task executions per loop (=number of promises returned, 1-10000, default: 1000) - * - How many meshes shall be kept as otherwise the continuous loading will (100-10000, default: 750) - * - * The tasks perform the same loading operation over and over again. - * This is not what you want to do in a real-world loading scenario, - * but it is very helpful to demonstrate: - * - A good CPU utilization can be achieved permanently if the selected amount of workers match the logical CPUs available - * - No memory is leaked, by the workers - * - It can be extended or altered to test new worker implementations + * The workers perform the same loading operation over and over again. This is not what you want to do + * in a real-world loading scenario, but it is very helpful to demonstrate that workers executed in + * parallel to main utilizes the CPU. */ - class TaskManagerPrototypeExample { + class WorkerTaskManagerExample { constructor ( elementToBindTo ) { + this.renderer = null; this.canvas = elementToBindTo; this.aspectRatio = 1; @@ -75,127 +64,16 @@ this.cameraTarget = this.cameraDefaults.posCameraTarget; this.controls = null; - this.workerTaskManager = null; - - this.taskDescriptions = new Map (); - this.tasksToUse = []; - this.executions = []; this.objectsUsed = new Map(); - this.meshesAdded = []; - this.meshCount = 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 ); - } - - _recalcExecutionNumbers () { - this.loopCount = this.overallExecutionCount / this.maxPerLoop; - } - - resetAppContext () { - this.workerTaskManager = new WorkerTaskManager(); - this.workerTaskManager.setVerbose( true ); - - // configure all task that shall be usable on register to the WorkerTaskManager - this.taskDescriptions.clear(); - this.taskDescriptions.set( 'tmProtoExample', { - name: 'tmProtoExample', - use: true, - fallback: false, - funcInit: WorkerFunctions.workerStandardInit, - funcExec: WorkerFunctions.workerStandardExec, - dependencies: [ - { url: "../build/three.min.js" }, - { code: ObjectUtils.serializeClass( THREE.BufferGeometry ) }, - { code: ObjectUtils.serializeClass( DataTransport ) }, - { code: ObjectUtils.serializeClass( GeometryTransport ) } - ] - } ); - this.taskDescriptions.set( 'tmProtoExampleModule', { - name: 'tmProtoExampleModule', - use: true, - fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmModuleExample.js' - } ); - this.taskDescriptions.set( 'tmProtoExampleModuleNoThree', { - name: 'tmProtoExampleModuleNoThree', - use: true, - fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js' - } ); - this.taskDescriptions.set( 'tmProtoExampleMain', { - name: 'tmProtoExampleMain', - use: false, - fallback: true, - funcInit: WorkerFunctions.workerStandardInit, - funcExec: WorkerFunctions.workerStandardExec - } ); - this.taskDescriptions.set( 'tmOBJLoaderModule', { - name: 'tmOBJLoaderModule', - use: true, - fallback: false, - module: './jsm/loaders/workerTaskManager/worker/tmOBJLoader.js', - filenameMtl: './models/obj/female02/female02.mtl', - filenameObj: './models/obj/female02/female02.obj', - materialStore: new MaterialStore( true ), - } ); - this.taskDescriptions.set( 'tmOBJLoaderStandard', { - name: 'tmOBJLoaderStandard', - use: true, - fallback: false, - funcInit: OBJLoaderWorker.init, - funcExec: OBJLoaderWorker.execute, - filenameMtl: './models/obj/male02/male02.mtl', - filenameObj: './models/obj/male02/male02.obj', - materialStore: new MaterialStore( true ), - dependencies: OBJLoaderWorker.buildStandardWorkerDependencies( '../build/three.min.js', '../examples/js/loaders/OBJLoader.js' ) - } ); + this.workerTaskManager = new WorkerTaskManager( 4 ).setVerbose( true ); this.tasksToUse = []; - this.executions = []; - this.objectsUsed = new Map(); - - if ( this.reset !== null ) { - - this._deleteMeshRange( this.meshesAdded.length ); - this.reset(); - this.reset = null; + this.materialStore = new MaterialStore( true ); - } - this.meshesAdded = []; - this.meshCount = 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; - - // 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 ); } initGL () { + this.renderer = new THREE.WebGLRenderer( { canvas: this.canvas, antialias: true, @@ -223,127 +101,91 @@ let helper = new THREE.GridHelper( 1000, 30, 0xFF4444, 0x404040 ); this.scene.add( helper ); + } - /** - * Registers any selected task at the {@link WorkerTaskManager} and initializes them. - * The initialization varies. Some need task only pass dummy params others need - * to init and send buffers to the workers - * - * @return {Promise} - */ + /** Registers both workers as tasks at the {@link WorkerTaskManager} and initializes them. */ async initContent () { - let awaiting = []; - this.tasksToUse = []; - - let taskDescr = this.taskDescriptions.get( 'tmProtoExample' ); - if ( taskDescr.use ) { + /** Simplest way to define a worker for {@link WorkerTaskManager} */ + const InlineWorker = { - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); - - } - taskDescr = this.taskDescriptions.get( 'tmProtoExampleModule' ); - if ( taskDescr.use ) { + workerStandardInit: function ( context, id, config ) { - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + context.storage = { whoami: config.id }; + context.postMessage( { cmd: "init", id: id } ); - } - taskDescr = this.taskDescriptions.get( 'tmProtoExampleModuleNoThree' ); - if ( taskDescr.use ) { + }, - let torus = new THREE.TorusBufferGeometry( 25, 8, 16, 100 ); - torus.name = 'torus'; + workerStandardExec: function ( context, id, config ) { - const sender = new GeometryTransport().setGeometry( torus, 0 ).package( true ); - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, sender.getMain(), sender.getTransferables() ).catch( e => console.error( e ) ) ); + let bufferGeometry = new THREE.SphereBufferGeometry( 40, 64, 64 ); + bufferGeometry.name = 'InlineWorker' + config.id; + let vertexArray = bufferGeometry.getAttribute( 'position' ).array; + for ( let i = 0; i < vertexArray.length; i ++ ) vertexArray[ i ] = vertexArray[ i ] * Math.random() * 0.48; + new MeshTransport( 'execComplete', config.id ) + .setGeometry( bufferGeometry, 0 ) + .package( false ) + .postMessage( context ); - } - taskDescr = this.taskDescriptions.get( 'tmProtoExampleMain' ); - if ( taskDescr.use ) { + }, - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, true ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + buildStandardWorkerDependencies( threeJsLocation ) { - } - taskDescr = this.taskDescriptions.get( 'tmOBJLoaderModule' ); - if ( taskDescr.use ) { - - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskTypeModule( taskDescr.name, taskDescr.module ); - await this.loadObjMtl( taskDescr ) - .then( buffer => { - const mt = new MaterialsTransport() - .addBuffer( 'modelData', buffer ) - .setMaterials( taskDescr.materialStore.getMaterials() ) - .cleanMaterials() - .package( false ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); - } ); + return [ + { url: threeJsLocation }, + { code: ObjectUtils.serializeClass( THREE.BufferGeometry ) }, + { code: ObjectUtils.serializeClass( DataTransport ) }, + { code: ObjectUtils.serializeClass( GeometryTransport ) }, + { code: ObjectUtils.serializeClass( MaterialUtils ) }, + { code: ObjectUtils.serializeClass( MaterialsTransport ) }, + { code: ObjectUtils.serializeClass( MeshTransport ) } + ]; - } - taskDescr = this.taskDescriptions.get( 'tmOBJLoaderStandard' ); - if ( taskDescr.use ) { - - this.tasksToUse.push( taskDescr ); - this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); - await this.loadObjMtl( taskDescr ) - .then( buffer => { - const mt = new MaterialsTransport() - .addBuffer( 'modelData', buffer ) - .setMaterials( taskDescr.materialStore.getMaterials() ) - .cleanMaterials() - .package( false ); - awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); - } ); + } } - if ( awaiting.length > 0 ) { - return await Promise.all( awaiting ); - - } else { - - return new Promise( ( resolve, reject ) => { reject ( 'No task type has been configured' ) } ) + let awaiting = []; + let taskDescr = { + name: 'InlineWorker', + funcInit: InlineWorker.workerStandardInit, + funcExec: InlineWorker.workerStandardExec, + dependencies: InlineWorker.buildStandardWorkerDependencies( "../build/three.min.js" ) + }; + this.tasksToUse.push( taskDescr ); + this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); + awaiting.push( this.workerTaskManager.initTaskType( taskDescr.name, { param1: 'param1value' } ).catch( e => console.error( e ) ) ); + const taskDescrObj = { + name: 'OBJLoaderStandard', + filenameObj: './models/obj/female02/female02_vertex_colors.obj', + funcInit: OBJLoaderWorker.init, + funcExec: OBJLoaderWorker.execute, + dependencies: OBJLoaderWorker.buildStandardWorkerDependencies( '../build/three.min.js', '../examples/js/loaders/OBJLoader.js' ) + }; + this.tasksToUse.push( taskDescrObj ); + this.workerTaskManager.registerTaskType( taskDescrObj.name, taskDescrObj.funcInit, taskDescrObj.funcExec, null, false, taskDescrObj.dependencies ); + const loadObj = async function ( filenameObj ) { + let fileLoader = new FileLoader(); + fileLoader.setResponseType( 'arraybuffer' ); + return await fileLoader.loadAsync( filenameObj ); } + await loadObj( taskDescrObj.filenameObj ) + .then( buffer => { + const mt = new MaterialsTransport() + .addBuffer( 'modelData', buffer ) + .setMaterials( this.materialStore.getMaterials() ) + .cleanMaterials() + .package( false ); + awaiting.push( this.workerTaskManager.initTaskType( taskDescrObj.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); + } ); - } - - /** Only once needed for OBJ/MTL initialization */ - async loadObjMtl ( taskDescr ) { - - let fileLoader = new FileLoader(); - fileLoader.setResponseType( 'arraybuffer' ); - - let loadMtl = new Promise(resolve => { - - let mtlLoader = new MTLLoader(); - mtlLoader.load( taskDescr.filenameMtl, resolve ); - - } ); - await loadMtl.then( materialCreator => { - - materialCreator.preload(); - taskDescr.materialStore.addMaterials( materialCreator.materials, false ); - - } ); - return await fileLoader.loadAsync( taskDescr.filenameObj ); + return await Promise.all( awaiting ); } - /** - * Once all tasks are initialized a number of tasks (maxPerLoop) are enqueued. - * This is repeated a configured number of times (loopCount) or the abort flag is set. - * @return {Promise} - */ + /** Once all tasks are initialized a 100 tasks are enqueued for execution by WorkerTaskManager. */ async executeWorkers () { if ( this.tasksToUse.length === 0 ) throw "No Tasks have been selected. Aborting..." @@ -351,413 +193,145 @@ console.time( 'start' ); let globalCount = 0; let taskToUseIndex = 0; - for ( let j = 0; j < this.loopCount && !this.abort; j++ ) { - - console.time( 'Completed ' + ( this.maxPerLoop + j * this.maxPerLoop ) ); - for ( let i = 0; i < this.maxPerLoop; i ++ ) { - - let taskDescr = this.tasksToUse[ taskToUseIndex ]; + const executions = []; - const tb = new DataTransport( 'execute', globalCount ).setParams( { modelName: taskDescr.name } ); - let promise = this.workerTaskManager.enqueueForExecution( taskDescr.name, tb.getMain(), - data => this._processMessage( taskDescr, data ) ) - .then( data => this._processMessage( taskDescr, data ) ) - .catch( e => console.error( e ) ) - this.executions.push( promise ); + for ( let i = 0; i < 1000; i ++ ) { - globalCount++; - taskToUseIndex++; - if ( taskToUseIndex === this.tasksToUse.length ) taskToUseIndex = 0; - - } - await Promise.all( this.executions ).then( x => { + let taskDescr = this.tasksToUse[ taskToUseIndex ]; + const tb = new DataTransport( 'execute', globalCount ).setParams( { modelName: taskDescr.name } ); + let promise = this.workerTaskManager.enqueueForExecution( taskDescr.name, tb.getMain(), + data => this._processMessage( data ) ) + .then( data => this._processMessage( data ) ) + .catch( e => console.error( e ) ) + executions.push( promise ); - this.executions = []; - console.timeEnd( 'Completed ' + ( this.maxPerLoop + j * this.maxPerLoop ) ); + globalCount++; + taskToUseIndex++; + if ( taskToUseIndex === this.tasksToUse.length ) taskToUseIndex = 0; - } ); } - this.workerTaskManager.dispose(); - console.timeEnd( 'start' ); + await Promise.all( executions ).then( x => { + + console.timeEnd( 'start' ); + this.workerTaskManager.dispose(); - if ( this.reset ) this.resetAppContext(); + } ); } /** * This method is invoked when {@link WorkerTaskManager} received a message from a worker. - * @param {object} taskDescr * @param {object} payload Message received from worker * @private */ - _processMessage ( taskDescr, payload ) { - let mesh, material; + _processMessage ( payload ) { switch ( payload.cmd ) { case 'init': console.log( 'Init Completed: ' + payload.id ); break; - case 'execComplete': case 'assetAvailable': - switch ( payload.type ) { + case 'execComplete': + if ( payload.type === 'MeshTransport' ) { - case 'GeometryTransport': - const geometryTransport = new GeometryTransport().loadData( payload ).reconstruct( payload.geometry ); + const meshTransport = new MeshTransport().loadData( payload ).reconstruct( false ); + + const materialsTransport = meshTransport.getMaterialsTransport(); + let material = materialsTransport.processMaterialTransport( this.materialStore ? this.materialStore.getMaterials() : {}, true ); + if ( material === undefined || material === null ) { let randArray = new Uint8Array( 3 ); window.crypto.getRandomValues( randArray ); - const color = new THREE.Color(); - color.r = randArray[ 0 ] / 255; - color.g = randArray[ 1 ] / 255; - color.b = randArray[ 2 ] / 255; + const color = new THREE.Color( randArray[ 0 ] / 255, randArray[ 1 ] / 255, randArray[ 2 ] / 255 ); material = new THREE.MeshPhongMaterial( { color: color } ); - mesh = new THREE.Mesh( geometryTransport.getBufferGeometry(), material ); - this._addMesh( mesh, geometryTransport.getId() ); - break; - - case 'MeshTransport' : - const meshTransport = new MeshTransport().loadData( payload ).reconstruct( false ); - - const materialsTransport = meshTransport.getMaterialsTransport(); - material = materialsTransport.processMaterialTransport( taskDescr.materialStore ? taskDescr.materialStore.getMaterials() : {}, true ); - if ( material === null ) material = new THREE.MeshStandardMaterial( { color: 0xFF0000 } ); + } + const mesh = new THREE.Mesh( meshTransport.getBufferGeometry(), material ); + this._addMesh( mesh, meshTransport.getId() ); - mesh = new THREE.Mesh( meshTransport.getBufferGeometry(), material ); - this._addMesh( mesh, meshTransport.getId() ); - break; + } else if ( payload.type !== 'DataTransport' ) { - case 'DataTransport': - if ( payload.cmd !== 'execComplete' ) console.log( 'DataTransport' ); - break; - - default: - console.error( 'Provided payload.type was neither mesh nor assetAvailable: ' + payload.cmd ); - break; + console.error( 'Provided payload.type was neither mesh nor assetAvailable: ' + payload.cmd ); } - this._cleanMeshes(); break; default: console.error( payload.id + ': Received unknown command: ' + payload.cmd ); break; + } } - /** - * Add mesh at random position, but keep sub-meshes of an object together - */ + /** Add mesh at random position, but keep sub-meshes of an object together, therefore we need */ _addMesh( mesh, id ) { - let storedPos = this.objectsUsed.get( id ); - let pos; - if ( storedPos ) { - - pos = storedPos.pos; - - } - else { + let pos = this.objectsUsed.get( id ); + if ( pos === undefined ) { - pos = new THREE.Vector3( this.baseFactor * Math.random(), this.baseFactor * Math.random(), this.baseFactor * Math.random() ); - pos.applyAxisAngle( this.baseVectorX, 2 * Math.PI * Math.random() ); - pos.applyAxisAngle( this.baseVectorY, 2 * Math.PI * Math.random() ); - pos.applyAxisAngle( this.baseVectorZ, 2 * Math.PI * Math.random() ); - this.objectsUsed.set( id, { name: mesh.name, pos: pos } ); + // sphere positions + const baseFactor = 750; + pos = new THREE.Vector3( baseFactor * Math.random(), baseFactor * Math.random(), baseFactor * Math.random() ); + pos.applyAxisAngle( new THREE.Vector3( 1, 0, 0 ), 2 * Math.PI * Math.random() ); + pos.applyAxisAngle( new THREE.Vector3( 0, 1, 0 ), 2 * Math.PI * Math.random() ); + pos.applyAxisAngle( new THREE.Vector3( 0, 0, 1 ), 2 * Math.PI * Math.random() ); + this.objectsUsed.set( id, pos ); } mesh.position.set( pos.x, pos.y, pos.z ); mesh.name = id + '' + mesh.name; this.scene.add( mesh ); - this.meshesAdded.push( mesh.name ); - this.meshCount ++; - - } - - /** - * Ensures that only the configured amount of meshes stay in the scene - * @private - */ - _cleanMeshes() { - - if ( this.meshesAdded.length >= this.numberOfMeshesToKeep ) { - - this._deleteMeshRange( this.removeCount ); - - } - - } - - /** - * Perform the actual deletion of meshes from the scene. - * @param {number} deleteRange - * @private - */ - _deleteMeshRange( deleteRange ) { - - let toBeRemoved; - let deleteCount = 0; - let i = 0; - while ( deleteCount < deleteRange && i < this.meshesAdded.length ) { - - let meshName = this.meshesAdded[ i ]; - toBeRemoved = this.scene.getObjectByName( meshName ); - if ( toBeRemoved ) { - - toBeRemoved.geometry.dispose(); - if ( toBeRemoved.material !== undefined && toBeRemoved.material !== null && toBeRemoved.material.dispose instanceof Function ) { - - toBeRemoved.material.dispose(); - - } - this.scene.remove( toBeRemoved ); - this.meshesAdded.splice( i, 1 ); - deleteCount++; - - } - else { - - i++; - console.log( 'Unable to remove: ' + meshName ); - - } - - } } resizeDisplayGL () { - this.controls.handleResize(); + this.controls.handleResize(); this.recalcAspectRatio(); this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false ); - this.updateCamera(); + } recalcAspectRatio () { + this.aspectRatio = (this.canvas.offsetHeight === 0) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight; + } resetCamera () { + this.camera.position.copy( this.cameraDefaults.posCamera ); this.cameraTarget.copy( this.cameraDefaults.posCameraTarget ); - this.updateCamera(); + } updateCamera () { + this.camera.aspect = this.aspectRatio; this.camera.lookAt( this.cameraTarget ); this.camera.updateProjectionMatrix(); + } render() { + if ( !this.renderer.autoClear ) this.renderer.clear(); this.controls.update(); this.renderer.render( this.scene, this.camera ); - } - - } - - /** - * Simplest way to define a worker for {@link WorkerTaskManager} - */ - const WorkerFunctions = { - - workerStandardInit: function ( context, id, config ) { - context.storage = { - whoami: config.id, - }; - - context.postMessage( { - cmd: "init", - id: id - } ); - - }, - - workerStandardExec: function ( context, id, config ) { - - let bufferGeometry = new THREE.SphereBufferGeometry( 40, 64, 64 ); - bufferGeometry.name = 'tmProto' + config.id; - let vertexArray = bufferGeometry.getAttribute( 'position' ).array; - for ( let i = 0; i < vertexArray.length; i ++ ) { - - vertexArray[ i ] = vertexArray[ i ] * Math.random() * 0.48; - - } - new GeometryTransport( 'execComplete', config.id ) - .setGeometry( bufferGeometry, 0 ) - .package( false ) - .postMessage( context ); } } - let app = new TaskManagerPrototypeExample( document.getElementById( 'example' ) ); - app.resetAppContext(); - - /** - * DAT UI configuration and behaviour. - */ - let tmControls = { - controls: [], - controlStop: null, - controlReset: null, - started: false, - tmProtoExampleName: false, - tmProtoExampleModule: false, - tmProtoExampleModuleNoThree: false, - tmProtoExampleMain: false, - tmOBJLoaderModule: false, - tmOBJLoaderStandard: false, - maxParallelExecutions: 0, - overallExecutionCount: 0, - numberOfMeshesToKeep: 0, - maxPerLoop: 0, - resetContent () { - - this.tmProtoExampleName = app.taskDescriptions.get( 'tmProtoExample' ).use; - this.tmProtoExampleModule = app.taskDescriptions.get( 'tmProtoExampleModule' ).use; - this.tmProtoExampleModuleNoThree = app.taskDescriptions.get( 'tmProtoExampleModuleNoThree' ).use; - this.tmProtoExampleMain = app.taskDescriptions.get( 'tmProtoExampleMain' ).use; - this.tmOBJLoaderModule = app.taskDescriptions.get( 'tmOBJLoaderModule' ).use; - this.tmOBJLoaderStandard = app.taskDescriptions.get( 'tmOBJLoaderStandard' ).use; - this.maxParallelExecutions = app.workerTaskManager.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(); - - }, - disableElement ( elementHandle ) { - - elementHandle.domElement.addEventListener( 'click', this.blockEvent, true ); - 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 () { - - 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(); - - } - - } - }; + let app = new WorkerTaskManagerExample( document.getElementById( 'example' ) ); - let menuDiv = document.getElementById( 'dat' ); - let gui = new GUI( { - autoPlace: false, - width: 400 - } ); - menuDiv.appendChild( gui.domElement ); - - let taskName0 = 'tmProtoExample'; - let index = 0; - tmControls.controls[ index ] = gui.add( tmControls, taskName0 + 'Name' ).name( 'Worker Standard + three' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName0 ).use = value; } ); - - let taskName1 = 'tmProtoExampleModule'; - index++; - tmControls.controls[ index ] = gui.add( tmControls, taskName1 ).name( 'Worker Module + three' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName1 ).use = value; } ); - - let taskName2 = 'tmProtoExampleModuleNoThree'; - index++; - tmControls.controls[ index ] = gui.add( tmControls, taskName2 ).name( 'Worker Module solo' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName2 ).use = value; } ); - - let taskName3 = 'tmProtoExampleMain'; - index++; - tmControls.controls[ index ] = gui.add( tmControls, taskName3 ).name( 'Worker Standard Main' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName3 ).use = value; } ); - - let taskName4 = 'tmOBJLoaderModule'; - index++; - tmControls.controls[ index ] = gui.add( tmControls, taskName4 ).name( 'OBJLoader Module' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName4 ).use = value; } ); - - let taskName5 = 'tmOBJLoaderStandard'; - index++; - tmControls.controls[ index ] = gui.add( tmControls, taskName5 ).name( 'OBJLoader Standard' ); - tmControls.controls[ index ].onChange( value => { app.taskDescriptions.get( taskName5 ).use = value; } ); - - index++; - tmControls.controls[ index ] = gui.add( tmControls, 'maxParallelExecutions', 1, 32 ).step( 1 ).name( 'Maximum Parallel Executions' ); - tmControls.controls[ index ].onChange( value => { app.workerTaskManager.setMaxParallelExecutions( value ) } ); - - index++; - tmControls.controls[ index ] = gui.add( tmControls, 'overallExecutionCount', 1000, 10000000 ).step( 1000 ).name( 'Overall Execution Count' ); - tmControls.controls[ index ].onChange( value => { app.overallExecutionCount = value; app._recalcExecutionNumbers() } ); - - index++; - tmControls.controls[ index ] = gui.add( tmControls, 'maxPerLoop', 1, 10000 ).step( 100 ).name( 'Loop executions' ); - tmControls.controls[ index ].onChange( value => { app.maxPerLoop = value; app._recalcExecutionNumbers() } ); - - index++; - tmControls.controls[ index ] = gui.add( tmControls, 'numberOfMeshesToKeep', 100, 10000 ).step( 25 ).name( 'Keep N Meshes' ); - tmControls.controls[ index ].onChange( value => { app.numberOfMeshesToKeep = value } ); - - index++; - tmControls.controls[ index ] = gui.add( tmControls, 'executeLoading' ).name( 'Engage' ); - tmControls.controls[ index ].domElement.id = 'startButton'; - - tmControls.controlStop = gui.add( tmControls, 'stopExecution' ).name( 'Stop' ); - tmControls.controlReset = gui.add( tmControls, 'resetExecution' ).name( 'Reset' ); - - tmControls.resetContent(); + console.time( 'Init tasks' ); + app.initContent().then( x => { + console.timeEnd( 'Init tasks' ); + app.executeWorkers(); + } ).catch( x => alert( x ) ); let resizeWindow = function () { app.resizeDisplayGL(); From 0fb53c3a77243e6cf94b5bc1cc052753fd173352 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 22 Feb 2021 23:37:00 +0100 Subject: [PATCH 09/12] Removed unneeded workers --- .../worker/tmModuleExample.js | 65 ------------------- .../worker/tmModuleExampleNoThree.js | 45 ------------- 2 files changed, 110 deletions(-) delete mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js delete mode 100644 examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js deleted file mode 100644 index 8948d9a1238876..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExample.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Development repository: https://github.com/kaisalmen/WWOBJLoader - */ - -import { - TorusKnotBufferGeometry, - Color, - MeshPhongMaterial -} from "../../../../../build/three.module.js"; -import { - MeshTransport, - MaterialsTransport -} from "../utils/TransportUtils.js"; -import { - MaterialUtils -} from '../utils/MaterialUtils.js'; -import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; - - -function init ( context, id, config ) { - context.storage = { - whoami: id, - }; - - context.postMessage( { - cmd: "init", - id: id - } ); - -} - -function execute ( context, id, config ) { - - let bufferGeometry = new TorusKnotBufferGeometry( 20, 3, 100, 64 ); - bufferGeometry.name = 'tmProto' + config.id; - - let vertexBA = bufferGeometry.getAttribute( 'position' ) ; - let vertexArray = vertexBA.array; - for ( let i = 0; i < vertexArray.length; i++ ) { - - vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); - - } - - const randArray = new Uint8Array( 3 ); - context.crypto.getRandomValues( randArray ); - const color = new Color(); - color.r = randArray[ 0 ] / 255; - color.g = randArray[ 1 ] / 255; - color.b = randArray[ 2 ] / 255; - const material = new MeshPhongMaterial( { color: color } ); - - const materialsTransport = new MaterialsTransport(); - MaterialUtils.addMaterial( materialsTransport.main.materials, material, 'randomColor' + config.id, false, false ); - materialsTransport.cleanMaterials(); - - new MeshTransport( 'execComplete', config.id ) - .setGeometry( bufferGeometry, 2 ) - .setMaterialsTransport( materialsTransport ) - .package( false ) - .postMessage( context ); - -} - -self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js b/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js deleted file mode 100644 index 8c39633349fdf9..00000000000000 --- a/examples/jsm/loaders/workerTaskManager/worker/tmModuleExampleNoThree.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Development repository: https://github.com/kaisalmen/WWOBJLoader - */ - -import { GeometryTransport } from "../utils/TransportUtils.js"; -import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; - -function init ( context, id, config ) { - - context.config = config; - context.postMessage( { - cmd: "init", - id: id - } ); - -} - -function execute ( context, id, config ) { - - const geometry = new GeometryTransport().loadData( context.config ).reconstruct( true ).getBufferGeometry(); - geometry.name = 'tmProto' + config.id; - let vertexArray = geometry.getAttribute( 'position' ).array; - for ( let i = 0; i < vertexArray.length; i++ ) { - - vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); - - } - - const sender = new GeometryTransport( 'execComplete', config.id ) - .setGeometry( geometry, 1 ) - .package( false ); - - let randArray = new Uint8Array( 3 ); - context.crypto.getRandomValues( randArray ); - sender.main.params.color = { - r: randArray[ 0 ] / 255, - g: randArray[ 1 ] / 255, - b: randArray[ 2 ] / 255 - }; - - sender.postMessage( context ); - -} - -self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, null, init, execute ), false ); From 67a8d4f9166bbb67452ea6f30949081c48326e19 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Tue, 13 Apr 2021 21:29:22 +0200 Subject: [PATCH 10/12] Use fake function call in OBJLoader worker wrapper allowing it to work with the unaltered loader. --- .../jsm/loaders/workerTaskManager/worker/tmOBJLoader.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js index 1baef3afc7cb91..dfdcaca758b44e 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js @@ -1,7 +1,3 @@ -/** - * @author Kai Salmen / www.kaisalmen.de - */ - import { DataTransport, MaterialsTransport, @@ -61,6 +57,8 @@ const OBJLoaderWorker = { const dataTransport = new DataTransport().loadData( config ); context.objLoader.loader.objectId = dataTransport.getId(); + let materials = context.objLoader.materials; + materials[ 'create' ] = function fakeMat( name ) { return materials[ name ]; }; context.objLoader.loader.setMaterials( context.objLoader.materials ); const enc = new TextDecoder("utf-8"); From 07bb155e28b478059f4c5fb7074fad9308605f06 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Thu, 29 Apr 2021 22:25:09 +0200 Subject: [PATCH 11/12] Fix missing EventDispatcher --- examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js | 1 + examples/webgl_loader_workertaskmanager.html | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js index dfdcaca758b44e..b7656879669574 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js @@ -21,6 +21,7 @@ const OBJLoaderWorker = { { code: 'const Material = THREE.Material;\n' }, { code: 'const Texture = THREE.Texture;\n' }, { code: 'const BufferGeometry = THREE.BufferGeometry;\n' }, + { code: 'const EventDispatcher = THREE.EventDispatcher;\n' }, { code: '\n\n' }, { url: objLoaderLocation }, { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 59aed165c5ed95..4d99270a32e1e3 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -134,7 +134,8 @@ return [ { url: threeJsLocation }, - { code: ObjectUtils.serializeClass( THREE.BufferGeometry ) }, + { code: 'const BufferGeometry = THREE.BufferGeometry;\n' }, + { code: 'const EventDispatcher = THREE.EventDispatcher;\n' }, { code: ObjectUtils.serializeClass( DataTransport ) }, { code: ObjectUtils.serializeClass( GeometryTransport ) }, { code: ObjectUtils.serializeClass( MaterialUtils ) }, From 3405ccbf99e34f2f3fbac02ecc5ab6cdf8948790 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Mon, 14 Jun 2021 21:54:03 +0200 Subject: [PATCH 12/12] Align code with latest code from three-wtm (1.0.0-beta.6) --- .../workerTaskManager/WorkerTaskManager.js | 88 ++++++++++++------- .../workerTaskManager/utils/MaterialStore.js | 3 +- .../workerTaskManager/utils/MaterialUtils.js | 10 ++- .../workerTaskManager/utils/TransportUtils.js | 83 ++++++++++++++--- .../worker/defaultRouting.js | 9 +- .../workerTaskManager/worker/tmOBJLoader.js | 35 ++++---- examples/webgl_loader_workertaskmanager.html | 46 +++++----- 7 files changed, 179 insertions(+), 95 deletions(-) diff --git a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js index fff8888fcb3848..5eb45f7a7c1a6c 100644 --- a/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js +++ b/examples/jsm/loaders/workerTaskManager/WorkerTaskManager.js @@ -1,6 +1,6 @@ /** - * Development repository: https://github.com/kaisalmen/WWOBJLoader - * Proposed by Don McCurdy / https://www.donmccurdy.com + * Development repository: https://github.com/kaisalmen/three-wtm + * Initial idea by Don McCurdy / https://www.donmccurdy.com */ import { FileLoader } from "../../../../build/three.module.js"; @@ -86,9 +86,9 @@ class WorkerTaskManager { * 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 - * @param {function} comRoutingFunction The function that should handle communication, leave undefined for default behavior + * @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 * @param {boolean} fallback Set to true if execution should be performed in main * @param {Object[]} [dependencyDescriptions] * @return {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) @@ -147,17 +147,17 @@ class WorkerTaskManager { if ( workerTypeDefinition.isWorkerModule() ) { await workerTypeDefinition.createWorkerModules() - .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) - .then( y => workerTypeDefinition.status.initComplete = true ) - .catch( x => console.error( x ) ); + .then( () => workerTypeDefinition.initWorkers( config, transferables ) ) + .then( () => workerTypeDefinition.status.initComplete = true ) + .catch( e => console.error( e ) ); } else { await workerTypeDefinition.loadDependencies() - .then( code => workerTypeDefinition.createWorkers() ) - .then( instances => workerTypeDefinition.initWorkers( config, transferables ) ) - .then( y => workerTypeDefinition.status.initComplete = true ) - .catch( x => console.error( x ) ); + .then( () => workerTypeDefinition.createWorkers() ) + .then( () => workerTypeDefinition.initWorkers( config, transferables ) ) + .then( () => workerTypeDefinition.status.initComplete = true ) + .catch( e => console.error( e ) ); } @@ -222,26 +222,25 @@ class WorkerTaskManager { this.actualExecutionCount ++; let promiseWorker = new Promise( ( resolveWorker, rejectWorker ) => { - taskWorker.onmessage = function ( e ) { + taskWorker.onmessage = message => { // allow intermediate asset provision before flagging execComplete - if ( e.data.cmd === 'assetAvailable' ) { + if ( message.data.cmd === 'assetAvailable' ) { if ( storedExecution.assetAvailableFunction instanceof Function ) { - storedExecution.assetAvailableFunction( e.data ); + storedExecution.assetAvailableFunction( message.data ); } } else { - resolveWorker( e ); + resolveWorker( message ); } }; taskWorker.onerror = rejectWorker; - taskWorker.postMessage( { cmd: "execute", workerId: taskWorker.getId(), @@ -249,10 +248,10 @@ class WorkerTaskManager { }, storedExecution.transferables ); } ); - promiseWorker.then( ( e ) => { + promiseWorker.then( ( message ) => { workerTypeDefinition.returnAvailableTask( taskWorker ); - storedExecution.resolve( e.data ); + storedExecution.resolve( message.data ); this.actualExecutionCount --; this._depleteExecutions(); @@ -309,11 +308,11 @@ class WorkerTypeDefinition { this.verbose = verbose === true; this.initialised = false; this.functions = { - /** @type {function} */ + /** @type {Function} */ init: null, - /** @type {function} */ + /** @type {Function} */ execute: null, - /** @type {function} */ + /** @type {Function} */ comRouting: null, dependencies: { /** @type {Object[]} */ @@ -354,9 +353,9 @@ 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 - * @param {function} [comRoutingFunction] The function that should handle communication, leave undefined for default behavior + * @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 */ setFunctions ( initFunction, executeFunction, comRoutingFunction ) { @@ -368,13 +367,32 @@ class WorkerTypeDefinition { this.functions.comRouting = WorkerTaskManagerDefaultRouting.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._addWorkerCode( 'init', this.functions.init.toString() ); + this._addWorkerCode( 'execute', this.functions.execute.toString() ); + this._addWorkerCode( 'comRouting', this.functions.comRouting.toString() ); this.workers.code.push( 'self.addEventListener( "message", message => comRouting( self, message, null, init, execute ), false );' ); } + /** + * + * @param {string} functionName Name of the function + * @param {string} functionString A function as string + * @private + */ + _addWorkerCode ( functionName, functionString ) { + if ( functionString.startsWith('function') ) { + + this.workers.code.push( 'const ' + functionName + ' = ' + functionString + ';\n\n' ); + + } else { + + this.workers.code.push( 'function ' + functionString + ';\n\n' ); + + } + + } + /** * Set the url of all dependent libraries (only used in non-module case). * @@ -415,7 +433,7 @@ class WorkerTypeDefinition { /** * Loads all dependencies and stores each as {@link ArrayBuffer} into the array. Returns if all loading is completed. * - * @return {} + * @return {String[]} */ async loadDependencies () { @@ -502,7 +520,12 @@ class WorkerTypeDefinition { let taskWorkerPromise = new Promise( ( resolveWorker, rejectWorker ) => { - taskWorker.onmessage = resolveWorker; + taskWorker.onmessage = message => { + + if ( this.verbose ) console.log( 'Init Complete: ' + message.data.id ); + resolveWorker( message ); + + } taskWorker.onerror = rejectWorker; // ensure all transferables are copies to all workers on init! @@ -524,6 +547,7 @@ class WorkerTypeDefinition { promises.push( taskWorkerPromise ); } + if ( this.verbose ) console.log( 'Task: ' + this.getTaskType() + ': Waiting for completion of initialization of all workers.'); await Promise.all( promises ); this.workers.available = this.workers.instances; @@ -652,8 +676,8 @@ class MockedTaskWorker { * Creates a new instance. * * @param {number} id - * @param {function} initFunction - * @param {function} executeFunction + * @param {Function} initFunction + * @param {Function} executeFunction */ constructor( id, initFunction, executeFunction ) { diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js index 2cff736dc290b9..55bdfb928a0e04 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialStore.js @@ -1,9 +1,8 @@ /** - * Development repository: https://github.com/kaisalmen/WWOBJLoader + * Development repository: https://github.com/kaisalmen/three-wtm */ import { - Material, MeshStandardMaterial, LineBasicMaterial, PointsMaterial, diff --git a/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js index 15e783f9f7a9b7..a80250e29b3f15 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/MaterialUtils.js @@ -1,9 +1,7 @@ /** - * Development repository: https://github.com/kaisalmen/WWOBJLoader + * Development repository: https://github.com/kaisalmen/three-wtm */ -import { Material } from "../../../../../build/three.module.js"; - /** * Static functions useful in the context of handling materials. */ @@ -62,7 +60,11 @@ class MaterialUtils { for ( const materialName in materialsObject ) { material = materialsObject[ materialName ]; - if ( material instanceof Material ) materialsJSON[ materialName ] = material.toJSON(); + if ( typeof material.toJSON === 'function' ) { + + materialsJSON[ materialName ] = material.toJSON(); + + } } return materialsJSON; diff --git a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js index eba5e1fdd0ad09..5f0e8e4e21174c 100644 --- a/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js +++ b/examples/jsm/loaders/workerTaskManager/utils/TransportUtils.js @@ -1,5 +1,5 @@ /** - * Development repository: https://github.com/kaisalmen/WWOBJLoader + * Development repository: https://github.com/kaisalmen/three-wtm */ import { @@ -8,12 +8,72 @@ import { Box3, Sphere, Texture, - Material, MaterialLoader } from "../../../../../build/three.module.js"; -import { - MaterialUtils -} from './MaterialUtils.js'; +import { MaterialUtils } from './MaterialUtils.js'; + +class DeUglify { + + static buildThreeConst () { + return 'const EventDispatcher = THREE.EventDispatcher;\n' + + 'const BufferGeometry = THREE.BufferGeometry;\n' + + 'const BufferAttribute = THREE.BufferAttribute;\n' + + 'const Box3 = THREE.Box3;\n' + + 'const Sphere = THREE.Sphere;\n' + + 'const Texture = THREE.Texture;\n' + + 'const MaterialLoader = THREE.MaterialLoader;\n'; + } + + static buildUglifiedThreeMapping () { + function _BufferGeometry() { return BufferGeometry; } + function _BufferAttribute () { return BufferAttribute; } + function _Box3 () { return Box3; } + function _Sphere () { return Sphere; } + function _Texture () { return Texture; } + function _MaterialLoader () { return MaterialLoader; } + + return DeUglify.buildUglifiedNameAssignment( _BufferGeometry, 'BufferGeometry', /_BufferGeometry/, false ) + + DeUglify.buildUglifiedNameAssignment( _BufferAttribute, 'BufferAttribute', /_BufferAttribute/, false ) + + DeUglify.buildUglifiedNameAssignment( _Box3, 'Box3', /_Box3/, false ) + + DeUglify.buildUglifiedNameAssignment( _Sphere, 'Sphere', /_Sphere/, false ) + + DeUglify.buildUglifiedNameAssignment( _Texture, 'Texture', /_Texture/, false ) + + DeUglify.buildUglifiedNameAssignment( _MaterialLoader, 'MaterialLoader', /_MaterialLoader/, false ); + } + + static buildUglifiedThreeWtmMapping () { + function _DataTransport () { return DataTransport; } + function _GeometryTransport () { return GeometryTransport; } + function _MeshTransport () { return MeshTransport; } + function _MaterialsTransport () { return MaterialsTransport; } + function _MaterialUtils () { return MaterialUtils; } + + return DeUglify.buildUglifiedNameAssignment( _DataTransport, 'DataTransport', /_DataTransport/, true ) + + DeUglify.buildUglifiedNameAssignment( _GeometryTransport, 'GeometryTransport', /_GeometryTransport/, true ) + + DeUglify.buildUglifiedNameAssignment( _MeshTransport, 'MeshTransport', /_MeshTransport/, true ) + + DeUglify.buildUglifiedNameAssignment( _MaterialsTransport, 'MaterialsTransport', /_MaterialsTransport/, true ) + + DeUglify.buildUglifiedNameAssignment( _MaterialUtils, 'MaterialUtils', /_MaterialUtils/, true ); + } + + static buildUglifiedNameAssignment(func, name, methodPattern, invert) { + let funcStr = func.toString(); + // remove the method name and any line breaks (rollup lib creation, non-uglify case + funcStr = funcStr.replace(methodPattern, "").replace(/[\r\n]+/gm, ""); + // remove return and any semi-colons + funcStr = funcStr.replace(/.*return/, "").replace(/\}/, "").replace(/;/gm, ""); + const retrieveNamed = funcStr.trim() + // return non-empty string in uglified case (name!=retrieveNamed); e.g. "const BufferGeometry = e"; + // return empty string in case of non-uglified lib/src + let output = ""; + if (retrieveNamed !== name) { + const left = invert ? name : retrieveNamed; + const right = invert ? retrieveNamed : name; + output = "const " + left + " = " + right + ";\n"; + } + return output; + } +} + + /** * Define a base structure that is used to ship data in between main and workers. @@ -181,7 +241,7 @@ class DataTransport { /** * Return all transferable in one array. - * @return {[]|ArrayBuffer[]} + * @return {ArrayBuffer[]} */ getTransferables() { @@ -314,7 +374,7 @@ class MaterialsTransport extends DataTransport { let clonedMaterial; for ( let material of Object.values( this.main.materials ) ) { - if ( material instanceof Material ) { + if ( typeof material.clone === 'function' ) { clonedMaterial = material.clone(); clonedMaterials[ clonedMaterial.name ] = this._cleanMaterial( clonedMaterial ); @@ -424,10 +484,8 @@ class GeometryTransport extends DataTransport { super( cmd, id ); this.main.type = 'GeometryTransport'; - /** - * @type {number} - * 0: mesh, 1: line, 2: point - */ + // 0: mesh, 1: line, 2: point + /** @type {number} */ this.main.geometryType = 0; /** @type {object} */ this.main.geometry = {}; @@ -808,5 +866,6 @@ export { MeshTransport, MaterialsTransport, ObjectUtils, - ObjectManipulator + ObjectManipulator, + DeUglify } diff --git a/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js b/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js index 2f25c430d91744..0c21fc9c9d0f1a 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js +++ b/examples/jsm/loaders/workerTaskManager/worker/defaultRouting.js @@ -1,10 +1,10 @@ /** - * Development repository: https://github.com/kaisalmen/WWOBJLoader + * Development repository: https://github.com/kaisalmen/three-wtm */ -const WorkerTaskManagerDefaultRouting = { +class WorkerTaskManagerDefaultRouting { - comRouting: function ( context, message, object, initFunction, executeFunction ) { + static comRouting ( context, message, object, initFunction, executeFunction ) { let payload = message.data; if ( payload.cmd === 'init' ) { @@ -12,6 +12,7 @@ const WorkerTaskManagerDefaultRouting = { if ( object !== undefined && object !== null ) { object[ initFunction ]( context, payload.workerId, payload.config ); + } else { initFunction( context, payload.workerId, payload.config ); @@ -34,6 +35,6 @@ const WorkerTaskManagerDefaultRouting = { } -}; +} export { WorkerTaskManagerDefaultRouting } diff --git a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js index b7656879669574..ac772d35db7fad 100644 --- a/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js +++ b/examples/jsm/loaders/workerTaskManager/worker/tmOBJLoader.js @@ -1,27 +1,24 @@ +import { OBJLoader } from '../../OBJLoader.js'; import { DataTransport, MaterialsTransport, GeometryTransport, MeshTransport, ObjectUtils, -} from "../utils/TransportUtils.js"; -import { - MaterialUtils -} from '../utils/MaterialUtils.js'; -import { OBJLoader } from "../../OBJLoader.js"; + DeUglify +} from '../utils/TransportUtils.js'; +import { MaterialUtils } from '../utils/MaterialUtils.js';; import { WorkerTaskManagerDefaultRouting } from "./defaultRouting.js"; -const OBJLoaderWorker = { +class OBJLoaderWorker { - buildStandardWorkerDependencies: function ( threeJsLocation, objLoaderLocation ) { + static buildStandardWorkerDependencies ( threeJsLocation, objLoaderLocation ) { return [ { url: threeJsLocation }, { code: '\n\n' }, - { code: 'const MaterialLoader = THREE.MaterialLoader;\n' }, - { code: 'const Material = THREE.Material;\n' }, - { code: 'const Texture = THREE.Texture;\n' }, - { code: 'const BufferGeometry = THREE.BufferGeometry;\n' }, - { code: 'const EventDispatcher = THREE.EventDispatcher;\n' }, + { code: DeUglify.buildThreeConst() }, + { code: '\n\n' }, + { code: DeUglify.buildUglifiedThreeMapping() }, { code: '\n\n' }, { url: objLoaderLocation }, { code: '\n\nconst OBJLoader = THREE.OBJLoader;\n\n' }, @@ -30,11 +27,13 @@ const OBJLoaderWorker = { { code: ObjectUtils.serializeClass( MaterialsTransport ) }, { code: ObjectUtils.serializeClass( MaterialUtils ) }, { code: ObjectUtils.serializeClass( GeometryTransport ) }, - { code: ObjectUtils.serializeClass( MeshTransport ) } + { code: ObjectUtils.serializeClass( MeshTransport ) }, + { code: DeUglify.buildUglifiedThreeWtmMapping() }, + { code: '\n\n' } ] - }, + } - init: function ( context, id, config ) { + static init ( context, id, config ) { const materialsTransport = new MaterialsTransport().loadData( config ); context.objLoader = { @@ -50,9 +49,9 @@ const OBJLoaderWorker = { cmd: "init", id: id } ); - }, + } - execute: function ( context, id, config ) { + static execute ( context, id, config ) { context.objLoader.loader = new OBJLoader(); const dataTransport = new DataTransport().loadData( config ); @@ -85,7 +84,7 @@ const OBJLoaderWorker = { } -}; +} self.addEventListener( 'message', message => WorkerTaskManagerDefaultRouting.comRouting( self, message, OBJLoaderWorker, 'init', 'execute' ), false ); diff --git a/examples/webgl_loader_workertaskmanager.html b/examples/webgl_loader_workertaskmanager.html index 4d99270a32e1e3..f5279f0bad8f57 100644 --- a/examples/webgl_loader_workertaskmanager.html +++ b/examples/webgl_loader_workertaskmanager.html @@ -20,20 +20,20 @@ 'use strict'; import * as THREE from '../build/three.module.js'; - import { TrackballControls } from "./jsm/controls/TrackballControls.js"; + import { WorkerTaskManager } from "./jsm/loaders/workerTaskManager/WorkerTaskManager.js"; import { DataTransport, GeometryTransport, MeshTransport, MaterialsTransport, - ObjectUtils + ObjectUtils, + DeUglify } from "./jsm/loaders/workerTaskManager/utils/TransportUtils.js" import { MaterialUtils } from "./jsm/loaders/workerTaskManager/utils/MaterialUtils.js" import { MaterialStore } from "./jsm/loaders/workerTaskManager/utils/MaterialStore.js" - import { FileLoader } from "../src/loaders/FileLoader.js"; - import { OBJLoaderWorker } from "./jsm/loaders/workerTaskManager/worker/tmOBJLoader.js"; + import { OBJLoaderWorker } from "./jsm/loaders/workerTaskManager/worker/tmOBJLoader.js" /** * The aim of this example is to show two possible ways how to use the {@link WorkerTaskManager}: @@ -66,7 +66,7 @@ this.objectsUsed = new Map(); - this.workerTaskManager = new WorkerTaskManager( 4 ).setVerbose( true ); + this.workerTaskManager = new WorkerTaskManager( 8 ).setVerbose( true ); this.tasksToUse = []; this.materialStore = new MaterialStore( true ); @@ -108,16 +108,16 @@ async initContent () { /** Simplest way to define a worker for {@link WorkerTaskManager} */ - const InlineWorker = { + class InlineWorker { - workerStandardInit: function ( context, id, config ) { + static init ( context, id, config ) { context.storage = { whoami: config.id }; context.postMessage( { cmd: "init", id: id } ); - }, + } - workerStandardExec: function ( context, id, config ) { + static execute ( context, id, config ) { let bufferGeometry = new THREE.SphereBufferGeometry( 40, 64, 64 ); bufferGeometry.name = 'InlineWorker' + config.id; @@ -128,19 +128,23 @@ .package( false ) .postMessage( context ); - }, - - buildStandardWorkerDependencies( threeJsLocation ) { + } + static buildStandardWorkerDependencies( threeJsLocation ) { return [ { url: threeJsLocation }, - { code: 'const BufferGeometry = THREE.BufferGeometry;\n' }, - { code: 'const EventDispatcher = THREE.EventDispatcher;\n' }, + { code: '\n\n' }, + { code: DeUglify.buildThreeConst() }, + { code: '\n\n' }, + { code: DeUglify.buildUglifiedThreeMapping() }, + { code: '\n\n' }, { code: ObjectUtils.serializeClass( DataTransport ) }, { code: ObjectUtils.serializeClass( GeometryTransport ) }, { code: ObjectUtils.serializeClass( MaterialUtils ) }, { code: ObjectUtils.serializeClass( MaterialsTransport ) }, - { code: ObjectUtils.serializeClass( MeshTransport ) } + { code: ObjectUtils.serializeClass( MeshTransport ) }, + { code: DeUglify.buildUglifiedThreeWtmMapping() }, + { code: '\n\n' } ]; } @@ -150,9 +154,9 @@ let awaiting = []; let taskDescr = { name: 'InlineWorker', - funcInit: InlineWorker.workerStandardInit, - funcExec: InlineWorker.workerStandardExec, - dependencies: InlineWorker.buildStandardWorkerDependencies( "../build/three.min.js" ) + funcInit: InlineWorker.init, + funcExec: InlineWorker.execute, + dependencies: InlineWorker.buildStandardWorkerDependencies( '../build/three.min.js' ) }; this.tasksToUse.push( taskDescr ); this.workerTaskManager.registerTaskType( taskDescr.name, taskDescr.funcInit, taskDescr.funcExec, null, false, taskDescr.dependencies ); @@ -168,7 +172,7 @@ this.tasksToUse.push( taskDescrObj ); this.workerTaskManager.registerTaskType( taskDescrObj.name, taskDescrObj.funcInit, taskDescrObj.funcExec, null, false, taskDescrObj.dependencies ); const loadObj = async function ( filenameObj ) { - let fileLoader = new FileLoader(); + let fileLoader = new THREE.FileLoader(); fileLoader.setResponseType( 'arraybuffer' ); return await fileLoader.loadAsync( filenameObj ); } @@ -181,7 +185,6 @@ .package( false ); awaiting.push( this.workerTaskManager.initTaskType( taskDescrObj.name, mt.getMain(), mt.getTransferables() ).catch( e => console.error( e ) ) ); } ); - return await Promise.all( awaiting ); } @@ -227,9 +230,6 @@ */ _processMessage ( payload ) { switch ( payload.cmd ) { - case 'init': - console.log( 'Init Completed: ' + payload.id ); - break; case 'assetAvailable': case 'execComplete':