dynmap-neoforge/src/main/java/org/dynmap/TileHashManager.java

167 lines
6.4 KiB
Java

package org.dynmap;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.io.IOException;
import java.util.zip.CRC32;
import org.dynmap.utils.LRULinkedHashMap;
/**
* Image hash code manager - used to reduce compression and notification of updated tiles that do not actually yield new content
*
*/
public class TileHashManager {
private File tiledir; /* Base tile directory */
private boolean enabled;
/**
* Each tile hash file is a 32x32 tile grid, with each file having a CRC32 hash code generated from its pre-compression frame buffer
*/
private static class TileHashFile {
final String key;
final String subtype;
final int x; /* minimum tile coordinate / 32 */
final int y; /* minimum tile coordinate / 32 */
private File hf;
TileHashFile(String key, String subtype, int x, int y) {
this.key = key;
if(subtype != null)
this.subtype = subtype;
else
this.subtype = "";
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof TileHashFile))
return false;
TileHashFile fo = (TileHashFile)o;
return (x == fo.x) && (y == fo.y) && key.equals(fo.key) && (subtype.equals(fo.subtype));
}
@Override
public int hashCode() {
return key.hashCode() ^ subtype.hashCode() ^ (x << 16) ^ y;
}
public File getHashFile(File tiledir) {
if(hf == null) {
String k;
int idx = key.indexOf('.'); /* Find first '.' - world name split */
if(idx > 0)
k = key.substring(0, idx) + File.separatorChar + key.substring(idx+1);
else
k = key;
hf = new File(tiledir, k + (subtype.equals("")?"":("." + subtype)) + "_" + x + "_" + y + ".hash");
}
return hf;
}
/* Write to file */
public void writeToFile(File tiledir, byte[] crcbuf) {
try {
RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "rw");
fd.seek(0);
fd.write(crcbuf);
fd.close();
} catch (IOException iox) {
Log.severe("Error writing hash file - " + getHashFile(tiledir).getPath());
}
}
/* Read from file */
public void readFromFile(File tiledir, byte[] crcbuf) {
try {
RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "r");
fd.seek(0);
fd.read(crcbuf);
fd.close();
} catch (IOException iox) {
Arrays.fill(crcbuf, (byte)0xFF);
writeToFile(tiledir, crcbuf);
}
}
/* Read CRC */
public long getCRC(int tx, int ty, byte[] crcbuf) {
int off = (128 * (ty & 0x1F)) + (4 * (tx & 0x1F));
long crc = 0;
for(int i = 0; i < 4; i++)
crc = (crc << 8) + (0xFF & (int)crcbuf[off+i]);
return crc;
}
/* Set CRC */
public void setCRC(int tx, int ty, byte[] crcbuf, long crc) {
int off = (128 * (ty & 0x1F)) + (4 * (tx & 0x1F));
for(int i = 0; i < 4; i++)
crcbuf[off+i] = (byte)((crc >> ((3-i)*8)) & 0xFF);
}
}
private static final int MAX_CACHED_TILEHASHFILES = 25;
private Object lock = new Object();
private LRULinkedHashMap<TileHashFile, byte[]> tilehash = new LRULinkedHashMap<TileHashFile, byte[]>(MAX_CACHED_TILEHASHFILES);
private CRC32 crc32 = new CRC32();
private byte[] crcworkbuf = new byte[8192];
public TileHashManager(File tileroot, boolean enabled) {
tiledir = tileroot;
this.enabled = enabled;
}
/* Read cached hashcode for given tile */
public long getImageHashCode(String key, String subtype, int tx, int ty) {
if(!enabled) {
return -1; /* Return value that never matches */
}
TileHashFile thf = new TileHashFile(key, subtype, tx >> 5, ty >> 5);
synchronized(lock) {
byte[] crcbuf = tilehash.get(thf); /* See if we have it cached */
if(crcbuf == null) { /* If not in cache, load it */
crcbuf = new byte[32*32*4]; /* Get our space */
Arrays.fill(crcbuf, (byte)0xFF); /* Fill with -1 */
tilehash.put(thf, crcbuf); /* Add to cache */
thf.readFromFile(tiledir, crcbuf);
}
return thf.getCRC(tx & 0x1F, ty & 0x1F, crcbuf);
}
}
/* Calculate hash code for given buffer */
public long calculateTileHash(int[] newbuf) {
if(!enabled) {
return 0; /* Return value that doesn't match */
}
synchronized(lock) {
if(crcworkbuf.length < (4*newbuf.length)){
crcworkbuf = new byte[4*newbuf.length];
}
for(int i = 0, off = 0; i < newbuf.length; i++) {
int v = newbuf[i];
crcworkbuf[off++] = (byte)v;
crcworkbuf[off++] = (byte)(v>>8);
crcworkbuf[off++] = (byte)(v>>16);
crcworkbuf[off++] = (byte)(v>>24);
}
/* Calculate CRC-32 for buffer */
crc32.reset();
crc32.update(crcworkbuf, 0, 4*newbuf.length);
return crc32.getValue();
}
}
/* Update hashcode for given tile */
public void updateHashCode(String key, String subtype, int tx, int ty, long newcrc) {
if(!enabled)
return;
synchronized(lock) {
/* Now, find and check existing value */
TileHashFile thf = new TileHashFile(key, subtype, tx >> 5, ty >> 5);
byte[] crcbuf = tilehash.get(thf); /* See if we have it cached */
if(crcbuf == null) { /* If not in cache, load it */
crcbuf = new byte[32*32*4]; /* Get our space */
tilehash.put(thf, crcbuf); /* Add to cache */
thf.readFromFile(tiledir, crcbuf);
}
thf.setCRC(tx & 0x1F, ty & 0x1F, crcbuf, newcrc); /* Update field */
thf.writeToFile(tiledir, crcbuf); /* And write it out */
}
}
}