I've been experimenting with jank-free rendering of complex scenes on HTML5 canvas. The idea is to split rendering into multiple batches with each batch taking a maximum of, say 12 ms, so that the concurrently running animations (very cheap to execute) are not interrupted.
Conceptually, batch-rendering is implemented like this:
function draw(ctx) {
var deadline = window.performance.now() + 12; // inaccurate, but enough for the example
var i = 0;
requestAnimationFrame(function drawWithDeadline() {
for (; i < itemsToRender.length; i++) {
if (window.performance.now() >= deadline) {
requestAnimationFrame(drawWithDeadline);
return;
}
var item = itemsToDraw[i];
// Draw item
}
});
}
The complete code is in this JSFiddle: https://jsfiddle.net/fkfnjrc2/5/. The code does the following things:
On each frame, modify the CSS transform property of the canvas (which is an example of the concurrently-running fast-to-execute animation).
Once in a while, initiate re-rendering of the canvas in the batched fashion as shown above.
Unfortunately, I'm seeing horrible janks exactly at the times when canvas contents is re-rendered. What I don't seem to be able to explain is what the Chrome Developer Tools timeline looks like:
The dropped frames seem to be caused by the fact that the requestAnimationFrame is not called right at the start of the frame, but towards the end of the ideal 16ms period. If the callback started right after the previous frame completed rendering, the code would most likely complete in time.
Rendering to an off-screen canvas (https://jsfiddle.net/fkfnjrc2/6/) and then copying the complete image to the screen canvas helps a little, but the janks are still there with exactly the same characteristics (delayed execution of rAF callback).
What is wrong with that code? Or maybe something's wrong with my browser / machine? I'm seeing the same behaviour on Chrome (49.0.2623.112) on Windows 10 and Mac OS.
The issues seem to be caused by Chrome's specific requestAnimationFrame callback scheduling. I've filed a bug to track this, it contains some simpler reproduction code examples.
Related
I'm trying to use MIDIjs to play audio (specifically because I'm using WAD.js, which uses it). Unfortunately I can't figure out how to loop audio, there's a frustrating lack of documentation, and I'm not finding anything in the source code. At the very least I'd like to know when the file has naturally ended to restart using MIDIjs.play(), but I'm not even seeing anything for that.
I'd appreciate it if someone pointed me towards a solution, thank you.
Correct, there's no loop function (that's documented anywhere here).
However, we can at least simulate it with MIDIjs.player_callback, which is called every 100ms with an object that looks like { time : number }. This is time in seconds elapsed. We can combine this with get_duration to detect the end of playback.
function playAutoReset(url)
{
// get_duration requires a callback as it returns nothing.
MIDIjs.get_duration(url, function (duration)
{
console.info(`Duration: ${duration}`);
// Start the initial play
MIDIjs.play(url);
// This lets us keep track of current play time in seconds
MIDIjs.player_callback = function (mes)
{
if (mes.time / duration >= 1)
{
console.info('End reached');
// This plays the audio again
MIDIjs.play(url);
}
};
});
}
Note:
This does call play each time, so it will probably re-download the source (the docs seem to imply that this is always the case). If you want something more network-efficient, you may want to look into XHRs/AJAX and creating an object URL, as the docs do specify that play takes a URL. However, I thought this would be the simplest solution, and it does show what you have to do to play the midi track again when it ends.
I had this same issue and wasn't entirely satisfied with the workaround in the other answer because of how it reloaded the file every time the url was passed into a MIDIjs function and it seemed to loop slightly too early.
So, I started digging into the MIDIjs source, which also has a map file, but I didn't bother trying to maximize/deobfuscate it. I noticed a couple mentions of loop that are set by second parameter t in function X. I wasn't sure, but thought maybe X was play, so I tried it.
MIDIjs.play(url, true);
It worked!
Since I'm working with Kotlin/JS, here's my external code:
external object MIDIjs {
fun play(url: String, loop: Boolean = definedExternally)
fun stop()
}
Call it in Kotlin exactly the same way, without the semicolon.
MIDIjs.play(url, true)
I am writing a website for one of my friends, and as a joke I want one page to have javascript that uses as much CPU as possible. The javascript doesn't actually have to do anything, just needs to somehow make computers as slow as possible while hes on the page. What is the best way to do this?
Just a simple infinite loop will already do wonders. You can top that off with spinning off a few web-workers that also do the same:
If you set x=0n this will use up more memory (due to BigInts allowing much larger numbers and much more memory)
let x=0;
while(true){
++x;
}
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
Keep in mind that a website by default only uses 1 thread of your CPU. Hence why web-workers to block more threads would be good. :)
You could also add a setInterval/setTimeout in the loop or other things to let the browser have time to repaint to be responsive if you want to hide it better to prevent the browser from prompting to end the unresponsive page.
Edit: You might want to consider adding some actual IO tasks (especially to the harddrive) to really slow it down.
If I remember correctly, there was a bug at one point that was being abused by downloading nearly infinite 0byte files to the host computer to essentially crash it.
This will hold up the CPU less than the initial infinite loop, but will leave the page responsive enough for the browser to not prompt to kill the script.
const wait = () => new Promise((res) => setTimeout(() => res(), 0));
async function timeWaster() {
let x = 0n;
while (true) {
x++;
if (x % 10000000n === 0n) {
await wait();
}
}
}
timeWaster()
I was recently working with <canvas> in JavaScript, and discovered the possibility to create a really bad "memory leak" (more like a memory explosion). When working with the canvas context, you have the ability to do context.save() to add the drawing styles to the "state stack" and context.restore() to remove it. (See the documentation for the rendering context on MDN.)
The problem occurs when you happen to continually save to the state stack without restoring. In Chrome v50 and Firefox v45 this seems to just take up more and more private memory, eventually crashing the browser tab. (Incidentally the JavaScript memory is unaffected in Chrome, so it's very hard to debug this with the profiler/timeline tools.)
My question: How can you clear out or delete the state stack for a canvas context? With a normal array, you would be able to check on the length, trim it with splice or simply reset is back to empty [], but I haven't seen a way to do any of this with the state stack.
[I].. discovered the possibility to create a really bad "memory leak"
This is technically not a memory leak. A leak would be to allocate memory and loose the pointer to it so it could not be freed. In this case the pointer is tracked but the memory not released.
The problem occurs when you happen to continually save to the state stack without restoring.
That is to be expected. Allocating memory without freeing it will accumulate allocated memory blocks.
How can you clear out or delete the state stack for a canvas context?
The only way is to either restore all saved states, or to reset the context by setting some size to the canvas element (ie. canvas.width = canvas.width).
It's also safe to call restore() more times than save() (in which case it just returns without doing anything) so you could in theory run it through a loop of n number of iterations. This latter would be more in the bad practice category though.
But with that being said: if there is a mismatch in numbers of save and restore when it's suppose to be equal, usually indicates a problem somewhere else in the code. Working around the problem with a reset or running multiple restores in post probably will only contribute to cover up the actual problem.
Here's an example on how to track the count of save/restore calls -
// NOTE: this code needs to run before a canvas context is created
CanvasRenderingContext2D.prototype.__save = CanvasRenderingContext2D.prototype.save;
CanvasRenderingContext2D.prototype.__restore = CanvasRenderingContext2D.prototype.restore;
// Our patch vectors
CanvasRenderingContext2D.prototype.__tracker = 0;
CanvasRenderingContext2D.prototype.save = function() {
this.__tracker++;
console.log("Track save:", this.__tracker);
this.__save()
}
CanvasRenderingContext2D.prototype.restore = function() {
this.__tracker--;
console.log("Track restore:", this.__tracker);
this.__restore()
}
// custom method to dump status
CanvasRenderingContext2D.prototype.trackstat = function() {
if (this.__tracker)
console.warn("Track stat:", this.__tracker);
else
console.log("Track stat: OK");
}
var ctx = document.createElement("canvas").getContext("2d");
ctx.save(); // do a couple of save()s
ctx.save();
ctx.restore(); // single restore()
ctx.trackstat(); // should report mismatch of 1
ctx.restore(); // last restore()
ctx.trackstat(); // should report OK
I'm trying to load some assets using the PreloadJS class from the CreateJS suite, but when very first progress event fired reports e.loaded and e.progress as 0.83, the numbers then decrease over the next few event before finally going back up again.
t.assets = new createjs.LoadQueue();
t.assets.installPlugin(createjs.Sound);
t.assets.setMaxConnections(10);
t.assets.addEventListener("progress", function(e){
console.log(e.loaded);
});
t.assets.addEventListener("complete", function(){
callback();
});
t.assets.loadManifest([
{ id: 'facebook_btn', src: 'img/ingame/facebook_white_btn.png' },
{ id: 'twitter_btn', src: 'img/ingame/twitter_white_btn.png' },
{ id: 'embed_btn', src: 'img/ingame/embed_white_btn.png' },
]);
I get these results in the console
0.8333333333333334
0.7142857142857143
0.625
0.5555555555555556
0.5
0.45454545454545453
0.4984983736293216
0.5894074645384125
0.6363636363636364
0.6663361591119469
0.7572452500210378
0.8261679748749072
0.9170770657839982
0.9390634318392196
1
Is this because it's working through the manifest initally and doesn't take into account everything right away?
Is checking the the progress is going the correct way before displaying these results in a preloader a good method of making sure it's sorted itself out?
Essentially the manifest is just providing a convienient way to deal with a batch of assets without you having to loop through them yourself. Have a look at the code, it's almost the same as the loadFile method, but loops through the array of objects. So it's not doing anything fancy in terms of better calculating the progress.
http://createjs.com/Docs/PreloadJS/files/.._src_preloadjs_LoadQueue.js.html#l898
You have a couple options though to make it look more accurate.
If you're not loading much and it's only going to take a few seconds you might just want to not use the progress event. Instead you can just catch load errors or other hangups.
If you are loading lots of things and it could take some time then consider waiting a few seconds before displaying the progress. That gives the manifest time to add enough stuff that it should just be moving in a positive direction towards 1.
function loopthrough (i) {
i++;
if (i <= 20) {
play_multi_sound("aud"+i);
$("#debug").html(i);
setTimeout("loopthrough("+i+");",242); }
else {
loops++;
$("#debug").html("rest");
$("#loops").html(loops);
setTimeout("loopthrough("+0+");",1000);
}
}
Does my code look like it is forking? since after about 3 loops it is litrally crashing the browser. I am using excessive HTML5 audio since I like to test new features and whilst im still a newbie at Javascript I really need this code to work. (Im making a simple beat game in canvas where you use a pong style paddle and each ball you hit of varying speed it will play a sound, thus making a cool beat)... Atm im just testing the capabilities of alot of audio being looped through at once.
So yeah my basic question is "Is my code efficient, is it forking and is there anyway of improving this greatly if you know of a better way?"
Thanks,
Tom C.
EDIT:
Just removed my loops++; (for some reason it was returning NaN even though it was defined as global) and it actually successfully looped 20 times without flaws.
I just did some tests, and I am sticking it here so that the code is clear.
function loopthrough (i) {
i++;
if (i <= 20) {
console.log("bla");
setTimeout(function(){ loopthrough(i); }, 242); }
else {
console.log("lala");
setTimeout(function(){ loopthrough(0); }, 1000);
}
}
loopthrough(0);
That worked fine and it never crashed. And it didn't fork either.
So, it's crashing on something else.
I would try this in another browser as well, and see if it crashes - could just be the FF beta 8 crashing.
Try removing 1 bit at a time from it and see if it starts to work - it's a good way to narrow down the culprit.
I bet it's the audio that's crashing it...
* update *
Try bumping up the timeout delay - it could be that the audio doesn't finish and then attempts to play again and is somehow backlogging.