Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Placing P5.js draw function in a custom loop function

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.

like image 771
Thomas Wagenaar Avatar asked Aug 11 '26 09:08

Thomas Wagenaar


2 Answers

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.

like image 76
Kevin Workman Avatar answered Aug 12 '26 23:08

Kevin Workman


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()
}
like image 44
Courtney G Avatar answered Aug 12 '26 22:08

Courtney G



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!