Timing: Decoupling rendering and logic updates

I have played around with some code from http://legion.gibbering.net/golem/tutorial_loop.html and got a working demonstration for the concept in Java Web Start:
http://www.javaengines.dk/test/TestTimer.jnlp.php shows 2 balls, both being updated at 4 fps but rendering as fast as possible.
1 ball is interpolated between last and next positions, the other isn’t.
When you click on the screen the balls are set back a few pixels - notice that the first ball moves back instead of jumping there too, because of the interpolation.
I’ll probably port to GAGETimer if System.currentTimeMillis won’t cut the cake.
Please send comments or questions, I’m pretty new to this kind of timing myself and made this just to test the concept before using it in my own game.
(I figure I have to decouple now, because I want logic framerate as low as 8-10 fps to “eat” some of the network latency while still maintaining a smooth “feel” of the game)

TestTimer.java:



import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class TestTimer extends JPanel implements Runnable
{
      private Point aLast = new Point(0, 20);
      private Point aNext = new Point(aLast);

      private Point b = new Point(0, 50);

      private float percentWithinTick = 0f;

      public TestTimer()
      {
            addMouseListener(new MouseAdapter()
            {
                  public void mouseClicked(MouseEvent e)
                  {
                        aNext.x -= 30;
                        b.x -= 30;
                  }
            });

      }

      public synchronized void paintComponent(Graphics g)
      {
            System.out.println("paint pwt=" + percentWithinTick);
            g.clearRect(0, 0, getWidth(), getHeight());
            int ax = aLast.x + (int) (percentWithinTick * (aNext.x - aLast.x));
            int ay = aLast.y + (int) (percentWithinTick * (aNext.y - aLast.y));
            g.fillOval(ax, ay, 10, 10);

            g.fillOval(b.x, b.y, 10, 10);
            notifyAll();//done painting
      }

      public void logicUpdate()
      {
            System.out.println("Logic");
            aLast.x = aNext.x;
            aLast.y = aNext.y;
            aNext.x += 5;
            b.x += 5;
      }


      public synchronized void run()
      {
            long time0, time1;
            int numLoops;
            int TICK_TIME = 250;
            int MAX_LOOPS = 2;
            time0 = System.currentTimeMillis();
            while (true)
            {
                  time1 = System.currentTimeMillis();
                  numLoops = 0;
                  while ((time1 - time0) > TICK_TIME && numLoops < MAX_LOOPS)
                  {
                        logicUpdate();
                        time0 += TICK_TIME;
                        numLoops++;
                  }
                  percentWithinTick = (time1 - time0) / (float) TICK_TIME;
                  if (percentWithinTick > 1)
                        percentWithinTick = 1;
                  repaint();
                  try
                  {
                        //System.out.println(System.currentTimeMillis());
                        //Deal with the asynchronous call to repaint()
                        //duration to wait for shouldn't matter, but if
                        //paintComponent for some reason isn't called
                        //TICK_TIME is probably the best answer
                        wait(TICK_TIME);
                        //System.out.println(" " + System.currentTimeMillis());
                  } catch (InterruptedException e)
                  {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                  }
            }
      }



      public static void main(String[] args)
      {
            final TestTimer timer = new TestTimer();
            JFrame f = new JFrame();

            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.getContentPane().add(timer, BorderLayout.CENTER);
            f.setSize(800, 600);

            JPanel p = new JPanel();
            JButton b = new JButton("Start");
            p.add(b);
            f.getContentPane().add(p, BorderLayout.SOUTH);
            b.addActionListener(new ActionListener()
            {
                  public void actionPerformed(ActionEvent e)
                  {
                        Thread t = new Thread(timer);
                        t.start();

                  }
            });
            f.setVisible(true);


      }
}


I’ve got a nice clocking package built into Typhon (http://typhon.egrsoftware.com/) that will be released when Auriga3D hits the net in December.

The clocking mechanism I created allows you to decouple scheduling on a single thread. In effect it is like psuedo-multitasking. If the clock is running at 60hz subdivisions of 30hz, 20hz, 15hz, 12hz, 10hz, and 5hz are available, etc. The clock is callback oriented so any “clockable” can be switched to a different subdivision dynamically.

Here are some notes and commentary from past presentations on it…

Why have a scheduler at all?

Clock classification:
“Subdivided do while thread yield”

-Cycle of 12 due to common factors
and equal prime number spread.

-Other subdivision patterns are possible.

A scheduler is necessary for three main reasons. It is required for active rendering for graphics display, to run reoccurring events in Typhon, & to execute code off of the AWT Event Queue that is used by the AWT and Swing graphics APIs.

A scheduler or some way to execute code from GUI events off of the AWT queue is absolutely necessary for high performance Java applications. Most APIs that perform this task work with threads including Foxtrot and SwingWorker.
However, most real time applications (games/simulations) are designed for a single thread of execution.

With Typhon I take a different approach by having one main thread of execution that is driven by a subdivided do while thread yield scheduler.

Now, the “do while thread yield” part sounds a little suspicious as isn’t that a spin wait? Well… Yes and no… If no other processes need to CPU then Typhon will consume all of the CPU resources, but if any other threads or applications require some CPU time Typhon applications will immediately yield.

This way it is possible to find a balance between scheduling events in Typhon and giving enough CPU time to other applications without being greedy. In fact this system works great while running a respectably high load in other software while Typhon applications are running.

The following slide will present some pseudo-code for the subdivided scheduler I am currently using.

Next slide:

Method clockLoop:


do {    
   ticks += tickSleep;    
   while (System.nanoTime() < ticks) Thread.yield();
   switch(++subdivideTicks)    
   {
       case SUB1:
          <handle add and remove>
          <handle pending clockable move events>
          <handle Subset 1 clockables>
       case SUB2:
          <handle Subset 2 clockables>
       case SUB3:
          <handle Subset 3 clockables>
       case SUB4:
          <handle Subset 4 clockables>
       case SUB5:
          <handle Subset 5 clockables>
          <handle abstract clockables 1>
       case SUB6:
          <handle Subset 6 clockables>
       case SUB7:
          <handle Subset 7 clockables>
          <handle pending clockable move events>
          <handle one time clockables>
       case SUB8:
          <handle Subset 8 clockables>
       case SUB9:
          <handle Subset 9 clockables>
       case SUB10:
          <handle Subset 10 clockables>
       case SUB11:
          <handle Subset 11 clockables>
       case SUB12:
          <handle Subset 12 clockables>
          subdivideTicks = -1
} while (clockLoopRunning) 

I was never a fan of that type of decoupling where you move your objects based on the last frame. I prefer to just specify a number of updates per second and in my main game loop I just do logic processing when I reach that update time. I basically just keep a counter of how much time has passed and when it adds up to my specified update time I update the logic, reset the counter, and make sure to keep track of any overflowed time. Of course that all requires a high performance, reliable timer, but it has the added benifit of making things like demos/networking much easier. There is a gamasutra article on this type of timing I believe. It’s an old one. For older java I just using thelorax’s timer to achieve a similar effect (i.e. you set the delay you want, sync your main loop to the timer, and update when it notifies).