I am attempting to use a callback to create an asynchronous function that does calculations behind a draw loop without slowing it down. I have read many callback examples and am clearly doing something wrong.
I have created a simplified version of what I would like to do. When you click your mouse it should do the math without hanging up the draw loop. Right now it causes a hangup:
var nPoints = 1;
var sumDone = false;
var sum = 0;
function setup() {
var myCanvas = createCanvas(200, 200);
myCanvas.parent("p5");
}
function draw(){
nPoints = nPoints+1
stroke(0,0,0);
background(245,245,245);
noFill();
rect(1,1,198,198);
textAlign(CENTER);
fill(0,0,0);
textSize(20);
if(sumDone){
text(sum,100,20);
}else{
text("not done",100,20);
}
noStroke();
push();
translate(100,100);
rotate(nPoints/50);
rect(-50,-10,100,20);
pop();
}
function mouseClicked(){
if(sumDone){
sumDone = false;
sum=0;
}else{
doMath(function (){
sumDone = true;
});
}
}
function doMath(callback){
for(var i=0;i<10000000;i++){
sum = sum + i;
}
callback();
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<body>
<div id="p5" align="center">
</div>
<script>
<!-- JAVASCRIPT CODE GOES HERE -->
</script>
</body>
As you can see, the math still completely hangs up the draw loop. Is it possible to do this in a way where the draw loop is not effected?
JavaScript is single-threaded. Moving the work to a callback function just ties up the thread that's used to run draw() and every other JavaScript function.
The only way to offload work like this is to do it on the server and use AJAX to query for the result.
Other than that, perhaps try breaking the calculation down into smaller chunks. Can you do 10% of the calculation per frame or something?
Edit: These threads mention the concept of web workers which you might look into.
Related
I am VERY new to P5.js/processing (taking programming for artists). I am trying to make a crude game where an image (Jar Jar) bounces across the screen and another image (lightsaber) that moves with the mouse and when the mouse attached image goes over the bouncing image then the lightsaber will be mirrored and activate a sound. If this at all makes sense...
I have the bouncing image part down so far, but I am unable to make the mousePressed() function work. like I mentioned, I need the "lightsaber.png" to flip when the mouse is pressed. Also, when the mouse is pressed and is directly over the JarJar image, how would I add a score count and sound event?
Thank you!
here is my code so far:
let jarJar;
let jarJarX=5;
let jarJarY=5;
let xspeed;
let yspeed;
let lightSaber;
function preload() {
jarJar = loadImage('jarjar.png');
lightSaber= loadImage ('lightSaber.png');
}
function setup() {
createCanvas(700,700);
xspeed=random (15,22);
yspeed=random (15,22);
}
function draw() {
background(0);
image (lightSaber,mouseX,mouseY,100,100);
image(jarJar,jarJarX,jarJarY, 140, 200);
jarJarX= jarJarX+xspeed;
if (jarJarX<=-300|| jarJarX>=width+200){
xspeed=xspeed*-1;
}
jarJarY= jarJarY+yspeed;
if (jarJarY<-200|| jarJarY>=height+200 ){
yspeed=yspeed*-1;
}
//picture mirrors when mouse pressed
if mouseClicked(){
scale(-1,1);
image(lightSaber);
}
//score counter coordinate with lightsaber hitting image
//
}
Let it be known that I'm not proficient at javaScript. This said, your question is quite simple so I can help anyway.
Some framework will have simple ways to mirror images. Processing likes to scale with a negative number. I re-coded some of your stuff to accommodate my changes. The main changes goes as follows:
I added a method to draw the lightsaber so we can "animate" it (read: flip it for a couple frames when the user clicks around).
I added a 'score' global variable to track the score, and a way for the user to see that score with the text method.
I added a method called "intersect" which isn't very well coded as it's something I did back when I was a student (please don't hurt me, it works just right so I still use it from time to time). For more details on how simple collisions works, take some time to read this answer I wrote some time ago, there are nice pictures too!
I added a mouseClicked method. This method will act like an event, which means that it will be triggered by a specific call (a left mouse button click in this case). This method contains the code to check for a collision between the squares which are the images. If there's an overlap, the score will increase and jarjar will run in another direction (this part is a bonus to demonstrate that this is the place where you can get creative about the collision).
I commented the code so you can get what I'm doing more easily:
let jarJar;
let jarJarX=5;
let jarJarY=5;
let xspeed;
let yspeed;
let lightSaber;
let flipLength;
let score = 0;
function preload() {
jarJar = loadImage('jarjar.png');
lightSaber= loadImage ('lightSaber.png');
}
function setup() {
createCanvas(700, 700);
runJarJarRun();
}
function draw() {
background(0);
drawLightSaber(); // this way I can deal with the lightsaber's appearance in a dedicated method
image(jarJar, jarJarX, jarJarY, 140, 200);
jarJarX= jarJarX+xspeed;
if (jarJarX<=-300|| jarJarX>=width+200) {
xspeed=xspeed*-1;
}
jarJarY= jarJarY+yspeed;
if (jarJarY<-200|| jarJarY>=height+200 ) {
yspeed=yspeed*-1;
}
//score counter coordinate with lightsaber hitting image
textSize(30);
fill(200, 200, 0);
text('Score: ' + score, 10, 40);
}
function drawLightSaber() {
if (flipLength) { // if the number is > 0 this will be true
flipLength--; // measure how ling the saber is flipped in frames # ~60 frames per second
push(); // isolating the translate ans scale manpulations to avoid ruining the rest of the sketch
translate(mouseX + 100, 0); // makes the coordinates so once flipped the lightsaber will still appear at the same location
scale(-1.0, 1.0); // flip x-axis backwards
image (lightSaber, 0, mouseY, 100, 100);
pop(); // ends the sequence started with 'push();'
} else {
image (lightSaber, mouseX, mouseY, 100, 100);
}
}
function runJarJarRun() {
xspeed=random (5, 10);
yspeed=random (5, 10);
}
function mouseClicked() { // this method will trigger once when the left mouse button is clicked
flipLength = 10;
if (intersect(jarJarX, jarJarY, 140, 200, mouseX, mouseY, 100, 100)) {
score++;
runJarJarRun(); // as a bonus, jarjar will run in another direction on hit
// you could totally put some more special effects, like a flash, a sound, some 'mesa ouchie bad!' text, whatever speaks to you
}
}
function intersect(x1, y1, w1, h1, x2, y2, w2, h2) {
let checkX = false;
let checkY = false;
if ( (x1<x2 && (x1+w1)>x2) || (x1<(x2+w2) && (x1+w1)>x2+w2) || (x1>x2 && (x1+w1)<(x2+w2)) ) {
checkX = true;
}
if ( (y1<y2 && (y1+h1)>y2) || (y1<(y2+h2) && (y1+h1)>y2+h2) || (y1>y2 && (y1+h1)<(y2+h2)) ) {
checkY = true;
}
return (checkX && checkY);
}
If there's something you don't understand, let me know in a comment and I'll be happy to elaborate. Good luck and have fun!
Hi and welcome to stack overflow. One thing to keep in mind when submitting here (or any forum where you're looking for help with code) is to post a minimal reproducible example. You'll be much more likely to get useful responses.
You'll also want to separate out your questions, as they each have multi-step responses.
Your first question is about how to get your sketch to display something when you press the mouse down. Your syntax isn't quite correct there. Here's a minimal example of how to check for a mouse held down.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
if (mouseIsPressed == true) {
ellipse(100, 100, 100, 100);
}
}
Just a quick note that I tried to make this as 'novice-friendly' as possible. The == true is optional and not usually included.
The squares still get drawn even tough they are deleted from the array that is drawn. Shouldn't they be deleted when they are being deleted from the Array. Does the array not update inside the go function?
Javascript:
var canvas;
var ctx;
$(document).ready(function(){
$("#thebutton").on('click',function(){
thearray.pop();
})
canvas = $('#canvas').get(0);
ctx =canvas.getContext("2d");
})
class Square{
constructor(p1,p2,p3,p4){
this.p1=p1;
this.p2=p2;
this.p3=p3;
this.p4=p4;
}
start(){
var that = this;
setTimeout(function(){
that.go();
that.start();
console.log("timeout running");
},1000);
}
go(){
for(let i = 0; i<thearray.length;i++){
console.log("loop running:"+i);
if(true){
ctx.clearRect(0,0,500,500);
console.log("clearing rect");
}
ctx.rect(thearray[i].p1, thearray[i].p2, thearray[i].p3, thearray[i].p4);
ctx.stroke();
}
}
}
var thearray=[];
var thesquare1 = new Square(20,20,150,100);
var thesquare2 = new Square(100,100,200,200);
var thesquare3 = new Square(200,200,300,300);
thearray.push(thesquare1);
thearray.push(thesquare2);
thearray.push(thesquare3);
thesquare1.start();
HTML:
<canvas id="canvas" height="500" width="500"></canvas>
<button type="button" name="button" id="thebutton">Pop Array</button>
Spent almost an hour on debugging your code!
This led to the finding that if fillRect() is used instead of rect() the code works well...
and then finally found this..
(I too didn't know it before ><)
Have a look at this: link
In short, just call beginPath() after clearRect() to start a new path instead of using old path stack!!!
go(){
ctx.clearRect(0,0,500,500);
ctx.beginPath(); //This line saved the day :)))
console.log("clearing rect");
for(let i = 0; i<thearray.length;i++) {
console.log("loop running:"+i);
ctx.rect(thearray[i].p1, thearray[i].p2, thearray[i].p3, thearray[i].p4);
ctx.stroke();
}
}
using this the code works for rect() as intended :)
Note: also you have to move that clearRect() call outside the for loop otherwise it will clear the canvas after every single iteration of the loop which results in showing only the 3rd rectangle on the canvas.
Also, that if(true){} is not at all necessary.
Update: Also checkout this thread for some other alternatives to beginPath() to handle such scenario
I'm working on a Paint App using Processing.js. Basically, when the mouse is dragged, mouseX and mouseY are saved in an array of objects called data[]. Afterwards the paint() function will run a loop that accesses every object of the data[] array and draws a line of color(data[i].R,data[i].G,data[i].B) and thickness data[i].T between the corresponding data[i].mouseX and data[i].mouseY coordinates. The problem is that the array keeps getting bigger the more you draw and in my case, when the length of the data[] array reaches ~800 elements it will start to lag, and keeps getting worse the more I keep drawing. Is there any tweak that will fix the lag or do I have to completely rethink the program?
<!DOCTYPE HTML>
<html>
<head>
<script src="https://github.com/downloads/processing-js/processing-js/processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="targetcanvas">
void setup() {
size(649, 600);
}
background(255,255,255);
var r=0;
var g=0;
var b=0;
var data = [];
var mousex;
var mousey;
var thickness=31;
var painting = false;
var counter=0;
var x;
var paint = function() {
background(255, 255, 255);
for(var i=1;i<data.length;i++){
if (data[i-1].mousex && data[i].mousex) {
strokeWeight(data[i].T);
stroke(data[i].R, data[i].G, data[i].B);
line(data[i].mousex,data[i].mousey,data[i-1].mousex,data[i-1].mousey);
fill(0,0,0);
text(data.length,10,10);
}
};
};
mouseDragged = function(){
painting = true;
data.push({mousex: mouseX, mousey: mouseY, R:r, G:g, B:b, T:thickness});
paint();
counter++;
};
mouseReleased = function() {
x=counter;
counter=0;
if(painting) {
data.push({mousex: 0, mousey: 0});
}
painting = false;
};
mouseOut = function() {
data.push({mousex: 0, mousey: 0});
}
</script>
<center>
<canvas id="targetcanvas"width="649" height="600" " style="border: 3px solid black; margin-top=100px;"></canvas>
</center>
</body>
</html>
Is this Processing.js or P5.js? Either way, the answer is the same.
You basically have a spectrum of options:
No data structures, no undo: Instead of storing your shapes and whatnot in data structures, just draw them directly to the canvas whenever the user does something. You can use a PGraphics canvas, or you can just draw directly to the screen, if you get rid of the call to background(). Then you don't need any data structures. The downside of this is you won't be able to remove shapes once they're drawn.
Some data structures, some undo: If you needed to be able to undo some of the shapes, but not all of them, you could do a mix of the above approach and your current approach. Instead of storing everything in data structures, you would only store the last 1-10 or so shapes. The rest of the shapes would be drawn directly to the PGraphics buffer.
Lots of data structure, lots of undo: If you really needed to be able to undo all of the shapes, then you could still use the PGraphics approach, but only redraw all of the shapes when something was removed.
I'm trying to stagger for loops over time to draw a grid and keep system load down. For example: for a 100x100 grid that would be 10,000 iterations, instead of doing them all instantly, I want to do 1,000 then wait 250ms and carry on the next 1,000 until it's complete.
I can't seem to figure out why it isn't working.
In this example i am trying to do just that, but it only draws the first 1,000 squares, but console.log('iterate'); still runs and the iteration values follow on!
http://jsfiddle.net/FMJXB/2/
In this example, i have removed the setTimeout and instead calling the function twice, to simulate 2 loops with instant effect, and that draws 2,000 squares!
http://jsfiddle.net/FMJXB/3/
Why does having the function in a setTimeout stop the code from working?
Here’s a coding pattern to stagger drawing iterations using setTimeout
The logic goes like this:
Use variable ”j” to track horizontally draws across your grid.
Use variable “i” to track vertical draws down your grid.
Use variable “iterations” to cut the drawing into blocks of 1000 draws at a time
When iterations has finished it’s 1000 draws, call setTimeout.
This code is for illustration—you will need to adapt it for your easelJS specific needs.
function draw(){
// reset iterations for this block of 1000 draws
var iterations = 0;
// loop through 1000 iterations
while(iterations++<1000){
// put drawing code here
graphics.drawRect(pixl.pixelSize*j, pixl.pixelSize*i, pixl.pixelSize, pixl.pixelSize);
// increment i and j
if(++j>canvas.width-1){ i++; j=0; }
// if done, break
if(i*j>pixelCount) { break; }
}
// schedule another loop unless we've drawn all pixels
if(i*j<=pixelCount) {
setTimeout(function(){ draw(); }, 1000);
}
}
Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/Z3fYG/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:20px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var i=0;
var j=0;
var pixelCount=canvas.width*canvas.height;
draw();
function draw(){
ctx.beginPath();
var iterations = 0;
while(iterations++<1000){
// put drawing code here
ctx.rect(j,i,1,1);
// increment i and j
if(++j>canvas.width-1){ i++; j=0; }
// if done, break
if(i*j>pixelCount) { break; }
}
ctx.fill();
// schedule another loop unless we've drawn all pixels
if(i*j<=pixelCount) {
setTimeout(function(){ draw(); }, 1000);
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas width="100" height="100" id="canvas"></canvas>
</body>
</html>
I have been trying to set up a javascript game loop and I have two issues I am running into. I find that in chrome when I lose focus of the browser window and then click back the animation I have running does this weird "catch up" thing where it quickly runs through the frames it should of been rendering in the background. I also have noticed that the animation is blury when moving at the current speed I have it at yet other people have been able to get their canvas drawings to move quickly and still look crisp. I know their seems to be a lot out about this but I cant make sense of what my issue really is. I thought this was a recommended way to create a game loop.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Frame Test</title>
<link href="/css/bootstrap.css" media="all" rel="stylesheet" type="text/css" />
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"
type="text/javascript">
</script>
<script language="javascript" src="js/jquery.hotkeys.js" type="text/javascript"></script>
<script language="javascript" src="js/key_status.js" type="text/javascript"></script>
<script language="javascript" src="js/util.js" type="text/javascript"></script>
<script language="javascript" src="js/sprite.js" type="text/javascript"></script>
</head>
<body>
<button id="button1">
Toggle Loop</button>
<h1 id="frameCount">
Game Loop Test</h1>
<canvas id="gameCanvas" width="800" height="500">
<p>Your browser doesn't support canvas.</p>
</canvas>
<script type='text/javascript'>
// demo code used for playing around with javascript-canvas animations
var frameCount = 0;
var drawingCanvas = document.getElementById('gameCanvas');
// Check the element is in the DOM and the browser supports canvas
if (drawingCanvas.getContext) {
var context = drawingCanvas.getContext('2d');
var x = 100;
var y = 100;
var right = true;
context.strokeStyle = "#000000";
context.fillStyle = "Green";
context.beginPath();
context.arc(x, y, 50, 0, Math.PI * 2, true);
context.closePath();
context.stroke();
context.fill();
}
function Timer(settings) {
this.settings = settings;
this.timer = null;
this.on = false; //Bool that represents if the timer is running or stoped
this.fps = settings.fps || 30; //Target frames per second value
this.interval = Math.floor(1000 / 30);
this.timeInit = null; //Initial time taken when start is called
return this;
}
Timer.prototype =
{
run: function () {
var $this = this;
this.settings.run();
this.timeInit += this.interval;
this.timer = setTimeout(
function () { $this.run() },
this.timeInit - (new Date).getTime()
);
},
start: function () {
if (this.timer == null) {
this.timeInit = (new Date).getTime();
this.run();
this.on = true;
}
},
stop: function () {
clearTimeout(this.timer);
this.timer = null;
this.on = false;
},
toggle: function () {
if (this.on) { this.stop(); }
else { this.start(); }
}
}
var timer = new Timer({
fps: 30,
run: function () {
//---------------------------------------------run game code here------------------------------------------------------
//Currently Chorme is playing a catch up game with the frames to be drawn when the user leaves the browser window and then returns
//A simple canvas animation is drawn here to try and figure out how to solve this issue. (Most likely related to the timer implimentation)
//Once figured out probably the only code in this loop should be something like
//updateGameLogic();
//updateGameCanvas();
frameCount++;
if (drawingCanvas.getContext) {
// Initaliase a 2-dimensional drawing context
//Canvas commands go here
context.clearRect((x - 52), 48, (x + 52), 104);
// Create the yellow face
context.strokeStyle = "#000000";
context.fillStyle = "Green";
context.beginPath();
if (right) {
x = x + 6;
if (x > 500)
right = false;
} else {
x = x - 6;
if (x < 100)
right = true;
}
context.arc(x, 100, 50, 0, Math.PI * 2, true);
context.closePath();
context.stroke();
context.fill();
}
document.getElementById("frameCount").innerHTML = frameCount;
//---------------------------------------------end of game loop--------------------------------------------------------
}
});
document.getElementById("button1").onclick = function () { timer.toggle(); };
frameCount++;
document.getElementById("frameCount").innerHTML = frameCount;
</script>
</body>
</html>
-------------Update ---------------------
I have used requestanimation frame and that has solved the frame rate problam but I still get weird ghosting/bluring when the animation is running. any idea how I should be drawing this thing?
Okay, so part of your problem is that when you switch tabs, Chrome throttles down its performance.
Basically, when you leave, Chrome slows all of the calculations on the page to 1 or 2 fps (battery-saver, and more performance for the current tab).
Using setTimeout in the way that you have is basically scheduling all of these calls, which sit and wait for the user to come back (or at most are only running at 1fps).
When the user comes back, you've got hundreds of these stacked calls, waiting to be handled, and because they've all been scheduled earlier, they've all passed their "wait" time, so they're all going to execute as fast as possible (fast-forward), until the stack is emptied to where you have to start waiting 32ms for the next call.
A solution to this is to stop the timer when someone leaves -- pause the game.
On some browsers which support canvas games in meaningful ways, there is also support for a PageVisibility API. You should look into it.
For other browsers, it'll be less simple, but you can tie to a blur event on the window for example.
Just be sure that when you restart, you also clear your interval for your updates.
Ultimately, I'd suggest moving over to `requestAnimationFrame, because it will intelligently handle frame rate, and also handle the throttling you see, due to the stacked calls, but your timer looks like a decent substitute for browsers which don't yet have it.
As for blurriness, that needs more insight.
Reasons off the top of my head, if you're talking about images, are either that your canvas' width/height are being set in CSS, somewhere, or your sprites aren't being used at a 1:1 scale from the image they're pulled from.
It can also come down to sub-pixel positioning of your images, or rotation.
Hope that helps a little.
...actually, after looking at your code again, try removing "width" and "height" from your canvas in HTML, and instead, change canvas.width = 800; canvas.height = 500; in JS, and see if that helps any.