From d04584410b030d1aec87ef8d6a6c5970bed0ae58 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 4 Jan 2023 05:42:36 -0500 Subject: [PATCH] Hearse: Async entity ai & traveling --- README.md | 1 + .../0037-Hearse-Async-entity-traveling.patch | 730 ++++++++++++++++++ .../server/0038-Hearse-Async-entity-ai.patch | 686 ++++++++++++++++ ...-fix-some-thread-problems-may-happen.patch | 179 +++++ 4 files changed, 1596 insertions(+) create mode 100644 patches/server/0037-Hearse-Async-entity-traveling.patch create mode 100644 patches/server/0038-Hearse-Async-entity-ai.patch create mode 100644 patches/server/0039-Hearse-Try-to-fix-some-thread-problems-may-happen.patch diff --git a/README.md b/README.md index 973e211c..f164afb3 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,4 @@ Credits: - [Petal](https://github.com/Bloom-host/Petal) - [Carpet Fixes](https://github.com/fxmorin/carpet-fixes) - [VMP](https://github.com/RelativityMC/VMP-fabric) +- [Hearse](https://github.com/NaturalCodeClub/Hearse) diff --git a/patches/server/0037-Hearse-Async-entity-traveling.patch b/patches/server/0037-Hearse-Async-entity-traveling.patch new file mode 100644 index 00000000..a26c73ef --- /dev/null +++ b/patches/server/0037-Hearse-Async-entity-traveling.patch @@ -0,0 +1,730 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BuildTools +Date: Wed, 4 Jan 2023 11:35:50 +0800 +Subject: [PATCH] Hearse: Async entity traveling + +Original license: +Original project: https://github.com/NaturalCodeClub/Hearse + +diff --git a/src/main/java/co/m2ek4u/aoame/AnotherTickThread.java b/src/main/java/co/m2ek4u/aoame/AnotherTickThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d5fe52beb25e7a95549cdf0ae19edf6029f10642 +--- /dev/null ++++ b/src/main/java/co/m2ek4u/aoame/AnotherTickThread.java +@@ -0,0 +1,13 @@ ++package co.m2ek4u.aoame; ++ ++import io.papermc.paper.util.TickThread; ++ ++public class AnotherTickThread extends TickThread { ++ public AnotherTickThread(String name) { ++ super(name); ++ } ++ ++ public AnotherTickThread(Runnable run, String name) { ++ super(run, name); ++ } ++} +diff --git a/src/main/java/co/m2ek4u/aoame/CallbackExecutor.java b/src/main/java/co/m2ek4u/aoame/CallbackExecutor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b861405a7626ba8fa677c455bf6507253b33c157 +--- /dev/null ++++ b/src/main/java/co/m2ek4u/aoame/CallbackExecutor.java +@@ -0,0 +1,99 @@ ++package co.m2ek4u.aoame; ++ ++import org.jetbrains.annotations.NotNull; ++import java.util.Queue; ++import java.util.concurrent.*; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.locks.LockSupport; ++ ++public class CallbackExecutor extends ThreadPoolExecutor { ++ private final AtomicBoolean isSubmittingStarted = new AtomicBoolean(false); ++ private final Queue submittedTasks = new ConcurrentLinkedDeque<>(); ++ ++ public CallbackExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue workQueue) { ++ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); ++ } ++ ++ public CallbackExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue workQueue, @NotNull ThreadFactory threadFactory) { ++ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); ++ } ++ ++ public CallbackExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue workQueue, @NotNull RejectedExecutionHandler handler) { ++ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); ++ } ++ ++ public CallbackExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue workQueue, @NotNull ThreadFactory threadFactory, @NotNull RejectedExecutionHandler handler) { ++ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); ++ } ++ ++ public void startSubmitting(){ ++ if (this.isSubmittingStarted.get()){ ++ throw new IllegalStateException(); ++ } ++ this.isSubmittingStarted.set(true); ++ } ++ ++ public void awaitSubmittingEnd(){ ++ while (this.isSubmittingStarted.get()){ ++ LockSupport.parkNanos(this,1); ++ } ++ } ++ ++ public void executeWithCallBack(Runnable command,Runnable callBack){ ++ if (this.isSubmittingStarted.get()){ ++ TaskEntry newTask = new TaskEntry(command,callBack); ++ this.execute(newTask::runMainTask); ++ this.submittedTasks.add(newTask); ++ return; ++ } ++ throw new IllegalStateException(); ++ } ++ ++ public boolean isSubmittingStarted(){ ++ return this.isSubmittingStarted.get(); ++ } ++ ++ public void stopSubmitting(){ ++ if (!this.isSubmittingStarted.get()){ ++ throw new IllegalStateException(); ++ } ++ this.isSubmittingStarted.set(false); ++ TaskEntry task; ++ while ((task = this.submittedTasks.poll())!=null){ ++ while (!task.runSubTask()){ ++ LockSupport.parkNanos(this,1); ++ } ++ } ++ } ++ ++ private static class TaskEntry{ ++ private final Runnable mainTask; ++ private final Runnable subTask; ++ private volatile boolean mainTaskFinished = false; ++ ++ public TaskEntry(Runnable mainTask,Runnable subTask){ ++ this.mainTask = mainTask; ++ this.subTask = subTask; ++ } ++ ++ public void runMainTask(){ ++ try { ++ this.mainTask.run(); ++ }finally { ++ this.mainTaskFinished = true; ++ } ++ } ++ ++ public boolean runSubTask(){ ++ if (!this.mainTaskFinished){ ++ return false; ++ } ++ try { ++ this.subTask.run(); ++ }catch (Exception e){ ++ e.printStackTrace(); ++ } ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +index 0133ea6feb1ab88f021f66855669f58367e7420b..b128433d2888a98bce55052e821626c0478748dd 100644 +--- a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +@@ -1,6 +1,9 @@ + package com.destroystokyo.paper.util.maplist; + + import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectList; ++import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.world.entity.Entity; + import java.util.Arrays; + import java.util.Iterator; +@@ -12,117 +15,42 @@ import java.util.NoSuchElementException; + */ + public final class EntityList implements Iterable { + +- protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); +- { +- this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); +- } +- +- protected static final Entity[] EMPTY_LIST = new Entity[0]; +- +- protected Entity[] entities = EMPTY_LIST; +- protected int count; ++ private final ObjectList objectList = ObjectLists.synchronize(new ObjectArrayList<>()); + + public int size() { +- return this.count; ++ return this.objectList.size(); + } + + public boolean contains(final Entity entity) { +- return this.entityToIndex.containsKey(entity.getId()); ++ return this.objectList.contains(entity); + } + + public boolean remove(final Entity entity) { +- final int index = this.entityToIndex.remove(entity.getId()); +- if (index == Integer.MIN_VALUE) { +- return false; +- } +- +- // move the entity at the end to this index +- final int endIndex = --this.count; +- final Entity end = this.entities[endIndex]; +- if (index != endIndex) { +- // not empty after this call +- this.entityToIndex.put(end.getId(), index); // update index +- } +- this.entities[index] = end; +- this.entities[endIndex] = null; +- +- return true; ++ return this.objectList.remove(entity); + } + + public boolean add(final Entity entity) { +- final int count = this.count; +- final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); +- +- if (currIndex != Integer.MIN_VALUE) { +- return false; // already in this list +- } +- +- Entity[] list = this.entities; +- +- if (list.length == count) { +- // resize required +- list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative +- } +- +- list[count] = entity; +- this.count = count + 1; +- +- return true; ++ return this.objectList.add(entity); + } + + public Entity getChecked(final int index) { +- if (index < 0 || index >= this.count) { +- throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); +- } +- return this.entities[index]; ++ return this.objectList.get(index); + } + + public Entity getUnchecked(final int index) { +- return this.entities[index]; ++ return this.objectList.get(index); + } + + public Entity[] getRawData() { +- return this.entities; ++ return this.objectList.toArray(Entity[]::new); + } + + public void clear() { +- this.entityToIndex.clear(); +- Arrays.fill(this.entities, 0, this.count, null); +- this.count = 0; ++ this.objectList.clear(); + } + + @Override + public Iterator iterator() { +- return new Iterator() { +- +- Entity lastRet; +- int current; +- +- @Override +- public boolean hasNext() { +- return this.current < EntityList.this.count; +- } +- +- @Override +- public Entity next() { +- if (this.current >= EntityList.this.count) { +- throw new NoSuchElementException(); +- } +- return this.lastRet = EntityList.this.entities[this.current++]; +- } +- +- @Override +- public void remove() { +- final Entity lastRet = this.lastRet; +- +- if (lastRet == null) { +- throw new IllegalStateException(); +- } +- this.lastRet = null; +- +- EntityList.this.remove(lastRet); +- --this.current; +- } +- }; ++ return this.objectList.iterator(); + } + } +diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +index 61c170555c8854b102c640b0b6a615f9f732edbf..ec90ff5c2581706180498b74dbbf960d52d47209 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java ++++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +@@ -6,8 +6,14 @@ import io.papermc.paper.util.CoordinateUtils; + import io.papermc.paper.util.TickThread; + import io.papermc.paper.util.WorldUtil; + import io.papermc.paper.world.ChunkEntitySlices; ++import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; ++import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps; + import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceMaps; + import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; + import net.minecraft.core.BlockPos; + import io.papermc.paper.chunk.system.ChunkSystem; +@@ -26,11 +32,8 @@ import net.minecraft.world.phys.AABB; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + import org.slf4j.Logger; +-import java.util.ArrayList; +-import java.util.Iterator; +-import java.util.List; +-import java.util.NoSuchElementException; +-import java.util.UUID; ++ ++import java.util.*; + import java.util.concurrent.locks.StampedLock; + import java.util.function.Consumer; + import java.util.function.Predicate; +@@ -46,15 +49,15 @@ public final class EntityLookup implements LevelEntityGetter { + public final ServerLevel world; + + private final StampedLock stateLock = new StampedLock(); +- protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); ++ protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f)); + + private final int minSection; // inclusive + private final int maxSection; // inclusive + private final LevelCallback worldCallback; + + private final StampedLock entityByLock = new StampedLock(); +- private final Int2ReferenceOpenHashMap entityById = new Int2ReferenceOpenHashMap<>(); +- private final Object2ReferenceOpenHashMap entityByUUID = new Object2ReferenceOpenHashMap<>(); ++ private final Map entityById = Int2ReferenceMaps.synchronize(new Int2ReferenceOpenHashMap<>()); ++ private final Object2ReferenceMap entityByUUID = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenHashMap<>()); + private final EntityList accessibleEntities = new EntityList(); + + public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { +@@ -208,8 +211,8 @@ public final class EntityLookup implements LevelEntityGetter { + public void get(final AABB box, final Consumer action) { + List entities = new ArrayList<>(); + this.getEntitiesWithoutDragonParts(null, box, entities, null); +- for (int i = 0, len = entities.size(); i < len; ++i) { +- action.accept(entities.get(i)); ++ for (Entity entity : entities) { ++ action.accept(entity); + } + } + +@@ -217,8 +220,8 @@ public final class EntityLookup implements LevelEntityGetter { + public void get(final EntityTypeTest filter, final AABB box, final AbortableIterationConsumer action) { + List entities = new ArrayList<>(); + this.getEntitiesWithoutDragonParts(null, box, entities, null); +- for (int i = 0, len = entities.size(); i < len; ++i) { +- final U casted = filter.tryCast(entities.get(i)); ++ for (Entity entity : entities) { ++ final U casted = filter.tryCast(entity); + if (casted != null && action.accept(casted).shouldAbort()) { + break; + } +@@ -231,14 +234,14 @@ public final class EntityLookup implements LevelEntityGetter { + + if (entity.updatingSectionStatus) { + // recursive status update +- LOGGER.error("Cannot recursively update entity chunk status for entity " + entity, new Throwable()); ++ LOGGER.warn("Cannot recursively update entity chunk status for entity " + entity); + return; + } + + final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates(); + + if (entityStatusUpdateBefore) { +- LOGGER.error("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update", new Throwable()); ++ LOGGER.warn("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update"); + return; + } + +@@ -347,7 +350,7 @@ public final class EntityLookup implements LevelEntityGetter { + } + + if (entity.updatingSectionStatus) { +- LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates", new Throwable()); ++ LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates"); + return false; + } + +diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java +index e08f4e39db4ee3fed62e37364d17dcc5c5683504..22a5470862acfa470c9acd97c96fea29aede0b68 100644 +--- a/src/main/java/io/papermc/paper/util/CachedLists.java ++++ b/src/main/java/io/papermc/paper/util/CachedLists.java +@@ -1,5 +1,6 @@ + package io.papermc.paper.util; + ++import com.google.common.collect.Lists; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.phys.AABB; + import org.bukkit.Bukkit; +@@ -8,50 +9,18 @@ import java.util.List; + + public final class CachedLists { + +- // Paper start - optimise collisions +- static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); +- static boolean tempCollisionListInUse; +- +- public static UnsafeList getTempCollisionList() { +- if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { +- return new UnsafeList<>(16); +- } +- tempCollisionListInUse = true; +- return TEMP_COLLISION_LIST; +- } +- +- public static void returnTempCollisionList(List list) { +- if (list != TEMP_COLLISION_LIST) { +- return; +- } +- ((UnsafeList)list).setSize(0); +- tempCollisionListInUse = false; ++ public static List getTempCollisionList() { ++ return Lists.newCopyOnWriteArrayList(); + } + +- static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); +- static boolean tempGetEntitiesListInUse; ++ public static void returnTempCollisionList(List list) {} + +- public static UnsafeList getTempGetEntitiesList() { +- if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { +- return new UnsafeList<>(16); +- } +- tempGetEntitiesListInUse = true; +- return TEMP_GET_ENTITIES_LIST; ++ public static List getTempGetEntitiesList() { ++ return Lists.newCopyOnWriteArrayList(); + } + +- public static void returnTempGetEntitiesList(List list) { +- if (list != TEMP_GET_ENTITIES_LIST) { +- return; +- } +- ((UnsafeList)list).setSize(0); +- tempGetEntitiesListInUse = false; +- } ++ public static void returnTempGetEntitiesList(List list) {} + // Paper end - optimise collisions + +- public static void reset() { +- // Paper start - optimise collisions +- TEMP_COLLISION_LIST.completeReset(); +- TEMP_GET_ENTITIES_LIST.completeReset(); +- // Paper end - optimise collisions +- } ++ public static void reset() {} + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 75daffc4eaa831708f1ccfa5370ddf1871998033..6eea6430c6fe9174979bf390c008bdedc4c2a51b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1,5 +1,7 @@ + package net.minecraft.server; + ++import co.m2ek4u.aoame.AnotherTickThread; ++import co.m2ek4u.aoame.CallbackExecutor; + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; + import co.aikar.timings.Timings; +@@ -41,9 +43,8 @@ import java.util.Map.Entry; + import java.util.Objects; + import java.util.Optional; + import java.util.Set; +-import java.util.concurrent.CompletableFuture; +-import java.util.concurrent.Executor; +-import java.util.concurrent.RejectedExecutionException; ++import java.util.concurrent.*; ++import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicReference; + import java.util.function.BooleanSupplier; + import java.util.function.Consumer; +@@ -284,6 +285,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(), ++ task -> { ++ AnotherTickThread worker = new AnotherTickThread(task,"Entity-Async-Worker # "+threadId.getAndIncrement()); ++ worker.setDaemon(true); ++ return worker; ++ } ++ ); ++ + // CraftBukkit start + public final WorldLoader.DataLoadContext worldLoader; + public org.bukkit.craftbukkit.CraftServer server; +@@ -1333,17 +1348,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= 5000000000L) { + this.lastServerStatus = i; +@@ -1522,11 +1536,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper +- worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper ++ // Paper - move down ++ for (ServerLevel worldserver : this.getAllLevels()) { ++ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper ++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.profiler.push(() -> { +@@ -1557,7 +1570,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> onLoad) { +- if (Thread.currentThread() != this.thread) { ++ if (Thread.currentThread() != this.thread && !(Thread.currentThread() instanceof AnotherTickThread)) { + this.getChunkSource().mainThreadProcessor.execute(() -> { + this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); + }); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 120c0804010fd5b38a5f806ca580962ff8b47339..2f2e67b922351ca10d32d807db6c92dc9146f0c0 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -44,6 +44,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -3420,52 +3421,38 @@ public abstract class LivingEntity extends Entity { + this.updateFallFlying(); + AABB axisalignedbb = this.getBoundingBox(); + +- // SpigotTimings.timerEntityAIMove.startTiming(); // Spigot // Paper +- this.travel(new Vec3((double) this.xxa, (double) this.yya, (double) this.zza)); +- // SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot // Paper +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("freezing"); +- boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES); +- int i; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ // SpigotTimings.timerEntityAIMove.startTiming(); // Spigot // Paper ++ this.travel(new Vec3((double) this.xxa, (double) this.yya, (double) this.zza)); ++ // SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot // Paper ++ //this.level.getProfiler().pop(); // Purpur ++ //this.level.getProfiler().push("freezing"); // Purpur ++ boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES); ++ int i; + +- if (!this.level.isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API +- i = this.getTicksFrozen(); +- if (this.isInPowderSnow && this.canFreeze()) { +- this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), i + 1)); +- } else { +- this.setTicksFrozen(Math.max(0, i - 2)); ++ if (!this.level.isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API ++ i = this.getTicksFrozen(); ++ if (this.isInPowderSnow && this.canFreeze()) { ++ this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), i + 1)); ++ } else { ++ this.setTicksFrozen(Math.max(0, i - 2)); ++ } + } +- } +- +- this.removeFrost(); +- this.tryAddFrost(); +- if (!this.level.isClientSide && this.tickCount % 40 == 0 && this.isFullyFrozen() && this.canFreeze()) { +- i = flag1 ? 5 : 1; +- this.hurt(DamageSource.FREEZE, (float) i); +- } + +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("push"); +- if (this.autoSpinAttackTicks > 0) { +- --this.autoSpinAttackTicks; +- this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); +- } ++ this.removeFrost(); ++ this.tryAddFrost(); ++ if (!this.level.isClientSide && this.tickCount % 40 == 0 && this.isFullyFrozen() && this.canFreeze()) { ++ i = flag1 ? 5 : 1; ++ this.hurt(DamageSource.FREEZE, (float) i); ++ } + +- this.pushEntities(); +- this.level.getProfiler().pop(); +- // Paper start +- if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { +- if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { +- Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); +- Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); +- io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); +- if (!event.callEvent()) { +- absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); +- } else if (!to.equals(event.getTo())) { +- absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); +- } ++ this.level.getProfiler().pop(); ++ if (this.autoSpinAttackTicks > 0) { ++ --this.autoSpinAttackTicks; ++ this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); + } + } ++ this.pushEntities(); + // Paper end + if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurt(DamageSource.DROWN, 1.0F); +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +index 9f138bc471b5c2a4fa813ff943dbe34018b8df74..5c8a90f8536c9291df5891d8c75de963b75ec4bd 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +@@ -7,6 +7,7 @@ import com.mojang.logging.LogUtils; + import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; + import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; ++import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; + import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; + import java.util.List; + import java.util.Map; +@@ -25,8 +26,9 @@ import org.slf4j.Logger; + + public class PoiSection { + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); +- private final Map, Set> byType = Maps.newHashMap(); public final Map, Set> getData() { return this.byType; } // Paper - public accessor ++ private final Short2ObjectMap records = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); ++ private final Map, Set> byType = Maps.newConcurrentMap(); ++ public final Map, Set> getData() { return this.byType; } // Paper - public accessor + private final Runnable setDirty; + private boolean isValid; + public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system +diff --git a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java +index daa03360dd7044f10b20f36023b305dc7e0bb7df..f11cf0c0701247692075da2f2db7602e72ef1ec8 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java +@@ -19,17 +19,17 @@ public class LegacyRandomSource implements BitRandomSource { + } + + @Override +- public RandomSource fork() { ++ public synchronized RandomSource fork() { + return new LegacyRandomSource(this.nextLong()); + } + + @Override +- public PositionalRandomFactory forkPositional() { ++ public synchronized PositionalRandomFactory forkPositional() { + return new LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong()); + } + + @Override +- public void setSeed(long seed) { ++ public synchronized void setSeed(long seed) { + if (!this.seed.compareAndSet(this.seed.get(), (seed ^ 25214903917L) & 281474976710655L)) { + throw ThreadingDetector.makeThreadingException("LegacyRandomSource", (Thread)null); + } else { +@@ -38,7 +38,7 @@ public class LegacyRandomSource implements BitRandomSource { + } + + @Override +- public int next(int bits) { ++ public synchronized int next(int bits) { + long l = this.seed.get(); + long m = l * 25214903917L + 11L & 281474976710655L; + if (!this.seed.compareAndSet(l, m)) { +@@ -49,7 +49,7 @@ public class LegacyRandomSource implements BitRandomSource { + } + + @Override +- public double nextGaussian() { ++ public synchronized double nextGaussian() { + return this.gaussianSource.nextGaussian(); + } + diff --git a/patches/server/0038-Hearse-Async-entity-ai.patch b/patches/server/0038-Hearse-Async-entity-ai.patch new file mode 100644 index 00000000..2d940299 --- /dev/null +++ b/patches/server/0038-Hearse-Async-entity-ai.patch @@ -0,0 +1,686 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BuildTools +Date: Wed, 4 Jan 2023 11:37:44 +0800 +Subject: [PATCH] Hearse: Async entity ai + +Original license: +Original project: https://github.com/NaturalCodeClub/Hearse + +diff --git a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java +index 0fd814f1d65c111266a2b20f86561839a4cef755..fe4d76875462ac9d408c972b968647af78f2ed14 100644 +--- a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java ++++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java +@@ -94,7 +94,7 @@ public final class IteratorSafeOrderedReferenceSet { + return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); + } + +- public int createRawIterator() { ++ public synchronized int createRawIterator() { + if (this.allowSafeIteration()) { + ++this.iteratorCount; + } +@@ -105,7 +105,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + } + +- public int advanceRawIterator(final int index) { ++ public synchronized int advanceRawIterator(final int index) { + final E[] elements = this.listElements; + int ret = index + 1; + for (int len = this.listSize; ret < len; ++ret) { +@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { + return -1; + } + +- public void finishRawIterator() { ++ public synchronized void finishRawIterator() { + if (this.allowSafeIteration() && --this.iteratorCount == 0) { + if (this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); +@@ -125,7 +125,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + } + +- public boolean remove(final E element) { ++ public synchronized boolean remove(final E element) { + final int index = this.indexMap.removeInt(element); + if (index >= 0) { + if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { +@@ -144,11 +144,11 @@ public final class IteratorSafeOrderedReferenceSet { + return false; + } + +- public boolean contains(final E element) { ++ public synchronized boolean contains(final E element) { + return this.indexMap.containsKey(element); + } + +- public boolean add(final E element) { ++ public synchronized boolean add(final E element) { + final int listSize = this.listSize; + + final int previous = this.indexMap.putIfAbsent(element, listSize); +@@ -223,30 +223,30 @@ public final class IteratorSafeOrderedReferenceSet { + //this.check(); + } + +- public E rawGet(final int index) { ++ public synchronized E rawGet(final int index) { + return this.listElements[index]; + } + +- public int size() { ++ public synchronized int size() { + // always returns the correct amount - listSize can be different + return this.indexMap.size(); + } + +- public IteratorSafeOrderedReferenceSet.Iterator iterator() { ++ public synchronized IteratorSafeOrderedReferenceSet.Iterator iterator() { + return this.iterator(0); + } + +- public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { ++ public synchronized IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { + if (this.allowSafeIteration()) { + ++this.iteratorCount; + } + return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); + } + +- public java.util.Iterator unsafeIterator() { ++ public synchronized java.util.Iterator unsafeIterator() { + return this.unsafeIterator(0); + } +- public java.util.Iterator unsafeIterator(final int flags) { ++ public synchronized java.util.Iterator unsafeIterator(final int flags) { + return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); + } + +@@ -273,7 +273,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + @Override +- public boolean hasNext() { ++ public synchronized boolean hasNext() { + if (this.finished) { + return false; + } +@@ -297,7 +297,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + @Override +- public E next() { ++ public synchronized E next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } +@@ -310,7 +310,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + @Override +- public void remove() { ++ public synchronized void remove() { + final E lastReturned = this.lastReturned; + if (lastReturned == null) { + throw new IllegalStateException(); +@@ -320,7 +320,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + @Override +- public void finishedIterating() { ++ public synchronized void finishedIterating() { + if (this.finished || !this.canFinish) { + throw new IllegalStateException(); + } +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index f597d65d56964297eeeed6c7e77703764178fee0..b3bcafc8bafe1e4a1a2b690499b91e5316a604f1 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -4,6 +4,7 @@ import com.destroystokyo.paper.util.maplist.EntityList; + import io.papermc.paper.chunk.system.entity.EntityLookup; + import io.papermc.paper.util.TickThread; + import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps; + import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.level.ChunkHolder; +@@ -34,7 +35,7 @@ public final class ChunkEntitySlices { + + protected final EntityCollectionBySection allEntities; + protected final EntityCollectionBySection hardCollidingEntities; +- protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; ++ protected final Reference2ObjectMap, EntityCollectionBySection> entitiesByClass; + protected final EntityList entities = new EntityList(); + + public ChunkHolder.FullChunkStatus status; +@@ -61,7 +62,7 @@ public final class ChunkEntitySlices { + + this.allEntities = new EntityCollectionBySection(this); + this.hardCollidingEntities = new EntityCollectionBySection(this); +- this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); ++ this.entitiesByClass = Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>()); + + this.status = status; + } +@@ -209,7 +210,7 @@ public final class ChunkEntitySlices { + } + + for (final Iterator, EntityCollectionBySection>> iterator = +- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) { + final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + + if (entry.getKey().isInstance(entity)) { +@@ -234,7 +235,7 @@ public final class ChunkEntitySlices { + } + + for (final Iterator, EntityCollectionBySection>> iterator = +- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) { + final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + + if (entry.getKey().isInstance(entity)) { +@@ -312,11 +313,11 @@ public final class ChunkEntitySlices { + this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); + } + +- public boolean isEmpty() { ++ public synchronized boolean isEmpty() { + return this.size == 0; + } + +- public int size() { ++ public synchronized int size() { + return this.size; + } + +@@ -328,7 +329,7 @@ public final class ChunkEntitySlices { + } + } + +- public void add(final E entity) { ++ public synchronized void add(final E entity) { + final int idx = this.size++; + if (idx >= this.storage.length) { + this.resize(); +@@ -338,7 +339,7 @@ public final class ChunkEntitySlices { + } + } + +- public int indexOf(final E entity) { ++ public synchronized int indexOf(final E entity) { + final E[] storage = this.storage; + + for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { +@@ -350,7 +351,7 @@ public final class ChunkEntitySlices { + return -1; + } + +- public boolean remove(final E entity) { ++ public synchronized boolean remove(final E entity) { + final int idx = this.indexOf(entity); + if (idx == -1) { + return false; +@@ -367,7 +368,7 @@ public final class ChunkEntitySlices { + return true; + } + +- public boolean has(final E entity) { ++ public synchronized boolean has(final E entity) { + return this.indexOf(entity) != -1; + } + } +@@ -388,7 +389,7 @@ public final class ChunkEntitySlices { + this.entitiesBySection = new BasicEntityList[sectionCount]; + } + +- public void addEntity(final Entity entity, final int sectionIndex) { ++ public synchronized void addEntity(final Entity entity, final int sectionIndex) { + BasicEntityList list = this.entitiesBySection[sectionIndex]; + + if (list != null && list.has(entity)) { +@@ -404,7 +405,7 @@ public final class ChunkEntitySlices { + ++this.count; + } + +- public void removeEntity(final Entity entity, final int sectionIndex) { ++ public synchronized void removeEntity(final Entity entity, final int sectionIndex) { + final BasicEntityList list = this.entitiesBySection[sectionIndex]; + + if (list == null || !list.remove(entity)) { +@@ -419,7 +420,7 @@ public final class ChunkEntitySlices { + } + } + +- public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ public synchronized void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + if (this.count == 0) { + return; + } +@@ -457,7 +458,7 @@ public final class ChunkEntitySlices { + } + } + +- public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, ++ public synchronized void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, + final Predicate predicate) { + if (this.count == 0) { + return; +@@ -508,7 +509,7 @@ public final class ChunkEntitySlices { + } + } + +- public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, ++ public synchronized void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, + final Predicate predicate) { + if (this.count == 0) { + return; +@@ -559,7 +560,7 @@ public final class ChunkEntitySlices { + } + } + +- public void getEntities(final EntityType type, final AABB box, final List into, ++ public synchronized void getEntities(final EntityType type, final AABB box, final List into, + final Predicate predicate) { + if (this.count == 0) { + return; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index d939b1e3bc101e66bc1019cf49d8079665dadfcc..897c9a664806c43a4e7ff444ef69392b9b5cfab6 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -15,6 +15,7 @@ import com.mojang.logging.LogUtils; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.JsonOps; + import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; +@@ -25,8 +26,8 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; + import it.unimi.dsi.fastutil.longs.LongIterator; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; +-import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; +-import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import it.unimi.dsi.fastutil.objects.*; ++ + import java.io.IOException; + import java.io.Writer; + import java.nio.file.Path; +@@ -52,6 +53,7 @@ import java.util.function.IntSupplier; + import java.util.function.Supplier; + import java.util.stream.Collectors; + import javax.annotation.Nullable; ++ + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; +@@ -113,7 +115,6 @@ import org.bukkit.craftbukkit.generator.CustomChunkGenerator; + import org.bukkit.entity.Player; + // CraftBukkit end + +-import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper + + public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { + +@@ -153,7 +154,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final Queue unloadQueue; + int viewDistance; + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper +- public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); ++ public final ReferenceSet needsChangeBroadcasting = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); + + // Paper - rewrite chunk system + // Paper start - optimise checkDespawn +@@ -295,7 +296,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper - rewrite chunk system + this.tickingGenerated = new AtomicInteger(); + this.playerMap = new PlayerMap(); +- this.entityMap = new Int2ObjectOpenHashMap(); ++ this.entityMap = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap()); + this.chunkTypeCache = new Long2ByteOpenHashMap(); + this.chunkSaveCooldowns = new Long2LongOpenHashMap(); + this.unloadQueue = Queues.newConcurrentLinkedQueue(); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index c6f5d6756fa0e068a462d9c0ded12e0771abba37..788222965a82b21f669a7e7b10c50762968b67c0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -6,12 +6,7 @@ import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Either; + import java.io.File; + import java.io.IOException; +-import java.util.Arrays; +-import java.util.Collections; +-import java.util.Iterator; +-import java.util.List; +-import java.util.Objects; +-import java.util.Optional; ++import java.util.*; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; + import java.util.function.BooleanSupplier; +@@ -803,7 +798,7 @@ public class ServerChunkCache extends ChunkSource { + gameprofilerfiller.popPush("broadcast"); + this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing + if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { +- ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); ++ List copy = new ArrayList<>(this.chunkMap.needsChangeBroadcasting); + this.chunkMap.needsChangeBroadcasting.clear(); + for (ChunkHolder holder : copy) { + holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index 50a9f33aa31e9273c7c52d4bb2b02f0f884f7ba5..6d94aa3c175345f701ec67175fad3fcde4481041 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -10,12 +10,14 @@ import java.util.Collections; + import java.util.Iterator; + import java.util.List; + import java.util.Map; ++import java.util.concurrent.CopyOnWriteArrayList; ++import java.util.stream.Collector; + import java.util.stream.Collectors; + + public class ClassInstanceMultiMap extends AbstractCollection { +- private final Map, List> byClass = Maps.newHashMap(); ++ private final Map, List> byClass = Maps.newConcurrentMap(); + private final Class baseClass; +- private final List allInstances = Lists.newArrayList(); ++ private final List allInstances = Lists.newCopyOnWriteArrayList(); + + public ClassInstanceMultiMap(Class elementType) { + this.baseClass = elementType; +@@ -58,22 +60,24 @@ public class ClassInstanceMultiMap extends AbstractCollection { + if (!this.baseClass.isAssignableFrom(type)) { + throw new IllegalArgumentException("Don't know how to search for " + type); + } else { +- List list = this.byClass.computeIfAbsent(type, (typeClass) -> { +- return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); +- }); +- return Collections.unmodifiableCollection(list); ++ List list = this.byClass.computeIfAbsent(type, (typeClass) -> this.allInstances.stream().filter(typeClass::isInstance).collect(toList())); ++ return (Collection) Collections.unmodifiableCollection(list); + } + } + + @Override + public Iterator iterator() { +- return (Iterator)(this.allInstances.isEmpty() ? Collections.emptyIterator() : Iterators.unmodifiableIterator(this.allInstances.iterator())); ++ return this.allInstances.isEmpty() ? Collections.emptyIterator() : Iterators.unmodifiableIterator(this.allInstances.iterator()); + } + + public List getAllInstances() { + return ImmutableList.copyOf(this.allInstances); + } + ++ public static Collector> toList() { ++ return Collectors.toCollection(CopyOnWriteArrayList::new); ++ } ++ + @Override + public int size() { + return this.allInstances.size(); +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 48746d84f18cc8ee2f57785c65a5659ced454d39..62e82f7e76bcf53da1caf709e5cf3088db840a32 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -23,6 +23,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; +@@ -216,12 +217,14 @@ public abstract class Mob extends LivingEntity { + public void inactiveTick() { + super.inactiveTick(); + boolean isThrottled = gg.pufferfish.pufferfish.PufferfishConfig.throttleInactiveGoalSelectorTick && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking +- if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking +- this.goalSelector.tick(); +- } +- if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority +- this.targetSelector.tick(); +- } ++ MinecraftServer.getServer().asyncExecutor.executeWithCallBack(()->{ ++ if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking ++ this.goalSelector.tick(); ++ } ++ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority ++ this.targetSelector.tick(); ++ } ++ },()->{}); + } + // Paper end + +@@ -878,42 +881,32 @@ public abstract class Mob extends LivingEntity { + if (i % 10 == 0) this.sensing.tick(); // petal - only refresh line of sight cache every half second + this.level.getProfiler().pop(); + +- if (i % 2 != 0 && this.tickCount > 1) { +- this.level.getProfiler().push("targetSelector"); +- if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking +- this.targetSelector.tickRunningGoals(false); +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("goalSelector"); +- if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking +- this.goalSelector.tickRunningGoals(false); +- this.level.getProfiler().pop(); +- } else { +- this.level.getProfiler().push("targetSelector"); +- if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking +- this.targetSelector.tick(); +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("goalSelector"); +- if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking +- this.goalSelector.tick(); +- this.level.getProfiler().pop(); +- } +- +- this.level.getProfiler().push("navigation"); +- this.navigation.tick(); +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("mob tick"); +- this.customServerAiStep(); +- this.level.getProfiler().pop(); +- this.level.getProfiler().push("controls"); +- this.level.getProfiler().push("move"); +- this.moveControl.tick(); +- this.level.getProfiler().popPush("look"); +- this.lookControl.tick(); +- this.level.getProfiler().popPush("jump"); +- this.jumpControl.tick(); +- this.level.getProfiler().pop(); +- this.level.getProfiler().pop(); +- this.sendDebugPackets(); ++ MinecraftServer.getServer().asyncExecutor.executeWithCallBack(() -> { ++ if (i % 2 != 0 && this.tickCount > 1) { ++ this.level.getProfiler().push("targetSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking ++ this.targetSelector.tickRunningGoals(false); ++ this.level.getProfiler().pop(); ++ this.level.getProfiler().push("goalSelector"); ++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking ++ this.goalSelector.tickRunningGoals(false); ++ this.level.getProfiler().pop(); ++ } else { ++ this.level.getProfiler().push("targetSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking ++ this.targetSelector.tick(); ++ this.level.getProfiler().pop(); ++ this.level.getProfiler().push("goalSelector"); ++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking ++ this.goalSelector.tick(); ++ this.level.getProfiler().pop(); ++ } ++ this.navigation.tick(); ++ this.customServerAiStep(); ++ this.moveControl.tick(); ++ this.lookControl.tick(); ++ this.jumpControl.tick(); ++ }, this::sendDebugPackets); + } + + protected void sendDebugPackets() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index 097007c1c25ba55d9916fc820dd1d1149d81f6f4..16eec12db529dd513e0971289a9326652369de58 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -30,11 +30,11 @@ import org.slf4j.Logger; + public class GossipContainer { + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int DISCARD_THRESHOLD = 2; +- public final Map gossips = Maps.newHashMap(); ++ public final Map gossips = Maps.newConcurrentMap(); + + @VisibleForDebug + public Map> getGossipEntries() { +- Map> map = Maps.newHashMap(); ++ Map> map = Maps.newConcurrentMap(); + this.gossips.keySet().forEach((uuid) -> { + GossipContainer.EntityGossips entityGossips = this.gossips.get(uuid); + map.put(uuid, entityGossips.entries); +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index a39d038b5198c9244e2225e0fe383f3c96b49a08..948b3ae132dc291551a466a2c7569de3ba632c04 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -274,16 +274,18 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (this.getUnhappyCounter() > 0) { + this.setUnhappyCounter(this.getUnhappyCounter() - 1); + } +- if (this.isEffectiveAi()) { +- if (level.spigotConfig.tickInactiveVillagers) { +- this.customServerAiStep(); +- } else { +- this.mobTick(true); ++ MinecraftServer.getServer().asyncExecutor.executeWithCallBack(()->{ ++ if (this.isEffectiveAi()) { ++ if (level.spigotConfig.tickInactiveVillagers) { ++ this.customServerAiStep(); ++ } else { ++ this.mobTick(true); ++ } + } +- } +- maybeDecayGossip(); ++ },()->{ ++ maybeDecayGossip(); ++ }); + // Paper end +- + super.inactiveTick(); + } + // Spigot End +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +index d45d832232be5017dde53816191c2b1830a0da32..f73f78e2f7c6e3eae66f7608a92854b3246e153d 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +@@ -8,13 +8,15 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + import java.util.Map; + import java.util.UUID; + import javax.annotation.Nullable; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; + import net.minecraft.util.AbortableIterationConsumer; + import org.slf4j.Logger; + + public class EntityLookup { + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Int2ObjectMap byId = new Int2ObjectLinkedOpenHashMap<>(); +- private final Map byUuid = Maps.newHashMap(); ++ private final Int2ObjectMap byId = Int2ObjectMaps.synchronize(new Int2ObjectLinkedOpenHashMap<>()); ++ private final Map byUuid = Maps.newConcurrentMap(); + + public void getEntities(EntityTypeTest filter, AbortableIterationConsumer consumer) { + for(T entityAccess : this.byId.values()) { +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index a77985b2dd7137d8eea03909403fc08e89376d73..6bcbbbfc39432076a3d7714ecc2d05d9112d405c 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -4,12 +4,8 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.Queues; + import com.google.common.collect.Sets; + import com.mojang.logging.LogUtils; +-import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.*; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; +-import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +-import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +-import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +-import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.objects.ObjectIterator; + import java.io.IOException; + import java.io.UncheckedIOException; +@@ -39,15 +35,15 @@ import org.bukkit.craftbukkit.event.CraftEventFactory; + public class PersistentEntitySectionManager implements AutoCloseable { + + static final Logger LOGGER = LogUtils.getLogger(); +- final Set knownUuids = Sets.newHashSet(); ++ final Set knownUuids = Sets.newConcurrentHashSet(); + final LevelCallback callbacks; + public final EntityPersistentStorage permanentStorage; + private final EntityLookup visibleEntityStorage = new EntityLookup<>(); + final EntitySectionStorage sectionStorage; + private final LevelEntityGetter entityGetter; +- private final Long2ObjectMap chunkVisibility = new Long2ObjectOpenHashMap(); +- private final Long2ObjectMap chunkLoadStatuses = new Long2ObjectOpenHashMap(); +- private final LongSet chunksToUnload = new LongOpenHashSet(); ++ private final Long2ObjectMap chunkVisibility = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); ++ private final Long2ObjectMap chunkLoadStatuses = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); ++ private final LongSet chunksToUnload = LongSets.synchronize(new LongOpenHashSet()); + private final Queue> loadingInbox = Queues.newConcurrentLinkedQueue(); + + public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess) { +diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +index b1c594dc6a6b8a6c737b99272acab9e7dbd0ed63..4aedee56077159aaf613033b688d2be6833f1ad1 100644 +--- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -3,7 +3,10 @@ package net.minecraft.world.level.redstone; + import com.mojang.logging.LogUtils; + import java.util.ArrayDeque; + import java.util.ArrayList; ++import java.util.Deque; + import java.util.List; ++import java.util.concurrent.ConcurrentLinkedDeque; ++import java.util.concurrent.CopyOnWriteArrayList; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; +@@ -16,8 +19,8 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + private static final Logger LOGGER = LogUtils.getLogger(); + private final Level level; + private final int maxChainedNeighborUpdates; +- private final ArrayDeque stack = new ArrayDeque<>(); +- private final List addedThisLayer = new ArrayList<>(); ++ private final Deque stack = new ConcurrentLinkedDeque<>(); ++ private final List addedThisLayer = new CopyOnWriteArrayList<>(); + private int count = 0; + + public CollectingNeighborUpdater(Level world, int maxChainDepth) { +@@ -26,22 +29,22 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + } + + @Override +- public void shapeUpdate(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth) { ++ public synchronized void shapeUpdate(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth) { + this.addAndRun(pos, new CollectingNeighborUpdater.ShapeUpdate(direction, neighborState, pos.immutable(), neighborPos.immutable(), flags)); + } + + @Override +- public void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos sourcePos) { ++ public synchronized void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos sourcePos) { + this.addAndRun(pos, new CollectingNeighborUpdater.SimpleNeighborUpdate(pos, sourceBlock, sourcePos.immutable())); + } + + @Override +- public void neighborChanged(BlockState state, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { ++ public synchronized void neighborChanged(BlockState state, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + this.addAndRun(pos, new CollectingNeighborUpdater.FullNeighborUpdate(state, pos.immutable(), sourceBlock, sourcePos.immutable(), notify)); + } + + @Override +- public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block sourceBlock, @Nullable Direction except) { ++ public synchronized void updateNeighborsAtExceptFromFacing(BlockPos pos, Block sourceBlock, @Nullable Direction except) { + this.addAndRun(pos, new CollectingNeighborUpdater.MultiNeighborUpdate(pos.immutable(), sourceBlock, except)); + } + diff --git a/patches/server/0039-Hearse-Try-to-fix-some-thread-problems-may-happen.patch b/patches/server/0039-Hearse-Try-to-fix-some-thread-problems-may-happen.patch new file mode 100644 index 00000000..47659067 --- /dev/null +++ b/patches/server/0039-Hearse-Try-to-fix-some-thread-problems-may-happen.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BuildTools +Date: Wed, 4 Jan 2023 15:19:31 +0800 +Subject: [PATCH] Hearse: Try to fix some thread problems may happen + +Original license: +Original project: https://github.com/NaturalCodeClub/Hearse + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/BinaryHeap.java b/src/main/java/net/minecraft/world/level/pathfinder/BinaryHeap.java +index 27b9cefc172b391824ead382a712b8b9b1ddfe45..c949325dc9b21d8a75ee639210911c61616949c8 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/BinaryHeap.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/BinaryHeap.java +@@ -4,10 +4,8 @@ public class BinaryHeap { + private Node[] heap = new Node[128]; + private int size; + +- public Node insert(Node node) { +- if (node.heapIdx >= 0) { +- throw new IllegalStateException("OW KNOWS!"); +- } else { ++ public synchronized Node insert(Node node) { ++ if (node.heapIdx < 0) { + if (this.size == this.heap.length) { + Node[] nodes = new Node[this.size << 1]; + System.arraycopy(this.heap, 0, nodes, 0, this.size); +@@ -17,19 +15,19 @@ public class BinaryHeap { + this.heap[this.size] = node; + node.heapIdx = this.size; + this.upHeap(this.size++); +- return node; + } ++ return node; + } + +- public void clear() { ++ public synchronized void clear() { + this.size = 0; + } + +- public Node peek() { ++ public synchronized Node peek() { + return this.heap[0]; + } + +- public Node pop() { ++ public synchronized Node pop() { + Node node = this.heap[0]; + this.heap[0] = this.heap[--this.size]; + this.heap[this.size] = null; +@@ -41,7 +39,7 @@ public class BinaryHeap { + return node; + } + +- public void remove(Node node) { ++ public synchronized void remove(Node node) { + this.heap[node.heapIdx] = this.heap[--this.size]; + this.heap[this.size] = null; + if (this.size > node.heapIdx) { +@@ -55,7 +53,7 @@ public class BinaryHeap { + node.heapIdx = -1; + } + +- public void changeCost(Node node, float weight) { ++ public synchronized void changeCost(Node node, float weight) { + float f = node.f; + node.f = weight; + if (weight < f) { +@@ -66,7 +64,7 @@ public class BinaryHeap { + + } + +- public int size() { ++ public synchronized int size() { + return this.size; + } + +@@ -135,11 +133,11 @@ public class BinaryHeap { + node.heapIdx = index; + } + +- public boolean isEmpty() { ++ public synchronized boolean isEmpty() { + return this.size == 0; + } + +- public Node[] getHeap() { ++ public synchronized Node[] getHeap() { + Node[] nodes = new Node[this.size()]; + System.arraycopy(this.heap, 0, nodes, 0, this.size()); + return nodes; +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/FlyNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/FlyNodeEvaluator.java +index b0bae04ab5a93dd4cf1eeeb02bed1e508e1f2913..d427735eff0056c171591709829d0bb76f7bb6f3 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/FlyNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/FlyNodeEvaluator.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.pathfinder; + + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import java.util.EnumSet; + import java.util.List; +@@ -15,7 +16,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + + public class FlyNodeEvaluator extends WalkNodeEvaluator { +- private final Long2ObjectMap pathTypeByPosCache = new Long2ObjectOpenHashMap<>(); ++ private final Long2ObjectMap pathTypeByPosCache = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private static final float SMALL_MOB_INFLATED_START_NODE_BOUNDING_BOX = 1.5F; + private static final int MAX_START_NODE_CANDIDATES = 10; + +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java +index a8a2594b8f5b3ebf6a1f918c7d822ad35b051b17..c614bcfc2bbbbccc7c4aac9389d4780478e739d2 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/NodeEvaluator.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.level.pathfinder; + + import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import net.minecraft.core.BlockPos; + import net.minecraft.util.Mth; +@@ -11,7 +12,7 @@ import net.minecraft.world.level.PathNavigationRegion; + public abstract class NodeEvaluator { + protected PathNavigationRegion level; + protected Mob mob; +- protected final Int2ObjectMap nodes = new Int2ObjectOpenHashMap<>(); ++ protected final Int2ObjectMap nodes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); + protected int entityWidth; + protected int entityHeight; + protected int entityDepth; +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java +index 6084631b5b502279b84f190dc62fc76b770e368e..f526adbd31e65fc74af48f6137d293a7a7ceafbb 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java +@@ -2,6 +2,7 @@ package net.minecraft.world.level.pathfinder; + + import com.google.common.collect.Maps; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import java.util.Map; + import javax.annotation.Nullable; +@@ -17,7 +18,7 @@ import net.minecraft.world.level.material.FluidState; + + public class SwimNodeEvaluator extends NodeEvaluator { + private final boolean allowBreaching; +- private final Long2ObjectMap pathTypesByPosCache = new Long2ObjectOpenHashMap<>(); ++ private final Long2ObjectMap pathTypesByPosCache = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + + public SwimNodeEvaluator(boolean canJumpOutOfWater) { + this.allowBreaching = canJumpOutOfWater; +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index 894881018c659d874f28f5744f0b8247cfecb1c1..ae06f7ef9c4b8147508984f8b46176de46171285 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -1,8 +1,10 @@ + package net.minecraft.world.level.pathfinder; + + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; + import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.objects.Object2BooleanMap; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMaps; + import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; + import java.util.EnumSet; + import javax.annotation.Nullable; +@@ -33,8 +35,8 @@ public class WalkNodeEvaluator extends NodeEvaluator { + public static final double SPACE_BETWEEN_WALL_POSTS = 0.5D; + private static final double DEFAULT_MOB_JUMP_HEIGHT = 1.125D; + protected float oldWaterCost; +- private final Long2ObjectMap pathTypesByPosCache = new Long2ObjectOpenHashMap<>(); +- private final Object2BooleanMap collisionCache = new Object2BooleanOpenHashMap<>(); ++ private final Long2ObjectMap pathTypesByPosCache = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); ++ private final Object2BooleanMap collisionCache = Object2BooleanMaps.synchronize(new Object2BooleanOpenHashMap<>()); + + @Override + public void prepare(PathNavigationRegion cachedWorld, Mob entity) {