P5.js: millis() behavior

Created on 22 Jan 2020  路  11Comments  路  Source: processing/p5.js

Hi there!

I'm using millis() to manage time on a few sketches. What's happening is that I'm rendering a few sketches and all of them share de same millis() return, even though each one starts at different moments.

I don't know if it is the expected behavior. Accordingly to the reference description of millis(), "the number of milliseconds since starting the program", the program is the p5.js, not the sketch, which makes sense. But I think would be nice to have a millis() value by sketch, ou at least another function with this behavior.

A sample code to show this:

const s = ( sketch ) => {

  let x = 100;
  let y = 100;

  sketch.setup = () => {
    sketch.createCanvas(200, 200);
    sketch._mycolor = sketch.color(sketch.random(255));
  };

  sketch.draw = () => {
    sketch.background(0);
    sketch.fill(sketch._mycolor);
    sketch.rect(x,y,50,50);
    sketch.fill(255)
    sketch.text(sketch.millis(), 0,10);
  };

  sketch.mousePressed = () => {
    sketch.remove();
    new p5(s);
  }
};

function createSketch () {
  new p5(s);
  setTimeout(createSketch, 2000);
}

createSketch();

All 11 comments

Welcome! 馃憢 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

The reference saying "program" is actually a holdout from Processing and isn't that well applied in this case. The millis() function is a direct alias to window.performance.now() which will calculate (in most cases) from when the browser context has started.

I do think that millis() should be calculated on a per instance basis as expected in this issue. The reference should probably replace "program" with sketch as well if the change is made or something along the lines of "window loaded" if no changes were made to the code.

@lmccart If a change should occur with the bahaviour of millis(), it would be best to squeeze this in before 1.0.0 release cutoff since technically this may be considered a breaking change.

@limzykenneth yes, I think we can add this one in. it doesn't seem like it would be that hard technically to implement.

@macarena This is a small workaround you can apply to get millies() for each sketch individually.
Just initialize a variable init_millis in sketch.setup() and use
curr_sketch_millies = sketch.millis() - init_millis
where curr_sketch_millies will be the millis of your current sketch.

const s = ( sketch ) => {

  let x = 100;
  let y = 100;
  let init_millis;
  let curr_sketch_millis;

  sketch.setup = () => {
    sketch.createCanvas(200, 200);
    sketch._mycolor = sketch.color(sketch.random(255));
    init_millis = sketch.millis();
  };

  sketch.draw = () => {
    sketch.background(0);
    sketch.fill(sketch._mycolor);
    sketch.rect(x,y,50,50);
    sketch.fill(255)
    curr_sketch_millis = sketch.millis() - init_millis; 
    sketch.text(curr_sketch_millis, 0,10);
  };

  sketch.mousePressed = () => {
    sketch.remove();
    new p5(s);
  }
};

function createSketch () {
  new p5(s);
  setTimeout(createSketch, 2000);
}

createSketch();

However, I do think that millis() should be instance-based and not program-based.

I agree that millis should represent the time of each instance and not window time, but this lead me to think if there are other cases where P5 is using program properties that should be instance based.

It might be good to think if there are any other functions whose behaviour should be clarified when running in instance mode vs global mode.

In the case of random when you use randomSeed it sets a different Linear Congruential Generator for each instance, but if you don't set a randomSeed it defaults to using Math.random(). Math.random() cannot be seeded in JS and is not instance based. Therefore we have both behaviours with random, an instance based RNG when initialized with a seed, and a global RNG in the form of Math.random when not initialized with a seed. Here is a simple sketch to play with. If you toggle the s.randomSeed(0); you'll see the two different behaviours.

const s1 = (s) => {
  s.setup = () => {
    // s.randomSeed(0);
    console.log(s.random(0, 100)); 
  }
}
let p1 = new p5(s1);
let p2 = new p5(s1);

@sixhat For the the two different behaviours of random() you described are expected and by design, so I don't see a problem there, unless I'm missing something.

The purpose of using a random seed is to ensure you can generate a fixed sequence of random numbers, if the LCG isn't tied to the instance, one instance calling the seeded random() will cause the global LCG to increment, meaning if you have two instance of p5 sketch running with the same random seed, they actually gives different sequence of random numbers.

While not using a random seed means that you want a random sequence of random number, there is no need for any certainty, instance specific or otherwise, so there is no need to tie unseeded random() to the instance. Additionally Math.random() is significantly faster than the implemented LCG, the LCG is there just to enable seeded RNG.

@limzykenneth Yes I was just using random as an illustration, not as a broken function needing to be fixed. Sorry if it caused confusion. My point is that we might need to think if there are other functions that need to be corrected or not. random is not one of them.

I'm going to work on this. One question, when should it start counting?

  1. When the instance is created (new p5() is called, internally or externally), before preload()
  2. After preload() before setup()
  3. After setup() before first call to draw()

I think that from a user perspective (not the programmer perspective) the sketch starts when the first frame is rendered either when someone draws in setup() or otherwise in draw(), so I think the timer should start at the moment the canvas is first created as it is the first manifestation of something existing in the html page the user sees.

Another option would be to allow the user to explicitly determine the timer start moment with a command like startTimer() that could revert to a default start time if he does nothing.

I agree with @sixhat. Rather than a startTimer() I wonder about something like an optional argument millis(0) that could reset it. Could be useful for timing based looping sketches. But maybe that's a whole other can of worms..

The existence of the canvas is not guaranteed (when noCanvas() is called) so I probably won't want to tie it to canvas creation. Multiple canvases can also be created in one instance as well. The first frame is technically the first call to draw() but since it is possible to do drawing in setup() I guess it may make sense to start calculating by then. I'm going to go with after preload() and before setup() in this case but if anyone feel other wise, let me know here.

I like the idea of millis(0) more than a new startTimer() function just to keep things minimal, though passing 0 to the function feels not quite right to me as it almost signals that passing a number will set the value of millis(). Unless this feature is important and useful to have now, we can also add it after the 1.0.0 release as part of a minor release, if there's demand for it, since it won't be a breaking change but just an added feature.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

dhowe picture dhowe  路  3Comments

NamanSharma5 picture NamanSharma5  路  3Comments

kartikay-bagla picture kartikay-bagla  路  3Comments

oranyelik picture oranyelik  路  3Comments