
// SwingTimerTest.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Test přesnosti časovače z javax.swing překreslováním instance
   třídy JPanel danou rychlostí snímkování (FPS) a 
   současné měření skutečně dosahované hodnoty FPS.

   Pojmem snímek rozumíme provedení metody paintComponent() ve
   třídě SwingTimerTest.

   Použití:
     java SwingTimerTest <požadovaná hodnota FPS>


   Naměřené průměrné hodnoty FPS při těchto požadovaných:
             20      50      80/83    100
            ------------------------------
   Win 98:   18      18      18       18
   Win 2000: 19      49      49       98
   Win XP:   16      32      64       64

   Při spuštění programu je hodnota FPS nízká, ale asi tak za 10 až 20
   sekund postupně vzrůstá.
 
   Voláme-li v metodě reportStats() metodu System.currentTimeMillis()
   namísto J3DTimer.getValue(), vypadají výsledky v podstatě stejně. To je
   dáno tím, že MAX_STATS_INTERVAL je větší než rozlišení časovače.  
*/


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.text.DecimalFormat;

import com.sun.j3d.utils.timer.J3DTimer;     // pro měření přesnosti


public class SwingTimerTest extends JPanel
                           implements ActionListener 
{
  private static int DEFAULT_FPS = 80;

  private static final int PWIDTH = 250;
  private static final int PHEIGHT = 75;

  private static long MAX_STATS_INTERVAL = 1000L;
    // zaznamenej statistiky (zhruba) každou sekundu

  private static int NUM_FPS = 10;
     // počet ukládaných hodnot FPS pro výpočet průměru


  // pro statistické údaje
  private long statsInterval = 0L;    // v ms
  private long prevStatsTime;   
  private long totalElapsedTime = 0L;

  private long frameCount = 0;
  private double fpsStore[];
  private long statsCount = 0;
  private double averageFPS = 0.0;


  private DecimalFormat df = new DecimalFormat("0.##");  // 2 des. m.
  private DecimalFormat time_df = new DecimalFormat("0.####");  // 4 des. m.

  private int period;       // interval v ms mezi vykresleními
              // tzn. čas požadovaný pro jednu iteraci


  public SwingTimerTest(int p)
  {
    period = p;

    setBackground(Color.white);
    setPreferredSize(new Dimension(PWIDTH, PHEIGHT));

    // inicializuj satistiky časovače
    fpsStore = new double[NUM_FPS];
    for (int i=0; i < NUM_FPS; i++)
      fpsStore[i] = 0.0;

    prevStatsTime = J3DTimer.getValue();

    // spusť časovač
    new Timer(period, this).start();
  } // konec metody: konstruktoru: SwingTimerTest()



   public void actionPerformed(ActionEvent e)
   // aktualizuj a překresli
   { sillyTask();
     repaint();  
   }


   public void paintComponent(Graphics g)
   {
     super.paintComponent(g);

     // vyčisti pozadí
     g.setColor(Color.white);
     g.fillRect (0, 0, PWIDTH, PHEIGHT);

    // zobraz průměrnou hodnotu FPS
    g.setColor(Color.black);
    g.drawString("FPS průměrně: " + df.format(averageFPS), 10, 25);

     reportStats();
   } // konec metody: metody: paintComponent()


   private void sillyTask()
   { long součet = 0;
     for (long i=0; i < 100000L; i++)  // was 200000
       součet += i;
     // System.out.println("součet: " + součet);
   } // konec metody: sillyTask()



  private void reportStats()
  /* Statistické údaje:
       - součet dob trvání všech dosud provedených iterací,
         skutečná doba trvání tohoto periodu,
         diff (odchylka) těchto dvou čísel

       - celkový počet volání metody paintComponent();
   
       - Počet snímků za sekundu v tomto mezidobí a průměrná
         hodnota FPS za posledních NUM_FPS periodů.

     Tyto informace ukládáme každých MAX_STATS_INTERVAL/1000 sekund.
  */
  { 
    frameCount++;
    statsInterval += period;
    
    // zaznamenej statistiky každých MAX_STATS_INTERVAL milisekund
    if (statsInterval >= MAX_STATS_INTERVAL) {    
      long timeNow = J3DTimer.getValue();

      // čas od posledního výpočtu statistických údajů
      long realElapsedTime = timeNow - prevStatsTime; 
      totalElapsedTime += realElapsedTime;

      long sInterval = (long)statsInterval*1000000L;  // ms --> ns
      double timingError = 
          ((double)(realElapsedTime - sInterval)) / sInterval * 100.0;

      double actualFPS = 0;     // výpočet nejnovější hodnoty FPS
      if (totalElapsedTime > 0)
        actualFPS = (((double)frameCount / totalElapsedTime) * 1000000000L);

      // ulož nejnovější FPS
      fpsStore[ (int)statsCount%NUM_FPS ] = actualFPS;
      statsCount = statsCount+1;

      double totalFPS = 0.0;     // suma předchozích FPS
      for (int i=0; i < NUM_FPS; i++)
        totalFPS += fpsStore[i];

      if (statsCount < NUM_FPS)  // výpočet průměrného FPS
        averageFPS = totalFPS/statsCount;
      else
        averageFPS = totalFPS/NUM_FPS;

      System.out.println(time_df.format( (double) statsInterval/1000) + " " + 
                        time_df.format((double) realElapsedTime/1000000000L) + "s " + 
						df.format(timingError) + "% " + 
                        frameCount + "v " +
                        df.format(actualFPS) + " " +
                        df.format(averageFPS) + " prům. fps" );

      prevStatsTime = timeNow;
      statsInterval = 0L;   // vynuluj
    }
  }  // konec metody: reportStats()


   // --------------------------------------------

   public static void main(String args[])
   {
     int fps = DEFAULT_FPS;
     if (args.length != 0)
       fps = Integer.parseInt(args[0]);

     int period = (int) 1000.0/fps;     // v ms
     System.out.println("fps: " + fps + "; period: " + period + " ms");

     SwingTimerTest timerPanel = new SwingTimerTest(period);

     // vytvoř JFrame, který bude uchovávat JPanel z našeho testu časovače
     JFrame app = new JFrame("Test časovače Swing");
     app.getContentPane().add(timerPanel, BorderLayout.CENTER);
     app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

     app.pack();
     app.setResizable(false);  
     app.setVisible(true);
  } // konec metody: main()

} // konec třídy: SwingTimerTest
