A whole bunch of refactoring to support a MediaOverlay which will allow us to

render sprites and animations anywhere in the frame for those times when you
just need to draw outside the lines.

The actual MediaOverlay isn't done yet, but all the refactoring to support it
is done, so the rest should be easy. In theory this shouldn't break anything
(like the Yohoho build), but we're about to find out.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@112 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2007-01-20 01:09:53 +00:00
parent 86645aa41e
commit a8de5bb789
13 changed files with 702 additions and 341 deletions
+1 -1
View File
@@ -59,7 +59,7 @@
<!-- build the java class files -->
<target name="compile" depends="check-available,prepare">
<javac srcdir="src/java" destdir="${classes.dir}"
<javac srcdir="src/java" destdir="${classes.dir}" includeAntRuntime="false"
debug="on" optimize="{$build.optimize}" deprecation="on"
source="1.5" target="1.5">
<classpath refid="classpath"/>
+1
View File
@@ -2,6 +2,7 @@
<!-- declares the libraries needed to build nenya -->
<project name="library-dependencies">
<fileset dir="${libs.dir}" id="nenya.libs">
<include name="ant.jar"/>
<include name="commons-collections.jar"/>
<include name="commons-digester.jar"/>
<include name="commons-io.jar"/>
@@ -41,10 +41,10 @@ public abstract class AbstractMediaManager
/**
* Default constructor.
*/
public AbstractMediaManager (MediaPanel panel)
public AbstractMediaManager (MediaHost host)
{
_panel = panel;
_remgr = panel.getRegionManager();
_host = host;
_remgr = host.getRegionManager();
}
/**
@@ -62,7 +62,7 @@ public abstract class AbstractMediaManager
*/
public Graphics2D createGraphics ()
{
return (Graphics2D)_panel.getGraphics();
return _host.createGraphics();
}
/**
@@ -303,8 +303,8 @@ public abstract class AbstractMediaManager
_notify.clear();
}
/** The media panel we're working with. */
protected MediaPanel _panel;
/** The media host we're working with. */
protected MediaHost _host;
/** The region manager. */
protected RegionManager _remgr;
@@ -32,7 +32,7 @@ import java.awt.image.VolatileImage;
*/
public class BackFrameManager extends FrameManager
{
// documentation inherited
// from FrameManager
protected void paint (long tickStamp)
{
// start out assuming we can do an incremental render
@@ -63,7 +63,7 @@ public class BackFrameManager extends FrameManager
if (_bgfx != null) {
_bgfx.dispose();
}
_bgfx = (Graphics2D)_backimg.getGraphics();
_bgfx = _backimg.createGraphics();
if (_fgfx != null) {
_fgfx.dispose();
_fgfx = null;
@@ -95,7 +95,13 @@ public class BackFrameManager extends FrameManager
} while (_backimg.contentsLost());
}
// documentation inherited
// from FrameManager
protected Graphics2D createGraphics ()
{
return _backimg.createGraphics();
}
// from FrameManager
protected void restoreFromBack (Rectangle dirty)
{
if (_fgfx == null || _backimg == null) {
@@ -34,7 +34,7 @@ import java.awt.image.BufferStrategy;
*/
public class FlipFrameManager extends FrameManager
{
// documentation inherited
// from FrameManager
protected void paint (long tickStamp)
{
// create our buffer strategy if we don't already have one
@@ -90,7 +90,13 @@ public class FlipFrameManager extends FrameManager
} while (_bufstrat.contentsLost());
}
// documentation inherited
// from FrameManager
protected Graphics2D createGraphics ()
{
return (Graphics2D)_bufstrat.getDrawGraphics();
}
// from FrameManager
protected void restoreFromBack (Rectangle dirty)
{
// nothing doing
@@ -414,7 +414,7 @@ public abstract class FrameManager
protected void tick (long tickStamp)
{
long start = 0L, paint = 0L;
if (MediaPanel._perfDebug.getValue()) {
if (_perfDebug.getValue()) {
start = paint = _timer.getElapsedMicros();
}
// if our frame is not showing (or is impossibly sized), don't try
@@ -427,7 +427,7 @@ public abstract class FrameManager
// repaint our participants and components
paint(tickStamp);
}
if (MediaPanel._perfDebug.getValue()) {
if (_perfDebug.getValue()) {
long end = _timer.getElapsedMicros();
getPerfMetrics()[1].record((int)(paint-start)/100);
getPerfMetrics()[2].record((int)(end-paint)/100);
@@ -492,6 +492,13 @@ public abstract class FrameManager
*/
protected abstract void paint (long tickStamp);
/**
* Returns a graphics context with which to layout its media objects. The returned context must
* be disposed when layout is complete and must not be retained across frame ticks. Used by the
* {@link MediaOverlay}.
*/
protected abstract Graphics2D createGraphics ();
/**
* Paints our frame participants and any dirty components via the
* repaint manager.
@@ -683,7 +690,7 @@ public abstract class FrameManager
"[sleepGran=" + _sleepGranularity.getValue() + "].");
while (_running) {
long start = 0L;
if (MediaPanel._perfDebug.getValue()) {
if (_perfDebug.getValue()) {
start = _timer.getElapsedMicros();
}
Unsafe.sleep(_sleepGranularity.getValue());
@@ -842,6 +849,11 @@ public abstract class FrameManager
"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;
@@ -0,0 +1,43 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media;
import java.awt.Graphics2D;
/**
* Provides the glue between {@link AbstractMediaManager} and {@link MediaPanel} and {@link
* MediaOverlay}.
*/
public interface MediaHost
{
/**
* Creates a graphics context for the component underlying this media host. This method can
* return null if the component is not currently displayable. The graphics created must be
* disposed by the caller.
*/
public Graphics2D createGraphics ();
/**
* Returns the region manager being used to manage dirty regions by the host.
*/
public RegionManager getRegionManager ();
}
@@ -0,0 +1,85 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media;
import java.awt.Graphics2D;
import com.threerings.media.animation.AnimationManager;
import com.threerings.media.sprite.SpriteManager;
/**
* Provides an overlaid media "canvas" that allows for the rendering of sprites and animations on
* top of everything else that takes place the view managed by a {@link FrameManager}. The media
* overlay coordinates through the {@link ActiveRepaintManager} to repaint areas of the screen that
* it has left dirty.
*/
public class MediaOverlay
implements MediaHost
{
protected MediaOverlay (FrameManager fmgr)
{
_framemgr = fmgr;
_remgr = new RegionManager();
_animmgr = new AnimationManager(this);
_spritemgr = new SpriteManager(this);
}
/**
* Returns a reference to the animation manager used by this media panel.
*/
public AnimationManager getAnimationManager ()
{
return _animmgr;
}
/**
* Returns a reference to the sprite manager used by this media panel.
*/
public SpriteManager getSpriteManager ()
{
return _spritemgr;
}
// from interface MediaHost
public Graphics2D createGraphics ()
{
return _framemgr.createGraphics();
}
// from interface MediaHost
public RegionManager getRegionManager ()
{
return _remgr;
}
/** The frame manager with whom we cooperate. */
protected FrameManager _framemgr;
/** The animation manager in use by this overlay. */
protected AnimationManager _animmgr;
/** The sprite manager in use by this overlay. */
protected SpriteManager _spritemgr;
/** Used to accumulate and merge dirty regions on each tick. */
protected RegionManager _remgr;
}
+150 -305
View File
@@ -21,10 +21,7 @@
package com.threerings.media;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Rectangle;
@@ -38,15 +35,11 @@ import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.MouseInputAdapter;
import java.util.Arrays;
import java.util.ArrayList;
import com.samskivert.swing.Controller;
import com.samskivert.swing.Label;
import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.swing.event.AncestorAdapter;
import com.samskivert.swing.event.CommandEvent;
import com.samskivert.util.IntListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.timer.MediaTimer;
@@ -65,60 +58,53 @@ import com.threerings.media.sprite.action.DisableableSprite;
import com.threerings.media.sprite.action.HoverSprite;
/**
* Provides a useful extensible framework for rendering animated displays
* that use sprites and animations. Sprites and animations can be added to
* this panel and they will automatically be ticked and rendered (see
* {@link #addSprite} and {@link #addAnimation}).
* Provides a useful extensible framework for rendering animated displays that use sprites and
* animations. Sprites and animations can be added to this panel and they will automatically be
* ticked and rendered (see {@link #addSprite} and {@link #addAnimation}).
*
* <p> To facilitate optimized sprite and animation rendering, the panel
* provides a dirty region manager which is used to only repaint dirtied
* regions on each frame. Derived classes can add dirty regions to the
* scene and/or augment the dirty regions created by moving sprites and
* <p> To facilitate optimized sprite and animation rendering, the panel provides a dirty region
* manager which is used to only repaint dirtied regions on each frame. Derived classes can add
* dirty regions to the scene and/or augment the dirty regions created by moving sprites and
* changing animations.
*
* <p> Sprite and animation rendering is done in two layers: front and
* back. Callbacks are provided to render behind the back layer ({@link
* #paintBehind}), in between front and back ({@link #paintBetween}) and
* in front of the front layer ({@link #paintInFront}).
* <p> Sprite and animation rendering is done in two layers: front and back. Callbacks are provided
* to render behind the back layer ({@link #paintBehind}), in between front and back ({@link
* #paintBetween}) and in front of the front layer ({@link #paintInFront}).
*
* <p> The animated panel automatically registers with the {@link
* FrameManager} to participate in the frame tick. It only does so while
* it is a visible part of the UI hierarchy, so animations and sprites are
* paused while the animated panel is hidden.
* <p> The animated panel automatically registers with the {@link FrameManager} to participate in
* the frame tick. It only does so while it is a visible part of the UI hierarchy, so animations
* and sprites are paused while the animated panel is hidden.
*/
public class MediaPanel extends JComponent
implements FrameParticipant, MediaConstants
implements FrameParticipant, MediaConstants, MediaHost
{
/**
* Constructs a media panel.
*/
public MediaPanel (FrameManager framemgr)
{
// keep this for later
_framemgr = framemgr;
setOpaque(true); // our repaints shouldn't cause other jcomponents to
// create our region manager
_remgr = new RegionManager();
// create our animation and sprite managers
_animmgr = new AnimationManager(this);
_spritemgr = new SpriteManager(this);
// create our meta manager
_metamgr = new MetaMediaManager(framemgr, this);
_animmgr = _metamgr.getAnimationManager();
_spritemgr = _metamgr.getSpriteManager();
_remgr = _metamgr.getRegionManager();
// participate in the frame when we're visible
addAncestorListener(new AncestorAdapter() {
public void ancestorAdded (AncestorEvent event) {
_framemgr.registerFrameParticipant(MediaPanel.this);
_metamgr.getFrameManager().registerFrameParticipant(MediaPanel.this);
}
public void ancestorRemoved (AncestorEvent event) {
_framemgr.removeFrameParticipant(MediaPanel.this);
_metamgr.getFrameManager().removeFrameParticipant(MediaPanel.this);
}
});
}
/**
* Get the bounds of the viewport, in media coordinates. For the base
* MediaPanel, this will always be (0, 0, width, height).
* Get the bounds of the viewport, in media coordinates. For the base MediaPanel, this will
* always be (0, 0, width, height).
*/
public Rectangle getViewBounds ()
{
@@ -126,12 +112,11 @@ public class MediaPanel extends JComponent
}
/**
* Returns a reference to the animation manager used by this media
* panel.
* Returns a reference to the animation manager used by this media panel.
*/
public AnimationManager getAnimationManager ()
{
return _animmgr;
return _metamgr.getAnimationManager();
}
/**
@@ -139,61 +124,25 @@ public class MediaPanel extends JComponent
*/
public SpriteManager getSpriteManager ()
{
return _spritemgr;
return _metamgr.getSpriteManager();
}
/**
* Pauses the sprites and animations that are currently active on this
* media panel. Also stops listening to the frame tick while paused.
* Pauses the sprites and animations that are currently active on this media panel. Also stops
* listening to the frame tick while paused.
*/
public void setPaused (boolean paused)
{
// sanity check
if ((paused && (_pauseTime != 0)) ||
(!paused && (_pauseTime == 0))) {
Log.warning("Requested to pause when paused or vice-versa " +
"[paused=" + paused + "].");
return;
}
if (_paused = paused) {
// make a note of our pause time
_pauseTime = _framemgr.getTimeStamp();
} else {
// let the animation and sprite managers know that we just
// warped into the future
long delta = _framemgr.getTimeStamp() - _pauseTime;
_animmgr.fastForward(delta);
_spritemgr.fastForward(delta);
// clear out our pause time
_pauseTime = 0;
}
_metamgr.setPaused(paused);
}
/**
* Returns a reference to the region manager that is used to
* accumulate dirty regions each frame.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Returns a timestamp from the {@link MediaTimer} used to track time
* intervals for this media panel. <em>Note:</em> this should only be
* called from the AWT thread.
* Returns a timestamp from the {@link MediaTimer} used to track time intervals for this media
* panel. <em>Note:</em> this should only be called from the AWT thread.
*/
public long getTimeStamp ()
{
return _framemgr.getTimeStamp();
}
// documentation inherited from interface
public void getPerformanceStatus (StringBuilder buf)
{
return _metamgr.getTimeStamp();
}
/**
@@ -201,8 +150,8 @@ public class MediaPanel extends JComponent
*/
public void addSprite (Sprite sprite)
{
_spritemgr.addSprite(sprite);
_metamgr.addSprite(sprite);
if (((sprite instanceof ActionSprite) ||
(sprite instanceof HoverSprite)) && (_actionSpriteCount++ == 0)) {
if (_actionHandler == null) {
@@ -218,7 +167,7 @@ public class MediaPanel extends JComponent
*/
public boolean isManaged (Sprite sprite)
{
return _spritemgr.isManaged(sprite);
return _metamgr.isManaged(sprite);
}
/**
@@ -226,8 +175,8 @@ public class MediaPanel extends JComponent
*/
public void removeSprite (Sprite sprite)
{
_spritemgr.removeSprite(sprite);
_metamgr.removeSprite(sprite);
if (((sprite instanceof ActionSprite) ||
(sprite instanceof HoverSprite)) && (--_actionSpriteCount == 0)) {
removeMouseListener(_actionHandler);
@@ -240,7 +189,7 @@ public class MediaPanel extends JComponent
*/
public void clearSprites ()
{
_spritemgr.clearMedia();
_metamgr.clearSprites();
if (_actionHandler != null) {
removeMouseListener(_actionHandler);
@@ -255,7 +204,7 @@ public class MediaPanel extends JComponent
*/
public void addAnimation (Animation anim)
{
_animmgr.registerAnimation(anim);
_metamgr.addAnimation(anim);
}
/**
@@ -263,7 +212,7 @@ public class MediaPanel extends JComponent
*/
public boolean isManaged (Animation anim)
{
return _animmgr.isManaged(anim);
return _metamgr.isManaged(anim);
}
/**
@@ -273,7 +222,7 @@ public class MediaPanel extends JComponent
*/
public void abortAnimation (Animation anim)
{
_animmgr.unregisterAnimation(anim);
_metamgr.abortAnimation(anim);
}
/**
@@ -281,125 +230,62 @@ public class MediaPanel extends JComponent
*/
public void clearAnimations ()
{
_animmgr.clearMedia();
_metamgr.clearAnimations();
}
// documentation inherited from interface
// from interface MediaHost
public RegionManager getRegionManager ()
{
return _metamgr.getRegionManager();
}
// from interface MediaHost
public Graphics2D createGraphics ()
{
return (Graphics2D)getGraphics();
}
// from interface FrameParticipant
public void tick (long tickStamp)
{
// bail if ticking is currently disabled
if (_paused) {
if (_metamgr.isPaused()) {
return;
}
// let derived classes do their business
willTick(tickStamp);
// now tick our animations and sprites
_animmgr.tick(tickStamp);
_spritemgr.tick(tickStamp);
// if performance debugging is enabled,
if (_perfDebug.getValue()) {
if (_perfLabel == null) {
_perfLabel = new Label(
"", Label.OUTLINE, Color.white, Color.black,
new Font("Arial", Font.PLAIN, 10));
}
if (_perfRect == null) {
_perfRect = new Rectangle(5, 5, 0, 0);
}
StringBuilder perf = new StringBuilder();
perf.append("[FPS: ");
perf.append(_framemgr.getPerfTicks()).append("/");
perf.append(_framemgr.getPerfTries());
perf.append(" PM:");
StringUtil.toString(perf, _framemgr.getPerfMetrics());
// perf.append(" MP:").append(_dirtyPerTick);
perf.append("]");
String perfStatus = perf.toString();
if (!_perfStatus.equals(perfStatus)) {
_perfStatus = perfStatus;
_perfLabel.setText(perfStatus);
Graphics2D gfx = (Graphics2D)getGraphics();
if (gfx != null) {
_perfLabel.layout(gfx);
gfx.dispose();
// make sure the region we dirty contains the old and
// the new text (which we ensure by never letting the
// rect shrink)
Dimension psize = _perfLabel.getSize();
_perfRect.width = Math.max(_perfRect.width, psize.width);
_perfRect.height = Math.max(_perfRect.height, psize.height);
dirtyScreenRect(new Rectangle(_perfRect));
}
}
}
// tick our meta manager which will tick our sprites and animations
_metamgr.tick(tickStamp);
// let derived classes do their business
didTick(tickStamp);
// make a note that the next paint will correspond to a call to
// tick()
// make a note that the next paint will correspond to a call to tick()
_tickPaintPending = true;
}
/**
* Derived classes can override this method and perform computation
* prior to the ticking of the sprite and animation managers.
*/
protected void willTick (long tickStamp)
{
}
/**
* Derived classes can override this method and perform computation
* subsequent to the ticking of the sprite and animation managers.
*/
protected void didTick (long tickStamp)
{
}
// documentation inherited from interface
// from interface FrameParticipant
public boolean needsPaint ()
{
// compute our average dirty regions per tick
if (_tick++ == 99) {
_tick = 0;
int dirty = IntListUtil.sum(_dirty);
Arrays.fill(_dirty, 0);
_dirtyPerTick = (float)dirty/100;
}
boolean needsPaint = _metamgr.needsPaint();
// if we have no dirty regions, clear our pending tick indicator
// because we're not going to get painted
boolean needsPaint = _remgr.haveDirtyRegions();
// if we have no dirty regions, clear our pending tick indicator because we're not going to
// get painted
if (!needsPaint) {
_tickPaintPending = false;
}
// regardless of whether or not we paint, we need to let our
// abstract media managers know that we've gotten to the point of
// painting because they need to remain prepared to deal with
// media changes that happen any time between the tick() and the
// paint() and thus need to know when paint() happens
_animmgr.willPaint();
_spritemgr.willPaint();
return needsPaint;
}
// documentation inherited from interface
// from interface FrameParticipant
public Component getComponent ()
{
return this;
}
// documentation inherited
@Override // from JComponent
public void setOpaque (boolean opaque)
{
if (!opaque) {
@@ -409,7 +295,7 @@ public class MediaPanel extends JComponent
super.setOpaque(true);
}
// documentation inherited
@Override // from JComponent
public void repaint (long tm, int x, int y, int width, int height)
{
if (width > 0 && height > 0) {
@@ -417,19 +303,17 @@ public class MediaPanel extends JComponent
}
}
// documentation inherited
@Override // from JComponent
public void paint (Graphics g)
{
Graphics2D gfx = (Graphics2D)g;
// no use in painting if we're not showing or if we've not yet
// been validated
// no use in painting if we're not showing or if we've not yet been validated
if (!isValid() || !isShowing()) {
return;
}
// if this isn't a tick paint, then we need to grab the clipping
// rectangle and mark it as dirty
// if this isn't a tick paint, then we need to mark the clipping rectangle as dirty
if (!_tickPaintPending) {
Shape clip = g.getClip();
if (clip == null) {
@@ -439,25 +323,24 @@ public class MediaPanel extends JComponent
dirtyScreenRect(clip.getBounds());
}
// we used to bail out here and not render until our next
// tick, but it turns out that we need to render here because
// Swing may have repainted our parent over us and expect that
// we're going to paint ourselves on top of whatever it just
// painted, so we go ahead and paint now to avoid flashing
// we used to bail out here and not render until our next tick, but it turns out that
// we need to render here because Swing may have repainted our parent over us and
// expect that we're going to paint ourselves on top of whatever it just painted, so we
// go ahead and paint now to avoid flashing
} else {
_tickPaintPending = false;
}
// if we have no invalid rects, there's no need to repaint
if (!_remgr.haveDirtyRegions()) {
if (!_metamgr.getRegionManager().haveDirtyRegions()) {
return;
}
// get our dirty rectangles and delegate the main painting to a
// method that can be more easily overridden
Rectangle[] dirty = _remgr.getDirtyRegions();
_dirty[_tick] = dirty.length;
// get our dirty rectangles and delegate the main painting to a method that can be more
// easily overridden
Rectangle[] dirty = _metamgr.getRegionManager().getDirtyRegions();
_metamgr.noteDirty(dirty.length);
try {
paint(gfx, dirty);
} catch (Throwable t) {
@@ -466,17 +349,29 @@ public class MediaPanel extends JComponent
}
// render our performance debugging if it's enabled
if (_perfRect != null && _perfDebug.getValue()) {
gfx.setClip(null);
_perfLabel.render(gfx, _perfRect.x, _perfRect.y);
}
_metamgr.paintPerf(gfx);
}
/**
* Performs the actual painting of the media panel. Derived methods
* can override this method if they wish to perform pre- and/or
* post-paint activities or if they wish to provide their own painting
* mechanism entirely.
* Derived classes can override this method and perform computation prior to the ticking of the
* sprite and animation managers.
*/
protected void willTick (long tickStamp)
{
}
/**
* Derived classes can override this method and perform computation subsequent to the ticking
* of the sprite and animation managers.
*/
protected void didTick (long tickStamp)
{
}
/**
* Performs the actual painting of the media panel. Derived methods can override this method if
* they wish to perform pre- and/or post-paint activities or if they wish to provide their own
* painting mechanism entirely.
*/
protected void paint (Graphics2D gfx, Rectangle[] dirty)
{
@@ -503,7 +398,7 @@ public class MediaPanel extends JComponent
clipToDirtyRegion(gfx, clip);
// paint the region
paintDirtyRect(gfx, clip);
paintDirtyRect(gfx, clip);
}
}
@@ -527,28 +422,24 @@ public class MediaPanel extends JComponent
// paint anything in front
paintInFront(gfx, rect);
}
/**
* Called by the main rendering code to constrain this dirty rectangle
* to the bounds of the media panel. If a derived class is using dirty
* rectangles that live in some sort of virtual coordinate system,
* they'll want to override this method and constraint the rectangles
* Called by the main rendering code to constrain this dirty rectangle to the bounds of the
* media panel. If a derived class is using dirty rectangles that live in some sort of virtual
* coordinate system, they'll want to override this method and constraint the rectangles
* properly.
*/
protected void constrainToBounds (Rectangle dirty)
{
SwingUtilities.computeIntersection(
0, 0, getWidth(), getHeight(), dirty);
SwingUtilities.computeIntersection(0, 0, getWidth(), getHeight(), dirty);
}
/**
* This is called to clip the rendering output to the supplied dirty
* region. This should use {@link Graphics#setClip} because the
* clipping region will need to be replaced as we iterate through our
* dirty regions. By default, a region is assumed to represent screen
* coordinates, but if a derived class wishes to maintain dirty
* regions in non-screen coordinates, it can override this method to
* properly clip to the dirty region.
* This is called to clip the rendering output to the supplied dirty region. This should use
* {@link Graphics#setClip} because the clipping region will need to be replaced as we iterate
* through our dirty regions. By default, a region is assumed to represent screen coordinates,
* but if a derived class wishes to maintain dirty regions in non-screen coordinates, it can
* override this method to properly clip to the dirty region.
*/
protected void clipToDirtyRegion (Graphics2D gfx, Rectangle dirty)
{
@@ -557,107 +448,64 @@ public class MediaPanel extends JComponent
}
/**
* Called to mark the specified rectangle (in screen coordinates) as
* dirty. The rectangle will be bent, folded and mutilated, so be sure
* you're not passing a rectangle into this method that is being used
* elsewhere.
* Called to mark the specified rectangle (in screen coordinates) as dirty. The rectangle will
* be bent, folded and mutilated, so be sure you're not passing a rectangle into this method
* that is being used elsewhere.
*
* <p> If derived classes wish to convert from screen coordinates to
* some virtual coordinate system to be used by their repaint manager,
* this is the place to do it.
* <p> If derived classes wish to convert from screen coordinates to some virtual coordinate
* system to be used by their repaint manager, this is the place to do it.
*/
protected void dirtyScreenRect (Rectangle rect)
{
_remgr.addDirtyRegion(rect);
_metamgr.getRegionManager().addDirtyRegion(rect);
}
/**
* Paints behind all sprites and animations. The supplied invalid
* rectangle should be redrawn in the supplied graphics context.
* Sub-classes should override this method to do the actual rendering
* for their display.
* Paints behind all sprites and animations. The supplied invalid rectangle should be redrawn
* in the supplied graphics context. Sub-classes should override this method to do the actual
* rendering for their display.
*/
protected void paintBehind (Graphics2D gfx, Rectangle dirtyRect)
{
}
/**
* Paints between the front and back layer of sprites and animations.
* The supplied invalid rectangle should be redrawn in the supplied
* graphics context. Sub-classes should override this method to do the
* actual rendering for their display.
* Paints between the front and back layer of sprites and animations. The supplied invalid
* rectangle should be redrawn in the supplied graphics context. Sub-classes should override
* this method to do the actual rendering for their display.
*/
protected void paintBetween (Graphics2D gfx, Rectangle dirtyRect)
{
}
/**
* Paints in front of all sprites and animations. The supplied invalid
* rectangle should be redrawn in the supplied graphics context.
* Sub-classes should override this method to do the actual rendering
* for their display.
* Paints in front of all sprites and animations. The supplied invalid rectangle should be
* redrawn in the supplied graphics context. Sub-classes should override this method to do the
* actual rendering for their display.
*/
protected void paintInFront (Graphics2D gfx, Rectangle dirtyRect)
{
}
/**
* Renders the sprites and animations that intersect the supplied
* dirty region in the specified layer. Derived classes can override
* this method if they need to do custom sprite or animation rendering
* (if they need to do special sprite z-order handling, for example).
* The clipping region will already be set appropriately.
* Renders the sprites and animations that intersect the supplied dirty region in the specified
* layer. Derived classes can override this method if they need to do custom sprite or
* animation rendering (if they need to do special sprite z-order handling, for example). The
* clipping region will already be set appropriately.
*/
protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty)
{
if (layer == FRONT) {
_spritemgr.paint(gfx, layer, dirty);
_animmgr.paint(gfx, layer, dirty);
} else {
_animmgr.paint(gfx, layer, dirty);
_spritemgr.paint(gfx, layer, dirty);
}
_metamgr.paintMedia(gfx, layer, dirty);
}
/**
* Creates the mouse listener that will handle action sprites and their
* variants.
* Creates the mouse listener that will handle action sprites and their variants.
*/
protected ActionSpriteHandler createActionSpriteHandler ()
{
return new ActionSpriteHandler();
}
/** The frame manager with whom we register. */
protected FrameManager _framemgr;
/** The animation manager in use by this panel. */
protected AnimationManager _animmgr;
/** The sprite manager in use by this panel. */
protected SpriteManager _spritemgr;
/** Used to accumulate and merge dirty regions on each tick. */
protected RegionManager _remgr;
/** Used to correlate tick()s with paint()s. */
protected boolean _tickPaintPending = false;
/** Whether we're currently paused. */
protected boolean _paused;
/** Used to track the clock time at which we were paused. */
protected long _pauseTime;
/** Used to keep metrics. */
protected int[] _dirty = new int[200];
/** Used to keep metrics. */
protected int _tick;
/** Used to keep metrics. */
protected float _dirtyPerTick;
/** Handles ActionSprite/HoverSprite/ArmingSprite manipulation. */
protected class ActionSpriteHandler extends MouseInputAdapter
{
@@ -676,14 +524,14 @@ public class MediaPanel extends JComponent
((ArmingSprite) _activeSprite).setArmed(true);
}
}
// documentation inherited
public void mouseReleased (MouseEvent me)
{
if (_activeSprite instanceof ArmingSprite) {
((ArmingSprite)_activeSprite).setArmed(false);
}
if ((_activeSprite instanceof ActionSprite) &&
_activeSprite.hitTest(me.getX(), me.getY())) {
ActionEvent event;
@@ -702,7 +550,7 @@ public class MediaPanel extends JComponent
}
Controller.postAction(event);
}
if (!(_activeSprite instanceof HoverSprite)) {
_activeSprite = null;
}
@@ -741,7 +589,7 @@ public class MediaPanel extends JComponent
protected Sprite getHit (MouseEvent me)
{
ArrayList list = new ArrayList();
_spritemgr.getHitSprites(list, me.getX(), me.getY());
getSpriteManager().getHitSprites(list, me.getX(), me.getY());
for (int ii = 0, nn = list.size(); ii < nn; ii++) {
Object o = list.get(ii);
if ((o instanceof HoverSprite || o instanceof ActionSprite) &&
@@ -756,28 +604,25 @@ public class MediaPanel extends JComponent
/** The active hover sprite, or action sprite. */
protected Sprite _activeSprite;
}
/** Handles most of our heavy lifting. */
protected MetaMediaManager _metamgr;
/** Legacy reference to avoid breaking children in the wild. */
protected AnimationManager _animmgr;
/** Legacy reference to avoid breaking children in the wild. */
protected SpriteManager _spritemgr;
/** Legacy reference to avoid breaking children in the wild. */
protected RegionManager _remgr;
/** Used to correlate tick()s with paint()s. */
protected boolean _tickPaintPending = false;
/** The action sprite handler, or null for none. */
protected ActionSpriteHandler _actionHandler;
/** The number of action/hover sprites being managed. */
protected int _actionSpriteCount;
// used to render performance metrics
protected String _perfStatus = "";
protected Label _perfLabel;
protected static Rectangle _perfRect;
/** 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) {
protected void adjusted (boolean newValue) {
// clear out some things if we're turned off
if (!newValue) {
_perfRect = null;
}
}
};
}
@@ -0,0 +1,370 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media;
import java.util.Arrays;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.samskivert.swing.Label;
import com.samskivert.util.IntListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationManager;
/**
* Coordinates interaction between a sprite and animation manager and the media host that hosts and
* renders them. This class is a little fiddly because {@link MediaPanel} has been around a long
* time and is thoroughly out in the wild and now that we need to abstract out a bunch of its
* functionality, we're constrained by all the extant usages and derivations.
*/
public class MetaMediaManager
implements MediaConstants
{
public MetaMediaManager (FrameManager framemgr, MediaHost host)
{
// keep these for later
_framemgr = framemgr;
_host = host;
// create our region manager
_remgr = new RegionManager();
// create our animation and sprite managers
_animmgr = new AnimationManager(host);
_spritemgr = new SpriteManager(host);
}
/**
* Returns the frame manager with which we are coordinating.
*/
public FrameManager getFrameManager ()
{
return _framemgr;
}
/**
* Returns a reference to the animation manager used by this media panel.
*/
public AnimationManager getAnimationManager ()
{
return _animmgr;
}
/**
* Returns a reference to the sprite manager used by this media panel.
*/
public SpriteManager getSpriteManager ()
{
return _spritemgr;
}
/**
* Returns true if we are paused, false if we are running normally.
*/
public boolean isPaused ()
{
return _paused;
}
/**
* Pauses the sprites and animations that are currently active on this media panel. Also stops
* listening to the frame tick while paused.
*/
public void setPaused (boolean paused)
{
// sanity check
if ((paused && (_pauseTime != 0)) || (!paused && (_pauseTime == 0))) {
Log.warning("Requested to pause when paused or vice-versa [paused=" + paused + "].");
return;
}
if (_paused = paused) {
// make a note of our pause time
_pauseTime = _framemgr.getTimeStamp();
} else {
// let the animation and sprite managers know that we just warped into the future
long delta = _framemgr.getTimeStamp() - _pauseTime;
_animmgr.fastForward(delta);
_spritemgr.fastForward(delta);
// clear out our pause time
_pauseTime = 0;
}
}
/**
* Returns a timestamp from the {@link MediaTimer} used to track time intervals for this media
* panel. <em>Note:</em> this should only be called from the AWT thread.
*/
public long getTimeStamp ()
{
return _framemgr.getTimeStamp();
}
/**
* Adds a sprite to this panel.
*/
public void addSprite (Sprite sprite)
{
_spritemgr.addSprite(sprite);
}
/**
* @return true if the sprite is already added to this panel.
*/
public boolean isManaged (Sprite sprite)
{
return _spritemgr.isManaged(sprite);
}
/**
* Removes a sprite from this panel.
*/
public void removeSprite (Sprite sprite)
{
_spritemgr.removeSprite(sprite);
}
/**
* Removes all sprites from this panel.
*/
public void clearSprites ()
{
_spritemgr.clearMedia();
}
/**
* Adds an animation to this panel. Animations are automatically removed when they finish.
*/
public void addAnimation (Animation anim)
{
_animmgr.registerAnimation(anim);
}
/**
* @return true if the animation is already added to this panel.
*/
public boolean isManaged (Animation anim)
{
return _animmgr.isManaged(anim);
}
/**
* Aborts a currently running animation and removes it from this panel. Animations are normally
* automatically removed when they finish.
*/
public void abortAnimation (Animation anim)
{
_animmgr.unregisterAnimation(anim);
}
/**
* Removes all animations from this panel.
*/
public void clearAnimations ()
{
_animmgr.clearMedia();
}
/**
* Returns the region manager used to coordiante our dirty regions.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Called by the host to coordinate dirty region tracking. This should be supplied with the
* number of dirty regions being painted on this tick and called just before painting them.
*/
public void noteDirty (int regions)
{
_dirty[_tick] = regions;
}
/**
* Our media front end should implement {@link FrameParticipant} and call this methed in their
* {@link FrameParticipant#tick} method. They must also first check {@link #isPaused} and not
* call this method if we are paused. As they will probably want to have willTick() and
* didTick() calldown methods, we cannot handle pausedness for them.
*/
public void tick (long tickStamp)
{
// now tick our animations and sprites
_animmgr.tick(tickStamp);
_spritemgr.tick(tickStamp);
// if performance debugging is enabled,
if (FrameManager._perfDebug.getValue()) {
if (_perfLabel == null) {
_perfLabel = new Label("", Label.OUTLINE, Color.white, Color.black,
new Font("Arial", Font.PLAIN, 10));
}
if (_perfRect == null) {
_perfRect = new Rectangle(5, 5, 0, 0);
}
StringBuilder perf = new StringBuilder();
perf.append("[FPS: ");
perf.append(_framemgr.getPerfTicks()).append("/");
perf.append(_framemgr.getPerfTries());
perf.append(" PM:");
StringUtil.toString(perf, _framemgr.getPerfMetrics());
// perf.append(" MP:").append(_dirtyPerTick);
perf.append("]");
String perfStatus = perf.toString();
if (!_perfStatus.equals(perfStatus)) {
_perfStatus = perfStatus;
_perfLabel.setText(perfStatus);
Graphics2D gfx = _host.createGraphics();
if (gfx != null) {
_perfLabel.layout(gfx);
gfx.dispose();
// make sure the region we dirty contains the old and the new text (which we
// ensure by never letting the rect shrink)
Dimension psize = _perfLabel.getSize();
_perfRect.width = Math.max(_perfRect.width, psize.width);
_perfRect.height = Math.max(_perfRect.height, psize.height);
_remgr.addDirtyRegion(new Rectangle(_perfRect));
}
}
} else {
_perfRect = null;
}
}
/**
* Our media front end should implement {@link FrameParticipant} and call this methed in their
* {@link FrameParticipant#needsPaint} method.
*/
public boolean needsPaint ()
{
// compute our average dirty regions per tick
if (_tick++ == 99) {
_tick = 0;
int dirty = IntListUtil.sum(_dirty);
Arrays.fill(_dirty, 0);
_dirtyPerTick = (float)dirty/100;
}
// regardless of whether or not we paint, we need to let our abstract media managers know
// that we've gotten to the point of painting because they need to remain prepared to deal
// with media changes that happen any time between the tick() and the paint() and thus need
// to know when paint() happens
_animmgr.willPaint();
_spritemgr.willPaint();
// if we have dirty regions, we need painting
return _remgr.haveDirtyRegions();
}
/**
* Renders the sprites and animations that intersect the supplied dirty region in the specified
* layer.
*/
public void paintMedia (Graphics2D gfx, int layer, Rectangle dirty)
{
if (layer == FRONT) {
_spritemgr.paint(gfx, layer, dirty);
_animmgr.paint(gfx, layer, dirty);
} else {
_animmgr.paint(gfx, layer, dirty);
_spritemgr.paint(gfx, layer, dirty);
}
}
/**
* Renders our performance debugging information if enabled.
*/
public void paintPerf (Graphics2D gfx)
{
if (_perfRect != null && FrameManager._perfDebug.getValue()) {
gfx.setClip(null);
_perfLabel.render(gfx, _perfRect.x, _perfRect.y);
}
}
/**
* If our host supports scrolling around in a virtual view, it should call this method when the
* view origin changes.
*/
public void viewLocationDidChange (int dx, int dy)
{
if (_perfRect != null) {
Rectangle sdirty = new Rectangle(_perfRect);
sdirty.translate(-dx, -dy);
_remgr.addDirtyRegion(sdirty);
}
// let our sprites and animations know what's up
_animmgr.viewLocationDidChange(dx, dy);
_spritemgr.viewLocationDidChange(dx, dy);
}
/** The frame manager with whom we register. */
protected FrameManager _framemgr;
/** Our media host, so gracious and accomodating. */
protected MediaHost _host;
/** The animation manager in use by this panel. */
protected AnimationManager _animmgr;
/** The sprite manager in use by this panel. */
protected SpriteManager _spritemgr;
/** Used to accumulate and merge dirty regions on each tick. */
protected RegionManager _remgr;
/** Whether we're currently paused. */
protected boolean _paused;
/** Used to track the clock time at which we were paused. */
protected long _pauseTime;
/** Used to keep metrics. */
protected int[] _dirty = new int[200];
/** Used to keep metrics. */
protected int _tick;
/** Used to keep metrics. */
protected float _dirtyPerTick;
// used to render performance metrics
protected String _perfStatus = "";
protected Label _perfLabel;
protected Rectangle _perfRect;
}
@@ -180,7 +180,7 @@ public class VirtualMediaPanel extends MediaPanel
{
// translate the screen rect into happy coordinates
rect.translate(_vbounds.x, _vbounds.y);
_remgr.addDirtyRegion(rect);
_metamgr.getRegionManager().addDirtyRegion(rect);
}
// documentation inherited
@@ -245,7 +245,7 @@ public class VirtualMediaPanel extends MediaPanel
// Mac OS X's redraw breaks on scrolling, so we repaint the
// entire panel
if (RunAnywhere.isMacOS()) {
_remgr.invalidateRegion(_nx, _ny, width, height);
_metamgr.getRegionManager().invalidateRegion(_nx, _ny, width, height);
} else {
_dx = dx;
_dy = dy;
@@ -257,15 +257,15 @@ public class VirtualMediaPanel extends MediaPanel
// and add invalid rectangles for the exposed areas
if (dy > 0) {
shei = Math.max(shei - dy, 0);
_remgr.invalidateRegion(_nx, _ny + height - dy, width, dy);
_metamgr.getRegionManager().invalidateRegion(_nx, _ny + height - dy, width, dy);
} else if (dy < 0) {
sy -= dy;
_remgr.invalidateRegion(_nx, _ny, width, -dy);
_metamgr.getRegionManager().invalidateRegion(_nx, _ny, width, -dy);
}
if (dx > 0) {
_remgr.invalidateRegion(_nx + width - dx, sy, dx, shei);
_metamgr.getRegionManager().invalidateRegion(_nx + width - dx, sy, dx, shei);
} else if (dx < 0) {
_remgr.invalidateRegion(_nx, sy, -dx, shei);
_metamgr.getRegionManager().invalidateRegion(_nx, sy, -dx, shei);
}
}
@@ -290,20 +290,13 @@ public class VirtualMediaPanel extends MediaPanel
*/
protected void viewLocationDidChange (int dx, int dy)
{
if (_perfRect != null) {
Rectangle sdirty = new Rectangle(_perfRect);
sdirty.translate(-dx, -dy);
dirtyScreenRect(sdirty);
}
// inform our view trackers
for (int ii = 0, ll = _trackers.size(); ii < ll; ii++) {
((ViewTracker)_trackers.get(ii)).viewLocationDidChange(dx, dy);
}
// let our sprites and animations know what's up
_animmgr.viewLocationDidChange(dx, dy);
_spritemgr.viewLocationDidChange(dx, dy);
// pass the word on to our sprite/anim managers via the meta manager
_metamgr.viewLocationDidChange(dx, dy);
}
/**
@@ -22,7 +22,7 @@
package com.threerings.media.animation;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.MediaPanel;
import com.threerings.media.MediaHost;
/**
* Manages a collection of animations, ticking them when the animation
@@ -35,9 +35,9 @@ public class AnimationManager extends AbstractMediaManager
* Construct and initialize the animation manager which readies itself
* to manage animations.
*/
public AnimationManager (MediaPanel panel)
public AnimationManager (MediaHost host)
{
super(panel);
super(host);
}
/**
@@ -31,7 +31,7 @@ import java.util.Iterator;
import com.samskivert.util.Predicate;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.MediaPanel;
import com.threerings.media.MediaHost;
/**
* The sprite manager manages the sprites running about in the game.
@@ -41,9 +41,9 @@ public class SpriteManager extends AbstractMediaManager
/**
* Construct and initialize the sprite manager.
*/
public SpriteManager (MediaPanel panel)
public SpriteManager (MediaHost host)
{
super(panel);
super(host);
}
/**