Search code examples
javascriptthree.jsweb-worker

Can web workers support multiple events like message and progress?


Is it possible to write a web worker to respond to two different events?

I have a worker that loads a buffergeometry from various .json files by taking out the respective normal, position, index array info and passes it along as an array buffer. The main thread then picks up this based on a 'message' event from the worker. Geometries load in the perfectly.

The difficulties, due to my lack of experience, is how to I pass the progress of the of the xhr request from the worker back to the main thread? If I append a progress property, only 100% gets passed.

Is there a way to create a 'progress' event listener and as this progress event fires, send that over to the main?

I do get my buffer arrays sent, I am able to build and render the Meshes in the Scene properly. I expect the progress of the data to be streamed over, but I only get the 100. Numbers other than 100 for progress are from console log.

I'd like the progress of the xhr req to be sent over from the worker to the main, but can't figure it out.

enter image description here

Web Worker excerpt:

import { BufferGeometryLoader, BufferGeometry } from 'three';


var progress;

const loader = new BufferGeometryLoader();
const geometry = new BufferGeometry();

  /* is this possible? how to get progress info in here to stream over? */
  self.addEventListener('progress', (event) => {
    console.log('event in progress: ', progress);
  });

  self.addEventListener('message', (event) => {
    const { model, part } = event.data;
     loader.load(`https://models/${model}/${part}.json`, (geometry) => {
      // all the code to load the buffergeometry and pass to the main thread
      // removed for sake of focus
      },
      (xhr) => {
       /* how to pass just the value of progress as it happens so i can get a progress bar in the DOM? Or say emit a progress event that this worker can respond to? */
       progress = (xhr.loaded / xhr.total * 100);
       console.log('progress: ', progress);
      },
      (err) => {
        console.log('err: ', err);
      }
    );
  });

Main excerpt

    /* If i can capture the progress data, i can easily build a loading progress bar */
    worker.addEventListener('progress', (event) => {
      console.log('event.data: ', event.data);
    });

    worker.addEventListener('message', (event) => {

      // build meshes from buffergeometries and assign materials and add to scene
    });

Solution

  • You'd do this with the message event, passing an object where you differentiate what type of message you're sending with a property (perhaps called type).

    SO in your worker, when you want to update the main thread with progress:

    postMessage({type: "progress", progress: xhr.loaded / xhr.total * 100});
    

    When you want to send the data:

    postMessage({type: "data", data: /*...*/});
    

    In main, you have an event handler for message which dispatches on type:

    worker.addEventListener('message', (event) => {
        const msg = event.data;
        switch (msg.type) {
            case "progress":
                // ...handle progress message, progress is in `msg.progress`
                break;
            case "data":
                // ...handle data message, data is in `msg.data`
                break;
        }
    });
    

    Live Example on plnkr