* 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;
import static com.threerings.miso.Log.log;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
@@ -40,11 +42,11 @@ import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.swing.Icon;
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.Sets;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.Controller;
import com.samskivert.swing.RadialMenu;
import com.samskivert.swing.RuntimeAdjust;
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.sprite.Sprite;
@@ -74,12 +77,11 @@ import com.threerings.miso.client.DirtyItemList.DirtyItem;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTile;
import com.threerings.miso.tile.AutoFringer.FringeTile;
import com.threerings.miso.util.MisoContext;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
import static com.threerings.miso.Log.log;
/**
* Renders a Miso scene for all to see.
*/
@@ -141,7 +143,7 @@ public class MisoScenePanel extends VirtualMediaPanel
{
_blocks.clear();
_vizobjs.clear();
_masks.clear();
_fringes.clear();
if (_dpanel != null) {
_dpanel.newScene();
}
@@ -366,7 +368,7 @@ public class MisoScenePanel extends VirtualMediaPanel
"[scene=" + StringUtil.shortClassName(this) +
", base=" + base.size() + "->" + (usage[0] / 1024) + "k" +
", fringe=" + fringe.size() + "->" + (usage[1] / 1024) + "k" +
", fmasks=" + _masks.size() + ", obj=" + object.size() +
", obj=" + object.size() +
"->" + (usage[2] / 1024) + "k" + "].");
}
@@ -858,7 +860,6 @@ public class MisoScenePanel extends VirtualMediaPanel
protected void computeInfluentialBounds ()
{
computeInfluentialBounds(_vbounds, _ibounds, _vibounds);
}
@@ -1405,7 +1406,7 @@ public class MisoScenePanel extends VirtualMediaPanel
/** Computes the fringe tile for the specified coordinate. */
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. */
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. */
protected DirtyItemList _dirtyItems = new DirtyItemList();
@@ -427,7 +427,6 @@ public class SceneBlock
Map<Tile.Key,ObjectTile> objects, long[] usage)
{
// account for our base tiles
MisoSceneModel model = _panel.getSceneModel();
for (int yy = 0; yy < _bounds.height; yy++) {
for (int xx = 0; xx < _bounds.width; xx++) {
int x = _bounds.x + xx, y = _bounds.y + yy;
@@ -21,40 +21,76 @@
package com.threerings.miso.tile;
import static com.threerings.miso.Log.log;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.samskivert.util.CheapIntMap;
import com.samskivert.util.QuickSort;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
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.
*/
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
* the supplied fringe configuration.
* Constructs an instance that will fringe according to the rules in the supplied fringe
* configuration.
*/
public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr,
TileManager tmgr)
public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr, TileManager tmgr)
{
_fringeconf = fringeconf;
_imgr = imgr;
@@ -64,15 +100,16 @@ public class AutoFringer
/**
* Returns the fringe configuration used by this fringer.
*/
public FringeConfiguration getFringeConf () {
public FringeConfiguration getFringeConf ()
{
return _fringeconf;
}
/**
* Compute and return the fringe tile to be inserted at the specified
* location.
* Compute and return the fringe tile to be inserted at the specified 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
int underset = adjustTileSetId(scene.getBaseTileId(col, row) >> 16);
@@ -114,12 +151,11 @@ public class AutoFringer
// we allow users to splash in the water.
if (passable && (btid > 0)) {
try {
BaseTile bt = (BaseTile) _tmgr.getTile(btid);
BaseTile bt = (BaseTile)_tmgr.getTile(btid);
passable = bt.isPassable();
} catch (NoSuchTileSetException nstse) {
log.warning("Autofringer couldn't find a base " +
"set while attempting to figure passability " +
"[error=" + nstse + "].");
log.warning("Autofringer couldn't find a base set while attempting to " +
"figure passability", nstse);
}
}
}
@@ -140,68 +176,90 @@ public class AutoFringer
}
}
BaseTile frTile = new BaseTile();
frTile.setPassable(passable);
composeFringeTile(frTile, frecs, masks, TileUtil.getTileHash(col, row));
return frTile;
return composeFringeTile(frecs, fringes, TileUtil.getTileHash(col, row), passable);
}
/**
* 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
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++) {
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++) {
try {
ftimg = getTileImage(ftimg, fringers[ii].baseset, indexes[jj], masks, hashValue);
img = getTileImage(img, tsr, fringers[ii].baseset, indexes[jj], hashValue);
} 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(ftimg));
frTile.setImage(new BufferedMirage(img));
fringes.put(frTile, frTile);
return frTile;
}
/**
* Retrieve or compose an image for the specified fringe.
*/
protected BufferedImage getTileImage (BufferedImage ftimg, int baseset, int index,
Map masks, int hashValue)
protected BufferedImage getTileImage (BufferedImage img,
FringeConfiguration.FringeTileSetRecord tsr, int baseset, int index, int hashValue)
throws NoSuchTileSetException
{
FringeConfiguration.FringeTileSetRecord tsr = _fringeconf.getFringe(baseset, hashValue);
int fringeset = tsr.fringe_tsid;
TileSet fset = _tmgr.getTileSet(fringeset);
if (!tsr.mask) {
// oh good, this is easy
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..
Long maskkey = Long.valueOf((((long) baseset) << 32) + (fringeset << 16) + index);
BufferedImage img = (BufferedImage)masks.get(maskkey);
if (img == null) {
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));
// otherwise, it's a mask..
BufferedImage fsrc = _tmgr.getTileSet(fringeset).getRawTileImage(index);
BufferedImage bsrc = _tmgr.getTileSet(baseset).getRawTileImage(0);
BufferedImage mask = ImageUtil.composeMaskedImage(_imgr, fsrc, bsrc);
return ftimg;
return stampTileImage(mask, img, mask.getWidth(null), mask.getHeight(null));
}
/** 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
if (ftimg == null) {
@@ -247,17 +305,16 @@ public class AutoFringer
return new int[0];
}
ArrayList indexes = new ArrayList();
ArrayList<Integer> indexes = Lists.newArrayList();
int weebits = 0;
for (int ii=(start + 1) % NUM_FRINGEBITS; ii != start;
ii = (ii + 1) % NUM_FRINGEBITS) {
for (int ii = (start + 1) % NUM_FRINGEBITS; ii != start; ii = (ii + 1) % NUM_FRINGEBITS) {
if (((1 << ii) & bits) != 0) {
weebits |= (1 << ii);
} else if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
indexes.add(index);
}
weebits = 0;
}
@@ -265,31 +322,33 @@ public class AutoFringer
if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
indexes.add(index);
}
}
int[] ret = new int[indexes.size()];
for (int ii=0; ii < ret.length; ii++) {
ret[ii] = ((Integer) indexes.get(ii)).intValue();
for (int ii = 0; ii < ret.length; ii++) {
ret[ii] = indexes.get(ii);
}
return ret;
}
/**
* Allow subclasses to apply arbitrary modifications to tileset ids for
* whatever nefarious purposes they may have.
* Allow subclasses to apply arbitrary modifications to tileset ids for whatever nefarious
* purposes they may have.
*/
protected int adjustTileSetId (int tileSetId) {
protected int adjustTileSetId (int tileSetId)
{
// by default, nothing.
return tileSetId;
}
/**
* A record for holding information about a particular fringe as we're
* computing what it will look like.
* A record for holding information about a particular fringe as we're computing what it will
* look like.
*/
static protected class FringerRec implements Comparable
static protected class FringerRec
implements Comparable<FringerRec>
{
int baseset;
int priority;
@@ -301,15 +360,16 @@ public class AutoFringer
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 ()
{
return "[base=" + baseset + ", pri=" + priority +
", bits=" + Integer.toString(bits, 16) + "]";
return "[base=" + baseset + ", pri=" + priority + ", bits="
+ Integer.toString(bits, 16) + "]";
}
}
@@ -327,8 +387,7 @@ public class AutoFringer
protected static final int NUM_FRINGEBITS = 8;
// A matrix mapping adjacent tiles to which fringe bits
// they affect.
// A matrix mapping adjacent tiles to which fringe bits they affect.
// (x and y are offset by +1, since we can't have -1 as an array index)
// 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
* up of continuous fringebits sections.
* Huh? see docs/miso/fringebits.png
* The fringe tiles we use. These are the 17 possible tiles made up of continuous fringebits
* sections. Huh? see docs/miso/fringebits.png
*/
protected static final int[] FRINGETILES = {
SOUTHEAST,