Implemented functionality to resume full renders by typing "dynmap fullrender resume <world>" or "dynmap fullrender resume <world>:<map>". At the start of the render, existing map tiles are loaded from storage and their location info saved in a set. Before each tile is rendered, the set is checked if it contains the tile about to be rendered. If so, the tile is skipped. Information about skipped tiles is output in the periodic tile update message.

This commit is contained in:
Marvos 2019-10-21 21:32:49 +02:00
parent 1be9f663c7
commit 7980c7dbe5
9 changed files with 278 additions and 44 deletions

View File

@ -1042,7 +1042,9 @@ public class DynmapCore implements DynmapCommonAPI {
new CommandInfo("dynmap", "render", "Renders the tile at your location."),
new CommandInfo("dynmap", "fullrender", "Render all maps for entire world from your location."),
new CommandInfo("dynmap", "fullrender", "<world>", "Render all maps for world <world>."),
new CommandInfo("dynmap", "fullrender", "<world>:<map>", "Render map <map> of world'<world>."),
new CommandInfo("dynmap", "fullrender", "<world>:<map>", "Render map <map> of world <world>."),
new CommandInfo("dynmap", "fullrender", "resume <world>", "Resume render of all maps for world <world>. Skip already rendered tiles."),
new CommandInfo("dynmap", "fullrender", "resume <world>:<map>", "Resume render of map <map> of world <world>. Skip already rendered tiles."),
new CommandInfo("dynmap", "radiusrender", "<radius>", "Render at least <radius> block radius from your location on all maps."),
new CommandInfo("dynmap", "radiusrender", "<radius> <mapname>", "Render at least <radius> block radius from your location on map <mapname>."),
new CommandInfo("dynmap", "radiusrender", "<world> <x> <z> <radius>", "Render at least <radius> block radius from location <x>,<z> on world <world>."),
@ -1315,7 +1317,7 @@ public class DynmapCore implements DynmapCommonAPI {
loc = new DynmapLocation(w.getName(), x, 64, z);
}
if(loc != null)
mapManager.renderFullWorld(loc, sender, mapname, true);
mapManager.renderFullWorld(loc, sender, mapname, true, false);
} else if (c.equals("hide")) {
if (args.length == 1) {
if(player != null && checkPlayerPermission(sender,"hide.self")) {
@ -1343,7 +1345,12 @@ public class DynmapCore implements DynmapCommonAPI {
} else if (c.equals("fullrender") && checkPlayerPermission(sender,"fullrender")) {
String map = null;
if (args.length > 1) {
boolean resume = false;
for (int i = 1; i < args.length; i++) {
if (args[i].equalsIgnoreCase("resume")) {
resume = true;
continue;
}
int dot = args[i].indexOf(":");
DynmapWorld w;
String wname = args[i];
@ -1358,7 +1365,7 @@ public class DynmapCore implements DynmapCommonAPI {
loc = w.center;
else
loc = w.getSpawnLocation();
mapManager.renderFullWorld(loc,sender, map, false);
mapManager.renderFullWorld(loc,sender, map, false, resume);
}
else
sender.sendMessage("World '" + wname + "' not defined/loaded");
@ -1368,7 +1375,7 @@ public class DynmapCore implements DynmapCommonAPI {
if(args.length > 1)
map = args[1];
if(loc != null)
mapManager.renderFullWorld(loc, sender, map, false);
mapManager.renderFullWorld(loc, sender, map, false, false);
} else {
sender.sendMessage("World name is required");
}

View File

@ -22,6 +22,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@ -32,6 +33,10 @@ import org.dynmap.debug.Debug;
import org.dynmap.exporter.OBJExport;
import org.dynmap.hdmap.HDMapManager;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageBaseTileEnumCB;
import org.dynmap.storage.MapStorageTileSearchEndCB;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.Polygon;
import org.dynmap.utils.TileFlags;
@ -233,6 +238,7 @@ public class MapManager {
LinkedList<MapTile> renderQueue = null;
MapTile tile0 = null;
int rendercnt = 0;
int skipcnt = 0;
DynmapCommandSender sender;
String player;
long timeaccum;
@ -246,22 +252,52 @@ public class MapManager {
boolean shutdown = false;
boolean pausedforworld = false;
boolean updaterender = false;
boolean resume = false;
boolean quiet = false;
String mapname;
AtomicLong total_render_ns = new AtomicLong(0L);
AtomicInteger rendercalls = new AtomicInteger(0);
long lastPendingSaveTS = 0; // Timestamp of last pending state save (msec)
HashSet<String> storedTileIds = new HashSet<>();
/* Full world, all maps render */
FullWorldRenderState(DynmapWorld dworld, DynmapLocation l, DynmapCommandSender sender, String mapname, boolean updaterender) {
FullWorldRenderState(DynmapWorld dworld, DynmapLocation l, DynmapCommandSender sender, String mapname, boolean updaterender, boolean resume) {
this(dworld, l, sender, mapname, -1);
if(updaterender) {
rendertype = RENDERTYPE_UPDATERENDER;
this.updaterender = true;
}
else
else {
rendertype = RENDERTYPE_FULLRENDER;
}
this.resume = resume;
final CountDownLatch latch = new CountDownLatch(1);
if (resume) { // if resume render
final MapStorage ms = world.getMapStorage();
ms.enumMapBaseTiles(world, map, new MapStorageBaseTileEnumCB() {
@Override
public void tileFound(MapStorageTile tile, MapType.ImageEncoding enc) {
String tileId = String.format("%s_%s_%d_%d", tile.world.getName(), tile.map.getName(), tile.x, tile.y);
//sender.sendMessage("Tile found: " + tileId);
storedTileIds.add(tileId);
}
}, new MapStorageTileSearchEndCB() {
@Override
public void searchEnded() {
latch.countDown();
}
});
try {
latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
sender.sendMessage(e.toString());
}
}
}
/* Full world, all maps render, with optional render radius */
FullWorldRenderState(DynmapWorld dworld, DynmapLocation l, DynmapCommandSender sender, String mapname, int radius) {
@ -481,13 +517,22 @@ public class MapManager {
if(rndcalls == 0) rndcalls = 1;
double rendtime = total_render_ns.doubleValue() * 0.000001 / rndcalls;
if(activemapcnt > 1) {
if (skipcnt > 1)
sendMessage(String.format("%s of maps [%s] of '%s' completed - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render) (%d tiles skipped)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime, skipcnt));
else
sendMessage(String.format("%s of maps [%s] of '%s' completed - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime));
}
else {
if (skipcnt > 1)
sendMessage(String.format("%s of map '%s' of '%s' completed - %d tiles rendered (%.2f msec/map-tile, %.2f msec per render) (%d tiles skipped)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime, skipcnt));
else
sendMessage(String.format("%s of map '%s' of '%s' completed - %d tiles rendered (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime));
}
skipcnt = 0;
/* Now, if fullrender, use the render bitmap to purge obsolete tiles */
if(rendertype.equals(RENDERTYPE_FULLRENDER)) {
if(activemapcnt == 1) {
@ -698,19 +743,37 @@ public class MapManager {
chunks_read[cs.ordinal()].addAndGet(cache.getChunksLoaded(cs));
chunks_read_times[cs.ordinal()].addAndGet(cache.getTotalRuntimeNanos(cs));
}
boolean skipTile = false;
if (resume) {
String tileId = String.format("%s_%s_%d_%d", tile.world.getName(), map.getName(), tile.tileOrdinalX(), tile.tileOrdinalY());
skipTile = storedTileIds.contains(tileId);
}
if(tile0 != null) { /* Single tile? */
if(cache.isEmpty() == false)
if(cache.isEmpty() == false) {
if (skipTile) {
skipcnt++;
} else {
tile.render(cache, null);
}
}
}
else {
/* Remove tile from tile queue, since we're processing it already */
tileQueue.remove(tile);
/* Switch to not checking if rendered tile is blank - breaks us on skylands, where tiles can be nominally blank - just work off chunk cache empty */
if (cache.isEmpty() == false) {
boolean upd;
if (skipTile) {
upd = false;
skipcnt++;
} else {
long rt0 = System.nanoTime();
boolean upd = tile.render(cache, mapname);
upd = tile.render(cache, mapname);
total_render_ns.addAndGet(System.nanoTime()-rt0);
rendercalls.incrementAndGet();
}
synchronized(lock) {
rendered.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), true);
if(upd || (!updaterender)) { /* If updated or not an update render */
@ -733,13 +796,23 @@ public class MapManager {
if (rndcalls == 0) rndcalls = 1;
double rendtime = total_render_ns.doubleValue() * 0.000001 / rndcalls;
double msecpertile = (double)timeaccum / (double)rendercnt / (double)activemapcnt;
if(activemapcnt > 1)
if(activemapcnt > 1) {
if (skipcnt > 1)
sendMessage(String.format("%s of maps [%s] of '%s' in progress - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render) (%d tiles skipped)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime, skipcnt));
else
sendMessage(String.format("%s of maps [%s] of '%s' in progress - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime));
} else {
if (skipcnt > 1)
sendMessage(String.format("%s of map '%s' of '%s' in progress - %d tiles rendered (%.2f msec/tile, %.2f msec per render) (%d tiles skipped)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime, skipcnt));
else
sendMessage(String.format("%s of map '%s' of '%s' in progress - %d tiles rendered (%.2f msec/tile, %.2f msec per render)",
rendertype, activemaps, world.getName(), rendercnt, msecpertile, rendtime));
}
skipcnt = 0;
}
}
}
}
@ -751,6 +824,7 @@ public class MapManager {
public void cancelRender() {
cancelled = true;
storedTileIds.clear();
}
public void shutdownRender() {
@ -968,7 +1042,7 @@ public class MapManager {
tileQueue.start();
}
void renderFullWorld(DynmapLocation l, DynmapCommandSender sender, String mapname, boolean update) {
void renderFullWorld(DynmapLocation l, DynmapCommandSender sender, String mapname, boolean update, boolean resume) {
DynmapWorld world = getWorld(l.world);
if (world == null) {
sender.sendMessage("Could not render: world '" + l.world + "' not defined in configuration.");
@ -982,7 +1056,7 @@ public class MapManager {
sender.sendMessage(rndr.rendertype + " of world '" + wname + "' already active.");
return;
}
rndr = new FullWorldRenderState(world,l,sender, mapname, update); /* Make new activation record */
rndr = new FullWorldRenderState(world,l,sender, mapname, update, resume); /* Make new activation record */
active_renders.put(wname, rndr); /* Add to active table */
}
/* Schedule first tile to be worked */
@ -990,6 +1064,8 @@ public class MapManager {
if(update)
sender.sendMessage("Update render starting on world '" + wname + "'...");
else if (resume)
sender.sendMessage("Full render resuming on world '" + wname + "'...");
else
sender.sendMessage("Full render starting on world '" + wname + "'...");
}

View File

@ -86,6 +86,15 @@ public abstract class MapStorage {
*/
public abstract void enumMapTiles(DynmapWorld world, MapType map, MapStorageTileEnumCB cb);
/**
* Enumerate existing map tiles, matching given constraints, with zoom at 0
* @param world - specific world
* @param map - specific map (if non-null)
* @param cbBase - callback to receive matching tiles
* @param cbEnd - callback to receive end-of-search event
*/
public abstract void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd);
/**
* Purge existing map tiles, matching given constraints
* @param world - specific world

View File

@ -0,0 +1,12 @@
package org.dynmap.storage;
import org.dynmap.MapType.ImageEncoding;
public interface MapStorageBaseTileEnumCB {
/**
* Callback for base (non-zoomed) tile enumeration calls
* @param tile - tile found
* @param enc - image encoding
*/
public void tileFound(MapStorageTile tile, ImageEncoding enc);
}

View File

@ -0,0 +1,10 @@
package org.dynmap.storage;
import org.dynmap.MapType.ImageEncoding;
public interface MapStorageTileSearchEndCB {
/**
* Callback for end of tile enumeration calls
*/
public void searchEnded();
}

View File

@ -22,6 +22,8 @@ import org.dynmap.debug.Debug;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.storage.MapStorageTileEnumCB;
import org.dynmap.storage.MapStorageBaseTileEnumCB;
import org.dynmap.storage.MapStorageTileSearchEndCB;
import org.dynmap.utils.BufferInputStream;
import org.dynmap.utils.BufferOutputStream;
@ -295,9 +297,13 @@ public class FileTreeMapStorage extends MapStorage {
}
private void processEnumMapTiles(DynmapWorld world, MapType map, File base, ImageVariant var, MapStorageTileEnumCB cb) {
private void processEnumMapTiles(DynmapWorld world, MapType map, File base, ImageVariant var, MapStorageTileEnumCB cb, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
File bdir = new File(base, map.getPrefix() + var.variantSuffix);
if (bdir.isDirectory() == false) return;
if (bdir.isDirectory() == false) {
if(cbEnd != null)
cbEnd.searchEnded();
return;
}
LinkedList<File> dirs = new LinkedList<File>(); // List to traverse
dirs.add(bdir); // Directory for map
@ -343,7 +349,10 @@ public class FileTreeMapStorage extends MapStorage {
int y = Integer.parseInt(coord[1]);
// Invoke callback
MapStorageTile t = new StorageTile(world, map, x, y, zoom, var);
if(cb != null)
cb.tileFound(t, fmt);
if(cbBase != null && t.zoom == 0)
cbBase.tileFound(t, fmt);
t.cleanup();
} catch (NumberFormatException nfx) {
}
@ -351,6 +360,9 @@ public class FileTreeMapStorage extends MapStorage {
}
}
}
if(cbEnd != null) {
cbEnd.searchEnded();
}
}
@Override
@ -367,7 +379,26 @@ public class FileTreeMapStorage extends MapStorage {
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, base, var, cb);
processEnumMapTiles(world, mt, base, var, cb, null, null);
}
}
}
@Override
public void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
File base = new File(baseTileDir, world.getName()); // Get base directory for world
List<MapType> mtlist;
if (map != null) {
mtlist = Collections.singletonList(map);
}
else { // Else, add all directories under world directory (for maps)
mtlist = new ArrayList<MapType>(world.maps);
}
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, base, var, null, cbBase, cbEnd);
}
}
}

View File

@ -25,6 +25,8 @@ import org.dynmap.PlayerFaces.FaceType;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.storage.MapStorageTileEnumCB;
import org.dynmap.storage.MapStorageBaseTileEnumCB;
import org.dynmap.storage.MapStorageTileSearchEndCB;
import org.dynmap.utils.BufferInputStream;
import org.dynmap.utils.BufferOutputStream;
@ -620,15 +622,36 @@ public class MariaDBMapStorage extends MapStorage {
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, cb);
processEnumMapTiles(world, mt, var, cb, null, null);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb) {
@Override
public void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
List<MapType> mtlist;
if (map != null) {
mtlist = Collections.singletonList(map);
}
else { // Else, add all directories under world directory (for maps)
mtlist = new ArrayList<MapType>(world.maps);
}
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, null, cbBase, cbEnd);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
Connection c = null;
boolean err = false;
Integer mapkey = getMapKey(world, map, var);
if (mapkey == null) return;
if (mapkey == null) {
if(cbEnd != null)
cbEnd.searchEnded();
return;
}
try {
c = getConnection();
// Query tiles for given mapkey
@ -636,9 +659,15 @@ public class MariaDBMapStorage extends MapStorage {
ResultSet rs = stmt.executeQuery("SELECT x,y,zoom,Format FROM " + tableTiles + " WHERE MapID=" + mapkey + ";");
while (rs.next()) {
StorageTile st = new StorageTile(world, map, rs.getInt("x"), rs.getInt("y"), rs.getInt("zoom"), var);
cb.tileFound(st, MapType.ImageEncoding.fromOrd(rs.getInt("Format")));
final MapType.ImageEncoding encoding = MapType.ImageEncoding.fromOrd(rs.getInt("Format"));
if(cb != null)
cb.tileFound(st, encoding);
if(cbBase != null && st.zoom == 0)
cbBase.tileFound(st, encoding);
st.cleanup();
}
if(cbEnd != null)
cbEnd.searchEnded();
rs.close();
stmt.close();
} catch (SQLException x) {

View File

@ -25,6 +25,8 @@ import org.dynmap.PlayerFaces.FaceType;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.storage.MapStorageTileEnumCB;
import org.dynmap.storage.MapStorageBaseTileEnumCB;
import org.dynmap.storage.MapStorageTileSearchEndCB;
import org.dynmap.utils.BufferInputStream;
import org.dynmap.utils.BufferOutputStream;
@ -622,15 +624,36 @@ public class MySQLMapStorage extends MapStorage {
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, cb);
processEnumMapTiles(world, mt, var, cb, null, null);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb) {
@Override
public void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
List<MapType> mtlist;
if (map != null) {
mtlist = Collections.singletonList(map);
}
else { // Else, add all directories under world directory (for maps)
mtlist = new ArrayList<MapType>(world.maps);
}
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, null, cbBase, cbEnd);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
Connection c = null;
boolean err = false;
Integer mapkey = getMapKey(world, map, var);
if (mapkey == null) return;
if (mapkey == null) {
if(cbEnd != null)
cbEnd.searchEnded();
return;
}
try {
c = getConnection();
// Query tiles for given mapkey
@ -638,9 +661,15 @@ public class MySQLMapStorage extends MapStorage {
ResultSet rs = stmt.executeQuery("SELECT x,y,zoom,Format FROM " + tableTiles + " WHERE MapID=" + mapkey + ";");
while (rs.next()) {
StorageTile st = new StorageTile(world, map, rs.getInt("x"), rs.getInt("y"), rs.getInt("zoom"), var);
cb.tileFound(st, MapType.ImageEncoding.fromOrd(rs.getInt("Format")));
final MapType.ImageEncoding encoding = MapType.ImageEncoding.fromOrd(rs.getInt("Format"));
if(cb != null)
cb.tileFound(st, encoding);
if(cbBase != null && st.zoom == 0)
cbBase.tileFound(st, encoding);
st.cleanup();
}
if(cbEnd != null)
cbEnd.searchEnded();
rs.close();
stmt.close();
} catch (SQLException x) {

View File

@ -23,6 +23,8 @@ import org.dynmap.PlayerFaces.FaceType;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.storage.MapStorageTileEnumCB;
import org.dynmap.storage.MapStorageBaseTileEnumCB;
import org.dynmap.storage.MapStorageTileSearchEndCB;
import org.dynmap.utils.BufferInputStream;
import org.dynmap.utils.BufferOutputStream;
@ -553,15 +555,38 @@ public class SQLiteMapStorage extends MapStorage {
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, cb);
processEnumMapTiles(world, mt, var, cb, null, null);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb) {
@Override
public void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
List<MapType> mtlist;
if (map != null) {
mtlist = Collections.singletonList(map);
}
else { // Else, add all directories under world directory (for maps)
mtlist = new ArrayList<MapType>(world.maps);
}
for (MapType mt : mtlist) {
ImageVariant[] vars = mt.getVariants();
for (ImageVariant var : vars) {
processEnumMapTiles(world, mt, var, null, cbBase, cbEnd);
}
}
}
private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) {
Connection c = null;
boolean err = false;
Integer mapkey = getMapKey(world, map, var);
if (mapkey == null) return;
if (mapkey == null) {
if(cbEnd != null)
cbEnd.searchEnded();
return;
}
try {
c = getConnection();
// Query tiles for given mapkey
@ -570,9 +595,15 @@ public class SQLiteMapStorage extends MapStorage {
ResultSet rs = doExecuteQuery(stmt, "SELECT x,y,zoom,Format FROM Tiles WHERE MapID=" + mapkey + ";");
while (rs.next()) {
StorageTile st = new StorageTile(world, map, rs.getInt("x"), rs.getInt("y"), rs.getInt("zoom"), var);
cb.tileFound(st, MapType.ImageEncoding.fromOrd(rs.getInt("Format")));
final MapType.ImageEncoding encoding = MapType.ImageEncoding.fromOrd(rs.getInt("Format"));
if(cb != null)
cb.tileFound(st, encoding);
if(cbBase != null && st.zoom == 0)
cbBase.tileFound(st, encoding);
st.cleanup();
}
if(cbEnd != null)
cbEnd.searchEnded();
rs.close();
stmt.close();
} catch (SQLException x) {