diff --git a/README.md b/README.md index d984e2e2..3a3858c2 100644 --- a/README.md +++ b/README.md @@ -64,4 +64,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) +- [Hearse](https://github.com/Era4FunMC/Hearse) diff --git a/patches/api/0011-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch b/patches/api/0011-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch new file mode 100644 index 00000000..ebd1274a --- /dev/null +++ b/patches/api/0011-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 24 Jan 2023 09:32:37 +0800 +Subject: [PATCH] Hearse: Fix some threading issue in bukkit event system + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 8eb8a788dc3dbc0d5ac24089a57c730089fd8dbe..91064280782956049f9f31fab97e567f3758e354 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1489,6 +1489,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + */ + boolean isPrimaryThread(); + ++ //Hearse start ++ /** ++ * Get current thread is worker or other ++ */ ++ boolean isWorkerThread(); ++ + // Paper start + /** + * Gets the message that is displayed on the server list. +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index ba869354adc59db2fc547c481c1ed4d5d0af23b7..6a0a1e95b81da0087c2d56b905f1de1d0225ae5b 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -646,8 +646,11 @@ public final class SimplePluginManager implements PluginManager { + defaultPerms.get(false).clear(); + } + } ++ + private void fireEvent(Event event) { callEvent(event); } // Paper - support old method incase plugin uses reflection + ++ private final Object eventLock = new Object();//Hearse ensure event system thread safe ++ + /** + * Calls an event with the given details. + * +@@ -666,6 +669,20 @@ public final class SimplePluginManager implements PluginManager { + } + // KTP end - optimize spigot event bus + ++ //Hearse start ++ if (server.isWorkerThread()) { ++ synchronized (this.eventLock) { ++ this.fireCallEvent(event); ++ } ++ return; ++ } ++ ++ this.fireCallEvent(event); ++ //Hearse end ++ } ++ ++ //Hearse start - split to a new method ++ private void fireCallEvent(Event event) { + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + +@@ -687,7 +704,7 @@ public final class SimplePluginManager implements PluginManager { + plugin.getDescription().getAuthors(), + plugin.getDescription().getFullName(), + ex.getMessage() +- )); ++ )); + } + } catch (Throwable ex) { + gg.pufferfish.pufferfish.sentry.SentryContext.setEventContext(event, registration); // Pufferfish +@@ -702,6 +719,7 @@ public final class SimplePluginManager implements PluginManager { + } + } + } ++ //Hearse end + + @Override + public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) { diff --git a/patches/server/0041-Hearse-Paper-chunk-system-changes.patch b/patches/server/0041-Hearse-Paper-chunk-system-changes.patch index 2ac1a385..c7899984 100644 --- a/patches/server/0041-Hearse-Paper-chunk-system-changes.patch +++ b/patches/server/0041-Hearse-Paper-chunk-system-changes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:52:27 +0800 Subject: [PATCH] Hearse: Paper chunk system changes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java index 0b060183429f4c72ec767075538477b4302bbf0d..028b23f5c23bbfd83498c3e06a56079ceb0798ad 100644 diff --git a/patches/server/0042-Hearse-Paper-world-code-changes.patch b/patches/server/0042-Hearse-Paper-world-code-changes.patch index 7668e24e..28ff6777 100644 --- a/patches/server/0042-Hearse-Paper-world-code-changes.patch +++ b/patches/server/0042-Hearse-Paper-world-code-changes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:54:18 +0800 Subject: [PATCH] Hearse: Paper world code changes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java index 470402573bc31106d5a63e415b958fb7f9c36aa9..e831738a2988746fe4e065f6ded811a8bdf5dabe 100644 diff --git a/patches/server/0043-Hearse-Paper-util-code-changes.patch b/patches/server/0043-Hearse-Paper-util-code-changes.patch index 2c0f99a4..996ed81a 100644 --- a/patches/server/0043-Hearse-Paper-util-code-changes.patch +++ b/patches/server/0043-Hearse-Paper-util-code-changes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:55:30 +0800 Subject: [PATCH] Hearse: Paper util code changes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse 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..a1ac254c71eb5559b88ed1a6bd5128539d3b38b5 100644 diff --git a/patches/server/0044-Hearse-Base-codes.patch b/patches/server/0044-Hearse-Base-codes.patch index 731f4ec8..8a3b52b3 100644 --- a/patches/server/0044-Hearse-Base-codes.patch +++ b/patches/server/0044-Hearse-Base-codes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:56:15 +0800 Subject: [PATCH] Hearse: Base codes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/co/earthme/hearse/Hearse.java b/src/main/java/co/earthme/hearse/Hearse.java new file mode 100644 diff --git a/patches/server/0045-Hearse-Add-mcmt-collections.patch b/patches/server/0045-Hearse-Add-mcmt-collections.patch index aeb2bfe5..33144433 100644 --- a/patches/server/0045-Hearse-Add-mcmt-collections.patch +++ b/patches/server/0045-Hearse-Add-mcmt-collections.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:56:52 +0800 Subject: [PATCH] Hearse: Add mcmt collections Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/net/himeki/mcmtfabric/parallelised/ConcurrentArrayDeque.java b/src/main/java/net/himeki/mcmtfabric/parallelised/ConcurrentArrayDeque.java new file mode 100644 diff --git a/patches/server/0046-Hearse-MC-Code-changes.patch b/patches/server/0046-Hearse-MC-Code-changes.patch index 577fd955..dc3c38da 100644 --- a/patches/server/0046-Hearse-MC-Code-changes.patch +++ b/patches/server/0046-Hearse-MC-Code-changes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:57:50 +0800 Subject: [PATCH] Hearse: MC Code changes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java index 24c677e80af652952263253409c050641e72e3b5..c6f9fb3efb92de0879eab6389fabd531bb4cfcb2 100644 diff --git a/patches/server/0047-Hearse-Misc-changes.patch b/patches/server/0047-Hearse-Misc-changes.patch index b7ce2f50..f1c809c0 100644 --- a/patches/server/0047-Hearse-Misc-changes.patch +++ b/patches/server/0047-Hearse-Misc-changes.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 09:58:00 +0800 Subject: [PATCH] Hearse: Misc changes Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 6a52ae70b5f7fd9953b6b2605cae722f606e7fec..af5956bd57141cae08fe839bb2176988a48cd9b8 100644 diff --git a/patches/server/0048-Hearse-Fix-a-CME-in-AttributeMap.patch b/patches/server/0048-Hearse-Fix-a-CME-in-AttributeMap.patch index e30b8b4a..ffe50684 100644 --- a/patches/server/0048-Hearse-Fix-a-CME-in-AttributeMap.patch +++ b/patches/server/0048-Hearse-Fix-a-CME-in-AttributeMap.patch @@ -4,7 +4,7 @@ Date: Sun, 15 Jan 2023 10:51:59 +0800 Subject: [PATCH] Hearse: Fix a CME in AttributeMap Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java index e283eb57c25f7de222f9d09dca851169f5f6e488..860a43c1f426b876eab5c908280cf379e331baf2 100644 diff --git a/patches/server/0049-Hearse-Remove-MCMT-s-fix-in-scheduled-ticks-and-add-.patch b/patches/server/0049-Hearse-Remove-MCMT-s-fix-in-scheduled-ticks-and-add-.patch index 5e88e701..83e66337 100644 --- a/patches/server/0049-Hearse-Remove-MCMT-s-fix-in-scheduled-ticks-and-add-.patch +++ b/patches/server/0049-Hearse-Remove-MCMT-s-fix-in-scheduled-ticks-and-add-.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Hearse: Remove MCMT's fix in scheduled ticks and add locks into them Original license: MIT -Original project: https://github.com/NaturalCodeClub/Hearse +Original project: https://github.com/Era4FunMC/Hearse diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java index 4c75f50ab0184637b72e08936ff8808ad6c6fb5f..367ce55fb9b31f718357a8da522a639848e9dc6a 100644 diff --git a/patches/server/0050-Hearse-Use-a-new-queue-in-thread-pool.patch b/patches/server/0050-Hearse-Use-a-new-queue-in-thread-pool.patch new file mode 100644 index 00000000..aaf02dc8 --- /dev/null +++ b/patches/server/0050-Hearse-Use-a-new-queue-in-thread-pool.patch @@ -0,0 +1,317 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Sun, 15 Jan 2023 22:35:45 +0800 +Subject: [PATCH] Hearse: Use a new queue in thread pool + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +index c0e7a9cf79ddf00827daba0aa9c7a32fa76b0c7c..8baccccee52b6e47bf51e51d976ad76920270ef4 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +@@ -5,6 +5,7 @@ import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.concurrent.WorkerThreadFactory; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; ++import co.earthme.hearse.util.ArrayListBlockingQueue; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; +@@ -40,7 +41,7 @@ public class ServerEntityTickHook { + workerCount, + 0L, + TimeUnit.MILLISECONDS, +- new LinkedBlockingQueue<>(), ++ new ArrayListBlockingQueue<>(), + defFactory + ); + Hearse.getWorkerManager().addWorker("entity",worker); +diff --git a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +index 8085eb700d8e5c20ebb5bfeceb78198c6e973019..987c98ea108d49c1335238bc529f782d3ec5b5e6 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +@@ -5,6 +5,7 @@ import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.concurrent.WorkerThread; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; ++import co.earthme.hearse.util.ArrayListBlockingQueue; + import net.minecraft.CrashReport; + import net.minecraft.ReportedException; + import net.minecraft.server.MinecraftServer; +@@ -12,6 +13,7 @@ import net.minecraft.server.level.ServerLevel; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + ++import java.util.concurrent.ArrayBlockingQueue; + import java.util.concurrent.LinkedBlockingQueue; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; +@@ -34,7 +36,7 @@ public class ServerLevelTickHook { + MinecraftServer.getServer().levels.size(), + Long.MAX_VALUE, + TimeUnit.MILLISECONDS, +- new LinkedBlockingQueue<>(), ++ new ArrayListBlockingQueue<>(), + workerFactory + ); + worker.allowCoreThreadTimeOut(true); +diff --git a/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java b/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2cea65ecfb8a41248e7ee74357b4127106f1d0a +--- /dev/null ++++ b/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java +@@ -0,0 +1,253 @@ ++package co.earthme.hearse.util; ++ ++import java.util.*; ++import java.util.concurrent.*; ++import java.util.concurrent.locks.*; ++ ++public class ArrayListBlockingQueue implements BlockingQueue { ++ private final List internalList = new ArrayList<>(); ++ private final StampedLock editLock = new StampedLock(); ++ ++ @Override ++ public boolean add(T t) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.add(t); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean offer(T t) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.add(t); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public T remove() { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.remove(0); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public T poll() { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.isEmpty() ? null : this.internalList.remove(0); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public T element() { ++ long id = this.editLock.readLock(); ++ try { ++ if (this.internalList.isEmpty()){ ++ throw new NoSuchElementException(); ++ } ++ return this.internalList.get(0); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public T peek() { ++ long id = this.editLock.readLock(); ++ try { ++ if (this.internalList.isEmpty()){ ++ throw new NoSuchElementException(); ++ } ++ return this.internalList.get(0); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public void put(T t) { ++ final long id = this.editLock.writeLock(); ++ try { ++ this.internalList.add(t); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean offer(T t, long timeout, TimeUnit unit) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.add(t); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public T take() throws InterruptedException { ++ T t; ++ while ((t = this.poll()) == null){ ++ synchronized (this){ ++ this.wait(1); ++ } ++ } ++ return t; ++ } ++ ++ @Override ++ public T poll(long timeout, TimeUnit unit) throws InterruptedException { ++ T t; ++ while ((t = this.poll()) == null){ ++ if (timeout == 0){ ++ break; ++ } ++ synchronized (this){ ++ unit.sleep(1); ++ } ++ timeout--; ++ } ++ return t; ++ } ++ ++ @Override ++ public int remainingCapacity() { ++ throw new UnsupportedOperationException("remainingCapacity"); ++ } ++ ++ @Override ++ public boolean remove(Object o) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.remove(o); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean containsAll(Collection c) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return new HashSet<>(this.internalList).containsAll(c); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean addAll(Collection c) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.addAll(c); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean removeAll(Collection c) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.removeAll(c); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public boolean retainAll(Collection c) { ++ final long id = this.editLock.writeLock(); ++ try { ++ return this.internalList.retainAll(c); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public void clear() { ++ final long id = this.editLock.writeLock(); ++ try { ++ this.internalList.clear(); ++ }finally { ++ this.editLock.unlockWrite(id); ++ } ++ } ++ ++ @Override ++ public int size() { ++ long id = this.editLock.readLock(); ++ try { ++ return this.internalList.size(); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ long id = this.editLock.readLock(); ++ try { ++ return this.internalList.isEmpty(); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ long id = this.editLock.readLock(); ++ try { ++ return this.internalList.contains(o); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public Iterator iterator() { ++ throw new UnsupportedOperationException("Iterator"); ++ } ++ ++ @Override ++ public Object[] toArray() { ++ long id = this.editLock.readLock(); ++ try { ++ return this.internalList.toArray(); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public T1[] toArray(T1[] a) { ++ long id = this.editLock.readLock(); ++ try { ++ return this.internalList.toArray(a); ++ }finally { ++ this.editLock.unlockRead(id); ++ } ++ } ++ ++ @Override ++ public int drainTo(Collection c) { ++ throw new UnsupportedOperationException("drainTo"); ++ } ++ ++ @Override ++ public int drainTo(Collection c, int maxElements) { ++ throw new UnsupportedOperationException("drainTo"); ++ } ++} ++ diff --git a/patches/server/0051-Hearse-Refactor-some-codes.patch b/patches/server/0051-Hearse-Refactor-some-codes.patch new file mode 100644 index 00000000..5b267055 --- /dev/null +++ b/patches/server/0051-Hearse-Refactor-some-codes.patch @@ -0,0 +1,359 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 12:09:59 +0800 +Subject: [PATCH] Hearse: Refactor some codes + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java +index e65b1eba68003a9f7ce5080d07a521817831ff48..5ffca8dac85fcbe50a4445ebc375b33d8228d690 100644 +--- a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java ++++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java +@@ -1,5 +1,7 @@ + package co.earthme.hearse.concurrent; + ++import co.earthme.hearse.concurrent.thread.WorkerThread; ++ + public interface WorkerThreadFactory { + WorkerThread getNewThread(Runnable task); + } +diff --git a/src/main/java/co/earthme/hearse/concurrent/thread/Worker.java b/src/main/java/co/earthme/hearse/concurrent/thread/Worker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7a944bd515af644bad37a23e012a5a1997e110d +--- /dev/null ++++ b/src/main/java/co/earthme/hearse/concurrent/thread/Worker.java +@@ -0,0 +1,7 @@ ++package co.earthme.hearse.concurrent.thread; ++ ++public interface Worker { ++ static boolean isWorker(){ ++ return Thread.currentThread() instanceof Worker; ++ } ++} +diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java b/src/main/java/co/earthme/hearse/concurrent/thread/WorkerThread.java +similarity index 50% +rename from src/main/java/co/earthme/hearse/concurrent/WorkerThread.java +rename to src/main/java/co/earthme/hearse/concurrent/thread/WorkerThread.java +index 421d4926ac674b5eb12d9613ceb6d20185ea557d..f27bfd7ab3ce10e6c318de0c6376a80fa9014d2a 100644 +--- a/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java ++++ b/src/main/java/co/earthme/hearse/concurrent/thread/WorkerThread.java +@@ -1,8 +1,8 @@ +-package co.earthme.hearse.concurrent; ++package co.earthme.hearse.concurrent.thread; + + import io.papermc.paper.util.TickThread; + +-public class WorkerThread extends TickThread { ++public class WorkerThread extends TickThread implements Worker{ + + public WorkerThread(String name) { + super(name); +@@ -11,8 +11,4 @@ public class WorkerThread extends TickThread { + public WorkerThread(Runnable run, String name) { + super(run, name); + } +- +- public static boolean isWorker(){ +- return Thread.currentThread() instanceof WorkerThread; +- } + } +diff --git a/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java b/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java +index 03a29509821a17faac2dc8ab810a2693b03bfbc6..b88b4f2d3df7303252a3c02824be3514c2618673 100644 +--- a/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java ++++ b/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java +@@ -1,11 +1,10 @@ + package co.earthme.hearse.concurrent.threadfactory; + +-import co.earthme.hearse.concurrent.WorkerThread; ++import co.earthme.hearse.concurrent.thread.WorkerThread; + import co.earthme.hearse.concurrent.WorkerThreadFactory; + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.server.MinecraftServer; +- + import java.util.List; + import java.util.concurrent.atomic.AtomicInteger; + +diff --git a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +index 987c98ea108d49c1335238bc529f782d3ec5b5e6..13ab82cf7d5b17768d9a83e6f92511c6d5fa60f3 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +@@ -2,19 +2,15 @@ package co.earthme.hearse.server; + + import co.earthme.hearse.Hearse; + import co.earthme.hearse.HearseConfig; +-import co.earthme.hearse.concurrent.WorkerThread; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; + import co.earthme.hearse.util.ArrayListBlockingQueue; +-import net.minecraft.CrashReport; +-import net.minecraft.ReportedException; ++import io.netty.handler.codec.serialization.ObjectEncoder; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +-import java.util.concurrent.ArrayBlockingQueue; +-import java.util.concurrent.LinkedBlockingQueue; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.locks.LockSupport; +diff --git a/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java b/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java +index b2cea65ecfb8a41248e7ee74357b4127106f1d0a..ec63191dca67c51f387ede9796a039210c8c3f99 100644 +--- a/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java ++++ b/src/main/java/co/earthme/hearse/util/ArrayListBlockingQueue.java +@@ -1,5 +1,7 @@ + package co.earthme.hearse.util; + ++import org.jetbrains.annotations.NotNull; ++ + import java.util.*; + import java.util.concurrent.*; + import java.util.concurrent.locks.*; +@@ -9,7 +11,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + private final StampedLock editLock = new StampedLock(); + + @Override +- public boolean add(T t) { ++ public boolean add(@NotNull T t) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.add(t); +@@ -19,7 +21,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean offer(T t) { ++ public boolean offer(@NotNull T t) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.add(t); +@@ -75,7 +77,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public void put(T t) { ++ public void put(@NotNull T t) { + final long id = this.editLock.writeLock(); + try { + this.internalList.add(t); +@@ -85,7 +87,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean offer(T t, long timeout, TimeUnit unit) { ++ public boolean offer(T t, long timeout, @NotNull TimeUnit unit) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.add(t); +@@ -99,23 +101,24 @@ public class ArrayListBlockingQueue implements BlockingQueue { + T t; + while ((t = this.poll()) == null){ + synchronized (this){ +- this.wait(1); ++ this.wait(0,1); + } + } + return t; + } + + @Override +- public T poll(long timeout, TimeUnit unit) throws InterruptedException { ++ public T poll(long timeout, @NotNull TimeUnit unit) throws InterruptedException { + T t; ++ long countTime = unit.toNanos(timeout); + while ((t = this.poll()) == null){ +- if (timeout == 0){ ++ if (countTime == 0){ + break; + } + synchronized (this){ +- unit.sleep(1); ++ this.wait(0,1); + } +- timeout--; ++ countTime--; + } + return t; + } +@@ -136,7 +139,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean containsAll(Collection c) { ++ public boolean containsAll(@NotNull Collection c) { + final long id = this.editLock.writeLock(); + try { + return new HashSet<>(this.internalList).containsAll(c); +@@ -146,7 +149,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean addAll(Collection c) { ++ public boolean addAll(@NotNull Collection c) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.addAll(c); +@@ -156,7 +159,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean removeAll(Collection c) { ++ public boolean removeAll(@NotNull Collection c) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.removeAll(c); +@@ -166,7 +169,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public boolean retainAll(Collection c) { ++ public boolean retainAll(@NotNull Collection c) { + final long id = this.editLock.writeLock(); + try { + return this.internalList.retainAll(c); +@@ -231,7 +234,7 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public T1[] toArray(T1[] a) { ++ public T1[] toArray(T1 @NotNull [] a) { + long id = this.editLock.readLock(); + try { + return this.internalList.toArray(a); +@@ -241,12 +244,12 @@ public class ArrayListBlockingQueue implements BlockingQueue { + } + + @Override +- public int drainTo(Collection c) { ++ public int drainTo(@NotNull Collection c) { + throw new UnsupportedOperationException("drainTo"); + } + + @Override +- public int drainTo(Collection c, int maxElements) { ++ public int drainTo(@NotNull Collection c, int maxElements) { + throw new UnsupportedOperationException("drainTo"); + } + } +diff --git a/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java b/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java +index 527dba288e1988773fd5a89f076f92084034f421..8cb0d00fb3cd4282873c8b8db88c87e59f8ef9de 100644 +--- a/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java ++++ b/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java +@@ -4,6 +4,7 @@ import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import com.google.common.collect.Maps; + import java.util.List; + import java.util.Map; ++import java.util.concurrent.ForkJoinPool; + import java.util.concurrent.TimeUnit; + + public class WorkerThreadPoolManager { +@@ -18,7 +19,7 @@ public class WorkerThreadPoolManager { + if (!worker.isShutdown()){ + worker.getQueue().clear(); //Clear the tasks.We don't need wait them + worker.shutdown(); +- while (worker.awaitTermination(100, TimeUnit.MILLISECONDS)) {} ++ while (worker.awaitTermination(100, TimeUnit.MILLISECONDS)); {} + } + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 39c3aaf91514bd8a2f9f04496e25a6253442939f..20a4fbbc25b670ba89b428dd4b14725469c9bc96 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1,7 +1,7 @@ + package net.minecraft.server.level; + + import co.aikar.timings.TimingHistory; +-import co.earthme.hearse.concurrent.WorkerThread; ++import co.earthme.hearse.concurrent.thread.Worker; + import co.earthme.hearse.server.ServerEntityTickHook; + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Lists; +@@ -223,7 +223,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, + java.util.function.Consumer> onLoad) { +- if (Thread.currentThread() != this.thread && !WorkerThread.isWorker()) { ++ if (Thread.currentThread() != this.thread && !Worker.isWorker()) { + this.getChunkSource().mainThreadProcessor.execute(() -> { + this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); + }); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 4a75c2895f4f5e3229be3c7b177445611a1e70b6..2f06b3782c4a21ed244b66dc146ebc71de8dac34 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1,11 +1,8 @@ + package net.minecraft.world.level; + +-import co.aikar.timings.Timing; +-import co.aikar.timings.Timings; +-import co.earthme.hearse.concurrent.WorkerThread; ++import co.earthme.hearse.concurrent.thread.Worker; + import com.destroystokyo.paper.event.server.ServerExceptionEvent; + import com.destroystokyo.paper.exception.ServerInternalException; +-import com.google.common.base.MoreObjects; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.mojang.serialization.Codec; +@@ -19,7 +16,6 @@ import java.util.function.Supplier; + import javax.annotation.Nullable; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; +-import net.minecraft.ReportedException; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Holder; +@@ -37,15 +33,12 @@ import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundSource; +-import net.minecraft.util.AbortableIterationConsumer; + import net.minecraft.util.Mth; + import net.minecraft.util.RandomSource; + import net.minecraft.util.profiling.ProfilerFiller; + import net.minecraft.world.DifficultyInstance; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; +-import net.minecraft.world.entity.boss.EnderDragonPart; +-import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; +@@ -83,7 +76,6 @@ import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.Scoreboard; + + // CraftBukkit start +-import java.util.HashMap; + import java.util.Map; + import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket; + import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket; +@@ -91,17 +83,14 @@ import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket; + import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket; + import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket; + import org.bukkit.Bukkit; +-import org.bukkit.Location; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.block.CapturedBlockState; + import org.bukkit.craftbukkit.block.CraftBlockState; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.craftbukkit.util.CraftSpawnCategory; +-import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.entity.SpawnCategory; + import org.bukkit.event.block.BlockPhysicsEvent; +-import org.bukkit.event.world.GenericGameEvent; + // CraftBukkit end + + public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1118,7 +1107,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end + // CraftBukkit end +- return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() && !WorkerThread.isWorker() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system ++ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() && !Worker.isWorker() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system + } + + public void setBlockEntity(BlockEntity blockEntity) { diff --git a/patches/server/0052-Hearse-Fix-a-deadlock-in-EntityLookup.patch b/patches/server/0052-Hearse-Fix-a-deadlock-in-EntityLookup.patch new file mode 100644 index 00000000..49733c6d --- /dev/null +++ b/patches/server/0052-Hearse-Fix-a-deadlock-in-EntityLookup.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 14:17:32 +0800 +Subject: [PATCH] Hearse: Fix a deadlock in EntityLookup + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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 ec8982b89cde24eefd3d392eed3e6cc926053dff..14e87135513c0f5fc1e16beae3ea596f9c23a542 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 +@@ -37,6 +37,8 @@ import java.util.Iterator; + import java.util.List; + import java.util.NoSuchElementException; + import java.util.UUID; ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; + import java.util.concurrent.locks.StampedLock; + import java.util.function.Consumer; + import java.util.function.Predicate; +@@ -52,7 +54,7 @@ public final class EntityLookup implements LevelEntityGetter { + public final ServerLevel world; + + private final StampedLock entityByLock = new StampedLock(); +- private final Object chunkEntitySlicesLock = new Object(); ++ private final Lock chunkEntitySlicesLock = new ReentrantLock(true); + + protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f)); + +@@ -374,8 +376,11 @@ public final class EntityLookup implements LevelEntityGetter { + throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); + } + ChunkEntitySlices slices; +- synchronized (this.chunkEntitySlicesLock){ ++ this.chunkEntitySlicesLock.lock(); ++ try { + slices = this.getChunk(sectionX, sectionZ); ++ }finally { ++ this.chunkEntitySlicesLock.unlock(); + } + // all entities should be in a chunk + if (slices == null) { +@@ -420,8 +425,11 @@ public final class EntityLookup implements LevelEntityGetter { + TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); + + ChunkEntitySlices old; +- synchronized (this.chunkEntitySlicesLock){ ++ this.chunkEntitySlicesLock.lock(); ++ try { + old = this.getChunk(entity.sectionX, entity.sectionZ); ++ }finally { ++ this.chunkEntitySlicesLock.unlock(); + } + final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); + +@@ -641,14 +649,17 @@ public final class EntityLookup implements LevelEntityGetter { + TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); + synchronized (this) { + final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); +- synchronized (this.chunkEntitySlicesLock){ +- if (curr != null) { ++ if (curr != null) { ++ this.chunkEntitySlicesLock.lock(); ++ try { + this.removeChunk(chunkX, chunkZ); + curr.mergeInto(slices); + this.addChunk(chunkX, chunkZ, slices); +- } else { +- this.addChunk(chunkX, chunkZ, slices); ++ }finally { ++ this.chunkEntitySlicesLock.unlock(); + } ++ } else { ++ this.addChunk(chunkX, chunkZ, slices); + } + } + } +@@ -656,9 +667,7 @@ public final class EntityLookup implements LevelEntityGetter { + + public void entitySectionUnload(final int chunkX, final int chunkZ) { + TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot unload entity section off-main"); +- synchronized (this.chunkEntitySlicesLock){ +- this.removeChunk(chunkX, chunkZ); +- } ++ this.removeChunk(chunkX, chunkZ); + } + + public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { diff --git a/patches/server/0053-Hearse-ForkJoin-worker-support.patch b/patches/server/0053-Hearse-ForkJoin-worker-support.patch new file mode 100644 index 00000000..bc20a163 --- /dev/null +++ b/patches/server/0053-Hearse-ForkJoin-worker-support.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 14:17:57 +0800 +Subject: [PATCH] Hearse: ForkJoin worker support :) + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/concurrent/thread/WorkerForkJoinThread.java b/src/main/java/co/earthme/hearse/concurrent/thread/WorkerForkJoinThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6a2fb0643b6fc5921b24674940c2c3b92b9e4e88 +--- /dev/null ++++ b/src/main/java/co/earthme/hearse/concurrent/thread/WorkerForkJoinThread.java +@@ -0,0 +1,10 @@ ++package co.earthme.hearse.concurrent.thread; ++ ++import java.util.concurrent.ForkJoinPool; ++import java.util.concurrent.ForkJoinWorkerThread; ++ ++public class WorkerForkJoinThread extends ForkJoinWorkerThread implements Worker { ++ protected WorkerForkJoinThread(ForkJoinPool pool) { ++ super(pool); ++ } ++} diff --git a/patches/server/0054-Hearse-Use-LinedBlockingQueue-XD.patch b/patches/server/0054-Hearse-Use-LinedBlockingQueue-XD.patch new file mode 100644 index 00000000..778b3f45 --- /dev/null +++ b/patches/server/0054-Hearse-Use-LinedBlockingQueue-XD.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 18:13:53 +0800 +Subject: [PATCH] Hearse: Use LinedBlockingQueue XD + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +index 8baccccee52b6e47bf51e51d976ad76920270ef4..c0d235e7227db0be6c6f753d8a6e13ad2716f798 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +@@ -41,7 +41,7 @@ public class ServerEntityTickHook { + workerCount, + 0L, + TimeUnit.MILLISECONDS, +- new ArrayListBlockingQueue<>(), ++ new LinkedBlockingQueue<>(), + defFactory + ); + Hearse.getWorkerManager().addWorker("entity",worker); +diff --git a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +index 13ab82cf7d5b17768d9a83e6f92511c6d5fa60f3..759b6dc9c719c6ff63348f9eacc760f8cef3163e 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +@@ -11,6 +11,7 @@ import net.minecraft.server.level.ServerLevel; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + ++import java.util.concurrent.ArrayBlockingQueue; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.locks.LockSupport; +@@ -32,7 +33,7 @@ public class ServerLevelTickHook { + MinecraftServer.getServer().levels.size(), + Long.MAX_VALUE, + TimeUnit.MILLISECONDS, +- new ArrayListBlockingQueue<>(), ++ new ArrayBlockingQueue<>(MinecraftServer.getServer().levels.size()), + workerFactory + ); + worker.allowCoreThreadTimeOut(true); diff --git a/patches/server/0055-Hearse-I-gave-up-to-fix-them.patch b/patches/server/0055-Hearse-I-gave-up-to-fix-them.patch new file mode 100644 index 00000000..64c3e11d --- /dev/null +++ b/patches/server/0055-Hearse-I-gave-up-to-fix-them.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 18:14:15 +0800 +Subject: [PATCH] Hearse: I gave up to fix them + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index af5956bd57141cae08fe839bb2176988a48cd9b8..c26e10ee64b60e8c73a07f24446f439d22167fa5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2,7 +2,6 @@ package org.bukkit.craftbukkit.event; + + import com.google.common.base.Function; + import com.google.common.base.Functions; +-import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Either; + import java.net.InetAddress; + import java.util.ArrayList; +@@ -230,7 +229,7 @@ public class CraftEventFactory { + public static final DamageSource MELTING = CraftDamageSource.copyOf(DamageSource.ON_FIRE); + public static final DamageSource POISON = CraftDamageSource.copyOf(DamageSource.MAGIC); + public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent +- public static volatile Entity entityDamage; // For use in EntityDamageByEntityEvent ++ public static Entity entityDamage; // For use in EntityDamageByEntityEvent + + // helper methods + private static boolean canBuild(ServerLevel world, Player player, int x, int z) { +@@ -1049,7 +1048,8 @@ public class CraftEventFactory { + } else if (source == DamageSource.IN_FIRE) { + cause = DamageCause.FIRE; + } else { +- throw new IllegalStateException(String.format("Unhandled damage of %s by %s from %s", entity, damager, source.msgId)); ++ cause = DamageCause.CUSTOM; ++ LogManager.getLogger().warn(String.format("Unhandled damage of %s by %s from %s", entity, damager, source.msgId)); + } + EntityDamageEvent event = new EntityDamageByBlockEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions); + event.setCancelled(cancelled); +@@ -1126,11 +1126,12 @@ public class CraftEventFactory { + cause = DamageCause.CUSTOM; + } + +- if (cause != null) { +- return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API ++ if (cause == null) { ++ cause = DamageCause.CUSTOM; + } + +- throw new IllegalStateException(String.format("Unhandled damage of %s from %s", entity, source.msgId)); ++ LogManager.getLogger().warn(String.format("Unhandled damage of %s from %s", entity, source.msgId)); ++ return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } + + @Deprecated // Paper - Add critical damage API diff --git a/patches/server/0056-Hearse-Add-entity-position-cache-to-fix-concurrent-p.patch b/patches/server/0056-Hearse-Add-entity-position-cache-to-fix-concurrent-p.patch new file mode 100644 index 00000000..4b8a7a8b --- /dev/null +++ b/patches/server/0056-Hearse-Add-entity-position-cache-to-fix-concurrent-p.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 19:05:17 +0800 +Subject: [PATCH] Hearse: Add entity position cache to fix concurrent problem-1 + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/util/EntityPositionCache.java b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0ff6ef85758c4f5780860178572e128080470d04 +--- /dev/null ++++ b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +@@ -0,0 +1,24 @@ ++package co.earthme.hearse.util; ++ ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.Vec3; ++ ++public class EntityPositionCache { ++ private final double x; ++ private final double y; ++ private final double z; ++ ++ public EntityPositionCache(Entity entity){ ++ this.x = entity.getX(); ++ this.y = entity.getY(); ++ this.z = entity.getZ(); ++ } ++ ++ public double distanceToSqr(Vec3 vector) { ++ double d0 = this.x - vector.x; ++ double d1 = this.y - vector.y; ++ double d2 = this.z - vector.z; ++ ++ return d0 * d0 + d1 * d1 + d2 * d2; ++ } ++} diff --git a/patches/server/0057-Hearse-Fix-a-sorting-exception-in-NearestLivingEntit.patch b/patches/server/0057-Hearse-Fix-a-sorting-exception-in-NearestLivingEntit.patch new file mode 100644 index 00000000..6a38719c --- /dev/null +++ b/patches/server/0057-Hearse-Fix-a-sorting-exception-in-NearestLivingEntit.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 19:41:50 +0800 +Subject: [PATCH] Hearse: Fix a sorting exception in NearestLivingEntitySensor + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/util/EntityPositionCache.java b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +index 0ff6ef85758c4f5780860178572e128080470d04..b7ec86d5cb2bb8d62d1a54f5e7b394e992a2b870 100644 +--- a/src/main/java/co/earthme/hearse/util/EntityPositionCache.java ++++ b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +@@ -1,17 +1,48 @@ + package co.earthme.hearse.util; + + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.phys.Vec3; + + public class EntityPositionCache { + private final double x; + private final double y; + private final double z; ++ private final LivingEntity currentEntity; + +- public EntityPositionCache(Entity entity){ ++ public EntityPositionCache(LivingEntity entity){ + this.x = entity.getX(); + this.y = entity.getY(); + this.z = entity.getZ(); ++ this.currentEntity = entity; ++ } ++ ++ public LivingEntity getCurrentEntity() { ++ return this.currentEntity; ++ } ++ ++ public double getX() { ++ return this.x; ++ } ++ ++ public double getY() { ++ return this.y; ++ } ++ ++ public double getZ() { ++ return this.z; ++ } ++ ++ public double distanceToSqr(double x, double y, double z) { ++ double d3 = this.x - x; ++ double d4 = this.y - y; ++ double d5 = this.z - z; ++ ++ return d3 * d3 + d4 * d4 + d5 * d5; ++ } ++ ++ public double distanceToSqr(Entity entity) { ++ return this.distanceToSqr(entity.position()); + } + + public double distanceToSqr(Vec3 vector) { +@@ -21,4 +52,8 @@ public class EntityPositionCache { + + return d0 * d0 + d1 * d1 + d2 * d2; + } ++ ++ public double distanceToSqr(EntityPositionCache entityPositionCache) { ++ return this.distanceToSqr(entityPositionCache.getX(),entityPositionCache.getY(),entityPositionCache.getZ()); ++ } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index c72920a17178059a29d21e96aeef398f6e0bbbdc..2658334ba2a22e808c747bbf81475ecb18a958d2 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity; + ++import co.earthme.hearse.util.EntityPositionCache; + import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; // Paper + import com.google.common.base.Objects; + import com.google.common.collect.ImmutableList; +@@ -878,6 +879,10 @@ public abstract class LivingEntity extends Entity { + private boolean isTickingEffects = false; + private List effectsToProcess = Lists.newCopyOnWriteArrayList(); + ++ public double distanceToSqr(EntityPositionCache entityPositionCache) { ++ return this.distanceToSqr(entityPositionCache.getX(),entityPositionCache.getY(),entityPositionCache.getZ()); ++ } ++ + private static class ProcessableEffect { + + private MobEffect type; +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +index d8cf99a3014a4b8152ae819fa663c2fdf34dce57..714cc41d6358ea57a23e2e663422fdba28efcf4d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +@@ -1,10 +1,17 @@ + package net.minecraft.world.entity.ai.sensing; + ++import co.earthme.hearse.util.EntityPositionCache; + import com.google.common.collect.ImmutableSet; + import java.util.Comparator; + import java.util.List; + import java.util.Set; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.Brain; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; +@@ -12,16 +19,28 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; + import net.minecraft.world.phys.AABB; + + public class NearestLivingEntitySensor extends Sensor { ++ private final List entitiesCache = ObjectLists.synchronize(new ObjectArrayList<>()); ++ private final AtomicBoolean calling = new AtomicBoolean(false); ++ + @Override + protected void doTick(ServerLevel world, T entity) { +- AABB aABB = entity.getBoundingBox().inflate((double)this.radiusXZ(), (double)this.radiusY(), (double)this.radiusXZ()); +- List list = world.getEntitiesOfClass(LivingEntity.class, aABB, (e) -> { +- return e != entity && e.isAlive(); +- }); +- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); +- Brain brain = entity.getBrain(); +- brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(entity, list)); ++ if (!this.calling.get()){ ++ this.calling.set(true); ++ try { ++ AABB aABB = entity.getBoundingBox().inflate(this.radiusXZ(), this.radiusY(), this.radiusXZ()); ++ this.entitiesCache.clear(); ++ this.entitiesCache.addAll(world.getEntitiesOfClass(LivingEntity.class, aABB, (e) -> e != entity && e.isAlive()).stream().map(EntityPositionCache::new).toList()); ++ final EntityPositionCache compareCache = new EntityPositionCache(entity); ++ this.entitiesCache.sort(Comparator.comparingDouble(compareCache::distanceToSqr)); ++ ++ Brain brain = entity.getBrain(); ++ final List list = this.entitiesCache.stream().map(EntityPositionCache::getCurrentEntity).toList(); ++ brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES,list); ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(entity, list)); ++ }finally { ++ this.calling.set(false); ++ } ++ } + } + + protected int radiusXZ() { diff --git a/patches/server/0058-Hearse-I-am-a-idiot.patch b/patches/server/0058-Hearse-I-am-a-idiot.patch new file mode 100644 index 00000000..d2d8ecf0 --- /dev/null +++ b/patches/server/0058-Hearse-I-am-a-idiot.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Mon, 16 Jan 2023 20:01:38 +0800 +Subject: [PATCH] Hearse: I am a idiot + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c26e10ee64b60e8c73a07f24446f439d22167fa5..cd972ab7048319de10c304419cd55a6920055218 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1127,10 +1127,10 @@ public class CraftEventFactory { + } + + if (cause == null) { ++ LogManager.getLogger().warn(String.format("Unhandled damage of %s from %s", entity, source.msgId)); + cause = DamageCause.CUSTOM; + } +- +- LogManager.getLogger().warn(String.format("Unhandled damage of %s from %s", entity, source.msgId)); ++ + return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } + diff --git a/patches/server/0059-Hearse-Fix-some-NPE.patch b/patches/server/0059-Hearse-Fix-some-NPE.patch new file mode 100644 index 00000000..c8e084ba --- /dev/null +++ b/patches/server/0059-Hearse-Fix-some-NPE.patch @@ -0,0 +1,249 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 17 Jan 2023 16:30:58 +0800 +Subject: [PATCH] Hearse: Fix some NPE + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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 14e87135513c0f5fc1e16beae3ea596f9c23a542..d183eb5ba934236469855b374904ef72a09b869f 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 +@@ -37,9 +37,7 @@ import java.util.Iterator; + import java.util.List; + import java.util.NoSuchElementException; + import java.util.UUID; +-import java.util.concurrent.locks.Lock; +-import java.util.concurrent.locks.ReentrantLock; +-import java.util.concurrent.locks.StampedLock; ++import java.util.concurrent.locks.*; + import java.util.function.Consumer; + import java.util.function.Predicate; + +@@ -54,9 +52,10 @@ public final class EntityLookup implements LevelEntityGetter { + public final ServerLevel world; + + private final StampedLock entityByLock = new StampedLock(); +- private final Lock chunkEntitySlicesLock = new ReentrantLock(true); ++ private final Lock entityGetChunkLock = new ReentrantLock(); ++ private final ReadWriteLock chunkEntitySlicesLock = new ReentrantReadWriteLock(); + +- protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f)); ++ protected final Long2ObjectMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); + + private final int minSection; // inclusive + private final int maxSection; // inclusive +@@ -136,7 +135,14 @@ public final class EntityLookup implements LevelEntityGetter { + } + + public String getDebugInfo() { +- return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size(); ++ int reginSize; ++ this.chunkEntitySlicesLock.readLock().lock(); ++ try { ++ reginSize = this.regions.size(); ++ }finally { ++ this.chunkEntitySlicesLock.readLock().unlock(); ++ } ++ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + reginSize; + } + + static final class ArrayIterable implements Iterable { +@@ -173,12 +179,12 @@ public final class EntityLookup implements LevelEntityGetter { + } + + @Override +- public synchronized boolean hasNext() { ++ public boolean hasNext() { + return this.off < this.length; + } + + @Override +- public synchronized T next() { ++ public T next() { + if (this.off >= this.length) { + throw new NoSuchElementException(); + } +@@ -186,7 +192,7 @@ public final class EntityLookup implements LevelEntityGetter { + } + + @Override +- public synchronized void remove() { ++ public void remove() { + throw new UnsupportedOperationException(); + } + } +@@ -355,7 +361,15 @@ public final class EntityLookup implements LevelEntityGetter { + entity.sectionX = sectionX; + entity.sectionY = sectionY; + entity.sectionZ = sectionZ; +- final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); ++ ChunkEntitySlices slices; ++ ++ this.entityGetChunkLock.lock(); ++ try { ++ slices = this.getOrCreateChunk(sectionX, sectionZ); ++ }finally { ++ this.entityGetChunkLock.unlock(); ++ } ++ + if (!slices.addEntity(entity, sectionY)) { + LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); + } +@@ -376,11 +390,11 @@ public final class EntityLookup implements LevelEntityGetter { + throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); + } + ChunkEntitySlices slices; +- this.chunkEntitySlicesLock.lock(); ++ this.entityGetChunkLock.lock(); + try { + slices = this.getChunk(sectionX, sectionZ); + }finally { +- this.chunkEntitySlicesLock.unlock(); ++ this.entityGetChunkLock.unlock(); + } + // all entities should be in a chunk + if (slices == null) { +@@ -425,13 +439,16 @@ public final class EntityLookup implements LevelEntityGetter { + TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); + + ChunkEntitySlices old; +- this.chunkEntitySlicesLock.lock(); ++ ChunkEntitySlices slices; ++ ++ this.entityGetChunkLock.lock(); + try { + old = this.getChunk(entity.sectionX, entity.sectionZ); ++ slices = this.getOrCreateChunk(newSectionX, newSectionZ); + }finally { +- this.chunkEntitySlicesLock.unlock(); ++ this.entityGetChunkLock.unlock(); + } +- final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); ++ + + if (!old.removeEntity(entity, entity.sectionY)) { + LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); +@@ -647,20 +664,18 @@ public final class EntityLookup implements LevelEntityGetter { + + public void entitySectionLoad(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { + TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); +- synchronized (this) { +- final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); ++ final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); ++ this.chunkEntitySlicesLock.writeLock().lock(); ++ try { + if (curr != null) { +- this.chunkEntitySlicesLock.lock(); +- try { +- this.removeChunk(chunkX, chunkZ); +- curr.mergeInto(slices); +- this.addChunk(chunkX, chunkZ, slices); +- }finally { +- this.chunkEntitySlicesLock.unlock(); +- } ++ this.removeChunk(chunkX, chunkZ); ++ curr.mergeInto(slices); ++ this.addChunk(chunkX, chunkZ, slices); + } else { + this.addChunk(chunkX, chunkZ, slices); + } ++ } finally { ++ this.chunkEntitySlicesLock.writeLock().unlock(); + } + } + +@@ -675,7 +690,6 @@ public final class EntityLookup implements LevelEntityGetter { + if (region == null) { + return null; + } +- + return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); + } + +@@ -692,32 +706,46 @@ public final class EntityLookup implements LevelEntityGetter { + + public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { + final long key = CoordinateUtils.getChunkKey(regionX, regionZ); +- return this.regions.get(key); ++ this.chunkEntitySlicesLock.readLock().lock(); ++ try { ++ return this.regions.get(key); ++ }finally { ++ this.chunkEntitySlicesLock.readLock().unlock(); ++ } + } + +- private synchronized void removeChunk(final int chunkX, final int chunkZ) { ++ private void removeChunk(final int chunkX, final int chunkZ) { + final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); + +- final ChunkSlicesRegion region = this.regions.get(key); +- final int remaining = region.remove(relIndex); +- +- if (remaining == 0) { +- this.regions.remove(key); ++ this.chunkEntitySlicesLock.writeLock().lock(); ++ try { ++ final ChunkSlicesRegion region = this.regions.get(key); ++ final int remaining = region.remove(relIndex); ++ if (remaining == 0) { ++ this.regions.remove(key); ++ } ++ }finally { ++ this.chunkEntitySlicesLock.writeLock().unlock(); + } + } + +- public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { ++ public void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { + final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); + +- ChunkSlicesRegion region = this.regions.get(key); +- if (region != null) { +- region.add(relIndex, slices); +- } else { +- region = new ChunkSlicesRegion(); +- region.add(relIndex, slices); +- this.regions.put(key, region); ++ this.chunkEntitySlicesLock.writeLock().lock(); ++ try { ++ ChunkSlicesRegion region = this.regions.get(key); ++ if (region != null) { ++ region.add(relIndex, slices); ++ } else { ++ region = new ChunkSlicesRegion(); ++ region.add(relIndex, slices); ++ this.regions.put(key, region); ++ } ++ } finally { ++ this.chunkEntitySlicesLock.writeLock().unlock(); + } + } + +@@ -726,11 +754,11 @@ public final class EntityLookup implements LevelEntityGetter { + protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; + protected int sliceCount; + +- public ChunkEntitySlices get(final int index) { ++ public synchronized ChunkEntitySlices get(final int index) { + return this.slices[index]; + } + +- public int remove(final int index) { ++ public synchronized int remove(final int index) { + final ChunkEntitySlices slices = this.slices[index]; + if (slices == null) { + throw new IllegalStateException(); +@@ -741,7 +769,7 @@ public final class EntityLookup implements LevelEntityGetter { + return --this.sliceCount; + } + +- public void add(final int index, final ChunkEntitySlices slices) { ++ public synchronized void add(final int index, final ChunkEntitySlices slices) { + final ChunkEntitySlices curr = this.slices[index]; + if (curr != null) { + throw new IllegalStateException(); diff --git a/patches/server/0060-Hearse-Refactor-paper-s-EntityLookup-and-fix-a-Index.patch b/patches/server/0060-Hearse-Refactor-paper-s-EntityLookup-and-fix-a-Index.patch new file mode 100644 index 00000000..8514d422 --- /dev/null +++ b/patches/server/0060-Hearse-Refactor-paper-s-EntityLookup-and-fix-a-Index.patch @@ -0,0 +1,285 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 17 Jan 2023 18:41:51 +0800 +Subject: [PATCH] Hearse: Refactor paper's EntityLookup and fix a + IndexOutOfBoundsException + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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 d183eb5ba934236469855b374904ef72a09b869f..09a85daaf8cb40b49fe264e999778b49160fe12a 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 +@@ -2,42 +2,31 @@ package io.papermc.paper.chunk.system.entity; + + import com.destroystokyo.paper.util.maplist.EntityList; + import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.ChunkSystem; + 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.Long2ObjectArrayMap; + 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 it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.core.BlockPos; +-import io.papermc.paper.chunk.system.ChunkSystem; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.AbortableIterationConsumer; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +-import net.minecraft.world.level.entity.EntityInLevelCallback; +-import net.minecraft.world.level.entity.EntityTypeTest; +-import net.minecraft.world.level.entity.LevelCallback; +-import net.minecraft.world.level.entity.LevelEntityGetter; +-import net.minecraft.world.level.entity.Visibility; ++import net.minecraft.world.level.entity.*; + 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.concurrent.locks.*; ++ ++import java.util.*; ++import java.util.concurrent.locks.ReadWriteLock; ++import java.util.concurrent.locks.ReentrantReadWriteLock; + import java.util.function.Consumer; + import java.util.function.Predicate; + +@@ -51,17 +40,14 @@ public final class EntityLookup implements LevelEntityGetter { + + public final ServerLevel world; + +- private final StampedLock entityByLock = new StampedLock(); +- private final Lock entityGetChunkLock = new ReentrantLock(); + private final ReadWriteLock chunkEntitySlicesLock = new ReentrantReadWriteLock(); + +- protected final Long2ObjectMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); ++ protected final Long2ObjectMap regions = new Long2ObjectArrayMap<>(); + + private final int minSection; // inclusive + private final int maxSection; // inclusive + private final LevelCallback worldCallback; +- private final Int2ReferenceMap entityById = new Int2ReferenceOpenHashMap<>(); +- private final Object2ReferenceMap entityByUUID = new Object2ReferenceOpenHashMap<>(); ++ private final List addedEntities = ObjectLists.synchronize(new ObjectArrayList<>()); + private final EntityList accessibleEntities = new EntityList(); + + public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { +@@ -82,52 +68,23 @@ public final class EntityLookup implements LevelEntityGetter { + @Nullable + @Override + public Entity get(final int id) { +- final long attempt = this.entityByLock.tryOptimisticRead(); +- if (attempt != 0L) { +- try { +- final Entity ret = this.entityById.get(id); +- +- if (this.entityByLock.validate(attempt)) { +- return maskNonAccessible(ret); +- } +- } catch (final Error error) { +- throw error; +- } catch (final Throwable thr) { +- // ignore ++ for (Entity entity : this.addedEntities){ ++ if (id == entity.getId()){ ++ return entity; + } + } +- +- this.entityByLock.readLock(); +- try { +- return maskNonAccessible(this.entityById.get(id)); +- } finally { +- this.entityByLock.tryUnlockRead(); +- } ++ return null; + } + + @Nullable + @Override + public Entity get(final UUID id) { +- final long attempt = this.entityByLock.tryOptimisticRead(); +- if (attempt != 0L) { +- try { +- final Entity ret = this.entityByUUID.get(id); +- if (this.entityByLock.validate(attempt)) { +- return maskNonAccessible(ret); +- } +- } catch (final Error error) { +- throw error; +- } catch (final Throwable thr) { +- // ignore ++ for (Entity entity : this.addedEntities){ ++ if (entity.getUUID().equals(id)){ ++ return entity; + } + } +- +- this.entityByLock.readLock(); +- try { +- return maskNonAccessible(this.entityByUUID.get(id)); +- } finally { +- this.entityByLock.tryUnlockRead(); +- } ++ return null; + } + + public boolean hasEntity(final UUID uuid) { +@@ -142,7 +99,7 @@ public final class EntityLookup implements LevelEntityGetter { + }finally { + this.chunkEntitySlicesLock.readLock().unlock(); + } +- return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + reginSize; ++ return "count:" + this.addedEntities.size() +",region_count:" + reginSize; + } + + static final class ArrayIterable implements Iterable { +@@ -205,7 +162,7 @@ public final class EntityLookup implements LevelEntityGetter { + + @Override + public void get(final EntityTypeTest filter, final AbortableIterationConsumer action) { +- for (final Entity entity : this.entityById.values()) { ++ for (final Entity entity : this.addedEntities) { + final Visibility visibility = EntityLookup.getEntityStatus(entity); + if (!visibility.isAccessible()) { + continue; +@@ -342,33 +299,16 @@ public final class EntityLookup implements LevelEntityGetter { + } + } + +- this.entityByLock.writeLock(); +- try { +- if (this.entityById.containsKey(entity.getId())) { +- LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity); +- return false; +- } +- if (this.entityByUUID.containsKey(entity.getUUID())) { +- LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity); ++ if (this.addedEntities.contains(entity)) { ++ LOGGER.warn("Entity already exists: " + entity); + return false; + } +- this.entityById.put(entity.getId(), entity); +- this.entityByUUID.put(entity.getUUID(), entity); +- } finally { +- this.entityByLock.tryUnlockWrite(); +- } ++ this.addedEntities.add(entity); + + entity.sectionX = sectionX; + entity.sectionY = sectionY; + entity.sectionZ = sectionZ; +- ChunkEntitySlices slices; +- +- this.entityGetChunkLock.lock(); +- try { +- slices = this.getOrCreateChunk(sectionX, sectionZ); +- }finally { +- this.entityGetChunkLock.unlock(); +- } ++ ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); + + if (!slices.addEntity(entity, sectionY)) { + LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); +@@ -389,13 +329,7 @@ public final class EntityLookup implements LevelEntityGetter { + if (!entity.isRemoved()) { + throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); + } +- ChunkEntitySlices slices; +- this.entityGetChunkLock.lock(); +- try { +- slices = this.getChunk(sectionX, sectionZ); +- }finally { +- this.entityGetChunkLock.unlock(); +- } ++ ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); + // all entities should be in a chunk + if (slices == null) { + LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); +@@ -406,17 +340,9 @@ public final class EntityLookup implements LevelEntityGetter { + } + entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; + +- this.entityByLock.writeLock(); +- try { +- if (!this.entityById.remove(entity.getId(), entity)) { +- LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId())); ++ if (!this.addedEntities.remove(entity)) { ++ LOGGER.warn("Failed to remove entity " + entity); + } +- if (!this.entityByUUID.remove(entity.getUUID(), entity)) { +- LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID())); +- } +- } finally { +- this.entityByLock.tryUnlockWrite(); +- } + } + + private ChunkEntitySlices moveEntity(final Entity entity) { +@@ -438,17 +364,8 @@ public final class EntityLookup implements LevelEntityGetter { + // ensure the old section is owned by this tick thread + TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); + +- ChunkEntitySlices old; +- ChunkEntitySlices slices; +- +- this.entityGetChunkLock.lock(); +- try { +- old = this.getChunk(entity.sectionX, entity.sectionZ); +- slices = this.getOrCreateChunk(newSectionX, newSectionZ); +- }finally { +- this.entityGetChunkLock.unlock(); +- } +- ++ ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); ++ ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); + + if (!old.removeEntity(entity, entity.sectionY)) { + LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 20a4fbbc25b670ba89b428dd4b14725469c9bc96..91eb39a7e23149bff16129f9f7622c44cfcb7afe 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -400,7 +400,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Paper end + + // Paper start - optimise checkDespawn +- public final List playersAffectingSpawning = new java.util.ArrayList<>(); ++ public final List playersAffectingSpawning = Lists.newCopyOnWriteArrayList(); + // Paper end - optimise checkDespawn + // Paper start - optimise get nearest players for entity AI + @Override +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 29fa9ad2223de668c15a5e5b433704b2c4765610..eea8ac88339c7ef58b8411a9c5357db8ae9f9289 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -831,7 +831,9 @@ public abstract class Mob extends LivingEntity { + // Paper start - optimise checkDespawn + Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper + if (entityhuman == null) { +- entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0); ++ synchronized (((ServerLevel) this.level).playersAffectingSpawning) { ++ entityhuman = ((ServerLevel) this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel) this.level).playersAffectingSpawning.get(0); ++ } + } + // Paper end - optimise checkDespawn + diff --git a/patches/server/0061-Lithium-ai.sensor.secondary_poi.patch b/patches/server/0061-Lithium-ai.sensor.secondary_poi.patch new file mode 100644 index 00000000..14754eaf --- /dev/null +++ b/patches/server/0061-Lithium-ai.sensor.secondary_poi.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 17 Jan 2023 21:47:31 +0800 +Subject: [PATCH] Lithium ai.sensor.secondary_poi + +Original license: GPL v3 +Original project: https://github.com/CaffeineMC/lithium-fabric + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +index cb1d91f9fe98f21c2afbe3894dfd9bca3bdd3ba6..c6a92edd7e7077437236d2d119ea6b1436f16e50 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +@@ -22,6 +22,13 @@ public class SecondaryPoiSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, Villager entity) { ++ ++ //lithium: ai.sensor.secondary_poi ++ if (entity.getVillagerData().getProfession().secondaryPoi().isEmpty()) { ++ entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); ++ return; ++ } ++ + ResourceKey resourceKey = world.dimension(); + BlockPos blockPos = entity.blockPosition(); + List list = Lists.newArrayList(); diff --git a/patches/server/0062-Lithium-collections.entity_filtering.patch b/patches/server/0062-Lithium-collections.entity_filtering.patch new file mode 100644 index 00000000..64e20fa2 --- /dev/null +++ b/patches/server/0062-Lithium-collections.entity_filtering.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 17 Jan 2023 21:57:25 +0800 +Subject: [PATCH] Lithium: collections.entity_filtering + +Original license: GPL v3 +Original project: https://github.com/CaffeineMC/lithium-fabric + +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index d5802cfe08f92b55ff1fd41648abda9ef2b7dd20..d6748f84e64bdafeadee38a4073583e6ea932bbe 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -28,7 +28,7 @@ public class ClassInstanceMultiMap extends AbstractCollection { + public boolean add(T object) { + boolean bl = false; + +- for(Map.Entry, List> entry : this.byClass.entrySet()) { ++ for (Map.Entry, List> entry : this.byClass.entrySet()) { + if (entry.getKey().isInstance(object)) { + bl |= entry.getValue().add(object); + } +@@ -41,7 +41,7 @@ public class ClassInstanceMultiMap extends AbstractCollection { + public boolean remove(Object object) { + boolean bl = false; + +- for(Map.Entry, List> entry : this.byClass.entrySet()) { ++ for (Map.Entry, List> entry : this.byClass.entrySet()) { + if (entry.getKey().isInstance(object)) { + List list = entry.getValue(); + bl |= list.remove(object); +@@ -57,23 +57,36 @@ public class ClassInstanceMultiMap extends AbstractCollection { + } + + public Collection find(Class type) { +- 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(toList()); +- }); +- return (Collection) Collections.unmodifiableCollection(list); ++ // JettPack start ++ Collection collection = this.byClass.get(type); ++ ++ if (collection == null) { ++ collection = this.createAllOfType(type); + } ++ ++ return (Collection) Collections.unmodifiableCollection(collection); ++ // JettPack end + } + +- public static Collector> toList() { +- return Collectors.toCollection(CopyOnWriteArrayList::new); ++ // JettPack start ++ private Collection createAllOfType(Class type) { ++ List list = new java.util.ArrayList<>(); ++ ++ for (T allElement : this.allInstances) { ++ if (type.isInstance(allElement)) { ++ list.add(allElement); ++ } ++ } ++ ++ this.byClass.put(type, list); ++ ++ return list; + } ++ // JettPack end + + @Override + public Iterator iterator() { +- return (Iterator)(this.allInstances.isEmpty() ? Collections.emptyIterator() : Iterators.unmodifiableIterator(this.allInstances.iterator())); ++ return (Iterator) (this.allInstances.isEmpty() ? Collections.emptyIterator() : Iterators.unmodifiableIterator(this.allInstances.iterator())); + } + + public List getAllInstances() { diff --git a/patches/server/0063-Hearse-Multithreaded-tracker.patch b/patches/server/0063-Hearse-Multithreaded-tracker.patch new file mode 100644 index 00000000..01ddfe8f --- /dev/null +++ b/patches/server/0063-Hearse-Multithreaded-tracker.patch @@ -0,0 +1,272 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 14:39:00 +0800 +Subject: [PATCH] Hearse: Multithreaded tracker + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index dfb747eba6bf7088af0ff400da169de00a076365..0f18f7ce182e35ee6f3f385553e1bdb5ee4ad587 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1,5 +1,7 @@ + package net.minecraft.server.level; + ++import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; ++import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; + import com.google.common.collect.*; + import com.google.common.collect.ImmutableList.Builder; + import com.google.gson.JsonElement; +@@ -13,10 +15,7 @@ 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.*; +-import it.unimi.dsi.fastutil.objects.ObjectIterator; +-import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +-import it.unimi.dsi.fastutil.objects.ReferenceSet; +-import it.unimi.dsi.fastutil.objects.ReferenceSets; ++import it.unimi.dsi.fastutil.objects.*; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; +@@ -61,10 +60,7 @@ import java.io.IOException; + import java.io.Writer; + import java.nio.file.Path; + import java.util.*; +-import java.util.concurrent.CancellationException; +-import java.util.concurrent.CompletableFuture; +-import java.util.concurrent.CompletionException; +-import java.util.concurrent.Executor; ++import java.util.concurrent.*; + import java.util.concurrent.atomic.AtomicInteger; + import java.util.function.*; + +@@ -1194,78 +1190,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.tracker = null; // Paper - We're no longer tracked + } + ++ private final Executor asyncTrackWorker = new WorkerThreadPoolExecutor( ++ 0, ++ 2, ++ 5L, ++ TimeUnit.SECONDS, ++ new LinkedBlockingQueue<>(), ++ new DefaultWorkerFactory("tracker-async")); ++ private final Executor concurrentTrackWorker = new WorkerThreadPoolExecutor( ++ Runtime.getRuntime().availableProcessors(), ++ Runtime.getRuntime().availableProcessors(), ++ 5L, ++ TimeUnit.SECONDS, ++ new LinkedBlockingQueue<>(), ++ new DefaultWorkerFactory("tracker-concurrent")); ++ ++ private final AtomicInteger totalRunning = new AtomicInteger(); ++ + // Paper start - optimised tracker + private final void processTrackQueue() { +- //this.level.timings.tracker1.startTiming(); // Purpur +- try { +- for (TrackedEntity tracker : this.entityMap.values()) { +- // update tracker entry +- tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); +- } +- } finally { +- //this.level.timings.tracker1.stopTiming(); // Purpur ++ if(this.totalRunning.get() > 0){ ++ return; + } + ++ this.totalRunning.set(2); ++ ++ this.asyncTrackWorker.execute(()->{ ++ //this.level.timings.tracker1.startTiming(); // Purpur ++ try { ++ CompletableFuture.allOf(this.entityMap.values() ++ .stream() ++ .map(tracker -> CompletableFuture.runAsync(()->{ ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ },this.concurrentTrackWorker)) ++ .toArray(CompletableFuture[]::new)).join(); ++ } finally { ++ //this.level.timings.tracker1.stopTiming(); // Purpur ++ this.totalRunning.getAndDecrement(); ++ } ++ }); + +- //this.level.timings.tracker2.startTiming(); // Purpur +- try { +- for (TrackedEntity tracker : this.entityMap.values()) { +- tracker.serverEntity.sendChanges(); ++ this.asyncTrackWorker.execute(()->{ ++ //this.level.timings.tracker2.startTiming(); // Purpur ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.sendChanges(); ++ } ++ } finally { ++ //this.level.timings.tracker2.stopTiming(); // Purpur ++ this.totalRunning.getAndDecrement(); + } +- } finally { +- //this.level.timings.tracker2.stopTiming(); // Purpur +- } ++ }); + } + // Paper end - optimised tracker + + protected void tick() { + // Paper start - optimized tracker +- if (true) { +- this.processTrackQueue(); +- return; +- } ++ this.processTrackQueue(); + // Paper end - optimized tracker +- List list = Lists.newArrayList(); +- List list1 = this.level.players(); +- Iterator objectiterator = this.entityMap.values().iterator(); +- //level.timings.tracker1.startTiming(); // Paper // Purpur +- +- ChunkMap.TrackedEntity playerchunkmap_entitytracker; +- +- while (objectiterator.hasNext()) { +- playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- SectionPos sectionposition = playerchunkmap_entitytracker.lastSectionPos; +- SectionPos sectionposition1 = SectionPos.of((EntityAccess) playerchunkmap_entitytracker.entity); +- boolean flag = !Objects.equals(sectionposition, sectionposition1); +- +- if (flag) { +- playerchunkmap_entitytracker.updatePlayers(list1); +- Entity entity = playerchunkmap_entitytracker.entity; +- +- if (entity instanceof ServerPlayer) { +- list.add((ServerPlayer) entity); +- } +- +- playerchunkmap_entitytracker.lastSectionPos = sectionposition1; +- } +- +- if (flag || this.distanceManager.inEntityTickingRange(sectionposition1.chunk().toLong())) { +- playerchunkmap_entitytracker.serverEntity.sendChanges(); +- } +- } +- //level.timings.tracker1.stopTiming(); // Paper // Purpur +- +- if (!list.isEmpty()) { +- objectiterator = this.entityMap.values().iterator(); +- +- //level.timings.tracker2.startTiming(); // Paper // Purpur +- while (objectiterator.hasNext()) { +- playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- playerchunkmap_entitytracker.updatePlayers(list); +- } +- //level.timings.tracker2.stopTiming(); // Paper // Purpur +- } +- + } + + public void broadcast(Entity entity, Packet packet) { +@@ -1446,7 +1428,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl ++ public final Set seenBy = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Paper - optimise map impl //Hearse - multithread tracker + + public TrackedEntity(Entity entity, int i, int j, boolean flag) { + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit +@@ -1464,12 +1446,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + if (newTrackerCandidates != null) { + Object[] rawData = newTrackerCandidates.getBackingSet(); +- for (int i = 0, len = rawData.length; i < len; ++i) { +- Object raw = rawData[i]; ++ for (Object raw : rawData) { + if (!(raw instanceof ServerPlayer)) { + continue; + } +- ServerPlayer player = (ServerPlayer)raw; ++ ServerPlayer player = (ServerPlayer) raw; + this.updatePlayer(player); + } + } +@@ -1500,14 +1481,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcast(Packet packet) { +- Iterator iterator = this.seenBy.iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next(); +- ++ for (ServerPlayerConnection serverplayerconnection : this.seenBy) { + serverplayerconnection.send(packet); + } +- + } + + public void broadcastAndSend(Packet packet) { +@@ -1519,14 +1495,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcastRemoved() { +- Iterator iterator = this.seenBy.iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next(); +- ++ for (ServerPlayerConnection serverplayerconnection : this.seenBy) { + this.serverEntity.removePairing(serverplayerconnection.getPlayer()); + } +- + } + + public void removePlayer(ServerPlayer player) { +@@ -1534,7 +1505,6 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + } +- + } + + public void updatePlayer(ServerPlayer player) { +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 50cf4d200bc2892f2140c9929193b4b20ad2bd17..afccc8e6d6a6f8d324a58570c7c2245544516e7b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -3,14 +3,12 @@ package net.minecraft.server.level; + import com.google.common.collect.Lists; + import com.mojang.datafixers.util.Pair; + import com.mojang.logging.LogUtils; +-import java.util.Collection; +-import java.util.Collections; +-import java.util.Iterator; +-import java.util.List; +-import java.util.Objects; +-import java.util.Set; ++ ++import java.util.*; + import java.util.function.Consumer; + import javax.annotation.Nullable; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArraySet; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +@@ -373,7 +371,7 @@ public class ServerEntity { + } + + if (this.entity instanceof LivingEntity) { +- Set set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes(); ++ List set = new ArrayList<>(((LivingEntity) this.entity).getAttributes().getDirtyAttributes()); + + if (!set.isEmpty()) { + // CraftBukkit start - Send scaled max health +@@ -394,6 +392,5 @@ public class ServerEntity { + if (this.entity instanceof ServerPlayer) { + ((ServerPlayer) this.entity).connection.send(packet); + } +- + } + } diff --git a/patches/server/0064-Hearse-Some-concurrent-fixes.patch b/patches/server/0064-Hearse-Some-concurrent-fixes.patch new file mode 100644 index 00000000..07074d83 --- /dev/null +++ b/patches/server/0064-Hearse-Some-concurrent-fixes.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 14:39:17 +0800 +Subject: [PATCH] Hearse: Some concurrent fixes + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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..b15b47041ad891deca9ff9b3bc6d196598f27a68 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,7 +30,7 @@ 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() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java b/src/main/java/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java +index d59857e9db945d5b659153e55dafa2d91c388458..3231a17627ab0d88d4a83371033dfd228c5169bc 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java ++++ b/src/main/java/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java +@@ -1,10 +1,12 @@ + package net.minecraft.world.entity.ai.memory; + +-import com.google.common.collect.Iterables; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMap; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMaps; + import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; + import java.util.List; + import java.util.Optional; + import java.util.function.Predicate; ++import java.util.stream.Collectors; + import java.util.stream.Stream; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.sensing.Sensor; +@@ -16,20 +18,14 @@ public class NearestVisibleLivingEntities { + + private NearestVisibleLivingEntities() { + this.nearbyEntities = List.of(); +- this.lineOfSightTest = (entity) -> { +- return false; +- }; ++ this.lineOfSightTest = (entity) -> false; + } + + public NearestVisibleLivingEntities(LivingEntity owner, List entities) { + this.nearbyEntities = entities; +- Object2BooleanOpenHashMap object2BooleanOpenHashMap = new Object2BooleanOpenHashMap<>(entities.size()); +- Predicate predicate = (entity) -> { +- return Sensor.isEntityTargetable(owner, entity); +- }; +- this.lineOfSightTest = (entity) -> { +- return object2BooleanOpenHashMap.computeIfAbsent(entity, predicate); +- }; ++ Object2BooleanMap object2BooleanOpenHashMap = Object2BooleanMaps.synchronize(new Object2BooleanOpenHashMap<>(entities.size())); ++ Predicate predicate = (entity) -> Sensor.isEntityTargetable(owner, entity); ++ this.lineOfSightTest = (entity) -> object2BooleanOpenHashMap.computeIfAbsent(entity, predicate); + } + + public static NearestVisibleLivingEntities empty() { +@@ -47,15 +43,11 @@ public class NearestVisibleLivingEntities { + } + + public Iterable findAll(Predicate predicate) { +- return Iterables.filter(this.nearbyEntities, (entity) -> { +- return predicate.test(entity) && this.lineOfSightTest.test(entity); +- }); ++ return this.nearbyEntities.stream().filter((entity) -> predicate.test(entity) && this.lineOfSightTest.test(entity)).collect(Collectors.toList()); + } + + public Stream find(Predicate predicate) { +- return this.nearbyEntities.stream().filter((entity) -> { +- return predicate.test(entity) && this.lineOfSightTest.test(entity); +- }); ++ return this.nearbyEntities.stream().filter((entity) -> predicate.test(entity) && this.lineOfSightTest.test(entity)); + } + + public boolean contains(LivingEntity entity) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +index 714cc41d6358ea57a23e2e663422fdba28efcf4d..0d0ca5d29f32286cb0609a1570a64731380f270c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +@@ -8,6 +8,7 @@ import java.util.Set; + import java.util.concurrent.atomic.AtomicBoolean; + import java.util.concurrent.atomic.AtomicInteger; + ++import com.google.common.collect.Lists; + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.server.level.ServerLevel; +@@ -34,7 +35,8 @@ public class NearestLivingEntitySensor extends Sensor + this.entitiesCache.sort(Comparator.comparingDouble(compareCache::distanceToSqr)); + + Brain brain = entity.getBrain(); +- final List list = this.entitiesCache.stream().map(EntityPositionCache::getCurrentEntity).toList(); ++ final List list = Lists.newCopyOnWriteArrayList(); ++ list.addAll(this.entitiesCache.stream().map(EntityPositionCache::getCurrentEntity).toList()); + brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES,list); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(entity, list)); + }finally { diff --git a/patches/server/0065-Hearse-Use-old-refacted-EntityLookup.patch b/patches/server/0065-Hearse-Use-old-refacted-EntityLookup.patch new file mode 100644 index 00000000..11d72fca --- /dev/null +++ b/patches/server/0065-Hearse-Use-old-refacted-EntityLookup.patch @@ -0,0 +1,399 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 16:57:32 +0800 +Subject: [PATCH] Hearse: Use old refacted EntityLookup + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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 09a85daaf8cb40b49fe264e999778b49160fe12a..387d07868301877dd7fca5d8dfd21e1331f4793e 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 +@@ -2,31 +2,45 @@ package io.papermc.paper.chunk.system.entity; + + import com.destroystokyo.paper.util.maplist.EntityList; + import com.mojang.logging.LogUtils; +-import io.papermc.paper.chunk.system.ChunkSystem; + 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.Int2ReferenceArrayMap; ++import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; ++import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +-import it.unimi.dsi.fastutil.objects.ObjectArrayList; +-import it.unimi.dsi.fastutil.objects.ObjectLists; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceArrayMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; + import net.minecraft.core.BlockPos; ++import io.papermc.paper.chunk.system.ChunkSystem; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.AbortableIterationConsumer; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +-import net.minecraft.world.level.entity.*; ++import net.minecraft.world.level.entity.EntityInLevelCallback; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.level.entity.LevelCallback; ++import net.minecraft.world.level.entity.LevelEntityGetter; ++import net.minecraft.world.level.entity.Visibility; + import net.minecraft.world.phys.AABB; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + import org.slf4j.Logger; +- +-import java.util.*; +-import java.util.concurrent.locks.ReadWriteLock; +-import java.util.concurrent.locks.ReentrantReadWriteLock; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.NoSuchElementException; ++import java.util.UUID; ++import java.util.concurrent.locks.Lock; ++import java.util.concurrent.locks.ReentrantLock; ++import java.util.concurrent.locks.StampedLock; + import java.util.function.Consumer; + import java.util.function.Predicate; + +@@ -40,14 +54,16 @@ public final class EntityLookup implements LevelEntityGetter { + + public final ServerLevel world; + +- private final ReadWriteLock chunkEntitySlicesLock = new ReentrantReadWriteLock(); ++ private final StampedLock entityByLock = new StampedLock(); ++ private final Lock regionLoadLock = new ReentrantLock(true); + +- protected final Long2ObjectMap regions = new Long2ObjectArrayMap<>(); ++ protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectArrayMap<>()); + + private final int minSection; // inclusive + private final int maxSection; // inclusive + private final LevelCallback worldCallback; +- private final List addedEntities = ObjectLists.synchronize(new ObjectArrayList<>()); ++ private final Int2ReferenceMap entityById = new Int2ReferenceArrayMap<>(); ++ private final Object2ReferenceMap entityByUUID = new Object2ReferenceArrayMap<>(); + private final EntityList accessibleEntities = new EntityList(); + + public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { +@@ -68,23 +84,52 @@ public final class EntityLookup implements LevelEntityGetter { + @Nullable + @Override + public Entity get(final int id) { +- for (Entity entity : this.addedEntities){ +- if (id == entity.getId()){ +- return entity; ++ final long attempt = this.entityByLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final Entity ret = this.entityById.get(id); ++ ++ if (this.entityByLock.validate(attempt)) { ++ return maskNonAccessible(ret); ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore + } + } +- return null; ++ ++ this.entityByLock.readLock(); ++ try { ++ return maskNonAccessible(this.entityById.get(id)); ++ } finally { ++ this.entityByLock.tryUnlockRead(); ++ } + } + + @Nullable + @Override + public Entity get(final UUID id) { +- for (Entity entity : this.addedEntities){ +- if (entity.getUUID().equals(id)){ +- return entity; ++ final long attempt = this.entityByLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final Entity ret = this.entityByUUID.get(id); ++ if (this.entityByLock.validate(attempt)) { ++ return maskNonAccessible(ret); ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore + } + } +- return null; ++ ++ this.entityByLock.readLock(); ++ try { ++ return maskNonAccessible(this.entityByUUID.get(id)); ++ } finally { ++ this.entityByLock.tryUnlockRead(); ++ } + } + + public boolean hasEntity(final UUID uuid) { +@@ -92,14 +137,7 @@ public final class EntityLookup implements LevelEntityGetter { + } + + public String getDebugInfo() { +- int reginSize; +- this.chunkEntitySlicesLock.readLock().lock(); +- try { +- reginSize = this.regions.size(); +- }finally { +- this.chunkEntitySlicesLock.readLock().unlock(); +- } +- return "count:" + this.addedEntities.size() +",region_count:" + reginSize; ++ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size(); + } + + static final class ArrayIterable implements Iterable { +@@ -136,12 +174,12 @@ public final class EntityLookup implements LevelEntityGetter { + } + + @Override +- public boolean hasNext() { ++ public synchronized boolean hasNext() { + return this.off < this.length; + } + + @Override +- public T next() { ++ public synchronized T next() { + if (this.off >= this.length) { + throw new NoSuchElementException(); + } +@@ -162,7 +200,7 @@ public final class EntityLookup implements LevelEntityGetter { + + @Override + public void get(final EntityTypeTest filter, final AbortableIterationConsumer action) { +- for (final Entity entity : this.addedEntities) { ++ for (final Entity entity : this.entityById.values()) { + final Visibility visibility = EntityLookup.getEntityStatus(entity); + if (!visibility.isAccessible()) { + continue; +@@ -299,17 +337,26 @@ public final class EntityLookup implements LevelEntityGetter { + } + } + +- if (this.addedEntities.contains(entity)) { +- LOGGER.warn("Entity already exists: " + entity); ++ this.entityByLock.writeLock(); ++ try { ++ if (this.entityById.containsKey(entity.getId())) { ++ LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity); + return false; + } +- this.addedEntities.add(entity); ++ if (this.entityByUUID.containsKey(entity.getUUID())) { ++ LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity); ++ return false; ++ } ++ this.entityById.put(entity.getId(), entity); ++ this.entityByUUID.put(entity.getUUID(), entity); ++ } finally { ++ this.entityByLock.tryUnlockWrite(); ++ } + + entity.sectionX = sectionX; + entity.sectionY = sectionY; + entity.sectionZ = sectionZ; +- ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); +- ++ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); + if (!slices.addEntity(entity, sectionY)) { + LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); + } +@@ -329,7 +376,14 @@ public final class EntityLookup implements LevelEntityGetter { + if (!entity.isRemoved()) { + throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); + } +- ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); ++ ChunkEntitySlices slices; ++ this.regionLoadLock.lock(); ++ try { ++ slices = this.getChunk(sectionX, sectionZ); ++ }finally { ++ this.regionLoadLock.unlock(); ++ } ++ + // all entities should be in a chunk + if (slices == null) { + LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); +@@ -340,9 +394,17 @@ public final class EntityLookup implements LevelEntityGetter { + } + entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; + +- if (!this.addedEntities.remove(entity)) { +- LOGGER.warn("Failed to remove entity " + entity); ++ this.entityByLock.writeLock(); ++ try { ++ if (!this.entityById.remove(entity.getId(), entity)) { ++ LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId())); ++ } ++ if (!this.entityByUUID.remove(entity.getUUID(), entity)) { ++ LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID())); + } ++ } finally { ++ this.entityByLock.tryUnlockWrite(); ++ } + } + + private ChunkEntitySlices moveEntity(final Entity entity) { +@@ -364,8 +426,16 @@ public final class EntityLookup implements LevelEntityGetter { + // ensure the old section is owned by this tick thread + TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); + +- ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); +- ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); ++ ChunkEntitySlices old; ++ ++ this.regionLoadLock.lock(); ++ try { ++ old = this.getChunk(entity.sectionX, entity.sectionZ); ++ }finally { ++ this.regionLoadLock.unlock(); ++ } ++ ++ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); + + if (!old.removeEntity(entity, entity.sectionY)) { + LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); +@@ -581,18 +651,20 @@ public final class EntityLookup implements LevelEntityGetter { + + public void entitySectionLoad(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { + TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); +- final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); +- this.chunkEntitySlicesLock.writeLock().lock(); +- try { +- if (curr != null) { +- this.removeChunk(chunkX, chunkZ); +- curr.mergeInto(slices); +- this.addChunk(chunkX, chunkZ, slices); +- } else { +- this.addChunk(chunkX, chunkZ, slices); ++ synchronized (this) { ++ final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); ++ this.regionLoadLock.lock(); ++ try { ++ if (curr != null) { ++ this.removeChunk(chunkX, chunkZ); ++ curr.mergeInto(slices); ++ this.addChunk(chunkX, chunkZ, slices); ++ } else { ++ this.addChunk(chunkX, chunkZ, slices); ++ } ++ } finally { ++ this.regionLoadLock.unlock(); + } +- } finally { +- this.chunkEntitySlicesLock.writeLock().unlock(); + } + } + +@@ -607,6 +679,7 @@ public final class EntityLookup implements LevelEntityGetter { + if (region == null) { + return null; + } ++ + return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); + } + +@@ -623,46 +696,32 @@ public final class EntityLookup implements LevelEntityGetter { + + public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { + final long key = CoordinateUtils.getChunkKey(regionX, regionZ); +- this.chunkEntitySlicesLock.readLock().lock(); +- try { +- return this.regions.get(key); +- }finally { +- this.chunkEntitySlicesLock.readLock().unlock(); +- } ++ return this.regions.get(key); + } + +- private void removeChunk(final int chunkX, final int chunkZ) { ++ private synchronized void removeChunk(final int chunkX, final int chunkZ) { + final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); + +- this.chunkEntitySlicesLock.writeLock().lock(); +- try { +- final ChunkSlicesRegion region = this.regions.get(key); +- final int remaining = region.remove(relIndex); +- if (remaining == 0) { +- this.regions.remove(key); +- } +- }finally { +- this.chunkEntitySlicesLock.writeLock().unlock(); ++ final ChunkSlicesRegion region = this.regions.get(key); ++ final int remaining = region.remove(relIndex); ++ ++ if (remaining == 0) { ++ this.regions.remove(key); + } + } + +- public void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { ++ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { + final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); + +- this.chunkEntitySlicesLock.writeLock().lock(); +- try { +- ChunkSlicesRegion region = this.regions.get(key); +- if (region != null) { +- region.add(relIndex, slices); +- } else { +- region = new ChunkSlicesRegion(); +- region.add(relIndex, slices); +- this.regions.put(key, region); +- } +- } finally { +- this.chunkEntitySlicesLock.writeLock().unlock(); ++ ChunkSlicesRegion region = this.regions.get(key); ++ if (region != null) { ++ region.add(relIndex, slices); ++ } else { ++ region = new ChunkSlicesRegion(); ++ region.add(relIndex, slices); ++ this.regions.put(key, region); + } + } + +@@ -671,11 +730,11 @@ public final class EntityLookup implements LevelEntityGetter { + protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; + protected int sliceCount; + +- public synchronized ChunkEntitySlices get(final int index) { ++ public ChunkEntitySlices get(final int index) { + return this.slices[index]; + } + +- public synchronized int remove(final int index) { ++ public int remove(final int index) { + final ChunkEntitySlices slices = this.slices[index]; + if (slices == null) { + throw new IllegalStateException(); +@@ -686,7 +745,7 @@ public final class EntityLookup implements LevelEntityGetter { + return --this.sliceCount; + } + +- public synchronized void add(final int index, final ChunkEntitySlices slices) { ++ public void add(final int index, final ChunkEntitySlices slices) { + final ChunkEntitySlices curr = this.slices[index]; + if (curr != null) { + throw new IllegalStateException(); diff --git a/patches/server/0066-Hearse-Fix-some-sort-problems-in-some-sensors.patch b/patches/server/0066-Hearse-Fix-some-sort-problems-in-some-sensors.patch new file mode 100644 index 00000000..d424a3bf --- /dev/null +++ b/patches/server/0066-Hearse-Fix-some-sort-problems-in-some-sensors.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 19:58:12 +0800 +Subject: [PATCH] Hearse: Fix some sort problems in some sensors + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index 2c4517850a9692f1c2b1332b58e8312fe1166772..bee1aa7eded53b3302f39053bfd9c4af5f3008c3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -27,12 +27,11 @@ public class NearestItemSensor extends Sensor { + List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0D, 16.0D, 32.0D), (itemEntity) -> { + return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities + }); +- list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. ++ list.sort(Comparator.comparingDouble(entity::distanceToSqr)); // better to take the sort perf hit than using line of sight more than we need to. + // Paper start - remove streams + // Paper start - remove streams in favour of lists + ItemEntity nearest = null; +- for (int i = 0; i < list.size(); i++) { +- ItemEntity entityItem = list.get(i); ++ for (ItemEntity entityItem : list) { + if (entity.hasLineOfSight(entityItem)) { + // Paper end - remove streams + nearest = entityItem; +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 75d9c4f011b5a97def215784c92bb57bbb35d06b..af6dcbd8f531705c356780cc79aa1868a10cfaf9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -1,11 +1,14 @@ + package net.minecraft.world.entity.ai.sensing; + ++import co.earthme.hearse.util.EntityPositionCache; + import com.google.common.collect.ImmutableSet; +-import java.util.Comparator; +-import java.util.List; +-import java.util.Optional; +-import java.util.Set; ++ ++import java.util.*; ++import java.util.concurrent.atomic.AtomicBoolean; + import java.util.stream.Collectors; ++ ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; ++import it.unimi.dsi.fastutil.objects.ObjectLists; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.EntitySelector; + import net.minecraft.world.entity.LivingEntity; +@@ -14,6 +17,9 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType; + import net.minecraft.world.entity.player.Player; + + public class PlayerSensor extends Sensor { ++ private final List playerList = ObjectLists.synchronize(new ObjectArrayList<>()); ++ private final AtomicBoolean calling = new AtomicBoolean(); ++ + @Override + public Set> requires() { + return ImmutableSet.of(MemoryModuleType.NEAREST_PLAYERS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER); +@@ -21,30 +27,51 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, LivingEntity entity) { +- // Paper start - remove streams +- List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); +- players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); +- Brain brain = entity.getBrain(); +- +- brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); +- +- Player firstTargetable = null; +- Player firstAttackable = null; +- for (int index = 0, len = players.size(); index < len; ++index) { +- Player player = players.get(index); +- if (firstTargetable == null && isEntityTargetable(entity, player)) { +- firstTargetable = player; +- } +- if (firstAttackable == null && isEntityAttackable(entity, player)) { +- firstAttackable = player; +- } ++ if (this.calling.get()){ ++ return; ++ } ++ ++ this.calling.set(true); ++ try { ++ // Paper start - remove streams ++ List playersPosCaches = new ArrayList<>(List.of(world ++ .getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS) ++ .stream() ++ .map(EntityPositionCache::new) ++ .toArray(EntityPositionCache[]::new))); ++ ++ final EntityPositionCache entityPositionCache = new EntityPositionCache(entity); ++ ++ playersPosCaches.sort(Comparator.comparingDouble(entityPositionCache::distanceToSqr)); ++ ++ final List players = playersPosCaches ++ .stream() ++ .map(cache -> ((Player) cache.getCurrentEntity())) ++ .toList(); ++ ++ Brain brain = entity.getBrain(); ++ ++ brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); ++ ++ Player firstTargetable = null; ++ Player firstAttackable = null; ++ for (Player player : players) { ++ if (firstTargetable == null && isEntityTargetable(entity, player)) { ++ firstTargetable = player; ++ } ++ if (firstAttackable == null && isEntityAttackable(entity, player)) { ++ firstAttackable = player; ++ } + +- if (firstAttackable != null && firstTargetable != null) { +- break; ++ if (firstAttackable != null && firstTargetable != null) { ++ break; ++ } + } ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); ++ // Paper end - remove streams ++ }finally { ++ this.calling.set(false); + } +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); +- // Paper end - remove streams + } + } diff --git a/patches/server/0067-Hearse-Add-config-for-multithreaded-tracker.patch b/patches/server/0067-Hearse-Add-config-for-multithreaded-tracker.patch new file mode 100644 index 00000000..d1dec2e7 --- /dev/null +++ b/patches/server/0067-Hearse-Add-config-for-multithreaded-tracker.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 20:08:55 +0800 +Subject: [PATCH] Hearse: Add config for multithreaded tracker + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0f18f7ce182e35ee6f3f385553e1bdb5ee4ad587..cf97584215e4b0fcb3b4b92942eaf1f6c8682f54 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; + import com.google.common.collect.*; +@@ -61,6 +62,7 @@ import java.io.Writer; + import java.nio.file.Path; + import java.util.*; + import java.util.concurrent.*; ++import java.util.concurrent.atomic.AtomicBoolean; + import java.util.concurrent.atomic.AtomicInteger; + import java.util.function.*; + +@@ -1197,18 +1199,43 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new DefaultWorkerFactory("tracker-async")); +- private final Executor concurrentTrackWorker = new WorkerThreadPoolExecutor( +- Runtime.getRuntime().availableProcessors(), +- Runtime.getRuntime().availableProcessors(), +- 5L, +- TimeUnit.SECONDS, +- new LinkedBlockingQueue<>(), +- new DefaultWorkerFactory("tracker-concurrent")); +- +- private final AtomicInteger totalRunning = new AtomicInteger(); ++ private static Executor concurrentTrackWorker = null; ++ private final AtomicInteger totalRunning = new AtomicInteger(0); ++ private static final AtomicBoolean isInited = new AtomicBoolean(false); ++ private static final AtomicBoolean enabled = new AtomicBoolean(); ++ ++ public static void tryInitIfNotInited(){ ++ if (!isInited.get()){ ++ enabled.set(HearseConfig.getBoolean("optimizations.enable-multithreaded-tracker",true)); ++ if (enabled.get()){ ++ final int threadCount = HearseConfig.getInt("optimizations.multithreaded-tracker-thread-count",Runtime.getRuntime().availableProcessors()); ++ concurrentTrackWorker = new WorkerThreadPoolExecutor( ++ threadCount, ++ threadCount, ++ 5L, ++ TimeUnit.SECONDS, ++ new LinkedBlockingQueue<>(), ++ new DefaultWorkerFactory("tracker-concurrent")); ++ } ++ isInited.set(true); ++ } ++ } + + // Paper start - optimised tracker + private final void processTrackQueue() { ++ tryInitIfNotInited(); ++ ++ if (!enabled.get()){ ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ } ++ ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.sendChanges(); ++ } ++ return; ++ } ++ + if(this.totalRunning.get() > 0){ + return; + } +@@ -1222,7 +1249,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + .stream() + .map(tracker -> CompletableFuture.runAsync(()->{ + tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); +- },this.concurrentTrackWorker)) ++ },concurrentTrackWorker)) + .toArray(CompletableFuture[]::new)).join(); + } finally { + //this.level.timings.tracker1.stopTiming(); // Purpur diff --git a/patches/server/0068-Hearse-Correct-some-config-key-name.patch b/patches/server/0068-Hearse-Correct-some-config-key-name.patch new file mode 100644 index 00000000..1cf24dc4 --- /dev/null +++ b/patches/server/0068-Hearse-Correct-some-config-key-name.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 20:32:12 +0800 +Subject: [PATCH] Hearse: Correct some config key name + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index cf97584215e4b0fcb3b4b92942eaf1f6c8682f54..bae79fd5d9e2bc3ce2386ac8f8e1c14deba94a8f 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1208,7 +1208,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (!isInited.get()){ + enabled.set(HearseConfig.getBoolean("optimizations.enable-multithreaded-tracker",true)); + if (enabled.get()){ +- final int threadCount = HearseConfig.getInt("optimizations.multithreaded-tracker-thread-count",Runtime.getRuntime().availableProcessors()); ++ final int threadCount = HearseConfig.getInt("workers.multithreaded-tracker-thread-count",Runtime.getRuntime().availableProcessors()); + concurrentTrackWorker = new WorkerThreadPoolExecutor( + threadCount, + threadCount, diff --git a/patches/server/0069-Hearse-forkjoinworker-can-not-skip-the-async-check.patch b/patches/server/0069-Hearse-forkjoinworker-can-not-skip-the-async-check.patch new file mode 100644 index 00000000..97015256 --- /dev/null +++ b/patches/server/0069-Hearse-forkjoinworker-can-not-skip-the-async-check.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 21:17:41 +0800 +Subject: [PATCH] Hearse: forkjoinworker can not skip the async check + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java +index fc57850b80303fcade89ca95794f63910404a407..ea277170975f59561775ad9b63467a7c9abdbbe3 100644 +--- a/src/main/java/io/papermc/paper/util/TickThread.java ++++ b/src/main/java/io/papermc/paper/util/TickThread.java +@@ -1,5 +1,6 @@ + package io.papermc.paper.util; + ++import co.earthme.hearse.concurrent.thread.Worker; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; +@@ -74,14 +75,14 @@ public class TickThread extends Thread { + } + + public static boolean isTickThread() { +- return Thread.currentThread() instanceof TickThread; ++ return Thread.currentThread() instanceof TickThread || Thread.currentThread() instanceof Worker; + } + + public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) { +- return Thread.currentThread() instanceof TickThread; ++ return Thread.currentThread() instanceof TickThread || Thread.currentThread() instanceof Worker; + } + + public static boolean isTickThreadFor(final Entity entity) { +- return Thread.currentThread() instanceof TickThread; ++ return Thread.currentThread() instanceof TickThread || Thread.currentThread() instanceof Worker; + } + } diff --git a/patches/server/0070-Hearse-Add-NotNull-annotation-to-EntityPositionCache.patch b/patches/server/0070-Hearse-Add-NotNull-annotation-to-EntityPositionCache.patch new file mode 100644 index 00000000..53e3d091 --- /dev/null +++ b/patches/server/0070-Hearse-Add-NotNull-annotation-to-EntityPositionCache.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 21:18:06 +0800 +Subject: [PATCH] Hearse: Add NotNull annotation to EntityPositionCache + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/util/EntityPositionCache.java b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +index b7ec86d5cb2bb8d62d1a54f5e7b394e992a2b870..6f34233901cf1943694224ab393dea5548cb8e5b 100644 +--- a/src/main/java/co/earthme/hearse/util/EntityPositionCache.java ++++ b/src/main/java/co/earthme/hearse/util/EntityPositionCache.java +@@ -3,6 +3,7 @@ package co.earthme.hearse.util; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.phys.Vec3; ++import org.jetbrains.annotations.NotNull; + + public class EntityPositionCache { + private final double x; +@@ -10,7 +11,7 @@ public class EntityPositionCache { + private final double z; + private final LivingEntity currentEntity; + +- public EntityPositionCache(LivingEntity entity){ ++ public EntityPositionCache(@NotNull LivingEntity entity){ + this.x = entity.getX(); + this.y = entity.getY(); + this.z = entity.getZ(); diff --git a/patches/server/0071-Hearse-Add-tracker-thread-pool-to-the-pool-managemen.patch b/patches/server/0071-Hearse-Add-tracker-thread-pool-to-the-pool-managemen.patch new file mode 100644 index 00000000..b0fb7cad --- /dev/null +++ b/patches/server/0071-Hearse-Add-tracker-thread-pool-to-the-pool-managemen.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Wed, 18 Jan 2023 21:30:18 +0800 +Subject: [PATCH] Hearse: Add tracker thread pool to the pool management list + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index bae79fd5d9e2bc3ce2386ac8f8e1c14deba94a8f..eb0319b961893cc4454196970402b88ced3bce7f 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import co.earthme.hearse.Hearse; + import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; +@@ -1199,7 +1200,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new DefaultWorkerFactory("tracker-async")); +- private static Executor concurrentTrackWorker = null; ++ private static WorkerThreadPoolExecutor concurrentTrackWorker = null; + private final AtomicInteger totalRunning = new AtomicInteger(0); + private static final AtomicBoolean isInited = new AtomicBoolean(false); + private static final AtomicBoolean enabled = new AtomicBoolean(); +@@ -1216,6 +1217,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new DefaultWorkerFactory("tracker-concurrent")); ++ Hearse.getWorkerManager().addWorker("tracker",concurrentTrackWorker); + } + isInited.set(true); + } diff --git a/patches/server/0072-Hearse-Add-locks-when-calling-getChunk-in-worker-thr.patch b/patches/server/0072-Hearse-Add-locks-when-calling-getChunk-in-worker-thr.patch new file mode 100644 index 00000000..c2a26ddb --- /dev/null +++ b/patches/server/0072-Hearse-Add-locks-when-calling-getChunk-in-worker-thr.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Thu, 19 Jan 2023 10:03:10 +0800 +Subject: [PATCH] Hearse: Add locks when calling getChunk in worker thread + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index e311724d2e723115bc9549a61e6206a8aed835d8..b151339cc56418f82f9b75b39b8fc6ab6686993e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import co.earthme.hearse.concurrent.thread.Worker; + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Lists; + import com.mojang.datafixers.DataFixer; +@@ -377,57 +378,68 @@ public class ServerChunkCache extends ChunkSource { + } + // Paper end - async chunk io + ++ private final Object workerGetChunkLock = new Object(); ++ + @Nullable + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { +- final int x1 = x; final int z1 = z; // Paper - conflict on variable change + if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + return (ChunkAccess) CompletableFuture.supplyAsync(() -> { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); + } else { +- // Paper start - optimise for loaded chunks +- LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); +- if (ifLoaded != null) { +- return ifLoaded; ++ if (Thread.currentThread() instanceof Worker){ ++ synchronized (this.workerGetChunkLock){ ++ return this.getChunkUnsafe(x,z,leastStatus,create); ++ } + } +- // Paper end +- //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur ++ return this.getChunkUnsafe(x,z,leastStatus,create); ++ } ++ } + +- //gameprofilerfiller.incrementCounter("getChunk"); // Purpur +- long k = ChunkPos.asLong(x, z); ++ private ChunkAccess getChunkUnsafe(int x,int z,ChunkStatus leastStatus,boolean create){ ++ final int x1 = x; final int z1 = z; // Paper - conflict on variable change ++ // Paper start - optimise for loaded chunks ++ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ // Paper end ++ //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur + +- ChunkAccess ichunkaccess; ++ //gameprofilerfiller.incrementCounter("getChunk"); // Purpur ++ long k = ChunkPos.asLong(x, z); + +- // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system ++ ChunkAccess ichunkaccess; + +- //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur +- CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper +- ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; ++ // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system + +- Objects.requireNonNull(completablefuture); +- if (!completablefuture.isDone()) { // Paper +- // Paper start - async chunk io/loading +- io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system +- // Paper end +- com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info +- //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur +- chunkproviderserver_b.managedBlock(completablefuture::isDone); +- io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system +- //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur +- } // Paper +- ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { +- return ichunkaccess1; +- }, (playerchunk_failure) -> { +- if (create) { +- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + playerchunk_failure)); +- } else { +- return null; +- } +- }); +- this.storeInCache(k, ichunkaccess, leastStatus); +- return ichunkaccess; +- } ++ //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur ++ CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper ++ ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; ++ ++ Objects.requireNonNull(completablefuture); ++ if (!completablefuture.isDone()) { // Paper ++ // Paper start - async chunk io/loading ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system ++ // Paper end ++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info ++ //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur ++ chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system ++ //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur ++ } // Paper ++ ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { ++ return ichunkaccess1; ++ }, (playerchunk_failure) -> { ++ if (create) { ++ throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + playerchunk_failure)); ++ } else { ++ return null; ++ } ++ }); ++ this.storeInCache(k, ichunkaccess, leastStatus); ++ return ichunkaccess; + } + + @Nullable +@@ -440,6 +452,7 @@ public class ServerChunkCache extends ChunkSource { + } + } + ++ + private void clearCache() { + Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS); + Arrays.fill(this.lastChunkStatus, (Object) null); diff --git a/patches/server/0073-Hearse-Change-back-some-collections-in-CollectingNei.patch b/patches/server/0073-Hearse-Change-back-some-collections-in-CollectingNei.patch new file mode 100644 index 00000000..8a6ee50d --- /dev/null +++ b/patches/server/0073-Hearse-Change-back-some-collections-in-CollectingNei.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Thu, 19 Jan 2023 10:04:04 +0800 +Subject: [PATCH] Hearse: Change back some collections in + CollectingNeighborUpdater because we already have lock to ensure thread safe + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +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 a1ff442357dfea868c319fd3c10ae28e6fb81956..5bb3ef743fd2c0e0ac69e340355acbf49e4c862b 100644 +--- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.redstone; + ++import com.google.common.collect.Lists; + import com.mojang.logging.LogUtils; + import java.util.ArrayDeque; + import java.util.ArrayList; +@@ -20,8 +21,8 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + private static final Logger LOGGER = LogUtils.getLogger(); + private final Level level; + private final int maxChainedNeighborUpdates; +- private final Deque stack = new ConcurrentLinkedDeque<>(); +- private final List addedThisLayer = new CopyOnWriteArrayList<>(); ++ private final Deque stack = new ArrayDeque<>(); ++ private final List addedThisLayer = Lists.newArrayList(); + private int count = 0; + + public CollectingNeighborUpdater(Level world, int maxChainDepth) { diff --git a/patches/server/0074-Hearse-Port-some-C2ME-s-fixes-and-fix-a-concurrent-p.patch b/patches/server/0074-Hearse-Port-some-C2ME-s-fixes-and-fix-a-concurrent-p.patch new file mode 100644 index 00000000..55cd95bb --- /dev/null +++ b/patches/server/0074-Hearse-Port-some-C2ME-s-fixes-and-fix-a-concurrent-p.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Sat, 21 Jan 2023 08:46:23 +0800 +Subject: [PATCH] Hearse: Port some C2ME's fixes and fix a concurrent problem + in ChunkMap + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index eb0319b961893cc4454196970402b88ced3bce7f..3cc0b79d94784a2c6895336ee1000b70b9278f4d 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1457,7 +1457,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Paper - optimise map impl //Hearse - multithread tracker ++ public final Set seenBy = Sets.newConcurrentHashSet(); // Paper - optimise map impl //Hearse - multithread tracker + + public TrackedEntity(Entity entity, int i, int j, boolean flag) { + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index b151339cc56418f82f9b75b39b8fc6ab6686993e..e73a7685cc78af407131388acff62c3427fd4618 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.level; + ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; + import co.earthme.hearse.concurrent.thread.Worker; + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Lists; +@@ -378,7 +379,7 @@ public class ServerChunkCache extends ChunkSource { + } + // Paper end - async chunk io + +- private final Object workerGetChunkLock = new Object(); ++ private final Object schedulingMutex = new Object(); + + @Nullable + @Override +@@ -388,58 +389,46 @@ public class ServerChunkCache extends ChunkSource { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); + } else { +- if (Thread.currentThread() instanceof Worker){ +- synchronized (this.workerGetChunkLock){ +- return this.getChunkUnsafe(x,z,leastStatus,create); +- } ++ final int x1 = x; final int z1 = z; // Paper - conflict on variable change ++ //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur ++ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ifLoaded != null) { ++ return ifLoaded; + } +- return this.getChunkUnsafe(x,z,leastStatus,create); +- } +- } +- +- private ChunkAccess getChunkUnsafe(int x,int z,ChunkStatus leastStatus,boolean create){ +- final int x1 = x; final int z1 = z; // Paper - conflict on variable change +- // Paper start - optimise for loaded chunks +- LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); +- if (ifLoaded != null) { +- return ifLoaded; +- } +- // Paper end +- //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur +- +- //gameprofilerfiller.incrementCounter("getChunk"); // Purpur +- long k = ChunkPos.asLong(x, z); ++ //gameprofilerfiller.incrementCounter("getChunk"); // Purpur ++ long k = ChunkPos.asLong(x, z); + +- ChunkAccess ichunkaccess; ++ ChunkAccess ichunkaccess; + +- // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system ++ // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system + +- //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur +- CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper +- ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; ++ //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur ++ CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper ++ ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; + +- Objects.requireNonNull(completablefuture); +- if (!completablefuture.isDone()) { // Paper +- // Paper start - async chunk io/loading +- io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system +- // Paper end +- com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info +- //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur +- chunkproviderserver_b.managedBlock(completablefuture::isDone); +- io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system +- //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur +- } // Paper +- ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { +- return ichunkaccess1; +- }, (playerchunk_failure) -> { +- if (create) { +- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + playerchunk_failure)); +- } else { +- return null; +- } +- }); +- this.storeInCache(k, ichunkaccess, leastStatus); +- return ichunkaccess; ++ Objects.requireNonNull(completablefuture); ++ if (!completablefuture.isDone()) { // Paper ++ // Paper start - async chunk io/loading ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system ++ // Paper end ++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info ++ //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur ++ chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system ++ //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur ++ } // Paper ++ ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { ++ return ichunkaccess1; ++ }, (playerchunk_failure) -> { ++ if (create) { ++ throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + playerchunk_failure)); ++ } else { ++ return null; ++ } ++ }); ++ this.storeInCache(k, ichunkaccess, leastStatus); ++ return ichunkaccess; ++ } + } + + @Nullable +@@ -509,11 +498,14 @@ public class ServerChunkCache extends ChunkSource { + } + }; + +- this.level.chunkTaskScheduler.scheduleChunkLoad( +- chunkX, chunkZ, leastStatus, true, +- isUrgent ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, +- complete +- ); ++ //C2ME --general issue fixes ++ synchronized (this.schedulingMutex){ ++ this.level.chunkTaskScheduler.scheduleChunkLoad( ++ chunkX, chunkZ, leastStatus, true, ++ isUrgent ? PrioritisedExecutor.Priority.BLOCKING : PrioritisedExecutor.Priority.NORMAL, ++ complete ++ ); ++ } + + return ret; + } else { diff --git a/patches/server/0075-Hearse-Fix-a-careless-error-in-PlayerChunkLoader.patch b/patches/server/0075-Hearse-Fix-a-careless-error-in-PlayerChunkLoader.patch new file mode 100644 index 00000000..f5b18307 --- /dev/null +++ b/patches/server/0075-Hearse-Fix-a-careless-error-in-PlayerChunkLoader.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Sat, 21 Jan 2023 08:49:00 +0800 +Subject: [PATCH] Hearse: Fix a careless error in PlayerChunkLoader + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +index 028b23f5c23bbfd83498c3e06a56079ceb0798ad..d9cee42da1b097590e627142d3c5dccbc180b5ae 100644 +--- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java ++++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +@@ -527,11 +527,9 @@ public final class PlayerChunkLoader { + // drain entries from wait queue + while ((data1 = this.chunkSendWaitQueue.pollFirst())!=null) { + if (data1.nextChunkSendTarget > time) { ++ this.chunkSendWaitQueue.add(data1); + break; + } +- +- this.chunkSendWaitQueue.pollFirst(); +- + this.chunkSendQueue.add(data1); + } + diff --git a/patches/server/0076-Hearse-Remove-some-fixes-and-await-entity-task.patch b/patches/server/0076-Hearse-Remove-some-fixes-and-await-entity-task.patch new file mode 100644 index 00000000..caeb3657 --- /dev/null +++ b/patches/server/0076-Hearse-Remove-some-fixes-and-await-entity-task.patch @@ -0,0 +1,235 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Sat, 21 Jan 2023 09:49:43 +0800 +Subject: [PATCH] Hearse: Remove some fixes and await entity task + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +index c0d235e7227db0be6c6f753d8a6e13ad2716f798..891a9167e4072d6ce5b526b1676d8ecb836c8733 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java +@@ -5,37 +5,37 @@ import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.concurrent.WorkerThreadFactory; + import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor; + import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory; +-import co.earthme.hearse.util.ArrayListBlockingQueue; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; +-import net.minecraft.world.entity.player.Player; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import java.util.concurrent.LinkedBlockingQueue; + import java.util.concurrent.RejectedExecutionException; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.locks.LockSupport; + + public class ServerEntityTickHook { + private static final Logger logger = LogManager.getLogger(); +- private static volatile boolean firstTick = false; + private static final WorkerThreadFactory defFactory = new DefaultWorkerFactory("entity"); +- private static final AtomicInteger threadId = new AtomicInteger(); + private static WorkerThreadPoolExecutor worker; +- private static boolean asyncEntityEnabled; ++ private static boolean asyncEntityEnabled = true; ++ private static boolean awaitEntityTasks = true; ++ private static final AtomicInteger taskCounter = new AtomicInteger(0); + +- public static void executeAsyncTask(Runnable task){ +- if (!asyncEntityEnabled){ +- throw new RejectedExecutionException(); ++ //To prevent the chunk system error.This is necessary ++ public static void awaitEntityTasks(){ ++ while (taskCounter.get() > 0){ ++ LockSupport.parkNanos("Await entities",1000000); + } +- worker.execute(task); + } + + public static void init(){ +- boolean asyncEntityEnabled1 = HearseConfig.getBoolean("optimizations.enable-async-entity",true); +- final int workerCount = HearseConfig.getInt("workers.async-entity-worker-count",Runtime.getRuntime().availableProcessors()); +- if (asyncEntityEnabled1){ ++ asyncEntityEnabled = HearseConfig.getBoolean("optimizations.enable-parallel-entity",asyncEntityEnabled); ++ awaitEntityTasks = HearseConfig.getBoolean("optimizations.await-parallel-entity-tasks",awaitEntityTasks); ++ if (asyncEntityEnabled){ ++ final int workerCount = HearseConfig.getInt("workers.async-entity-worker-count",Runtime.getRuntime().availableProcessors()); + worker = new WorkerThreadPoolExecutor( + workerCount, + workerCount, +@@ -46,47 +46,33 @@ public class ServerEntityTickHook { + ); + Hearse.getWorkerManager().addWorker("entity",worker); + } +- asyncEntityEnabled = asyncEntityEnabled1; +- } +- +- public static void executeAsyncTaskWithMainThreadCallback(Runnable task,Runnable callBack){ +- if (!asyncEntityEnabled){ +- throw new IllegalStateException(); +- } +- worker.executeWithSubTask(task,callBack); +- } +- +- public static void callTickStart(){ +- if (!firstTick){ +- firstTick = true; +- return; +- } +- if (!asyncEntityEnabled){ +- return; +- } +- worker.runAllSubTasks(); + } + + public static void callAsyncEntityTick(Entity entity, ServerLevel level){ + MinecraftServer.getServer().executeMidTickTasks(); ++ taskCounter.getAndIncrement(); + Runnable task = ()->{ +- entity.activatedPriorityReset = false; +- if (!entity.isRemoved()) { +- entity.checkDespawn(); +- Entity entity1 = entity.getVehicle(); +- if (entity1 != null) { +- if (!entity1.isRemoved() && entity1.hasPassenger(entity)) { +- return; ++ try { ++ entity.activatedPriorityReset = false; ++ if (!entity.isRemoved()) { ++ entity.checkDespawn(); ++ Entity entity1 = entity.getVehicle(); ++ if (entity1 != null) { ++ if (!entity1.isRemoved() && entity1.hasPassenger(entity)) { ++ return; ++ } ++ entity.stopRiding(); ++ } ++ try { ++ level.tickNonPassenger(entity); ++ } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; ++ level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(throwable.getMessage(), throwable))); ++ throwable.printStackTrace(); + } +- entity.stopRiding(); +- } +- try { +- level.tickNonPassenger(entity); +- } catch (Throwable throwable) { +- if (throwable instanceof ThreadDeath) throw throwable; +- level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(throwable.getMessage(), throwable))); +- throwable.printStackTrace(); + } ++ }finally { ++ taskCounter.getAndDecrement(); + } + }; + if (!asyncEntityEnabled){ +diff --git a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +index 759b6dc9c719c6ff63348f9eacc760f8cef3163e..4c85bf8e4705a781a55a048f0b0541f0d32e2f07 100644 +--- a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java ++++ b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java +@@ -20,13 +20,11 @@ import java.util.function.BooleanSupplier; + public class ServerLevelTickHook { + private static final DefaultWorkerFactory workerFactory = new DefaultWorkerFactory("world"); + private static WorkerThreadPoolExecutor worker; +- private static boolean enabledParaWorld; +- private static volatile boolean inited = false; + private static final AtomicInteger activeTaskCount = new AtomicInteger(); + private static final Logger logger = LogManager.getLogger(); + + public static void initWorker(){ +- enabledParaWorld = HearseConfig.getBoolean("optimizations.enableparallelworldtick",true); ++ boolean enabledParaWorld = HearseConfig.getBoolean("optimizations.enableparallelworldtick", true); + if (enabledParaWorld){ + worker = new WorkerThreadPoolExecutor( + MinecraftServer.getServer().levels.size(), +@@ -43,16 +41,11 @@ public class ServerLevelTickHook { + logger.warn("World worker name:{}.This can help you to slove the lag problems when you using parallel world ticking",worker.getName()); + } + } +- inited = true; +- } +- +- public static boolean isInited(){ +- return inited; + } + + public static void callWorldTick(ServerLevel worldserver, BooleanSupplier shouldKeepTicking){ + activeTaskCount.getAndIncrement(); +- worker.execute(()->{ ++ Runnable task = () -> { + try { + try { + worldserver.tick(shouldKeepTicking); +@@ -66,7 +59,14 @@ public class ServerLevelTickHook { + }finally { + activeTaskCount.getAndDecrement(); + } +- }); ++ }; ++ ++ if (worker == null){ ++ task.run(); ++ return; ++ } ++ ++ worker.execute(task); + } + + public static void awaitWorldTicKTasks(){ +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 42599340856aa5ffcfae2281684174ca4dee289a..f0f164c6c95c0abde63f1327335af94148910152 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1,7 +1,6 @@ + package net.minecraft.server; + + import co.earthme.hearse.Hearse; +-import co.earthme.hearse.HearseConfig; + import co.earthme.hearse.server.ServerEntityTickHook; + import co.earthme.hearse.server.ServerLevelTickHook; + import com.google.common.base.Splitter; +@@ -1397,7 +1396,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 22 Jan 2023 13:32:24 +0800 +Subject: [PATCH] Hearse: Fix a threading issue and ingore an error + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +index d9cee42da1b097590e627142d3c5dccbc180b5ae..23c32a06dce8f0c45647c3619c98ba95290cfa7d 100644 +--- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java ++++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +@@ -518,6 +518,7 @@ public final class PlayerChunkLoader { + protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); + protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); + private static long nextChunkSend; ++ + private void trySendChunks() { + final long time = System.nanoTime(); + if (time < nextChunkSend) { +@@ -560,7 +561,7 @@ public final class PlayerChunkLoader { + } + + if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { +- throw new IllegalStateException(); ++ continue; + } + + data.nextChunkSendTarget = nextPlayerDeadline; +@@ -722,12 +723,13 @@ public final class PlayerChunkLoader { + } + } + ++ + public void tickMidTick() { +- // try to send more chunks +- this.trySendChunks(); ++ // try to send more chunks ++ this.trySendChunks(); + +- // try to queue more chunks to load +- this.tryLoadChunks(); ++ // try to queue more chunks to load ++ this.tryLoadChunks(); + } + + static final class ChunkPriorityHolder { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index becac3c1087c77b289c3b0a721c4146bea046e51..2caa5ab3a7b4bf0c535863f5ff54fcb8c0dc3c59 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -937,6 +937,8 @@ public class ServerChunkCache extends ChunkSource { + this.spawnFriendlies = spawnAnimals; + } + ++ private final Object pollTaskLock = new Object(); ++ + public String getChunkDebugData(ChunkPos pos) { + return this.chunkMap.getChunkDebugData(pos); + } +@@ -963,6 +965,7 @@ public class ServerChunkCache extends ChunkSource { + this.distanceManager.removeTicketsOnClosing(); + } + ++ + public final class MainThreadExecutor extends BlockableEventLoop { + + MainThreadExecutor(Level world) { +@@ -998,9 +1001,11 @@ public class ServerChunkCache extends ChunkSource { + @Override + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task + public boolean pollTask() { +- ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); +- if (ServerChunkCache.this.runDistanceManagerUpdates()) { +- return true; ++ synchronized (ServerChunkCache.this.pollTaskLock){ ++ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); ++ if (ServerChunkCache.this.runDistanceManagerUpdates()) { ++ return true; ++ } + } + return super.pollTask() | ServerChunkCache.this.level.chunkTaskScheduler.executeMainThreadTask(); // Paper - rewrite chunk system + } diff --git a/patches/server/0078-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch b/patches/server/0078-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch new file mode 100644 index 00000000..3a64510b --- /dev/null +++ b/patches/server/0078-Hearse-Fix-some-threading-issue-in-bukkit-event-syst.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wangxyper +Date: Tue, 24 Jan 2023 09:32:37 +0800 +Subject: [PATCH] Hearse-Fix some threading issue in bukkit event system + +Original license: MIT +Original project: https://github.com/Era4FunMC/Hearse + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 96cde1f86ca073e7e9e5799bcb12a10adf9230b2..a273d04c9c1645f1b1147e1a8502856a410400e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit; + ++import co.earthme.hearse.concurrent.thread.Worker; + import com.google.common.base.Charsets; + import com.google.common.base.Function; + import com.google.common.base.Preconditions; +@@ -2220,6 +2221,11 @@ public final class CraftServer implements Server { + return io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system + } + ++ @Override ++ public boolean isWorkerThread(){ ++ return Worker.isWorker(); ++ } ++ + // Paper start + @Override + public net.kyori.adventure.text.Component motd() {