More more more more more more more work

This commit is contained in:
Nassim Jahnke 2021-11-23 16:50:18 +01:00
parent 82f6e6bb0e
commit c36c2d46d3
20 changed files with 179 additions and 212 deletions

View File

@ -72,12 +72,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ continue;
+ }
+ // Paper end
Phantom entityphantom = (Phantom) EntityType.PHANTOM.create((Level) world);
Phantom entityphantom = (Phantom) EntityType.PHANTOM.create(world);
-
+ entityphantom.setSpawningEntity(entityhuman.getUUID()); // Paper
entityphantom.moveTo(blockposition1, 0.0F, 0.0F);
groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null);
world.addAllEntities(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java

View File

@ -23,9 +23,9 @@ diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/sr
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty;
@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.FlowingFluid;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.pathfinder.PathComputationType;
@ -35,8 +35,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@Override
public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
if (this.shouldSpreadLiquid(world, pos, state)) {
- world.getLiquidTicks().scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay((LevelReader) world));
+ world.getLiquidTicks().scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world));
+ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
}
}
@ -64,8 +64,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) {
if (this.shouldSpreadLiquid(world, pos, state)) {
- world.getLiquidTicks().scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay((LevelReader) world));
+ world.getLiquidTicks().scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world));
+ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
}
}

View File

@ -14,11 +14,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
log("Creeper lingering effect: " + disableCreeperLingeringEffect);
}
+
+ public double squidMaxSpawnHeight;
+ private void squidMaxSpawnHeight() {
+ squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D);
+ }
+
+ public boolean disableSprintInterruptionOnAttack;
+ private void disableSprintInterruptionOnAttack() {
+ disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false);

View File

@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 26 Aug 2018 20:49:50 -0400
Subject: [PATCH] Optimize MappedRegistry
Use larger initial sizes to increase bucket capacity on the BiMap
BiMap.get was seen to be using a good bit of CPU time.
diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/core/MappedRegistry.java
+++ b/src/main/java/net/minecraft/core/MappedRegistry.java
@@ -0,0 +0,0 @@ public class MappedRegistry<T> extends WritableRegistry<T> {
protected static final Logger LOGGER = LogManager.getLogger();
private final ObjectList<T> byId = new ObjectArrayList(256);
- private final Object2IntMap<T> toId = (Object2IntMap) Util.make(new Object2IntOpenCustomHashMap(Util.identityStrategy()), (object2intopencustomhashmap) -> {
- object2intopencustomhashmap.defaultReturnValue(-1);
- });
- private final BiMap<ResourceLocation, T> storage = HashBiMap.create();
- private final BiMap<ResourceKey<T>, T> keyStorage = HashBiMap.create();
- private final Map<T, Lifecycle> lifecycles = Maps.newIdentityHashMap();
+ private final it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap<T> toId = new it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap<T>(2048);// Paper - use bigger expected size to reduce collisions and direct intent for FastUtil to be identity map
+ private final BiMap<ResourceLocation, T> storage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions
+ private final BiMap<ResourceKey<T>, T> keyStorage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions
+ private final Map<T, Lifecycle> lifecycles = new java.util.IdentityHashMap<>(2048); // Paper - use bigger expected size to reduce collisions
private Lifecycle elementsLifecycle;
@Nullable
protected Object[] randomCache;
@@ -0,0 +0,0 @@ public class MappedRegistry<T> extends WritableRegistry<T> {
public MappedRegistry(ResourceKey<? extends Registry<T>> key, Lifecycle lifecycle) {
super(key, lifecycle);
- this.elementsLifecycle = lifecycle;
+ this.toId.defaultReturnValue(-1); // Paper
}
public static <T> MapCodec<MappedRegistry.RegistryEntry<T>> withNameAndId(ResourceKey<? extends Registry<T>> key, MapCodec<T> entryCodec) {

View File

@ -85,7 +85,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@Override
@@ -0,0 +0,0 @@ public class Slime extends Mob implements Enemy {
this.slime.lookAt((Entity) this.slime.getTarget(), 10.0F, 10.0F);
((Slime.SlimeMoveControl) this.slime.getMoveControl()).setDirection(this.slime.getYRot(), this.slime.isDealsDamage());
}
+
@ -107,12 +107,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
@Override
public void tick() {
@@ -0,0 +0,0 @@ public class Slime extends Mob implements Enemy {
if (--this.nextRandomizeTime <= 0) {
this.nextRandomizeTime = 40 + this.slime.getRandom().nextInt(60);
- this.chosenDegrees = (float) this.slime.getRandom().nextInt(360);
this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60));
this.chosenDegrees = (float) this.slime.getRandom().nextInt(360);
+ // Paper start
+ SlimeChangeDirectionEvent event = new SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (float) this.slime.getRandom().nextInt(360));
+ SlimeChangeDirectionEvent event = new SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees);
+ if (!this.slime.canWander || !event.callEvent()) return;
+ this.chosenDegrees = event.getNewYaw();
+ // Paper end

View File

@ -3,6 +3,9 @@ From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 13 Jul 2019 09:23:10 -0700
Subject: [PATCH] Asynchronous chunk IO and loading
# UPDATE NOTES: RegionFileStorage and SectionStorage need resolving (will conflict on apply)
# ChunkSerializer needs the new tick lists to be saved (see added todos)
This patch re-adds a file IO thread as well as shoving de-serializing
chunk NBT data onto worker threads. This patch also will shove
chunk data serialization onto the same worker threads when the chunk
@ -2282,8 +2285,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +0,0 @@ public final class MCUtil {
return null;
}
public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
}
+
+ public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
@ -2296,7 +2299,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/java/net/minecraft/server/Main.java
@@ -0,0 +0,0 @@ public class Main {
convertable_conversionsession.a((IRegistryCustom) iregistrycustom_dimension, (SaveData) object);
convertable_conversionsession.saveDataTag(iregistrycustom_dimension, (SaveData) object);
*/
+ Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName());// Paper - load this sync so it won't fail later async
final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
@ -2364,18 +2367,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return true;
});
- this.flushWorker();
+ //this.flushWorker(); // Paper - nuke IOWorker
+ this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
+// this.i(); // Paper - nuke IOWorker
} else {
this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> {
ChunkAccess ichunkaccess = (ChunkAccess) playerchunk.getChunkToSave().getNow(null); // CraftBukkit - decompile error
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; // Spigot
+ private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more
protected void tick(BooleanSupplier shouldKeepTicking) {
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
@ -2387,10 +2384,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (!this.level.noSave()) {
+ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper
this.processUnloads(shouldKeepTicking);
+ }// Paper
+ } // Paper
}
gameprofilerfiller.pop();
}
- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; // Spigot
+ private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more
private void processUnloads(BooleanSupplier shouldKeepTicking) {
LongIterator longiterator = this.toDrop.iterator();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (playerchunk != null) {
this.pendingUnloads.put(j, playerchunk);
@ -2402,7 +2406,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
// Spigot end
- this.scheduleUnload(j, playerchunk);
+ //this.a(j, playerchunk); // Paper - move up because spigot did a dumb
+ //this.scheduleUnload(j, playerchunk); // Paper - move up because spigot did a dumb
}
}
activityAccountant.endActivity(); // Spigot
@ -2418,7 +2422,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ChunkPos chunkPos = chunk.getPos();
+ CompoundTag poiData;
+ try (Timing ignored = this.level.timings.chunkUnloadPOISerialization.startTiming()) {
+ poiData = this.getVillagePlace().getData(chunk.getPos());
+ poiData = this.poiManager.getData(chunk.getPos());
+ }
+
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkPos.x, chunkPos.z,
@ -2467,7 +2471,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ LOGGER.fatal("Failed to prepare async save, attempting synchronous save", ex);
+ this.save(ichunkaccess);
+ }
+ // Paper end - async chunk saving
+ // Paper end - async chunk savin
if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) {
LevelChunk chunk = (LevelChunk) ichunkaccess;
@ -2484,30 +2488,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- try (Timing ignored2 = this.level.timings.chunkIO.startTimingIfSync()) { // Paper start - timings
- nbttagcompound = this.readChunk(pos);
- } // Paper end
-
- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings
- boolean flag = nbttagcompound.contains("Status", 8);
-
- if (flag) {
- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, nbttagcompound);
+ // Paper start
+ if (ioThrowable != null) {
+ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable);
+ }
- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings
- boolean flag = nbttagcompound.contains("Level", 10) && nbttagcompound.getCompound("Level").contains("Status", 8);
+ this.getVillagePlace().loadInData(pos, chunkHolder.poiData);
+ this.poiManager.loadInData(pos, chunkHolder.poiData);
+ chunkHolder.tasks.forEach(Runnable::run);
+ // Paper end
- if (flag) {
- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.structureManager, this.poiManager, pos, nbttagcompound);
+ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
+
+ if (true) {
+ ProtoChunk protochunk = chunkHolder.protoChunk;
this.markPosition(pos, protochunk.getStatus().getChunkType());
return Either.left(protochunk);
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.markPositionReplaceable(pos);
return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level));
return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level, this.level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), (BlendingData) null));
- }, this.mainThreadExecutor);
+ // Paper start - Async chunk io
+ };
@ -2537,7 +2540,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
}
private void markPositionReplaceable(ChunkPos chunkcoordintpair) {
private void markPositionReplaceable(ChunkPos pos) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
@ -2560,13 +2563,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.level.getProfiler().incrementCounter("chunkSave");
- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk);
-
- this.write(chunkcoordintpair, nbttagcompound);
+ CompoundTag nbttagcompound;
+ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveDataSerialization.startTiming()) { // Paper
+ nbttagcompound = ChunkSerializer.write(this.level, chunk);
+ } // Paper
+ } // Paper;
+
- this.write(chunkcoordintpair, nbttagcompound);
+ // Paper start - async chunk io
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkcoordintpair.x, chunkcoordintpair.z,
+ null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
@ -2581,7 +2584,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } // Paper
}
private boolean isExistingChunkFull(ChunkPos chunkcoordintpair) {
private boolean isExistingChunkFull(ChunkPos pos) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
}
@ -2618,14 +2621,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@Nullable
public CompoundTag readChunk(ChunkPos pos) throws IOException {
CompoundTag nbttagcompound = this.read(pos);
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
+ @Deprecated public PoiManager getVillagePlace() { return this.getPoiManager(); } // Paper - OBFHELPER
protected PoiManager getPoiManager() {
return this.poiManager;
}
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
@ -2765,7 +2760,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
gameprofilerfiller.incrementCounter("getChunkCacheMiss");
- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create);
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper
ServerChunkCache.MainThreadExecutor chunkproviderserver_a = this.mainThreadProcessor;
ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
Objects.requireNonNull(completablefuture);
if (!completablefuture.isDone()) { // Paper
@ -2774,7 +2769,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
+ // Paper end
this.level.timings.syncChunkLoad.startTiming(); // Paper
chunkproviderserver_a.managedBlock(completablefuture::isDone);
chunkproviderserver_b.managedBlock(completablefuture::isDone);
+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
this.level.timings.syncChunkLoad.stopTiming(); // Paper
} // Paper
@ -2937,8 +2932,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private final LongSet loadedChunks = new LongOpenHashSet();
+ private final net.minecraft.server.level.ServerLevel world; // Paper
public PoiManager(File directory, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
super(directory, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper
this.distanceTracker = new PoiManager.DistanceTracker();
}
@ -2999,32 +2994,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public static enum Occupancy {
HAS_SPACE(PoiRecord::hasSpace),
IS_OCCUPIED(PoiRecord::isOccupied),
diff --git a/src/main/java/net/minecraft/world/level/TickNextTickData.java b/src/main/java/net/minecraft/world/level/TickNextTickData.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/TickNextTickData.java
+++ b/src/main/java/net/minecraft/world/level/TickNextTickData.java
@@ -0,0 +0,0 @@ import java.util.Comparator;
import net.minecraft.core.BlockPos;
public class TickNextTickData<T> {
- private static long counter;
+ private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading
private final T type;
public final BlockPos pos;
public final long triggerTick;
@@ -0,0 +0,0 @@ public class TickNextTickData<T> {
}
public TickNextTickData(BlockPos pos, T t, long time, TickPriority priority) {
- this.c = (long)(counter++);
+ this.c = (TickNextTickData.COUNTER.getAndIncrement()); // Paper - async chunk loading
this.pos = pos.immutable();
this.type = t;
this.triggerTick = time;
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.ticks.LevelChunkTicks;
import net.minecraft.world.ticks.ProtoChunkTicks;
+import net.minecraft.world.ticks.TickContainerAccess;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -0,0 +0,0 @@ public class ChunkSerializer {
public ChunkSerializer() {}
@ -3044,56 +3025,50 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end
+
public static ProtoChunk read(ServerLevel world, StructureManager structureManager, PoiManager poiStorage, ChunkPos pos, CompoundTag nbt) {
public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) {
+ // Paper start - add variant for async calls
+ InProgressChunkHolder holder = loadChunk(world, structureManager, poiStorage, pos, nbt, true);
+ InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true);
+ holder.tasks.forEach(Runnable::run);
+ return holder.protoChunk;
+ }
+ public static InProgressChunkHolder loadChunk(ServerLevel world, StructureManager structureManager, PoiManager poiStorage, ChunkPos pos, CompoundTag nbt, boolean distinguish) {
+
+ public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) {
+ java.util.ArrayDeque<Runnable> tasksToExecuteOnMain = new java.util.ArrayDeque<>();
+ // Paper end
ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
CompoundTag nbttagcompound1 = nbt.getCompound("Level");
ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
if (!Objects.equals(chunkPos, chunkcoordintpair1)) {
@@ -0,0 +0,0 @@ public class ChunkSerializer {
LevelLightEngine lightengine = chunkproviderserver.getLightEngine();
if (flag) {
+ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main
lightengine.retainData(pos, true);
lightengine.retainData(chunkPos, true);
+ }); // Paper - delay this task since we're executing off-main
}
for (int j = 0; j < nbttaglist.size(); ++j) {
Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
@@ -0,0 +0,0 @@ public class ChunkSerializer {
achunksection[world.getSectionIndexFromSectionY(b0)] = chunksection;
}
+ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main
poiStorage.checkConsistencyWithBlocks(pos, chunksection);
+ }); // Paper - delay this task since we're executing off-main
}
if (flag) {
if (nbttagcompound2.contains("BlockLight", 7)) {
- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(pos, b0), new DataLayer(nbttagcompound2.getByteArray("BlockLight")), true);
if (nbttagcompound1.contains("BlockLight", 7)) {
- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight")), true);
+ // Paper start - delay this task since we're executing off-main
+ DataLayer blockLight = new DataLayer(nbttagcompound2.getByteArray("BlockLight"));
+ DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight"));
+ tasksToExecuteOnMain.add(() -> {
+ lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkcoordintpair1, b0), blockLight, true);
+ });
+ // Paper end - delay this task since we're executing off-main
}
if (flag1 && nbttagcompound2.contains("SkyLight", 7)) {
- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(pos, b0), new DataLayer(nbttagcompound2.getByteArray("SkyLight")), true);
if (flag1 && nbttagcompound1.contains("SkyLight", 7)) {
- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight")), true);
+ // Paper start - delay this task since we're executing off-main
+ DataLayer skyLight = new DataLayer(nbttagcompound2.getByteArray("SkyLight"));
+ DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight"));
+ tasksToExecuteOnMain.add(() -> {
+ lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkcoordintpair1, b0), skyLight, true);
+ });
+ // Paper end - delay this task since we're executing off-main
+ // Paper end - delay this task since we're executing off-mai
}
}
}
@ -3101,20 +3076,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) {
- return new ImposterProtoChunk((LevelChunk) object);
+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object), tasksToExecuteOnMain); // Paper - Async chunk loading
- return new ImposterProtoChunk((LevelChunk) object, false);
+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object, false), tasksToExecuteOnMain); // Paper - Async chunk loading
} else {
ProtoChunk protochunk1 = (ProtoChunk) object;
@@ -0,0 +0,0 @@ public class ChunkSerializer {
protochunk1.setCarvingMask(worldgenstage_features, BitSet.valueOf(nbttagcompound5.getByteArray(s1)));
protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound4.getLongArray(s1), ((ChunkAccess) object).getMinBuildHeight()));
}
- return protochunk1;
+ return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading
+ }
+ }
+
}
}
+ // Paper start - async chunk save for unload
+ public static final class AsyncSaveData {
+ public final DataLayer[] blockLight;
@ -3163,8 +3138,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ skyLight[i - lightenginethreaded.getMinLightSection()] = skyArray;
+ }
+
+ TickList<Block> blockTickList = chunk.getBlockTicks();
+ TickContainerAccess<Block> blockTickList = chunk.getBlockTicks();
+
+ //TODO check ChunkSerializer "block_ticks"
+ ListTag blockTickListSerialized;
+ if (blockTickList instanceof ProtoTickList || blockTickList instanceof ChunkTickList) {
+ blockTickListSerialized = null;
@ -3172,13 +3148,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ blockTickListSerialized = world.getBlockTicks().save(chunkPos);
+ }
+
+ TickList<Fluid> fluidTickList = chunk.getLiquidTicks();
+ TickContainerAccess<Fluid> fluidTickList = chunk.getFluidTicks();
+
+ //TODO
+ ListTag fluidTickListSerialized;
+ if (fluidTickList instanceof ProtoTickList || fluidTickList instanceof ChunkTickList) {
+ fluidTickListSerialized = null;
+ } else {
+ fluidTickListSerialized = world.getLiquidTicks().save(chunkPos);
+ fluidTickListSerialized = world.getFluidTicks().save(chunkPos);
+ }
+
+ ListTag blockEntitiesSerialized = new ListTag();
@ -3187,9 +3164,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ if (blockEntityNbt != null) {
+ blockEntitiesSerialized.add(blockEntityNbt);
+ }
}
+ }
+
+ return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, blockEntitiesSerialized, world.getGameTime());
+ }
+
private static void logErrors(ChunkPos chunkPos, int y, String message) {
ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message);
}
@@ -0,0 +0,0 @@ public class ChunkSerializer {
}
public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
@ -3199,23 +3182,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
ChunkPos chunkcoordintpair = chunk.getPos();
CompoundTag nbttagcompound = new CompoundTag();
CompoundTag nbttagcompound1 = new CompoundTag();
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound.put("Level", nbttagcompound1);
nbttagcompound1.putInt("xPos", chunkcoordintpair.x);
nbttagcompound1.putInt("zPos", chunkcoordintpair.z);
- nbttagcompound1.putLong("LastUpdate", world.getGameTime());
+ nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
nbttagcompound1.putLong("InhabitedTime", chunk.getInhabitedTime());
nbttagcompound1.putString("Status", chunk.getStatus().getName());
UpgradeData chunkconverter = chunk.getUpgradeData();
nbttagcompound.putInt("yPos", chunk.getMinSection());
nbttagcompound.putInt("zPos", chunkcoordintpair.z);
nbttagcompound.putLong("LastUpdate", world.getGameTime());
+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
nbttagcompound.putString("Status", chunk.getStatus().getName());
BlendingData blendingdata = chunk.getBlendingData();
@@ -0,0 +0,0 @@ public class ChunkSerializer {
LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> {
return chunksection1 != null && SectionPos.blockToSectionCoord(chunksection1.bottomBlockY()) == finalI; // CraftBukkit - decompile errors
}).findFirst().orElse(LevelChunk.EMPTY_SECTION);
for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
int j = chunk.getSectionIndexFromSectionY(i);
boolean flag1 = j >= 0 && j < achunksection.length;
- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
-
+ // Paper start - async chunk save for unload
+ DataLayer nibblearray; // block light
+ DataLayer nibblearray1; // sky light
@ -3227,11 +3208,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ nibblearray1 = asyncsavedata.skyLight[i - lightenginethreaded.getMinLightSection()];
+ }
+ // Paper end
if (chunksection != LevelChunk.EMPTY_SECTION || nibblearray != null || nibblearray1 != null) {
CompoundTag nbttagcompound2 = new CompoundTag();
if (flag1 || nibblearray != null || nibblearray1 != null) {
CompoundTag nbttagcompound1 = new CompoundTag();
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound1.putIntArray("Biomes", biomestorage.writeBiomes());
nbttagcompound.putBoolean("isLightOn", true);
}
- ListTag nbttaglist1 = new ListTag();
@ -3248,38 +3229,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end
CompoundTag nbttagcompound3;
CompoundTag nbttagcompound2;
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound1.put("ToBeTicked", ((ProtoTickList) ticklist).save());
} else if (ticklist instanceof ChunkTickList) {
nbttagcompound1.put("TileTicks", ((ChunkTickList) ticklist).save());
+ // Paper start - async chunk save for unload
+ } else if (asyncsavedata != null) {
+ nbttagcompound1.put("TileTicks", asyncsavedata.blockTickList);
+ // Paper end
} else {
nbttagcompound1.put("TileTicks", world.getBlockTicks().save(chunkcoordintpair));
}
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound1.put("LiquidsToBeTicked", ((ProtoTickList) ticklist1).save());
} else if (ticklist1 instanceof ChunkTickList) {
nbttagcompound1.put("LiquidTicks", ((ChunkTickList) ticklist1).save());
+ // Paper start - async chunk save for unload
+ } else if (asyncsavedata != null) {
+ nbttagcompound1.put("LiquidTicks", asyncsavedata.fluidTickList);
+ // Paper end
} else {
nbttagcompound1.put("LiquidTicks", world.getLiquidTicks().save(chunkcoordintpair));
}
private static void saveTicks(ServerLevel world, CompoundTag nbt, ChunkAccess.TicksToSave tickSchedulers) {
long i = world.getLevelData().getGameTime();
+ //TODO original patch line 3259
nbt.put("block_ticks", tickSchedulers.blocks().save(i, (block) -> {
return Registry.BLOCK.getKey(block).toString();
}));
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
public class ChunkStorage implements AutoCloseable {
public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493;
- private final IOWorker worker;
+ // Paper - nuke IO worker
protected final DataFixer fixerUpper;
@ -3291,7 +3258,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public final RegionFileStorage regionFileCache;
+ // Paper end - async chunk loading
public ChunkStorage(File directory, DataFixer dataFixer, boolean dsync) {
public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) {
this.fixerUpper = dataFixer;
- this.worker = new IOWorker(directory, dsync, "chunk");
+ // Paper start - async chunk io
@ -3366,7 +3333,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- this.worker.close();
+ this.regionFileCache.close(); // Paper - nuke IO worker
}
}
public ChunkScanAccess chunkScanner() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@ -3377,8 +3345,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
protected final RegionBitmap usedSectors;
+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
public RegionFile(File file, File directory, boolean dsync) throws IOException {
this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
public RegionFile(Path path, Path path1, boolean dsync) throws IOException {
this(path, path1, RegionFileVersion.VERSION_DEFLATE, dsync);
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
return (byteCount + 4096 - 1) / 4096;
}
@ -3410,7 +3378,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } // Paper end
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@ -3418,7 +3386,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
this.sync = dsync;
}
- public RegionFile getFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
+ // Paper start
+ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) {
@ -3438,7 +3406,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
if (regionfile != null) {
+ // Paper start
+ if (lock) {
@ -3451,7 +3419,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (this.regionCache.size() >= com.destroystokyo.paper.PaperConfig.regionFileCacheSize) { // Paper - configurable
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
RegionFile regionfile1 = new RegionFile(file1, this.folder, this.sync);
this.regionCache.putAndMoveToFirst(i, regionfile1);
+ // Paper start
+ if (lock) {
@ -3474,23 +3442,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// CraftBukkit end
+ try { // Paper
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
CompoundTag nbttagcompound;
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
}
return nbttagcompound;
+ } finally { // Paper start
+ regionfile.fileLock.unlock();
+ } // Paper end
}
protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
- RegionFile regionfile = this.getFile(pos, false); // CraftBukkit
+ RegionFile regionfile = this.getFile(pos, false, true); // CraftBukkit // Paper
+ try { // Paper
int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper
if (nbt == null) {
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
MinecraftServer.LOGGER.error("Failed to save chunk", laste);
@ -3500,20 +3468,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ regionfile.fileLock.unlock();
+ } // Paper end
}
- public void close() throws IOException {
+ public synchronized void close() throws IOException { // Paper -> synchronized
ExceptionCollector<IOException> exceptionsuppressor = new ExceptionCollector<>();
ObjectIterator objectiterator = this.regionCache.values().iterator();
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
exceptionsuppressor.throwIfPresent();
}
- public void flush() throws IOException {
+ public synchronized void flush() throws IOException { // Paper - synchronize
ObjectIterator objectiterator = this.regionCache.values().iterator();
while (objectiterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
@ -3522,7 +3490,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ import net.minecraft.world.level.LevelHeightAccessor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-public class SectionStorage<R> implements AutoCloseable {
+public class SectionStorage<R> extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker
private static final Logger LOGGER = LogManager.getLogger();
@ -3534,7 +3502,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private final Function<Runnable, Codec<R>> codec;
@@ -0,0 +0,0 @@ public class SectionStorage<R> implements AutoCloseable {
protected final LevelHeightAccessor levelHeightAccessor;
public SectionStorage(File directory, Function<Runnable, Codec<R>> codecFactory, Function<Runnable, R> factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, LevelHeightAccessor world) {
+ super(directory, dsync); // Paper - nuke IOWorker
this.codec = codecFactory;
@ -3545,11 +3513,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- this.worker = new IOWorker(directory, dsync, directory.getName());
+ // Paper - remove mojang I/O thread
}
protected void tick(BooleanSupplier shouldKeepTicking) {
@@ -0,0 +0,0 @@ public class SectionStorage<R> implements AutoCloseable {
}
private void readColumn(ChunkPos chunkPos) {
- this.readColumn(chunkPos, NbtOps.INSTANCE, this.tryRead(chunkPos));
+ // Paper start - expose function to load in data
@ -3559,7 +3527,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.readColumn(chunkPos, NbtOps.INSTANCE, compound);
+ // Paper end - expose function to load in data
}
@Nullable
private CompoundTag tryRead(ChunkPos pos) {
try {
@ -3577,9 +3545,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
} else {
LOGGER.error("Expected compound tag, got {}", (Object)tag);
}
}
+ // Paper start - internal get data function, copied from above
+ private CompoundTag getDataInternal(ChunkPos chunkcoordintpair) {
+ Dynamic<Tag> dynamic = this.writeColumn(chunkcoordintpair, NbtOps.INSTANCE);
@ -3595,9 +3563,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
private <T> Dynamic<T> writeColumn(ChunkPos chunkPos, DynamicOps<T> dynamicOps) {
Map<T, T> map = Maps.newHashMap();
@@ -0,0 +0,0 @@ public class SectionStorage<R> implements AutoCloseable {
@Override
public void close() throws IOException {
- this.worker.close();

View File

@ -1,35 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 26 Aug 2018 20:49:50 -0400
Subject: [PATCH] Optimize MappedRegistry
Use larger initial sizes to increase bucket capacity on the BiMap
BiMap.get was seen to be using a good bit of CPU time.
diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/core/MappedRegistry.java
+++ b/src/main/java/net/minecraft/core/MappedRegistry.java
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger;
public class MappedRegistry<T> extends WritableRegistry<T> {
protected static final Logger LOGGER = LogManager.getLogger();
private final ObjectList<T> byId = new ObjectArrayList<>(256);
- private final Object2IntMap<T> toId = new Object2IntOpenCustomHashMap<>(Util.identityStrategy());
+ private final it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap<T> toId = new it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap<T>(2048);// Paper - use bigger expected size to reduce collisions and direct intent for FastUtil to be identity map
private final BiMap<ResourceLocation, T> storage;
private final BiMap<ResourceKey<T>, T> keyStorage;
private final Map<T, Lifecycle> lifecycles;
@@ -0,0 +0,0 @@ public class MappedRegistry<T> extends WritableRegistry<T> {
public MappedRegistry(ResourceKey<? extends Registry<T>> key, Lifecycle lifecycle) {
super(key, lifecycle);
this.toId.defaultReturnValue(-1);
- this.storage = HashBiMap.create();
- this.keyStorage = HashBiMap.create();
- this.lifecycles = Maps.newIdentityHashMap();
+ this.storage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions
+ this.keyStorage = HashBiMap.create(2048); // Paper - use bigger expected size to reduce collisions
+ this.lifecycles = new java.util.IdentityHashMap<>(2048); // Paper - use bigger expected size to reduce collisions
this.elementsLifecycle = lifecycle;
}