From f9b79828f9a75f134c70203bf763e907e35b79b4 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Sat, 6 Jun 2020 23:27:40 +0200 Subject: [PATCH] TaskManager: - WIP: Updating jsdoc TransferableUtils: - split into TransferableUtils and MeshMessageStructure to have a now have a formal definition of what should is transferred. ResourceDescriptor & FileLoaderBufferAsync (renamed from FileLoadingExecutor) - Reviewed, reworked and added jsdoc --- examples/jsm/loaders/TaskManager.js | 147 +++++++--- .../obj2/utils/FileLoaderBufferAsync.js | 58 ++++ .../loaders/obj2/utils/FileLoadingExecutor.js | 88 ------ .../loaders/obj2/utils/ResourceDescriptor.js | 184 +++++++------ .../loaders/obj2/utils/TransferableUtils.js | 258 +++++++++++------- examples/jsm/taskmanager/tmJsmExample.js | 3 +- .../jsm/taskmanager/tmJsmExampleNoThree.js | 7 +- 7 files changed, 431 insertions(+), 314 deletions(-) create mode 100644 examples/jsm/loaders/obj2/utils/FileLoaderBufferAsync.js delete mode 100644 examples/jsm/loaders/obj2/utils/FileLoadingExecutor.js diff --git a/examples/jsm/loaders/TaskManager.js b/examples/jsm/loaders/TaskManager.js index b39117d2d97ab0..769615304d3f5d 100644 --- a/examples/jsm/loaders/TaskManager.js +++ b/examples/jsm/loaders/TaskManager.js @@ -1,14 +1,17 @@ -import { FileLoadingExecutor } from "./obj2/utils/FileLoadingExecutor.js"; -import { ResourceDescriptor } from "./obj2/utils/ResourceDescriptor.js"; - /** * @author Don McCurdy / https://www.donmccurdy.com * @author Kai Salmen / https://kaisalmen.de */ -class WorkerDefinition { +import { FileLoaderBufferAsync } from "./obj2/utils/FileLoaderBufferAsync.js"; + +/** + * Defines a worker type: functions, dependencies and runtime information once it was created. + */ +class WorkerTypeDefinition { /** + * Creates a new instance of {@link WorkerTypeDefinition}. * * @param {Number} maximumCount */ @@ -27,18 +30,30 @@ class WorkerDefinition { code: null } }; - this.dependencyDescriptors = new Set (); + /** + * @type {Set} + */ + this.dependencyUrls = new Set(); + /** + * @type {URL} + */ this.workerJsmUrl = null; this.workers = { maximumCount: maximumCount, code: [], - instances: new Set (), + instances: new Set(), available: [], storedPromises: [] }; } + /** + * + * @param {function} initFunction + * @param {function} executeFunction + * @param {function} comRouterFunction + */ setFunctions ( initFunction, executeFunction, comRouterFunction ) { this.functions.init.ref = initFunction; @@ -48,46 +63,62 @@ class WorkerDefinition { } /** + * Set the url of all dependent libraries (non-jsm). * * @param {String[]} dependencyUrls */ setDependencyUrls ( dependencyUrls ) { - dependencyUrls.forEach( url => this.dependencyDescriptors.add( new ResourceDescriptor( url ).setUrl( url ) ) ) + dependencyUrls.forEach( url => { this.dependencyUrls.add( new URL( url, window.location.href ) ) } ); } + /** + * Set the url of the jsm worker. + * + * @param {string} workerJsmUrl + */ setWorkerJsm ( workerJsmUrl ) { this.workerJsmUrl = new URL( workerJsmUrl, window.location.href ); } + /** + * Is it a jsm worker? + * @return {boolean} + */ isWorkerJsm () { return ( this.workerJsmUrl !== null ); } + /** + * Loads all dependencies and stores each as {@link ArrayBuffer} into the array. + * + * @return {Promise} + */ async loadDependencies () { - let dependencyDescriptors = []; - for ( let dependency of this.dependencyDescriptors.entries() ) { + let fileLoaderBufferAsync = new FileLoaderBufferAsync(); + let buffers = []; + for ( let url of this.dependencyUrls.entries() ) { - dependencyDescriptors.push( await FileLoadingExecutor.loadFileAsync( { - resourceDescriptor: dependency[ 1 ], - instanceNo: 0, - description: 'loadAssets', - reportCallback: ( report => console.log( report.detail.text ) ) - } )); + buffers.push( fileLoaderBufferAsync.loadFileAsync( url[ 1 ], ( report => console.log( report.detail.text ) ) ) ); } console.log( 'Waiting for completion of loading of all assets!'); - return await Promise.all( dependencyDescriptors ); + return await Promise.all( buffers ); } - async generateWorkerCode ( dependencyDescriptors ) { + /** + * + * @param {ArrayBuffer[]} buffers + * @return {Promise} + */ + async generateWorkerCode ( buffers ) { this.functions.init.code = 'const init = ' + this.functions.init.ref.toString() + ';\n\n'; this.functions.execute.code = 'const execute = ' + this.functions.execute.ref.toString() + ';\n\n'; @@ -96,24 +127,29 @@ class WorkerDefinition { this.functions.comRouter.code = "const comRouter = " + this.functions.comRouter.ref.toString() + ";\n\n"; } - - dependencyDescriptors.forEach( dependency => this.workers.code.push( dependency.content.data ) ); + buffers.forEach( buffer => this.workers.code.push( buffer ) ); this.workers.code.push( this.functions.init.code ); this.workers.code.push( this.functions.execute.code ); this.workers.code.push( this.functions.comRouter.code ); - this.workers.code.push( "self.addEventListener( 'message', comRouter, false );" ); + this.workers.code.push( 'self.addEventListener( "message", comRouter, false );' ); return this.workers.code; } + /** + * + * @param {string} code + * @return {Promise>} + */ async createWorkers ( code ) { for ( let worker, i = 0; i < this.workers.maximumCount; i++ ) { let workerBlob = new Blob( code, { type: 'application/javascript' } ); worker = new Worker( window.URL.createObjectURL( workerBlob ) ); + // TODO: why is this not a map with int index? this.workers.instances.add( { worker: worker, id: i @@ -124,6 +160,10 @@ class WorkerDefinition { } + /** + * + * @return {Promise>} + */ async createWorkersJsm () { for ( let worker, i = 0; i < this.workers.maximumCount; i++ ) { @@ -139,6 +179,13 @@ class WorkerDefinition { } + /** + * + * @param {object} instances + * @param {object} config + * @param {Transferable[]} transferables + * @return {Promise<[]>} + */ async initWorkers ( instances, config, transferables ) { let it = instances.values(); @@ -156,12 +203,21 @@ class WorkerDefinition { } + /** + * Returns a Worker or none. + * + * @return {Worker|null} + */ getAvailableTask () { return this.workers.available.shift(); } + /** + * + * @param {object} workerObj + */ returnAvailableTask ( workerObj ) { this.workers.available.push( workerObj ); @@ -173,6 +229,9 @@ class WorkerDefinition { } + /** + * Dispose all worker instances. + */ dispose () { let it = this.workers.instances.values(); @@ -186,6 +245,9 @@ class WorkerDefinition { } +/** + * + */ class TaskManager { constructor () { @@ -217,10 +279,10 @@ class TaskManager { */ registerType ( type, maximumWorkerCount, initFunction, executeFunction, comRouterFunction, dependencyUrls ) { - let workerDefinition = new WorkerDefinition( maximumWorkerCount ); - workerDefinition.setFunctions( initFunction, executeFunction, comRouterFunction ); - workerDefinition.setDependencyUrls( dependencyUrls ); - this.types.set( type, workerDefinition ); + let workerTypeDefinition = new WorkerTypeDefinition( maximumWorkerCount ); + workerTypeDefinition.setFunctions( initFunction, executeFunction, comRouterFunction ); + workerTypeDefinition.setDependencyUrls( dependencyUrls ); + this.types.set( type, workerTypeDefinition ); return this; } @@ -234,9 +296,9 @@ class TaskManager { */ registerTypeJsm ( type, maximumWorkerCount, workerJsmUrl ) { - let workerDefinition = new WorkerDefinition( maximumWorkerCount ); - workerDefinition.setWorkerJsm( workerJsmUrl ); - this.types.set( type, workerDefinition ); + let workerTypeDefinition = new WorkerTypeDefinition( maximumWorkerCount ); + workerTypeDefinition.setWorkerJsm( workerJsmUrl ); + this.types.set( type, workerTypeDefinition ); return this; } @@ -245,23 +307,24 @@ class TaskManager { * Provides initialization configuration and dependencies for all tasks of given type. * @param {string} type * @param {object} config - * @param {Object} [transferables] + * @param {Transferable[]} [transferables] */ async initType ( type, config, transferables ) { - let workerDefinition = this.types.get( type ); - if ( workerDefinition.isWorkerJsm() ) { + let workerTypeDefinition = this.types.get( type ); + if ( workerTypeDefinition.isWorkerJsm() ) { - return await workerDefinition.createWorkersJsm() - .then( instances => workerDefinition.initWorkers( instances, config, transferables ) ); + return await workerTypeDefinition.createWorkersJsm() + .then( instances => workerTypeDefinition.initWorkers( instances, config, transferables ) ); } else { - return await workerDefinition.loadDependencies() - .then( deps => workerDefinition.generateWorkerCode( deps ) ) - .then( code => workerDefinition.createWorkers( code ) ) - .then( instances => workerDefinition.initWorkers( instances, config, transferables ) ); + return await workerTypeDefinition.loadDependencies() + .then( buffers => workerTypeDefinition.generateWorkerCode( buffers ) ) + .then( code => workerTypeDefinition.createWorkers( code ) ) + .then( instances => workerTypeDefinition.initWorkers( instances, config, transferables ) ) + .catch( x => console.error( x ) ); } } @@ -276,8 +339,8 @@ class TaskManager { */ async addTask ( type, cost, config, transferables ) { - let workerDefinition = this.types.get( type ); - let workerObj = workerDefinition.getAvailableTask(); + let workerTypeDefinition = this.types.get( type ); + let workerObj = workerTypeDefinition.getAvailableTask(); return new Promise( ( resolveUser, rejectUser ) => { @@ -298,7 +361,7 @@ class TaskManager { promiseWorker.then( ( e ) => { resolveExecute( e.data ); - workerDefinition.returnAvailableTask( workerObjExecute ); + workerTypeDefinition.returnAvailableTask( workerObjExecute ); } ).catch( ( e ) => { @@ -315,7 +378,7 @@ class TaskManager { } else { - workerDefinition.workers.storedPromises.push( { + workerTypeDefinition.workers.storedPromises.push( { exec: executeWorker, resolve: resolveUser, reject: rejectUser @@ -332,9 +395,9 @@ class TaskManager { */ dispose () { - for ( const [ key, workerDefinition ] of this.types.entries() ) { + for ( let workerTypeDefinition of this.types.values() ) { - workerDefinition.dispose(); + workerTypeDefinition.dispose(); } return this; diff --git a/examples/jsm/loaders/obj2/utils/FileLoaderBufferAsync.js b/examples/jsm/loaders/obj2/utils/FileLoaderBufferAsync.js new file mode 100644 index 00000000000000..a7a72269831c46 --- /dev/null +++ b/examples/jsm/loaders/obj2/utils/FileLoaderBufferAsync.js @@ -0,0 +1,58 @@ +/** + * @author Kai Salmen / https://kaisalmen.de + * Development repository: https://github.com/kaisalmen/WWOBJLoader + */ + +import { + FileLoader +} from "../../../../../build/three.module.js"; + +/** + * Extension of {@link FileLoader} that configures "arraybuffer" as default response type and carries a specific + * onProgress function for {@link FileLoader#loadAsync}. + */ +class FileLoaderBufferAsync extends FileLoader { + + constructor() { + super(); + this.setResponseType( 'arraybuffer' ); + } + + /** + * Load the resource defined in the first argument. + * + * @param {URL} url + * @param {Function} [onProgress] + * @return {Promise} + */ + loadFileAsync ( url, onProgress ) { + let numericalValueRef = 0; + let numericalValue = 0; + function scopedOnReportProgress( event ) { + + if ( ! event.lengthComputable ) return; + numericalValue = event.loaded / event.total; + if ( numericalValue > numericalValueRef ) { + + numericalValueRef = numericalValue; + let output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%'; + if ( onProgress !== undefined && onProgress !== null ) { + + onProgress( { + detail: { + type: 'progressLoad', + text: output, + numericalValue: numericalValue + } + } ); + + } + } + } + return this.loadAsync( url.href, scopedOnReportProgress ); + + } + +} + +export { FileLoaderBufferAsync } diff --git a/examples/jsm/loaders/obj2/utils/FileLoadingExecutor.js b/examples/jsm/loaders/obj2/utils/FileLoadingExecutor.js deleted file mode 100644 index 6fc31185297c44..00000000000000 --- a/examples/jsm/loaders/obj2/utils/FileLoadingExecutor.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @author Kai Salmen / https://kaisalmen.de - * Development repository: https://github.com/kaisalmen/WWOBJLoader - */ - -import { - FileLoader -} from "../../../../../build/three.module.js"; -import { Zlib } from "../../../libs/gunzip.module.min.js"; - - -const FileLoadingExecutor = { - - loadFileAsync: function ( { resourceDescriptor, instanceNo, description, reportCallback } ) { - let scope = this; - return new Promise(( resolveFunction, rejectFunction ) => { - scope.loadFile( { resourceDescriptor, instanceNo, description, reportCallback }, ( error, response ) => { - if ( error ) { - - rejectFunction( error ); - - } - resolveFunction( response ); - } ); - } ); - }, - - loadFile: function ( params, onCompleteFileLoading ) { - let numericalValueRef = 0; - let numericalValue = 0; - let resourceDescriptor = params.resourceDescriptor; - let url = ''; - if ( resourceDescriptor !== undefined && resourceDescriptor !== null ) { - url = resourceDescriptor.url; - } - - function scopedOnReportProgress( event ) { - if ( ! event.lengthComputable ) return; - - numericalValue = event.loaded / event.total; - if ( numericalValue > numericalValueRef ) { - - numericalValueRef = numericalValue; - let output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%'; - if ( params.reportCallback !== undefined && params.reportCallback !== null ) { - - params.reportCallback( { - detail: { - type: 'progressLoad', - modelName: params.description, - text: output, - instanceNo: params.instanceNo, - numericalValue: numericalValue - - } - } ); - - } - } - } - - function scopedOnReportError( event ) { - let errorMessage = 'Error occurred while downloading "' + url + '"'; - onCompleteFileLoading( { resourceDescriptor, errorMessage, event } ); - } - - function processResourcesProxy( content ) { - - let uint8array = content; - if ( resourceDescriptor.isCompressed() ) { - - let inflate = new Zlib.Gunzip( new Uint8Array( content ) ); // eslint-disable-line no-undef - uint8array = inflate.decompress(); - - } - resourceDescriptor.setBuffer( uint8array ); - onCompleteFileLoading( null, resourceDescriptor ); - - } - - let fileLoader = new FileLoader(); - fileLoader.setResponseType( 'arraybuffer' ); - fileLoader.load( url, processResourcesProxy, scopedOnReportProgress, scopedOnReportError ); - - } -}; - -export { FileLoadingExecutor } diff --git a/examples/jsm/loaders/obj2/utils/ResourceDescriptor.js b/examples/jsm/loaders/obj2/utils/ResourceDescriptor.js index 04361e92ed6c01..8695fc325ebb0e 100644 --- a/examples/jsm/loaders/obj2/utils/ResourceDescriptor.js +++ b/examples/jsm/loaders/obj2/utils/ResourceDescriptor.js @@ -3,97 +3,135 @@ * Development repository: https://github.com/kaisalmen/WWOBJLoader */ +import { Zlib } from "../../../libs/gunzip.module.min.js"; + /** - * - * @param {String} name - * @constructor + * Encapsulates a url and derived values (filename, extension and path and stores the {@link ArrayBufer} + * loaded from the resource described by the url. */ -const ResourceDescriptor = function ( name) { - this.name = ( name !== undefined && name !== null ) ? name : 'Unnamed_Resource'; - this.content = { - data: null, - dataOptions: null, - needStringOutput: false, - compressed: false - }; - this.url = null; - this.filename = null; - this.path; - this.resourcePath; - this.extension = null; - this.transferables = []; -}; - -ResourceDescriptor.prototype = { - - constructor: ResourceDescriptor, +class ResourceDescriptor { /** + * Creates a new instance of {@link ResourceDescriptor}. * - * @param url - * @return {ResourceDescriptor} + * @param {string} url URL as text */ - setUrl: function ( url ) { + constructor ( url ) { - this.url = ( url === undefined || url === null ) ? this.name : url; - this.url = new URL( this.url, window.location.href ).href; - this.filename = this.url; - let urlParts = this.url.split( '/' ); + this.url = new URL( url, window.location.href ); + this.path = './'; + this.filename = url; + this.extension = null; + + let urlParts = this.url.href.split( '/' ); if ( urlParts.length > 2 ) { this.filename = urlParts[ urlParts.length - 1 ]; let urlPartsPath = urlParts.slice( 0, urlParts.length - 1 ).join( '/' ) + '/'; - if ( urlPartsPath !== undefined && urlPartsPath !== null ) this.path = urlPartsPath; + if ( urlPartsPath !== undefined ) this.path = urlPartsPath; } let filenameParts = this.filename.split( '.' ); if ( filenameParts.length > 1 ) this.extension = filenameParts[ filenameParts.length - 1 ]; - return this; - }, + this.buffer = null; + this.needStringOutput = false; + this.compressed = false; + + } + + /** + * Returns the URL. + * @return {URL} + */ + getUrl() { + + return this.url; + + } + + /** + * Returns ths path from the base of the URL to the file + * @return {string} + */ + getPath () { + + return this.path; + + } + + /** + * Returns the filename. + * @return {string} + */ + getFilename () { + + return this.filename; + + } + + /** + * Returns the file extension if it was found + * @return {null|string} + */ + getExtension () { + + return this.extension; + + } /** + * Allows to set if the buffer should be converted to string which is possible via {@link getBufferAsString}. * - * @param needStringOutput + * @param {boolean} needStringOutput * @return {ResourceDescriptor} */ - setNeedStringOutput: function ( needStringOutput ) { + setNeedStringOutput ( needStringOutput ) { - this.content.needStringOutput = needStringOutput; + this.needStringOutput = needStringOutput; return this; - }, + } + + /** + * Tells if buffer should be returned as string. + * @return {boolean} + */ + isNeedStringOutput () { + + return this.needStringOutput; + + } /** * * @param {boolean} compressed * @return {ResourceDescriptor} */ - setCompressed: function ( compressed ) { + setCompressed ( compressed ) { - this.content.compressed = compressed; + this.compressed = compressed; return this; - }, + } /** - * + * Tells if the resource is compressed * @return {boolean} */ - isCompressed: function () { + isCompressed () { - return this.content.compressed; + return this.compressed; - }, + } /** - * - * @param buffer + * Set the buffer after loading. It will be decompressed if {@link isCompressed} is true. + * @param {ArrayBuffer} buffer + * @return {ResourceDescriptor} */ - setBuffer: function ( buffer ) { + setBuffer ( buffer ) { - // fast-fail on unset input - if ( buffer === null ) return; if ( ! ( buffer instanceof ArrayBuffer || buffer instanceof Int8Array || buffer instanceof Uint8Array || @@ -108,55 +146,43 @@ ResourceDescriptor.prototype = { throw( 'Provided input is neither an "ArrayBuffer" nor a "TypedArray"! Aborting...' ); } + if ( this.isCompressed() ) { - if ( this.content.needStringOutput ) { - - this.content.data = new TextDecoder("utf-8" ).decode( buffer ) ; + let inflate = new Zlib.Gunzip( new Uint8Array( buffer ) ); // eslint-disable-line no-undef + this.buffer = inflate.decompress(); } else { - this.content.data = buffer; + this.buffer = buffer; } + return this; - }, + } /** + * Returns the buffer. * - * @param name - * @param object - * @param transferables + * @return {ArrayBuffer} */ - setInputDataOption: function ( name, object, transferables ) { - - if ( ! Array.isArray( transferables ) ) transferables = []; - this.content.dataOptions[ name ] = { - name: name, - object: object - }; - if ( transferables.length > 0 ) { + getBuffer () { - this.transferables = this.transferables.concat( transferables ); + return this.buffer; - } - - }, + } /** + * Returns the buffer as string by using {@link TextDecoder}. * - * @return {ResourceDescriptor} + * @return {string} */ - createSendable: function () { - let copy = new ResourceDescriptor( this.name ); - copy.url = this.url; - copy.filename = this.filename; - copy.path = this.path; - copy.resourcePath = this.resourcePath; - copy.extension = this.extension; - this.content.data = null; - return copy; + getBufferAsString () { + + return new TextDecoder("utf-8" ).decode( this.buffer ) ; + } -}; + +} export { ResourceDescriptor } diff --git a/examples/jsm/loaders/obj2/utils/TransferableUtils.js b/examples/jsm/loaders/obj2/utils/TransferableUtils.js index 1ee9ff1b53072c..b6f4b0b5da9c6f 100644 --- a/examples/jsm/loaders/obj2/utils/TransferableUtils.js +++ b/examples/jsm/loaders/obj2/utils/TransferableUtils.js @@ -7,49 +7,168 @@ import { BufferGeometry } from "../../../../../build/three.module.js"; +/** + * Define a fixed structure that is used to ship data in between main and workers. + */ +class MeshMessageStructure { -class TransferableUtils { - - static createMessageStructure( meshName ) { - return { - main: { - cmd: 'exec', - type: 'mesh', - meshName: meshName, - progress: { - numericalValue: 0 - }, - params: { - // 0: mesh, 1: line, 2: point - geometryType: 0 - }, - materials: { - multiMaterial: false, - materialNames: [], - materialGroups: [] - }, - buffers: { - vertices: null, - indices: null, - colors: null, - normals: null, - uvs: null, - skinIndex: null, - skinWeight: null - } + /** + * Creates a new {@link MeshMessageStructure}. + * + * @param {string} cmd + * @param {string} meshName + */ + constructor( cmd, meshName ) { + this.main = { + cmd: cmd, + type: 'mesh', + meshName: meshName, + progress: { + numericalValue: 0 + }, + params: { + // 0: mesh, 1: line, 2: point + geometryType: 0 }, - transferables: { - vertex: null, - index: null, - color: null, - normal: null, - uv: null, + materials: { + multiMaterial: false, + materialNames: [], + materialGroups: [] + }, + buffers: { + vertices: null, + indices: null, + colors: null, + normals: null, + uvs: null, skinIndex: null, skinWeight: null } + }; + this.transferables = { + vertex: null, + index: null, + color: null, + normal: null, + uv: null, + skinIndex: null, + 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 ) { @@ -88,12 +207,15 @@ class TransferableUtils { } + /** + * Package {@link BufferGeometry} into {@link MeshMessageStructure} * * @param {BufferGeometry} bufferGeometry * @param {string} meshName * @param {number} geometryType * @param {string[]} [materialNames] + * @return {MeshMessageStructure} */ static packageBufferGeometry( bufferGeometry, meshName, geometryType, materialNames ) { let vertexBA = bufferGeometry.getAttribute( 'position' ); @@ -112,7 +234,7 @@ class TransferableUtils { let skinWeightFA = (skinWeightBA !== null && skinWeightBA !== undefined) ? skinWeightBA.array : null; - let payload = TransferableUtils.createMessageStructure( meshName ); + let payload = new MeshMessageStructure( 'exec', meshName ); payload.main.params.geometryType = geometryType; payload.main.materials.materialNames = materialNames; if ( vertexFA !== null ) { @@ -160,68 +282,6 @@ class TransferableUtils { return payload; } - static cloneMessageStructure( input ) { - - let payload = input; - if ( input.main.buffers.vertices !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.vertices.length ); - TransferableUtils.copyTypedArray( input.main.buffers.vertices, arrayOut ) - payload.transferables.vertex = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.indices !== null ) { - - let arrayOut = new Uint32Array( input.main.buffers.indices ); - TransferableUtils.copyTypedArray( input.main.buffers.indices, arrayOut ); - payload.transferables.index = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.colors !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.colors.length ); - TransferableUtils.copyTypedArray( input.main.buffers.colors, arrayOut ) - payload.transferables.color = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.normals !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.normals.length ); - TransferableUtils.copyTypedArray( input.main.buffers.normals, arrayOut ) - payload.transferables.normal = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.uvs !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.uvs.length ); - TransferableUtils.copyTypedArray( input.main.buffers.uvs, arrayOut ) - payload.transferables.uv = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.skinIndex !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.skinIndex.length ); - TransferableUtils.copyTypedArray( input.main.buffers.skinIndex, arrayOut ) - payload.transferables.skinIndex = [ arrayOut.buffer ]; - - } - if ( input.main.buffers.skinWeight !== null ) { - - let arrayOut = new Float32Array( input.main.buffers.skinWeight.length ); - TransferableUtils.copyTypedArray( input.main.buffers.skinWeight, arrayOut ) - payload.transferables.skinWeight = [ arrayOut.buffer ]; - - } - return payload; - - } - - static copyTypedArray ( arrayIn, arrayOut ) { - - for ( let i = 0; i < arrayIn.length; i++ ) arrayOut[ i ] = arrayIn[ i ]; - - } - } -export { TransferableUtils } +export { TransferableUtils, MeshMessageStructure } diff --git a/examples/jsm/taskmanager/tmJsmExample.js b/examples/jsm/taskmanager/tmJsmExample.js index b119e24c3ad6de..bdb1cbe5ffad28 100644 --- a/examples/jsm/taskmanager/tmJsmExample.js +++ b/examples/jsm/taskmanager/tmJsmExample.js @@ -32,7 +32,6 @@ async function execute ( id, config ) { vertexArray[ i ] = vertexArray[ i ] + 10 * ( Math.random() - 0.5 ); } - let payload = TransferableUtils.packageBufferGeometry( bufferGeometry, 'tmProto' + config.count, 2,[ 'defaultPointMaterial' ] ); let randArray = new Uint8Array( 3 ); @@ -42,7 +41,7 @@ async function execute ( id, config ) { g: randArray[ 1 ] / 255, b: randArray[ 2 ] / 255 }; - self.postMessage( payload.main, payload.transferables ); + payload.postMessage( self ); } diff --git a/examples/jsm/taskmanager/tmJsmExampleNoThree.js b/examples/jsm/taskmanager/tmJsmExampleNoThree.js index 32c1a360a71cb3..4146cd373b828f 100644 --- a/examples/jsm/taskmanager/tmJsmExampleNoThree.js +++ b/examples/jsm/taskmanager/tmJsmExampleNoThree.js @@ -3,7 +3,7 @@ */ import { - TransferableUtils + MeshMessageStructure } from "../loaders/obj2/utils/TransferableUtils.js"; @@ -21,7 +21,7 @@ function init ( id, config ) { function execute ( id, config ) { - let payload = TransferableUtils.cloneMessageStructure( self.config ); + let payload = MeshMessageStructure.cloneMessageStructure( self.config ); let vertexArray = payload.main.buffers.vertices.buffer; for ( let i = 0; i < vertexArray.length; i++ ) { @@ -38,8 +38,7 @@ function execute ( id, config ) { g: randArray[ 1 ] / 255, b: randArray[ 2 ] / 255 }; - - self.postMessage( payload.main, payload.transferables ); + payload.postMessage( self ); }