diff --git a/src/main/java/mcjty/lostcities/worldgen/ChunkDriver.java b/src/main/java/mcjty/lostcities/worldgen/ChunkDriver.java index 4b70f3785..d2ec65958 100644 --- a/src/main/java/mcjty/lostcities/worldgen/ChunkDriver.java +++ b/src/main/java/mcjty/lostcities/worldgen/ChunkDriver.java @@ -68,7 +68,21 @@ private void setBlock(BlockPos p, BlockState state) { // This version of getBlock() is less optimal but it will work for different chunks private BlockState getBlockSafe(BlockPos p) { - return isThisChunk(p) ? getBlock(p) : region.getBlockState(p); + if (isThisChunk(p)) { + return getBlock(p); + } + // During parallel world generation a neighbouring chunk may not be part of (or ready in) + // the current WorldGenRegion. Reading it would throw "Requested chunk unavailable during + // world generation" and kill chunk generation. Treat unavailable neighbours as air: + // only cosmetic connection states (fences, walls, stairs) depend on these reads + if (!region.hasChunk(p.getX() >> 4, p.getZ() >> 4)) { + return Blocks.AIR.defaultBlockState(); + } + try { + return region.getBlockState(p); + } catch (RuntimeException e) { + return Blocks.AIR.defaultBlockState(); + } } private BlockState getBlock(BlockPos p) { @@ -174,7 +188,17 @@ private BlockState updateAdjacent(BlockState state, Direction direction, BlockPo return adjacent; } if (newAdjacent != adjacent) { - ChunkAccess chunk = region.getChunk(pos); + // The adjacent position may be outside the available region during parallel world + // generation. In that case just skip the (cosmetic) update + if (!region.hasChunk(pos.getX() >> 4, pos.getZ() >> 4)) { + return adjacent; + } + ChunkAccess chunk; + try { + chunk = region.getChunk(pos); + } catch (RuntimeException e) { + return adjacent; + } if (chunk == thisChunk) { setBlock(pos, newAdjacent); } else if (chunk.getPersistedStatus().isOrAfter(ChunkStatus.FULL)) { diff --git a/src/main/java/mcjty/lostcities/worldgen/LostCityFeature.java b/src/main/java/mcjty/lostcities/worldgen/LostCityFeature.java index 62afa01d3..2deaf7629 100644 --- a/src/main/java/mcjty/lostcities/worldgen/LostCityFeature.java +++ b/src/main/java/mcjty/lostcities/worldgen/LostCityFeature.java @@ -57,14 +57,22 @@ public boolean place(FeaturePlaceContext context) { int chunkX = center.x; int chunkZ = center.z; - diminfo.setWorld(level); - try { - diminfo.getFeature().generate(region, region.getChunk(chunkX, chunkZ)); - } catch (Exception e) { - LostCities.getLogger().error("Error generating chunk {},{}: {}", chunkX, chunkZ, e.getMessage(), e); - e.printStackTrace(); - ErrorLogger.logChunkInfo(chunkX, chunkZ, diminfo); - ErrorLogger.report("There was an error generating a chunk. See log for details!"); + // The terrain feature (and its ChunkDriver) is shared per dimension while the + // feature step can run concurrently for different chunks. Serialize the world + // swap + generation on the shared feature instance so one thread can never tear + // down the driver or swap the region out from under another. + // LostCitySphereFeature locks the same monitor + LostCityTerrainFeature feature = diminfo.getFeature(); + synchronized (feature) { + diminfo.setWorld(level); + try { + feature.generate(region, region.getChunk(chunkX, chunkZ)); + } catch (Exception e) { + LostCities.getLogger().error("Error generating chunk {},{}: {}", chunkX, chunkZ, e.getMessage(), e); + e.printStackTrace(); + ErrorLogger.logChunkInfo(chunkX, chunkZ, diminfo); + ErrorLogger.report("There was an error generating a chunk. See log for details!"); + } } return true; } diff --git a/src/main/java/mcjty/lostcities/worldgen/LostCitySphereFeature.java b/src/main/java/mcjty/lostcities/worldgen/LostCitySphereFeature.java index 026783c5e..3ed97da4d 100644 --- a/src/main/java/mcjty/lostcities/worldgen/LostCitySphereFeature.java +++ b/src/main/java/mcjty/lostcities/worldgen/LostCitySphereFeature.java @@ -33,8 +33,13 @@ public boolean place(FeaturePlaceContext context) { int chunkX = center.x; int chunkZ = center.z; - diminfo.setWorld(level); - Spheres.generateSpheres(diminfo.getFeature(), region, region.getChunk(chunkX, chunkZ)); + // See LostCityFeature.place: the terrain feature and its ChunkDriver are shared + // per dimension. Serialize on the same monitor to avoid concurrent driver teardown + LostCityTerrainFeature feature = diminfo.getFeature(); + synchronized (feature) { + diminfo.setWorld(level); + Spheres.generateSpheres(feature, region, region.getChunk(chunkX, chunkZ)); + } return true; } }