New and improved ChunkManager

This commit is contained in:
kuroppoi 2021-10-18 00:12:07 +02:00
parent 71c747c050
commit 15e2c22d86
3 changed files with 182 additions and 86 deletions

View file

@ -1,84 +0,0 @@
package brainwine.gameserver.zone;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.msgpack.packer.BufferPacker;
import org.msgpack.unpacker.BufferUnpacker;
import brainwine.gameserver.msgpack.MessagePackHelper;
import brainwine.gameserver.util.ZipUtils;
/**
* TODO in the event of a load/save failure, the zone should shut-down.
*/
public class ChunkIOManager {
public static final int ALLOC_SIZE = 2048;
private static final Logger logger = LogManager.getLogger();
private final Zone zone;
private RandomAccessFile dataFile;
public ChunkIOManager(Zone zone) {
this.zone = zone;
File dataDir = new File("zones", zone.getDocumentId());
dataDir.mkdirs();
File blockDataFile = new File(dataDir, "blocks");
try {
if(!blockDataFile.exists()) {
blockDataFile.createNewFile();
}
dataFile = new RandomAccessFile(blockDataFile, "rw");
} catch(IOException e) {
logger.error("ChunkIOManager construction for zone {} failed", zone.getDocumentId(), e);
}
}
public void saveModifiedChunks() {
for(Chunk chunk : zone.getChunks()) {
if(chunk.isModified()) {
chunk.setModified(false);
saveChunk(chunk);
}
}
}
public void saveChunk(Chunk chunk) {
int index = zone.getChunkIndex(chunk.getX(), chunk.getY());
try {
BufferPacker packer = MessagePackHelper.createBufferPacker();
packer.write(chunk);
byte[] bytes = packer.toByteArray();
packer.close();
bytes = ZipUtils.deflateBytes(bytes);
dataFile.seek(index * ALLOC_SIZE);
dataFile.writeShort(bytes.length);
dataFile.write(bytes);
} catch(Exception e) {
logger.error("Could not save chunk {} of zone {}", index, zone.getDocumentId(), e);
}
}
public Chunk loadChunk(int index) {
Chunk chunk = null;
try {
dataFile.seek(index * ALLOC_SIZE);
byte[] bytes = new byte[dataFile.readShort()];
dataFile.read(bytes);
BufferUnpacker unpacker = MessagePackHelper.createBufferUnpacker(ZipUtils.inflateBytes(bytes));
chunk = unpacker.read(Chunk.class);
unpacker.close();
} catch(Exception e) {
logger.error("Could not load chunk {} of zone {}", index, zone.getDocumentId(), e);
}
return chunk;
}
}

View file

@ -0,0 +1,176 @@
package brainwine.gameserver.zone;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.Arrays;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.msgpack.unpacker.Unpacker;
import brainwine.gameserver.msgpack.MessagePackHelper;
import brainwine.gameserver.util.ZipUtils;
public class ChunkManager {
private static final Logger logger = LogManager.getLogger();
private static final String headerString = "brainwine blocks file";
private static final int latestVersion = 1;
private static final int dataOffset = headerString.length() + 4;
private static final int allocSize = 2048;
private static boolean conversionNotified;
private final Zone zone;
private RandomAccessFile file;
public ChunkManager(Zone zone) {
this.zone = zone;
try {
if(file == null) {
File blocksFile = new File(zone.getDirectory(), "blocks.dat");
File legacyBlocksFile = new File(zone.getDirectory(), "blocks");
if(!blocksFile.exists()) {
blocksFile.createNewFile();
}
file = new RandomAccessFile(blocksFile, "rw");
if(file.length() == 0) {
file.writeUTF(headerString);
file.writeInt(latestVersion);
if(legacyBlocksFile.exists()) {
if(!conversionNotified) {
logger.info("One or more block data files need to be converted. This might take a while ...");
conversionNotified = true;
}
convertLegacyBlocksFile(legacyBlocksFile);
}
} else {
if(!file.readUTF().equals(headerString)) {
throw new IOException("Invalid header string");
}
}
}
} catch(Exception e) {
logger.error("ChunkManager construction for zone {} failed", zone.getDocumentId(), e);
}
}
private void convertLegacyBlocksFile(File legacyBlocksFile) throws Exception {
byte[] bytes = Files.readAllBytes(legacyBlocksFile.toPath());
for(int i = 0; i < bytes.length; i += 2048) {
short length = (short)(((bytes[i] & 0xFF) << 8) + (bytes[i + 1] & 0xFF));
byte[] chunkBytes = ZipUtils.inflateBytes(Arrays.copyOfRange(bytes, i + 2, i + 2 + length));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
Unpacker unpacker = MessagePackHelper.createBufferUnpacker(chunkBytes);
unpacker.readArrayBegin();
int x = unpacker.readInt();
int y = unpacker.readInt();
int width = unpacker.readInt();
int height = unpacker.readInt();
dos.writeInt(x);
dos.writeInt(y);
dos.writeInt(width);
dos.writeInt(height);
unpacker.readArrayBegin();
for(int j = 0; j < width * height; j++) {
dos.writeInt(unpacker.readInt());
dos.writeInt(unpacker.readInt());
dos.writeInt(unpacker.readInt());
}
unpacker.close();
byte[] updatedBytes = ZipUtils.deflateBytes(baos.toByteArray());
dos.close();
file.seek(dataOffset + zone.getChunkIndex(x, y) * allocSize);
file.writeShort(updatedBytes.length);
file.write(updatedBytes);
}
}
public Chunk loadChunk(int index) {
Chunk chunk = null;
DataInputStream dis = null;
try {
file.seek(dataOffset + index * allocSize);
byte[] bytes = new byte[file.readShort()];
file.read(bytes);
dis = new DataInputStream(new ByteArrayInputStream(ZipUtils.inflateBytes(bytes)));
chunk = new Chunk(dis.readInt(), dis.readInt(), dis.readInt(), dis.readInt());
for(int i = 0; i < zone.getChunkWidth() * zone.getChunkHeight(); i++) {
chunk.setBlock(i, new Block(dis.readInt(), dis.readInt(), dis.readInt()));
}
} catch(Exception e) {
logger.error("Could not load chunk {} of zone {}", index, zone.getDocumentId(), e);
} finally {
if(dis != null) {
try {
dis.close();
} catch (IOException e) {
logger.warn("Resource could not be closed", e);
}
}
}
return chunk;
}
public void saveModifiedChunks() {
for(Chunk chunk : zone.getChunks()) {
if(chunk.isModified()) {
saveChunk(chunk);
}
}
}
public void saveChunk(Chunk chunk) {
DataOutputStream dos = null;
int index = zone.getChunkIndex(chunk.getX(), chunk.getY());
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(allocSize);
dos = new DataOutputStream(baos);
dos.writeInt(chunk.getX());
dos.writeInt(chunk.getY());
dos.writeInt(chunk.getWidth());
dos.writeInt(chunk.getHeight());
for(Block block : chunk.getBlocks()) {
dos.writeInt(block.getBase());
dos.writeInt(block.getBack());
dos.writeInt(block.getFront());
}
byte[] bytes = ZipUtils.deflateBytes(baos.toByteArray());
file.seek(dataOffset + index * allocSize);
file.writeShort(bytes.length);
file.write(bytes);
chunk.setModified(false);
} catch(Exception e) {
logger.error("Could not save chunk %s of zone %s", index, zone.getDocumentId(), e);
} finally {
if(dos != null) {
try {
dos.close();
} catch (IOException e) {
logger.warn("Resource could not be closed", e);
}
}
}
}
}

View file

@ -77,7 +77,7 @@ public class Zone {
private float cloudiness = 5000;
private float precipitation = 0;
private float acidity = 0;
private final ChunkIOManager chunkManager;
private final ChunkManager chunkManager;
private final Queue<DugBlock> digQueue = new ArrayDeque<>(); // TODO should be saved
private final Set<Integer> pendingSunlight = new HashSet<>();
private final Map<Integer, Entity> entities = new HashMap<>();
@ -105,12 +105,12 @@ public class Zone {
this.height = height;
this.chunkWidth = chunkWidth;
this.chunkHeight = chunkHeight;
chunkManager = new ChunkIOManager(this);
numChunksWidth = width / chunkWidth;
numChunksHeight = height / chunkHeight;
surface = new int[width];
sunlight = new int[width];
chunksExplored = new boolean[numChunksWidth * numChunksHeight];
chunkManager = new ChunkManager(this);
Arrays.fill(surface, height);
Arrays.fill(sunlight, height);
}
@ -839,6 +839,10 @@ public class Zone {
return chunksExplored[chunkIndex] = true;
}
public File getDirectory() {
return new File("zones", documentId);
}
/**
* @return A float between 0 and 1, where 0 is completely unexplored and 1 is fully explored.
*/