Timing for draw loop

edited November 2016 in Questions about Code

Hello,

I have to sychronize an animation to 1 frame per second.

Until now I used the millis() function for the timing. But annoyingly the time is running away. The Code is like:

draw(){
  while ((millis () >= time + timeStep)){
    println(time);
    ...
  }
  time = millis();

With timeStep = 500 I get time codes like:

8165,
8768,
9305,
9818

What I want is:

8000,
8500,
9000,
9500

How do I reallize this?

Answers

  • You could use the frameCount variable to go by frames instead.

  • do you mean >= there?

    your draw loop gets called, by default, 60 times a second. you're checking things within this draw loop. your code only runs when the draw loop as been called, so your timing is being obscured by that.

  • but you won't get exactly 500 millisecond steps anyway - the code takes time to run

  • edited October 2016

    @leclerke -- You are trying to take the 'slack' out of your clock to have millisecond-consistent starting times.

    First, frameRate() won't work as a precision timer, it drifts:

    int startTime;
    void setup(){
      frameRate(2);
      startTime = millis();
    }
    void draw(){
      println(millis()-startTime);
    }
    

    This outputs:

    499
    1004
    1503
    1998
    2500
    3003
    3502
    4001
    

    Instead, align your time frame within the draw loop, like this:

    /**
     * Draw Timing Alignment
     * 2016-10-12
     * Processing 3.2.1
     **/
    
    int nextTime;
    int step;
    
    void setup() {
      // ...
      // ... insert time-consuming setup code
      // ...
      step = 500;
      // wait for first frame -- aligned to step, it will occur between step and 2step-1
      nextTime = millis()-millis()%step + 2*step;
    }
    
    void draw() {
      while (millis()<nextTime) {
        // do nothing while running out the 'slack' in the clock
      }
      println(millis()); // we are starting at the correct wall clock position
      // ...
      // ... insert draw code that takes well under 'step' to execute
      // ...
      nextTime = nextTime + step;
    }
    

    ... which will print:

    1500
    2000
    2500
    3000
    3500
    4000
    

    ... etc.

    P.S. as @koogs pointed out, your sample code above gives an infinite while loop that hangs the sketch on the first draw. You probably meant <.

  • Yeah, koogs' of course right, I didn't want to put the whole code in here, its too huge ;-) I'm gonna try your way jeremy. Thank you so far!

  • I would not recommend using an empty while loop to take up the slack. The draw method is executed in the main event thread thread and there is a risk the while loop will hog the thread making it unresponsive to mouse and keyboard events.

    The code below averages out at 1 step every 500 milliseconds.

    int currTime, nextTime;
    final int INTERVAL = 500;
    
    int c = 0;
    
    void setup() {
      size(400, 200);
      textSize(100);
      fill(255);
      // Make this the last line in setup
      nextTime = millis() + INTERVAL;
    }
    
    void draw() {
      background(64);
      if(millis() >= nextTime){
        println(millis()); // Print actual time for debug
        // Now put the time dependant code here
        c++;
        // Update the time for the next time
        nextTime += INTERVAL;
      }
      text(c, 70, 140);
    } 
    

    Here is the output I got

    2066
    2567
    3067
    3567
    4066
    4566
    5066
    5568
    6067
    6568
    7067
    7568
    8068
    8567
    9067
    9568
    10066
    10568
    11067
    11567
    
  • edited October 2016

    ... and there is a risk the while loop will hog the thread making it unresponsive to mouse and keyboard events.

    There's a separate Thread just to enqueue all Processing's input events.
    However, the dequeueing happens in the main "Animation" Thread though. 8-|
    In other words, enqueue is asynchronous, while dequeueing is synchronous. :-B

  • edited October 2016

    How very true. :)

    So anything that restricts dequeuing the events will make the sketch less responsive to mouse and keyboard.

  • edited October 2016

    Yup! But eventually all of those accumulated input events are gonna be dequeued at once when draw() finally returns. :P

  • I suppose it depends on the goal, @quark.

    I thought that @leclerke 's question was about millisecond-synchronized animation -- in the example requested, at precise half-second intervals. If we aren't losing input events, then isn't being "more responsive" in this case just another way of saying "desynchronized", which isn't the goal?

  • Yup! But eventually all of those accumulated input events are gonna be dequeued at once when draw() finally returns.

    That's true but then all those dequeued events will be processed before draw() is executed again :)

    which isn't the goal

    If leclerke REALLY needs exact millisecond precision (although I can't image why) then by all means use the while loop. But if the aim is to have accurate average timing and individual timings can be within a few milliseconds then it can be done without the while loop.

    Using tight loops inside the main event processing thread to create delays is not good programming practice.

  • edited October 2016

    Using tight loops inside the main event processing thread to create delays is not good programming practice.

    @quark -- I agree that this seems a bit hack-ish in order to achieve the stated goals (1000, 1500, 2000 etc.) -- especially if you don't need that last lousy millisecond. However I'm not sure that the language design of Processing agrees with you that this kind of main thread blocking is bad practice. After all, isn't Processing's delay() precisely a millisecond time checking loop that makes the main thread unresponsive? The difference is that writing a delay-aligning timer loop by hand such as while (millis()<nextTime){} (as above) generates slightly more precise results than specifying the exact same thing in Processing's native idiom:

    int STEP = 500;
    void draw() {
      delay(STEP-millis()%STEP);
      println(millis());
    }
    

    ...which produces:

    1003
    1500
    2001
    2500
    3002
    3505
    4001
    
  • edited October 2016

    Processing already got frameRate() in order to control its speed:
    https://Processing.org/reference/frameRate_.html

    delay() should only be used for other threads, not the main animation thread. [-X

  • edited October 2016

    @GoToLoop -- I understand that point of view -- and, as I've said, neither frameRate() nor delay() actually produces the precision of a simple while(){} -- which is functionally equivalent to delay(), just faster. So:

    • frameRate() - imprecise, non-blocking
    • if(millis()...) - imprecise, non-blocking
    • delay() - imprecise, blocking
    • while(millis()...) - precise, blocking

    If you need to be precise, what should you use? while.

    However, re: "delay() should only be used for other threads" -- I'm pointing out that using delay in the draw loop is actually how Processing documents and gives examples of the function! For one, the canonical delay() reference demo is actually "a sketch that needs to wait a few milliseconds" -- just like the OP example here -- and it uses delay in the draw loop (i.e. the main thread).

  • ... while () -- which is functionally equivalent to delay(), just faster.

    An infinite loop w/o any delay() uses 100% of the CPU time!!! :-SS

    ... is actually how Processing documents and gives examples of the function! For one, the "canonical" ...

    As I've already denounced many times in this forum, Processing API's reference is incomplete, imprecise and even superstitious! [-(

    The delay() entry was added and removed countless times.
    They tried to hide it in the hopes it'd be forgotten.
    But folks coming from Arduino kept using it.
    There are many useful functions and variables which are very useful but are censored too!

    I only send folks to the reference b/c I'm lazy.
    Even though they may learn bad programming and receive some erroneous info there! :-\"

  • Well, 100% of CPU time for a fraction of a second every single second isn't great. Of course, if your goal is to use the computer to achieve a particular timing/output/effect, it also isn't that bad.

    Let's make this constructive:

    Suppose someone wants to execute two Processing statements precisely 500 millis() apart. They can do this. They do this using while.

    Question: are there any other ways for them to achieve this result? If so, what are those ways?

    For the purposes of this thought experiment, saying "you shouldn't want to do this in the first place!" = "no".

  • IMO, if some1 wants to execute 2 functions w/ a specific & precise time apart, they should do so in another Thread instead. (:|

  • Okay, a thread. So, when leclerke asked "How do I reallize this?"... how does he realize this? Does the thread contain a tight while loop? Or does it use delay, or some other Java timing facility?

  • edited October 2016
    // forum.processing.org/two/discussion/18517/
    // timing-for-draw-loop#Item_18
    
    // GoToLoop (2016-Oct-15)
    
    static final int STEP = 500;
    
    void setup() {
      size(300, 200);
      frameRate(1);
      thread("myTimedAction");
    }
    
    void draw() {
      surface.setTitle(str(frameCount));
      background((color) random(#000000));
    }
    
    void myTimedAction() {
      int nextTime = millis() + STEP;
    
      for (;; delay(STEP)) {
        int m = millis();
        println(m, m - nextTime);
        nextTime += STEP;
      }
    }
    
  • Interesting approach! I don't think it works, though.

    OP requested:

    8000
    8500
    9000
    9500
    

    And this is producing e.g.:

    7768 -467
    8269 -466
    8770 -465
    9271 -464
    9774 -461
    

    ...so I'm seeing thread actions drift about as much as if the sketch simply used frameRate(2).

  • You can replace the delay() there for a while () block if you really need so.
    Regardless, placing such tight loop in a separate Thread eases the main 1. #:-S

  • Another way is to takeover Processing's timing loop with your own timer like this.

    import javax.swing.*;
    import java.awt.event.*;
    
    final int INTERVAL = 1000;
    Timer timer;
    
    int c = 0;
    
    void setup() {
      size(400, 200);
      noLoop();
      textSize(100);
      fill(255);
      // Create the timer
      timer = new Timer(INTERVAL, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          c++;
          redraw(); // ask Processing
        }
      }
      );  
      timer.start();
    }
    
    void draw() {
      println(millis());
      background(64);
      text(c, 70, 140);
    } 
    

    The output I got was

    1490
    2494
    3495
    4494
    5494
    6510
    7511
    8511
    9511
    10511
    11511
    12511
    13510
    14511
    15511
    

    Just one blip at 5494 > 6510 = 1016ms

    However I'm not sure that the language design of Processing agrees with you that this kind of main thread blocking is bad practice.

    Not sure whether the 'language design of Processing' can have an an opinion ;) but thread blocking using tight loops IS bad programming practice. I have presented 2 methods that provide accurate (but not to the milisecond) timings.

    If the OP wants exact millisecond precision, and I can't think why, then use the while loop if it has no adverse impact on the running sketch.

    AFAIK the delay method was removed because it suspended the main thread preventing queued events to be processed but was dragged back because so may people used it. Just to clarify, the delay() method does NOT use a loop to create the delay rather it sends the main thread to sleep allowing other processes to use the CPU.

  • edited October 2016

    Wow @quark, nice sketch's takeover! =D>
    Although not as tricksy, neither synchronized w/ the "Animation Thread", I've got 1 example w/ TimerTask from this old forum thread: :ar!

    https://forum.Processing.org/two/discussion/1725/millis-and-timer#Item_7

    /** 
     * TimerTask (v1.3)
     * by GoToLoop (2013/Dec)
     * 
     * forum.Processing.org/two/discussion/1725/millis-and-timer#Item_7
     */
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    final Timer t = new Timer();
    boolean hasFinished = true;
    
    void draw() {
      if (hasFinished) {
        final float waitTime = random(10);
        createScheduleTimer(waitTime);
    
        println("\n\nTimer scheduled for "
          + nf(waitTime, 0, 2) + " secs.\n");
      }
    
      if ((frameCount & 0xF) == 0)   print('.');
    }
    
    void createScheduleTimer(final float sec) {
      hasFinished = false;
    
      t.schedule(new TimerTask() {
        public void run() {
          print(" " + nf(sec, 0, 2));
          hasFinished = true;
        }
      }
      , (long) (sec*1e3));
    }
    

    P.S.: Can you spot something very rare in Java there? Parameter sec is a closure inside the anonymous instance of class TimerTask! :P

    When run() is called back after the tasked waiting time, it remembers the value passed to parameter sec, even though it shoulda ceased to exist when createScheduleTimer() returned earlier! @-)

  • That is a very cool timer, @GoToLoop !

    For thread readers interested in millisecond timings or just in average drift, just be aware that -- like many solutions -- it drifts over time. You can't use it as an animation clock or a metronome if that matters.

    Timer scheduled for 2.00 secs.
    804
    ....... 2.00
    Timer scheduled for 2.00 secs.
    2784
    ........ 2.00
    Timer scheduled for 2.00 secs.
    4802
    ....... 2.00
    Timer scheduled for 2.00 secs.
    6821
    ........ 2.00
    Timer scheduled for 2.00 secs.
    8823
    ....... 2.00
    
  • edited November 2016

    This is an updated demo of the dreaded "tight loop timer." :ar!

    It uses a conditional return of the draw loop to avoid taxing the CPU -- and the tight loop only runs once per timing step, whenever the clock is within less than a single frame worth of time to the next step time.

    /**
     * Draw Timing Alignment Slacker
     * 2016-11-15
     * Processing 3.2.3
     **/
    
    int nextTime;
    int step;
    float frameError;
    float alignWindow;
    
    void setup() {
      // ...
      // ... insert time-consuming setup code
      // ...
      frameRate(60);
      step = 250; 
      frameError = 1.75;
      alignWindow = 1000/frameRate * frameError;
      // wait for first frame -- aligned it to step, it will occur between step and 2step-1
      nextTime = millis()-millis()%step + 2*step;
    }
    
    void draw() {
      if(millis() < nextTime){
        // skip drawing
        if(millis()+alignWindow < nextTime){
          print("*"); // a skipped frame
          return;
        // do nothing while running out the last partial frame with of clock time
        } else {
          while (millis()<nextTime) {}
          print("_"); // a partial frame aligned with a tight while loop
        }
      }
      println(millis()); // we are starting at the correct wall clock position
      // ...
      // ... insert draw code that takes well under 'step' to execute
      // ...
      nextTime = nextTime + step;
    }
    

    The net effect is to align the clock by pinning the CPU (and blocking the draw loop, becoming non-interactive) for a fraction of one frame per second. That works great at 60fps or 30fps; it can hit the CPU hard at really low framerates (like 2).

    When set to a quarter-second metronome running at ~60fps, here is the example output:

    *************_1250
    *****_1500
    *****_1750
    *****_2000
    *****_2250
    *****_2500
    *****_2750
    *****_3000
    *****_3250
    *****_3500
    *****_3750
    *****_4000
    *****_4250
    *****_4500
    

    Ah, sweet, clean, millisecond-perfect timings.

    Each * indicates a frame that draw() skipped drawing -- each _ indicates a partial frame where it used a tight loop to perfectly align the clock before executing the next draw.

    It might be interesting to try wrapping this approach in a thread. Not sure what that would do performance-wise, though.

  • edited November 2016

    You can't use it as an animation clock or a metronome if that matters.

    Indeed method schedule() isn't adequate for metronome stuff: :-<
    http://docs.Oracle.com/javase/8/docs/api/java/util/Timer.html#schedule-java.util.TimerTask-long-long-

    However, scheduleAtFixedRate() is a hopeful approach: :>
    http://docs.Oracle.com/javase/8/docs/api/java/util/Timer.html#scheduleAtFixedRate-java.util.TimerTask-long-long-

    /** 
     * FixedRate TimerTask (v1.0.2)
     * by GoToLoop (2016-Nov-15)
     * 
     * https://forum.Processing.org/two/discussion/18517/
     * timing-for-draw-loop#Item_26
     */
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    new Timer().scheduleAtFixedRate(new TimerTask() {
      @Override void run() {
        print(millis(), TAB);
      }
    }
    , 0, 2000);
    
  • The whole timing problem cannot be solved that easily - there are just too many factors involved.
    With @jeremydouglass's code, it will only work if your processor and GPU can actually do the specified frameRate. Once the processor starts getting overloaded, no solution could possibly work.
    This has been explained in many of the existing pages related to timing. It just depends on how much inaccuracy is ok.

  • Hello

    This is exactly what I was looking for, about my fast reading system....

    Thanks

Sign In or Register to comment.