diff --git a/build.xml b/build.xml index 3a96af7d..c71ef68c 100644 --- a/build.xml +++ b/build.xml @@ -59,7 +59,7 @@ - diff --git a/libs-incl.xml b/libs-incl.xml index 545af1f8..dc716e82 100644 --- a/libs-incl.xml +++ b/libs-incl.xml @@ -2,6 +2,7 @@ + diff --git a/src/java/com/threerings/media/AbstractMediaManager.java b/src/java/com/threerings/media/AbstractMediaManager.java index 05f2e057..2d7d10e7 100644 --- a/src/java/com/threerings/media/AbstractMediaManager.java +++ b/src/java/com/threerings/media/AbstractMediaManager.java @@ -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; diff --git a/src/java/com/threerings/media/BackFrameManager.java b/src/java/com/threerings/media/BackFrameManager.java index f8488ac2..2849c5bf 100644 --- a/src/java/com/threerings/media/BackFrameManager.java +++ b/src/java/com/threerings/media/BackFrameManager.java @@ -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) { diff --git a/src/java/com/threerings/media/FlipFrameManager.java b/src/java/com/threerings/media/FlipFrameManager.java index 0e309b55..8afaeaea 100644 --- a/src/java/com/threerings/media/FlipFrameManager.java +++ b/src/java/com/threerings/media/FlipFrameManager.java @@ -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 diff --git a/src/java/com/threerings/media/FrameManager.java b/src/java/com/threerings/media/FrameManager.java index 2041db30..5c32d99b 100644 --- a/src/java/com/threerings/media/FrameManager.java +++ b/src/java/com/threerings/media/FrameManager.java @@ -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; diff --git a/src/java/com/threerings/media/MediaHost.java b/src/java/com/threerings/media/MediaHost.java new file mode 100644 index 00000000..a9b5f5bb --- /dev/null +++ b/src/java/com/threerings/media/MediaHost.java @@ -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 (); +} diff --git a/src/java/com/threerings/media/MediaOverlay.java b/src/java/com/threerings/media/MediaOverlay.java new file mode 100644 index 00000000..5313b696 --- /dev/null +++ b/src/java/com/threerings/media/MediaOverlay.java @@ -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; +} diff --git a/src/java/com/threerings/media/MediaPanel.java b/src/java/com/threerings/media/MediaPanel.java index abe2e174..609f7755 100644 --- a/src/java/com/threerings/media/MediaPanel.java +++ b/src/java/com/threerings/media/MediaPanel.java @@ -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}). * - *

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 + *

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. * - *

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}). + *

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}). * - *

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. + *

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. Note: 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. Note: 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. * - *

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. + *

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; - } - } - }; } diff --git a/src/java/com/threerings/media/MetaMediaManager.java b/src/java/com/threerings/media/MetaMediaManager.java new file mode 100644 index 00000000..fc0c3794 --- /dev/null +++ b/src/java/com/threerings/media/MetaMediaManager.java @@ -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. Note: 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; +} diff --git a/src/java/com/threerings/media/VirtualMediaPanel.java b/src/java/com/threerings/media/VirtualMediaPanel.java index 6ac4aecd..1293962d 100644 --- a/src/java/com/threerings/media/VirtualMediaPanel.java +++ b/src/java/com/threerings/media/VirtualMediaPanel.java @@ -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); } /** diff --git a/src/java/com/threerings/media/animation/AnimationManager.java b/src/java/com/threerings/media/animation/AnimationManager.java index 23a06607..3112b4c8 100644 --- a/src/java/com/threerings/media/animation/AnimationManager.java +++ b/src/java/com/threerings/media/animation/AnimationManager.java @@ -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); } /** diff --git a/src/java/com/threerings/media/sprite/SpriteManager.java b/src/java/com/threerings/media/sprite/SpriteManager.java index 488606d9..a98bd6ef 100644 --- a/src/java/com/threerings/media/sprite/SpriteManager.java +++ b/src/java/com/threerings/media/sprite/SpriteManager.java @@ -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); } /**