tile cache test build

I’ve uploaded a test build of the tile cache work I’ve been doing on the uckelman-working branch. The primary upshot of this is that you can run the Case Blue/GDII module with a 256MB heap now, which means that you can probably run any existing module with 256MB heap.

The test build is 3.2.0-svn7390. Note that you won’t get everything in this build if you check out the uckelman-working branch from SVN, since this build contains a whole of uncommitted changes.

What you should see the first time you load a module is a dialog showing you the progress of slicing map images to tiles and writing those tiles to disk. So long as the images in the module aren’t modified and you don’t delete the tile cache, you’ll never see this dialog again once the tiling is done. (When a module is loaded, it checks that the cached tiles for each image are fresh. Stale or missing tiles are recreated, fresh ones are left alone.)

There are some rough edges still, namely:

  • There’s no way to force the tile cache to be cleared from within VASSAL. The only way to do that right now is to manually delete the cache directory for the module.

  • Canceling tiling leaves you with a “busy” cursor.

  • Writing tiles for modules or image files which contain characters in their names which aren’t legal in filenames on your system will cause an IOException. The solution to this is to sanitize such names before trying to write to disk. (We should really do this for modules, too—there are a few modules which contain files with bad names, which prevents standard ZIP tools from unzipping them.)

  • The tile slicer, which runs as a child process, presently has its max heap fixed at 512MB. This may be too much for some images, too little for others. It would be nice if we could automatically set the max heap for the tile slicer based on the dimensions of the largest image it needs to tile.

  • Displaying progress as a percentage is surprisingly complex.

The most common type of progress dialog, for file downloads, is simple, as in that setting, you know at the start how large the file is and that you’re planning to download all the bytes. By knowing how many bytes you have and how long that took, you can make a completion time estimate.

Tiling is not a single, homogeneous operation. During tiling, there are four operations happening: image loading, image type conversion, tile slicing, and tile writing. Presently, the only one of these which generates progress updates is tile writing. In particular, what you’re seeing in the progress bar is the percentage of tiles written so far. However, the other three processes are much more time-consuming, which is why you’ll see the progress bar advance fitfully instead of smoothly.

Let me describe the process in more detail, because I’d like to get some suggestions for how to display progress more smoothly. For each image to be tiled, the tile slicer

  1. loads the image, via ImageIO,
  2. converts the image to TYPE_INT_RGB or TYPE_INT_ARGB,
  3. slices each tile from the image, scaled to the requested size,
  4. writes the tile’s image data to the disk cache

Of these, ImageIO has progress hooks, so we can get completion percentage from that, and writing tile data to disk is quite fast (tiles are on the order of 100KB), so the only progress worth tracking there is counting how many have completed. For slicing, it’s the scaling which takes all the time; adding progress listener support to the scaler wouldn’t be too hard. Image conversion is more complicated: In 3.1, we did all image type conversions in memory, which unfortunately means that you need to have enough heap to store two copies of the image simultaneously. For very large images, this isn’t feasible. What the type converter I wrote does is tries an in-memory conversion using Graphics2D.drawImage(), and if that fails, it uses BufferedImage.getRGB() to convert the image data to TYPE_INT_ARGB, writes that to a temporary file one row at a time, lets the original image be garbage collected, creates the destination image, reads the image data back from disk, and writes to the new image using BufferedImage.setRGB(). In the on-disk case, we can count progress by seeing how many rows of pixels have been written to disk, and then by how many have been read. In the in-memory case, the only progress indication we can get from Graphics2D.drawImage() is that it hasn’t started (because we haven’t called it yet) and that it’s finished (because execution has moved to the next line). To make things worse, we have no way of telling which conversion method will be used for any given image, since the on-disk conversion is attempted only when the in-memory one fails with an OutOfMemoryException, or even whether we’ll be doing a conversion at all, since in some (rare?) cases, ImageIO will give us an image which is already the right type.

That said, even if we could get a completion percentage from each part of the process, it’s not the case that 1% of image loading will take the same amount of time as converting 1% of the image rows or writing 1% of the tiles. The scaling time for tiles of the same size at different scale factors isn’t even consistent, so slicing 1% of the tiles might take dramatically longer than slicing some other 1% of the tiles. (Rather perversely, scaling time grows as the scale factor decreases, because you’re filtering more source pixels into each destination pixel.) So, I’m rather perplexed as to how to do a good job with this. Maybe it doesn’t matter that much, but I think that users will suspect that the program has seized up if they see the progress bar remaining at one point for 10-20 seconds.

Tim found an (unrelated) bug in the test build for Windows. I’ve uploaded a version where I believe this is fixed, 3.2.0-svn7405.

Using 7405, no crash but no map either when using a multi row config board setup. Im only going to post the first part from error log, rest is same goes on forever.

2010-10-18 17:55:43,890 [0-main] INFO VASSAL.launch.StartUp - Starting
2010-10-18 17:55:43,906 [0-main] INFO VASSAL.launch.StartUp - OS Windows XP
2010-10-18 17:55:43,906 [0-main] INFO VASSAL.launch.StartUp - Java version 1.6.0_21
2010-10-18 17:55:43,906 [0-main] INFO VASSAL.launch.StartUp - VASSAL version 3.2.0-svn7405
2010-10-18 17:55:43,937 [0-AWT-EventQueue-0] INFO VASSAL.launch.ModuleManager - Manager
2010-10-18 17:56:56,937 [0-SwingWorker-pool-31299729-thread-4] INFO VASSAL.launch.AbstractLaunchAction - Loading module file C:\Documents and Settings\Administrator\Desktop\WSR.vmod
2010-10-18 17:56:56,937 [0-SwingWorker-pool-31299729-thread-4] INFO VASSAL.launch.AbstractLaunchAction - Loading module White Star Rising
2010-10-18 17:56:56,953 [0-SwingWorker-pool-31299729-thread-4] INFO VASSAL.tools.io.ProcessLauncher - launching C:\Program Files (x86)\Java\jre6\bin\java -Xms256M -Xmx512M -DVASSAL.id=1 -DVASSAL.port=2980 -Duser.home=C:\Documents and Settings\Administrator -cp lib\Vengine.jar VASSAL.launch.Player --load – C:\Documents and Settings\Administrator\Desktop\WSR.vmod
2010-10-18 17:56:58,031 [1-main] INFO VASSAL.launch.StartUp - Starting
2010-10-18 17:56:58,046 [1-main] INFO VASSAL.launch.StartUp - OS Windows XP
2010-10-18 17:56:58,046 [1-main] INFO VASSAL.launch.StartUp - Java version 1.6.0_21
2010-10-18 17:56:58,046 [1-main] INFO VASSAL.launch.StartUp - VASSAL version 3.2.0-svn7405
2010-10-18 17:56:58,046 [1-main] INFO VASSAL.launch.Launcher - Player
2010-10-18 17:57:00,515 [1-AWT-EventQueue-0] WARN VASSAL.launch.BasicModule - White Star Rising version 0.99
2010-10-18 17:57:07,468 [1-AWT-EventQueue-0] WARN VASSAL.tools.ErrorDialog - Image not found: MAP_A.jpg(1,0)@1:4
2010-10-18 17:57:07,468 [1-AWT-EventQueue-0] ERROR VASSAL.tools.ErrorDialog -
VASSAL.tools.image.ImageNotFoundException: C:\Documents and Settings\Administrator\VASSAL\tiles\White Star Rising_0.99\MAP_A.jpg(1,0)@1:4
at VASSAL.tools.image.tilecache.TileUtils.read(TileUtils.java:92) ~[Vengine.jar:na]
at VASSAL.tools.image.tilecache.TileUtils.read(TileUtils.java:70) ~[Vengine.jar:na]
at VASSAL.tools.image.tilecache.ImageTileDiskCache.getTile(ImageTileDiskCache.java:35) ~[Vengine.jar:na]
at VASSAL.tools.imageop.SourceOpDiskCacheBitmapImpl.eval(SourceOpDiskCacheBitmapImpl.java:106) ~[Vengine.jar:na]
at VASSAL.tools.imageop.SourceOpDiskCacheBitmapImpl.eval(SourceOpDiskCacheBitmapImpl.java:41) ~[Vengine.jar:na]
at VASSAL.tools.opcache.OpCache.getFuture(OpCache.java:311) ~[Vengine.jar:na]
at VASSAL.tools.opcache.OpCache.get(OpCache.java:254) ~[Vengine.jar:na]
at VASSAL.tools.opcache.AbstractOpImpl.get(AbstractOpImpl.java:79) ~[Vengine.jar:na]
at VASSAL.tools.imageop.AbstractOpImpl.getImage(AbstractOpImpl.java:98) ~[Vengine.jar:na]
at VASSAL.tools.imageop.AbstractTiledOpImpl.getTile(AbstractTiledOpImpl.java:127) ~[Vengine.jar:na]
at VASSAL.tools.imageop.AbstractOpImpl.getTile(AbstractOpImpl.java:163) ~[Vengine.jar:na]
at VASSAL.tools.imageop.ScaleOpTiledBitmapImpl$TileOp.eval(ScaleOpTiledBitmapImpl.java:191) ~[Vengine.jar:na]
at VASSAL.tools.imageop.ScaleOpTiledBitmapImpl$TileOp.eval(ScaleOpTiledBitmapImpl.java:79) ~[Vengine.jar:na]
at VASSAL.tools.opcache.OpCache$Request.doInBackground(OpCache.java:190) ~[Vengine.jar:na]
at org.jdesktop.swingworker.SwingWorker$1.call(Unknown Source) ~[swing-worker-1.2.jar:na]
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) ~[na:1.6.0_21]
at java.util.concurrent.FutureTask.run(Unknown Source) ~[na:1.6.0_21]
at org.jdesktop.swingworker.SwingWorker.run(Unknown Source) ~[swing-worker-1.2.jar:na]
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) ~[na:1.6.0_21]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:1.6.0_21]
at java.lang.Thread.run(Unknown Source) ~[na:1.6.0_21]
Caused by: java.io.FileNotFoundException: C:\Documents and Settings\Administrator\VASSAL\tiles\White Star Rising_0.99\MAP_A.jpg(1,0)@1:4 (The system cannot find the path specified)
at java.io.FileInputStream.open(Native Method) ~[na:1.6.0_21]
at java.io.FileInputStream.(Unknown Source) ~[na:1.6.0_21]
at VASSAL.tools.image.tilecache.TileUtils.read(TileUtils.java:85) ~[Vengine.jar:na]
… 20 common frames omitted

Problem persists with simple 1 map module also, Bugs generated when closing player or manager - submitted

I’m not sure how you handling zooms etc in your cache but would it be an idea to tile the map images when they are loaded in the editor and save the tiles in the module (instead of the whole image) at zoom = 1.0.

You could then serve the tiles from the module or if that is too slow extract the tiles at startup or just save each tile locally after the first use.

This would avoid filling the user disk with vast numbers of tiles/directories from different games and fix any naming problems (the tile files could be named image0tile0x0, image0tile0x1 and so on. Downside would be needing to keep tile objects (but not the image part) for every active map in memory all the time and user would need to reconstruct map image to edit it (if he didn’t keep a copy). Imagesaver could do that with an empty map of the board required

Thus spake george973:

I’m not sure how you handling zooms etc in your cache but would it be an
idea to tile the map images when they are loaded in the editor and save
the tiles in the module (instead of the whole image) at zoom = 1.0.

I’m making tiles for each scale fator 1/2^n, for all n >= 0, until some
dimension of the tile would be less than 1 pixel. There are two reasons for
making a full image pyramid beforehand:

  1. High-quality scaling isn’t very fast. I don’t see that it can be made
    much faster—it’s the most-optimized part of VASSAL there is.

  2. The further you zoom out, the more pixel data goes into each tile,
    which means that zooming out on the fly becomes more memory intensive
    as you go. Having most of the work done beforehand means that you can
    take the nearest level in the image pyramid and scale from that instead.

You could then serve the tiles from the module or if that is too slow
extract the tiles at startup or just save each tile locally after the
first use.

I had thought about that, but it would more than double the size of
modules if they had to carry around all that tile data. Disk space
is cheap and plentiful these days; bandwidth, not so much.

This would avoid filling the user disk with vast numbers of
tiles/directories from different games and fix any naming problems (the
tile files could be named image0tile0x0, image0tile0x1 and so on.
Downside would be needing to keep tile objects (but not the image part)
for every active map in memory all the time and user would need to
reconstruct map image to edit it (if he didn’t keep a copy). Imagesaver
could do that with an empty map of the board required

I’m fixing naming problems through filename munging. This should’t be
a problem.

Storing only tiles in modules will be inconvenient for module designers.
There is already a tool for reassembling tiles into complete images, but
I was hoping that nobody would need to use it.


J.

Thus spake Tim M:

Using 7405, no crash but no map either when using a multi row config
board setup. Im only going to post the first part from error log, rest
is same goes on forever.

I tried this with the WSR module, and believe that the work I’ve since
done fixes the problem. The current build is svn7429:

nomic.net/~uckelman/tmp/vass … indows.exe

However, looking at this module turned up a fascinating new problem,
namely that something is screwed up with how the color profile in the
JPEGs is handled. The terrain in these maps looks purple to me. It’s
not just a problem with my 3.2 build—this happens in 3.1 as well.
Is it that way for you, too?


J.

Error log sent on the 3.2 build - still no map, explain in bug submittal.

Using same module. Colors look fine for me in 3.1 - nothing is purple at
least, map looks like typical green. Terrain tiles look ok as well

-----Original Message-----
From: messages-bounces@vassalengine.org
[mailto:messages-bounces@vassalengine.org] On Behalf Of Joel Uckelman
Sent: Wednesday, October 27, 2010 4:57 PM
To: messages@vassalengine.org
Subject: Re: [messages] [Developers] Re: tile cache test build

Thus spake Tim M:

Using 7405, no crash but no map either when using a multi row config
board setup. Im only going to post the first part from error log, rest
is same goes on forever.

I tried this with the WSR module, and believe that the work I’ve since
done fixes the problem. The current build is svn7429:

nomic.net/~uckelman/tmp/vass … indows.exe

However, looking at this module turned up a fascinating new problem,
namely that something is screwed up with how the color profile in the
JPEGs is handled. The terrain in these maps looks purple to me. It’s
not just a problem with my 3.2 build—this happens in 3.1 as well.
Is it that way for you, too?


J.