3

P5.js has it's own loop, which calls the draw() function FPS amount times per second. However, there is a function noLoop() which can be placed in the setup()function, which disables P5.js own loop.

So I made my own loop (this is necessary for my project), which looks like follows:

customLoop = function(){
  while(somethingIsTrue){
    //custom code here
    draw();
  }
}

So I expect to see the canvas updated quite fast as there is no FPS set. However, the canvas is only redrawn again AFTER the customLoop is done.

Short in short: the canvas doesn't update while the custom loop is still running, it will only show the updates after the custom loop is done.

How can I update the canvas in my own loop continiously?


People have asked for more context, so i'll try my best to explain. I have made my own JS library that makes it easy to create genetic algorithms for neural networks.

The genetical algorithm in my library is set up as follows:

var evol = new Evolution({
  size: 50,
  mutationRate: 0.05,
  networkSize: [4,10,1],
  mutationMethod: [Mutate.MODIFY_RANDOM_BIAS, Mutate.MODIFY_RANDOM_WEIGHT],
  crossOverMethod: [Crossover.UNIFORM, Crossover.AVERAGE],
  selectionMethod: [Selection.FITNESS_PROPORTIONATE],
  elitism: 5,
  fitnessFunction: function(network){
    //something here that computes for every network, AT THE SAME TIME showing it LIVE for the user to watch
  }
});

Pay attention to the comment in the fitnessFunction. Then I run a genetic algorithm loop as follows:

 var notFinished = true;
  while(notFinished){
    evol.evaluate();
    if(evol.getAverage() > 2000){
      notFinished = false;
      console.log('done');
    }
    evol.select();
    evol.crossOver();
    evol.mutate();
    evol.replace();
  };

So as you see, my code continously runs the genetical algorithm. But the whole problem lies in the fitness function. This specific genetical algorithm is made to train neural networks how to play snake. So in the fitness function, i'm actually running the neural network until the snake dies. However, for every snake that is tested, I want the user to see how the neural networks are doing. So my fitness function looks something as follows:

fitnessFunction = function(network){
  while(snakeIsNotDead){
    computeNeuralNetworkOutput();
    giveSnakeNewMovementDirectionBasedOnOutput();

    // most importantly
    redrawTheSnakeWithNewPosition(); // which is redraw() with P5.js
}

So for every generation that is tested, I want to see all the 50 snakes performing. However, all I see after the network is done is the last position the snake had in the last snake evaluation.

4
  • I've been using p5 for quite some time now and I have tried to do something like this and as far as I know, there is no way to achieve exactly what you want, maybe you can provide more context about your project so people can help with alternatives? Commented Mar 1, 2017 at 16:05
  • It's looking like a dead end yes. I have updated the post providnig more context. Commented Mar 1, 2017 at 16:22
  • 1
    Have you considered storing the history of every snake's last n locations that gets overwritten for every other generation, then you can loop through those elements in draw and draw snakes at those positions. Commented Mar 1, 2017 at 16:30
  • That would require a workaround, but is possible ofcourse. I'll take a look. Commented Mar 1, 2017 at 17:00

2 Answers 2

3

Please try to post your code as an MCVE that we can actually run. Here's a small example that shows your problem:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    redraw();
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

This will show a canvas with a bunch of random circles already drawn on it. I gather that what you want is to see the circles accumulate, like you're "fast-forwarding" the sketch.

Using your own loop is a little fishy. Processing already has its own looping mechanism, and it contains logic for double-buffering the canvas. That's why you don't see anything until the drawing is completed. You could just rely on Processing's built-in timing mechanisms. Here's an example:

function setup(){
  createCanvas(500, 500);
  frameRate(120);
}

function draw(){
  if(frameCount == 600){
    frameRate(60);
  }
  ellipse(random(width), random(height), 20, 20);

}

Note that this might not work if the device you're running limits the framerate. But the point is that you shouldn't need to create your own loop. Just use Processing's built-in looping logic.

Edit: Like I said, the reason you aren't seeing any of your frames is because Processing is double-buffered. That means Processing will wait until it goes through all events and draws everything to the screen. This is an oversimplification, but you can think of the above loop as adding 600 redraw events to the event queue. Processing goes through them, but doesn't actually show the result until it's done.

You can work around this by calling each redraw after this event queue is emptied out. One slightly hackish way to do that is using the setTimeout() function:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    setTimeout(redraw, 0);
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

Now instead of calling the redraw() function 600 times in a row, the code adds 600 timeout events to the event queue. Each one of them will be processed individually, so you see the intermittent frames.

I will say that this still seems like a hacky way to accomplish your goal. If I were you I would still try to separate the draw() function from your actual logic. Have your simulation set variables, and have the draw() function use those variables. That's the "correct" way to solve this problem. Using the setTimeout() function will work for now, but my guess is that it'll cause problems for you down the road.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you, this helped me a little bit. But the whole problem is, is that I'm using a javascript library for genetical algorithms which has it's own fitness evaluation loop. I want to 'connect' this genetical algorithm loop with the p5js drawing loop, by calling the draw() function in the fitness function. I have updated my post!
@ThomasW I don't think you should call the draw() function from the fitness() function. Instead, you should split your logic up from your drawing. Create an update() function in Processing, and then call that from your fitness function. The update() function should simply update the variables used for drawing. The draw() function uses those variables to draw each frame. Then you can just leave the draw() function alone. Alternatively, could you reverse the logic and call the fitness() function from the draw() function?
I can't call the fitness() from the draw(), that would require to modify the library and the goal of this little project is to show what the library is capable of. Additionally, the draw function IS called and the var's ARE updated but the DOM just doesn't get rendered :(
1

Based on the documentation, you want to avoid calling "draw()" directly. Try something like:

customLoop = function(){
    while(somethingIsTrue){
        //custom code here
        redraw();  // Causes the code inside "draw()" to execute once
    }
}

https://p5js.org/reference/#/p5/draw

Specifically: draw() is called automatically and should never be called explicitly. It should always be controlled with noLoop(), redraw() and loop().

In addition, since JavaScript is single threaded, so long as the while loop is ticking away, nothing else can execute. Here's a related article:

Asynchronous function in a while-loop

And a nice, thorough explanation:

http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

You could try something like:

var timerId = 0;

timerId = setTimeout(myCustomRedraw(), 10); // Every 10 milliseconds.
myCustomRedraw = function () {
    // custom code here
    if (<stop loop condition>) {
        clearTimeout(timer); // Will stop the timeout loop.
    }
    redraw()
}

4 Comments

This helps me a little bit. But it still won't update after the loop is done: "This is because redraw() does not run draw() immediately (it only sets a flag that indicates an update is needed)." p5js.org/reference/#/p5/redraw
I think there is something wrong with the code below, check it maybe? But the weird thing is, during my loop, the draw() function is actually called --> It's outputting to the console, but it's not updating the canvas!
Do you mean the example code I provided? This is psuedo-code, and you would need to update it with your loop condition and custom code. It won't work just copy / pasted. Can you share your specific implementation, and I'll be happy to take a look?
I have udpated my question with more context!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.