* Add a fringe tile cache

Fringe tiles are added to a WeakHashMap used across the scene panel as they're
created and then held with hard references by the scene blocks they're in.
Whenever the last scene block referencing a fringe tile goes out of the
influential radius, the fringe can be collected from the cache.  In fringe
heavy scenes this reduces the memory for fringe by a third.

* Remove the mask cache

The fringe cache reduced the number of times a mask needs to be generated, so
eliminate the mask cache in the hopes of reclaiming a little more memory.



git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@548 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Charlie Groves
2008-06-23 21:59:16 +00:00
parent d426b70e58
commit 7ace9ac320
3 changed files with 142 additions and 78 deletions
@@ -21,6 +21,8 @@
package com.threerings.miso.client; package com.threerings.miso.client;
import static com.threerings.miso.Log.log;
import java.awt.AlphaComposite; import java.awt.AlphaComposite;
import java.awt.BasicStroke; import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
@@ -40,11 +42,11 @@ import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionListener;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JFrame; import javax.swing.JFrame;
@@ -53,12 +55,13 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.Controller; import com.samskivert.swing.Controller;
import com.samskivert.swing.RadialMenu; import com.samskivert.swing.RadialMenu;
import com.samskivert.swing.RuntimeAdjust; import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.swing.event.CommandEvent; import com.samskivert.swing.event.CommandEvent;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.media.VirtualMediaPanel; import com.threerings.media.VirtualMediaPanel;
import com.threerings.media.sprite.Sprite; import com.threerings.media.sprite.Sprite;
@@ -74,12 +77,11 @@ import com.threerings.miso.client.DirtyItemList.DirtyItem;
import com.threerings.miso.data.MisoSceneModel; import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo; import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTile; import com.threerings.miso.tile.BaseTile;
import com.threerings.miso.tile.AutoFringer.FringeTile;
import com.threerings.miso.util.MisoContext; import com.threerings.miso.util.MisoContext;
import com.threerings.miso.util.MisoSceneMetrics; import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil; import com.threerings.miso.util.MisoUtil;
import static com.threerings.miso.Log.log;
/** /**
* Renders a Miso scene for all to see. * Renders a Miso scene for all to see.
*/ */
@@ -141,7 +143,7 @@ public class MisoScenePanel extends VirtualMediaPanel
{ {
_blocks.clear(); _blocks.clear();
_vizobjs.clear(); _vizobjs.clear();
_masks.clear(); _fringes.clear();
if (_dpanel != null) { if (_dpanel != null) {
_dpanel.newScene(); _dpanel.newScene();
} }
@@ -366,7 +368,7 @@ public class MisoScenePanel extends VirtualMediaPanel
"[scene=" + StringUtil.shortClassName(this) + "[scene=" + StringUtil.shortClassName(this) +
", base=" + base.size() + "->" + (usage[0] / 1024) + "k" + ", base=" + base.size() + "->" + (usage[0] / 1024) + "k" +
", fringe=" + fringe.size() + "->" + (usage[1] / 1024) + "k" + ", fringe=" + fringe.size() + "->" + (usage[1] / 1024) + "k" +
", fmasks=" + _masks.size() + ", obj=" + object.size() + ", obj=" + object.size() +
"->" + (usage[2] / 1024) + "k" + "]."); "->" + (usage[2] / 1024) + "k" + "].");
} }
@@ -858,7 +860,6 @@ public class MisoScenePanel extends VirtualMediaPanel
protected void computeInfluentialBounds () protected void computeInfluentialBounds ()
{ {
computeInfluentialBounds(_vbounds, _ibounds, _vibounds); computeInfluentialBounds(_vbounds, _ibounds, _vibounds);
} }
@@ -1405,7 +1406,7 @@ public class MisoScenePanel extends VirtualMediaPanel
/** Computes the fringe tile for the specified coordinate. */ /** Computes the fringe tile for the specified coordinate. */
protected BaseTile computeFringeTile (int tx, int ty) protected BaseTile computeFringeTile (int tx, int ty)
{ {
return _ctx.getTileManager().getAutoFringer().getFringeTile(_model, tx, ty, _masks); return _ctx.getTileManager().getAutoFringer().getFringeTile(_model, tx, ty, _fringes);
} }
/** /**
@@ -1553,8 +1554,14 @@ public class MisoScenePanel extends VirtualMediaPanel
/** A list of the potentially visible objects in the scene. */ /** A list of the potentially visible objects in the scene. */
protected List<SceneObject> _vizobjs = Lists.newArrayList(); protected List<SceneObject> _vizobjs = Lists.newArrayList();
/** For computing fringe tiles. */ /**
protected Map _masks = Maps.newHashMap(); * Map of active fringe tiles. Scene blocks have hard references to fringe tiles in this map
* for the tiles they're using, so the blocks coming in and out of the influential bounds
* determines which tiles remain in the map. The map is from FringeTile to FringeTile so a
* fully created FringeTile can be extracted from the map using a tile that contains only
* what's needed for hashCode and equals: id and passability.
*/
protected Map<FringeTile, FringeTile> _fringes = new WeakHashMap<FringeTile, FringeTile>();
/** The dirty sprites and objects that need to be re-painted. */ /** The dirty sprites and objects that need to be re-painted. */
protected DirtyItemList _dirtyItems = new DirtyItemList(); protected DirtyItemList _dirtyItems = new DirtyItemList();
@@ -427,7 +427,6 @@ public class SceneBlock
Map<Tile.Key,ObjectTile> objects, long[] usage) Map<Tile.Key,ObjectTile> objects, long[] usage)
{ {
// account for our base tiles // account for our base tiles
MisoSceneModel model = _panel.getSceneModel();
for (int yy = 0; yy < _bounds.height; yy++) { for (int yy = 0; yy < _bounds.height; yy++) {
for (int xx = 0; xx < _bounds.width; xx++) { for (int xx = 0; xx < _bounds.width; xx++) {
int x = _bounds.x + xx, y = _bounds.y + yy; int x = _bounds.x + xx, y = _bounds.y + yy;
@@ -21,40 +21,76 @@
package com.threerings.miso.tile; package com.threerings.miso.tile;
import static com.threerings.miso.Log.log;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Transparency; import java.awt.Transparency;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Lists;
import com.samskivert.util.CheapIntMap; import com.samskivert.util.CheapIntMap;
import com.samskivert.util.QuickSort; import com.samskivert.util.QuickSort;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.BufferedMirage; import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.NoSuchTileSetException; import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.Tile; import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager; import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil; import com.threerings.media.tile.TileUtil;
import com.threerings.miso.data.MisoSceneModel; import com.threerings.miso.data.MisoSceneModel;
import static com.threerings.miso.Log.log;
/** /**
* Automatically fringes a scene according to the rules in the supplied fringe configuration. * Automatically fringes a scene according to the rules in the supplied fringe configuration.
*/ */
public class AutoFringer public class AutoFringer
{ {
public static class FringeTile extends BaseTile
{
public FringeTile (long[] fringeId, boolean passable)
{
setPassable(passable);
_fringeId = fringeId;
}
@Override
public boolean equals (Object obj)
{
if (!(obj instanceof FringeTile)) {
return false;
}
FringeTile fObj = (FringeTile)obj;
return _passable == fObj._passable && Arrays.equals(_fringeId, fObj._fringeId);
}
@Override
public int hashCode ()
{
int result = 33;
for (long key : _fringeId) {
result = result * 37 + (int)(key ^ (key >>> 32));
}
if(_passable) {
result++;
}
return result;
}
/** The fringe keys of the tiles that went into this tile in the order they were drawn. */
protected long[] _fringeId;
}
/** /**
* Constructs an instance that will fringe according to the rules in * Constructs an instance that will fringe according to the rules in the supplied fringe
* the supplied fringe configuration. * configuration.
*/ */
public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr, public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr, TileManager tmgr)
TileManager tmgr)
{ {
_fringeconf = fringeconf; _fringeconf = fringeconf;
_imgr = imgr; _imgr = imgr;
@@ -64,15 +100,16 @@ public class AutoFringer
/** /**
* Returns the fringe configuration used by this fringer. * Returns the fringe configuration used by this fringer.
*/ */
public FringeConfiguration getFringeConf () { public FringeConfiguration getFringeConf ()
{
return _fringeconf; return _fringeconf;
} }
/** /**
* Compute and return the fringe tile to be inserted at the specified * Compute and return the fringe tile to be inserted at the specified location.
* location.
*/ */
public BaseTile getFringeTile (MisoSceneModel scene, int col, int row, Map masks) public BaseTile getFringeTile (MisoSceneModel scene, int col, int row,
Map<FringeTile, FringeTile> fringes)
{ {
// get the tileset id of the base tile we are considering // get the tileset id of the base tile we are considering
int underset = adjustTileSetId(scene.getBaseTileId(col, row) >> 16); int underset = adjustTileSetId(scene.getBaseTileId(col, row) >> 16);
@@ -114,12 +151,11 @@ public class AutoFringer
// we allow users to splash in the water. // we allow users to splash in the water.
if (passable && (btid > 0)) { if (passable && (btid > 0)) {
try { try {
BaseTile bt = (BaseTile) _tmgr.getTile(btid); BaseTile bt = (BaseTile)_tmgr.getTile(btid);
passable = bt.isPassable(); passable = bt.isPassable();
} catch (NoSuchTileSetException nstse) { } catch (NoSuchTileSetException nstse) {
log.warning("Autofringer couldn't find a base " + log.warning("Autofringer couldn't find a base set while attempting to " +
"set while attempting to figure passability " + "figure passability", nstse);
"[error=" + nstse + "].");
} }
} }
} }
@@ -140,68 +176,90 @@ public class AutoFringer
} }
} }
BaseTile frTile = new BaseTile(); return composeFringeTile(frecs, fringes, TileUtil.getTileHash(col, row), passable);
frTile.setPassable(passable);
composeFringeTile(frTile, frecs, masks, TileUtil.getTileHash(col, row));
return frTile;
} }
/** /**
* Compose a FringeTile out of the various fringe images needed. * Compose a FringeTile out of the various fringe images needed.
*/ */
protected void composeFringeTile (Tile frTile, FringerRec[] fringers, Map masks, int hashValue) protected FringeTile composeFringeTile (FringerRec[] fringers,
Map<FringeTile, FringeTile> fringes, int hashValue, boolean passable)
{ {
// sort the array so that higher priority fringers get drawn first // sort the array so that higher priority fringers get drawn first
QuickSort.sort(fringers); QuickSort.sort(fringers);
BufferedImage ftimg = null; // Generate an identifier for the fringe tile being created as an array of the keys of its
// component tiles in the order they'll be drawn in the fringe tile.
List<Long> keys = Lists.newArrayList();
for (int ii = 0; ii < fringers.length; ii++) { for (int ii = 0; ii < fringers.length; ii++) {
int[] indexes = getFringeIndexes(fringers[ii].bits); int[] indexes = getFringeIndexes(fringers[ii].bits);
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(
fringers[ii].baseset, hashValue);
int fringeset = tsr.fringe_tsid;
for (int jj = 0; jj < indexes.length; jj++) {
// Add a key for this tile as a long containing its base tile, the fringe set it's
// working with and the index used in that set.
keys.add((((long)fringers[ii].baseset) << 32) + (fringeset << 16) + indexes[jj]);
}
}
long[] fringeId = new long[keys.size()];
for (int ii = 0; ii < fringeId.length; ii++) {
fringeId[ii] = keys.get(ii);
}
FringeTile frTile = new FringeTile(fringeId, passable);
// If the fringes map contains something with the same fringe identifier, this will pull
// it out and we can use it instead.
FringeTile result = fringes.get(frTile);
if (result != null) {
return result;
}
// There's no fringe with he same identifier, so we need to create the tile.
BufferedImage img = null;
for (int ii = 0; ii < fringers.length; ii++) {
int[] indexes = getFringeIndexes(fringers[ii].bits);
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(
fringers[ii].baseset, hashValue);
for (int jj = 0; jj < indexes.length; jj++) { for (int jj = 0; jj < indexes.length; jj++) {
try { try {
ftimg = getTileImage(ftimg, fringers[ii].baseset, indexes[jj], masks, hashValue); img = getTileImage(img, tsr, fringers[ii].baseset, indexes[jj], hashValue);
} catch (NoSuchTileSetException nstse) { } catch (NoSuchTileSetException nstse) {
log.warning("Autofringer couldn't find a needed tileset [error=" + nstse + "]."); log.warning("Autofringer couldn't find a needed tileset", nstse);
} }
} }
} }
frTile.setImage(new BufferedMirage(img));
frTile.setImage(new BufferedMirage(ftimg)); fringes.put(frTile, frTile);
return frTile;
} }
/** /**
* Retrieve or compose an image for the specified fringe. * Retrieve or compose an image for the specified fringe.
*/ */
protected BufferedImage getTileImage (BufferedImage ftimg, int baseset, int index, protected BufferedImage getTileImage (BufferedImage img,
Map masks, int hashValue) FringeConfiguration.FringeTileSetRecord tsr, int baseset, int index, int hashValue)
throws NoSuchTileSetException throws NoSuchTileSetException
{ {
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(baseset, hashValue);
int fringeset = tsr.fringe_tsid; int fringeset = tsr.fringe_tsid;
TileSet fset = _tmgr.getTileSet(fringeset); TileSet fset = _tmgr.getTileSet(fringeset);
if (!tsr.mask) { if (!tsr.mask) {
// oh good, this is easy // oh good, this is easy
Tile stamp = fset.getTile(index); Tile stamp = fset.getTile(index);
return stampTileImage(stamp, ftimg, stamp.getWidth(), stamp.getHeight()); return stampTileImage(stamp, img, stamp.getWidth(), stamp.getHeight());
} }
// otherwise, it's a mask.. look for it in the cache.. // otherwise, it's a mask..
Long maskkey = Long.valueOf((((long) baseset) << 32) + (fringeset << 16) + index); BufferedImage fsrc = _tmgr.getTileSet(fringeset).getRawTileImage(index);
BufferedImage img = (BufferedImage)masks.get(maskkey); BufferedImage bsrc = _tmgr.getTileSet(baseset).getRawTileImage(0);
if (img == null) { BufferedImage mask = ImageUtil.composeMaskedImage(_imgr, fsrc, bsrc);
BufferedImage fsrc = fset.getRawTileImage(index);
BufferedImage bsrc = _tmgr.getTileSet(baseset).getRawTileImage(0);
img = ImageUtil.composeMaskedImage(_imgr, fsrc, bsrc);
masks.put(maskkey, img);
}
ftimg = stampTileImage(img, ftimg, img.getWidth(null), img.getHeight(null));
return ftimg; return stampTileImage(mask, img, mask.getWidth(null), mask.getHeight(null));
} }
/** Helper function for {@link #getTileImage}. */ /** Helper function for {@link #getTileImage}. */
protected BufferedImage stampTileImage (Object stamp, BufferedImage ftimg, int width, int height) protected BufferedImage stampTileImage (Object stamp, BufferedImage ftimg, int width,
int height)
{ {
// create the target image if necessary // create the target image if necessary
if (ftimg == null) { if (ftimg == null) {
@@ -247,17 +305,16 @@ public class AutoFringer
return new int[0]; return new int[0];
} }
ArrayList indexes = new ArrayList(); ArrayList<Integer> indexes = Lists.newArrayList();
int weebits = 0; int weebits = 0;
for (int ii=(start + 1) % NUM_FRINGEBITS; ii != start; for (int ii = (start + 1) % NUM_FRINGEBITS; ii != start; ii = (ii + 1) % NUM_FRINGEBITS) {
ii = (ii + 1) % NUM_FRINGEBITS) {
if (((1 << ii) & bits) != 0) { if (((1 << ii) & bits) != 0) {
weebits |= (1 << ii); weebits |= (1 << ii);
} else if (weebits != 0) { } else if (weebits != 0) {
index = BITS_TO_INDEX[weebits]; index = BITS_TO_INDEX[weebits];
if (index != -1) { if (index != -1) {
indexes.add(Integer.valueOf(index)); indexes.add(index);
} }
weebits = 0; weebits = 0;
} }
@@ -265,31 +322,33 @@ public class AutoFringer
if (weebits != 0) { if (weebits != 0) {
index = BITS_TO_INDEX[weebits]; index = BITS_TO_INDEX[weebits];
if (index != -1) { if (index != -1) {
indexes.add(Integer.valueOf(index)); indexes.add(index);
} }
} }
int[] ret = new int[indexes.size()]; int[] ret = new int[indexes.size()];
for (int ii=0; ii < ret.length; ii++) { for (int ii = 0; ii < ret.length; ii++) {
ret[ii] = ((Integer) indexes.get(ii)).intValue(); ret[ii] = indexes.get(ii);
} }
return ret; return ret;
} }
/** /**
* Allow subclasses to apply arbitrary modifications to tileset ids for * Allow subclasses to apply arbitrary modifications to tileset ids for whatever nefarious
* whatever nefarious purposes they may have. * purposes they may have.
*/ */
protected int adjustTileSetId (int tileSetId) { protected int adjustTileSetId (int tileSetId)
{
// by default, nothing. // by default, nothing.
return tileSetId; return tileSetId;
} }
/** /**
* A record for holding information about a particular fringe as we're * A record for holding information about a particular fringe as we're computing what it will
* computing what it will look like. * look like.
*/ */
static protected class FringerRec implements Comparable static protected class FringerRec
implements Comparable<FringerRec>
{ {
int baseset; int baseset;
int priority; int priority;
@@ -301,15 +360,16 @@ public class AutoFringer
priority = pri; priority = pri;
} }
public int compareTo (Object o) public int compareTo (FringerRec o)
{ {
return priority - ((FringerRec) o).priority; return priority - o.priority;
} }
@Override
public String toString () public String toString ()
{ {
return "[base=" + baseset + ", pri=" + priority + return "[base=" + baseset + ", pri=" + priority + ", bits="
", bits=" + Integer.toString(bits, 16) + "]"; + Integer.toString(bits, 16) + "]";
} }
} }
@@ -327,8 +387,7 @@ public class AutoFringer
protected static final int NUM_FRINGEBITS = 8; protected static final int NUM_FRINGEBITS = 8;
// A matrix mapping adjacent tiles to which fringe bits // A matrix mapping adjacent tiles to which fringe bits they affect.
// they affect.
// (x and y are offset by +1, since we can't have -1 as an array index) // (x and y are offset by +1, since we can't have -1 as an array index)
// again, see docs/miso/fringebits.png // again, see docs/miso/fringebits.png
// //
@@ -339,9 +398,8 @@ public class AutoFringer
}; };
/** /**
* The fringe tiles we use. These are the 17 possible tiles made * The fringe tiles we use. These are the 17 possible tiles made up of continuous fringebits
* up of continuous fringebits sections. * sections. Huh? see docs/miso/fringebits.png
* Huh? see docs/miso/fringebits.png
*/ */
protected static final int[] FRINGETILES = { protected static final int[] FRINGETILES = {
SOUTHEAST, SOUTHEAST,