Wait or delay into Loop

I want to create a pause, wait or delay inside the loops. Is this possible?

1

when you want to see the animation (ellipses appearing slowly) this delay won’t help you, because draw() updates the screen only at the end and not throughout.

Hence you need to get rid of the for-loops and use the fact that draw() repeats itself automatically 60 times per second.

In my example the delay is done by frameRate but you can search millis() or timer here on the forum for a timer. The command delay() is not useful here.

Please remember to post your code as text not as image.

Chrisir

Example


int x=20; 
int y=20;

void setup() {
  size(600, 600);
  frameRate(2);
}

void draw() {
  ellipse(x, y, 40, 40); 

  y += 40; 

  if (y>=height) {
    x+=40;
    y=20;
  }
}

In theory there are several ways to introduce a wait/delay into the draw function.

1. Using async/await

function setup() {
  createCanvas(400, 400);
  frameRate(1);
}

async function draw() {
  background(220);
  for (let i = 0; i < 10; i++) {
    circle(random(0, width), random(0, height), 20);
    await delay(500);
  }
}

function delay(ms) {
  return new Promise(done => setTimeout(done, ms));
}

Technically this does introduce a delay, and when draw is called one circle will be drawn every 1/2 second. However p5.js ignores the return value from draw and doesn’t know that it should wait for the Promise that is implicitly returned due to the use of async before calling draw() again. So if you run this you will see that more an more circles keep getting added because you end up with many instances of draw() running simultaneously. You could improve the behavior of this approach by calling noLoop() in your setup and then calling redraw() after your for loops exit:

function setup() {
  createCanvas(400, 400);
  noLoop();
}

async function draw() {
  background(220);
  for (let i = 0; i < 10; i++) {
    circle(random(0, width), random(0, height), 20);
    await delay(500);
  }
  redraw();
}

function delay(ms) {
  return new Promise(done => setTimeout(done, ms));
}

This is actually a pretty good result.

2. “busy wait” in draw()

You really don’t want to do this, but I just want to document that fact. This code blocks the draw() function for 500 milliseconds per circle drawn:

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  for (let i = 0; i < 10; i++) {
    circle(random(0, width), random(0, height), 20);
    delay(500);
  }
}

function delay(ms) {
	let t = new Date().getTime();
	while (new Date().getTime() - t < (ms || 0)) {
		// busy wait
	}
}

The problem here is that because of the single threaded nature of JavaScript, if you’ve got the main thread busily “delaying” in your draw() function in between “drawing” each circle, the browser actually never gets a chance to display the stuff that you are “drawing” to the canvas (when you call a function like circle() it doesn’t actually immediately display the circle, you can basically think of it as queuing the circle to be displayed once the draw() function exits, or drawing the circle to an off screen buffer that is not actually painted onto the screen until the draw() function exits.

The result of running this code will be 1) your browser tab becomes mostly unresponsive, 2) your CPU usage spikes, and 3) the set of 10 circles will all be displayed at once at every 5 seconds or so.

3. Using a generator function

Lastly, this is a bit more advanced, but you could put your drawing sequence into a type of function called a generator function that returns an iterator:

const delay = 500;

let nextStepT;
let iter;

function setup() {
  createCanvas(400, 400);
  
  nextStepT = millis();
  iter = drawSequence();
}

function draw() {
  if (nextStepT < millis()) {
    let n = iter.next();
    if (n.value) {
      // done, reset
      iter = drawSequence();
    }
    
    nextStepT = millis() + delay;
  }
}

function* drawSequence() {
  background(220);
  for (let i = 0; i < 10; i++) {
    yield false;
    circle(random(0, width), random(0, height), 20);
  }
  yield true;
}

For your use case this could be needlessly complex, but I have found uses for it.

4. Re-structure your code so that you “delay” elements getting drawn by controlling what gets drawn based on time

This would be preferable if 1) you don’t want to dive into advanced JavaScript concepts like Promises or iterators, or 2) you have a mix of things being rendered in real time and on a timeline. For example:

const delay = 500;
const spacing = 40;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  let done = false;
  let columns = floor(width / 40) - 1;
  let rows = floor(height / 40) - 1;
  for (let y = 0; !done && y < rows; y++) {
    for (let x = 0; x < columns; x++) {
      // limit how many circles are displayed based on time
      if ((y * columns + x) * delay > millis()) {
        done = true;
        break;
      }
      
      circle((x + 1) * spacing, (y + 1) * spacing, 20);
    }
  }
  
  // also draw something in real time
  circle(mouseX, mouseY, 20);
}
2 Likes