How to write Javascript code that doesn't block the ui - javascript

I'm working on a javascript application that performs 2 jobs.
The first job is more important and needs to run at 60fps. The other job is a "background" job that still needs to run but it's okay if it takes longer.
Normally the way I would do this is have the more important job's code in a RequestAnimationFrame loop, and put the background job on a web worker.
However the main job is already spawning 2 web workers, and I don't want to spawn a third for context switching and memory consumption reasons.
There is ~8 ms of processing time left over on the RequestAnimationFrame loop that I have to work with for the background job to run on, however it is a job that will take about 100 ms to complete.
My question is there a way to write a loop that will pause itself every time the ui is about to be blocked?
Basically run as much code as you can until the remaining 8ms of time are up for the frame, and then pause until there is free time again.

This is currently experimental technology which isn't well-supported yet, but: There's requestIdleCallback, which:
...queues a function to be called during a browser's idle periods. This enables developers to perform background and low priority work on the main event loop, without impacting latency-critical events such as animation and input response. Functions are generally called in first-in-first-out order; however, callbacks which have a timeout specified may be called out-of-order if necessary in order to run them before the timeout elapses.
One of the key things about rIC is that it receives an IdleDeadline object which
...lets you determine how much longer the user agent estimates it will remain idle and a property, didTimeout, which lets you determine if your callback is executing because its timeout duration expired.
So you could have your loop stop when the deadline.timeRemaining() method returns a small enough number of remaining milliseconds.
That said, I think I'd probably add the third worker and see what it looks like in aggressive testing before I tried other approaches. Yes, it's true that context-switching is costly and you don't want to overdo it. On the other hand, there's already plenty of other stuff going on on mobiles and architectures these days are quite fast at context switching. I can't speak to the memory demands of workers on mobiles (haven't measured them myself), but that's where I'd start.

I recommend requestIdleCallback() as the accepted answer does, but it is still experimental and I like coming up with stuff like this. You might even combine rIC with this answer to produce something more suited to your needs.
The first task is to split up your idle code into small runnable chunks so you can check how much time you have/spent between chunks.
One way is to create several functions in a queue that do the work needed, such as unprocessed.forEach(x=>workQueue.push(idleFunc.bind(null,x)));}, then have an executor that will at some point process the queue for a set amount of time.
If you have a loop that takes awhile to finish, you could use a generator function and yield at the end of each loop, then run it inside recursive calls to setTimeout() with your own deadline or requestIdleCallback().
You could also have a recursive function that when processed, would add itself back to the end of the queue, which could help when you want to give other work time to run or when creating a function per piece of work would be absurd (e.g., hundreds of array items bound to a function that together only take 1ms to process).
Anyway, here's something I whipped up out of curiosity.
class IdleWorkExecutor {
constructor() {
this.workQueue=[];
this.running=null;
}
addWork(func) {
this.workQueue.push(_=>func());
this.start();
}
//
addWorkPromise(func) {
return new Promise(r=>{
this.workQueue.push(_=>r(func()));
this.start();
});
//DRY alternative with more overhead:
//return new Promise(r=>this.addWork(_=>r(func())));
}
sleep(ms) {
return new Promise(r=>setTimeout(r,ms));
}
//Only run the work loop when there is work to be done
start() {
if (this.running) {return this.running;}
return this.running=(async _=>{
//Create local reference to the queue and sleep for negligible performance gain...
const {workQueue,sleep}=this;
//Declare deadline as 0 to pause execution as soon as the loop is entered.
let deadline=0;
while (workQueue.length!==0) {
if (performance.now()>deadline) {
await sleep(10);
deadline=performance.now()+1;
}
/*shift* off and execute a piece of work. *push and shift are used to
create a FIFO buffer, but a growable ring buffer would be better. This
was chosen over unshift and pop because expensive operations shouldn't
be performed outside the idle executor.*/
workQueue.shift()(deadline);
}
this.running=false;
})();
}
}
//Trying out the class.
let executor=new IdleWorkExecutor();
executor.addWork(_=>console.log('Hello World!'));
executor.addWorkPromise(_=>1+1).then(ans=>{
executor.addWork(_=>console.log('Answer: '+ans));
});
//A recursive busy loop function.
executor.addWork(function a(counter=20) {
const deadline=performance.now()+0.2;
let i=0;
while (performance.now()<deadline) {i++}
console.log(deadline,i);
if (counter>0) {
executor.addWork(a.bind(null,counter-1));
}
});
If you can use requestIdleCallback() in your code, adding it to IdleWorkExecutor is pretty simple:
function rICPromise(opt) {
return new Promise(r=>{
requestIdleCallback(r,opt);
});
}
if (!deadline||deadline.timeRemaining()>0) {
deadline=await rICPromise({timeout:5000});
}

Related

Wait until a node is available without using an interval or fixed retry time

Is there a smarter solution to wait until a node exists without using an interval or a fixed retry time?
Currently, I do such like this:
let interval = setInterval(function () {
let neededElement = document.querySelector("small > time");
if (neededElement !== null) {
clearInterval(interval);
...code...
}
}, 100);
What bothers me is that it always waits blindly. If the timing is low, it consumes resources unnecessarily and if it is high, it reacts too slowly to changes. And if the node never exists, it consumes resources unnecessarily in the background.
Probably a mutations observer would be a solution. But seems also not very slick to me, especially if I need this often. Or I would need some kind of (factory) function to build mutation observers.
EDIT:
The main problem of the mutation observer in my use case is the reusability in a script and the accuracy. Because nodes that do not exist yet cannot be observed. Therefore you have to go to higher nodes where it is sure that they exist immediately. But then the observer reacts also on unimportant changes. Depending upon use case these can be very many. So in the end you probably save less resources than with an interval.

Do calls to requestAnimationFrame stack?

I have the following functions
function render() {
init = timeStep(init)
if (steps <= yQuotient) {
requestAnimationFrame(render)
}
}
export function fire() {
setup();
render();
}
I then call fire() whenever I need to start/restart the application. My question is whether this will result in multiple requestAnimationLoop()'s active simulatenously as they build up like eventListeners do when called repeatedly, presumably affecting performance.
Do I need to amend my fire() function to something like this?
export function fire() {
cancelAnimationFrame(render)
setup();
render();
}
Or does requestAnimationLoop() just automatically know to end it's current loop when a new one is started?
Yes, calls to requestAnimationFrame will stack all the callbacks into the map of animation frame callbacks.
Each painting frame, the keys of this map are all gotten and looped over, allowing to schedule a new animation frame callback from such a callback, for firing at the next painting frame.
So yes, scheduling multiple times the same callback in the same event loop iteration will worsen the performances of your page, since it will have to do multiple times the same work.
It's quite unclear how all your system works, but a quite often used strategyfor games and interactive animations is to keep a single main animation loop running all the time and calling sub-tasks. External events only modifying states of objects that get visited from these sub-tasks.

javascript set interval run as separate thread?

I want to use a timer as a fallback in case I end up in an infinite loop. It seems that set interval is the right way to do this. However, it's not working for me.
From my research, it seems like setInterval should run in a separate thread in the background, but I don't see it.
Why is this behavior happening? And how do I solve this?
var time = 0;
window.setInterval(function(){time++;}, 1000);
while (true) {
//stuff done
if (time >= 5) {
break;
}
}
Browser javascript runs in a single thread. So if you perform something that takes too long - it will freeze browser.
See John Resig article for further details: http://ejohn.org/blog/how-javascript-timers-work/
After you read that article you'll get that your setInterval callback queued to be run in 1000ms after now but only after the current code is finished. It cannot finish though, because of the infinite loop.
zerkms has the correct answer. But I would add that web workers are a way to get some multi-threaded-ish behavior from client side javascript.
var worker = new Worker('my_task.js');
worker.onmessage = function(event) {
console.log("Called back by the worker!\n");
};
The worker runs in a background thread, and you can exchange messages and subscribe to events. It's pretty nifty.
As has been already said - the callback to setInterval doesn't run until the infinite loop finishes. To do what you are trying to achieve - without using web workers - you have to check the time from the loop itself:
var start = Date.now();
while((Date.now() - start) < 5000){
...
}

Game loop crashing my browser

I'm trying to write my first html5 game. However, the game loop causes my browser to become unresponsive (eventually being shut down by the browser). I created a state machine:
while(state != State.EXIT){
switch(state){
case State.SPLASH:
break;
case State.HOW_TO:
break;
case State.PLAY:
oldTime=Date.now();
state=gameLoop();
break;
case State.GAME_OVER:
break;
default:
state=State.EXIT;
}
}
That seems to be working okay. So, then, here's the game loop:
function gameLoop(){
var newTime=Date.now();
var delta=newTime-oldTime;
update(delta/1000);
render();
oldTime=newTime;
return state;
}
This is where the crash happens. If I take out the return statement, it returns null or whatever javascript returns. And, that's fine. It runs once and exits. However, if I leave it in there, this is where the browser seizes up. The update function gives my character the ability to move and the render function draws one image to the screen. Very simple stuff.
NOTE: This is being written in the canvas element if that matters.
SOLUTION! I created a stateSelector() function which contains the switch statement above(without the while). However, rather than state=gameLoop, I used interval=setInterval(gameLoop, 1). Then, I use clearInterval(interval) when I want to stop, followed immediately by stateSelector(). Obviously, if I want to change the state, I do that before calling the stateSelector function. I could probably have it take in a parameter containing the state I want to go into, but that a small change that I could evaluate later. I just wanted to announce my solution in case anyone else runs into this.
JavaScript is single-threaded and runs (in effect) in the GUI thread in all common browser environments. When you're JavaScript, the UI of the browser is not updated until the JavaScript finishes running.
You're using a while loop that will never finish, and so the UI will never get updated. To fix this, you need to restructure a little: render a frame and then tell the browser you want to render another frame soon; it can update the UI and do other browsery things and then it can get back to you to render another frame.
Implementation
There's an experimental new function called requestAnimationFrame that can do this. Since it's still experimental, to use it, you need to check for browser-specific versions of it, or if it's not available at all, provide a fallback. Here are some of the names of the browser-specific versions:
mozRequestAnimationFrame for Gecko (Firefox)
webkitRequestAnimationFrame for WebKit (Chrome and Safari)
msRequestAnimationFrame for Trident (Internet Explorer)
So if an unprefixed requestAnimationFrame is available, use that. If that's not available but a prefixed one is, use that. If none of those work, you can use a fallback:
function fallbackRequestAnimationFrame(func) {
setTimeout(func, 10); // Schedule func to be run in 10 milliseconds.
}
Here's a slightly-modified version of the code found on MDN:
var myRequestAnimationFrame =
window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| fallbackRequestAnimationFrame;
Once you've figured out which requestAnimationFrame function you can use, you can change your game loop (which seems to be not the gameLoop function, which has no loops, but rather the while loop) to look like this:
function runFrame() {
switch(state) {
// state handling code
}
if(state != State.EXIT) {
myRequestAnimationFrame(runFrame);
}
}
Then start it off:
runFrame();
I think you may need some sort of a pause, if you're looping with no pause it will consume all of the CPU processing the loop over and over, preventing the page from rendering.
JavaScript runs on the same thread the browser uses to render the page, so if you write an infinite loop the browser never gets control back to refresh the page. (Modern browsers detect "long running" loops and offer the user a chance to abort them, but that doesn't help you with your game.)
You need to use either setTimeout() or setInterval() with some variation on the following:
function gameLoop() {
// do calculations
// render
// etc
if (!gameOver)
setTimeout(gameLoop, 30);
}
gameLoop();
(Note: your original gameLoop() function doesn't actually loop - your loop is controlled outside the function - whereas what I've just showed does loop.)
The setTimeout() function queues up a function to be run later and then immediately continues with the next line of code. When the current code finishes executing the browser then gets control back to update the display, etc. Then after (approximately) the specified interval (in milliseconds) the queued function is executed.
The effect above is similar to a recursive call where a function calls itself directly, except using setTimeout() yields control back to the browser in the meantime.
Outside the gameLoop() function you can then define event handlers for key and/or mouse events, and have those update variables that gameLoop() will use to decide how to, e.g., move the player's character, something like I said in this answer to another question.
Generally, programmers make loops play nice by adding sleep(s) or yield() calls, but since javascript's event driven model lacks these, you would instead replace your loop with a setInterval() which could call a function containing something like your loop body every specified interval, say, every 33 milliseconds, for a 30 fps experience.

Using setTimeout to improve responsiveness

When looking to improve a page's performance, one technique I haven't heard mentioned before is using setTimeout to prevent javascript from holding up the rendering of a page.
For example, imagine we have a particularly time-consuming piece of jQuery inline with the html:
$('input').click(function () {
// Do stuff
});
If this code is inline, we are holding up the perceived completion of the page while the piece of jquery is busy attaching a click handler to every input on the page.
Would it be wise to spawn a new thread instead:
setTimeout(function() {
$('input').click(function () {
// Do stuff
})
}, 100);
The only downside I can see is that there is now a greater chance the user clicks on an element before the click handler is attached. However, this risk may be acceptable and we have a degree of this risk anyway, even without setTimeout.
Am I right, or am I wrong?
The actual technique is to use setTimeout with a time of 0.
This works because JavaScript is single-threaded. A timeout doesn't cause the browser to spawn another thread, nor does it guarantee that the code will execute in the specified time. However, the code will be executed when both:
The specified time has elapsed.
Execution control is handed back to the browser.
Therefore calling setTimeout with a time of 0 can be considered as temporarily yielding to the browser.
This means if you have long running code, you can simulate multi-threading by regularly yielding with a setTimeout. Your code may look something like this:
var batches = [...]; // Some array
var currentBatch = 0;
// Start long-running code, whenever browser is ready
setTimeout(doBatch, 0);
function doBatch() {
if (currentBatch < batches.length) {
// Do stuff with batches[currentBatch]
currentBatch++;
setTimeout(doBatch, 0);
}
}
Note: While it's useful to know this technique in some scenarios, I highly doubt you will need it in the situation you describe (assigning event handlers on DOM ready). If performance is indeed an issue, I would suggest looking into ways of improving the real performance by tweaking the selector.
For example if you only have one form on the page which contains <input>s, then give the <form> an ID, and use $('#someId input').
setTimeout() can be used to improve the "perceived" load time -- but not the way you've shown it. Using setTimeout() does not cause your code to run in a separate thread. Instead setTimeout() simply yields the thread back to the browser for (approximately) the specified amount of time. When it's time for your function to run, the browser will yield the thread back to the javascript engine. In javascript there is never more than one thread (unless you're using something like "Web Workers").
So, if you want to use setTimeout() to improve performance during a computation-intensive task, you must break that task into smaller chunks, and execute them in-order, chaining them together using setTimeout(). Something like this works well:
function runTasks( tasks, idx ) {
idx = idx || 0;
tasks[idx++]();
if( idx < tasks.length ) {
setTimeout( function(){ runTasks(tasks, idx); },1);
}
}
runTasks([
function() {
/* do first part */
},
function() {
/* do next part */
},
function() {
/* do final part */
}
]);
Note:
The functions are executed in order. There can be as many as you need.
When the first function returns, the next one is called via setTimeout().
The timeout value I've used is 1. This is sufficient to cause a yield, and the browser will take the thread if it needs it, or allow the next task to proceed if there's time. You can experiment with other values if you feel the need, but usually 1 is what you want for these purposes.
You are correct, there is a greater chance of a "missed" click, but with a low timeout value, its pretty unlikely.

Categories

Resources