Remove ChunkNotGeneratedException and treat not loaded chunks just empty when rendering

This commit is contained in:
Blue (Lukas Rieger) 2020-01-06 13:04:19 +01:00
parent c1bcd4733d
commit b15fc534e6
16 changed files with 166 additions and 68 deletions

View File

@ -31,7 +31,6 @@ import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class MapType {
@ -69,7 +68,7 @@ public class MapType {
return tileRenderer;
}
public void renderTile(Vector2i tile) throws IOException, ChunkNotGeneratedException {
public void renderTile(Vector2i tile) throws IOException {
getTileRenderer().render(new WorldTile(getWorld(), tile));
}

View File

@ -39,7 +39,6 @@ import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class RenderTask {
@ -163,7 +162,7 @@ public class RenderTask {
tileRenderer.render(tile);
} catch (IOException e) {
Logger.global.logError("Failed to render tile " + tilePos, e);
} catch (ChunkNotGeneratedException e) {}
}
renderedTiles++;
}

View File

@ -39,6 +39,11 @@ public abstract class Chunk {
private final MCAWorld world;
private final Vector2i chunkPos;
protected Chunk(MCAWorld world, Vector2i chunkPos) {
this.world = world;
this.chunkPos = chunkPos;
}
protected Chunk(MCAWorld world, CompoundTag chunkTag) {
this.world = world;
@ -73,4 +78,8 @@ public abstract class Chunk {
return new ChunkAnvil113(world, chunkTag);
}
public static Chunk empty(MCAWorld world, Vector2i chunkPos) {
return new EmptyChunk(world, chunkPos);
}
}

View File

@ -63,6 +63,10 @@ class ChunkAnvil112 extends Chunk {
}
biomes = levelData.getByteArray("Biomes");
if (biomes == null || biomes.length == 0) {
biomes = new byte[2048];
}
}
@Override

View File

@ -81,7 +81,8 @@ class ChunkAnvil113 extends Chunk {
else if (tag instanceof IntArrayTag) {
biomes = ((IntArrayTag) tag).getValue();
}
else {
if (biomes == null || biomes.length == 0) {
biomes = new int[2048];
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.mca;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData;
public class EmptyChunk extends Chunk {
protected EmptyChunk(MCAWorld world, Vector2i chunkPos) {
super(world, chunkPos);
}
@Override
public boolean isGenerated() {
return false;
}
@Override
public BlockState getBlockState(Vector3i pos) {
return BlockState.AIR;
}
@Override
public LightData getLightData(Vector3i pos) {
return LightData.ZERO;
}
@Override
public Biome getBiome(Vector3i pos) {
return Biome.DEFAULT;
}
}

View File

@ -67,7 +67,6 @@ import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockProperties;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.World;
import net.querz.nbt.CompoundTag;
@ -140,7 +139,7 @@ public class MCAWorld implements World {
}
@Override
public Block getBlock(Vector3i pos) throws ChunkNotGeneratedException {
public Block getBlock(Vector3i pos) {
if (pos.getY() < getMinY()) {
return new MCABlock(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.SOLID, pos);
}
@ -160,11 +159,11 @@ public class MCAWorld implements World {
return new MCABlock(this, blockState, lightData, biome, properties, pos);
} catch (IOException ex) {
throw new ChunkNotGeneratedException(ex); // to resolve the error, act like the chunk has not been generated yet
throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex);
}
}
private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) throws ChunkNotGeneratedException {
private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) {
BlockState blockState = chunk.getBlockState(pos);
if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions
@ -176,10 +175,9 @@ public class MCAWorld implements World {
return blockState;
}
public Chunk getChunk(Vector2i chunkPos) throws IOException, ChunkNotGeneratedException {
public Chunk getChunk(Vector2i chunkPos) throws IOException {
try {
Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunk(chunkPos));
if (!chunk.isGenerated()) throw new ChunkNotGeneratedException();
return chunk;
} catch (UncheckedExecutionException | ExecutionException e) {
Throwable cause = e.getCause();
@ -187,16 +185,12 @@ public class MCAWorld implements World {
if (cause instanceof IOException) {
throw (IOException) cause;
}
else if (cause instanceof ChunkNotGeneratedException) {
throw (ChunkNotGeneratedException) cause;
}
else throw new IOException(cause);
}
}
private Chunk loadChunk(Vector2i chunkPos) throws IOException, ChunkNotGeneratedException {
private Chunk loadChunk(Vector2i chunkPos) throws IOException {
Vector2i regionPos = chunkToRegion(chunkPos);
Path regionPath = getMCAFilePath(regionPos);
@ -211,7 +205,9 @@ public class MCAWorld implements World {
offset *= 4096;
int size = raf.readByte() * 4096;
if (size == 0) throw new ChunkNotGeneratedException();
if (size == 0) {
return Chunk.empty(this, chunkPos);
}
raf.seek(offset + 4); // +4 skip chunk size
@ -232,14 +228,10 @@ public class MCAWorld implements World {
}
}
public boolean isChunkGenerated(Vector2i chunkPos) {
try {
getChunk(chunkPos);
} catch (ChunkNotGeneratedException | IOException e) {
return false;
}
return true;
@Override
public boolean isChunkGenerated(Vector2i chunkPos) throws IOException {
Chunk chunk = getChunk(chunkPos);
return chunk.isGenerated();
}
@Override

View File

@ -29,7 +29,7 @@ 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;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.util.AABB;
public class TileRenderer {
private HiresModelManager hiresModelManager;
@ -43,11 +43,14 @@ public class TileRenderer {
}
/**
* Renders the provided WorldTile
* @throws ChunkNotGeneratedException if that WorldTile's WorldChunk is not fully generated
* @throws IOException if a lowres-model that needs to be updated could not be loaded
* 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, ChunkNotGeneratedException {
public void render(WorldTile tile) throws IOException {
//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;
HiresModel hiresModel = hiresModelManager.render(tile, renderSettings);
lowresModelManager.render(hiresModel);
}

View File

@ -24,6 +24,7 @@
*/
package de.bluecolored.bluemap.core.render.context;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
@ -141,6 +142,11 @@ public class EmptyBlockContext implements ExtendedBlockContext {
@Override
public void invalidateChunkCache(Vector2i chunk) {}
@Override
public boolean isChunkGenerated(Vector2i chunkPos) throws IOException {
return false;
}
}

View File

@ -27,7 +27,6 @@ package de.bluecolored.bluemap.core.render.context;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class WorldBlockContext implements ExtendedBlockContext {
@ -64,11 +63,7 @@ public class WorldBlockContext implements ExtendedBlockContext {
}
protected Block getBlock(Vector3i position) {
try {
return world.getBlock(position);
} catch (ChunkNotGeneratedException ex) {
return EmptyBlockContext.AIR_BLOCK;
}
return world.getBlock(position);
}
}

View File

@ -44,7 +44,6 @@ import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.FileUtils;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
public class HiresModelManager {
@ -74,7 +73,7 @@ public class HiresModelManager {
* Renders the given world tile with the provided render-settings
* @throws ChunkNotGeneratedException if a minecraft-chunk needed for thies tile is not yet generated
*/
public HiresModel render(WorldTile tile, RenderSettings renderSettings) throws ChunkNotGeneratedException {
public HiresModel render(WorldTile tile, RenderSettings renderSettings) {
HiresModel model = renderer.render(tile, getTileRegion(tile), renderSettings);
save(model);
return model;

View File

@ -41,7 +41,6 @@ import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.MathUtils;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class HiresModelRenderer {
@ -56,7 +55,7 @@ public class HiresModelRenderer {
this.modelFactory = modelFactory;
}
public HiresModel render(WorldTile tile, AABB region, RenderSettings renderSettings) throws ChunkNotGeneratedException {
public HiresModel render(WorldTile tile, AABB region, RenderSettings renderSettings) {
Vector3i min = region.getMin().toInt();
Vector3i max = region.getMax().toInt();

View File

@ -24,12 +24,15 @@
*/
package de.bluecolored.bluemap.core.world;
import java.io.IOException;
import java.util.Collection;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
/**
* Represents a World on the Server<br>
* <br>
@ -58,14 +61,14 @@ public interface World {
* <br>
* <i>(The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)</i><br>
*/
Block getBlock(Vector3i pos) throws ChunkNotGeneratedException;
Block getBlock(Vector3i pos);
/**
* Returns the Block on the specified position.<br>
* <br>
* <i>(The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)</i><br>
*/
default Block getBlock(int x, int y, int z) throws ChunkNotGeneratedException {
default Block getBlock(int x, int y, int z) {
return getBlock(new Vector3i(x, y, z));
}
@ -83,6 +86,45 @@ public interface World {
*/
public Collection<Vector2i> 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;
/**
* Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done.
* @param area The area to check
* @throws IOException
*/
public default boolean isAreaGenerated(AABB area) throws IOException {
return isAreaGenerated(area.getMin().toInt(), area.getMax().toInt());
}
/**
* Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done.
* @param area The area to check
* @throws IOException
*/
public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) throws IOException {
return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax));
}
/**
* Returns true if and only if all chunks in the given range are fully generated and no world-generation or lighting has yet to be done.
* @param area The area to check
* @throws IOException
*/
public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) throws IOException {
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;
}
}
return true;
}
/**
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
*/

View File

@ -21,6 +21,7 @@ import org.spongepowered.api.world.Locatable;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.storage.WorldProperties;
import com.flowpowered.math.GenericMath;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
@ -28,7 +29,6 @@ import com.google.common.collect.Lists;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class Commands {
@ -45,20 +45,16 @@ public class Commands {
CommandSpec debugCommand = CommandSpec.builder()
.executor((source, args) -> {
if (source instanceof Locatable) {
try {
Location<org.spongepowered.api.world.World> loc = ((Locatable) source).getLocation();
UUID worldUuid = loc.getExtent().getUniqueId();
World world = plugin.getWorld(worldUuid);
Block block = world.getBlock(loc.getBlockPosition());
Block blockBelow = world.getBlock(loc.getBlockPosition().add(0, -1, 0));
source.sendMessages(Lists.newArrayList(
Text.of("Block: " + block),
Text.of("Block below: " + blockBelow)
));
} catch (ChunkNotGeneratedException e) {
Logger.global.logError("Failed to debug!", e);
}
Location<org.spongepowered.api.world.World> loc = ((Locatable) source).getLocation();
UUID worldUuid = loc.getExtent().getUniqueId();
World world = plugin.getWorld(worldUuid);
Block block = world.getBlock(loc.getBlockPosition());
Block blockBelow = world.getBlock(loc.getBlockPosition().add(0, -1, 0));
source.sendMessages(Lists.newArrayList(
Text.of("Block: " + block),
Text.of("Block below: " + blockBelow)
));
}
return CommandResult.success();
@ -266,10 +262,12 @@ public class Commands {
long ert = (long)((time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
double tps = task.getRenderedTileCount() / (time / 1000.0);
lines.add(Text.of(TextColors.BLUE, "Current task:"));
lines.add(Text.of(" ", createCancelTaskText(task), TextColors.WHITE, " Task ", TextColors.GOLD, task.getName(), TextColors.WHITE, " for map ", TextActions.showText(Text.of(TextColors.WHITE, "World: ", TextColors.GOLD, task.getMapType().getWorld().getName())), TextColors.GOLD, task.getMapType().getName()));
lines.add(Text.of(TextColors.WHITE, " rendered ", TextColors.GOLD, task.getRenderedTileCount(), TextColors.WHITE, " tiles ", TextColors.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "%)", TextColors.WHITE, " in ", TextColors.GOLD, durationString));
lines.add(Text.of(TextColors.WHITE, " rendered ", TextColors.GOLD, task.getRenderedTileCount(), TextColors.WHITE, " tiles ", TextColors.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "% | " + GenericMath.round(tps, 1) + "t/s)", TextColors.WHITE, " in ", TextColors.GOLD, durationString));
lines.add(Text.of(TextColors.WHITE, " with ", TextColors.GOLD, task.getRemainingTileCount(), TextColors.WHITE, " tiles to go. ETA: ", TextColors.GOLD, ertDurationString));
}

View File

@ -31,7 +31,6 @@ import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class MapType {
@ -69,7 +68,7 @@ public class MapType {
return tileRenderer;
}
public void renderTile(Vector2i tile) throws IOException, ChunkNotGeneratedException {
public void renderTile(Vector2i tile) throws IOException {
getTileRenderer().render(new WorldTile(getWorld(), tile));
}

View File

@ -5,8 +5,6 @@ import java.util.Objects;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
public class RenderTicket {
private final MapType map;
@ -22,12 +20,7 @@ public class RenderTicket {
public synchronized void render() throws IOException {
if (!finished) {
try {
map.renderTile(tile);
} catch (ChunkNotGeneratedException e) {
//ignore
}
map.renderTile(tile);
finished = true;
}