From 6cff845a6c19a57923a36a973c6acde9f0f1ace5 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 30 Apr 2003 06:42:50 +0000 Subject: [PATCH] Use our own thread to queue up frame ticks rather than the standard Java Timer which does undesirable things like suffer from 20 ms granularity on Linux and rapid fire multiple invocations of our timer task if it has fallen behind. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2507 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../com/threerings/media/FrameManager.java | 88 ++++++++++++++++--- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/java/com/threerings/media/FrameManager.java b/src/java/com/threerings/media/FrameManager.java index 4e3003ec1..acef7e0fb 100644 --- a/src/java/com/threerings/media/FrameManager.java +++ b/src/java/com/threerings/media/FrameManager.java @@ -1,5 +1,5 @@ // -// $Id: FrameManager.java,v 1.38 2003/04/29 18:14:37 mdb Exp $ +// $Id: FrameManager.java,v 1.39 2003/04/30 06:42:50 mdb Exp $ package com.threerings.media; @@ -25,10 +25,6 @@ import java.awt.event.WindowListener; import java.awt.EventQueue; -import java.util.Date; -import java.util.Timer; -import java.util.TimerTask; - import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLayeredPane; @@ -44,6 +40,7 @@ import com.samskivert.util.StringUtil; import com.threerings.media.timer.MediaTimer; import com.threerings.media.timer.SystemMediaTimer; +import com.threerings.media.util.TrailingAverage; /** * Provides a central point from which the computation for each "frame" or @@ -296,8 +293,8 @@ public abstract class FrameManager public void start () { if (_ticker == null) { - _ticker = new Timer(true); - _ticker.scheduleAtFixedRate(_callTick, new Date(), _millisPerFrame); + _ticker = new Ticker(); + _ticker.start(); _lastTickStamp = 0; } } @@ -338,20 +335,44 @@ public abstract class FrameManager return Math.round(_fps[0]); } + /** + * Returns debug performance metrics. + */ + public TrailingAverage[] getPerfMetrics () + { + if (_metrics == null) { + _metrics = new TrailingAverage[] { + new TrailingAverage(150), + new TrailingAverage(150), + new TrailingAverage(150) }; + } + return _metrics; + } + /** * Called to perform the frame processing and rendering. */ protected void tick (long tickStamp) { + long start = 0L, paint = 0L; + if (MediaPanel._perfDebug.getValue()) { + start = paint = System.currentTimeMillis(); + } // if our frame is not showing (or is impossibly sized), don't try // rendering anything if (_frame.isShowing() && _frame.getWidth() > 0 && _frame.getHeight() > 0) { // tick our participants tickParticipants(tickStamp); + paint = System.currentTimeMillis(); // repaint our participants and components paint(tickStamp); } + if (MediaPanel._perfDebug.getValue()) { + long end = System.currentTimeMillis(); + getPerfMetrics()[1].record((int)(paint-start)); + getPerfMetrics()[2].record((int)(end-paint)); + } } /** @@ -650,13 +671,38 @@ public abstract class FrameManager } /** Used to effect periodic calls to {@link #tick}. */ - protected TimerTask _callTick = new TimerTask () { + protected class Ticker extends Thread + { public void run () { - if (testAndSet()) { - EventQueue.invokeLater(_awtTicker); + while (_running) { + long start = 0L; + if (MediaPanel._perfDebug.getValue()) { + start = _timer.getElapsedMillis(); + } + try { + Thread.sleep(_sleepGranularity.getValue()); + } catch (InterruptedException ie) { + Log.warning("Ticker thread interrupted."); + } + + long woke = _timer.getElapsedMillis(); + if (start > 0L) { + getPerfMetrics()[0].record((int)(woke-start)); + } + if (woke - _lastAttempt >= _millisPerFrame) { + _lastAttempt = woke; + if (testAndSet()) { + EventQueue.invokeLater(_awtTicker); + } + // else: drop the frame + } } - // else: drop the frame + } + + public void cancel () + { + _running = false; } protected final synchronized boolean testAndSet () @@ -696,9 +742,15 @@ public abstract class FrameManager } }; + /** Used to stick a fork in our ticker when desired. */ + protected transient boolean _running = true; + /** Used to detect when we need to drop frames. */ protected boolean _ticking; + /** The time at which we last attempted to tick. */ + protected long _lastAttempt; + /** Used to compute metrics. */ protected int _tries, _ticks, _time; @@ -722,12 +774,15 @@ public abstract class FrameManager /** Used to track big delays in calls to our tick method. */ protected long _lastTickStamp; - /** The timer that dispatches our frame ticks. */ - protected Timer _ticker; + /** The thread that dispatches our frame ticks. */ + protected Ticker _ticker; /** Used to track and report frames per second. */ protected float[] _fps = new float[2]; + /** Used to track performance metrics. */ + protected TrailingAverage[] _metrics; + /** Used to avoid creating rectangles when rendering layered * components. */ protected Rectangle _lbounds = new Rectangle(); @@ -766,6 +821,13 @@ public abstract class FrameManager "[requires restart]", "narya.media.frame", MediaPrefs.config, false); + /** Allows us to tweak the sleep granularity. */ + protected static RuntimeAdjust.IntAdjust _sleepGranularity = + new RuntimeAdjust.IntAdjust( + "The number of milliseconds slept before checking to see if " + + "it's time to queue up a new frame tick.", "narya.media.sleep_gran", + MediaPrefs.config, 7); + /** Whether to enable AWT event debugging for the frame. */ protected static final boolean DEBUG_EVENTS = false; }