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:
OLEGSHA 2021-08-29 02:08:19 +03:00
parent cd16334db8
commit f4300558d5
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
8 changed files with 428 additions and 836 deletions

View File

@ -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();

View File

@ -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 ;
}
}

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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) {

View File

@ -56,9 +56,6 @@ public class PlanetFeatureGenerator {
generateBorderFeatures(server, chunk); generateBorderFeatures(server, chunk);
} }
chunk.computeEmpty();
chunk.computeOpaque();
chunk.setGenerationHint(true); chunk.setGenerationHint(true);
} }

View 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;
}
}

View File

@ -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");
}
} }
} }