From 7ace9ac32071049a99b07b97573b31d30fd4d74b Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Mon, 23 Jun 2008 21:59:16 +0000 Subject: [PATCH] * 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 --- .../miso/client/MisoScenePanel.java | 29 ++- .../threerings/miso/client/SceneBlock.java | 1 - .../com/threerings/miso/tile/AutoFringer.java | 190 ++++++++++++------ 3 files changed, 142 insertions(+), 78 deletions(-) diff --git a/src/java/com/threerings/miso/client/MisoScenePanel.java b/src/java/com/threerings/miso/client/MisoScenePanel.java index c2f44201..21595050 100644 --- a/src/java/com/threerings/miso/client/MisoScenePanel.java +++ b/src/java/com/threerings/miso/client/MisoScenePanel.java @@ -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 _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 _fringes = new WeakHashMap(); /** The dirty sprites and objects that need to be re-painted. */ protected DirtyItemList _dirtyItems = new DirtyItemList(); diff --git a/src/java/com/threerings/miso/client/SceneBlock.java b/src/java/com/threerings/miso/client/SceneBlock.java index d7d2ed8e..e93f3d6e 100644 --- a/src/java/com/threerings/miso/client/SceneBlock.java +++ b/src/java/com/threerings/miso/client/SceneBlock.java @@ -427,7 +427,6 @@ public class SceneBlock Map 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; diff --git a/src/java/com/threerings/miso/tile/AutoFringer.java b/src/java/com/threerings/miso/tile/AutoFringer.java index 813706ff..59d77a5a 100644 --- a/src/java/com/threerings/miso/tile/AutoFringer.java +++ b/src/java/com/threerings/miso/tile/AutoFringer.java @@ -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 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 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 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 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 { 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,