diff --git a/src/java/com/threerings/media/FrameManager.java b/src/java/com/threerings/media/FrameManager.java index 5c32d99b..f9e5819e 100644 --- a/src/java/com/threerings/media/FrameManager.java +++ b/src/java/com/threerings/media/FrameManager.java @@ -56,74 +56,63 @@ import com.threerings.media.util.TrailingAverage; import com.threerings.util.unsafe.Unsafe; /** - * Provides a central point from which the computation for each "frame" or tick - * can be dispatched. This assumed that the application structures its activity - * around the rendering of each frame, which is a common architecture for - * games. The animation and sprite support provided by other classes in this - * package are structured for use in an application that uses a frame manager - * to tick everything once per frame. + * Provides a central point from which the computation for each "frame" or tick can be dispatched. + * This assumed that the application structures its activity around the rendering of each frame, + * which is a common architecture for games. The animation and sprite support provided by other + * classes in this package are structured for use in an application that uses a frame manager to + * tick everything once per frame. * *
The frame manager goes through a simple two part procedure every frame: * *
The ticking and rendering takes place on the AWT thread so as to - * avoid the need for complicated coordination between AWT event handler - * code and frame code. However, this means that all AWT (and Swing) event - * handlers must not perform any complicated processing. After - * each frame, control of the AWT thread is given back to the AWT which - * processes all pending AWT events before giving the frame manager an - * opportunity to process the next frame. Thus the convenience of - * everything running on the AWT thread comes with the price of requiring - * that AWT event handlers not block or perform any intensive processing. - * In general, this is a sensible structure for an application anyhow, so - * this organization tends to be preferable to an organization where the - * AWT and frame threads are separate and must tread lightly so as not to + *
The ticking and rendering takes place on the AWT thread so as to avoid the need for + * complicated coordination between AWT event handler code and frame code. However, this means that + * all AWT (and Swing) event handlers must not perform any complicated processing. After + * each frame, control of the AWT thread is given back to the AWT which processes all pending AWT + * events before giving the frame manager an opportunity to process the next frame. Thus the + * convenience of everything running on the AWT thread comes with the price of requiring that AWT + * event handlers not block or perform any intensive processing. In general, this is a sensible + * structure for an application anyhow, so this organization tends to be preferable to an + * organization where the AWT and frame threads are separate and must tread lightly so as not to * collide. * - *
Note: the way that JScrollPane goes about improving
- * performance when scrolling complicated contents cannot work with active
- * rendering. If you use a JScrollPane in an application that
- * uses the frame manager, you should either use the provided {@link
- * SafeScrollPane} or set your scroll panes' viewports to
- * SIMPLE_SCROLL_MODE.
+ *
Note: the way that JScrollPane goes about improving performance when scrolling
+ * complicated contents cannot work with active rendering. If you use a JScrollPane in
+ * an application that uses the frame manager, you should either use the provided {@link
+ * SafeScrollPane} or set your scroll panes' viewports to SIMPLE_SCROLL_MODE.
*/
public abstract class FrameManager
{
/**
- * Normally, the frame manager will repaint any component in a {@link
- * JLayeredPane} layer (popups, overlays, etc.) that overlaps a frame
- * participant on every tick because the frame participant could
- * have changed underneath the overlay which would require that the overlay
- * be repainted. If the application knows that the frame participant
- * beneath the overlay will never change, it can have its overlay implement
- * this interface and avoid the expense of forcibly fully repainting the
- * overlay on every frame.
+ * Normally, the frame manager will repaint any component in a {@link JLayeredPane} layer
+ * (popups, overlays, etc.) that overlaps a frame participant on every tick because the frame
+ * participant could have changed underneath the overlay which would require that the
+ * overlay be repainted. If the application knows that the frame participant beneath the
+ * overlay will never change, it can have its overlay implement this interface and avoid the
+ * expense of forcibly fully repainting the overlay on every frame.
*/
public static interface SafeLayerComponent
{
}
/**
- * Provides a bridge between either {@link ManagedJFrame} or {@link
- * ManagedJApplet} and the frame manager.
+ * Provides a bridge between either {@link ManagedJFrame} or {@link ManagedJApplet} and the
+ * frame manager.
*/
public static interface ManagedRoot
{
@@ -138,16 +127,15 @@ public abstract class FrameManager
}
/**
- * Creates a frame manager that will use a {@link SystemMediaTimer} to
- * obtain timing information, which is available on every platform, but
- * returns inaccurate time stamps on many platforms.
+ * Creates a frame manager that will use a {@link SystemMediaTimer} to obtain timing
+ * information, which is available on every platform, but returns inaccurate time stamps on
+ * many platforms.
*
* @see #newInstance(ManagedRoot, MediaTimer)
*/
public static FrameManager newInstance (ManagedRoot root)
{
- // first try creating a PerfTimer which is the best if we're using
- // JDK1.4.2
+ // first try creating a PerfTimer which is the best if we're using JDK1.4.2
MediaTimer timer = null;
try {
timer = (MediaTimer)Class.forName(PERF_TIMER).newInstance();
@@ -160,144 +148,21 @@ public abstract class FrameManager
}
/**
- * Constructs a frame manager that will do its rendering to the supplied
- * root.
+ * Constructs a frame manager that will do its rendering to the supplied root.
*/
public static FrameManager newInstance (ManagedRoot root, MediaTimer timer)
{
- FrameManager fmgr;
- if (_useFlip.getValue()) {
- Log.info("Creating flip frame manager.");
- fmgr = new FlipFrameManager();
- } else {
- Log.info("Creating back frame manager.");
- fmgr = new BackFrameManager();
- }
+ FrameManager fmgr = _useFlip.getValue() ? new FlipFrameManager() : new BackFrameManager();
fmgr.init(root, timer);
return fmgr;
}
/**
- * Initializes this frame manager and prepares it for operation.
- */
- protected void init (ManagedRoot root, MediaTimer timer)
- {
- _window = root.getWindow();
- _root = root;
- _root.init(this);
- _timer = timer;
-
- // set up our custom repaint manager
- _remgr = new ActiveRepaintManager(_window);
- RepaintManager.setCurrentManager(_remgr);
-
- // turn off double buffering for the whole business because we
- // handle repaints
- _remgr.setDoubleBufferingEnabled(false);
-
- if (DEBUG_EVENTS) {
- addTestListeners();
- }
- }
-
- /**
- * Adds a variety of listeners to the frame in order to provide
- * visibility into the various events received by the frame.
- */
- protected void addTestListeners ()
- {
- // add a test window listener
- _window.addWindowListener(new WindowListener() {
- public void windowActivated (WindowEvent e) {
- Log.info("Window activated [evt=" + e + "].");
- }
-
- public void windowClosed (WindowEvent e) {
- Log.info("Window closed [evt=" + e + "].");
- }
-
- public void windowClosing (WindowEvent e) {
- Log.info("Window closing [evt=" + e + "].");
- }
-
- public void windowDeactivated (WindowEvent e) {
- Log.info("Window deactivated [evt=" + e + "].");
- }
-
- public void windowDeiconified (WindowEvent e) {
- Log.info("Window deiconified [evt=" + e + "].");
- }
-
- public void windowIconified (WindowEvent e) {
- Log.info("Window iconified [evt=" + e + "].");
- }
-
- public void windowOpened (WindowEvent e) {
- Log.info("Window opened [evt=" + e + "].");
- }
- });
-
- // add a component listener
- _window.addComponentListener(new ComponentListener() {
- public void componentHidden (ComponentEvent e) {
- Log.info("Window component hidden [evt=" + e + "].");
- }
-
- public void componentShown (ComponentEvent e) {
- Log.info("Window component shown [evt=" + e + "].");
- }
-
- public void componentMoved (ComponentEvent e) {
- Log.info("Window component moved [evt=" + e + "].");
- }
-
- public void componentResized (ComponentEvent e) {
- Log.info("Window component resized [evt=" + e + "].");
- }
- });
-
- // add test ancestor focus listener
- _root.getRootPane().addAncestorListener(
- new AncestorListener() {
- public void ancestorAdded (AncestorEvent e) {
- Log.info("Root pane ancestor added [e=" + e + "].");
- }
-
- public void ancestorRemoved (AncestorEvent e) {
- Log.info("Root pane ancestor removed [e=" + e + "].");
- }
-
- public void ancestorMoved (AncestorEvent e) {
- Log.info("Root pane ancestor moved [e=" + e + "].");
- }
- });
-
- // add test key event dispatcher
- KeyboardFocusManager.getCurrentKeyboardFocusManager().
- addKeyEventDispatcher(new KeyEventDispatcher() {
- public boolean dispatchKeyEvent (KeyEvent e) {
-// if ((e.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) != 0 &&
-// e.getKeyCode() == KeyEvent.VK_TAB) {
-// Log.info("Detected alt-tab key event " +
-// "[e=" + e + "].");
-// // attempt to eat the event so that windows
-// // doesn't alt-tab into unhappy land
-// e.consume();
-// return true;
-// }
-
- return false;
- }
- });
- }
-
- /**
- * Instructs the frame manager to target the specified number of
- * frames per second. If the computation and rendering for a frame are
- * completed with time to spare, the frame manager will wait until the
- * proper time to begin processing for the next frame. If a frame
- * takes longer than its alotted time, the frame manager will
- * immediately begin processing on the next frame.
+ * Instructs the frame manager to target the specified number of frames per second. If the
+ * computation and rendering for a frame are completed with time to spare, the frame manager
+ * will wait until the proper time to begin processing for the next frame. If a frame takes
+ * longer than its alotted time, the frame manager will immediately begin processing on the
+ * next frame.
*/
public void setTargetFrameRate (int fps)
{
@@ -306,15 +171,14 @@ public abstract class FrameManager
}
/**
- * Registers a frame participant. The participant will be given the
- * opportunity to do processing and rendering on each frame.
+ * Registers a frame participant. The participant will be given the opportunity to do
+ * processing and rendering on each frame.
*/
public void registerFrameParticipant (FrameParticipant participant)
{
Object[] nparts = ListUtil.testAndAddRef(_participants, participant);
if (nparts == null) {
- Log.warning("Refusing to add duplicate frame participant! " +
- participant);
+ Log.warning("Refusing to add duplicate frame participant! " + participant);
} else {
_participants = nparts;
}
@@ -337,9 +201,9 @@ public abstract class FrameManager
}
/**
- * Returns a millisecond granularity time stamp using the {@link
- * MediaTimer} with which this frame manager was configured.
- * Note: this should only be called from the AWT thread.
+ * Returns a millisecond granularity time stamp using the {@link MediaTimer} with which this
+ * frame manager was configured. Note: this should only be called from the AWT
+ * thread.
*/
public long getTimeStamp ()
{
@@ -370,8 +234,8 @@ public abstract class FrameManager
}
/**
- * Returns true if the tick interval is be running (not necessarily at
- * that instant, but in general).
+ * Returns true if the tick interval is be running (not necessarily at that instant, but in
+ * general).
*/
public synchronized boolean isRunning ()
{
@@ -401,13 +265,51 @@ public abstract class FrameManager
{
if (_metrics == null) {
_metrics = new TrailingAverage[] {
- new TrailingAverage(150),
- new TrailingAverage(150),
- new TrailingAverage(150) };
+ new TrailingAverage(150), new TrailingAverage(150), new TrailingAverage(150)
+ };
}
return _metrics;
}
+ /**
+ * Returns the root component for the supplied component or null if it is not part of a rooted
+ * hierarchy or if any parent along the way is found to be hidden or without a peer. Along the
+ * way, it adjusts the supplied component-relative rectangle to be relative to the returned
+ * root component.
+ */
+ public static Component getRoot (Component comp, Rectangle rect)
+ {
+ for (Component c = comp; c != null; c = c.getParent()) {
+ if (!c.isVisible() || !c.isDisplayable()) {
+ return null;
+ }
+ if (c instanceof Window || c instanceof Applet) {
+ return c;
+ }
+ rect.x += c.getX();
+ rect.y += c.getY();
+ }
+ return null;
+ }
+
+ /**
+ * Initializes this frame manager and prepares it for operation.
+ */
+ protected void init (ManagedRoot root, MediaTimer timer)
+ {
+ _window = root.getWindow();
+ _root = root;
+ _root.init(this);
+ _timer = timer;
+
+ // set up our custom repaint manager
+ _remgr = new ActiveRepaintManager(_window);
+ RepaintManager.setCurrentManager(_remgr);
+
+ // turn off double buffering for the whole business because we handle repaints
+ _remgr.setDoubleBufferingEnabled(false);
+ }
+
/**
* Called to perform the frame processing and rendering.
*/
@@ -417,16 +319,16 @@ public abstract class FrameManager
if (_perfDebug.getValue()) {
start = paint = _timer.getElapsedMicros();
}
- // if our frame is not showing (or is impossibly sized), don't try
- // rendering anything
- if (_window.isShowing() &&
- _window.getWidth() > 0 && _window.getHeight() > 0) {
+
+ // if our frame is not showing (or is impossibly sized), don't try rendering anything
+ if (_window.isShowing() && _window.getWidth() > 0 && _window.getHeight() > 0) {
// tick our participants
tickParticipants(tickStamp);
paint = _timer.getElapsedMicros();
// repaint our participants and components
paint(tickStamp);
}
+
if (_perfDebug.getValue()) {
long end = _timer.getElapsedMicros();
getPerfMetrics()[1].record((int)(paint-start)/100);
@@ -435,8 +337,8 @@ public abstract class FrameManager
}
/**
- * Called once per frame to invoke {@link FrameParticipant#tick} on
- * all of our frame participants.
+ * Called once per frame to invoke {@link FrameParticipant#tick} on all of our frame
+ * participants.
*/
protected void tickParticipants (long tickStamp)
{
@@ -486,9 +388,8 @@ public abstract class FrameManager
}
/**
- * Called once per frame to invoke {@link Component#paint} on all of
- * our frame participants' components and all dirty components managed
- * by our {@link ActiveRepaintManager}.
+ * Called once per frame to invoke {@link Component#paint} on all of our frame participants'
+ * components and all dirty components managed by our {@link ActiveRepaintManager}.
*/
protected abstract void paint (long tickStamp);
@@ -500,15 +401,13 @@ public abstract class FrameManager
protected abstract Graphics2D createGraphics ();
/**
- * Paints our frame participants and any dirty components via the
- * repaint manager.
+ * Paints our frame participants and any dirty components via the repaint manager.
*
* @return true if anything was painted, false if not.
*/
protected boolean paint (Graphics2D gfx)
{
- // paint our frame participants (which want to be handled
- // specially)
+ // paint our frame participants (which want to be handled specially)
int painted = 0;
for (int ii = 0; ii < _participants.length; ii++) {
FrameParticipant part = (FrameParticipant)_participants[ii];
@@ -529,24 +428,21 @@ public abstract class FrameManager
// get the bounds of this component
pcomp.getBounds(_tbounds);
- // the bounds adjustment we're about to call will add in the
- // components initial bounds offsets, so we remove them here
+ // the bounds adjustment we're about to call will add in the components initial bounds
+ // offsets, so we remove them here
_tbounds.setLocation(0, 0);
- // convert them into top-level coordinates; also note that if
- // this component does not have a valid or visible root, we
- // don't want to paint it either
+ // convert them into top-level coordinates; also note that if this component does not
+ // have a valid or visible root, we don't want to paint it either
if (getRoot(pcomp, _tbounds) == null) {
continue;
}
try {
- // render this participant; we don't set the clip because
- // frame participants are expected to handle clipping
- // themselves; otherwise we might pointlessly set the clip
- // here, creating a few Rectangle objects in the process,
- // only to have the frame participant immediately set the
- // clip to something more sensible
+ // render this participant; we don't set the clip because frame participants are
+ // expected to handle clipping themselves; otherwise we might pointlessly set the
+ // clip here, creating a few Rectangle objects in the process, only to have the
+ // frame participant immediately set the clip to something more sensible
gfx.translate(_tbounds.x, _tbounds.y);
pcomp.paint(gfx);
gfx.translate(-_tbounds.x, -_tbounds.y);
@@ -554,13 +450,11 @@ public abstract class FrameManager
} catch (Throwable t) {
String ptos = StringUtil.safeToString(part);
- Log.warning("Frame participant choked during paint " +
- "[part=" + ptos + "].");
+ Log.warning("Frame participant choked during paint [part=" + ptos + "].");
Log.logStackTrace(t);
}
- // render any components in our layered pane that are not in
- // the default layer
+ // render any components in our layered pane that are not in the default layer
_clipped[0] = false;
renderLayers(gfx, pcomp, _tbounds, _clipped);
@@ -573,8 +467,7 @@ public abstract class FrameManager
}
}
- // repaint any widgets that have declared they need to be
- // repainted since the last tick
+ // repaint any widgets that have declared they need to be repainted since the last tick
boolean pcomp = _remgr.paintComponents(gfx, this);
// let the caller know if anybody painted anything
@@ -582,20 +475,18 @@ public abstract class FrameManager
}
/**
- * Called by the {@link ManagedJFrame} when our window was hidden and
- * reexposed.
+ * Called by the {@link ManagedJFrame} when our window was hidden and reexposed.
*/
protected abstract void restoreFromBack (Rectangle dirty);
/**
- * Renders all components in all {@link JLayeredPane} layers that
- * intersect the supplied bounds.
+ * Renders all components in all {@link JLayeredPane} layers that intersect the supplied
+ * bounds.
*/
protected void renderLayers (Graphics2D g, Component pcomp,
Rectangle bounds, boolean[] clipped)
{
- JLayeredPane lpane =
- JLayeredPane.getLayeredPaneAbove(pcomp);
+ JLayeredPane lpane = JLayeredPane.getLayeredPaneAbove(pcomp);
if (lpane != null) {
renderLayer(g, bounds, lpane, clipped, JLayeredPane.PALETTE_LAYER);
renderLayer(g, bounds, lpane, clipped, JLayeredPane.MODAL_LAYER);
@@ -605,12 +496,11 @@ public abstract class FrameManager
}
/**
- * Renders all components in the specified layer of the supplied
- * layered pane that intersect the supplied bounds.
+ * Renders all components in the specified layer of the supplied layered pane that intersect
+ * the supplied bounds.
*/
- protected void renderLayer (Graphics2D g, Rectangle bounds,
- JLayeredPane pane, boolean[] clipped,
- Integer layer)
+ protected void renderLayer (Graphics2D g, Rectangle bounds, JLayeredPane pane,
+ boolean[] clipped, Integer layer)
{
// stop now if there are no components in that layer
int ccount = pane.getComponentCountInLayer(layer.intValue());
@@ -626,16 +516,16 @@ public abstract class FrameManager
continue;
}
- // if this overlay does not intersect the component we just
- // rendered, we don't need to repaint it
+ // if this overlay does not intersect the component we just rendered, we don't need to
+ // repaint it
_tbounds.setBounds(0, 0, comp.getWidth(), comp.getHeight());
getRoot(comp, _tbounds);
if (!_tbounds.intersects(bounds)) {
continue;
}
- // if the clipping region has not yet been set during this
- // render pass, the time has come to do so
+ // if the clipping region has not yet been set during this render pass, the time has
+ // come to do so
if (!clipped[0]) {
g.setClip(bounds);
clipped[0] = true;
@@ -653,34 +543,6 @@ public abstract class FrameManager
}
}
- // documentation inherited
- public void checkpoint (String name, int ticks)
- {
- Log.info("Frames in last second: " + ticks);
- }
-
- /**
- * Returns the root component for the supplied component or null if it
- * is not part of a rooted hierarchy or if any parent along the way is
- * found to be hidden or without a peer. Along the way, it adjusts the
- * supplied component-relative rectangle to be relative to the
- * returned root component.
- */
- public static Component getRoot (Component comp, Rectangle rect)
- {
- for (Component c = comp; c != null; c = c.getParent()) {
- if (!c.isVisible() || !c.isDisplayable()) {
- return null;
- }
- if (c instanceof Window || c instanceof Applet) {
- return c;
- }
- rect.x += c.getX();
- rect.y += c.getY();
- }
- return null;
- }
-
/** Used to effect periodic calls to {@link #tick}. */
protected class Ticker extends Thread
{
@@ -704,12 +566,11 @@ public abstract class FrameManager
}
}
- // work around sketchy bug on WinXP that causes the clock
- // to leap into the past from time to time
+ // work around sketchy bug on WinXP that causes the clock to leap into the past
+ // from time to time
if (woke < _lastAttempt) {
- Log.warning("Zoiks! We've leapt into the past, coping " +
- "as best we can [dt=" +
- (woke - _lastAttempt) + "].");
+ Log.warning("Zoiks! We've leapt into the past, coping as best we can " +
+ "[dt=" + (woke - _lastAttempt) + "].");
_lastAttempt = woke;
}
@@ -750,12 +611,9 @@ public abstract class FrameManager
_ticking = false;
}
- /** Used to invoke the call to {@link #tick} on the AWT event
- * queue thread. */
- protected Runnable _awtTicker = new Runnable ()
- {
- public void run ()
- {
+ /** Used to invoke the call to {@link #tick} on the AWT event queue thread. */
+ protected Runnable _awtTicker = new Runnable () {
+ public void run () {
long elapsed = _timer.getElapsedMillis();
try {
tick(elapsed);
@@ -793,8 +651,7 @@ public abstract class FrameManager
/** Our custom repaint manager. */
protected ActiveRepaintManager _remgr;
- /** The number of milliseconds per frame (14 by default, which gives
- * an fps of ~71). */
+ /** The number of milliseconds per frame (14 by default, which gives an fps of ~71). */
protected long _millisPerFrame = 14;
/** Used to track big delays in calls to our tick method. */
@@ -812,8 +669,7 @@ public abstract class FrameManager
/** A temporary bounds rectangle used to avoid lots of object creation. */
protected Rectangle _tbounds = new Rectangle();
- /** Used to lazily set the clip when painting popups and other
- * "layered" components. */
+ /** Used to lazily set the clip when painting popups and other "layered" components. */
protected boolean[] _clipped = new boolean[1];
/** The entites that are ticked each frame. */
@@ -822,42 +678,32 @@ public abstract class FrameManager
/** If we don't get ticked for 500ms, that's worth complaining about. */
protected static final long BIG_GAP = 500L;
- /** If we don't get ticked for 100ms and we're hang debugging,
- * complain. */
+ /** If we don't get ticked for 100ms and we're hang debugging, complain. */
protected static final long HANG_GAP = 100L;
- /** Enable this to log warnings when ticking or painting takes too
- * long. */
+ /** Enable this to log warnings when ticking or painting takes too long. */
protected static final boolean HANG_DEBUG = false;
/** A debug hook that toggles debug rendering of sprite paths. */
- protected static RuntimeAdjust.BooleanAdjust _useFlip =
- new RuntimeAdjust.BooleanAdjust(
- "When active a flip-buffer will be used to manage our " +
- "rendering, otherwise a volatile back buffer is used " +
- "[requires restart]", "narya.media.frame",
- // back buffer rendering doesn't work on the Mac, so we
- // default to flip buffer on that platform; we still allow it
- // to be toggled so that we can easily test things when they
- // release new JVMs
- MediaPrefs.config, RunAnywhere.isMacOS());
+ protected static RuntimeAdjust.BooleanAdjust _useFlip = new RuntimeAdjust.BooleanAdjust(
+ "When active a flip-buffer will be used to manage our rendering, otherwise a " +
+ "volatile back buffer is used [requires restart]", "narya.media.frame",
+ // back buffer rendering doesn't work on the Mac, so we default to flip buffer on that
+ // platform; we still allow it to be toggled so that we can easily test things when
+ // they release new JVMs
+ MediaPrefs.config, RunAnywhere.isMacOS());
/** 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, RunAnywhere.isWindows() ? 10 : 7);
+ 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, RunAnywhere.isWindows() ? 10 : 7);
/** A debug hook that toggles FPS rendering. */
protected static RuntimeAdjust.BooleanAdjust _perfDebug = new RuntimeAdjust.BooleanAdjust(
"Toggles frames per second and dirty regions per tick rendering.",
"narya.media.fps_display", MediaPrefs.config, false);
- /** Whether to enable AWT event debugging for the frame. */
- protected static final boolean DEBUG_EVENTS = false;
-
/** The name of the high-performance timer class we attempt to load. */
- protected static final String PERF_TIMER =
- "com.threerings.media.timer.PerfTimer";
+ protected static final String PERF_TIMER = "com.threerings.media.timer.PerfTimer";
}