net/minecraft/world/level/chunk/

This commit is contained in:
Owen1212055
2024-12-14 15:20:06 -05:00
parent c31ab10475
commit a3bd0b2bbb
15 changed files with 673 additions and 875 deletions

View File

@@ -0,0 +1,85 @@
--- a/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -64,7 +_,7 @@
protected final ShortList[] postProcessing;
private volatile boolean unsaved;
private volatile boolean isLightCorrect;
- protected final ChunkPos chunkPos;
+ protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
private long inhabitedTime;
@Nullable
@Deprecated
@@ -82,6 +_,11 @@
public final Map<BlockPos, BlockEntity> blockEntities = new Object2ObjectOpenHashMap<>();
protected final LevelHeightAccessor levelHeightAccessor;
protected final LevelChunkSection[] sections;
+ // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading.
+ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+ public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
+ // CraftBukkit end
+ public final Registry<Biome> biomeRegistry; // CraftBukkit
public ChunkAccess(
ChunkPos chunkPos,
@@ -92,7 +_,8 @@
@Nullable LevelChunkSection[] sections,
@Nullable BlendingData blendingData
) {
- this.chunkPos = chunkPos;
+ this.locX = chunkPos.x; this.locZ = chunkPos.z; // Paper - reduce need for field lookups
+ this.chunkPos = chunkPos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
this.upgradeData = upgradeData;
this.levelHeightAccessor = levelHeightAccessor;
this.sections = new LevelChunkSection[levelHeightAccessor.getSectionsCount()];
@@ -109,6 +_,7 @@
}
replaceMissingSections(biomeRegistry, this.sections);
+ this.biomeRegistry = biomeRegistry; // Craftbukkit
}
private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
@@ -273,6 +_,7 @@
public boolean tryMarkSaved() {
if (this.unsaved) {
this.unsaved = false;
+ this.persistentDataContainer.dirty(false); // CraftBukkit - SPIGOT-6814: chunk was saved, pdc is no longer dirty
return true;
} else {
return false;
@@ -280,7 +_,7 @@
}
public boolean isUnsaved() {
- return this.unsaved;
+ return this.unsaved || this.persistentDataContainer.dirty(); // CraftBukkit - SPIGOT-6814: chunk is unsaved if pdc was mutated
}
public abstract ChunkStatus getPersistedStatus();
@@ -446,6 +_,26 @@
throw new ReportedException(crashReport);
}
}
+ // CraftBukkit start
+ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+ try {
+ int l = QuartPos.fromBlock(this.getMinY());
+ int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
+ int j1 = Mth.clamp(j, l, i1);
+ int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
+
+ this.sections[k1].setBiome(i & 3, j1 & 3, k & 3, biome);
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Setting biome");
+ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being set");
+
+ crashreportsystemdetails.setDetail("Location", () -> {
+ return CrashReportCategory.formatLocation(this, i, j, k);
+ });
+ throw new ReportedException(crashreport);
+ }
+ }
+ // CraftBukkit end
public void fillBiomesFromNoise(BiomeResolver resolver, Climate.Sampler sampler) {
ChunkPos pos = this.getPos();

View File

@@ -0,0 +1,148 @@
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -104,8 +_,8 @@
protected abstract MapCodec<? extends ChunkGenerator> codec();
- public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed) {
- return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup);
+ public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed, org.spigotmc.SpigotWorldConfig conf) { // Spigot
+ return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup, conf); // Spigot
}
public Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> getTypeNameForDataFixer() {
@@ -127,6 +_,24 @@
public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(
ServerLevel level, HolderSet<Structure> structure, BlockPos pos, int searchRadius, boolean skipKnownStructures
) {
+ // Paper start - StructuresLocateEvent
+ final org.bukkit.World bukkitWorld = level.getWorld();
+ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(level, pos);
+ final List<org.bukkit.generator.structure.Structure> apiStructures = structure.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
+ if (!apiStructures.isEmpty()) {
+ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, searchRadius, skipKnownStructures);
+ if (!event.callEvent()) {
+ return null;
+ }
+ if (event.getResult() != null) {
+ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
+ }
+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin());
+ searchRadius = event.getRadius();
+ skipKnownStructures = event.shouldFindUnexplored();
+ structure = HolderSet.direct(api -> level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
+ }
+ // Paper end
ChunkGeneratorStructureState generatorState = level.getChunkSource().getGeneratorState();
Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap<>();
@@ -222,6 +_,7 @@
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
for (ChunkPos chunkPos : ringPositionsFor) {
+ if (!level.paperConfig().environment.locateStructuresOutsideWorldBorder && !level.getWorldBorder().isChunkInBounds(chunkPos.x, chunkPos.z)) { continue; } // Paper - Bound treasure maps to world border
mutableBlockPos.set(SectionPos.sectionToBlockCoord(chunkPos.x, 8), 32, SectionPos.sectionToBlockCoord(chunkPos.z, 8));
double d1 = mutableBlockPos.distSqr(pos);
boolean flag = pair == null || d1 < d;
@@ -255,11 +_,15 @@
int spacing = spreadPlacement.spacing();
for (int i = -z; i <= z; i++) {
- boolean flag = i == -z || i == z;
+ // Paper start - Perf: iterate over border chunks instead of entire square chunk area
+ final int radius = z;
+ boolean flag = i == -z || i == z; final boolean onBorderAlongZAxis = flag; // Paper - OBFHELPER
- for (int i1 = -z; i1 <= z; i1++) {
- boolean flag1 = i1 == -z || i1 == z;
- if (flag || flag1) {
+ for (int i1 = -radius; i1 <= radius; i1 += onBorderAlongZAxis ? 1 : radius * 2) {
+ // boolean flag1 = i1 == -z || i1 == z;
+ // if (flag || flag1) {
+ if (true) {
+ // Paper end - Perf: iterate over border chunks instead of entire square chunk area
int i2 = x + spacing * i;
int i3 = y + spacing * i1;
ChunkPos potentialStructureChunk = spreadPlacement.getPotentialStructureChunk(seed, i2, i3);
@@ -312,7 +_,7 @@
}
}
- public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
+ public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) { // CraftBukkit - rename
ChunkPos pos = chunk.getPos();
if (!SharedConstants.debugVoidTerrain(pos)) {
SectionPos sectionPos = SectionPos.of(pos, level.getMinSectionY());
@@ -385,7 +_,14 @@
int i3 = ints[i2];
PlacedFeature placedFeature = stepFeatureData1.features().get(i3);
Supplier<String> supplier1 = () -> registry1.getResourceKey(placedFeature).map(Object::toString).orElseGet(placedFeature::toString);
- worldgenRandom.setFeatureSeed(l, i3, i);
+ // Paper start - Configurable feature seeds; change populationSeed used in random
+ long featurePopulationSeed = i;
+ final long configFeatureSeed = level.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedFeature.feature());
+ if (configFeatureSeed != -1) {
+ featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See seededrandom.setDecorationSeed from above
+ }
+ worldgenRandom.setFeatureSeed(featurePopulationSeed, i3, i);
+ // Paper end - Configurable feature seeds
try {
level.setCurrentlyGenerating(supplier1);
@@ -407,6 +_,32 @@
}
}
}
+ // CraftBukkit start
+ public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
+ this.applyBiomeDecoration(world, chunk, structureAccessor, true);
+ }
+
+ public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
+ if (vanilla) {
+ this.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
+ }
+
+ org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld();
+ // only call when a populator is present (prevents unnecessary entity conversion)
+ if (!world.getPopulators().isEmpty()) {
+ org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos());
+ int x = ichunkaccess.getPos().x;
+ int z = ichunkaccess.getPos().z;
+ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+ WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed()));
+ seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z);
+ populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
+ }
+ limitedRegion.saveEntities();
+ limitedRegion.breakLink();
+ }
+ }
+ // CraftBukkit end
private static BoundingBox getWritableArea(ChunkAccess chunk) {
ChunkPos pos = chunk.getPos();
@@ -483,7 +_,7 @@
}
}
- if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z)) {
+ if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z, structurePlacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
if (list.size() == 1) {
this.tryGenerateStructure(
list.get(0),
@@ -577,6 +_,14 @@
predicate
);
if (structureStart.isValid()) {
+ // CraftBukkit start
+ BoundingBox box = structureStart.getBoundingBox();
+ org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureStart.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunkPos.x, chunkPos.z);
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return true;
+ }
+ // CraftBukkit end
structureManager.setStartForStructure(sectionPos, structure, structureStart, chunk);
return true;
} else {

View File

@@ -0,0 +1,161 @@
--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
+++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
@@ -1,3 +_,4 @@
+// mc-dev import
package net.minecraft.world.level.chunk;
import com.google.common.base.Stopwatch;
@@ -41,22 +_,109 @@
private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap<>();
private boolean hasGeneratedPositions;
private final List<Holder<StructureSet>> possibleStructureSets;
+ public final org.spigotmc.SpigotWorldConfig conf; // Paper - Add missing structure set seed configs
public static ChunkGeneratorStructureState createForFlat(
- RandomState randomState, long levelSeed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets
+ RandomState randomState, long levelSeed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets, org.spigotmc.SpigotWorldConfig conf // Spigot
) {
List<Holder<StructureSet>> list = structureSets.filter(structureSet -> hasBiomesForStructureSet(structureSet.value(), biomeSource)).toList();
- return new ChunkGeneratorStructureState(randomState, biomeSource, levelSeed, 0L, list);
+ return new ChunkGeneratorStructureState(randomState, biomeSource, levelSeed, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
}
public static ChunkGeneratorStructureState createForNormal(
- RandomState randomState, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetLookup
+ RandomState randomState, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetLookup, org.spigotmc.SpigotWorldConfig conf // Spigot
) {
List<Holder<StructureSet>> list = structureSetLookup.listElements()
.filter(structureSet -> hasBiomesForStructureSet(structureSet.value(), biomeSource))
.collect(Collectors.toUnmodifiableList());
- return new ChunkGeneratorStructureState(randomState, biomeSource, seed, seed, list);
- }
+ return new ChunkGeneratorStructureState(randomState, biomeSource, seed, seed, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+ }
+ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key
+ public static final class KeyedRandomSpreadStructurePlacement extends net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement {
+ public final net.minecraft.resources.ResourceKey<StructureSet> key;
+ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey<StructureSet> key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional<StructurePlacement.ExclusionZone> exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) {
+ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType);
+ this.key = key;
+ }
+ }
+ // Paper end - Add missing structure set seed configs
+
+ // Spigot start
+ private static List<Holder<StructureSet>> injectSpigot(List<Holder<StructureSet>> list, org.spigotmc.SpigotWorldConfig conf) {
+ return list.stream().map((holder) -> {
+ StructureSet structureset = holder.value();
+ final Holder<StructureSet> newHolder; // Paper - Add missing structure set seed configs
+ if (structureset.placement() instanceof net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path
+ String name = holder.unwrapKey().orElseThrow().location().getPath();
+ int seed = randomConfig.salt;
+
+ switch (name) {
+ case "desert_pyramids":
+ seed = conf.desertSeed;
+ break;
+ case "end_cities":
+ seed = conf.endCitySeed;
+ break;
+ case "nether_complexes":
+ seed = conf.netherSeed;
+ break;
+ case "igloos":
+ seed = conf.iglooSeed;
+ break;
+ case "jungle_temples":
+ seed = conf.jungleSeed;
+ break;
+ case "woodland_mansions":
+ seed = conf.mansionSeed;
+ break;
+ case "ocean_monuments":
+ seed = conf.monumentSeed;
+ break;
+ case "nether_fossils":
+ seed = conf.fossilSeed;
+ break;
+ case "ocean_ruins":
+ seed = conf.oceanSeed;
+ break;
+ case "pillager_outposts":
+ seed = conf.outpostSeed;
+ break;
+ case "ruined_portals":
+ seed = conf.portalSeed;
+ break;
+ case "shipwrecks":
+ seed = conf.shipwreckSeed;
+ break;
+ case "swamp_huts":
+ seed = conf.swampSeed;
+ break;
+ case "villages":
+ seed = conf.villageSeed;
+ break;
+ // Paper start - Add missing structure set seed configs
+ case "ancient_cities":
+ seed = conf.ancientCitySeed;
+ break;
+ case "trail_ruins":
+ seed = conf.trailRuinsSeed;
+ break;
+ case "trial_chambers":
+ seed = conf.trialChambersSeed;
+ break;
+ // Paper end - Add missing structure set seed configs
+ }
+
+ // Paper start - Add missing structure set seed configs
+ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType()));
+ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here
+ } else {
+ newHolder = holder;
+ }
+ return newHolder;
+ // Paper end - Add missing structure set seed configs
+ }).collect(Collectors.toUnmodifiableList());
+ }
+ // Spigot end
private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) {
Stream<Holder<Biome>> stream = structureSet.structures().stream().flatMap(structureEntry -> {
@@ -67,13 +_,14 @@
}
private ChunkGeneratorStructureState(
- RandomState randomState, BiomeSource biomeSource, long levelSeed, long cocentricRingsSeed, List<Holder<StructureSet>> possibleStructureSets
+ RandomState randomState, BiomeSource biomeSource, long levelSeed, long cocentricRingsSeed, List<Holder<StructureSet>> possibleStructureSets, org.spigotmc.SpigotWorldConfig conf // Paper - Add missing structure set seed configs
) {
this.randomState = randomState;
this.levelSeed = levelSeed;
this.biomeSource = biomeSource;
this.concentricRingsSeed = cocentricRingsSeed;
this.possibleStructureSets = possibleStructureSets;
+ this.conf = conf; // Paper - Add missing structure set seed configs
}
public List<Holder<StructureSet>> possibleStructureSets() {
@@ -118,7 +_,13 @@
int spread = placement.spread();
HolderSet<Biome> holderSet = placement.preferredBiomes();
RandomSource randomSource = RandomSource.create();
+ // Paper start - Add missing structure set seed configs
+ if (this.conf.strongholdSeed != null && structureSet.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) {
+ randomSource.setSeed(this.conf.strongholdSeed);
+ } else {
+ // Paper end - Add missing structure set seed configs
randomSource.setSeed(this.concentricRingsSeed);
+ } // Paper - Add missing structure set seed configs
double d = randomSource.nextDouble() * Math.PI * 2.0;
int i = 0;
int i1 = 0;
@@ -197,7 +_,7 @@
for (int i = x - range; i <= x + range; i++) {
for (int i1 = z - range; i1 <= z + range; i1++) {
- if (structurePlacement.isStructureChunk(this, i, i1)) {
+ if (structurePlacement.isStructureChunk(this, i, i1, structurePlacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
return true;
}
}

View File

@@ -0,0 +1,7 @@
--- a/net/minecraft/world/level/chunk/DataLayer.java
+++ b/net/minecraft/world/level/chunk/DataLayer.java
@@ -1,3 +_,4 @@
+// mc-dev import
package net.minecraft.world.level.chunk;
import java.util.Arrays;

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
@@ -25,6 +_,12 @@
public BlockState getBlockState(BlockPos pos) {
return Blocks.VOID_AIR.defaultBlockState();
}
+ // Paper start
+ @Override
+ public BlockState getBlockState(final int x, final int y, final int z) {
+ return Blocks.VOID_AIR.defaultBlockState();
+ }
+ // Paper end
@Nullable
@Override

View File

@@ -0,0 +1,30 @@
--- a/net/minecraft/world/level/chunk/HashMapPalette.java
+++ b/net/minecraft/world/level/chunk/HashMapPalette.java
@@ -20,7 +_,7 @@
}
public HashMapPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler) {
- this(registry, bits, resizeHandler, CrudeIncrementalIntIdentityHashBiMap.create(1 << bits));
+ this(registry, bits, resizeHandler, CrudeIncrementalIntIdentityHashBiMap.create((1 << bits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap
}
private HashMapPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler, CrudeIncrementalIntIdentityHashBiMap<T> values) {
@@ -38,10 +_,16 @@
public int idFor(T state) {
int id = this.values.getId(state);
if (id == -1) {
- id = this.values.add(state);
- if (id >= 1 << this.bits) {
+ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
+ // We use size() instead of the result from add(K)
+ // This avoids adding another object unnecessarily
+ // Without this change, + 2 would be required in the constructor
+ if (this.values.size() >= 1 << this.bits) {
id = this.resizeHandler.onResize(this.bits + 1, state);
+ } else {
+ id = this.values.add(state);
}
+ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
}
return id;

View File

@@ -0,0 +1,307 @@
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -76,7 +_,7 @@
};
private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
public boolean loaded;
- public final Level level;
+ public final ServerLevel level; // CraftBukkit - type
@Nullable
private Supplier<FullChunkStatus> fullStatus;
@Nullable
@@ -85,6 +_,14 @@
private final LevelChunkTicks<Block> blockTicks;
private final LevelChunkTicks<Fluid> fluidTicks;
private LevelChunk.UnsavedListener unsavedListener = chunkPos -> {};
+ // CraftBukkit start
+ public boolean mustNotSave;
+ public boolean needsDecoration;
+ // CraftBukkit end
+
+ // Paper start
+ boolean loadedTicketLevel;
+ // Paper end
public LevelChunk(Level level, ChunkPos pos) {
this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
@@ -102,7 +_,7 @@
@Nullable BlendingData blendingData
) {
super(pos, data, level, level.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData);
- this.level = level;
+ this.level = (net.minecraft.server.level.ServerLevel) level; // CraftBukkit - type
this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
for (Heightmap.Types types : Heightmap.Types.values()) {
@@ -154,6 +_,10 @@
this.skyLightSources = chunk.skyLightSources;
this.setLightCorrect(chunk.isLightCorrect());
this.markUnsaved();
+ this.needsDecoration = true; // CraftBukkit
+ // CraftBukkit start
+ this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
+ // CraftBukkit end
}
public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
@@ -162,6 +_,12 @@
unsavedListener.setUnsaved(this.chunkPos);
}
}
+ // Paper start
+ @Override
+ public long getInhabitedTime() {
+ return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
+ }
+ // Paper end
@Override
public void markUnsaved() {
@@ -195,8 +_,25 @@
: super.getListenerRegistry(sectionY);
}
+ // Paper start - Perf: Reduce instructions and provide final method
+ public BlockState getBlockState(final int x, final int y, final int z) {
+ return this.getBlockStateFinal(x, y, z);
+ }
+ public BlockState getBlockStateFinal(final int x, final int y, final int z) {
+ // Copied and modified from below
+ final int sectionIndex = this.getSectionIndex(y);
+ if (sectionIndex < 0 || sectionIndex >= this.sections.length
+ || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
+ return Blocks.AIR.defaultBlockState();
+ }
+ return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
+ }
@Override
public BlockState getBlockState(BlockPos pos) {
+ if (true) {
+ return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
+ }
+ // Paper end - Perf: Reduce instructions and provide final method
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
@@ -231,33 +_,54 @@
}
}
+ // Paper start - If loaded util
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ return this.getFluidState(blockposition);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ return this.getBlockState(blockposition);
+ }
+ // Paper end
+
@Override
public FluidState getFluidState(BlockPos pos) {
return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
}
public FluidState getFluidState(int x, int y, int z) {
- try {
+ // try { // Paper start - Perf: Optimise Chunk#getFluid
int sectionIndex = this.getSectionIndex(y);
if (sectionIndex >= 0 && sectionIndex < this.sections.length) {
LevelChunkSection levelChunkSection = this.sections[sectionIndex];
if (!levelChunkSection.hasOnlyAir()) {
- return levelChunkSection.getFluidState(x & 15, y & 15, z & 15);
+ return levelChunkSection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid
}
}
return Fluids.EMPTY.defaultFluidState();
+ /* // Paper - Perf: Optimise Chunk#getFluid
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Getting fluid state");
CrashReportCategory crashReportCategory = crashReport.addCategory("Block being got");
crashReportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, x, y, z));
throw new ReportedException(crashReport);
}
+ */ // Paper - Perf: Optimise Chunk#getFluid
}
@Nullable
@Override
public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) {
+// CraftBukkit start
+ return this.setBlockState(pos, state, isMoving, true);
+ }
+
+ @Nullable
+ public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) {
+ // CraftBukkit end
int y = pos.getY();
LevelChunkSection section = this.getSection(this.getSectionIndex(y));
boolean hasOnlyAir = section.hasOnlyAir();
@@ -292,7 +_,7 @@
}
boolean hasBlockEntity = blockState.hasBlockEntity();
- if (!this.level.isClientSide) {
+ if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
blockState.onRemove(this.level, pos, state, isMoving);
} else if (!blockState.is(block) && hasBlockEntity) {
this.removeBlockEntity(pos);
@@ -301,7 +_,7 @@
if (!section.getBlockState(i, i1, i2).is(block)) {
return null;
} else {
- if (!this.level.isClientSide) {
+ if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
state.onPlace(this.level, pos, blockState, isMoving);
}
@@ -355,7 +_,12 @@
@Nullable
public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
- BlockEntity blockEntity = this.blockEntities.get(pos);
+ // CraftBukkit start
+ BlockEntity blockEntity = this.level.capturedTileEntities.get(pos);
+ if (blockEntity == null) {
+ blockEntity = this.blockEntities.get(pos);
+ }
+ // CraftBukkit end
if (blockEntity == null) {
CompoundTag compoundTag = this.pendingBlockEntities.remove(pos);
if (compoundTag != null) {
@@ -409,7 +_,13 @@
BlockPos blockPos = blockEntity.getBlockPos();
BlockState blockState = this.getBlockState(blockPos);
if (!blockState.hasBlockEntity()) {
- LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", blockEntity, blockPos, blockState);
+ // Paper start - ServerExceptionEvent
+ com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException(
+ "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockPos, blockState)
+ );
+ e.printStackTrace();
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e);
+ // Paper end - ServerExceptionEvent
} else {
BlockState blockState1 = blockEntity.getBlockState();
if (blockState != blockState1) {
@@ -457,6 +_,11 @@
public void removeBlockEntity(BlockPos pos) {
if (this.isInLevel()) {
BlockEntity blockEntity = this.blockEntities.remove(pos);
+ // CraftBukkit start - SPIGOT-5561: Also remove from pending map
+ if (!this.pendingBlockEntities.isEmpty()) {
+ this.pendingBlockEntities.remove(pos);
+ }
+ // CraftBukkit end
if (blockEntity != null) {
if (this.level instanceof ServerLevel serverLevel) {
this.removeGameEventListener(blockEntity, serverLevel);
@@ -499,6 +_,65 @@
}
}
+ // CraftBukkit start
+ public void loadCallback() {
+ // Paper start
+ this.loadedTicketLevel = true;
+ // Paper end
+ org.bukkit.Server server = this.level.getCraftServer();
+ this.level.getChunkSource().addLoadedChunk(this); // Paper
+ if (server != null) {
+ /*
+ * If it's a new world, the first few chunks are generated inside
+ * the World constructor. We can't reliably alter that, so we have
+ * no way of creating a CraftWorld/CraftServer at that point.
+ */
+ 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;
+ java.util.Random random = new java.util.Random();
+ random.setSeed(this.level.getSeed());
+ long xRand = random.nextLong() / 2L * 2L + 1L;
+ long zRand = random.nextLong() / 2L * 2L + 1L;
+ random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed());
+
+ org.bukkit.World world = this.level.getWorld();
+ if (world != null) {
+ this.level.populating = true;
+ try {
+ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+ populator.populate(world, random, bukkitChunk);
+ }
+ } finally {
+ this.level.populating = false;
+ }
+ }
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
+ }
+ }
+ }
+
+ public void unloadCallback() {
+ org.bukkit.Server server = this.level.getCraftServer();
+ 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();
+ this.level.getChunkSource().removeLoadedChunk(this); // Paper
+ // Paper start
+ this.loadedTicketLevel = false;
+ // Paper end
+ }
+
+ @Override
+ public boolean isUnsaved() {
+ return super.isUnsaved() && !this.mustNotSave;
+ }
+ // CraftBukkit end
+
public boolean isEmpty() {
return false;
}
@@ -711,23 +_,25 @@
if (this.blockEntity.getType().isValid(blockState)) {
this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity);
this.loggedInvalidBlockState = false;
- } else if (!this.loggedInvalidBlockState) {
- this.loggedInvalidBlockState = true;
- LevelChunk.LOGGER
- .warn(
- "Block entity {} @ {} state {} invalid for ticking:",
- LogUtils.defer(this::getType),
- LogUtils.defer(this::getPos),
- blockState
- );
- }
+ // Paper start - Remove the Block Entity if it's invalid
+ } else {
+ LevelChunk.this.removeBlockEntity(this.getPos());
+ if (!this.loggedInvalidBlockState) {
+ this.loggedInvalidBlockState = true;
+ LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), blockState});
+ }
+ // Paper end - Remove the Block Entity if it's invalid
+ }
+
profilerFiller.pop();
} catch (Throwable var5) {
- CrashReport crashReport = CrashReport.forThrowable(var5, "Ticking block entity");
- CrashReportCategory crashReportCategory = crashReport.addCategory("Block entity being ticked");
- this.blockEntity.fillCrashReportCategory(crashReportCategory);
- throw new ReportedException(crashReport);
+ // Paper start - Prevent block entity and entity crashes
+ final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
+ net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
+ net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
+ LevelChunk.this.removeBlockEntity(this.getPos());
+ // Paper end - Prevent block entity and entity crashes
}
}
}

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -18,11 +_,11 @@
public static final int SECTION_HEIGHT = 16;
public static final int SECTION_SIZE = 4096;
public static final int BIOME_CONTAINER_BITS = 2;
- private short nonEmptyBlockCount;
+ short nonEmptyBlockCount; // Paper - package private
private short tickingBlockCount;
private short tickingFluidCount;
public final PalettedContainer<BlockState> states;
- private PalettedContainerRO<Holder<Biome>> biomes;
+ private PalettedContainer<Holder<Biome>> biomes; // CraftBukkit - read/write
private LevelChunkSection(LevelChunkSection section) {
this.nonEmptyBlockCount = section.nonEmptyBlockCount;
@@ -32,7 +_,7 @@
this.biomes = section.biomes.copy();
}
- public LevelChunkSection(PalettedContainer<BlockState> states, PalettedContainerRO<Holder<Biome>> biomes) {
+ public LevelChunkSection(PalettedContainer<BlockState> states, PalettedContainer<Holder<Biome>> biomes) { // CraftBukkit - read/write
this.states = states;
this.biomes = biomes;
this.recalcBlockCounts();
@@ -48,7 +_,7 @@
}
public FluidState getFluidState(int x, int y, int z) {
- return this.states.get(x, y, z).getFluidState();
+ return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid.
}
public void acquire() {
@@ -185,6 +_,11 @@
public Holder<Biome> getNoiseBiome(int x, int y, int z) {
return this.biomes.get(x, y, z);
}
+ // CraftBukkit start
+ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+ this.biomes.set(i, j, k, biome);
+ }
+ // CraftBukkit end
public void fillBiomesFromNoise(BiomeResolver biomeResolver, Climate.Sampler climateSampler, int x, int y, int z) {
PalettedContainer<Holder<Biome>> palettedContainer = this.biomes.recreate();

View File

@@ -0,0 +1,74 @@
--- a/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -30,14 +_,14 @@
public final IdMap<T> registry;
private volatile PalettedContainer.Data<T> data;
private final PalettedContainer.Strategy strategy;
- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
+ //private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
public void acquire() {
- this.threadingDetector.checkAndLock();
+ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization
}
public void release() {
- this.threadingDetector.checkAndUnlock();
+ // this.threadingDetector.checkAndUnlock(); // Paper - disable this - use proper synchronization
}
public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> registry, Codec<T> codec, PalettedContainer.Strategy strategy, T value) {
@@ -99,7 +_,7 @@
}
@Override
- public int onResize(int bits, T objectAdded) {
+ public synchronized int onResize(int bits, T objectAdded) { // Paper - synchronize
PalettedContainer.Data<T> data = this.data;
PalettedContainer.Data<T> data1 = this.createOrReuseData(data, bits);
data1.copyFrom(data.palette, data.storage);
@@ -107,7 +_,7 @@
return data1.palette.idFor(objectAdded);
}
- public T getAndSet(int x, int y, int z, T state) {
+ public synchronized T getAndSet(int x, int y, int z, T state) { // Paper - synchronize
this.acquire();
Object var5;
@@ -130,7 +_,7 @@
return this.data.palette.valueFor(andSet);
}
- public void set(int x, int y, int z, T state) {
+ public synchronized void set(int x, int y, int z, T state) { // Paper - synchronize
this.acquire();
try {
@@ -163,7 +_,7 @@
set.forEach(id -> consumer.accept(palette.valueFor(id)));
}
- public void read(FriendlyByteBuf buffer) {
+ public synchronized void read(FriendlyByteBuf buffer) { // Paper - synchronize
this.acquire();
try {
@@ -178,7 +_,7 @@
}
@Override
- public void write(FriendlyByteBuf buffer) {
+ public synchronized void write(FriendlyByteBuf buffer) { // Paper - synchronize
this.acquire();
try {
@@ -226,7 +_,7 @@
}
@Override
- public PalettedContainerRO.PackedData<T> pack(IdMap<T> registry, PalettedContainer.Strategy strategy) {
+ public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> registry, PalettedContainer.Strategy strategy) { // Paper - synchronize
this.acquire();
PalettedContainerRO.PackedData var12;

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -85,6 +_,18 @@
return new ChunkAccess.PackedTicks(this.blockTicks.pack(gametime), this.fluidTicks.pack(gametime));
}
+ // Paper start - If loaded util
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ return this.getFluidState(blockposition);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ return this.getBlockState(blockposition);
+ }
+ // Paper end
+
@Override
public BlockState getBlockState(BlockPos pos) {
int y = pos.getY();

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/level/chunk/UpgradeData.java
+++ b/net/minecraft/world/level/chunk/UpgradeData.java
@@ -113,6 +_,24 @@
}
}
+ // Paper start - filter out relocated neighbour ticks
+ // The lists are only supposed to contain ticks for the 1 radius neighbours of the chunk
+ private static <T> void filterTickList(int chunkX, int chunkZ, List<SavedTick<T>> ticks) {
+ for (java.util.Iterator<SavedTick<T>> iterator = ticks.iterator(); iterator.hasNext();) {
+ SavedTick<T> tick = iterator.next();
+ BlockPos tickPos = tick.pos();
+ int tickCX = tickPos.getX() >> 4;
+ int tickCZ = tickPos.getZ() >> 4;
+
+ int dist = Math.max(Math.abs(chunkX - tickCX), Math.abs(chunkZ - tickCZ));
+
+ if (dist != 1) {
+ LOGGER.warn("Neighbour tick '" + tick + "' serialized in chunk (" + chunkX + "," + chunkZ + ") is too far (" + tickCX + "," + tickCZ + ")");
+ iterator.remove();
+ }
+ }
+ }
+ // Paper end - filter out relocated neighbour ticks
public void upgrade(LevelChunk chunk) {
this.upgradeInside(chunk);
@@ -120,6 +_,10 @@
upgradeSides(chunk, direction8);
}
+ // Paper start - filter out relocated neighbour ticks
+ filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks);
+ filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks);
+ // Paper end - filter out relocated neighbour ticks
Level level = chunk.getLevel();
this.neighborBlockTicks.forEach(blockTicker -> {
Block block = blockTicker.type() == Blocks.AIR ? level.getBlockState(blockTicker.pos()).getBlock() : blockTicker.type();
@@ -129,6 +_,7 @@
Fluid fluid = fluidTicker.type() == Fluids.EMPTY ? level.getFluidState(fluidTicker.pos()).getType() : fluidTicker.type();
level.scheduleTick(fluidTicker.pos(), fluid, fluidTicker.delay(), fluidTicker.priority());
});
+ UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised
CHUNKY_FIXERS.forEach(fixers -> fixers.processChunk(level));
}