diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java index 5d3a11d3..687fa720 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MapType.java @@ -24,8 +24,6 @@ */ package de.bluecolored.bluemap.common; -import java.io.IOException; - import com.flowpowered.math.vector.Vector2i; import com.google.common.base.Preconditions; @@ -68,7 +66,7 @@ public TileRenderer getTileRenderer() { return tileRenderer; } - public void renderTile(Vector2i tile) throws IOException { + public void renderTile(Vector2i tile) { getTileRenderer().render(new WorldTile(getWorld(), tile)); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java index 53f1ac9c..31c884c7 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderManager.java @@ -163,17 +163,17 @@ private void renderThread() { if (ticket != null) { try { ticket.render(); - } catch (IOException e) { - if (ticket.getRenderAttempts() <= 1) { - createTicket(ticket); - } else { - Logger.global.logDebug("Failed to render tile " + ticket.getTile() + " of map '" + ticket.getMapType().getId() + "' after " + ticket.getRenderAttempts() + " render-attempts! (" + e.toString() + ")"); - } + } catch (Exception e) { + //catch possible runtime exceptions, display them, and wait a while .. then resurrect this render-thread + Logger.global.logError("Unexpected exception in render-thread!", e); + try { + Thread.sleep(10000); + } catch (InterruptedException interrupt) { break; } } } else { try { Thread.sleep(1000); // we don't need a super fast response time, so waiting a second is totally fine - } catch (InterruptedException e) { break; } + } catch (InterruptedException interrupt) { break; } } } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java index 5c36f6fa..69842450 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/RenderTicket.java @@ -24,7 +24,6 @@ */ package de.bluecolored.bluemap.common; -import java.io.IOException; import java.util.Objects; import com.flowpowered.math.vector.Vector2i; @@ -34,25 +33,13 @@ public class RenderTicket { private final MapType map; private final Vector2i tile; - private int renderAttempts; - private boolean successfullyRendered; - public RenderTicket(MapType map, Vector2i tile) { this.map = map; this.tile = tile; - - this.renderAttempts = 0; - this.successfullyRendered = false; } - public synchronized void render() throws IOException { - renderAttempts++; - - if (!successfullyRendered) { - map.renderTile(tile); - - successfullyRendered = true; - } + public synchronized void render() { + map.renderTile(tile); } public MapType getMapType() { @@ -63,10 +50,6 @@ public Vector2i getTile() { return tile; } - public int getRenderAttempts() { - return renderAttempts; - } - @Override public int hashCode() { return Objects.hash(map.getId(), tile); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 3404c6bb..b32ea461 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -100,13 +100,19 @@ public void init() { LiteralCommandNode debugCommand = literal("debug") .requires(requirements("bluemap.debug")) - .executes(this::debugCommand) - - .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) - .then(argument("x", DoubleArgumentType.doubleArg()) - .then(argument("y", DoubleArgumentType.doubleArg()) - .then(argument("z", DoubleArgumentType.doubleArg()) - .executes(this::debugCommand))))) + + .then(literal("block") + .executes(this::debugBlockCommand) + + .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) + .then(argument("x", DoubleArgumentType.doubleArg()) + .then(argument("y", DoubleArgumentType.doubleArg()) + .then(argument("z", DoubleArgumentType.doubleArg()) + .executes(this::debugBlockCommand)))))) + + .then(literal("cache") + .executes(this::debugClearCacheCommand)) + .build(); LiteralCommandNode pauseCommand = @@ -310,17 +316,28 @@ public int reloadCommand(CommandContext context) { return 1; } - public int debugCommand(CommandContext context) throws CommandSyntaxException { + public int debugClearCacheCommand(CommandContext context) throws CommandSyntaxException { CommandSource source = commandSourceInterface.apply(context.getSource()); + for (World world : plugin.getWorlds()) { + world.invalidateChunkCache(); + } + + source.sendMessage(Text.of(TextColor.GREEN, "All caches cleared!")); + return 1; + } + + public int debugBlockCommand(CommandContext context) throws CommandSyntaxException { + final CommandSource source = commandSourceInterface.apply(context.getSource()); + // parse arguments Optional worldName = getOptionalArgument(context, "world", String.class); Optional x = getOptionalArgument(context, "x", Double.class); Optional y = getOptionalArgument(context, "y", Double.class); Optional z = getOptionalArgument(context, "z", Double.class); - World world; - Vector3d position; + final World world; + final Vector3d position; if (worldName.isPresent() && x.isPresent() && y.isPresent() && z.isPresent()) { world = parseWorld(worldName.get()).orElse(null); @@ -340,30 +357,29 @@ public int debugCommand(CommandContext context) throws CommandSyntaxException } } - // output debug info - Vector3i blockPos = position.floor().toInt(); - Block block = world.getBlock(blockPos); - Block blockBelow = world.getBlock(blockPos.add(0, -1, 0)); - - String blockIdMeta = ""; - String blockBelowIdMeta = ""; - - if (world instanceof MCAWorld) { - try { + new Thread(() -> { + // collect and output debug info + Vector3i blockPos = position.floor().toInt(); + Block block = world.getBlock(blockPos); + Block blockBelow = world.getBlock(blockPos.add(0, -1, 0)); + + String blockIdMeta = ""; + String blockBelowIdMeta = ""; + + if (world instanceof MCAWorld) { Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); if (chunk instanceof ChunkAnvil112) { blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")"; blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")"; } - } catch (IOException ex) { - Logger.global.logError("Failed to read chunk for debug!", ex); } - } - - source.sendMessages(Lists.newArrayList( - Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), - Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) - )); + + source.sendMessages(Lists.newArrayList( + Text.of(TextColor.GOLD, "Is generated: ", TextColor.WHITE, world.isChunkGenerated(world.blockPosToChunkPos(blockPos))), + Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), + Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) + )); + }).start(); return 1; } @@ -395,12 +411,13 @@ public int resumeCommand(CommandContext context) { } public int renderCommand(CommandContext context) { - CommandSource source = commandSourceInterface.apply(context.getSource()); + final CommandSource source = commandSourceInterface.apply(context.getSource()); // parse world/map argument Optional worldOrMap = getOptionalArgument(context, "world|map", String.class); - World world = null; - MapType map = null; + + final World world; + final MapType map; if (worldOrMap.isPresent()) { world = parseWorld(worldOrMap.get()).orElse(null); @@ -411,9 +428,12 @@ public int renderCommand(CommandContext context) { source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get())); return 0; } + } else { + map = null; } } else { world = source.getWorld().orElse(null); + map = null; if (world == null) { source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to render!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render "))); @@ -422,8 +442,8 @@ public int renderCommand(CommandContext context) { } // parse radius and center arguments - int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1); - Vector2i center = null; + final int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1); + final Vector2i center; if (radius >= 0) { Optional x = getOptionalArgument(context, "x", Double.class); Optional z = getOptionalArgument(context, "z", Double.class); @@ -439,14 +459,18 @@ public int renderCommand(CommandContext context) { center = position.toVector2(true).floor().toInt(); } + } else { + center = null; } - // execute render - if (world != null) { - helper.createWorldRenderTask(source, world, center, radius); - } else { - helper.createMapRenderTask(source, map, center, radius); - } + // execute render + new Thread(() -> { + if (world != null) { + helper.createWorldRenderTask(source, world, center, radius); + } else { + helper.createMapRenderTask(source, map, center, radius); + } + }).start(); return 1; } diff --git a/BlueMapCore/build.gradle b/BlueMapCore/build.gradle index e412d83c..5d189319 100644 --- a/BlueMapCore/build.gradle +++ b/BlueMapCore/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { compile 'com.google.guava:guava:21.0' compile 'com.google.code.gson:gson:2.8.0' - compile 'org.apache.commons:commons-lang3:3.5' + compile 'org.apache.commons:commons-lang3:3.6' compile group: 'commons-io', name: 'commons-io', version: '2.5' compile 'com.flowpowered:flow-math:1.0.3' compile 'ninja.leaping.configurate:configurate-hocon:3.3' diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java index 9ddf7119..6be78d15 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java @@ -24,6 +24,8 @@ */ package de.bluecolored.bluemap.core.mca; +import java.util.Arrays; + import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; @@ -60,15 +62,21 @@ public ChunkAnvil112(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing levelData.getBoolean("TerrainPopulated"); sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe? - for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { - Section section = new Section(sectionTag); - sections[section.getSectionY()] = section; + if (levelData.containsKey("Sections")) { + for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { + Section section = new Section(sectionTag); + if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; + } } biomes = levelData.getByteArray("Biomes"); if (biomes == null || biomes.length == 0) { - biomes = new byte[2048]; + biomes = new byte[256]; + } + + if (biomes.length < 256) { + biomes = Arrays.copyOf(biomes, 256); } } @@ -132,6 +140,11 @@ public Section(CompoundTag sectionData) { this.blockLight = sectionData.getByteArray("BlockLight"); this.skyLight = sectionData.getByteArray("SkyLight"); this.data = sectionData.getByteArray("Data"); + + if (blocks.length < 4096) blocks = Arrays.copyOf(blocks, 4096); + if (blockLight.length < 2048) blockLight = Arrays.copyOf(blockLight, 2048); + if (skyLight.length < 2048) skyLight = Arrays.copyOf(skyLight, 2048); + if (data.length < 2048) data = Arrays.copyOf(data, 2048); } public int getSectionY() { @@ -148,7 +161,7 @@ public BlockState getBlockState(Vector3i pos) { int blockId = this.blocks[blockByteIndex] & 0xFF; - if (this.add.length > 0) { + if (this.add.length > blockHalfByteIndex) { blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8); } @@ -172,7 +185,7 @@ public String getBlockIdMeta(Vector3i pos) { int blockId = this.blocks[blockByteIndex] & 0xFF; - if (this.add.length > 0) { + if (this.add.length > blockHalfByteIndex) { blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java index 0a67022e..ef6c2a02 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -71,7 +72,7 @@ public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing if (levelData.containsKey("Sections")) { for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { Section section = new Section(sectionTag); - if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; + if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; } } @@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) { } if (biomes == null || biomes.length == 0) { - biomes = new int[2048]; + biomes = new int[256]; + } + + if (biomes.length < 256) { + biomes = Arrays.copyOf(biomes, 256); } } @@ -138,14 +143,18 @@ private class Section { private long[] blocks; private BlockState[] palette; + private int bitsPerBlock; + @SuppressWarnings("unchecked") public Section(CompoundTag sectionData) { this.sectionY = sectionData.getByte("Y"); this.blockLight = sectionData.getByteArray("BlockLight"); - if (blockLight.length == 0) blockLight = new byte[2048]; this.skyLight = sectionData.getByteArray("SkyLight"); - if (skyLight.length == 0) skyLight = new byte[2048]; this.blocks = sectionData.getLongArray("BlockStates"); + + if (blocks.length < 256) blocks = Arrays.copyOf(blocks, 256); + if (blockLight.length < 2048) blockLight = Arrays.copyOf(blockLight, 2048); + if (skyLight.length < 2048) skyLight = Arrays.copyOf(skyLight, 2048); //read block palette ListTag paletteTag = (ListTag) sectionData.getListTag("Palette"); @@ -174,6 +183,8 @@ public Section(CompoundTag sectionData) { } else { this.palette = new BlockState[0]; } + + this.bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section } public int getSectionY() { @@ -187,7 +198,6 @@ public BlockState getBlockState(Vector3i pos) { int y = pos.getY() & 0xF; int z = pos.getZ() & 0xF; int blockIndex = y * 256 + z * 16 + x; - int bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section int index = blockIndex * bitsPerBlock; int firstLong = index >> 6; // index / 64 int bitoffset = index & 0x3F; // Math.floorMod(index, 64) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java index 9ecede17..2ebfa8a0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -71,7 +72,7 @@ public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing if (levelData.containsKey("Sections")) { for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { Section section = new Section(sectionTag); - if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; + if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; } } @@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) { } if (biomes == null || biomes.length == 0) { - biomes = new int[2048]; + biomes = new int[1024]; + } + + if (biomes.length < 1024) { + biomes = Arrays.copyOf(biomes, 1024); } } @@ -145,10 +150,12 @@ private class Section { public Section(CompoundTag sectionData) { this.sectionY = sectionData.getByte("Y"); this.blockLight = sectionData.getByteArray("BlockLight"); - if (blockLight.length == 0) blockLight = new byte[2048]; this.skyLight = sectionData.getByteArray("SkyLight"); - if (skyLight.length == 0) skyLight = new byte[2048]; this.blocks = sectionData.getLongArray("BlockStates"); + + if (blocks.length < 256) blocks = Arrays.copyOf(blocks, 256); + if (blockLight.length < 2048) blockLight = Arrays.copyOf(blockLight, 2048); + if (skyLight.length < 2048) skyLight = Arrays.copyOf(skyLight, 2048); //read block palette ListTag paletteTag = (ListTag) sectionData.getListTag("Palette"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java index bcc0868c..0d0d7c1a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -71,7 +72,7 @@ public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing if (levelData.containsKey("Sections")) { for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { Section section = new Section(sectionTag); - if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; + if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; } } @@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) { } if (biomes == null || biomes.length == 0) { - biomes = new int[2048]; + biomes = new int[1024]; + } + + if (biomes.length < 1024) { + biomes = Arrays.copyOf(biomes, 1024); } } @@ -146,10 +151,12 @@ private class Section { public Section(CompoundTag sectionData) { this.sectionY = sectionData.getByte("Y"); this.blockLight = sectionData.getByteArray("BlockLight"); - if (blockLight.length == 0) blockLight = new byte[2048]; this.skyLight = sectionData.getByteArray("SkyLight"); - if (skyLight.length == 0) skyLight = new byte[2048]; this.blocks = sectionData.getLongArray("BlockStates"); + + if (blocks.length < 256) blocks = Arrays.copyOf(blocks, 256); + if (blockLight.length < 2048) blockLight = Arrays.copyOf(blockLight, 2048); + if (skyLight.length < 2048) skyLight = Arrays.copyOf(skyLight, 2048); //read block palette ListTag paletteTag = (ListTag) sectionData.getListTag("Palette"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index 42fa4b31..e2251f21 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -156,40 +156,32 @@ public BlockState getBlockState(Vector3i pos) { @Override public Biome getBiome(Vector3i pos) { - try { - - Vector2i chunkPos = blockToChunk(pos); - Chunk chunk = getChunk(chunkPos); - return chunk.getBiome(pos); - - } catch (IOException ex) { - throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex); + if (pos.getY() < getMinY()) { + pos = new Vector3i(pos.getX(), getMinY(), pos.getZ()); + } else if (pos.getY() > getMaxY()) { + pos = new Vector3i(pos.getX(), getMaxY(), pos.getZ()); } + + Vector2i chunkPos = blockToChunk(pos); + Chunk chunk = getChunk(chunkPos); + return chunk.getBiome(pos); } @Override public Block getBlock(Vector3i pos) { if (pos.getY() < getMinY()) { return new Block(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); - } - - if (pos.getY() > getMaxY()) { + } else if (pos.getY() > getMaxY()) { return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); } - try { - - Vector2i chunkPos = blockToChunk(pos); - Chunk chunk = getChunk(chunkPos); - BlockState blockState = getExtendedBlockState(chunk, pos); - LightData lightData = chunk.getLightData(pos); - Biome biome = chunk.getBiome(pos); - BlockProperties properties = blockPropertiesMapper.get(blockState); - return new Block(this, blockState, lightData, biome, properties, pos); - - } catch (IOException ex) { - throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex); - } + Vector2i chunkPos = blockToChunk(pos); + Chunk chunk = getChunk(chunkPos); + BlockState blockState = getExtendedBlockState(chunk, pos); + LightData lightData = chunk.getLightData(pos); + Biome biome = chunk.getBiome(pos); + BlockProperties properties = blockPropertiesMapper.get(blockState); + return new Block(this, blockState, lightData, biome, properties, pos); } private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) { @@ -204,26 +196,43 @@ private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) { return blockState; } - public Chunk getChunk(Vector2i chunkPos) throws IOException { + public Chunk getChunk(Vector2i chunkPos) { try { - Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunk(chunkPos)); + Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunkOrEmpty(chunkPos, 2, 1000)); return chunk; } catch (UncheckedExecutionException | ExecutionException e) { - Throwable cause = e.getCause(); - - if (cause instanceof IOException) { - throw (IOException) cause; - } - - else throw new IOException(cause); + throw new RuntimeException(e.getCause()); } } + private Chunk loadChunkOrEmpty(Vector2i chunkPos, int tries, long tryInterval) { + Exception loadException = null; + for (int i = 0; i < tries; i++) { + try { + return loadChunk(chunkPos); + } catch (Exception e) { + loadException = e; + + if (tryInterval > 0 && i+1 < tries) { + try { + Thread.sleep(tryInterval); + } catch (InterruptedException interrupt) {} + } + } + } + + Logger.global.logDebug("Unexpected exception trying to load chunk (" + chunkPos + "):" + loadException); + return Chunk.empty(this, chunkPos); + } + private Chunk loadChunk(Vector2i chunkPos) throws IOException { Vector2i regionPos = chunkToRegion(chunkPos); Path regionPath = getMCAFilePath(regionPos); - - try (RandomAccessFile raf = new RandomAccessFile(regionPath.toFile(), "r")) { + + File regionFile = regionPath.toFile(); + if (!regionFile.exists()) return Chunk.empty(this, chunkPos); + + try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) { int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32); @@ -243,7 +252,7 @@ private Chunk loadChunk(Vector2i chunkPos) throws IOException { byte compressionTypeByte = raf.readByte(); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); if (compressionType == null) { - throw new IOException("invalid compression type " + compressionTypeByte); + throw new IOException("Invalid compression type " + compressionTypeByte); } DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD())))); @@ -251,13 +260,15 @@ private Chunk loadChunk(Vector2i chunkPos) throws IOException { if (tag instanceof CompoundTag) { return Chunk.create(this, (CompoundTag) tag, ignoreMissingLightData); } else { - throw new IOException("invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); + throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); } + } catch (Exception e) { + throw new IOException("Exception trying to load chunk (" + chunkPos + ")", e); } } @Override - public boolean isChunkGenerated(Vector2i chunkPos) throws IOException { + public boolean isChunkGenerated(Vector2i chunkPos) { Chunk chunk = getChunk(chunkPos); return chunk.isGenerated(); } @@ -279,24 +290,26 @@ public Collection getChunkList(long modifiedSinceMillis, Predicate= (modifiedSinceMillis / 1000)) { - Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z); - if (filter.test(chunk)) { + Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z); + if (filter.test(chunk)) { + + int xzChunk = z * 32 + x; + + raf.seek(xzChunk * 4 + 3); + int size = raf.readByte() * 4096; + + if (size == 0) continue; + + raf.seek(xzChunk * 4 + 4096); + int timestamp = raf.read() << 24; + timestamp |= (raf.read() & 0xFF) << 16; + timestamp |= (raf.read() & 0xFF) << 8; + timestamp |= raf.read() & 0xFF; + + if (timestamp >= (modifiedSinceMillis / 1000)) { chunks.add(chunk); } + } } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java index c65d03e7..4e876042 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java @@ -24,8 +24,6 @@ */ package de.bluecolored.bluemap.core.render; -import java.io.IOException; - import de.bluecolored.bluemap.core.render.hires.HiresModel; import de.bluecolored.bluemap.core.render.hires.HiresModelManager; import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; @@ -42,9 +40,8 @@ public TileRenderer(HiresModelManager hiresModelManager, LowresModelManager lowr /** * Renders the provided WorldTile (only) if the world is generated - * @throws IOException If an IO-Exception occurs during the render */ - public void render(WorldTile tile) throws IOException { + public void render(WorldTile tile) { //check if the region is generated before rendering, don't render if it's not generated AABB area = hiresModelManager.getTileRegion(tile); if (!tile.getWorld().isAreaGenerated(area)) return; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java index 98940a32..256a9718 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java @@ -172,10 +172,10 @@ private Iterable iterateAverageBiomes(Block block){ final World world = block.getWorld(); final int sx = pos.getX() - radius.getX(), - sy = pos.getY() - radius.getY(), + sy = Math.max(0, pos.getY() - radius.getY()), sz = pos.getZ() - radius.getZ(); final int mx = pos.getX() + radius.getX(), - my = pos.getY() + radius.getY(), + my = Math.min(255, pos.getY() + radius.getY()), mz = pos.getZ() + radius.getZ(); return () -> new Iterator() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java index 36e69c7e..3da22abb 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java @@ -24,7 +24,6 @@ */ package de.bluecolored.bluemap.core.world; -import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.UUID; @@ -114,24 +113,24 @@ public Collection getChunkList(long modifiedSince, Predicate } @Override - public boolean isChunkGenerated(Vector2i chunkPos) throws IOException { + public boolean isChunkGenerated(Vector2i chunkPos) { if (!isInside(chunkPos)) return false; return world.isChunkGenerated(chunkPos); } @Override - public boolean isAreaGenerated(AABB area) throws IOException { + public boolean isAreaGenerated(AABB area) { return isAreaGenerated(area.getMin(), area.getMax()); } @Override - public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) throws IOException { + public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) { return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); } @Override - public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) throws IOException { + public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) { if (!isInside(chunkMin) && !isInside(chunkMax) && !isInside(new Vector2i(chunkMin.getX(), chunkMax.getY())) && diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index fbb96862..443b1b73 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -110,7 +110,7 @@ public default Collection getChunkList(long modifiedSince){ /** * Returns true if and only if that chunk is fully generated and no world-generation or lighting has yet to be done. */ - public boolean isChunkGenerated(Vector2i chunkPos) throws IOException; + public boolean isChunkGenerated(Vector2i chunkPos); /** @@ -118,7 +118,7 @@ public default Collection getChunkList(long modifiedSince){ * @param area The area to check * @throws IOException */ - public default boolean isAreaGenerated(AABB area) throws IOException { + public default boolean isAreaGenerated(AABB area) { return isAreaGenerated(area.getMin(), area.getMax()); } @@ -127,7 +127,7 @@ public default boolean isAreaGenerated(AABB area) throws IOException { * @param area The area to check * @throws IOException */ - public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) throws IOException { + public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) { return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); } @@ -136,7 +136,7 @@ public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) thr * @param area The area to check * @throws IOException */ - public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) throws IOException { + public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) { for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) { for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) { if (!isChunkGenerated(new Vector2i(x, z))) return false; diff --git a/README.md b/README.md index 9a69cca3..94c1f666 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ You found a bug, have another issue or a suggestion? Please create an issue [her You are welcome to contribute! Just create a pull request with your changes :) +If you want to have your changes merged faster, make sure they are complete, documented and well tested! + +The `master`-branch is for the latest version of minecraft. +The `mc/xx`-branches are for other minecraft-versions. +Changes that apply to all versions should be made on the `mc/1.13`-branch. This branch can be merged into `master` and every other `mc/xx` branch. + ### Todo / planned features Here is a *(surely incomplete)* list of things that i want to include in future versions. *(They are not in any specific order. There is no guarantee that any of those things will ever be included.)*