TaskManager: Proposed worker management class
See original GitHub issueFollowing up on a conversation in https://github.com/mrdoob/three.js/pull/18123, this is a proposal for a utility class for managing tasks distributed to Web Workers. Currently BasisTextureLoader, DRACOLoader, and OBJLoader2 implement redundant logic for that purpose.
A full-featured library for this purpose could easily get pretty complex. I’m hoping that we can write something fairly lightweight for internal use by threejs examples. A more robust version could probably be a standalone library, or part of ECSY, but that’s beyond the scope I personally want to attempt.
Proposed API:
interface TaskManager {
/** Returns true if support for the given task type is available. */
supportsType( type: string ): boolean;
/** Registers functionality for a new task type. */
registerType( type: string, init: Function, execute: Function ): TaskManager;
/** Provides initialization configuration and dependencies for all tasks of given type. */
initType( type: string, config: object, transfer: Transferrable[] ): TaskManager;
/** Queues a new task of the given type. Task will not execute until initialization completes. */
addTask( type: string, cost: number, config: object, transfer: Transferrable[] ): Promise<any>;
/** Destroys all workers and associated resources. */
dispose(): TaskManager;
}
Use in DRACOLoader:
const DRACO_DECODE = 'draco/decode';
class DRACOLoader {
constructor ( loadingManager: LoadingManager, taskManager: TaskManager ) {
this.loadingManager = loadingManager || DefaultLoadingManager;
this.taskManager = taskManager || DefaultTaskManager;
if ( ! this.taskManager.supportsType( DRACO_DECODE ) ) {
this.taskManager
.registerType( DRACO_DECODE, taskInit, taskExecute )
.initType( DRACO_DECODE, decoderConfig, decoderTransfers );
}
}
load ( url, onLoad, onProgress, onError ) {
const data = await fetch( url ).then( r => r.arrayBuffer() );
const cost = data.byteLength;
const config = { data, ... };
this.taskManager
.addTask( DRACO_DECODE, cost, config, [ data ] )
.then( onLoad ).catch( onError );
}
}
These two functions are passed to the TaskManager, and their function bodies are copied into the Web Worker (without surrounding context).
// Sets up state before a worker begins taking Draco tasks.
function taskInit ( config: object ): Promise<void> {
// do async setup
return Promise.resolve();
}
// Executes on a worker for each Draco task.
function taskExecute ( taskConfig: object ): Promise<any> {
// do expensive processing
return Promise.resolve( result );
}
The TaskManager class then takes on the responsibilities of:
- Creating a configurable number of Web Workers.
- Installing the dependencies (i.e. function bodies + transfers) for each supported task type.
- Distributing tasks evenly across workers.
- Disposing of workers if necessary.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:4
- Comments:26 (15 by maintainers)
Top GitHub Comments
Between @kaisalmen’s work in https://github.com/kaisalmen/wtd, and the small WorkerPool.js utility in the three.js repository, I believe this issue can be resolved. 🎉
Am able to make all eight logical cores 100% busy: One simple worker (10^8 additions in for loop), 8 instance, 1000 added tasks . I have not modified any loader code, because I wanted to get the concept straight, first. This is still WIP. Sorry, things move very slow, but spare time is more rare than usual for me these days.