4

I have a series of UI sliders (dat.GUI) and I am posting the slider values to the web worker whenever a UI slider has changed.
The calculations done in the worker need about 3-8 seconds until the worker posts back the information I need.

In case the UI interface is used in a fast rate (e.g. five of the sliders are changed before the first calculation is done), the worker has still received five messages and will still respond to each one of them, with each new information overwriting the last one. Is there any way for the worker to ignore the in-between messages and only perform the calculations with the last received message before the current calculation was finished?

I am a beginner on workers so for now I am only using the Worker.onmessage property and Worker.postMessage() method.

Noah Freitas
  • 17,240
  • 10
  • 50
  • 67
des
  • 103
  • 1
  • 5
  • Tell us more about your architecture. How would the worker now which is the last message? If it is known in advance, then why are the other messages sent to the worker in the first place? – doldt Jul 11 '15 at 17:26
  • How about using [`debounce`](http://stackoverflow.com/q/24004791/1233508)? It delays the processing for a bit, and if a new message is received during that delay, it discards the first one and delays the new one for a bit, repeating it until messages stop coming. – DCoder Jul 11 '15 at 17:29
  • @doldt All the messages are sent because each time one slider value has changed new calculations should be performed but at the same time the user can keep using or stop using the sliders at any time, so I do not know which is the last message to send only that. 'How would the worker know which is the last message' i guess is part of my own question. – des Jul 11 '15 at 17:44
  • @DCoder I will check what debounce is and see if it helps, even though i would prefer to avoid delaying the processing, i'm looking how to completely skip processing rounds in case more than one messages have been stacked. – des Jul 11 '15 at 17:44
  • @des I see you want to terminate a previously processing task once a new message has came into your worker. Keep in mind that there is a bit of a challenge with this approach. A worker is strictly single threaded on its own. That's how JavaScript avoid synchronization issues. So let's say your worker receives a message then started a super intensive procedure that takes 5 seconds. Then user makes another UI interaction 1 second later and so a new message is sent to the same worker. The `onmessage` callback of your worker will not get kicked off until 4 seconds later when the thread frees up. – initialxy Jul 11 '15 at 17:59
  • @des you will have to implement your background procedure in a way that it breaks off once a while and queue the rest of the procedure to the end of the task queue so that `onmessage` will get a change to barge in task queue and cancel the rest of the previously queued procedure. When I say break off and queue to the end of the task queue, I meant `setTimeout(foo, 0)`. – initialxy Jul 11 '15 at 18:03

1 Answers1

2

This doesn't have all that much to do with WebWorker. This seems like a typical debounce use case. The problem here is that once your worker receives a message, how does it know it's the last one? It can't tell the future. It doesn't know if user will make another UI interaction in the near future. So the solution to this kind of problems is debounce. The idea is that you don't execute your code right away, but wait slightly, say 200ms. Within this period of time, if there was another message, then it will wait another 200ms without doing anything. You can implement something like this on your own, or you can import and use one of the existing libraries like underscore.js

EDIT: Here is an example using underscore.js/lodash (both have similar API). Suppose originally, your code was something like this:

onmessage = function(e) {
    var params = e.data.something;
    // TODO: Do something with params.
};

With debounce, you will rewrite it as...

onmessage = _.debounce(function(e){
    var params = e.data.something;
    // TODO: Do something with params.
}, 200);

What's happening here is that _.debounce returns a wrapper function that can be called as frequently as it needs. However it will not trigger the function wrapped inside immediately. It schedules it to execute in 200ms (tweak this for your needs). During this period, if there was another call, the previously scheduled execution will be canceled, and a new execution will be scheduled in 200ms at this point. When time is up, your function will be executed with the latest version of arguments. Now you are probably wondering about what happens to the arguments from previous calls? The answer is they are simply discarded by the wrapper function. Notice that arguments passed to onmessage are passed to the wrapper function first, which keeps only the latest one to be passed to the wrapped function. You can play with the fiddle I created here to see it in action. Another note I want to add is that you can do this debounce logic on your main thread too before passing it to WebWorker.

EDIT: In case you need a guide on how to import a third-party library in WebWorker. https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Importing_scripts_and_libraries

initialxy
  • 1,193
  • 8
  • 17
  • I decided to use debounce. I know how to import a library in WebWorker but i'm still confused about how to implement the function. Right now, in my worker i have: onmessage = function(e) { //(1 - get values that were sent from the main thread) sliderValues = e.data.sliderValues; //(2 - perform calculations) getMeshInformation(); //(3 - post results) postMessage({ 'triangles' : triangles }); }; now i only want no.1 to be executed, then wait (debounce) for no.2+3, in case it receives a new no.1 that overwrites the previous one. – des Jul 14 '15 at 16:30
  • If you use one of the existing implementations, all you really need to do is wrap your original `onmessage` callback with debounce and that's all there is. However if you want to make your own implementation, you can look at [underscore.js](https://github.com/jashkenas/underscore/blob/master/underscore.js#L821) or [lodash](https://github.com/lodash/lodash/blob/master/lodash.js#L7655)'s source code for some inspirations. Of course, your implementation doesn't need to be that fancy. I will edit my answer above to include an example, because posting code in comment is ugly. – initialxy Jul 14 '15 at 18:58
  • Thank you for your time, this is very helpful. – des Jul 15 '15 at 11:09