
com.threerings.media.MetaMediaManager Maven / Gradle / Ivy
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/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.util.Iterator;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.google.common.collect.Iterators;
import com.samskivert.util.IntListUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.Label;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationManager;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.timer.MediaTimer;
import static com.threerings.media.Log.log;
/**
* 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, Iterable
{
public MetaMediaManager (FrameManager framemgr, MediaHost host)
{
// keep these for later
_framemgr = framemgr;
_host = host;
// initialize our managers
_animmgr.init(host, _remgr);
_spritemgr.init(host, _remgr);
}
/**
* 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 the region manager used to coordinate our dirty regions.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* 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;
}
_paused = paused;
if (_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();
}
/**
* 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 method 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 method 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);
}
public Iterator iterator ()
{
return Iterators.concat(_spritemgr.enumerateSprites(), _animmgr.iterator());
}
/** The frame manager with whom we register. */
protected FrameManager _framemgr;
/** Our media host, so gracious and accomodating. */
protected MediaHost _host;
/** Used to accumulate and merge dirty regions on each tick. */
protected RegionManager _remgr = new RegionManager();
/** The animation manager in use by this panel. */
protected AnimationManager _animmgr = new AnimationManager();
/** The sprite manager in use by this panel. */
protected SpriteManager _spritemgr = new SpriteManager();
/** 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;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy