Formatted and broke the saving mechanism. I'm too tired to bugfix
- Refactored and formatted TestWorldDiskIO - Removed HashableVec3i - Added Coordinates methods for custom bit count - Properly reverted commit 98250cd - Known bugs: - Server shutdown close()s regions too early - Re-entering a world does not show saved changes
This commit is contained in:
parent
cd16334db8
commit
f4300558d5
@ -102,10 +102,8 @@ public class ChunkRender
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void render(ShapeRenderHelper renderer) {
|
public synchronized void render(ShapeRenderHelper renderer) {
|
||||||
if (!data.isEmpty) {
|
|
||||||
model.render(renderer);
|
model.render(renderer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void update() {
|
public synchronized void update() {
|
||||||
model.update();
|
model.update();
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package ru.windcorp.progressia.common.util;
|
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
|
||||||
|
|
||||||
public class HashableVec3i {
|
|
||||||
|
|
||||||
public Vec3i value;
|
|
||||||
|
|
||||||
public HashableVec3i(Vec3i inValue)
|
|
||||||
{
|
|
||||||
value = inValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() // Uses first 3 primes greater than 2**30
|
|
||||||
{
|
|
||||||
return 1073741827 * value.x + 1073741831 * value.y + 1073741833 * value.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object comparee)
|
|
||||||
{
|
|
||||||
if (comparee == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (comparee.getClass() != HashableVec3i.class)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HashableVec3i compareeCast = (HashableVec3i) comparee;
|
|
||||||
return compareeCast.value.x == value.x && compareeCast.value.y == value.y && compareeCast.value.z == value.z ;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -163,4 +163,71 @@ public class Coordinates {
|
|||||||
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
|
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generalized versions
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static int convertGlobalToCell(int bits, int global) {
|
||||||
|
return global >> bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vec3i convertGlobalToCell(
|
||||||
|
int bits,
|
||||||
|
Vec3i global,
|
||||||
|
Vec3i output
|
||||||
|
) {
|
||||||
|
if (output == null)
|
||||||
|
output = new Vec3i();
|
||||||
|
|
||||||
|
output.x = convertGlobalToCell(bits, global.x);
|
||||||
|
output.y = convertGlobalToCell(bits, global.y);
|
||||||
|
output.z = convertGlobalToCell(bits, global.z);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int convertGlobalToInCell(int bits, int global) {
|
||||||
|
int mask = (1 << bits) - 1;
|
||||||
|
return global & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vec3i convertGlobalToInCell(
|
||||||
|
int bits,
|
||||||
|
Vec3i global,
|
||||||
|
Vec3i output
|
||||||
|
) {
|
||||||
|
if (output == null)
|
||||||
|
output = new Vec3i();
|
||||||
|
|
||||||
|
output.x = convertGlobalToInCell(bits, global.x);
|
||||||
|
output.y = convertGlobalToInCell(bits, global.y);
|
||||||
|
output.z = convertGlobalToInCell(bits, global.z);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getGlobal(int bits, int cell, int inCell) {
|
||||||
|
return inCell | (cell << bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vec3i getGlobal(
|
||||||
|
int bits,
|
||||||
|
Vec3i cell,
|
||||||
|
Vec3i inCell,
|
||||||
|
Vec3i output
|
||||||
|
) {
|
||||||
|
if (output == null)
|
||||||
|
output = new Vec3i();
|
||||||
|
|
||||||
|
output.x = getGlobal(bits, cell.x, inCell.x);
|
||||||
|
output.y = getGlobal(bits, cell.y, inCell.y);
|
||||||
|
output.z = getGlobal(bits, cell.z, inCell.z);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isOnCellBorder(int bits, int inCell) {
|
||||||
|
return inCell == 0 || inCell == (1 << bits) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.world.block.BlockData;
|
import ru.windcorp.progressia.common.world.block.BlockData;
|
||||||
@ -46,11 +46,6 @@ public class DefaultChunkData implements ChunkData {
|
|||||||
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
|
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
|
||||||
public static final int CHUNK_RADIUS = BLOCKS_PER_CHUNK / 2;
|
public static final int CHUNK_RADIUS = BLOCKS_PER_CHUNK / 2;
|
||||||
|
|
||||||
public boolean isEmpty = false;
|
|
||||||
public boolean isOpaque = false;
|
|
||||||
|
|
||||||
public static HashSet<BlockData> transparent;
|
|
||||||
|
|
||||||
private final Vec3i position = new Vec3i();
|
private final Vec3i position = new Vec3i();
|
||||||
private final DefaultWorldData world;
|
private final DefaultWorldData world;
|
||||||
|
|
||||||
@ -205,44 +200,6 @@ public class DefaultChunkData implements ChunkData {
|
|||||||
this.generationHint = generationHint;
|
this.generationHint = generationHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void computeOpaque()
|
|
||||||
{
|
|
||||||
for (int xyz=0;xyz<BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK;xyz++)
|
|
||||||
{
|
|
||||||
if (transparent.contains( blocks[xyz]))
|
|
||||||
{
|
|
||||||
isOpaque = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isOpaque = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOpaque()
|
|
||||||
{
|
|
||||||
return isOpaque;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void computeEmpty()
|
|
||||||
{
|
|
||||||
BlockData air = new BlockData("Test:Air");
|
|
||||||
for (int xyz=0;xyz<BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK*BLOCKS_PER_CHUNK;xyz++)
|
|
||||||
{
|
|
||||||
if (blocks[xyz] != air)
|
|
||||||
{
|
|
||||||
isEmpty = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isEmpty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty()
|
|
||||||
{
|
|
||||||
return isEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link TileDataStack} used internally by
|
* Implementation of {@link TileDataStack} used internally by
|
||||||
* {@link DefaultChunkData} to
|
* {@link DefaultChunkData} to
|
||||||
|
@ -20,8 +20,7 @@ package ru.windcorp.progressia.server.management.load;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.Units;
|
import ru.windcorp.progressia.common.Units;
|
||||||
import ru.windcorp.progressia.common.world.generic.ChunkMap;
|
import ru.windcorp.progressia.common.world.generic.ChunkMap;
|
||||||
@ -45,8 +44,6 @@ public class ChunkRequestDaemon {
|
|||||||
private final ChunkSet toGenerate = ChunkSets.newHashSet();
|
private final ChunkSet toGenerate = ChunkSets.newHashSet();
|
||||||
private final ChunkSet toRequestUnload = ChunkSets.newHashSet();
|
private final ChunkSet toRequestUnload = ChunkSets.newHashSet();
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
private final Collection<Vec3i> buffer = new ArrayList<>();
|
private final Collection<Vec3i> buffer = new ArrayList<>();
|
||||||
|
|
||||||
private static class ChunkUnloadRequest {
|
private static class ChunkUnloadRequest {
|
||||||
@ -116,16 +113,16 @@ public class ChunkRequestDaemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processLoadQueues() {
|
private void processLoadQueues() {
|
||||||
toRequestUnload.forEach((pos) -> executor.submit(() -> scheduleUnload(pos)));
|
toRequestUnload.forEach(this::scheduleUnload);
|
||||||
toRequestUnload.clear();
|
toRequestUnload.clear();
|
||||||
|
|
||||||
toLoad.forEach((pos) -> executor.submit(() -> getChunkManager().loadOrGenerateChunk(pos)));
|
toLoad.forEach(getChunkManager()::loadOrGenerateChunk);
|
||||||
toLoad.clear();
|
toLoad.clear();
|
||||||
|
|
||||||
toGenerate.forEach((pos) -> executor.submit(() -> getChunkManager().loadOrGenerateChunk(pos)));
|
toGenerate.forEach(getChunkManager()::loadOrGenerateChunk);
|
||||||
toGenerate.clear();
|
toGenerate.clear();
|
||||||
|
|
||||||
executor.submit(() -> unloadScheduledChunks());
|
unloadScheduledChunks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleUnload(Vec3i chunkPos) {
|
private void scheduleUnload(Vec3i chunkPos) {
|
||||||
|
@ -56,9 +56,6 @@ public class PlanetFeatureGenerator {
|
|||||||
generateBorderFeatures(server, chunk);
|
generateBorderFeatures(server, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.computeEmpty();
|
|
||||||
chunk.computeOpaque();
|
|
||||||
|
|
||||||
chunk.setGenerationHint(true);
|
chunk.setGenerationHint(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
249
src/main/java/ru/windcorp/progressia/test/region/Region.java
Normal file
249
src/main/java/ru/windcorp/progressia/test/region/Region.java
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test.region;
|
||||||
|
|
||||||
|
import static ru.windcorp.progressia.test.region.TestWorldDiskIO.REGION_DIAMETER;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
import glm.vec._3.i.Vec3i;
|
||||||
|
import ru.windcorp.progressia.common.state.IOContext;
|
||||||
|
import ru.windcorp.progressia.common.world.DecodingException;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.ChunkMap;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
|
||||||
|
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
||||||
|
import ru.windcorp.progressia.server.Server;
|
||||||
|
|
||||||
|
public class Region {
|
||||||
|
|
||||||
|
private static final boolean RESET_CORRUPTED = true;
|
||||||
|
|
||||||
|
// 1 MiB
|
||||||
|
private static final int MAX_CHUNK_SIZE = 1024 * 1024;
|
||||||
|
private static final int SECTOR_SIZE = MAX_CHUNK_SIZE / 256;
|
||||||
|
|
||||||
|
private static final int HEADER_SIZE = Integer.BYTES * REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
|
||||||
|
|
||||||
|
private final RandomAccessFile file;
|
||||||
|
|
||||||
|
private final ChunkMap<Integer> offsets = ChunkMaps.newHashMap();
|
||||||
|
private final ChunkMap<Integer> lengths = ChunkMaps.newHashMap();
|
||||||
|
|
||||||
|
public Region(RandomAccessFile file) throws IOException {
|
||||||
|
this.file = file;
|
||||||
|
|
||||||
|
try {
|
||||||
|
confirmHeaderHealth();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
TestWorldDiskIO.LOG.debug("Uh the file broke");
|
||||||
|
if (RESET_CORRUPTED) {
|
||||||
|
byte headerBytes[] = new byte[HEADER_SIZE];
|
||||||
|
Arrays.fill(headerBytes, (byte) 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.write(headerBytes);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e.addSuppressed(e1);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RandomAccessFile getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
this.file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset(Vec3i chunkLoc) {
|
||||||
|
return offsets.get(chunkLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOffset(Vec3i pos) {
|
||||||
|
return offsets.containsKey(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putOffset(Vec3i pos, int offset) {
|
||||||
|
offsets.put(pos, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength(Vec3i chunkLoc) {
|
||||||
|
return lengths.get(chunkLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLength(Vec3i pos) {
|
||||||
|
return lengths.containsKey(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putLength(Vec3i pos, int length) {
|
||||||
|
lengths.put(pos, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmHeaderHealth() throws IOException {
|
||||||
|
|
||||||
|
Set<Integer> used = new HashSet<Integer>();
|
||||||
|
final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
|
||||||
|
|
||||||
|
file.seek(0);
|
||||||
|
|
||||||
|
if (file.length() < HEADER_SIZE) {
|
||||||
|
throw new IOException("File is too short to contain a header");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < chunksPerRegion; i++) {
|
||||||
|
int offset = file.readInt();
|
||||||
|
|
||||||
|
int sectorLength = file.read();
|
||||||
|
if (sectorLength == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3i pos = new Vec3i();
|
||||||
|
pos.x = i / REGION_DIAMETER / REGION_DIAMETER;
|
||||||
|
pos.y = (i / REGION_DIAMETER) % REGION_DIAMETER;
|
||||||
|
pos.z = i % REGION_DIAMETER;
|
||||||
|
|
||||||
|
offsets.put(pos, offset);
|
||||||
|
lengths.put(pos, sectorLength);
|
||||||
|
|
||||||
|
for (int sector = 0; sector < sectorLength; sector++) {
|
||||||
|
if (!used.add(offset + sector)) {
|
||||||
|
throw new IOException("A sector is used twice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(DefaultChunkData chunk, Server server) throws IOException {
|
||||||
|
Vec3i pos = TestWorldDiskIO.getInRegionCoords(chunk.getPosition());
|
||||||
|
int definitionOffset = Integer.BYTES * (pos.z + REGION_DIAMETER * (pos.y + REGION_DIAMETER * pos.x));
|
||||||
|
|
||||||
|
if (!hasOffset(pos)) {
|
||||||
|
allocateChunk(definitionOffset, pos);
|
||||||
|
}
|
||||||
|
int dataOffset = getOffset(pos);
|
||||||
|
|
||||||
|
byte[] buffer = saveToBuffer(chunk, server);
|
||||||
|
writeBuffer(buffer, definitionOffset, dataOffset, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] saveToBuffer(DefaultChunkData chunk, Server server) throws IOException {
|
||||||
|
ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
|
||||||
|
try (
|
||||||
|
DataOutputStream dataStream = new DataOutputStream(
|
||||||
|
new DeflaterOutputStream(
|
||||||
|
new BufferedOutputStream(arrayStream)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ChunkIO.save(chunk, dataStream, IOContext.SAVE);
|
||||||
|
TestWorldDiskIO.writeGenerationHint(chunk, dataStream, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBuffer(byte[] buffer, int definitionOffset, int dataOffset, Vec3i pos) throws IOException {
|
||||||
|
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
||||||
|
file.write(buffer);
|
||||||
|
|
||||||
|
file.seek(definitionOffset + Integer.BYTES - 1);
|
||||||
|
|
||||||
|
int sectors = buffer.length / SECTOR_SIZE + 1;
|
||||||
|
file.write(sectors);
|
||||||
|
|
||||||
|
putLength(pos, sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void allocateChunk(int definitionOffset, Vec3i pos) throws IOException {
|
||||||
|
int outputLen = (int) file.length();
|
||||||
|
|
||||||
|
int dataOffset = (int) (outputLen - HEADER_SIZE) / SECTOR_SIZE + 1;
|
||||||
|
|
||||||
|
file.seek(definitionOffset);
|
||||||
|
file.writeInt(dataOffset);
|
||||||
|
|
||||||
|
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
|
||||||
|
putOffset(pos, dataOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server)
|
||||||
|
throws IOException,
|
||||||
|
DecodingException {
|
||||||
|
|
||||||
|
int dataOffset = 0;
|
||||||
|
int sectorLength = 0;
|
||||||
|
Vec3i pos = TestWorldDiskIO.getInRegionCoords(chunkPos);
|
||||||
|
|
||||||
|
if (hasOffset(pos)) {
|
||||||
|
dataOffset = getOffset(pos);
|
||||||
|
sectorLength = getLength(pos);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = readBuffer(dataOffset, sectorLength);
|
||||||
|
DefaultChunkData result = loadFromBuffer(buffer, chunkPos, world, server);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultChunkData loadFromBuffer(byte[] buffer, Vec3i chunkPos, DefaultWorldData world, Server server)
|
||||||
|
throws IOException,
|
||||||
|
DecodingException {
|
||||||
|
|
||||||
|
DataInputStream dataStream = new DataInputStream(
|
||||||
|
new InflaterInputStream(
|
||||||
|
new BufferedInputStream(
|
||||||
|
new ByteArrayInputStream(buffer)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DefaultChunkData result = ChunkIO.load(world, chunkPos, dataStream, IOContext.SAVE);
|
||||||
|
TestWorldDiskIO.readGenerationHint(result, dataStream, server);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readBuffer(int dataOffset, int sectorLength) throws IOException {
|
||||||
|
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
|
||||||
|
|
||||||
|
byte buffer[] = new byte[SECTOR_SIZE * sectorLength];
|
||||||
|
file.read(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
@ -18,792 +18,149 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.test.region;
|
package ru.windcorp.progressia.test.region;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Scanner;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.zip.DeflaterOutputStream;
|
|
||||||
import java.util.zip.InflaterInputStream;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.common.state.IOContext;
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
import ru.windcorp.progressia.common.util.HashableVec3i;
|
|
||||||
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
import ru.windcorp.progressia.common.world.DefaultChunkData;
|
||||||
|
import ru.windcorp.progressia.common.world.Coordinates;
|
||||||
import ru.windcorp.progressia.common.world.DecodingException;
|
import ru.windcorp.progressia.common.world.DecodingException;
|
||||||
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
import ru.windcorp.progressia.common.world.DefaultWorldData;
|
||||||
import ru.windcorp.progressia.common.world.io.ChunkIO;
|
import ru.windcorp.progressia.common.world.generic.ChunkMap;
|
||||||
|
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
|
||||||
import ru.windcorp.progressia.server.Server;
|
import ru.windcorp.progressia.server.Server;
|
||||||
import ru.windcorp.progressia.server.world.io.WorldContainer;
|
import ru.windcorp.progressia.server.world.io.WorldContainer;
|
||||||
|
|
||||||
public class TestWorldDiskIO implements WorldContainer {
|
public class TestWorldDiskIO implements WorldContainer {
|
||||||
|
|
||||||
private static final boolean resetCorrupted = true;
|
private static final boolean ENABLE = true;
|
||||||
|
|
||||||
public class RandomFileMapped {
|
private static final String FILE_NAME_FORMAT = "region_%d_%d_%d.progressia_region";
|
||||||
public RandomAccessFile file;
|
|
||||||
public HashMap<HashableVec3i, Integer> offsets;
|
|
||||||
public HashMap<HashableVec3i, Integer> lengths;
|
|
||||||
|
|
||||||
public RandomFileMapped(RandomAccessFile inFile)
|
private static final int BITS_IN_CHUNK_COORDS = 4;
|
||||||
{
|
public static final int REGION_DIAMETER = 1 << BITS_IN_CHUNK_COORDS;
|
||||||
boolean check = false;
|
|
||||||
offsets = new HashMap<>();
|
public static Vec3i getRegionCoords(Vec3i chunkCoords) {
|
||||||
lengths = new HashMap<>();
|
return Coordinates.convertGlobalToCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
|
||||||
try {
|
|
||||||
check = confirmHeaderHealth(inFile, offsets, lengths);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
if (!check)
|
|
||||||
{
|
|
||||||
LOG.debug("Uh the file broke");
|
|
||||||
if (resetCorrupted) {
|
|
||||||
byte headerBytes[] = new byte[4 * chunksPerRegion];
|
|
||||||
for (int i = 0; i < 4 * chunksPerRegion; i++) {
|
|
||||||
headerBytes[i] = (byte) 0;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
inFile.write(headerBytes);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Vec3i getInRegionCoords(Vec3i chunkCoords) {
|
||||||
file = inFile;
|
return Coordinates.convertGlobalToInCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOffset(Vec3i chunkLoc)
|
static final Logger LOG = LogManager.getLogger();
|
||||||
{
|
|
||||||
return offsets.get(new HashableVec3i(chunkLoc));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasOffset(Vec3i pos) {
|
private final Path path;
|
||||||
return offsets.containsKey(new HashableVec3i(pos));
|
private final ChunkMap<Region> regions = ChunkMaps.newHashMap();
|
||||||
}
|
|
||||||
|
|
||||||
public void putOffset(Vec3i pos, int offset)
|
public TestWorldDiskIO(Path path) {
|
||||||
{
|
this.path = path;
|
||||||
offsets.put(new HashableVec3i(pos), offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLength(Vec3i chunkLoc)
|
|
||||||
{
|
|
||||||
return lengths.get(new HashableVec3i(chunkLoc));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasLength(Vec3i pos) {
|
|
||||||
return lengths.containsKey(new HashableVec3i(pos));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putLength(Vec3i pos, int length)
|
|
||||||
{
|
|
||||||
lengths.put(new HashableVec3i(pos), length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path SAVE_DIR = Paths.get("tmp_world");
|
|
||||||
private static final String formatFile = "world.format";
|
|
||||||
private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
|
|
||||||
|
|
||||||
private HashMap<HashableVec3i, RandomFileMapped> inOutMap;
|
|
||||||
private static final boolean ENABLE = false;
|
|
||||||
|
|
||||||
private static int maxSize = 1048576;
|
|
||||||
private static int sectorSize = maxSize / 256;
|
|
||||||
|
|
||||||
private static final int bestFormat = 65536;
|
|
||||||
|
|
||||||
// private Map<Vec3i,Vec3i> regions = new HashMap<Vec3i,Vec3i>();
|
|
||||||
private Vec3i regionSize;
|
|
||||||
private int chunksPerRegion;
|
|
||||||
|
|
||||||
private int currentFormat = -1;
|
|
||||||
private String extension = ".null";
|
|
||||||
|
|
||||||
private static int natFromInt(int loc) {
|
|
||||||
if (loc < 0)
|
|
||||||
return -2*loc - 1;
|
|
||||||
return 2*loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* private static int intFromNat(int loc) // Possibly unused
|
|
||||||
* {
|
|
||||||
* if ((loc & 1) == 1)
|
|
||||||
* return -loc >> 1;
|
|
||||||
* return loc >> 1;
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
private Vec3i getRegion(Vec3i chunkLoc) {
|
|
||||||
int x = chunkLoc.x;
|
|
||||||
if (x<0)
|
|
||||||
{
|
|
||||||
x /= regionSize.x;
|
|
||||||
x--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
x /= regionSize.x;
|
|
||||||
}
|
|
||||||
int y = chunkLoc.y;
|
|
||||||
if (y<0)
|
|
||||||
{
|
|
||||||
y /= regionSize.y;
|
|
||||||
y--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
y /= regionSize.y;
|
|
||||||
}
|
|
||||||
int z = chunkLoc.z;
|
|
||||||
if (z<0)
|
|
||||||
{
|
|
||||||
z /= regionSize.z;
|
|
||||||
z--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
z /= regionSize.z;
|
|
||||||
}
|
|
||||||
return new Vec3i(
|
|
||||||
natFromInt(x),
|
|
||||||
natFromInt(y),
|
|
||||||
natFromInt(z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int mod(int a, int m) {
|
|
||||||
return ((a % m) + m) % m;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vec3i getRegionLoc(Vec3i chunkLoc) {
|
|
||||||
return new Vec3i(mod(chunkLoc.x, regionSize.x), mod(chunkLoc.y, regionSize.y), mod(chunkLoc.z, regionSize.z));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestWorldDiskIO(Path worldPath) {
|
|
||||||
if (worldPath != null) {
|
|
||||||
SAVE_DIR = worldPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// regions.put(new Vec3i(0,0,0), new Vec3i(1,1,1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public static int getAvailableSector(MappedByteBuffer mbb)
|
|
||||||
{
|
|
||||||
int sectorsUsed = 0;
|
|
||||||
for (int i=offsetBytes; i<(offsetBytes+1)*chunksPerRegion; i+= (offsetBytes+1))
|
|
||||||
{
|
|
||||||
|
|
||||||
sectorsUsed += mbb.get(i);
|
|
||||||
}
|
|
||||||
return sectorsUsed;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private void setRegionSize(int format) {
|
|
||||||
inOutMap = new HashMap<HashableVec3i, RandomFileMapped>();
|
|
||||||
switch (format) {
|
|
||||||
case 65536:
|
|
||||||
default:
|
|
||||||
regionSize = new Vec3i(16);
|
|
||||||
chunksPerRegion = 16 * 16 * 16;
|
|
||||||
currentFormat = 65536;
|
|
||||||
extension = ".progressia_region";
|
|
||||||
break;
|
|
||||||
case 65537:
|
|
||||||
regionSize = new Vec3i(16);
|
|
||||||
chunksPerRegion = 16 * 16 * 16;
|
|
||||||
currentFormat = 65536;
|
|
||||||
extension = ".progressia_regionx";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean confirmHeaderHealth(RandomAccessFile input, HashMap<HashableVec3i, Integer> offsets, HashMap<HashableVec3i, Integer> length) throws IOException
|
|
||||||
{
|
|
||||||
Set<Integer> used = new HashSet<Integer>();
|
|
||||||
input.seek(0);
|
|
||||||
if (input.length() < 4*chunksPerRegion)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int i=0;i<4*chunksPerRegion;i+=4)
|
|
||||||
{
|
|
||||||
int offset = 0;
|
|
||||||
for (int ii = 0; ii < 3; ii++) {
|
|
||||||
offset *= 256;
|
|
||||||
offset += input.read();
|
|
||||||
}
|
|
||||||
int sectorLength = input.read();
|
|
||||||
if (sectorLength==0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int headerPos = i/4;
|
|
||||||
int x = headerPos/regionSize.y/regionSize.z;
|
|
||||||
int y = (headerPos/regionSize.z)%regionSize.y;
|
|
||||||
int z = headerPos%regionSize.z;
|
|
||||||
HashableVec3i key = new HashableVec3i(new Vec3i(x,y,z));
|
|
||||||
offsets.put(key , offset);
|
|
||||||
length.put(key, sectorLength);
|
|
||||||
for (int ii=0;ii<sectorLength;ii++)
|
|
||||||
{
|
|
||||||
if (used.contains(offset+ii))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
used.add(offset+ii);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(DefaultChunkData chunk, DefaultWorldData world, Server server) {
|
|
||||||
if (!ENABLE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (currentFormat == 65536) {
|
|
||||||
LOG.debug(
|
|
||||||
"Saving {} {} {}",
|
|
||||||
chunk.getPosition().x,
|
|
||||||
chunk.getPosition().y,
|
|
||||||
chunk.getPosition().z
|
|
||||||
);
|
|
||||||
|
|
||||||
Files.createDirectories(SAVE_DIR);
|
|
||||||
|
|
||||||
Vec3i saveCoords = getRegion(chunk.getPosition());
|
|
||||||
|
|
||||||
Path path = SAVE_DIR.resolve(
|
|
||||||
String.format(
|
|
||||||
"%d_%d_%d" + extension,
|
|
||||||
saveCoords.x,
|
|
||||||
saveCoords.y,
|
|
||||||
saveCoords.z
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
RandomFileMapped outputMap = inOutMap.get(new HashableVec3i(saveCoords));
|
|
||||||
//LOG.info("saveCoords {},{},{}", saveCoords.x, saveCoords.y, saveCoords.z);
|
|
||||||
if (outputMap == null)
|
|
||||||
{
|
|
||||||
outputMap = makeNew(path, new HashableVec3i(saveCoords));
|
|
||||||
}
|
|
||||||
RandomAccessFile output = outputMap.file;
|
|
||||||
|
|
||||||
Vec3i pos = getRegionLoc(chunk.getPosition());
|
|
||||||
int shortOffset = 4 * (pos.z + regionSize.z * (pos.y + regionSize.y * pos.x));
|
|
||||||
int fullOffset = 4 * (chunksPerRegion);
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
if (outputMap.hasOffset(pos))
|
|
||||||
{
|
|
||||||
offset = outputMap.getOffset(pos);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
output.seek(shortOffset);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
offset *= 256;
|
|
||||||
offset += output.read();
|
|
||||||
}
|
|
||||||
int sectorLength = output.read();
|
|
||||||
if (sectorLength == 0) {
|
|
||||||
int outputLen = (int) output.length();
|
|
||||||
offset = (int) (outputLen - fullOffset) / sectorSize + 1;
|
|
||||||
int tempOffset = offset;
|
|
||||||
output.seek(shortOffset);
|
|
||||||
|
|
||||||
byte readOffset[] = new byte[3];
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
readOffset[2 - i] = (byte) (tempOffset % 256);
|
|
||||||
tempOffset >>= 8;
|
|
||||||
}
|
|
||||||
output.write(readOffset);
|
|
||||||
|
|
||||||
output.setLength(fullOffset + offset * sectorSize);
|
|
||||||
}
|
|
||||||
outputMap.putOffset(pos, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream tempDataStream = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream trueOutput = new DataOutputStream(
|
|
||||||
new DeflaterOutputStream(
|
|
||||||
new BufferedOutputStream(tempDataStream)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ChunkIO.save(chunk, trueOutput, IOContext.SAVE);
|
|
||||||
writeGenerationHint(chunk, trueOutput, server);
|
|
||||||
|
|
||||||
trueOutput.close();
|
|
||||||
|
|
||||||
byte tempData[] = tempDataStream.toByteArray();
|
|
||||||
|
|
||||||
output.seek( fullOffset + sectorSize * offset);
|
|
||||||
output.write(tempData);
|
|
||||||
|
|
||||||
output.seek(shortOffset + 3);
|
|
||||||
output.write(tempData.length / sectorSize + 1);
|
|
||||||
outputMap.putLength(pos, tempData.length / sectorSize + 1);
|
|
||||||
// LOG.info("Used {} sectors",(int)
|
|
||||||
// tempData.length/sectorSize + 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (currentFormat == 65537) {
|
|
||||||
LOG.debug(
|
|
||||||
"Saving {} {} {}",
|
|
||||||
chunk.getPosition().x,
|
|
||||||
chunk.getPosition().y,
|
|
||||||
chunk.getPosition().z
|
|
||||||
);
|
|
||||||
|
|
||||||
Files.createDirectories(SAVE_DIR);
|
|
||||||
|
|
||||||
Vec3i saveCoords = getRegion(chunk.getPosition());
|
|
||||||
|
|
||||||
Path path = SAVE_DIR.resolve(
|
|
||||||
String.format(
|
|
||||||
"%d_%d_%d" + extension,
|
|
||||||
saveCoords.x,
|
|
||||||
saveCoords.y,
|
|
||||||
saveCoords.z
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
RandomFileMapped outputMap = inOutMap.get(new HashableVec3i(saveCoords));
|
|
||||||
//LOG.info("saveCoords {},{},{}", saveCoords.x, saveCoords.y, saveCoords.z);
|
|
||||||
if (outputMap == null)
|
|
||||||
{
|
|
||||||
outputMap = makeNew(path, new HashableVec3i(saveCoords));
|
|
||||||
}
|
|
||||||
RandomAccessFile output = outputMap.file;
|
|
||||||
|
|
||||||
Vec3i pos = getRegionLoc(chunk.getPosition());
|
|
||||||
int shortOffset = 4 * (pos.z + regionSize.z * (pos.y + regionSize.y * pos.x));
|
|
||||||
int fullOffset = 4 * (chunksPerRegion);
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
if (outputMap.hasOffset(pos))
|
|
||||||
{
|
|
||||||
offset = outputMap.getOffset(pos);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
output.seek(shortOffset);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
offset *= 256;
|
|
||||||
offset += output.read();
|
|
||||||
}
|
|
||||||
int sectorLength = output.read();
|
|
||||||
if (sectorLength == 0) {
|
|
||||||
int outputLen = (int) output.length();
|
|
||||||
offset = (int) (outputLen - fullOffset) / sectorSize + 1;
|
|
||||||
int tempOffset = offset;
|
|
||||||
output.seek(shortOffset);
|
|
||||||
|
|
||||||
byte readOffset[] = new byte[3];
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
readOffset[2 - i] = (byte) (tempOffset % 256);
|
|
||||||
tempOffset >>= 8;
|
|
||||||
}
|
|
||||||
output.write(readOffset);
|
|
||||||
|
|
||||||
output.setLength(fullOffset + offset * sectorSize);
|
|
||||||
}
|
|
||||||
outputMap.putOffset(pos, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream tempDataStream = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream trueOutput = new DataOutputStream(
|
|
||||||
new DeflaterOutputStream(
|
|
||||||
new BufferedOutputStream(tempDataStream)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ChunkIO.save(chunk, trueOutput, IOContext.SAVE);
|
|
||||||
writeGenerationHint(chunk, trueOutput, server);
|
|
||||||
|
|
||||||
trueOutput.close();
|
|
||||||
|
|
||||||
byte tempData[] = tempDataStream.toByteArray();
|
|
||||||
|
|
||||||
output.seek( fullOffset + sectorSize * offset);
|
|
||||||
|
|
||||||
chunk.computeOpaque();
|
|
||||||
chunk.computeEmpty();
|
|
||||||
output.write((chunk.isOpaque() ? 1 : 0) << 1 + (chunk.isEmpty() ? 1 : 0)); //Writes extra flag byte of whether or not the chunk is empty or solid
|
|
||||||
output.write(tempData);
|
|
||||||
|
|
||||||
output.seek(shortOffset + 3);
|
|
||||||
output.write(tempData.length / sectorSize + 1);
|
|
||||||
outputMap.putLength(pos, tempData.length / sectorSize + 1);
|
|
||||||
// LOG.info("Used {} sectors",(int)
|
|
||||||
// tempData.length/sectorSize + 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
// else if (currentFormat)
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RandomFileMapped makeNew(Path path, Object hashObj) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
|
|
||||||
//FileChannel fc = raf.getChannel();
|
|
||||||
//MappedByteBuffer output = fc.map(FileChannel.MapMode.READ_WRITE, 0, maxSize*chunksPerRegion);
|
|
||||||
//output.limit(maxSize*chunksPerRegion);
|
|
||||||
RandomFileMapped rfm = new RandomFileMapped(raf);
|
|
||||||
inOutMap.put((HashableVec3i) hashObj, rfm);
|
|
||||||
return rfm;
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
LOG.warn("bad things");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
|
|
||||||
throws IOException {
|
|
||||||
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) {
|
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) {
|
||||||
if (!ENABLE)
|
if (!ENABLE) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentFormat == -1) {
|
|
||||||
Path formatPath = SAVE_DIR.resolve(formatFile);
|
|
||||||
File format = formatPath.toFile();
|
|
||||||
|
|
||||||
if (format.exists()) {
|
|
||||||
String data = null;
|
|
||||||
try {
|
try {
|
||||||
Scanner reader = new Scanner(format);
|
|
||||||
|
|
||||||
data = reader.next();
|
Region region = getRegion(chunkPos, false);
|
||||||
|
if (region == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
reader.close();
|
DefaultChunkData result = region.load(chunkPos, world, server);
|
||||||
} catch (FileNotFoundException e) {
|
return result;
|
||||||
// TODO Auto-generated catch block
|
|
||||||
|
} catch (IOException | DecodingException e) {
|
||||||
|
LOG.warn(
|
||||||
|
"Failed to load chunk {} {} {}",
|
||||||
|
chunkPos.x,
|
||||||
|
chunkPos.y,
|
||||||
|
chunkPos.z
|
||||||
|
);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] formatBytes = data.getBytes();
|
@Override
|
||||||
int formatNum = formatBytes[0] * 256 * 256 * 256 + formatBytes[1] * 256 * 256 + formatBytes[2] * 256
|
public void save(DefaultChunkData chunk, DefaultWorldData world, Server server) {
|
||||||
+ formatBytes[3];
|
if (!ENABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setRegionSize(formatNum);
|
|
||||||
} else {
|
|
||||||
setRegionSize(bestFormat);
|
|
||||||
|
|
||||||
LOG.debug("Making new world with format {}", bestFormat);
|
|
||||||
|
|
||||||
BufferedWriter bw;
|
|
||||||
try {
|
try {
|
||||||
bw = new BufferedWriter(new FileWriter(format));
|
LOG.debug(
|
||||||
|
"Saving {} {} {}",
|
||||||
|
chunk.getPosition().x,
|
||||||
|
chunk.getPosition().y,
|
||||||
|
chunk.getPosition().z
|
||||||
|
);
|
||||||
|
|
||||||
int bfClone = bestFormat;
|
Region region = getRegion(chunk.getPosition(), true);
|
||||||
|
region.save(chunk, server);
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
bw.write(bfClone >> 24);
|
|
||||||
LOG.debug(bfClone >> 24);
|
|
||||||
bfClone = bfClone << 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* bw.write(
|
|
||||||
* new char[] {
|
|
||||||
* (char) bestFormat / (256 * 256 * 256),
|
|
||||||
* (char) (bestFormat % 256) / (256 * 256),
|
|
||||||
* (char) (bestFormat % (256 * 256)) / (256),
|
|
||||||
* (char) (bestFormat % (256 * 256 * 256)) }
|
|
||||||
* );
|
|
||||||
*/
|
|
||||||
|
|
||||||
bw.close();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Auto-generated catch block
|
LOG.warn(
|
||||||
|
"Failed to save chunk {} {} {}",
|
||||||
|
chunk.getPosition().x,
|
||||||
|
chunk.getPosition().y,
|
||||||
|
chunk.getPosition().z
|
||||||
|
);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentFormat == 65536) {
|
private Region getRegion(Vec3i position, boolean createIfMissing) throws IOException {
|
||||||
Vec3i saveCoords = getRegion(chunkPos);
|
if (regions.isEmpty()) {
|
||||||
|
Files.createDirectories(getPath());
|
||||||
|
}
|
||||||
|
|
||||||
Path path = SAVE_DIR.resolve(
|
Vec3i regionCoords = getRegionCoords(position);
|
||||||
|
|
||||||
|
Region region = regions.get(regionCoords);
|
||||||
|
if (region == null) {
|
||||||
|
|
||||||
|
Path path = getPath().resolve(
|
||||||
String.format(
|
String.format(
|
||||||
"%d_%d_%d" + extension,
|
FILE_NAME_FORMAT,
|
||||||
saveCoords.x,
|
regionCoords.x,
|
||||||
saveCoords.y,
|
regionCoords.y,
|
||||||
saveCoords.z
|
regionCoords.z
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!Files.exists(path)) {
|
if (!Files.exists(path) && !createIfMissing) {
|
||||||
LOG.debug(
|
|
||||||
"Not found {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
region = openRegion(path, regionCoords);
|
||||||
DefaultChunkData result = loadRegion(path, chunkPos, world, server);
|
|
||||||
|
|
||||||
LOG.debug(
|
|
||||||
"Loaded {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
LOG.debug(
|
|
||||||
"Could not load {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentFormat == 65537) {
|
|
||||||
Vec3i saveCoords = getRegion(chunkPos);
|
|
||||||
|
|
||||||
Path path = SAVE_DIR.resolve(
|
|
||||||
String.format(
|
|
||||||
"%d_%d_%d" + extension,
|
|
||||||
saveCoords.x,
|
|
||||||
saveCoords.y,
|
|
||||||
saveCoords.z
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!Files.exists(path)) {
|
|
||||||
LOG.debug(
|
|
||||||
"Not found {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return region;
|
||||||
DefaultChunkData result = loadRegionX(path, chunkPos, world, server);
|
|
||||||
|
|
||||||
LOG.debug(
|
|
||||||
"Loaded {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
LOG.debug(
|
|
||||||
"Could not load {} {} {}",
|
|
||||||
chunkPos.x,
|
|
||||||
chunkPos.y,
|
|
||||||
chunkPos.z
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultChunkData loadRegion(Path path, Vec3i chunkPos, DefaultWorldData world, Server server)
|
private Region openRegion(Path path, Vec3i regionCoords) throws IOException {
|
||||||
throws IOException,
|
RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
|
||||||
DecodingException {
|
Region region = new Region(raf);
|
||||||
int offset = 0;
|
regions.put(regionCoords, region);
|
||||||
int sectorLength = 0;
|
return region;
|
||||||
Vec3i pos;
|
|
||||||
RandomFileMapped inputMap;
|
|
||||||
int fullOffset = 4 * (chunksPerRegion);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Vec3i streamCoords = getRegion(chunkPos);
|
|
||||||
|
|
||||||
inputMap = inOutMap.get(new HashableVec3i(streamCoords));
|
|
||||||
//LOG.info("streamCoords {},{},{}", streamCoords.x,streamCoords.y,streamCoords.z);
|
|
||||||
if (inputMap == null)
|
|
||||||
{
|
|
||||||
//input = new RandomAccessFile(path.toFile(), "rw");
|
|
||||||
//input = Files.newByteChannel(path);
|
|
||||||
inputMap = makeNew(path, new HashableVec3i(streamCoords));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RandomAccessFile input = inputMap.file;
|
static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
|
||||||
|
throws IOException {
|
||||||
|
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
|
||||||
pos = getRegionLoc(chunkPos);
|
|
||||||
|
|
||||||
if (inputMap.hasOffset(pos))
|
|
||||||
{
|
|
||||||
offset = inputMap.getOffset(pos);
|
|
||||||
sectorLength = inputMap.getLength(pos);
|
|
||||||
//LOG.info("{},{}", offset, sectorLength);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
// LOG.info(path.toString());
|
|
||||||
|
|
||||||
int shortOffset = 4 * (pos.z + regionSize.z * (pos.y + regionSize.y * pos.x));
|
|
||||||
|
|
||||||
input.seek(shortOffset);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
offset *= 256;
|
|
||||||
offset += input.read();
|
|
||||||
}
|
|
||||||
sectorLength = input.read();
|
|
||||||
if (sectorLength == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
inputMap.putOffset(pos, offset);
|
|
||||||
inputMap.putLength(pos, sectorLength);
|
|
||||||
}
|
|
||||||
input.seek(fullOffset + sectorSize * offset);
|
|
||||||
|
|
||||||
// LOG.info("Read {} sectors", sectorLength);
|
|
||||||
|
|
||||||
byte tempData[] = new byte[sectorSize * sectorLength];
|
|
||||||
input.read(tempData);
|
|
||||||
|
|
||||||
DataInputStream trueInput = new DataInputStream(
|
|
||||||
new InflaterInputStream(new BufferedInputStream(new ByteArrayInputStream(tempData)))
|
|
||||||
);
|
|
||||||
DefaultChunkData chunk = ChunkIO.load(world, chunkPos, trueInput, IOContext.SAVE);
|
|
||||||
readGenerationHint(chunk, trueInput, server);
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
catch (EOFException e)
|
|
||||||
{
|
|
||||||
LOG.warn("Reached end of file, offset was {}, sectors was {}", offset, sectorLength);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultChunkData loadRegionX(Path path, Vec3i chunkPos, DefaultWorldData world, Server server)
|
static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
|
||||||
throws IOException,
|
|
||||||
DecodingException {
|
|
||||||
int offset = 0;
|
|
||||||
int sectorLength = 0;
|
|
||||||
Vec3i pos;
|
|
||||||
RandomFileMapped inputMap;
|
|
||||||
int fullOffset = 4 * (chunksPerRegion);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Vec3i streamCoords = getRegion(chunkPos);
|
|
||||||
|
|
||||||
inputMap = inOutMap.get(new HashableVec3i(streamCoords));
|
|
||||||
//LOG.info("streamCoords {},{},{}", streamCoords.x,streamCoords.y,streamCoords.z);
|
|
||||||
if (inputMap == null)
|
|
||||||
{
|
|
||||||
//input = new RandomAccessFile(path.toFile(), "rw");
|
|
||||||
//input = Files.newByteChannel(path);
|
|
||||||
inputMap = makeNew(path, new HashableVec3i(streamCoords));
|
|
||||||
}
|
|
||||||
|
|
||||||
RandomAccessFile input = inputMap.file;
|
|
||||||
|
|
||||||
|
|
||||||
pos = getRegionLoc(chunkPos);
|
|
||||||
|
|
||||||
if (inputMap.hasOffset(pos))
|
|
||||||
{
|
|
||||||
offset = inputMap.getOffset(pos);
|
|
||||||
sectorLength = inputMap.getLength(pos);
|
|
||||||
//LOG.info("{},{}", offset, sectorLength);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
// LOG.info(path.toString());
|
|
||||||
|
|
||||||
int shortOffset = 4 * (pos.z + regionSize.z * (pos.y + regionSize.y * pos.x));
|
|
||||||
|
|
||||||
input.seek(shortOffset);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
offset *= 256;
|
|
||||||
offset += input.read();
|
|
||||||
}
|
|
||||||
sectorLength = input.read();
|
|
||||||
if (sectorLength == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
inputMap.putOffset(pos, offset);
|
|
||||||
inputMap.putLength(pos, sectorLength);
|
|
||||||
}
|
|
||||||
input.seek(fullOffset + sectorSize * offset);
|
|
||||||
|
|
||||||
int xByte = input.read();
|
|
||||||
|
|
||||||
// LOG.info("Read {} sectors", sectorLength);
|
|
||||||
|
|
||||||
byte tempData[] = new byte[sectorSize * sectorLength];
|
|
||||||
input.read(tempData);
|
|
||||||
|
|
||||||
DataInputStream trueInput = new DataInputStream(
|
|
||||||
new InflaterInputStream(new BufferedInputStream(new ByteArrayInputStream(tempData)))
|
|
||||||
);
|
|
||||||
DefaultChunkData chunk = ChunkIO.load(world, chunkPos, trueInput, IOContext.SAVE);
|
|
||||||
readGenerationHint(chunk, trueInput, server);
|
|
||||||
|
|
||||||
chunk.isOpaque = (xByte & 2)==2;
|
|
||||||
chunk.isEmpty = (xByte & 1)==1;
|
|
||||||
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
catch (EOFException e)
|
|
||||||
{
|
|
||||||
LOG.warn("Reached end of file, offset was {}, sectors was {}", offset, sectorLength);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
|
|
||||||
throws IOException,
|
throws IOException,
|
||||||
DecodingException {
|
DecodingException {
|
||||||
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
|
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
|
||||||
@ -811,13 +168,18 @@ public class TestWorldDiskIO implements WorldContainer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path getPath() {
|
public Path getPath() {
|
||||||
return SAVE_DIR;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
// TODO Auto-generated method stub
|
try {
|
||||||
|
for (Region region : regions.values()) {
|
||||||
|
region.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
CrashReports.report(e, "Could not close region files");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user