update tile rendering code

master
Thomas Rudin 2018-07-03 13:45:56 +02:00
parent 6c72f8052f
commit 218567c7f2
8 changed files with 285 additions and 260 deletions

View File

@ -55,7 +55,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.1.4</version>
<version>42.2.2</version>
</dependency>
<dependency>

View File

@ -100,10 +100,6 @@ Max y value for blocks to render (in Mapblocks)
Min y value for blocks to render (in Mapblocks)
* Default: **-1** (Equals -16 blocks)
### tilerenderer.processes
Number of processes to allow for rendering
* Default: **24**
### tilerenderer.updateinterval
Update interval to check for new tiles (in seconds)
* Default: **20**

View File

@ -9,12 +9,15 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.zip.DataFormatException;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.util.concurrent.Striped;
import io.rudin.minetest.tileserver.config.TileServerConfig;
import org.jooq.Condition;
import org.jooq.DSLContext;
@ -42,10 +45,22 @@ public class TileRenderer {
this.cfg = cfg;
this.yCondition = BLOCKS.POSY.between(cfg.tilesMinY(), cfg.tilesMaxY());
this.lock = Striped.lazyWeakReadWriteLock(50);
ImageIO.setUseCache(false);
}
private String getKey(int x, int y, int z){
return x + "/" + y + "/" + z;
}
private final Striped<ReadWriteLock> lock;
private ReadWriteLock getLock(int x, int y, int z){
return lock.get(getKey(x,y,z));
}
private final Condition yCondition;
private final TileServerConfig cfg;
@ -119,7 +134,17 @@ public class TileRenderer {
}
/**
* Render the actual image to a bufferedImage
* @param tileX
* @param tileY
* @param zoom
* @return
* @throws IllegalArgumentException
* @throws DataFormatException
* @throws IOException
* @throws ExecutionException
*/
public BufferedImage renderImage(int tileX, int tileY, int zoom) throws IllegalArgumentException, DataFormatException, IOException, ExecutionException {
//Check if binary cached, use cached version for rendering
@ -128,165 +153,185 @@ public class TileRenderer {
return ImageIO.read(new ByteArrayInputStream(data));
}
final int HALF_TILE_PIXEL_SIZE = CoordinateResolver.TILE_PIXEL_SIZE / 2;
ReadWriteLock lock = getLock(tileX, tileY, zoom);
Lock writeLock = lock.writeLock();
//writeLock.lock();
try {
//re-check in critical section
/*
if (cache.has(tileX, tileY, zoom)) {
byte[] data = cache.get(tileX, tileY, zoom);
return ImageIO.read(new ByteArrayInputStream(data));
}
*/
final int HALF_TILE_PIXEL_SIZE = CoordinateResolver.TILE_PIXEL_SIZE / 2;
if (zoom > DEFAULT_BLOCK_ZOOM) {
//TODO: needed anymore?
//Zoom in
if (zoom > DEFAULT_BLOCK_ZOOM) {
//TODO: needed anymore?
//Zoom in
int nextZoom = zoom - 1;
int offsetNextZoomX = Math.abs(tileX % 2);
int offsetNextZoomY = Math.abs(tileY % 2);
int nextZoom = zoom - 1;
int offsetNextZoomX = Math.abs(tileX % 2);
int offsetNextZoomY = Math.abs(tileY % 2);
int nextZoomX = (tileX - offsetNextZoomX) / 2;
int nextZoomY = (tileY - offsetNextZoomY) / 2;
int nextZoomX = (tileX - offsetNextZoomX) / 2;
int nextZoomY = (tileY - offsetNextZoomY) / 2;
BufferedImage tile = createTile();
Graphics2D graphics = tile.createGraphics();
BufferedImage tile = createTile();
Graphics2D graphics = tile.createGraphics();
//Get all quadrants
BufferedImage image = renderImage(nextZoomX, nextZoomY, nextZoom);
BufferedImage quadrant = image.getSubimage(
HALF_TILE_PIXEL_SIZE * offsetNextZoomX,
HALF_TILE_PIXEL_SIZE * offsetNextZoomY,
HALF_TILE_PIXEL_SIZE,
HALF_TILE_PIXEL_SIZE
);
Image scaledInstance = quadrant.getScaledInstance(
CoordinateResolver.TILE_PIXEL_SIZE,
CoordinateResolver.TILE_PIXEL_SIZE,
Image.SCALE_FAST
);
graphics.drawImage(scaledInstance, 0, 0, CoordinateResolver.TILE_PIXEL_SIZE, CoordinateResolver.TILE_PIXEL_SIZE, null);
//Get all quadrants
BufferedImage image = renderImage(nextZoomX, nextZoomY, nextZoom);
ByteArrayOutputStream output = new ByteArrayOutputStream(12000);
ImageIO.write(tile, "png", output);
BufferedImage quadrant = image.getSubimage(
HALF_TILE_PIXEL_SIZE * offsetNextZoomX,
HALF_TILE_PIXEL_SIZE * offsetNextZoomY,
HALF_TILE_PIXEL_SIZE,
HALF_TILE_PIXEL_SIZE
);
byte[] data = output.toByteArray();
Image scaledInstance = quadrant.getScaledInstance(
CoordinateResolver.TILE_PIXEL_SIZE,
CoordinateResolver.TILE_PIXEL_SIZE,
Image.SCALE_FAST
);
cache.put(tileX, tileY, zoom, data);
graphics.drawImage(scaledInstance, 0, 0, CoordinateResolver.TILE_PIXEL_SIZE, CoordinateResolver.TILE_PIXEL_SIZE, null);
ByteArrayOutputStream output = new ByteArrayOutputStream(12000);
ImageIO.write(tile, "png", output);
byte[] data = output.toByteArray();
cache.put(tileX, tileY, zoom, data);
return tile;
return tile;
} else if (zoom < DEFAULT_BLOCK_ZOOM) {
//Zoom out
} else if (zoom < DEFAULT_BLOCK_ZOOM) {
//Zoom out
BufferedImage tile = createTile();
//Pack 4 tiles from higher zoom into 1 tile
BufferedImage tile = createTile();
//Pack 4 tiles from higher zoom into 1 tile
int nextZoom = zoom + 1;
int nextZoomX = tileX * 2;
int nextZoomY = tileY * 2;
int nextZoom = zoom + 1;
int nextZoomX = tileX * 2;
int nextZoomY = tileY * 2;
BufferedImage upperLeft = renderImage(nextZoomX, nextZoomY, nextZoom);
BufferedImage upperRightImage = renderImage(nextZoomX+1, nextZoomY, nextZoom);
BufferedImage lowerLeftImage = renderImage(nextZoomX, nextZoomY+1, nextZoom);
BufferedImage lowerRightImage = renderImage(nextZoomX+1, nextZoomY+1, nextZoom);
BufferedImage upperLeft = renderImage(nextZoomX, nextZoomY, nextZoom);
BufferedImage upperRightImage = renderImage(nextZoomX + 1, nextZoomY, nextZoom);
BufferedImage lowerLeftImage = renderImage(nextZoomX, nextZoomY + 1, nextZoom);
BufferedImage lowerRightImage = renderImage(nextZoomX + 1, nextZoomY + 1, nextZoom);
long start = System.currentTimeMillis();
Graphics2D graphics = tile.createGraphics();
Image upperLeftScaledInstance = upperLeft.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(upperLeftScaledInstance, 0, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
Image upperRightScaledInstance = upperRightImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(upperRightScaledInstance, HALF_TILE_PIXEL_SIZE, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
Image lowerLeftScaledInstance = lowerLeftImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(lowerLeftScaledInstance, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
Image lowerRightScaledInstance = lowerRightImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(lowerRightScaledInstance, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
ByteArrayOutputStream output = new ByteArrayOutputStream(12000);
ImageIO.write(tile, "png", output);
byte[] data = output.toByteArray();
long diff = System.currentTimeMillis() - start;
logger.debug("Timings of cross-stitched tile X={} Y={} Zoom={}: render={} ms", tileX, tileY, zoom, diff);
cache.put(tileX, tileY, zoom, data);
return tile;
}
//Default zoom (13 == 1 mapblock with 16px wide blocks)
long start = System.currentTimeMillis();
Graphics2D graphics = tile.createGraphics();
MapBlockCoordinateInfo coordinateInfo = CoordinateResolver.fromTile(tileX, tileY, zoom);
Image upperLeftScaledInstance = upperLeft.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(upperLeftScaledInstance, 0, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
//16x16 mapblocks on a tile
BufferedImage image = createTile();
Graphics2D graphics = image.createGraphics();
Image upperRightScaledInstance = upperRightImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(upperRightScaledInstance, HALF_TILE_PIXEL_SIZE, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, CoordinateResolver.TILE_PIXEL_SIZE, CoordinateResolver.TILE_PIXEL_SIZE);
Image lowerLeftScaledInstance = lowerLeftImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(lowerLeftScaledInstance, 0, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
Image lowerRightScaledInstance = lowerRightImage.getScaledInstance(HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, Image.SCALE_FAST);
graphics.drawImage(lowerRightScaledInstance, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, HALF_TILE_PIXEL_SIZE, null);
int mapblockX = coordinateInfo.x;
int mapblockZ = coordinateInfo.z;
long now = System.currentTimeMillis();
long timingImageSetup = now - start;
start = now;
Integer count = ctx
.select(DSL.count())
.from(BLOCKS)
.where(
BLOCKS.POSX.eq(mapblockX)
.and(BLOCKS.POSZ.eq(mapblockZ))
.and(yCondition)
)
.fetchOne(DSL.count());
now = System.currentTimeMillis();
long timingZeroCountCheck = now - start;
start = now;
long timingRender = 0;
if (count > 0) {
blockRenderer.render(mapblockX, mapblockZ, graphics, 16);
now = System.currentTimeMillis();
timingRender = now - start;
start = now;
}
ByteArrayOutputStream output = new ByteArrayOutputStream(12000);
ImageIO.write(tile, "png", output);
ImageIO.write(image, "png", output);
byte[] data = output.toByteArray();
long diff = System.currentTimeMillis() - start;
now = System.currentTimeMillis();
long timingBufferOutput = now - start;
logger.debug("Timings of cross-stitched tile X={} Y={} Zoom={}: render={} ms", tileX, tileY, zoom, diff);
logger.debug("Timings of tile X={} Y={} Zoom={}: setup={} ms, zeroCheck={} ms, render={} ms, output={} ms",
tileX, tileY, zoom,
timingImageSetup, timingZeroCountCheck, timingRender, timingBufferOutput
);
cache.put(tileX, tileY, zoom, data);
return tile;
return image;
} finally {
//writeLock.unlock();
}
//Default zoom (13 == 1 mapblock with 16px wide blocks)
long start = System.currentTimeMillis();
MapBlockCoordinateInfo coordinateInfo = CoordinateResolver.fromTile(tileX, tileY, zoom);
//16x16 mapblocks on a tile
BufferedImage image = createTile();
Graphics2D graphics = image.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, CoordinateResolver.TILE_PIXEL_SIZE, CoordinateResolver.TILE_PIXEL_SIZE);
int mapblockX = coordinateInfo.x;
int mapblockZ = coordinateInfo.z;
long now = System.currentTimeMillis();
long timingImageSetup = now - start;
start = now;
Integer count = ctx
.select(DSL.count())
.from(BLOCKS)
.where(
BLOCKS.POSX.eq(mapblockX)
.and(BLOCKS.POSZ.eq(mapblockZ))
.and(yCondition)
)
.fetchOne(DSL.count());
now = System.currentTimeMillis();
long timingZeroCountCheck = now - start;
start = now;
long timingRender = 0;
if (count > 0) {
blockRenderer.render(mapblockX, mapblockZ, graphics, 16);
now = System.currentTimeMillis();
timingRender = now - start;
start = now;
}
ByteArrayOutputStream output = new ByteArrayOutputStream(12000);
ImageIO.write(image, "png", output);
byte[] data = output.toByteArray();
now = System.currentTimeMillis();
long timingBufferOutput = now - start;
logger.debug("Timings of tile X={} Y={} Zoom={}: setup={} ms, zeroCheck={} ms, render={} ms, output={} ms",
tileX, tileY, zoom,
timingImageSetup, timingZeroCountCheck, timingRender, timingBufferOutput
);
cache.put(tileX, tileY, zoom, data);
return image;
}
}

View File

@ -30,10 +30,6 @@ public interface TileServerConfig extends Config {
@DefaultValue("false")
boolean tilerendererEnableInitialRendering();
@Key("tilerenderer.processes")
@DefaultValue("24")
int tilerendererProcesses();
@Key("tilerenderer.updateinterval")
@DefaultValue("20")
int tilerendererUpdateInterval();

View File

@ -1,5 +1,6 @@
package io.rudin.minetest.tileserver.job;
import com.google.common.util.concurrent.Striped;
import io.rudin.minetest.tileserver.TileRenderer;
import io.rudin.minetest.tileserver.config.TileServerConfig;
import io.rudin.minetest.tileserver.util.CoordinateResolver;
@ -13,6 +14,8 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.concurrent.locks.ReadWriteLock;
import static io.rudin.minetest.tileserver.blockdb.tables.Blocks.BLOCKS;
@Singleton
@ -35,90 +38,97 @@ public class InitialTileRendererJob implements Runnable {
@Override
public void run() {
logger.info("Starting initial tilerendering");
try {
logger.info("Starting initial tilerendering");
Record2<Integer, Integer> minMaxRecords = ctx
.select(DSL.max(BLOCKS.POSX), DSL.min(BLOCKS.POSX))
.from(BLOCKS)
.where(yCondition)
.fetchOne();
Integer maxX = minMaxRecords.get(DSL.max(BLOCKS.POSX));
Integer minX = minMaxRecords.get(DSL.min(BLOCKS.POSX));
long start = System.currentTimeMillis();
int xStripes = maxX - minX;
long byteCount = 0;
int errorCount = 0;
long lastProgressUpdate = 0;
int tileCount = 0;
logger.info("X-Stripe count: {} (from {} to {})", xStripes, minX, maxX);
for (int posx = minX; posx <= maxX; posx++){
double xProgress = (posx - minX) / (double)xStripes;
minMaxRecords = ctx
.select(DSL.max(BLOCKS.POSZ), DSL.min(BLOCKS.POSZ))
Record2<Integer, Integer> minMaxRecords = ctx
.select(DSL.max(BLOCKS.POSX), DSL.min(BLOCKS.POSX))
.from(BLOCKS)
.where(yCondition)
.and(BLOCKS.POSX.eq(posx))
.fetchOne();
Integer maxZ = minMaxRecords.get(DSL.max(BLOCKS.POSZ));
Integer minZ = minMaxRecords.get(DSL.min(BLOCKS.POSZ));
Integer maxX = minMaxRecords.get(DSL.max(BLOCKS.POSX));
Integer minX = minMaxRecords.get(DSL.min(BLOCKS.POSX));
int zCount = maxZ - minZ;
long start = System.currentTimeMillis();
int xStripes = maxX - minX;
long byteCount = 0;
int errorCount = 0;
long lastProgressUpdate = 0;
int tileCount = 0;
for (int posz = minZ; posz <= maxZ; posz++){
logger.info("X-Stripe count: {} (from {} to {})", xStripes, minX, maxX);
double zProgress = (posz - minZ) / (double)zCount;
for (int posx = minX; posx <= maxX; posx++) {
CoordinateResolver.TileInfo tileInfo = CoordinateResolver.fromCoordinates(posx, posz);
double xProgress = (posx - minX) / (double) xStripes;
try {
byte[] data = renderer.render(tileInfo.x, tileInfo.x, CoordinateResolver.ONE_TO_ONE_ZOOM);
byteCount += data.length;
tileCount++;
minMaxRecords = ctx
.select(DSL.max(BLOCKS.POSZ), DSL.min(BLOCKS.POSZ))
.from(BLOCKS)
.where(yCondition)
.and(BLOCKS.POSX.eq(posx))
.fetchOne();
} catch (Exception e){
errorCount++;
logger.error("run", e);
Integer maxZ = minMaxRecords.get(DSL.max(BLOCKS.POSZ));
Integer minZ = minMaxRecords.get(DSL.min(BLOCKS.POSZ));
if (errorCount > 100){
logger.error("Error-count: {}, bailing out!", errorCount);
return;
}
if (maxZ == null || minZ == null){
continue; //empty column
}
long now = System.currentTimeMillis();
long diff = now - lastProgressUpdate;
int zCount = maxZ - minZ;
if (diff > 5000){
//Report every 5 seconds
lastProgressUpdate = now;
for (int posz = minZ; posz <= maxZ; posz++) {
logger.info("Initial rendering status: x-progress({}%) z-progress({}%) tiles({}) data({} MB) posx({}) posz({})",
Math.floor(xProgress*100), Math.floor(zProgress*100), tileCount, Math.floor(byteCount/1000)/1000,
posx, posz
);
double zProgress = (posz - minZ) / (double) zCount;
CoordinateResolver.TileInfo tileInfo = CoordinateResolver.fromCoordinates(posx, posz);
try {
byte [] data = renderer.render(tileInfo.x, tileInfo.y, CoordinateResolver.ONE_TO_ONE_ZOOM);
byteCount += data.length;
tileCount++;
} catch (Exception e) {
errorCount++;
logger.error("run", e);
if (errorCount > 100) {
logger.error("Error-count: {}, bailing out!", errorCount);
return;
}
}
long now = System.currentTimeMillis();
long diff = now - lastProgressUpdate;
if (diff > 5000) {
//Report every 5 seconds
lastProgressUpdate = now;
logger.info("Initial rendering status: x-progress({}%) z-progress({}%) tiles({}) data({} MB) posx({}) posz({})",
Math.floor(xProgress * 100), Math.floor(zProgress * 100), tileCount, Math.floor(byteCount / 1000) / 1000,
posx, posz
);
//Explicit gc()
//Runtime.getRuntime().gc();
}
//Explicit gc()
Runtime.getRuntime().gc();
}
}
long renderTime = System.currentTimeMillis() - start;
logger.info("Initial rendering completed in {} seconds ({} MB, {} tiles)",
renderTime, Math.floor(byteCount / 1000) / 1000, tileCount
);
} catch (Exception e){
logger.error("run", e);
}
long renderTime = System.currentTimeMillis() - start;
logger.info("Initial rendering completed in {} seconds ({} MB, {} tiles)",
renderTime, Math.floor(byteCount/1000)/1000, tileCount
);
}
}

View File

@ -97,7 +97,7 @@ public class UpdateChangedTilesJob implements Runnable {
.limit(LIMIT)
.fetch();
if (blocks.size() == LIMIT){
if (blocks.size() == LIMIT) {
logger.warn("Got max-blocks ({}) from update-queue", LIMIT);
}
@ -105,11 +105,11 @@ public class UpdateChangedTilesJob implements Runnable {
List<String> updatedTileKeys = new ArrayList<>();
for (BlocksRecord record: blocks) {
for (BlocksRecord record : blocks) {
Integer x = record.getPosx();
Integer z = record.getPosz();
if (record.getMtime() > latestTimestamp){
if (record.getMtime() > latestTimestamp) {
//Update timestamp
latestTimestamp = record.getMtime();
}
@ -117,7 +117,7 @@ public class UpdateChangedTilesJob implements Runnable {
TileInfo tileInfo = CoordinateResolver.fromCoordinates(x, z);
//remove all tiles in every zoom
for (int i=CoordinateResolver.MAX_ZOOM; i>=CoordinateResolver.MIN_ZOOM; i--) {
for (int i = CoordinateResolver.MAX_ZOOM; i >= CoordinateResolver.MIN_ZOOM; i--) {
TileInfo zoomedTile = tileInfo.toZoom(i);
String tileKey = getTileKey(zoomedTile);
@ -140,6 +140,8 @@ public class UpdateChangedTilesJob implements Runnable {
}
} catch(Exception e){
logger.error("tile-updater", e);
} finally {
running = false;

View File

@ -24,7 +24,6 @@ public class TileRoute implements Route {
@Inject
public TileRoute(TileRenderer renderer, TileServerConfig cfg, TileCache cache) {
this.renderer = renderer;
this.executorService = Executors.newFixedThreadPool(cfg.tilerendererProcesses());
this.cache = cache;
}
@ -32,8 +31,6 @@ public class TileRoute implements Route {
private final TileCache cache;
private final ExecutorService executorService;
@Override
public Object handle(Request req, Response res) throws Exception {
res.header("Content-Type", "image/png");
@ -49,10 +46,7 @@ public class TileRoute implements Route {
logger.debug("Rendering tile @ {}/{} zoom: {}", x,y,z);
//dispatch rendering to fixed pool
Future<byte[]> future = executorService.submit(() -> renderer.render(x, y, z));
return future.get();
return renderer.render(x, y, z);
}
}

View File

@ -17,6 +17,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
@ -29,10 +30,9 @@ public class DatabaseTileCache implements TileCache {
private static final Logger logger = LoggerFactory.getLogger(DatabaseTileCache.class);
@Inject
public DatabaseTileCache(@TileDB DSLContext ctx, ExecutorService executor){
public DatabaseTileCache(@TileDB DSLContext ctx){
this.ctx = ctx;
this.lock = Striped.lazyWeakReadWriteLock(50);
this.executor = executor;
this.cache = CacheBuilder
.newBuilder()
@ -45,16 +45,14 @@ public class DatabaseTileCache implements TileCache {
private final Cache<String, byte[]> cache;
private final ExecutorService executor;
private final Striped<ReadWriteLock> lock;
private final DSLContext ctx;
private String getKey(int x, int y, int z){
return x + "/" + y + "/" + z;
}
private final Striped<ReadWriteLock> lock;
private ReadWriteLock getLock(int x, int y, int z){
return lock.get(getKey(x,y,z));
}
@ -63,68 +61,52 @@ public class DatabaseTileCache implements TileCache {
public void put(int x, int y, int z, byte[] data) {
final String key = getKey(x,y,z);
cache.put(key, data);
ReadWriteLock lock = getLock(x, y, z);
Lock writeLock = lock.writeLock();
writeLock.lock();
long now = System.currentTimeMillis();
TilesRecord record = ctx.newRecord(TILES);
record.setTile(data);
record.setX(x);
record.setY(y);
record.setZ(z);
record.setMtime(now);
AsyncTileInsertJob job = new AsyncTileInsertJob(record, lock);
executor.submit(job);
}
/**
* Async insert job
*/
public class AsyncTileInsertJob implements Runnable {
AsyncTileInsertJob(TilesRecord record, ReadWriteLock lock){
this.record = record;
this.lock = lock;
}
private final ReadWriteLock lock;
private final TilesRecord record;
@Override
public void run() {
Lock writeLock = lock.writeLock();
writeLock.lock();
try {
long now = System.currentTimeMillis();
try {
//re-check in critical section
if (has(record.getX(),record.getY(),record.getZ(), false)){
//already inserted
return;
}
TilesRecord record = ctx
.selectFrom(TILES)
.where(TILES.X.eq(x))
.and(TILES.Y.eq(y))
.and(TILES.Z.eq(z))
.fetchOne();
if (record == null) {
//Insert
record = ctx.newRecord(TILES);
record.setX(x);
record.setY(y);
record.setZ(z);
record.insert();
} finally {
writeLock.unlock();
long diff = System.currentTimeMillis() - now;
if (diff > 1000){
logger.warn("Insert of tile {}/{}/{} took {} ms", record.getX(), record.getY(), record.getZ(), diff);
}
}
//update
record.setTile(data);
record.setMtime(now);
record.store();
long diff = System.currentTimeMillis() - now;
if (diff > 1000){
logger.warn("Insert of tile {}/{}/{} took {} ms", record.getX(), record.getY(), record.getZ(), diff);
}
} finally {
writeLock.unlock();
}
}
@Override
public byte[] get(int x, int y, int z) {