More patches

This commit is contained in:
Nassim Jahnke 2024-04-25 13:02:27 +02:00
parent 4047edb114
commit c42850dbcc
16 changed files with 90 additions and 105 deletions

View File

@ -47,10 +47,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) {
public ChunkStorage(RegionStorageInfo storageKey, Path directory, DataFixer dataFixer, boolean dsync) {
this.fixerUpper = dataFixer;
- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - rewrite chunk system; async chunk IO
+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - rewrite chunk system; async chunk IO & Attempt to recalculate regionfile header if it is corrupt
- this.regionFileCache = new RegionFileStorage(storageKey, directory, dsync); // Paper - rewrite chunk system; async chunk IO
+ this.regionFileCache = new RegionFileStorage(storageKey, directory, dsync, true); // Paper - rewrite chunk system; async chunk IO & Attempt to recalculate regionfile header if it is corrupt
}
public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) {
@ -91,9 +91,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
protected final RegionBitmap usedSectors;
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper
public final Path regionFile; // Paper
+
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ private static long roundToSectors(long bytes) {
+ long sectors = bytes >>> 12; // 4096 = 2^12
@ -447,31 +448,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
}
+ // Paper start - add can recalc flag
+ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this(file, directory, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader);
+ public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader);
+ }
+ // Paper end - add can recalc flag
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
+ // Paper start - add can recalc flag
+ this(file, directory, outputChunkStreamVersion, dsync, false);
public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException {
+ this(storageKey, path, directory, compressionFormat, dsync, true);
+ }
+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
+
+ public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this.canRecalcHeader = canRecalcHeader;
+ // Paper end - add can recalc flag
this.header = ByteBuffer.allocateDirect(8192);
this.regionFile = file; // Paper
initOversizedState(); // Paper
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i);
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", path, i);
}
- long j = Files.size(file);
+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption
- long j = Files.size(path);
+ final long j = Files.size(path); final long regionFileSize = j; // Paper - recalculate header on header corruption
- for (int k = 0; k < 1024; ++k) {
- int l = this.offsets.get(k);
@ -495,15 +495,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
if (i1 < 2) {
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1});
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1});
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if (j1 == 0) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k);
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, k);
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if ((long) i1 * 4096L > j) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1});
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{path, k, i1});
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else {
@ -559,6 +559,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
}
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
}
private Path getExternalChunkPath(ChunkPos chunkPos) {
@ -656,6 +657,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, j1);
- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
@ -689,7 +691,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap();
private final RegionStorageInfo info;
private final Path folder;
private final boolean sync;
+ private final boolean isChunkData; // Paper
@ -699,16 +701,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
// Paper end - cache regionfile does not exist state
protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor
protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected constructor
+ // Paper start - add isChunkData param
+ this(directory, dsync, false);
+ this(storageKey, directory, dsync, false);
+ }
+ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) {
+ RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync, boolean isChunkData) {
+ this.isChunkData = isChunkData;
+ // Paper end - add isChunkData param
this.folder = directory;
this.sync = dsync;
}
this.info = storageKey;
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
// Paper - only create directory if not existing only - moved down
Path path = this.folder;
@ -722,8 +724,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
// Paper end - cache regionfile does not exist state
FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
- RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync);
+ RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
this.regionCache.putAndMoveToFirst(i, regionfile1);
// Paper start
@ -766,12 +768,12 @@ diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVer
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
import net.minecraft.util.FastBufferedInputStream;
@@ -0,0 +0,0 @@ import org.slf4j.Logger;
public class RegionFileVersion {
private static final Logger LOGGER = LogUtils.getLogger();
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
new RegionFileVersion(
1, stream -> new FastBufferedInputStream(new GZIPInputStream(stream)), stream -> new BufferedOutputStream(new GZIPOutputStream(stream))

View File

@ -177,14 +177,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
private final ImmutableMap<Property<?>, Comparable<?>> values;
private final Reference2ObjectArrayMap<Property<?>, Comparable<?>> values;
private Table<Property<?>, Comparable<?>, S> neighbours;
protected final MapCodec<S> propertiesCodec;
+ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup
protected StateHolder(O owner, ImmutableMap<Property<?>, Comparable<?>> entries, MapCodec<S> codec) {
protected StateHolder(O owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<S> codec) {
this.owner = owner;
this.values = entries;
this.values = propertyMap;
this.propertiesCodec = codec;
+ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup
}

View File

@ -1117,7 +1117,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
@Override
public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
if (!oldState.is(state.getBlock()) && !world.isClientSide) {
- this.updatePowerStrength(world, pos, state);
+ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone
@ -1134,7 +1134,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
}
@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
if (!world.isClientSide) {
if (state.canSurvive(world, pos)) {
- this.updatePowerStrength(world, pos, state);

View File

@ -95,7 +95,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// CraftBukkit end
this.dead = true;
- this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customCausingEntity(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit
+ this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customEventDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit
+ this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customEventDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API
this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
this.spawnLingeringCloud();
// CraftBukkit start

View File

@ -218,14 +218,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.worldData.setDataConfiguration(worlddataconfiguration);
this.resources.managers.updateRegistryTags(this.registryAccess());
net.minecraft.world.item.alchemy.PotionBrewing.reload(); // Paper - Custom Potion Mixes
this.resources.managers.updateRegistryTags();
this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
- this.getPlayerList().saveAll();
+ // Paper start
+ if (Thread.currentThread() != this.serverThread) {
+ return;
+ }
+ // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements
+ // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
+ for (ServerPlayer player : this.getPlayerList().getPlayers()) {
+ player.getAdvancements().save();
+ }
@ -537,8 +537,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="WARN" packages="com.mojang.util">
+<Configuration status="WARN" packages="com.mojang.util" shutdownHook="disable">
-<Configuration status="WARN">
+<Configuration status="WARN" shutdownHook="disable">
<Appenders>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />

View File

@ -389,7 +389,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- public int tickingBlockCount;
- public int tickingFluidCount;
-
- a() {}
- a(final LevelChunkSection chunksection) {}
-
- public void accept(BlockState iblockdata, int i) {
+ // Paper start - unfuck this
@ -425,13 +425,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ });
}
-
- a a0 = new a();
- a a0 = new a(this);
-
- this.states.count(a0);
- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
- this.tickingBlockCount = (short) a0.tickingBlockCount;
- this.tickingFluidCount = (short) a0.tickingFluidCount;
+ // Paper end
+ // Paper end - unfuck this
}
public PalettedContainer<BlockState> getStates() {

View File

@ -11,7 +11,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
.stable();
};
private static final Logger LOGGER = LogUtils.getLogger();
public static final BlockPos ZERO = new BlockPos(0, 0, 0);
- private static final int PACKED_X_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000));

View File

@ -24,11 +24,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return null;
} else {
- Map<Target, BlockPos> map = positions.stream()
- .collect(Collectors.toMap(pos -> this.nodeEvaluator.getGoal((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()), Function.identity()));
- .collect(Collectors.toMap(pos -> this.nodeEvaluator.getTarget((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()), Function.identity()));
+ // Paper start - Perf: remove streams and optimize collection
+ List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
+ for (BlockPos pos : positions) {
+ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), pos));
+ for (final BlockPos pos : positions) {
+ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos));
+ }
+ // Paper end - Perf: remove streams and optimize collection
Path path = this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier);

View File

@ -69,7 +69,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
@@ -0,0 +0,0 @@ public class PistonBaseBlock extends DirectionalBlock {
@Override
public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING);
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below
@ -110,23 +110,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
/** @deprecated */
@Deprecated
public void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> stackMerger) {
}
protected void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> stackMerger) {
- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) {
+ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed
Block block = state.getBlock();
boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
/** @deprecated */
@Deprecated
public boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
}
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem()));
+ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
}
/** @deprecated */
protected boolean canBeReplaced(BlockState state, Fluid fluid) {
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.legacySolid;
}

View File

@ -239,7 +239,7 @@ diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/jav
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.EntityDataSerializers;
@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.SyncedDataHolder;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@ -247,7 +247,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.teleportTo(worldserver, null);
}
// Paper end - make end portalling safe

View File

@ -5,26 +5,6 @@ Subject: [PATCH] Write SavedData IO async
Co-Authored-By: Shane Freeder <theboyetronic@gmail.com>
diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
@@ -0,0 +0,0 @@ public class ThreadedWorldUpgrader {
}
this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5));
+ // Paper start - Write SavedData IO async
+ this.threadPool.execute(() -> {
+ try {
+ worldPersistentData.close();
+ } catch (IOException exception) {
+ LOGGER.error("Failed to close persistent world data", exception);
+ }
+ });
+ // Paper end - Write SavedData IO async
}
this.threadPool.shutdown();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@ -85,20 +65,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@@ -0,0 +0,0 @@ public class WorldUpgrader {
}
}
- this.overworldDataStorage.save();
+ // Paper start - Write SavedData IO async
+ try {
+ this.overworldDataStorage.close();
+ } catch (IOException exception) {
+ LOGGER.error("Failed to close persistent world data", exception);
+ }
+ // Paper end - Write SavedData IO async
i = Util.getMillis() - i;
WorldUpgrader.LOGGER.info("World optimizaton finished after {} ms", i);
this.finished = true;
(new WorldUpgrader.PoiUpgrader(this)).upgrade();
WorldUpgrader.LOGGER.info("Upgrading blocks");
(new WorldUpgrader.ChunkUpgrader()).upgrade();
- this.overworldDataStorage.save();
+ // Paper start - Write SavedData IO async
+ try {
+ this.overworldDataStorage.close();
+ } catch (final IOException e) {
+ LOGGER.error("Failed to close persistent world data", e);
+ }
+ // Paper end - Write SavedData IO async
i = Util.getMillis() - i;
WorldUpgrader.LOGGER.info("World optimizaton finished after {} seconds", i / 1000L);
this.finished = true;
diff --git a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
@ -107,22 +87,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return this.dirty;
}
+ @io.papermc.paper.annotation.DoNotUse // Paper - Write SavedData IO async - This is dead
public void save(File file) {
+ save(file, null).join(); // Paper - Write SavedData IO async - joining is evil, but we assume the old blocking behavior here just for safety
+ // Paper start - Write SavedData IO async - joining is evil, but we assume the old blocking behavior here just for safety
+ @io.papermc.paper.annotation.DoNotUse
public void save(File file, HolderLookup.Provider registryLookup) {
+ save(file, registryLookup, null).join();
+ }
+
+ public java.util.concurrent.CompletableFuture<Void> save(File file, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) { // Paper - Write SavedData IO async
+ public java.util.concurrent.CompletableFuture<Void> save(File file, HolderLookup.Provider registryLookup, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) {
+ // Paper end - Write SavedData IO async
if (this.isDirty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.put("data", this.save(new CompoundTag()));
compoundTag.put("data", this.save(new CompoundTag(), registryLookup));
NbtUtils.addCurrentDataVersion(compoundTag);
+ Runnable writeRunnable = () -> { // Paper - Write SavedData IO async
try {
NbtIo.writeCompressed(compoundTag, file.toPath());
} catch (IOException var4) {
LOGGER.error("Could not save data {}", this, var4);
} catch (IOException var5) {
LOGGER.error("Could not save data {}", this, var5);
}
+ }; // Paper - Write SavedData IO async
@ -137,7 +119,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - Write SavedData IO async
}
public static record Factory<T extends SavedData>(Supplier<T> constructor, Function<CompoundTag, T> deserializer, DataFixTypes type) {
public static record Factory<T extends SavedData>(
diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
@ -151,14 +133,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private static final Logger LOGGER = LogUtils.getLogger();
public final Map<String, SavedData> cache = Maps.newHashMap();
private final DataFixer fixerUpper;
private final HolderLookup.Provider registries;
private final File dataFolder;
+ protected final java.util.concurrent.ExecutorService ioExecutor; // Paper - Write SavedData IO async
public DimensionDataStorage(File directory, DataFixer dataFixer) {
public DimensionDataStorage(File directory, DataFixer dataFixer, HolderLookup.Provider registryLookup) {
this.fixerUpper = dataFixer;
this.dataFolder = directory;
+ String worldFolder = dataFolder.getParent(); // Paper - Write SavedData IO async
+ this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + worldFolder + " - %d").setDaemon(true).build()); // Paper - Write SavedData IO async
this.registries = registryLookup;
+ this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + dataFolder.getParent() + " - %d").setDaemon(true).build()); // Paper - Write SavedData IO async
}
private File getDataFile(String id) {
@ -178,9 +161,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public void save(boolean async) { // Paper - Write SavedData IO async
this.cache.forEach((id, state) -> {
if (state != null) {
- state.save(this.getDataFile(id));
- state.save(this.getDataFile(id), this.registries);
+ // Paper start - Write SavedData IO async
+ final java.util.concurrent.CompletableFuture<Void> save = state.save(this.getDataFile(id), ioExecutor);
+ final java.util.concurrent.CompletableFuture<Void> save = state.save(this.getDataFile(id), this.registries, this.ioExecutor);
+ if (!async) {
+ save.join();
+ }