From ee73dd43de2b864b14dc32bc82014910c50dad13 Mon Sep 17 00:00:00 2001 From: Mike Thomas Date: Tue, 13 Nov 2007 01:12:18 +0000 Subject: [PATCH] New type of ResourceBundle - NetworkResourceBundle - This bundle grabs its resources over HTTP from a root URL rather than from a local jar file. To make use of this, we need a way to put all the contents of the bundle into an appropriate directory instead of a jar, including its metadata, so some new Bundler Tasks were created to do this. Finally, allow tile set trimming to be done to a non-raw image format through passing an optional imgFormat parameter. If no parameter is passed, it'll default ot the old behavior of using raw/FastIO. Note that to use network bundles, you can set the set_type in the resource manager.properties file. (e.g. "resource.set_type.tilesets = network") If no set_type is set for a resource set, it defaults to a normal FileResourceBundle git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@343 ed5b42cb-e716-0410-a449-f6a68f950b19 --- .../bundle/tools/ComponentBundlerTask.java | 59 +++++-- .../tools/DirectoryComponentBundlerTask.java | 65 +++++++ .../tools/DirectoryMetadataBundlerTask.java | 51 ++++++ .../bundle/tools/MetadataBundlerTask.java | 47 +++-- .../media/tile/TrimmedObjectTileSet.java | 32 ++-- .../threerings/media/tile/TrimmedTileSet.java | 22 ++- .../bundle/tools/DirectoryTileSetBundler.java | 163 ++++++++++++++++++ .../tools/DirectoryTileSetBundlerTask.java | 72 ++++++++ .../tile/bundle/tools/TileSetBundler.java | 30 +++- .../tile/bundle/tools/TileSetBundlerTask.java | 23 ++- .../media/tile/util/TileSetTrimmer.java | 20 ++- .../resource/NetworkResourceBundle.java | 92 ++++++++++ .../threerings/resource/ResourceManager.java | 110 +++++++++--- 13 files changed, 713 insertions(+), 73 deletions(-) create mode 100644 src/java/com/threerings/cast/bundle/tools/DirectoryComponentBundlerTask.java create mode 100644 src/java/com/threerings/cast/bundle/tools/DirectoryMetadataBundlerTask.java create mode 100644 src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundler.java create mode 100644 src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundlerTask.java create mode 100644 src/java/com/threerings/resource/NetworkResourceBundle.java diff --git a/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java index 7d8036b8..fa79e055 100644 --- a/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java +++ b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java @@ -33,6 +33,7 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectOutputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -166,9 +167,7 @@ public class ComponentBundlerTask extends Task try { // make sure we can create our bundle file - FileOutputStream fout = new FileOutputStream(_target); - JarOutputStream jout = new JarOutputStream(fout); - jout.setLevel(Deflater.BEST_COMPRESSION); + OutputStream fout = createOutputStream(_target); // we'll fill this with component id to tuple mappings HashIntMap mapping = new HashIntMap(); @@ -206,7 +205,7 @@ public class ComponentBundlerTask extends Task mapping.put(cid, new Tuple(info[0], info[1])); // process and store the main component image - processComponent(info, aset, cfile, jout); + processComponent(info, aset, cfile, fout); // pick up any auxiliary images as well like the shadow or // crop files @@ -217,20 +216,20 @@ public class ComponentBundlerTask extends Task FileUtil.resuffix(cfile, ext, AUX_EXTS[aa] + ext)); if (afile.exists()) { info[2] = action + AUX_EXTS[aa]; - processComponent(info, aset, afile, jout); + processComponent(info, aset, afile, fout); } } } } // write our mapping table to the jar file as well - jout.putNextEntry(new JarEntry(BundleUtil.COMPONENTS_PATH)); - ObjectOutputStream oout = new ObjectOutputStream(jout); + fout = nextEntry(fout, BundleUtil.COMPONENTS_PATH); + ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(mapping); oout.flush(); // seal up our jar file - jout.close(); + fout.close(); } catch (IOException ioe) { String errmsg = "Unable to create component bundle."; @@ -246,13 +245,13 @@ public class ComponentBundlerTask extends Task } protected void processComponent ( - String[] info, TileSet aset, File cfile, JarOutputStream jout) + String[] info, TileSet aset, File cfile, OutputStream fout) throws IOException, BuildException { // construct the path that'll go in the jar file String ipath = composePath( info, BundleUtil.IMAGE_EXTENSION); - jout.putNextEntry(new JarEntry(ipath)); + fout = nextEntry(fout, ipath); // create a trimmed tileset based on the source action tileset and // stuff the new trimmed image into the jar file at the same time @@ -260,7 +259,7 @@ public class ComponentBundlerTask extends Task TrimmedTileSet tset = null; try { - tset = TrimmedTileSet.trimTileSet(aset, jout); + tset = trim(aset, fout); tset.setImagePath(ipath); } catch (Throwable t) { System.err.println( @@ -278,8 +277,9 @@ public class ComponentBundlerTask extends Task if (tset != null) { String tpath = composePath( info, BundleUtil.TILESET_EXTENSION); - jout.putNextEntry(new JarEntry(tpath)); - ObjectOutputStream oout = new ObjectOutputStream(jout); + fout = nextEntry(fout, tpath); + + ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(tset); oout.flush(); } @@ -417,7 +417,7 @@ public class ComponentBundlerTask extends Task digester.addSetProperties("actions" + ActionRuleSet.ACTION_PATH); addTileSetRuleSet(digester, new SwissArmyTileSetRuleSet()); addTileSetRuleSet(digester, new UniformTileSetRuleSet("/uniformTileset")); - + HashMap actsets = new ActionMap(); digester.push(actsets); @@ -439,6 +439,37 @@ public class ComponentBundlerTask extends Task return actsets; } + /** + * Creates the base output stream to which to write our bundle's files. + */ + protected OutputStream createOutputStream (File target) + throws IOException + { + JarOutputStream jout = new JarOutputStream(new FileOutputStream(target)); + jout.setLevel(Deflater.BEST_COMPRESSION); + return jout; + } + + /** + * Advances to the next named entry in the bundle and returns the stream to which to write + * that entry. + */ + protected OutputStream nextEntry (OutputStream lastEntry, String path) + throws IOException + { + ((JarOutputStream)lastEntry).putNextEntry(new JarEntry(path)); + return lastEntry; + } + + /** + * Converts the tileset to a trimmed tile set and saves it at the specified location. + */ + protected TrimmedTileSet trim (TileSet aset, OutputStream fout) + throws IOException + { + return TrimmedTileSet.trimTileSet(aset, fout); + } + /** Used when parsing action tilesets. */ public static class ActionMap extends HashMap { diff --git a/src/java/com/threerings/cast/bundle/tools/DirectoryComponentBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/DirectoryComponentBundlerTask.java new file mode 100644 index 00000000..02d17dbc --- /dev/null +++ b/src/java/com/threerings/cast/bundle/tools/DirectoryComponentBundlerTask.java @@ -0,0 +1,65 @@ +// +// $Id: ComponentBundlerTask.java 281 2007-08-02 23:18:16Z charlie $ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 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.cast.bundle.tools; + +import java.io.File; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import com.threerings.media.tile.TileSet; + +import com.threerings.media.tile.TrimmedTileSet; + +/** + * Creates all the information for a component bundle but places it into a specified directory + * rather than a bundle jar file. + */ +public class DirectoryComponentBundlerTask extends ComponentBundlerTask +{ + @Override // documentation inherited. + protected OutputStream createOutputStream (File target) + throws IOException + { + // Since we recreate our output stream on every entry, we don't need one to start with. + return null; + } + + @Override // documentation inherited + protected OutputStream nextEntry (OutputStream lastEntry, String path) + throws IOException + { + File file = new File(_target, path); + file.getParentFile().mkdirs(); + return new FileOutputStream(file); + } + + @Override // documentation inherited + protected TrimmedTileSet trim (TileSet aset, OutputStream fout) + throws IOException + { + return TrimmedTileSet.trimTileSet(aset, fout, "png"); + } + + +} diff --git a/src/java/com/threerings/cast/bundle/tools/DirectoryMetadataBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/DirectoryMetadataBundlerTask.java new file mode 100644 index 00000000..65fbc4ec --- /dev/null +++ b/src/java/com/threerings/cast/bundle/tools/DirectoryMetadataBundlerTask.java @@ -0,0 +1,51 @@ +// +// $Id: ComponentBundlerTask.java 281 2007-08-02 23:18:16Z charlie $ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 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.cast.bundle.tools; + +import java.io.File; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Creates all the information that would be in a metadata bundle and place it in a directory. + */ +public class DirectoryMetadataBundlerTask extends MetadataBundlerTask +{ + @Override // documentation inherited. + protected OutputStream createOutputStream (File target) + throws IOException + { + // Since we recreate our output stream on every entry, we don't need one to start with. + return null; + } + + @Override // documentation inherited + protected OutputStream nextEntry (OutputStream lastEntry, String path) + throws IOException + { + File file = new File(_target, path); + file.getParentFile().mkdirs(); + return new FileOutputStream(file); + } +} diff --git a/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java index 8137bc2f..12d4c247 100644 --- a/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java +++ b/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java @@ -28,6 +28,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.jar.JarEntry; @@ -92,9 +93,8 @@ public class MetadataBundlerTask extends Task "file via the 'target' attribute."); // make sure we can write to the target bundle file - FileOutputStream fout = null; + OutputStream fout = null; try { - fout = new FileOutputStream(_target); // parse our metadata Tuple tuple = parseActions(); @@ -102,33 +102,28 @@ public class MetadataBundlerTask extends Task HashMap actionSets = (HashMap)tuple.right; HashMap classes = parseClasses(); - // and create the bundle file - JarOutputStream jout = new JarOutputStream(fout); - jout.setLevel(Deflater.BEST_COMPRESSION); + fout = createOutputStream(_target); // throw the serialized actions table in there - JarEntry aentry = new JarEntry(BundleUtil.ACTIONS_PATH); - jout.putNextEntry(aentry); - ObjectOutputStream oout = new ObjectOutputStream(jout); + fout = nextEntry(fout, BundleUtil.ACTIONS_PATH); + ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(actions); oout.flush(); // throw the serialized action tilesets table in there - JarEntry sentry = new JarEntry(BundleUtil.ACTION_SETS_PATH); - jout.putNextEntry(sentry); - oout = new ObjectOutputStream(jout); + fout = nextEntry(fout, BundleUtil.ACTION_SETS_PATH); + oout = new ObjectOutputStream(fout); oout.writeObject(actionSets); oout.flush(); // throw the serialized classes table in there - JarEntry centry = new JarEntry(BundleUtil.CLASSES_PATH); - jout.putNextEntry(centry); - oout = new ObjectOutputStream(jout); + fout = nextEntry(fout, BundleUtil.CLASSES_PATH); + oout = new ObjectOutputStream(fout); oout.writeObject(classes); oout.flush(); // close it up and we're done - jout.close(); + fout.close(); } catch (IOException ioe) { String errmsg = "Unable to output to target bundle " + @@ -146,6 +141,28 @@ public class MetadataBundlerTask extends Task } } + /** + * Creates the base output stream to which to write our bundle's files. + */ + protected OutputStream createOutputStream (File target) + throws IOException + { + JarOutputStream jout = new JarOutputStream(new FileOutputStream(target)); + jout.setLevel(Deflater.BEST_COMPRESSION); + return jout; + } + + /** + * Advances to the next named entry in the bundle and returns the stream to which to write + * that entry. + */ + protected OutputStream nextEntry (OutputStream lastEntry, String path) + throws IOException + { + ((JarOutputStream)lastEntry).putNextEntry(new JarEntry(path)); + return lastEntry; + } + /** * Configures ruleSet and hooks it into digester. */ diff --git a/src/java/com/threerings/media/tile/TrimmedObjectTileSet.java b/src/java/com/threerings/media/tile/TrimmedObjectTileSet.java index c746b095..3a49de4e 100644 --- a/src/java/com/threerings/media/tile/TrimmedObjectTileSet.java +++ b/src/java/com/threerings/media/tile/TrimmedObjectTileSet.java @@ -30,6 +30,7 @@ import com.samskivert.util.ListUtil; import com.samskivert.util.StringUtil; import com.threerings.media.image.Colorization; +import com.threerings.resource.FastImageIO; import com.threerings.media.tile.util.TileSetTrimmer; /** @@ -87,7 +88,7 @@ public class TrimmedObjectTileSet extends TileSet { return (_bits == null) ? null : _bits[tileIdx].constraints; } - + /** * Checks whether the tile at the specified index has the given constraint. */ @@ -96,7 +97,7 @@ public class TrimmedObjectTileSet extends TileSet return (_bits == null || _bits[tileIdx].constraints == null) ? false : ListUtil.contains(_bits[tileIdx].constraints, constraint); } - + // documentation inherited from interface RecolorableTileSet public String[] getColorizations () { @@ -167,14 +168,25 @@ public class TrimmedObjectTileSet extends TileSet } /** - * Creates a trimmed object tileset from the supplied source object tileset. The image path - * must be set by hand to the appropriate path based on where the image data that is written to - * the destImage parameter is actually stored on the file system. See {@link - * TileSetTrimmer#trimTileSet} for further information. + * Convenience function to trim the tile set to a file using FastImageIO. */ public static TrimmedObjectTileSet trimObjectTileSet ( ObjectTileSet source, OutputStream destImage) throws IOException + { + return trimObjectTileSet(source, destImage, FastImageIO.FILE_SUFFIX); + } + + /** + * Creates a trimmed object tileset from the supplied source object tileset. The image path + * must be set by hand to the appropriate path based on where the image data that is written to + * the destImage parameter is actually stored on the file system. If imgFormat is + * null, uses FastImageIO to save the file. See {@link TileSetTrimmer#trimTileSet} for further + * information. + */ + public static TrimmedObjectTileSet trimObjectTileSet ( + ObjectTileSet source, OutputStream destImage, String imgFormat) + throws IOException { final TrimmedObjectTileSet tset = new TrimmedObjectTileSet(); tset.setName(source.getName()); @@ -199,10 +211,10 @@ public class TrimmedObjectTileSet extends TileSet // fill in the original object metrics for (int ii = 0; ii < tcount; ii++) { tset._ometrics[ii] = new Rectangle(); - if (source._xorigins != null) { + if (source._xorigins != null) { tset._ometrics[ii].x = source._xorigins[ii]; } - if (source._yorigins != null) { + if (source._yorigins != null) { tset._ometrics[ii].y = source._yorigins[ii]; } tset._ometrics[ii].width = source._owidths[ii]; @@ -234,7 +246,7 @@ public class TrimmedObjectTileSet extends TileSet tset._bounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight); } }; - TileSetTrimmer.trimTileSet(source, destImage, tmr); + TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat); // Log.info("Trimmed object tileset [bounds=" + StringUtil.toString(tset._bounds) + // ", metrics=" + StringUtil.toString(tset._ometrics) + "]."); @@ -259,7 +271,7 @@ public class TrimmedObjectTileSet extends TileSet /** The constraints associated with this object. */ public String[] constraints; - + /** Generates a string representation of this instance. */ public String toString () { diff --git a/src/java/com/threerings/media/tile/TrimmedTileSet.java b/src/java/com/threerings/media/tile/TrimmedTileSet.java index c4f0a1ad..10991050 100644 --- a/src/java/com/threerings/media/tile/TrimmedTileSet.java +++ b/src/java/com/threerings/media/tile/TrimmedTileSet.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.OutputStream; import com.threerings.media.image.Colorization; +import com.threerings.resource.FastImageIO; import com.threerings.media.tile.util.TileSetTrimmer; /** @@ -62,13 +63,24 @@ public class TrimmedTileSet extends TileSet } /** - * Creates a trimmed tileset from the supplied source tileset. The image path must be set by - * hand to the appropriate path based on where the image data that is written to the - * destImage parameter is actually stored on the file system. See {@link - * TileSetTrimmer#trimTileSet} for further information. + * Convenience function to trim the tile set and save it using FastImageIO. */ public static TrimmedTileSet trimTileSet (TileSet source, OutputStream destImage) throws IOException + { + return trimTileSet(source, destImage, FastImageIO.FILE_SUFFIX); + } + + /** + * Creates a trimmed tileset from the supplied source tileset. The image path must be set by + * hand to the appropriate path based on where the image data that is written to the + * destImage parameter is actually stored on the file system. The image format + * indicateds how the resulting image should be saved. If null, we save using FastImageIO + * See {@link TileSetTrimmer#trimTileSet} for further information. + */ + public static TrimmedTileSet trimTileSet (TileSet source, OutputStream destImage, + String imgFormat) + throws IOException { final TrimmedTileSet tset = new TrimmedTileSet(); tset.setName(source.getName()); @@ -90,7 +102,7 @@ public class TrimmedTileSet extends TileSet tset._obounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight); } }; - TileSetTrimmer.trimTileSet(source, destImage, tmr); + TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat); return tset; } diff --git a/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundler.java b/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundler.java new file mode 100644 index 00000000..507281b9 --- /dev/null +++ b/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundler.java @@ -0,0 +1,163 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 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.tile.bundle.tools; + +import java.util.Iterator; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; + +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; + +import org.apache.commons.io.IOUtils; + +import com.threerings.media.Log; +import com.threerings.media.tile.ImageProvider; +import com.threerings.media.tile.ObjectTileSet; +import com.threerings.media.tile.TileSet; +import com.threerings.media.tile.TrimmedObjectTileSet; +import com.threerings.media.tile.bundle.BundleUtil; +import com.threerings.media.tile.bundle.TileSetBundle; + +public class DirectoryTileSetBundler extends TileSetBundler +{ + public DirectoryTileSetBundler (File configFile) + throws IOException + { + super(configFile); + } + + public DirectoryTileSetBundler (String configPath) + throws IOException + { + super(configPath); + } + + /** + * Finish the creation of a tileset bundle jar file. + * + * @param target the tileset bundle file that will be created. + * @param bundle contains the tilesets we'd like to save out to the bundle. + * @param improv the image provider. + * @param imageBase the base directory for getting images for non + * ObjectTileSet tilesets. + */ + public boolean createBundle ( + File target, TileSetBundle bundle, ImageProvider improv, String imageBase) + throws IOException + { + try { + // write all of the image files to the bundle's target path, converting the + // tilesets to trimmed tilesets in the process + Iterator iditer = bundle.enumerateTileSetIds(); + while (iditer.hasNext()) { + int tileSetId = ((Integer)iditer.next()).intValue(); + TileSet set = bundle.getTileSet(tileSetId); + String imagePath = set.getImagePath(); + + // sanity checks + if (imagePath == null) { + Log.warning("Tileset contains no image path " + + "[set=" + set + "]. It ain't gonna work."); + continue; + } + + // if this is an object tileset, we can't trim it! + if (set instanceof ObjectTileSet) { + // set the tileset up with an image provider; we + // need to do this so that we can trim it! + set.setImageProvider(improv); + + // we're going to trim it, so adjust the path + imagePath = adjustImagePath(imagePath); + + try { + // create a trimmed object tileset, which will + // write the trimmed tileset image to the destination + // output stream + File outFile = new File(target, imagePath); + outFile.getParentFile().mkdirs(); + FileOutputStream fout = new FileOutputStream(outFile); + TrimmedObjectTileSet tset = + TrimmedObjectTileSet.trimObjectTileSet( + (ObjectTileSet)set, fout, "png"); + tset.setImagePath(imagePath); + // replace the original set with the trimmed + // tileset in the tileset bundle + bundle.addTileSet(tileSetId, tset); + + } catch (Exception e) { + e.printStackTrace(System.err); + + String msg = "Error adding tileset to bundle " + imagePath + + ", " + set.getName() + ": " + e; + throw (IOException) new IOException(msg).initCause(e); + } + + } else { + // read the image file and convert it to our custom + // format in the bundle + File ifile = new File(imageBase, imagePath); + try { + BufferedImage image = ImageIO.read(ifile); + File outFile = new File(target, imagePath); + outFile.getParentFile().mkdirs(); + FileOutputStream fout = new FileOutputStream(outFile); + FileInputStream imgin = new FileInputStream(ifile); + IOUtils.copy(imgin, fout); + } catch (Exception e) { + String msg = "Failure bundling image " + ifile + + ": " + e; + throw (IOException) new IOException(msg).initCause(e); + } + } + } + + // now write a serialized representation of the tileset bundle + // object to the bundle jar file + File outFile = new File(target, BundleUtil.METADATA_PATH); + outFile.getParentFile().mkdirs(); + FileOutputStream fout = new FileOutputStream(outFile); + ObjectOutputStream oout = new ObjectOutputStream(fout); + oout.writeObject(bundle); + oout.flush(); + + return true; + + } catch (Exception e) { + String errmsg = "Failed to create bundle " + target + ": " + e; + throw (IOException) new IOException(errmsg).initCause(e); + } + } + + @Override // documentation inherited + protected boolean maySkipIfNewer () + { + // Can't skip since we're copying individual files. + return false; + } +} diff --git a/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundlerTask.java b/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundlerTask.java new file mode 100644 index 00000000..4d499837 --- /dev/null +++ b/src/java/com/threerings/media/tile/bundle/tools/DirectoryTileSetBundlerTask.java @@ -0,0 +1,72 @@ +// +// $Id: TileSetBundlerTask.java 158 2007-02-24 00:38:17Z mdb $ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 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.tile.bundle.tools; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +import com.threerings.media.tile.tools.MapFileTileSetIDBroker; + +/** + * Ant task for creating tileset bundles that are placed in a specified directory instead + * of wrapped up in a fancy jar file. + */ +public class DirectoryTileSetBundlerTask extends TileSetBundlerTask +{ + /** + * Sets the path to the directory in which we'll be putting our bundle's files. + */ + public void setDeployDir (File deployDir) + { + _deployDir = deployDir; + } + + @Override // documentation inherited + public void execute () throws BuildException + { + ensureSet(_deployDir, "Must specify the path to which we want to deploy tileset files."); + super.execute(); + } + + @Override // documentation inherited + protected TileSetBundler createBundler () + throws IOException + { + return new DirectoryTileSetBundler(_config); + } + + @Override // documentation inherited + protected String getTargetPath (File fromDir, String path) + { + File xmlFile = new File(path.replace(fromDir.getPath(), _deployDir.getPath())); + return xmlFile.getParent(); + } + + /** The directory in which we want to place our tile set files for deployment. */ + protected File _deployDir; +} diff --git a/src/java/com/threerings/media/tile/bundle/tools/TileSetBundler.java b/src/java/com/threerings/media/tile/bundle/tools/TileSetBundler.java index 28a7deaf..2143c113 100644 --- a/src/java/com/threerings/media/tile/bundle/tools/TileSetBundler.java +++ b/src/java/com/threerings/media/tile/bundle/tools/TileSetBundler.java @@ -310,7 +310,7 @@ public class TileSetBundler } // see if our newest file is newer than the tileset bundle - if (newest < target.lastModified()) { + if (newest < target.lastModified() && maySkipIfNewer()) { return false; } @@ -325,6 +325,14 @@ public class TileSetBundler return createBundle(target, bundle, improv, bundleDesc.getParent()); } + /** + * Whether we're allowed to skip creation of the bundle if we're not out of date. + */ + protected boolean maySkipIfNewer () + { + return true; + } + /** * Finish the creation of a tileset bundle jar file. * @@ -334,7 +342,23 @@ public class TileSetBundler * @param imageBase the base directory for getting images for non * ObjectTileSet tilesets. */ - public static boolean createBundle ( + public boolean createBundle ( + File target, TileSetBundle bundle, ImageProvider improv, String imageBase) + throws IOException + { + return createBundleJar(target, bundle, improv, imageBase); + } + + /** + * Create a tileset bundle jar file. + * + * @param target the tileset bundle file that will be created. + * @param bundle contains the tilesets we'd like to save out to the bundle. + * @param improv the image provider. + * @param imageBase the base directory for getting images for non + * ObjectTileSet tilesets. + */ + public static boolean createBundleJar ( File target, TileSetBundle bundle, ImageProvider improv, String imageBase) throws IOException { @@ -385,7 +409,7 @@ public class TileSetBundler } catch (Exception e) { e.printStackTrace(System.err); - String msg = "Error adding tileset to bundle " + imagePath + + String msg = "Error adding tileset to bundle " + imagePath + ", " + set.getName() + ": " + e; throw (IOException) new IOException(msg).initCause(e); } diff --git a/src/java/com/threerings/media/tile/bundle/tools/TileSetBundlerTask.java b/src/java/com/threerings/media/tile/bundle/tools/TileSetBundlerTask.java index 77dd1d6e..70e362ec 100644 --- a/src/java/com/threerings/media/tile/bundle/tools/TileSetBundlerTask.java +++ b/src/java/com/threerings/media/tile/bundle/tools/TileSetBundlerTask.java @@ -22,6 +22,7 @@ package com.threerings.media.tile.bundle.tools; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import org.apache.tools.ant.BuildException; @@ -76,7 +77,7 @@ public class TileSetBundlerTask extends Task File cfile = null; try { // create a tileset bundler - TileSetBundler bundler = new TileSetBundler(_config); + TileSetBundler bundler = createBundler(); // create our tileset id broker MapFileTileSetIDBroker broker = @@ -102,8 +103,7 @@ public class TileSetBundlerTask extends Task "Config file should end with .xml."); continue; } - String bpath = - cpath.substring(0, cpath.length()-4) + ".jar"; + String bpath = getTargetPath(fromDir, cpath); File bfile = new File(bpath); // create the bundle @@ -127,6 +127,23 @@ public class TileSetBundlerTask extends Task } } + /** + * Create the bundler to use during creation. + */ + protected TileSetBundler createBundler () + throws IOException + { + return new TileSetBundler(_config); + } + + /** + * Returns the target path in which our bundler will write the tile set. + */ + protected String getTargetPath (File fromDir, String path) + { + return path.substring(0, path.length()-4) + ".jar"; + } + protected void ensureSet (Object value, String errmsg) throws BuildException { diff --git a/src/java/com/threerings/media/tile/util/TileSetTrimmer.java b/src/java/com/threerings/media/tile/util/TileSetTrimmer.java index f5cb766e..b4d84648 100644 --- a/src/java/com/threerings/media/tile/util/TileSetTrimmer.java +++ b/src/java/com/threerings/media/tile/util/TileSetTrimmer.java @@ -22,6 +22,7 @@ package com.threerings.media.tile.util; import java.awt.Rectangle; +import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.awt.image.RasterFormatException; @@ -68,6 +69,16 @@ public class TileSetTrimmer int trimWidth, int trimHeight); } + /** + * Convenience function to trim the tile set using FastImageIO to save the result. + */ + public static void trimTileSet ( + TileSet source, OutputStream destImage, TrimMetricsReceiver tmr) + throws IOException + { + trimTileSet(source, destImage, tmr, FastImageIO.FILE_SUFFIX); + } + /** * Generates a trimmed tileset image from the supplied source * tileset. The source tileset must be configured with an image @@ -80,9 +91,10 @@ public class TileSetTrimmer * will be written. * @param tmr a callback object that will be used to inform the caller * of the trimmed tile metrics. + * @param imgFormat the format in which to write the image file - or if null, use FastImageIO. */ public static void trimTileSet ( - TileSet source, OutputStream destImage, TrimMetricsReceiver tmr) + TileSet source, OutputStream destImage, TrimMetricsReceiver tmr, String imgFormat) throws IOException { int tcount = source.getTileCount(); @@ -140,6 +152,10 @@ public class TileSetTrimmer } // write out trimmed image - FastImageIO.write(image, destImage); + if (imgFormat == null || FastImageIO.FILE_SUFFIX.equals(imgFormat)) { + FastImageIO.write(image, destImage); + } else { + ImageIO.write(image, imgFormat, destImage); + } } } diff --git a/src/java/com/threerings/resource/NetworkResourceBundle.java b/src/java/com/threerings/resource/NetworkResourceBundle.java new file mode 100644 index 00000000..6acfa7ce --- /dev/null +++ b/src/java/com/threerings/resource/NetworkResourceBundle.java @@ -0,0 +1,92 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 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.resource; + +import java.io.InputStream; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +import java.awt.image.BufferedImage; + +/** + * Resource bundle that retrieves its contents via HTTP over the network from a root URL. + */ +public class NetworkResourceBundle extends ResourceBundle +{ + public NetworkResourceBundle (String root, String path) + { + try { + _bundleURL = new URL(root + path); + } catch (MalformedURLException mue) { + Log.warning("Created malformed URL for resource. [root=" + root + ", path=" + path); + } + _ident = path; + } + + @Override // documentation inherited + public String getIdent () + { + return _ident; + } + + @Override // documentation inherited + public InputStream getResource (String path) + throws IOException + { + URL resourceUrl = new URL(_bundleURL, path); + HttpURLConnection ucon = null; + try { + ucon = (HttpURLConnection) resourceUrl.openConnection(); + } catch (IOException ioe) { + } + + if (ucon == null) { + return null; + } + try { + ucon.connect(); + return ucon.getInputStream(); + } catch (IOException ioe) { + return null; + } + } + + @Override // documentation inherited + public BufferedImage getImageResource (String path, boolean useFastIO) + throws IOException + { + InputStream in = getResource(path); + if (in == null) { + return null; + } + return ResourceManager.loadImage(in); + } + + /** Our identifier for this bundle. */ + protected String _ident; + + /** Our root url to the resources in this bundle. */ + protected URL _bundleURL; +} diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java index 607ada2d..301529be 100644 --- a/src/java/com/threerings/resource/ResourceManager.java +++ b/src/java/com/threerings/resource/ResourceManager.java @@ -162,7 +162,27 @@ public class ResourceManager */ public ResourceManager (String resourceRoot, ClassLoader loader) { - _rootPath = resourceRoot; + this(resourceRoot, null, loader); + } + + /** + * Creates a resource manager with a root path to resources over the network. See + * {@link #ResourceManager(String)} for further documentation. + */ + public ResourceManager (String resourceRoot, String networkResourceRoot) + { + this(resourceRoot, networkResourceRoot, ResourceManager.class.getClassLoader()); + } + + /** + * Creates a resource manager with a root path to resources over the network and the specified + * class loader via which to load classes. See {@link #ResourceManager(String)} for further + * documentation. + */ + public ResourceManager (String fileResourceRoot, String networkResourceRoot, ClassLoader loader) + { + _rootPath = fileResourceRoot; + _networkRootPath = networkResourceRoot; _loader = loader; // check a system property to determine if we should unpack our bundles, but don't freak @@ -239,16 +259,7 @@ public class ResourceManager } // load up our configuration - Properties config = new Properties(); - try { - config.load(new FileInputStream(new File(_rdir, configPath))); - } catch (Exception e) { - String errmsg = "Unable to load resource manager config [rdir=" + _rdir + - ", cpath=" + configPath + "]"; - Log.warning(errmsg + "."); - Log.logStackTrace(e); - throw new IOException(errmsg); - } + Properties config = loadConfig(configPath); // resolve the configured resource sets List dlist = new ArrayList(); @@ -259,7 +270,9 @@ public class ResourceManager continue; } String setName = key.substring(RESOURCE_SET_PREFIX.length()); - resolveResourceSet(setName, config.getProperty(key), dlist); + String resourceSetType = config.getProperty(RESOURCE_SET_TYPE_PREFIX + setName, + FILE_SET_TYPE); + resolveResourceSet(setName, config.getProperty(key), resourceSetType, dlist); } // if an observer was passed in, then we do not need to block the caller @@ -408,7 +421,14 @@ public class ResourceManager // first look for this resource in our default resource bundle for (ResourceBundle bundle : _default) { - in = bundle.getResource(path); + // Try a localized version first. + if (_localePrefix != null) { + in = bundle.getResource(PathUtil.appendPath(_localePrefix, path)); + } + // If that didn't work, try generic. + if (in == null) { + in = bundle.getResource(path); + } if (in != null) { return in; } @@ -462,7 +482,18 @@ public class ResourceManager { // first look for this resource in our default resource bundle for (ResourceBundle bundle : _default) { - BufferedImage image = bundle.getImageResource(path, false); + // try a localized version first + BufferedImage image = null; + if (_localePrefix != null) { + image = + bundle.getImageResource(PathUtil.appendPath(_localePrefix, path), false); + } + + // if we didn't find that, try generic + if (image == null) { + image = bundle.getImageResource(path, false); + } + if (image != null) { return image; } @@ -606,6 +637,25 @@ public class ResourceManager return (ResourceBundle[])_sets.get(name); } + /** + * Loads the configuration properties for our resource sets. + */ + protected Properties loadConfig (String configPath) + throws IOException + { + Properties config = new Properties(); + try { + config.load(new FileInputStream(new File(_rdir, configPath))); + } catch (Exception e) { + String errmsg = "Unable to load resource manager config [rdir=" + _rdir + + ", cpath=" + configPath + "]"; + Log.warning(errmsg + "."); + Log.logStackTrace(e); + throw new IOException(errmsg); + } + return config; + } + protected void initResourceDir (String resourceDir) { // if none was specified, check the resource_dir system property @@ -633,19 +683,25 @@ public class ResourceManager * Loads up a resource set based on the supplied definition information. */ protected void resolveResourceSet ( - String setName, String definition, List dlist) + String setName, String definition, String setType, List dlist) { List set = new ArrayList(); StringTokenizer tok = new StringTokenizer(definition, ":"); while (tok.hasMoreTokens()) { String path = tok.nextToken().trim(); - FileResourceBundle bundle = - new FileResourceBundle(getResourceFile(path), true, _unpack); - set.add(bundle); - if (bundle.isUnpacked() && bundle.sourceIsReady()) { - continue; + if (setType.equals(FILE_SET_TYPE)) { + FileResourceBundle bundle = + new FileResourceBundle(getResourceFile(path), true, _unpack); + set.add(bundle); + if (bundle.isUnpacked() && bundle.sourceIsReady()) { + continue; + } + dlist.add(bundle); + } else if (setType.equals(NETWORK_SET_TYPE)) { + NetworkResourceBundle bundle = + new NetworkResourceBundle(_networkRootPath, path); + set.add(bundle); } - dlist.add(bundle); } // convert our array list into an array and stick it in the table @@ -768,6 +824,9 @@ public class ResourceManager * classpath. */ protected String _rootPath; + /** The root path we give to network bundles for all resources they're interested in. */ + protected String _networkRootPath; + /** Whether or not to unpack our resource bundles. */ protected boolean _unpack; @@ -783,6 +842,15 @@ public class ResourceManager /** The prefix of configuration entries that describe a resource set. */ protected static final String RESOURCE_SET_PREFIX = "resource.set."; + /** The prefix of configuration entries that describe a resource set. */ + protected static final String RESOURCE_SET_TYPE_PREFIX = "resource.set_type."; + /** The name of the default resource set. */ protected static final String DEFAULT_RESOURCE_SET = "default"; + + /** Resource set type indicating the resources should be loaded from local files. */ + protected static final String FILE_SET_TYPE = "file"; + + /** Resource set type indicating the resources should be loaded over the network. */ + protected static final String NETWORK_SET_TYPE = "network"; }