From 1fddefce1cdce44010927b888432bf70c0e88cde Mon Sep 17 00:00:00 2001 From: DerFrZocker Date: Sun, 2 Apr 2023 13:06:59 +1000 Subject: [PATCH] #1155: Allow getting chunks without generating them and optimize chunk data request for ungenerated chunks --- .../minecraft/world/level/chunk/Chunk.patch | 37 ++++----- .../org/bukkit/craftbukkit/CraftChunk.java | 81 ++++++++++--------- .../org/bukkit/craftbukkit/CraftWorld.java | 19 ++++- 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/nms-patches/net/minecraft/world/level/chunk/Chunk.patch b/nms-patches/net/minecraft/world/level/chunk/Chunk.patch index 5d3ffc729..c6a1c246e 100644 --- a/nms-patches/net/minecraft/world/level/chunk/Chunk.patch +++ b/nms-patches/net/minecraft/world/level/chunk/Chunk.patch @@ -18,28 +18,19 @@ this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); HeightMap.Type[] aheightmap_type = HeightMap.Type.values(); int j = aheightmap_type.length; -@@ -110,8 +110,20 @@ - this.postLoad = chunk_c; - this.blockTicks = levelchunkticks; +@@ -112,6 +112,11 @@ this.fluidTicks = levelchunkticks1; -+ // CraftBukkit start -+ this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); } -+ public org.bukkit.Chunk bukkitChunk; -+ public org.bukkit.Chunk getBukkitChunk() { -+ return bukkitChunk; -+ } -+ ++ // CraftBukkit start + public boolean mustNotSave; + public boolean needsDecoration; -+ + // CraftBukkit end + public Chunk(WorldServer worldserver, ProtoChunk protochunk, @Nullable Chunk.c chunk_c) { this(worldserver, protochunk.getPos(), protochunk.getUpgradeData(), protochunk.unpackBlockTicks(), protochunk.unpackFluidTicks(), protochunk.getInhabitedTime(), protochunk.getSections(), chunk_c, protochunk.getBlendingData()); Iterator iterator = protochunk.getBlockEntities().values().iterator(); -@@ -142,6 +154,10 @@ +@@ -142,6 +147,10 @@ this.setLightCorrect(protochunk.isLightCorrect()); this.unsaved = true; @@ -50,7 +41,7 @@ } @Override -@@ -246,9 +262,16 @@ +@@ -246,9 +255,16 @@ } } @@ -67,7 +58,7 @@ int i = blockposition.getY(); ChunkSection chunksection = this.getSection(this.getSectionIndex(i)); boolean flag1 = chunksection.hasOnlyAir(); -@@ -287,7 +310,8 @@ +@@ -287,7 +303,8 @@ if (!chunksection.getBlockState(j, k, l).is(block)) { return null; } else { @@ -77,7 +68,7 @@ iblockdata.onPlace(this.level, blockposition, iblockdata1, flag); } -@@ -332,7 +356,12 @@ +@@ -332,7 +349,12 @@ @Nullable public TileEntity getBlockEntity(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { @@ -91,7 +82,7 @@ if (tileentity == null) { NBTTagCompound nbttagcompound = (NBTTagCompound) this.pendingBlockEntities.remove(blockposition); -@@ -410,6 +439,13 @@ +@@ -410,6 +432,13 @@ tileentity1.setRemoved(); } @@ -105,7 +96,7 @@ } } -@@ -439,6 +475,12 @@ +@@ -439,6 +468,12 @@ if (this.isInLevel()) { TileEntity tileentity = (TileEntity) this.blockEntities.remove(blockposition); @@ -118,7 +109,7 @@ if (tileentity != null) { World world = this.level; -@@ -491,6 +533,55 @@ +@@ -491,6 +526,57 @@ } @@ -131,7 +122,8 @@ + * the World constructor. We can't reliably alter that, so we have + * no way of creating a CraftWorld/CraftServer at that point. + */ -+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(this.bukkitChunk, this.needsDecoration)); ++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); ++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); + + if (this.needsDecoration) { + this.needsDecoration = false; @@ -159,7 +151,8 @@ + + public void unloadCallback() { + org.bukkit.Server server = this.level.getCraftServer(); -+ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved()); ++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); ++ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); + server.getPluginManager().callEvent(unloadEvent); + // note: saving can be prevented, but not forced if no saving is actually required + this.mustNotSave = !unloadEvent.isSaveChunk(); @@ -174,7 +167,7 @@ public boolean isEmpty() { return false; } -@@ -694,7 +785,7 @@ +@@ -694,7 +780,7 @@ private void updateBlockEntityTicker(T t0) { IBlockData iblockdata = t0.getBlockState(); @@ -183,7 +176,7 @@ if (blockentityticker == null) { this.removeBlockEntityTicker(t0.getBlockPos()); -@@ -787,7 +878,7 @@ +@@ -787,7 +873,7 @@ private boolean loggedInvalidBlockState; a(TileEntity tileentity, BlockEntityTicker blockentityticker) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index f1fde234b..01d9473d2 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -3,7 +3,6 @@ package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.mojang.serialization.Codec; -import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; import java.util.Objects; @@ -51,7 +50,6 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftChunk implements Chunk { - private WeakReference weakChunk; private final WorldServer worldServer; private final int x; private final int z; @@ -59,15 +57,12 @@ public class CraftChunk implements Chunk { private static final byte[] emptyLight = new byte[2048]; public CraftChunk(net.minecraft.world.level.chunk.Chunk chunk) { - this.weakChunk = new WeakReference(chunk); - - worldServer = (WorldServer) getHandle().level; - x = getHandle().getPos().x; - z = getHandle().getPos().z; + worldServer = chunk.level; + x = chunk.getPos().x; + z = chunk.getPos().z; } public CraftChunk(WorldServer worldServer, int x, int z) { - this.weakChunk = new WeakReference<>(null); this.worldServer = worldServer; this.x = x; this.z = z; @@ -82,20 +77,8 @@ public class CraftChunk implements Chunk { return (CraftWorld) getWorld(); } - public net.minecraft.world.level.chunk.Chunk getHandle() { - net.minecraft.world.level.chunk.Chunk c = weakChunk.get(); - - if (c == null) { - c = worldServer.getChunk(x, z); - - weakChunk = new WeakReference(c); - } - - return c; - } - - void breakLink() { - weakChunk.clear(); + public IChunkAccess getHandle(ChunkStatus chunkStatus) { + return worldServer.getChunk(x, z, chunkStatus); } @Override @@ -115,7 +98,7 @@ public class CraftChunk implements Chunk { @Override public Block getBlock(int x, int y, int z) { - validateChunkCoordinates(getHandle().getMinBuildHeight(), getHandle().getMaxBuildHeight(), x, y, z); + validateChunkCoordinates(worldServer.getMinBuildHeight(), worldServer.getMaxBuildHeight(), x, y, z); return new CraftBlock(worldServer, new BlockPosition((this.x << 4) | x, y, (this.z << 4) | z)); } @@ -184,22 +167,23 @@ public class CraftChunk implements Chunk { getWorld().getChunkAt(x, z); // Transient load for this tick } int index = 0; - net.minecraft.world.level.chunk.Chunk chunk = getHandle(); + IChunkAccess chunk = getHandle(ChunkStatus.FULL); BlockState[] entities = new BlockState[chunk.blockEntities.size()]; - for (Object obj : chunk.blockEntities.keySet().toArray()) { - if (!(obj instanceof BlockPosition)) { - continue; - } - - BlockPosition position = (BlockPosition) obj; + for (BlockPosition position : chunk.blockEntities.keySet()) { entities[index++] = worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); } return entities; } + @Override + public boolean isGenerated() { + IChunkAccess chunk = getHandle(ChunkStatus.EMPTY); + return chunk.getStatus().isOrAfter(ChunkStatus.FULL); + } + @Override public boolean isLoaded() { return getWorld().isChunkLoaded(this); @@ -258,14 +242,14 @@ public class CraftChunk implements Chunk { @Override public long getInhabitedTime() { - return getHandle().getInhabitedTime(); + return getHandle(ChunkStatus.EMPTY).getInhabitedTime(); } @Override public void setInhabitedTime(long ticks) { Preconditions.checkArgument(ticks >= 0, "ticks cannot be negative"); - getHandle().setInhabitedTime(ticks); + getHandle(ChunkStatus.STRUCTURE_STARTS).setInhabitedTime(ticks); } @Override @@ -273,7 +257,7 @@ public class CraftChunk implements Chunk { Preconditions.checkArgument(block != null, "Block cannot be null"); Predicate nms = Predicates.equalTo(((CraftBlockData) block).getState()); - for (ChunkSection section : getHandle().getSections()) { + for (ChunkSection section : getHandle(ChunkStatus.FULL).getSections()) { if (section != null && section.getStates().maybeHas(nms)) { return true; } @@ -286,8 +270,9 @@ public class CraftChunk implements Chunk { public boolean contains(Biome biome) { Preconditions.checkArgument(biome != null, "Biome cannot be null"); - Predicate> nms = Predicates.equalTo(CraftBlock.biomeToBiomeBase(getHandle().biomeRegistry, biome)); - for (ChunkSection section : getHandle().getSections()) { + IChunkAccess chunk = getHandle(ChunkStatus.BIOMES); + Predicate> nms = Predicates.equalTo(CraftBlock.biomeToBiomeBase(chunk.biomeRegistry, biome)); + for (ChunkSection section : chunk.getSections()) { if (section != null && section.getBiomes().maybeHas(nms)) { return true; } @@ -303,7 +288,7 @@ public class CraftChunk implements Chunk { @Override public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { - net.minecraft.world.level.chunk.Chunk chunk = getHandle(); + IChunkAccess chunk = getHandle(ChunkStatus.FULL); ChunkSection[] cs = chunk.getSections(); DataPaletteBlock[] sectionBlockIDs = new DataPaletteBlock[cs.length]; @@ -321,7 +306,7 @@ public class CraftChunk implements Chunk { data.put("block_states", ChunkRegionLoader.BLOCK_STATE_CODEC.encodeStart(DynamicOpsNBT.INSTANCE, cs[i].getStates()).get().left().get()); sectionBlockIDs[i] = ChunkRegionLoader.BLOCK_STATE_CODEC.parse(DynamicOpsNBT.INSTANCE, data.getCompound("block_states")).get().left().get(); - LightEngine lightengine = chunk.level.getLightEngine(); + LightEngine lightengine = worldServer.getLightEngine(); NibbleArray skyLightArray = lightengine.getLayerListener(EnumSkyBlock.SKY).getDataLayerData(SectionPosition.of(x, i, z)); if (skyLightArray == null) { sectionSkyLights[i] = emptyLight; @@ -356,7 +341,27 @@ public class CraftChunk implements Chunk { @Override public PersistentDataContainer getPersistentDataContainer() { - return getHandle().persistentDataContainer; + return getHandle(ChunkStatus.STRUCTURE_STARTS).persistentDataContainer; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CraftChunk that = (CraftChunk) o; + + if (x != that.x) return false; + if (z != that.z) return false; + return worldServer.equals(that.worldServer); + } + + @Override + public int hashCode() { + int result = worldServer.hashCode(); + result = 31 * result + x; + result = 31 * result + z; + return result; } public static ChunkSnapshot getEmptyChunkSnapshot(int x, int z, CraftWorld world, boolean includeBiome, boolean includeBiomeTempRain) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index d0934ff02..01fe9c5b9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -139,6 +139,7 @@ import org.bukkit.util.Consumer; import org.bukkit.util.RayTraceResult; import org.bukkit.util.StructureSearchResult; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; @@ -212,7 +213,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public Chunk getChunkAt(int x, int z) { - return this.world.getChunkSource().getChunk(x, z, true).bukkitChunk; + net.minecraft.world.level.chunk.Chunk chunk = (net.minecraft.world.level.chunk.Chunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); + return new CraftChunk(chunk); + } + + @NotNull + @Override + public Chunk getChunkAt(int x, int z, boolean generate) { + if (generate) { + return getChunkAt(x, z); + } + + return new CraftChunk(getHandle(), x, z); } @Override @@ -239,7 +251,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public Chunk[] getLoadedChunks() { Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; - return chunks.values().stream().map(PlayerChunk::getFullChunkNow).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.Chunk::getBukkitChunk).toArray(Chunk[]::new); + return chunks.values().stream().map(PlayerChunk::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new); } @Override @@ -277,7 +289,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } net.minecraft.world.level.chunk.Chunk chunk = world.getChunk(x, z); - chunk.mustNotSave = !save; + chunk.setUnsaved(!save); // Use method call to account for persistentDataContainer unloadChunkRequest(x, z); world.getChunkSource().purgeUnload(); @@ -366,7 +378,6 @@ public class CraftWorld extends CraftRegionAccessor implements World { Preconditions.checkArgument(chunk != null, "null chunk"); loadChunk(chunk.getX(), chunk.getZ()); - ((CraftChunk) getChunkAt(chunk.getX(), chunk.getZ())).getHandle().bukkitChunk = chunk; } @Override