I have a script that draws a bunch of lines on canvas, but it's pretty intense so while rendering freezes browser for a few seconds. I added setTimeout() so that the browser wouldn't freeze and it effectively messed up my script. It's difficult to explain how, so I have two examples online:
Without setTimeout() : http://www.modwebsolutions.com/test1
With setTimeout() : http://www.modwebsolutions.com/test2
Note, that I only change a single line in the whole script, that is line 69:
without setTimeout(): vLoop();
with setTimeout(): setTimeout(vLoop,1);
The problem here, as hinted at by others, is that you are drawing the lines a quadrant at a time. As soon as the SetTimeout method is called and the first vLoop returns, the code carries on running into the next drawVertical which changes all the global variables and so on.
What you need to do is synchronise how you're calling vLoop and how you are changing the globals.
This is basically the solution:
Replace ...
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2-50,0);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2-50,0);
... with ...
var q = new Array();
q[0] = [c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2+50,y];
q[1] = [c,step,stepInt,bigStep,xStart,xEnd,y/2+50,y];
q[2] = [c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2-50,0];
q[3] = [c,step,stepInt,bigStep,xStart,xEnd,y/2-50,0];
drawQuadrant(q, 0);
Replace your drawVertical function with ...
function drawQuadrant(q, i)
{
var r = q[i];
c__ = r[0];
step__ = r[1];
stepInt__ = r[2];
bigStep__ = r[3];
xStart__ = r[4];
xEnd__ = r[5];
yStart__ = r[6];
yEnd__ = r[7];
vLoop(q,i);
}
change the vLoop function prototype to look like this ...
function vLoop(q,i)
and finally replace your recursive vLoop call (from within vLoop) with ...
if ((xStart__ > 0) && (xStart__ < window.innerWidth))
{
setTimeout( function(){vLoop(q,i)}, 1 );
}
else if (++i < 4)
{
setTimeout( function(){drawQuadrant(q,i)}, 1 );
}
The last block is where it ensures that the quadrants are not stepping over each other.
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2-50,0);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2-50,0);
Your calling 4 recursive functions of vLoop at once. The problem here is that setTimeout is non-blocking where as recursion is blocking. So basically you now have all 4 of these drawVertical functions running in parallel rather then in sequence.
The other problem is that all 4 refer and mess with global state and your entire program breaks.
What's happening is what setTimeout() delays all that execution until later. Unfortunately, by that point your global variables have all moved to their ending positions from the initial loop, since it completed before the first line was drawn.
If you moved your timeout further up (so the shared variables you're using aren't affected until draw time) you could achieve what you're after, for example:
setTimeout(function() {
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2+50,y);
drawVertical(c,-1*step,-1*stepInt,-1*bigStep,xStart,xEnd,y/2-50,0);
drawVertical(c,step,stepInt,bigStep,xStart,xEnd,y/2-50,0);
});
Then this would work (but it's dangerous, order isn't absolutely garunteed!)
You can see a working example here.
Related
Somehow i did my research and found out javascript is single threaded !.
I've been trying to figure out , how to make animation via for loop in javascript.
This is what i've done so far.
I'm trying to draw an object on a canvas using javascript. It run the loop it doesn't go step by step. It's just run the loop and draw once instead of 10 times and it ignores the timeout function.
In a single instance , it drew (P/S. Ignore the multiple version cause i was testing it so i removed the context.clearRect(0, 0, context.canvas.width, context.canvas.height); :
JS:
// down button click
down.onclick = function() {
if (!imgLoaded) return;
flag=false;
setTimeout(function() {
for(var i = 0; i < 15 ; i++) {
posY += 10;
context.drawImage(img, posX, posY );
}}, 9);
// call next step
That's not the way setTimeout works. The way you wrote it, setTimeout calls its first parameter one time, after 9 ms, then loops with no delay.
You could use the setInterval function (don't forget to removeInterval after the 15th iteration) if you want to be called a regular interval.
Note that in both case (setTimeout, setInterval), given delay is only indicative.
What you might want to use is the Window.requestAnimationFrame() function, which is the usual way to do animation in the browser.
I have written javascript that takes 20-30 seconds to process and I want to show the progress by updating the progress bar on my webpage.
I have used setTimeout in an attempt to allow webpage to be re-drawn.
This is how my code looks like:
function lengthyFun(...){
for(...){
var progress = ...
document.getElementById('progress-bar').setAttribute('style',"width:{0}%".format(Math.ceil(progress)));
var x = ...
// Processing
setTimeout(function(x) { return function() { ... }; }(x), 0);
}
}
It does not work, I know why it does not work, but I don't know how to refactor my code to make it work.
As you probably know, the problem here is that you main process (the one that takes a lot of time), is blocking any rendering. That's because JavaScript is (mostly) mono-threaded.
From my point of view, you have two solutions to do this.
The first one is to cut down your main process into different parts and to do the rendering between each of them. I.e. you could have something like that (using Promises) :
var processParts = [/* array of func returning promises */];
function start(){
// call the first process parts
var firstPartPromise = (processParts.shift())();
// chain it with all the other process parts interspersed by updateDisplay
return processParts.reduce(function(prev, current){
return val.then(current).then(updateDisplay);
}, firstPartPromise);
}
You will probably need a polyfill for the promises (one here). If you use jQuery, they have a (bad non standard) implementation.
The second solution can be to use webworkers which allows you to create threads in JavaScript. It works on all modern browsers.
It is probably the best solution in your case.
I never used them but you are supposed to be able to do stuff like:
var process = new Worker("process.js");
worker.onmessage(function(event){
updateProgress(event.data.progress)
});
And the in process.js:
postMessage({progress: 0.1});
// stuff
postMessage({progress: 0.4});
// stuff
postMessage({progress: 0.7});
//etc
Try setting progress element attribute min to 0 , max to 20000 , value to 0 ; create function where if value less than max increment value by 1000 ; utilize setTimeout with duration set to 1000 to call function recursively until value reaches max
var p = document.querySelector("progress");
function redraw() {
if (p.value < p.max) {
p.value += 1000;
setTimeout("redraw()", 1000)
}
}
redraw()
<progress max="20000" min="0" value="0"></progress>
There are a couple of ways that I know of to trigger sequential HTML redraws through Javascript:
Incremental Timeout Period
Recursive Method Calls
The first and easiest way of doing this is by using a multiplier (such as the iterator) on the timeout interval in a loop. This method should be sufficient if the operation is independent of external variables and only needs to be run a finite and relatively few number of times. The more operations required/likely to occur, the greater the strain on resources - just for calculating intervals. Another drawback takes effect when the processing time exceeds the timeout interval, causing a knock-on to the interval of the observed redraws. The result of this can be that the web page freezes up entirely until all operations are done.
Example
for (var i=0, limit=n; i<limit; i++) {
setTimeout((function(params) {
return function() {
some_func(params);
}
})(param_values), i*1000);
}
The second method is a little more convoluted, but guarantees redraws between each operation, regardless of the timeout interval. Here, the timeout only affects the time between redraws ands resists the effects of consecutive operation variables. However, the processing time for the current operation is still a factor for the observed interval and will still freeze up a web page between redraws if the operation is computationally intensive.
Example
var limit = n;
var i = 0;
recursive_timeout();
function recursive_timeout() {
setTimeout((function(params) {
return function() {
some_func(params);
i++;
if (i<limit) {
recursive_timeout();
}
}
})(param_values, i, limit), 1000);
}
Refined Example (based off guest271314's answer)
var still_true = true;
recursive_timeout();
function recursive_timeout() {
some_func(params);
if (still_true) {
setTimeout(function() {recursive_timeout();}, 1000);
}
}
While the incremental method is fine for simple tasks, recursion will reliably perform redraws. If long processing times per operation is an issue, then it might be worth delving into asynchronous tasks in addition to using recursion in order to avoid rendering a web page temporarily unusable.
Anyway, hope this helps!
Ha! Just realised guest271314 put up a much more elegant example of the recursive method... Oh well, more info can't hurt.
Why does the div[id=box] not get updated until the for loop finishes? If I comment out the for loop, the div displays instantly.
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// loop after element update
for (var i = 0; i < 2000000000; ++i) {}
};
http://jsfiddle.net/472BU/
Simply, ALL browser processes (JS, repainting the page, even responding to user-clicks/key-presses and in most cases refreshes page-changes... even closing the tab) all happen in the same process thread.
Thankfully this isn't 100% true, 100% of the time, anymore.
Certain browser-vendors are working to move different parts of the web-platform to different threads, for a smoother experience, but typically, if you lock your JS up, you lock everything.
This simply means that the browser won't actually repaint until JS has finished running, and gives control back to the DOM.
The good news is that it means you can measure elements by unhiding them, grabbing their dimensions and hiding them again, at the end of the function. The width/height that they would take up is calculated on the spot, but a large portion of the page might have to be painted if you change an element, so if it's possible to change 30000 elements in a loop, then painting them all as it happens would be a very bad thing.
The cause is already explained by others. If you want the box to be painted instantly, the solution is simple. Put the loop in a timeout:
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// no delay anymore
setTimeout( function(){for (var i = 0; i < 2000000000; ++i) {}},10);
};
jsFiddle
Also check web workers
That amount of iterations running continuously will use up all of the browser's resources and it won't be able to worry with applying styles.
Your javascript is executed in the order it appears there, but behind the scenes there is a queue for rendering style changes. In any normal usage, you wouldn't notice this behavior, but since you're running an poor performant loop, it becomes evident.
Problem
It's because JavaScript is single-threaded and will only be able to run that loop.
Anything else will be on hold for as long as the loop lasts. As the DOM is wired into the JavaScript the DOM will be blocked as well (in general, except in browsers where DOM runs on a separate thread and will generate an event for the event queue instead which will be on hold until the current executing scope has finished).
Solution
To avoid this you need to split your functions into several asynchronous operations (not the same as multi-threaded) which will enable the browser to invoke some of the events queued up in the event queue (for example paint events).
You can do this by splitting up your function to perform iteration in segments using an inner mechanism to dispatch batches instead.
For example:
Live demo
function busyLoop(callback) {
var segCounter = 0, /// keep track of segment
totCounter = 0, /// keep track of total count
max = 2000000000, /// max count
segment = 1000000; /// segment size (smaller = better response)
/// invoke first batch
(function nextBatch() {
segCounter = 0; /// reset segment counter for each time
for(; segCounter < segment && totCounter <= max; segCounter++, totCounter++) {
///...work here...
}
if (totCounter < max) {
/// call setTimeout() which makes it async, +/- 11ms gives browser
/// chance to process other events such as paint events:
setTimeout(nextBatch, 11);
/// optional progress callback here
} else
callback();
})();
}
Then call it with a callback function:
busyLoop(doneFunction);
Notice that you can now interact with DOM as well as getting feedback.
Tip: The smaller segments the more responsive the DOM but the longer the total time as the delay in-between accumulates. Experiment to find a balance that suits your solution.
Hope this helps.
Im trying to do code in javascript a ruzzle solver. For now it just dig through the maze and find every possible path ( in the future I will match them against a dictionary to find the real valid words in it)
You can see it here : http://178.239.177.105/ruzzle/
I wanted to do it with an animation that show how the algorithm works on it, but im issuing a problem.
If you load it, the page just dont show anything, and my browser crash after a while.
BUT...
if you set an alert("") function, somewhere in the middle of the recursion function, you would be able to go through any step in the algorithm.
Especially if you set the browser to prevent to show any further alert messages, you'll finally see the animation working on the maze.
I was actually trying to do this via setInterval(), but is not working.
So I have two questions:
- Why do the script cause the page to crash, or not if there's an alert?
- How can I properly show the animation using some kind on wait() mechanism?
Thanks
You can see all the code by going on the page and look at the source code, however for the sake of clarity I'll paste the relevant code here:
You can also play with the code here : http://jsfiddle.net/Gcw2U/
(you will have to uncomment the last line in the to make it run)
//this matrix of chars rapresent the 4x4 puzzle
var ruzle_model = [["w","a","l","k"],["m","o","o","n"],["h","a","t","e"],["r","o","p","e"]];
// ""offsets" rapresent the four motion vector(up,down,left,right)
// used to visit the matrix
var offsets = [[1,0],[0,1],[-1,0],[0,-1]];
//recursive function to dig the maze
function path(m,i,j,paths,checkeds){
alert("SET BROWSER TO AVOID NEXT ALERTS MSGs!");
//base case, if not hitting a wall or already checked cell
if ( ! (i<=3 && i>=0 && j>=0 && j<=3) || isChecked(checkeds,i,j)){
terminal.innerHTML = terminal.innerHTML + "-"+ paths;
uncheckAllCells();
return paths;
}
//call path for every direction (up,down,left,right) stored in offsets
var tmp = [];
for (var c=0; c<offsets.length;++c){
var offset = offsets[c];
checkCells(i,j);
checkeds.push(new Array(i,j));
tmp.push(path(m,i+offset[0],j+offset[1],paths + m[i][j],copy(checkeds)));
}
return tmp;
}
//call path on every cell in the maze
function ruzzle(r){
var sol = []
for(var i=0; i<4; ++i){
for(var j=0; j<4; ++j){
var checkeds = new Array();
sol.push(path(r,i,j,'',checkeds));
}
}
terminal.innerHTML = sol;
return sol;
}
Javascript loops and recursions inhibit rendering of the page, so any changes made will stay invisible until the script stops executing, like when you spawn an alert. When a user sets "do not show alert messages", the alert still yields execution time to the underlying eventloop, which will update the page.
For as-fast-as-possible (high fps) animations, use requestAnimationFrame().
In your case, setTimeout() is the best way to go. Set a timeout on the recursive call to path.
function recursive(args) {
// do stuff to args
setTimeout(function () {
recursive(args);
}, 5);
}
Example
nowadays i am optimizing some js code.
there is a function named appendXYZ,and it is invoked in a loop with other functions.
it looks like as the following:
function OuterFunc (){
for(...){// about 150 times
...
appendXYZ();
//other dependent functions
...
}
}
and now i am pretty sure that appendXYZ cause high cpu usage - it can reach 50%,
but if i remove this function,cpu usage is only 1%.
when the cpu usage is 50%,the browser is nearly frozen and the page is lack of responsiveness.
what is more ,the OuterFunc execute every 20 seconds and appendXYZ is from a third party script code and i cant modify it.
so how to optimize this code?
now i am trying to use setTimeout but i dont know whether it works.
I don't know what that function does, but you could try making its invocation asynchronous.
It may or may not work, and it will still require the same amount of CPU, but it should at least free up the browser a bit.
function OuterFunc (){
for( var i = 0; i < 150; i++ ){
// ...
setTimeout( appendXYZ, 0 );
//other dependent functions
// ...
}
}
Again this may break the function. Can't tell without seeing more code.
If you're passing arguments, then you'd need something like:
function invoker( j ) {
return function() {
appendXYZ( j );
};
}
function OuterFunc (){
for( var i = 0; i < 150; i++ ){
// ...
setTimeout( invoker( i ), 0 );
//other dependent functions
// ...
}
}
If there's nothing you can do to optimize the actual code, you can spread around the execution of the loop iterations to keep the browser responsive. According to Robert Miller's paper, the maximum amount of time you can hold up a UI and still have it feel responsive to the user is 100 milliseconds. For a technique of how to do this using setTimeout see UI responsiveness and javascript.
A possibility is that the OuterFunc execution time is bigger that it's repetition interval.
In other words, the OutherFunc takes longer than 20 milliseconds to execute and being called every 20 seconds it will produce a stackoverflow exception because the function is being called before it finished it's execution in an infinite loop.
If you are using setInterval to execute the OuterFunc function every 20 milliseconds, this can be fixed by using setTimeout calls to simulate the setInterval function :
(function helper(){
OutherFunc();
// after the OutherFunc is done executing, trigger it after 20 milliseconds
setTimeout(helper, 20);
})();
This might help you only if the setInterval is the cause of the browser freeze.
If this doesn't help you and if you don't care that much about old browsers, maybe you could implement a sort of "threading" using web-workers. This way your code gets executed in different threads which will definitely speed up your app (a.k.a bye bye browser freeze).
Hope this helps!