mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-26 18:39:23 +00:00
Some work
This commit is contained in:
@@ -169,7 +169,7 @@ index 18071dcc69cc28471dddb7de94e803ec1e5fc2e4..e30bb9c4046200c1a6e4e917d15b205f
|
||||
}
|
||||
}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd7e83a34a 100644
|
||||
index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..923fc9d611d46017cf7ac8e6de6cf0966e0ce9f9 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -113,19 +113,8 @@ import net.minecraft.util.TimeUtil;
|
||||
@@ -276,15 +276,18 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0;
|
||||
try {
|
||||
this.isSaving = true;
|
||||
@@ -1558,7 +1522,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
profiler.pop();
|
||||
@@ -1555,10 +1519,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
- profiler.pop();
|
||||
// Paper end - Incremental chunk and player saving
|
||||
|
||||
- ProfilerFiller profilerFiller = Profiler.get();
|
||||
this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
|
||||
this.server.spark.executeMainThreadTasks(); // Paper - spark
|
||||
// Paper start - Server Tick Events
|
||||
@@ -1567,7 +1530,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1567,7 +1529,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
|
||||
// Paper end - Server Tick Events
|
||||
this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark
|
||||
@@ -292,7 +295,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
long l = Util.getNanos() - nanos;
|
||||
int i1 = this.tickCount % 100;
|
||||
this.aggregatedTickTimesNanos = this.aggregatedTickTimesNanos - this.tickTimesNanos[i1];
|
||||
@@ -1580,16 +1542,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1580,16 +1541,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.tickTimes60s.add(this.tickCount, l);
|
||||
// Paper end - Add tick times API and /mspt command
|
||||
this.logTickMethodTime(nanos);
|
||||
@@ -309,7 +312,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
LOGGER.debug("Autosave finished");
|
||||
}
|
||||
|
||||
@@ -1655,7 +1613,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1655,7 +1612,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
protected void tickChildren(BooleanSupplier hasTimeLeft) {
|
||||
@@ -317,7 +320,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
|
||||
this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
|
||||
// Paper start - Folia scheduler API
|
||||
@@ -1673,9 +1630,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1673,9 +1629,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
});
|
||||
// Paper end - Folia scheduler API
|
||||
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
|
||||
@@ -327,15 +330,16 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
|
||||
// CraftBukkit start
|
||||
// Run tasks that are waiting on processing
|
||||
@@ -1710,7 +1665,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1710,17 +1664,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
|
||||
serverLevel.updateLagCompensationTick(); // Paper - lag compensation
|
||||
net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
|
||||
- profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
|
||||
/* Drop global time updates
|
||||
if (this.tickCount % 20 == 0) {
|
||||
profilerFiller.push("timeSync");
|
||||
@@ -1719,8 +1673,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
- profilerFiller.push("timeSync");
|
||||
this.synchronizeTime(serverLevel);
|
||||
- profilerFiller.pop();
|
||||
}
|
||||
// CraftBukkit end */
|
||||
|
||||
@@ -344,7 +348,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
try {
|
||||
serverLevel.tick(hasTimeLeft);
|
||||
} catch (Throwable var7) {
|
||||
@@ -1729,34 +1681,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1729,34 +1678,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
throw new ReportedException(crashReport);
|
||||
}
|
||||
|
||||
@@ -379,7 +383,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
}
|
||||
|
||||
public void tickConnection() {
|
||||
@@ -1772,14 +1714,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1772,14 +1711,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
public void forceTimeSynchronization() {
|
||||
@@ -394,7 +398,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
}
|
||||
|
||||
public boolean isLevelEnabled(Level level) {
|
||||
@@ -2595,55 +2532,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2595,55 +2529,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
@@ -450,7 +454,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
public Path getWorldPath(LevelResource levelResource) {
|
||||
return this.storageSource.getLevelPath(levelResource);
|
||||
}
|
||||
@@ -2693,24 +2581,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2693,24 +2578,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
return this.isSaving;
|
||||
}
|
||||
|
||||
@@ -475,7 +479,7 @@ index d8179f1b7441679a96ac8ccbd67c2cb1c4fc4fd6..2505000585f6b726914861faf8f731bd
|
||||
public int getMaxChainedNeighborUpdates() {
|
||||
return 1000000;
|
||||
}
|
||||
@@ -2816,55 +2686,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2816,55 +2683,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
public record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ Original license: MIT
|
||||
Original project: https://github.com/PurpurMC/Purpur
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index ff1f5943cb99353df6069060c67da86516c9c956..0c4c76eb3fe04a67784997a19678f081eb86d00e 100644
|
||||
index af17193fde9d4c13e84fb7952521430299f2cfec..4b163dbc99cbfcef4d7bae97055af844e400d87a 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1805,7 +1805,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1802,7 +1802,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
@DontObfuscate
|
||||
public String getServerModName() {
|
||||
|
||||
@@ -13,7 +13,7 @@ To avoid the hefty ArrayDeque's size() call, we check if we *really* need to exe
|
||||
Most entities won't have any scheduled tasks, so this is a nice performance bonus. These optimizations, however, wouldn't work in a Folia environment, but because in SparklyPaper executeTick is always executed on the main thread, it ain't an issue for us (yay).
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 0c4c76eb3fe04a67784997a19678f081eb86d00e..6685763f33a86c7faf7d26d58685e8402d399980 100644
|
||||
index 4b163dbc99cbfcef4d7bae97055af844e400d87a..18083f96f95eb4436cefe5a99fd0fe25d102bae7 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -286,6 +286,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -24,7 +24,7 @@ index 0c4c76eb3fe04a67784997a19678f081eb86d00e..6685763f33a86c7faf7d26d58685e840
|
||||
|
||||
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
|
||||
AtomicReference<S> atomicReference = new AtomicReference<>();
|
||||
@@ -1629,6 +1630,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1628,6 +1629,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
|
||||
// Paper start - Folia scheduler API
|
||||
((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
|
||||
@@ -47,7 +47,7 @@ index 0c4c76eb3fe04a67784997a19678f081eb86d00e..6685763f33a86c7faf7d26d58685e840
|
||||
getAllLevels().forEach(level -> {
|
||||
for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) {
|
||||
if (entity.isRemoved()) {
|
||||
@@ -1640,6 +1657,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1639,6 +1656,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ Subject: [PATCH] Virtual thread for chat executor
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 6685763f33a86c7faf7d26d58685e8402d399980..8d8e6cc29a783810a27483ad213f020979ede359 100644
|
||||
index 18083f96f95eb4436cefe5a99fd0fe25d102bae7..301b255d43a160b462e546ab894378cc38ae18e6 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -2628,7 +2628,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2625,7 +2625,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
|
||||
|
||||
@@ -5,7 +5,7 @@ Subject: [PATCH] Remove stream in entity visible effects filter
|
||||
|
||||
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index e65914c2623197031d50508af5c45a4db6b98836..d55a701d1f9a39b4734c8b02b655e1f1a53e616b 100644
|
||||
index e65914c2623197031d50508af5c45a4db6b98836..4fd0a728bcc3a7063cefe4d163329f07db06176f 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -975,12 +975,15 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
@@ -19,7 +19,7 @@ index e65914c2623197031d50508af5c45a4db6b98836..d55a701d1f9a39b4734c8b02b655e1f1
|
||||
- .map(MobEffectInstance::getParticleOptions)
|
||||
- .toList();
|
||||
+ // Leaf start - Remove stream in entity visible effects filter
|
||||
+ List<ParticleOptions> list = new ArrayList<>();
|
||||
+ List<ParticleOptions> list = new java.util.ArrayList<>();
|
||||
+
|
||||
+ for (MobEffectInstance effect : this.activeEffects.values()) {
|
||||
+ if (effect.isVisible()) {
|
||||
|
||||
@@ -6,7 +6,7 @@ Subject: [PATCH] Replace Entity active effects map with optimized collection
|
||||
Dreeam TODO: check this
|
||||
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index d55a701d1f9a39b4734c8b02b655e1f1a53e616b..82289f1b8b3097fe20c3508461c79ab42fbef0f6 100644
|
||||
index 4fd0a728bcc3a7063cefe4d163329f07db06176f..0e1fb766570b5ff0e3c4a0f04c4ef3c589417322 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -196,6 +196,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
@@ -23,7 +23,7 @@ index d55a701d1f9a39b4734c8b02b655e1f1a53e616b..82289f1b8b3097fe20c3508461c79ab4
|
||||
@@ -977,15 +981,16 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
private void updateSynchronizedMobEffectParticles() {
|
||||
// Leaf start - Remove stream in entity visible effects filter
|
||||
List<ParticleOptions> list = new ArrayList<>();
|
||||
List<ParticleOptions> list = new java.util.ArrayList<>();
|
||||
+ final Collection<MobEffectInstance> effectsValues = this.activeEffects.values(); // Leaf - Replace Entity active effects map with optimized collection
|
||||
|
||||
- for (MobEffectInstance effect : this.activeEffects.values()) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: HaHaWTH <fsjk947@gmail.com>
|
||||
Date: Wed, 13 Mar 2024 03:33:08 +0800
|
||||
Subject: [PATCH] Fix MC-65198
|
||||
|
||||
Mojang issues: https://bugs.mojang.com/browse/MC/issues/MC-65198
|
||||
|
||||
diff --git a/net/minecraft/world/inventory/ItemCombinerMenu.java b/net/minecraft/world/inventory/ItemCombinerMenu.java
|
||||
index 34d52c941395645e77de810855b14012c259cf02..7ad3edf0e65c688408bf48305028468e2731cf75 100644
|
||||
--- a/net/minecraft/world/inventory/ItemCombinerMenu.java
|
||||
+++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
|
||||
@@ -120,6 +120,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu {
|
||||
if (slot != null && slot.hasItem()) {
|
||||
ItemStack item = slot.getItem();
|
||||
itemStack = item.copy();
|
||||
+ ItemStack itemStack2 = itemStack.copy(); // Leaf - Fix MC-65198
|
||||
int inventorySlotStart = this.getInventorySlotStart();
|
||||
int useRowEnd = this.getUseRowEnd();
|
||||
if (index == this.getResultSlot()) {
|
||||
@@ -156,7 +157,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
- slot.onTake(player, item);
|
||||
+ slot.onTake(player, itemStack2); // Leaf - Fix MC-65198
|
||||
}
|
||||
|
||||
return itemStack;
|
||||
diff --git a/net/minecraft/world/inventory/ResultSlot.java b/net/minecraft/world/inventory/ResultSlot.java
|
||||
index e4cba45c327d96550a92cd5f9a30b1e5bd212747..ba237017e21d66b4eb3b47b4c7160015993da348 100644
|
||||
--- a/net/minecraft/world/inventory/ResultSlot.java
|
||||
+++ b/net/minecraft/world/inventory/ResultSlot.java
|
||||
@@ -49,7 +49,7 @@ public class ResultSlot extends Slot {
|
||||
@Override
|
||||
protected void checkTakeAchievements(ItemStack stack) {
|
||||
if (this.removeCount > 0) {
|
||||
- stack.onCraftedBy(this.player, this.removeCount);
|
||||
+ stack.onCraftedBy(this.player, stack.getCount()); // Leaf - Fix MC-65198
|
||||
}
|
||||
|
||||
if (this.container instanceof RecipeCraftingHolder recipeCraftingHolder) {
|
||||
diff --git a/net/minecraft/world/inventory/StonecutterMenu.java b/net/minecraft/world/inventory/StonecutterMenu.java
|
||||
index 9b0b213d7550f02ddba2f9f19fa7b0a45437e968..516acc81b12c003df7744cd66f5dd9b95c0485a4 100644
|
||||
--- a/net/minecraft/world/inventory/StonecutterMenu.java
|
||||
+++ b/net/minecraft/world/inventory/StonecutterMenu.java
|
||||
@@ -238,6 +238,7 @@ public class StonecutterMenu extends AbstractContainerMenu {
|
||||
ItemStack item = slot.getItem();
|
||||
Item item1 = item.getItem();
|
||||
itemStack = item.copy();
|
||||
+ ItemStack itemStack2 = itemStack.copy(); // Leaf - Fix MC-65198
|
||||
if (index == 1) {
|
||||
item1.onCraftedBy(item, player);
|
||||
if (!this.moveItemStackTo(item, 2, 38, true)) {
|
||||
@@ -270,7 +271,7 @@ public class StonecutterMenu extends AbstractContainerMenu {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
- slot.onTake(player, item);
|
||||
+ slot.onTake(player, itemStack2); // Leaf - Fix MC-65198
|
||||
if (index == 1) {
|
||||
player.drop(item, false);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
|
||||
Date: Sun, 7 Jul 2024 01:29:57 +0800
|
||||
Subject: [PATCH] Fix MC-200418
|
||||
|
||||
Related MC issue: https://bugs.mojang.com/browse/MC/issues/MC-200418
|
||||
|
||||
diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java
|
||||
index a8cd7103e636b57be1270d0f3549c709330b5536..33369d4faf80d36cee5dd3317a8778da5edd060a 100644
|
||||
--- a/net/minecraft/world/entity/monster/ZombieVillager.java
|
||||
+++ b/net/minecraft/world/entity/monster/ZombieVillager.java
|
||||
@@ -233,6 +233,11 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
|
||||
if (!this.isSilent()) {
|
||||
level.levelEvent(null, 1027, this.blockPosition(), 0);
|
||||
}
|
||||
+ // Leaf start - Fix MC-200418
|
||||
+ if (villager.isPassenger() && villager.getVehicle() instanceof net.minecraft.world.entity.animal.Chicken && villager.isBaby()) {
|
||||
+ villager.removeVehicle();
|
||||
+ }
|
||||
+ // Leaf end - Fix MC-200418
|
||||
// CraftBukkit start
|
||||
}, org.bukkit.event.entity.EntityTransformEvent.TransformReason.CURED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED // CraftBukkit
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
|
||||
Date: Sun, 7 Jul 2024 01:42:45 +0800
|
||||
Subject: [PATCH] Fix MC-119417
|
||||
|
||||
Related MC issue: https://bugs.mojang.com/browse/MC/issues/MC-119417
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index d32acdf38a35d569669b272560c72651240178db..637e595c99bf0580d59913dff579868dbf01491b 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -2123,6 +2123,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId()));
|
||||
if (gameMode == GameType.SPECTATOR) {
|
||||
this.removeEntitiesOnShoulder();
|
||||
+ this.stopSleeping(); // Leaf - Fix MC-119417
|
||||
this.stopRiding();
|
||||
EnchantmentHelper.stopLocationBasedEffects(this);
|
||||
} else {
|
||||
@@ -0,0 +1,19 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
|
||||
Date: Sun, 7 Jul 2024 01:59:11 +0800
|
||||
Subject: [PATCH] Fix MC-223153
|
||||
|
||||
Related MC issue: https://bugs.mojang.com/browse/MC/issues/MC-223153
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java
|
||||
index cea1e405c940cd51cf830f28bfc6ce72c0c36a12..01b1495dfd8619ae29591ed4cadce9a0330b85bc 100644
|
||||
--- a/net/minecraft/world/level/block/Blocks.java
|
||||
+++ b/net/minecraft/world/level/block/Blocks.java
|
||||
@@ -6742,6 +6742,7 @@ public class Blocks {
|
||||
.mapColor(MapColor.COLOR_ORANGE)
|
||||
.instrument(NoteBlockInstrument.BASEDRUM)
|
||||
.requiresCorrectToolForDrops()
|
||||
+ .sound(SoundType.COPPER) // Leaf - Fix MC-223153
|
||||
.strength(5.0F, 6.0F)
|
||||
);
|
||||
public static final Block RAW_GOLD_BLOCK = register(
|
||||
@@ -0,0 +1,40 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
|
||||
Date: Sun, 4 Aug 2024 19:34:29 +0800
|
||||
Subject: [PATCH] Configurable player knockback zombie
|
||||
|
||||
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index 0e1fb766570b5ff0e3c4a0f04c4ef3c589417322..4b3de5d5a1c29f8f2c62ef059e90614d0791d88b 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -2018,6 +2018,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
}
|
||||
|
||||
public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause eventCause) { // Paper - knockback events
|
||||
+ if (!canKnockback(attacker, this)) return; // Leaf - Configurable player knockback zombie
|
||||
+
|
||||
strength *= 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
|
||||
if (true || !(strength <= 0.0)) { // CraftBukkit - Call event even when force is 0
|
||||
// this.hasImpulse = true; // CraftBukkit - Move down
|
||||
@@ -2048,6 +2050,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
}
|
||||
}
|
||||
|
||||
+ // Leaf start - Configurable player knockback zombie
|
||||
+ private boolean canKnockback(@Nullable Entity attacker, LivingEntity target) {
|
||||
+ if (!org.dreeam.leaf.config.modules.gameplay.Knockback.canPlayerKnockbackZombie) {
|
||||
+ if (attacker instanceof ServerPlayer && target.getType() == EntityType.ZOMBIE) { // Player -> Zombie
|
||||
+ return false;
|
||||
+ } else if (attacker instanceof Projectile projectile && projectile.getOwner() instanceof ServerPlayer && target.getType() == EntityType.ZOMBIE) { // Player -> projectile -> Zombie
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Leaf end - Configurable player knockback zombie
|
||||
+
|
||||
public void indicateDamage(double xDistance, double zDistance) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: froobynooby <froobynooby@froobworld.com>
|
||||
Date: Mon, 5 Aug 2024 20:33:57 +0800
|
||||
Subject: [PATCH] Paper PR: Skip AI during inactive ticks for non-aware mobs
|
||||
|
||||
Original license: GPLv3
|
||||
Original project: https://github.com/PaperMC/Paper
|
||||
Paper pull request: https://github.com/PaperMC/Paper/pull/10990
|
||||
|
||||
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
|
||||
index a1ecb7c5ee0e1fe1164e277d8991d6d990035f76..c3f659396fd7d01140f8038ea0acc4b4f10bded6 100644
|
||||
--- a/net/minecraft/world/entity/Mob.java
|
||||
+++ b/net/minecraft/world/entity/Mob.java
|
||||
@@ -206,6 +206,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
|
||||
@Override
|
||||
public void inactiveTick() {
|
||||
super.inactiveTick();
|
||||
+ // Paper start - Skip AI during inactive ticks for non-aware mobs
|
||||
+ if (org.dreeam.leaf.config.modules.opt.SkipAIForNonAwareMob.enabled && !(this.isEffectiveAi() && this.aware)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Skip AI during inactive ticks for non-aware mobs
|
||||
boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking
|
||||
if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking
|
||||
this.goalSelector.tick();
|
||||
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
|
||||
index cb9c722251e01cbbd827af9aff5f5942c62d2011..8b96a2695d6cb62e8d38e429769bd7964241c002 100644
|
||||
--- a/net/minecraft/world/entity/npc/Villager.java
|
||||
+++ b/net/minecraft/world/entity/npc/Villager.java
|
||||
@@ -275,7 +275,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
|
||||
if (this.getUnhappyCounter() > 0) {
|
||||
this.setUnhappyCounter(this.getUnhappyCounter() - 1);
|
||||
}
|
||||
- if (this.isEffectiveAi()) {
|
||||
+ if (this.isEffectiveAi() && (!org.dreeam.leaf.config.modules.opt.SkipAIForNonAwareMob.enabled || this.aware)) { // Paper - Skip AI during inactive ticks for non-aware mobs
|
||||
if (this.level().spigotConfig.tickInactiveVillagers) {
|
||||
this.customServerAiStep(this.level().getMinecraftWorld());
|
||||
} else {
|
||||
@@ -0,0 +1,31 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Newwind <support@newwindserver.com>
|
||||
Date: Sat, 27 Jul 2024 11:09:37 +0200
|
||||
Subject: [PATCH] Paper PR: Prevent zombie reinforcements loading chunks
|
||||
|
||||
Original license: GPLv3
|
||||
Original project: https://github.com/PaperMC/Paper
|
||||
Paper pull request: https://github.com/PaperMC/Paper/pull/11175
|
||||
|
||||
When a zombie calls reinforcements it tries to spawn them in a random location within a 40 block radius of the zombie,
|
||||
before spawning, it checks isSpawnPositionOk() for the position which loads the block to check if a mob can spawn on said block.
|
||||
This patch ensures the chunk at the random location is loaded before trying to spawn the reinforcement zombie in it.
|
||||
|
||||
diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java
|
||||
index 39b65970a48568c95ff482b9636e7391f300ffa8..4395947fc8c719864ac2afde5e6bbb53da5129c2 100644
|
||||
--- a/net/minecraft/world/entity/monster/Zombie.java
|
||||
+++ b/net/minecraft/world/entity/monster/Zombie.java
|
||||
@@ -353,6 +353,13 @@ public class Zombie extends Monster {
|
||||
int i2 = floor1 + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
|
||||
int i3 = floor2 + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
|
||||
BlockPos blockPos = new BlockPos(i1, i2, i3);
|
||||
+
|
||||
+ // Paper start - Prevent reinforcement checks from loading chunks
|
||||
+ if (this.level().getChunkIfLoadedImmediately(blockPos.getX() >> 4, blockPos.getZ() >> 4) == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ // Paper end - Prevent reinforcement checks from loading chunks
|
||||
+
|
||||
if (SpawnPlacements.isSpawnPositionOk(type, level, blockPos)
|
||||
&& SpawnPlacements.checkSpawnRules(type, level, EntitySpawnReason.REINFORCEMENT, blockPos, level.random)) {
|
||||
zombie.setPos(i1, i2, i3);
|
||||
@@ -0,0 +1,99 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Warrior <50800980+Warriorrrr@users.noreply.github.com>
|
||||
Date: Thu, 31 Aug 2023 10:23:06 +0200
|
||||
Subject: [PATCH] PaperPR: Fix some beacon event issues
|
||||
|
||||
Original license: GPLv3
|
||||
Original project: https://github.com/PaperMC/Paper
|
||||
Paper pull request: https://github.com/PaperMC/Paper/pull/9674
|
||||
|
||||
Closes Paper#8947
|
||||
|
||||
Moves the deactivate event call into the onRemove method for the beacon block itself to prevent it from running when the block entity is unloaded. Also fixes an issue where the events were not being called when the beacon beam gets blocked.
|
||||
|
||||
The field I added feels a bit wrong but it works, it's to prevent the activation event being called immediately after loading, can't see any better way to differentiate between a newly placed beacon and a newly loaded one.
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/BeaconBlock.java b/net/minecraft/world/level/block/BeaconBlock.java
|
||||
index 66eee067b4ffdd72393ca813de995062be5b7a90..38d36a2798b9aa5298ae2936f872fc63d73b7aa2 100644
|
||||
--- a/net/minecraft/world/level/block/BeaconBlock.java
|
||||
+++ b/net/minecraft/world/level/block/BeaconBlock.java
|
||||
@@ -52,4 +52,16 @@ public class BeaconBlock extends BaseEntityBlock implements BeaconBeamBlock {
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
+
|
||||
+ // Paper start - BeaconDeactivatedEvent
|
||||
+ @Override
|
||||
+ public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) {
|
||||
+ if (!state.is(newState.getBlock()) && world.getBlockEntity(pos) instanceof BeaconBlockEntity beacon && beacon.levels > 0 && !beacon.getBeamSections().isEmpty()) {
|
||||
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
|
||||
+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
|
||||
+ }
|
||||
+
|
||||
+ super.onRemove(state, world, pos, newState, moved);
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
|
||||
index b77cdbf3e8cf0e9d66c9e5288ebae38c79dae1fe..bdf120291f52642ffa4266ac786bb2975b40bf10 100644
|
||||
--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
|
||||
@@ -162,6 +162,8 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
|
||||
return VALID_EFFECTS.contains(effect) ? effect : null;
|
||||
}
|
||||
|
||||
+ public boolean justLoadedAndPreviouslyActive; // Paper - consider beacon previously active for first tick to skip activate event/sound
|
||||
+
|
||||
public BeaconBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(BlockEntityType.BEACON, pos, blockState);
|
||||
}
|
||||
@@ -225,10 +227,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
|
||||
}
|
||||
}
|
||||
// Paper start - beacon activation/deactivation events
|
||||
- if (originalLevels <= 0 && blockEntity.levels > 0) {
|
||||
+ // Paper start
|
||||
+ final boolean prevActive = originalLevels > 0 && (!blockEntity.beamSections.isEmpty() || (blockEntity.justLoadedAndPreviouslyActive && !blockEntity.checkingBeamSections.isEmpty()));
|
||||
+ blockEntity.justLoadedAndPreviouslyActive = false;
|
||||
+ final boolean newActive = blockEntity.levels > 0 && !blockEntity.checkingBeamSections.isEmpty();
|
||||
+ if (!prevActive && newActive) {
|
||||
+ // Paper end
|
||||
org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
|
||||
new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent();
|
||||
- } else if (originalLevels > 0 && blockEntity.levels <= 0) {
|
||||
+ } else if (prevActive && !newActive) { // Paper
|
||||
org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
|
||||
new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
|
||||
}
|
||||
@@ -236,10 +243,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
|
||||
|
||||
if (blockEntity.lastCheckY >= height) {
|
||||
blockEntity.lastCheckY = level.getMinY() - 1;
|
||||
- boolean flag = i > 0;
|
||||
+ boolean flag = prevActive; // Paper - Fix MC-183981
|
||||
blockEntity.beamSections = blockEntity.checkingBeamSections;
|
||||
if (!level.isClientSide) {
|
||||
- boolean flag1 = blockEntity.levels > 0;
|
||||
+ boolean flag1 = newActive; // Paper - Fix MC-183981
|
||||
if (!flag && flag1) {
|
||||
playSound(level, pos, SoundEvents.BEACON_ACTIVATE);
|
||||
|
||||
@@ -283,10 +290,6 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
|
||||
|
||||
@Override
|
||||
public void setRemoved() {
|
||||
- // Paper start - beacon activation/deactivation events
|
||||
- org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition);
|
||||
- new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
|
||||
- // Paper end - beacon activation/deactivation events
|
||||
// Paper start - fix MC-153086
|
||||
if (this.levels > 0 && !this.beamSections.isEmpty()) {
|
||||
playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
|
||||
@@ -414,6 +417,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
|
||||
this.primaryPower = loadEffect(tag, "primary_effect");
|
||||
this.secondaryPower = loadEffect(tag, "secondary_effect");
|
||||
this.levels = tag.getIntOr("Levels", 0); // CraftBukkit - SPIGOT-5053, use where available
|
||||
+ this.justLoadedAndPreviouslyActive = this.levels > 0; // Paper
|
||||
this.name = parseCustomNameSafe(tag.get("CustomName"), registries);
|
||||
this.lockKey = LockCode.fromTag(tag, registries);
|
||||
this.effectRange = tag.getDoubleOr(PAPER_RANGE_TAG, -1); // Paper - Custom beacon ranges
|
||||
@@ -0,0 +1,45 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: William Blake Galbreath <blake.galbreath@gmail.com>
|
||||
Date: Sat, 6 Jul 2019 17:00:04 -0500
|
||||
Subject: [PATCH] Dont send useless entity packets
|
||||
|
||||
TODO: Add more reducers
|
||||
|
||||
Original license: MIT
|
||||
Original project: https://github.com/PurpurMC/Purpur
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
|
||||
index 936429fd17d8649329e6258a4e10c9e6bf62f6de..94fa37653598014d8187c4ecfd486709e4fb9f91 100644
|
||||
--- a/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -204,6 +204,8 @@ public class ServerEntity {
|
||||
}
|
||||
// Gale end - Airplane - better checking for useless move packets
|
||||
|
||||
+ if (org.dreeam.leaf.config.modules.opt.ReduceUselessPackets.reduceUselessEntityMovePackets && isUselessMoveEntityPacket(packet)) packet = null; // Purpur
|
||||
+
|
||||
if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) {
|
||||
Vec3 deltaMovement = this.entity.getDeltaMovement();
|
||||
if (deltaMovement != this.lastSentMovement) { // SparklyPaper start - skip distanceToSqr call in ServerEntity#sendChanges if the delta movement hasn't changed
|
||||
@@ -287,6 +289,21 @@ public class ServerEntity {
|
||||
);
|
||||
}
|
||||
|
||||
+ // Purpur start
|
||||
+ private boolean isUselessMoveEntityPacket(@Nullable Packet<?> packet) {
|
||||
+ if (!(packet instanceof ClientboundMoveEntityPacket moveEntityPacket)) return false;
|
||||
+ return switch (packet) {
|
||||
+ case ClientboundMoveEntityPacket.Pos ignored ->
|
||||
+ moveEntityPacket.getXa() == 0 && moveEntityPacket.getYa() == 0 && moveEntityPacket.getZa() == 0;
|
||||
+ case ClientboundMoveEntityPacket.PosRot ignored ->
|
||||
+ moveEntityPacket.getXa() == 0 && moveEntityPacket.getYa() == 0 && moveEntityPacket.getZa() == 0 && moveEntityPacket.getYRot() == 0 && moveEntityPacket.getXRot() == 0;
|
||||
+ case ClientboundMoveEntityPacket.Rot ignored ->
|
||||
+ moveEntityPacket.getYRot() == 0 && moveEntityPacket.getXRot() == 0;
|
||||
+ default -> false;
|
||||
+ };
|
||||
+ }
|
||||
+ // Purpur end
|
||||
+
|
||||
private void handleMinecartPosRot(NewMinecartBehavior behavior, byte yRot, byte xRot, boolean dirty) {
|
||||
this.sendDirtyEntityData();
|
||||
if (behavior.lerpSteps.isEmpty()) {
|
||||
@@ -0,0 +1,266 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: peaches94 <peachescu94@gmail.com>
|
||||
Date: Sat, 2 Jul 2022 00:35:56 -0500
|
||||
Subject: [PATCH] Multithreaded Tracker
|
||||
|
||||
Original license: GPL v3
|
||||
Original project: https://github.com/Bloom-host/Petal
|
||||
|
||||
Original license: GPL v3
|
||||
Original project: https://github.com/TECHNOVE/Airplane-Experimental
|
||||
|
||||
Co-authored-by: Paul Sauve <paul@technove.co>
|
||||
Co-authored-by: Kevin Raneri <kevin.raneri@gmail.com>
|
||||
Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
|
||||
|
||||
This patch refactored from original multithreaded tracker (Petal version),
|
||||
and is derived from the Airplane fork by Paul Sauve, the tree is like:
|
||||
Airplane -> Pufferfish? -> Petal -> Leaf
|
||||
|
||||
We made much of tracking logic asynchronously, and fixed visible issue
|
||||
for the case of some NPC plugins which using real entity type, e.g. Citizens.
|
||||
|
||||
But it is still recommending to use those packet based, virtual entity
|
||||
based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc.
|
||||
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
||||
index 02a9ef1694c796584c29430d27f0a09047368835..32608df3da169159c070f37cb55407f4f6187744 100644
|
||||
--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
||||
@@ -340,7 +340,7 @@ public final class RegionizedPlayerChunkLoader {
|
||||
private boolean canGenerateChunks = true;
|
||||
|
||||
private final ArrayDeque<ChunkHolderManager.TicketOperation<?, ?>> delayedTicketOps = new ArrayDeque<>();
|
||||
- private final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
||||
+ private final LongOpenHashSet sentChunks = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled ? new org.dreeam.leaf.util.map.ConcurrentLongHashSet() : new LongOpenHashSet(); // Leaf - Multithreaded tracker
|
||||
|
||||
private static final byte CHUNK_TICKET_STAGE_NONE = 0;
|
||||
private static final byte CHUNK_TICKET_STAGE_LOADING = 1;
|
||||
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
|
||||
index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da393052a1f320 100644
|
||||
--- a/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -255,6 +255,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
|
||||
+ // Leaf start - Multithreaded tracker
|
||||
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled)
|
||||
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
+ final ServerPlayer player = backingSet[i];
|
||||
+ if (player == null) continue;
|
||||
+ ++(player.mobCounts[index]);
|
||||
+ }
|
||||
+ else
|
||||
+ // Leaf end - Multithreaded tracker
|
||||
for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
++(backingSet[i].mobCounts[index]);
|
||||
}
|
||||
@@ -1013,6 +1022,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
// Paper end - optimise entity tracker
|
||||
|
||||
protected void tick() {
|
||||
+ // Leaf start - Multithreaded tracker
|
||||
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
|
||||
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level;
|
||||
+ org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Leaf end - Multithreaded tracker
|
||||
// Paper start - optimise entity tracker
|
||||
if (true) {
|
||||
this.newTrackerTick();
|
||||
@@ -1135,7 +1151,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
final Entity entity;
|
||||
private final int range;
|
||||
SectionPos lastSectionPos;
|
||||
- public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
|
||||
+ public final Set<ServerPlayerConnection> seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker
|
||||
|
||||
// Paper start - optimise entity tracker
|
||||
private long lastChunkUpdate = -1L;
|
||||
@@ -1162,7 +1178,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
this.lastTrackedChunk = chunk;
|
||||
|
||||
final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
|
||||
+ final int playersLen = players.size(); // Ensure length won't change in the future tasks
|
||||
+
|
||||
+ // Leaf start - Multithreaded tracker
|
||||
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
||||
+ final boolean isServerPlayer = this.entity instanceof ServerPlayer;
|
||||
+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer();
|
||||
+ Runnable updatePlayerTasks = () -> {
|
||||
+ for (int i = 0; i < playersLen; ++i) {
|
||||
+ final ServerPlayer player = playersRaw[i];
|
||||
+ this.updatePlayer(player);
|
||||
+ }
|
||||
|
||||
+ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) {
|
||||
+ // need to purge any players possible not in the chunk list
|
||||
+ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) {
|
||||
+ final ServerPlayer player = conn.getPlayer();
|
||||
+ if (!players.contains(player)) {
|
||||
+ this.removePlayer(player);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ // Only update asynchronously for real player, and sync update for fake players
|
||||
+ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens
|
||||
+ // To prevent visible issue with player type NPCs
|
||||
+ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc.
|
||||
+ if (isRealPlayer || !isServerPlayer) {
|
||||
+ org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks);
|
||||
+ } else {
|
||||
+ updatePlayerTasks.run();
|
||||
+ }
|
||||
+ } else {
|
||||
for (int i = 0, len = players.size(); i < len; ++i) {
|
||||
final ServerPlayer player = playersRaw[i];
|
||||
this.updatePlayer(player);
|
||||
@@ -1177,6 +1225,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
+ }
|
||||
+ // Leaf end - Multithreaded tracker
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1238,7 +1288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void broadcast(Packet<?> packet) {
|
||||
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker
|
||||
serverPlayerConnection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -1259,21 +1309,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void broadcastRemoved() {
|
||||
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker
|
||||
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too
|
||||
if (this.seenBy.remove(player.connection)) {
|
||||
this.serverEntity.removePairing(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePlayer(ServerPlayer player) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Leaf - petal - Multithreaded tracker - We can update async
|
||||
if (player != this.entity) {
|
||||
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && player == null) return; // Leaf - Multithreaded tracker
|
||||
// Paper start - remove allocation of Vec3D here
|
||||
// Vec3 vec3 = player.position().subtract(this.entity.position());
|
||||
double vec3_dx = player.getX() - this.entity.getX();
|
||||
diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java
|
||||
index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca675930ff2 100644
|
||||
--- a/net/minecraft/server/level/ServerBossEvent.java
|
||||
+++ b/net/minecraft/server/level/ServerBossEvent.java
|
||||
@@ -13,7 +13,7 @@ import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.BossEvent;
|
||||
|
||||
public class ServerBossEvent extends BossEvent {
|
||||
- private final Set<ServerPlayer> players = Sets.newHashSet();
|
||||
+ private final Set<ServerPlayer> players = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? Sets.newConcurrentHashSet() : Sets.newHashSet(); // Leaf - petal - Multithreaded tracker - players can be removed in async tracking
|
||||
private final Set<ServerPlayer> unmodifiablePlayers = Collections.unmodifiableSet(this.players);
|
||||
public boolean visible = true;
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
|
||||
index 94fa37653598014d8187c4ecfd486709e4fb9f91..8aebb5e5f42fd4ae54cdea8ab7573bda3dea1d30 100644
|
||||
--- a/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -443,12 +443,15 @@ public class ServerEntity {
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
|
||||
if (!attributesToSync.isEmpty()) {
|
||||
+ // Leaf start - petal - Multithreaded tracker - send in main thread
|
||||
+ final Set<AttributeInstance> copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync);
|
||||
// CraftBukkit start - Send scaled max health
|
||||
if (this.entity instanceof ServerPlayer serverPlayer) {
|
||||
- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
|
||||
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false);
|
||||
}
|
||||
// CraftBukkit end
|
||||
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
|
||||
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
|
||||
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
||||
}
|
||||
|
||||
attributesToSync.clear();
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index 2c65987f7dda5b46a232a69e46b91090801fc246..0d73baebab6bdde6e279cc0da9c0ef8a275537ee 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -2407,7 +2407,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
|
||||
@Override
|
||||
public LevelEntityGetter<Entity> getEntities() {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot // Leaf - Multithreaded tracker
|
||||
return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 6314831d7ecefa14be1386eced3ee50510ebe769..f1cdeb9c1f8eeb69870d17d5453a311358740625 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -1731,7 +1731,7 @@ public class ServerGamePacketListenerImpl
|
||||
}
|
||||
|
||||
public void internalTeleport(PositionMoveRotation posMoveRotation, Set<Relative> relatives) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // Leaf - Multithreaded tracker
|
||||
// Paper start - Prevent teleporting dead entities
|
||||
if (this.player.isRemoved()) {
|
||||
LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
|
||||
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..d99bbf299af2b2d3a61761c5c3c33c4d371d1b9b 100644
|
||||
--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
@@ -26,8 +26,11 @@ public class AttributeInstance {
|
||||
private final Map<AttributeModifier.Operation, Map<ResourceLocation, AttributeModifier>> modifiersByOperation = Maps.newEnumMap(
|
||||
AttributeModifier.Operation.class
|
||||
);
|
||||
- private final Map<ResourceLocation, AttributeModifier> modifierById = new Object2ObjectArrayMap<>();
|
||||
- private final Map<ResourceLocation, AttributeModifier> permanentModifiers = new Object2ObjectArrayMap<>();
|
||||
+ // Leaf start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled;
|
||||
+ private final Map<ResourceLocation, AttributeModifier> modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>();
|
||||
+ private final Map<ResourceLocation, AttributeModifier> permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>();
|
||||
+ // Leaf end - Multithreaded tracker
|
||||
private double baseValue;
|
||||
private boolean dirty = true;
|
||||
private double cachedValue;
|
||||
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
index 93a079df455e371a0ca7ada253dc8b7e16b0146f..fce4fa42dbec302b5c49c954d3286deea6f81d45 100644
|
||||
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
@@ -14,11 +14,14 @@ import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class AttributeMap {
|
||||
+ // Leaf start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled;
|
||||
// Gale start - Lithium - replace AI attributes with optimized collections
|
||||
- private final Map<Holder<Attribute>, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
|
||||
- private final Set<AttributeInstance> attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
||||
- private final Set<AttributeInstance> attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
||||
+ private final Map<Holder<Attribute>, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
|
||||
+ private final Set<AttributeInstance> attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
||||
+ private final Set<AttributeInstance> attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
||||
// Gale end - Lithium - replace AI attributes with optimized collections
|
||||
+ // Leaf end - Multithreaded tracker
|
||||
private final AttributeSupplier supplier;
|
||||
private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
|
||||
Date: Fri, 23 Aug 2024 22:04:20 -0400
|
||||
Subject: [PATCH] Nitori: Async playerdata Save
|
||||
|
||||
Original license: GPL v3
|
||||
Original project: https://github.com/Gensokyo-Reimagined/Nitori
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 301b255d43a160b462e546ab894378cc38ae18e6..b74b58f6d110100c647ca4ffb9fbc8eb5c637dd9 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1055,6 +1055,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
|
||||
this.onServerExit();
|
||||
// Paper end - Improved watchdog support - move final shutdown items here
|
||||
+ // Leaf start - Async playerdata saving
|
||||
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.IO_POOL.shutdown();
|
||||
+ try {
|
||||
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.IO_POOL.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
|
||||
+ } catch (java.lang.InterruptedException ignored) {}
|
||||
+ // Leaf end - Async playerdata saving
|
||||
}
|
||||
|
||||
public String getLocalIp() {
|
||||
diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cece4432ad1 100644
|
||||
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
@@ -521,15 +521,26 @@ public class LevelStorageSource {
|
||||
private void saveLevelData(CompoundTag tag) {
|
||||
Path path = this.levelDirectory.path();
|
||||
|
||||
+ // Leaf start - Async playerdata saving
|
||||
+ // Save level.dat asynchronously
|
||||
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
|
||||
try {
|
||||
- Path path1 = Files.createTempFile(path, "level", ".dat");
|
||||
- NbtIo.writeCompressed(tag, path1);
|
||||
- Path path2 = this.levelDirectory.oldDataFile();
|
||||
- Path path3 = this.levelDirectory.dataFile();
|
||||
- Util.safeReplaceFile(path3, path1, path2);
|
||||
+ NbtIo.writeCompressed(tag, nbtBytes);
|
||||
} catch (Exception var6) {
|
||||
- LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
|
||||
+ LevelStorageSource.LOGGER.error("Failed to encode level {}", path, var6);
|
||||
}
|
||||
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> {
|
||||
+ try {
|
||||
+ Path path1 = Files.createTempFile(path, "level", ".dat");
|
||||
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
|
||||
+ Path path2 = this.levelDirectory.oldDataFile();
|
||||
+ Path path3 = this.levelDirectory.dataFile();
|
||||
+ Util.safeReplaceFile(path3, path1, path2);
|
||||
+ } catch (Exception var6) {
|
||||
+ LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
|
||||
+ }
|
||||
+ });
|
||||
+ // Leaf end - Async playerdata saving
|
||||
}
|
||||
|
||||
public Optional<Path> getIconFile() {
|
||||
diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
|
||||
index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63c440bc59 100644
|
||||
--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
|
||||
+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
|
||||
@@ -23,6 +23,7 @@ public class PlayerDataStorage {
|
||||
private final File playerDir;
|
||||
protected final DataFixer fixerUpper;
|
||||
private static final DateTimeFormatter FORMATTER = FileNameDateFormatter.create();
|
||||
+ public final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<java.util.UUID> savingQueue = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); // Leaf - Async playerdata saving
|
||||
|
||||
public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) {
|
||||
this.fixerUpper = fixerUpper;
|
||||
@@ -32,17 +33,43 @@ public class PlayerDataStorage {
|
||||
|
||||
public void save(Player player) {
|
||||
if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
|
||||
+ // Leaf start - Async playerdata saving
|
||||
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
|
||||
try {
|
||||
CompoundTag compoundTag = player.saveWithoutId(new CompoundTag());
|
||||
- Path path = this.playerDir.toPath();
|
||||
- Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat");
|
||||
- NbtIo.writeCompressed(compoundTag, path1);
|
||||
- Path path2 = path.resolve(player.getStringUUID() + ".dat");
|
||||
- Path path3 = path.resolve(player.getStringUUID() + ".dat_old");
|
||||
- Util.safeReplaceFile(path2, path1, path3);
|
||||
- } catch (Exception var7) {
|
||||
- LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), var7); // Paper - Print exception
|
||||
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
|
||||
+ } catch (Exception exception) {
|
||||
+ LOGGER.warn("Failed to encode player data for {}", player.getScoreboardName(), exception);
|
||||
}
|
||||
+ String playerName = player.getScoreboardName();
|
||||
+ String stringUuid = player.getStringUUID();
|
||||
+ java.util.UUID playerUuid = player.getUUID();
|
||||
+ synchronized (PlayerDataStorage.this) {
|
||||
+ while (savingQueue.contains(playerUuid)) {
|
||||
+ try {
|
||||
+ Thread.sleep(1L);
|
||||
+ } catch (InterruptedException ignored) {
|
||||
+ }
|
||||
+ }
|
||||
+ savingQueue.add(playerUuid);
|
||||
+ }
|
||||
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> {
|
||||
+ try {
|
||||
+ Path path = this.playerDir.toPath();
|
||||
+ Path path1 = Files.createTempFile(path, stringUuid + "-", ".dat");
|
||||
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
|
||||
+ Path path2 = path.resolve(stringUuid + ".dat");
|
||||
+ Path path3 = path.resolve(stringUuid + ".dat_old");
|
||||
+ Util.safeReplaceFile(path2, path1, path3);
|
||||
+ } catch (Exception var7) {
|
||||
+ LOGGER.warn("Failed to save player data for {}", playerName, var7); // Paper - Print exception
|
||||
+ } finally {
|
||||
+ synchronized (PlayerDataStorage.this) {
|
||||
+ savingQueue.remove(playerUuid);
|
||||
+ }
|
||||
+ }
|
||||
+ });
|
||||
+ // Leaf end - Async playerdata saving
|
||||
}
|
||||
|
||||
private void backup(String name, String stringUuid, String suffix) { // CraftBukkit
|
||||
@@ -58,7 +85,20 @@ public class PlayerDataStorage {
|
||||
}
|
||||
}
|
||||
|
||||
- private Optional<CompoundTag> load(String name, String stringUuid, String suffix) { // CraftBukkit
|
||||
+ // Leaf start - Async playerdata saving
|
||||
+ private Optional<CompoundTag> load(String name, String stringUuid, String suffix) {
|
||||
+ return load(name, stringUuid, suffix, java.util.UUID.fromString(stringUuid));
|
||||
+ }
|
||||
+ private Optional<CompoundTag> load(String name, String stringUuid, String suffix, java.util.UUID playerUuid) { // CraftBukkit
|
||||
+ synchronized (PlayerDataStorage.this) {
|
||||
+ while (savingQueue.contains(playerUuid)) {
|
||||
+ try {
|
||||
+ Thread.sleep(1L);
|
||||
+ } catch (InterruptedException ignored) {
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // Leaf end - Async playerdata saving
|
||||
File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
|
||||
// Spigot start
|
||||
boolean usingWrongFile = false;
|
||||
@@ -89,7 +129,7 @@ public class PlayerDataStorage {
|
||||
|
||||
public Optional<CompoundTag> load(Player player) {
|
||||
// CraftBukkit start
|
||||
- return this.load(player.getName().getString(), player.getStringUUID()).map((tag) -> {
|
||||
+ return this.load(player.getName().getString(), player.getStringUUID(), player.getUUID()).map((tag) -> { // Leaf - Async playerdata saving
|
||||
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
|
||||
org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer = serverPlayer.getBukkitEntity();
|
||||
// Only update first played if it is older than the one we have
|
||||
@@ -104,20 +144,25 @@ public class PlayerDataStorage {
|
||||
});
|
||||
}
|
||||
|
||||
+ // Leaf start - Async playerdata saving
|
||||
public Optional<CompoundTag> load(String name, String uuid) {
|
||||
+ return this.load(name, uuid, java.util.UUID.fromString(uuid));
|
||||
+ }
|
||||
+ public Optional<CompoundTag> load(String name, String uuid, java.util.UUID playerUuid) {
|
||||
// CraftBukkit end
|
||||
- Optional<CompoundTag> optional = this.load(name, uuid, ".dat"); // CraftBukkit
|
||||
+ Optional<CompoundTag> optional = this.load(name, uuid, ".dat", playerUuid); // CraftBukkit
|
||||
if (optional.isEmpty()) {
|
||||
this.backup(name, uuid, ".dat"); // CraftBukkit
|
||||
}
|
||||
|
||||
- return optional.or(() -> this.load(name, uuid, ".dat_old")).map(compoundTag -> { // CraftBukkit
|
||||
+ return optional.or(() -> this.load(name, uuid, ".dat_old", playerUuid)).map(compoundTag -> { // CraftBukkit
|
||||
int dataVersion = NbtUtils.getDataVersion(compoundTag, -1);
|
||||
compoundTag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundTag, dataVersion);
|
||||
// player.load(compoundTag); // CraftBukkit - handled above
|
||||
return compoundTag;
|
||||
});
|
||||
}
|
||||
+ // Leaf end - Async playerdata saving
|
||||
|
||||
// CraftBukkit start
|
||||
public File getPlayerDir() {
|
||||
@@ -0,0 +1,141 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
|
||||
Date: Sat, 19 Oct 2024 03:28:33 -0400
|
||||
Subject: [PATCH] Optimize nearby alive players for spawning
|
||||
|
||||
Use SpottedLeaf's nearby players system to avoid iterating over all online players
|
||||
and reduce the cost on predicate test
|
||||
|
||||
diff --git a/net/minecraft/world/entity/EntitySelector.java b/net/minecraft/world/entity/EntitySelector.java
|
||||
index bfd58eb04eee606ac0a8071de9bf75f46c35decb..6ee502128da76a22bfc6e9bae813cd4c759af5b0 100644
|
||||
--- a/net/minecraft/world/entity/EntitySelector.java
|
||||
+++ b/net/minecraft/world/entity/EntitySelector.java
|
||||
@@ -30,7 +30,7 @@ public final class EntitySelector {
|
||||
// Paper end - Ability to control player's insomnia and phantoms
|
||||
// Paper start - Affects Spawning API
|
||||
public static final Predicate<Entity> PLAYER_AFFECTS_SPAWNING = (entity) -> {
|
||||
- return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning;
|
||||
+ return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning; // Leaf - Optimize nearby alive players for spawning - diff on change
|
||||
};
|
||||
// Paper end - Affects Spawning API
|
||||
|
||||
diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java
|
||||
index 4395947fc8c719864ac2afde5e6bbb53da5129c2..a326483505245f6106b8ea72574bc2ab23f5402f 100644
|
||||
--- a/net/minecraft/world/entity/monster/Zombie.java
|
||||
+++ b/net/minecraft/world/entity/monster/Zombie.java
|
||||
@@ -363,7 +363,7 @@ public class Zombie extends Monster {
|
||||
if (SpawnPlacements.isSpawnPositionOk(type, level, blockPos)
|
||||
&& SpawnPlacements.checkSpawnRules(type, level, EntitySpawnReason.REINFORCEMENT, blockPos, level.random)) {
|
||||
zombie.setPos(i1, i2, i3);
|
||||
- if (!level.hasNearbyAlivePlayerThatAffectsSpawning(i1, i2, i3, 7.0) // Paper - affects spawning api
|
||||
+ if (!level.hasNearbyAlivePlayerThatAffectsSpawningForZombie(i1, i2, i3, 7.0) // Paper - affects spawning api // Leaf - Optimize nearby alive players for spawning
|
||||
&& level.isUnobstructed(zombie)
|
||||
&& level.noCollision(zombie)
|
||||
&& (zombie.canSpawnInLiquids() || !level.containsAnyLiquid(zombie.getBoundingBox()))) {
|
||||
diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java
|
||||
index 650ebce14d618076cec2066d134d2ae51a87076a..4137115888eeede519e10b87e520539b92eb75a8 100644
|
||||
--- a/net/minecraft/world/level/BaseSpawner.java
|
||||
+++ b/net/minecraft/world/level/BaseSpawner.java
|
||||
@@ -54,7 +54,7 @@ public abstract class BaseSpawner {
|
||||
}
|
||||
|
||||
public boolean isNearPlayer(Level level, BlockPos pos) {
|
||||
- return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API
|
||||
+ return level.hasNearbyAlivePlayerThatAffectsSpawningForSpawner(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API // Leaf - Optimize nearby alive players for spawning
|
||||
}
|
||||
|
||||
public void clientTick(Level level, BlockPos pos) {
|
||||
diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java
|
||||
index 892a7c1eb1b321ca6d5ca709142e7feae1220815..3e3592e40950d54fd4b730442764b1de877ec9df 100644
|
||||
--- a/net/minecraft/world/level/EntityGetter.java
|
||||
+++ b/net/minecraft/world/level/EntityGetter.java
|
||||
@@ -112,6 +112,89 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst
|
||||
// Paper end - optimise collisions
|
||||
}
|
||||
|
||||
+ // Leaf start - Optimize nearby alive players for spawning
|
||||
+ default boolean hasNearbyAlivePlayerThatAffectsSpawningForSpawner(double x, double y, double z, double range) {
|
||||
+ if (range > 33) {
|
||||
+ return hasNearbyAlivePlayerThatAffectsSpawningForLargerRangeSpawner(x, y, z, range);
|
||||
+ }
|
||||
+
|
||||
+ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos();
|
||||
+
|
||||
+ mutablePos.set(x, y, z);
|
||||
+
|
||||
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) this).moonrise$getNearbyPlayers().getPlayers(
|
||||
+ mutablePos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.GENERAL // NearbyPlayers.GENERAL_AREA_VIEW_DISTANCE: 33
|
||||
+ );
|
||||
+
|
||||
+ if (players == null) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ final net.minecraft.server.level.ServerPlayer[] raw = players.getRawDataUnchecked();
|
||||
+ final int len = players.size();
|
||||
+
|
||||
+ java.util.Objects.checkFromIndexSize(0, len, raw.length);
|
||||
+
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
+ final net.minecraft.server.level.ServerPlayer player = raw[i];
|
||||
+ final double distanceSqr = player.distanceToSqr(x, y, z);
|
||||
+
|
||||
+ if (range < 0.0D || distanceSqr < range * range) {
|
||||
+ if (!player.isSpectator() && player.isAlive() && player.affectsSpawning) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ default boolean hasNearbyAlivePlayerThatAffectsSpawningForLargerRangeSpawner(double x, double y, double z, double range) {
|
||||
+ for (Player player : this.players()) {
|
||||
+ double distanceSqr = player.distanceToSqr(x, y, z);
|
||||
+ if (range < 0.0D || distanceSqr < range * range) {
|
||||
+ if (!player.isSpectator() && player.isAlive() && player.affectsSpawning) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ default boolean hasNearbyAlivePlayerThatAffectsSpawningForZombie(int x, int y, int z, double range) {
|
||||
+ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos();
|
||||
+
|
||||
+ mutablePos.set(x, y, z);
|
||||
+
|
||||
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) this).moonrise$getNearbyPlayers().getPlayers(
|
||||
+ mutablePos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE // NearbyPlayers.PLAYER_SPAWN_TRACK_RANGE: 8
|
||||
+ );
|
||||
+
|
||||
+ if (players == null) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ final net.minecraft.server.level.ServerPlayer[] raw = players.getRawDataUnchecked();
|
||||
+ final int len = players.size();
|
||||
+
|
||||
+ java.util.Objects.checkFromIndexSize(0, len, raw.length);
|
||||
+
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
+ final net.minecraft.server.level.ServerPlayer player = raw[i];
|
||||
+ final double distanceSqr = player.distanceToSqr(x, y, z);
|
||||
+
|
||||
+ if (range < 0.0D || distanceSqr < range * range) {
|
||||
+ if (!player.isSpectator() && player.isAlive() && player.affectsSpawning) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+ // Leaf end - Optimize nearby alive players for spawning
|
||||
+
|
||||
// Paper start - Affects Spawning API
|
||||
default @Nullable Player findNearbyPlayer(Entity entity, double maxDistance, @Nullable Predicate<Entity> predicate) {
|
||||
return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate);
|
||||
@@ -0,0 +1,39 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
|
||||
Date: Tue, 22 Oct 2024 17:07:36 +0800
|
||||
Subject: [PATCH] Cache blockstate cache array
|
||||
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
index 331474bb33c8612283a0ec478c1ae8768180b22d..84f4d772bfe06383ca718b6a00d983e97e2e35f4 100644
|
||||
--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
@@ -1047,6 +1047,10 @@ public abstract class BlockBehaviour implements FeatureElement {
|
||||
static final class Cache {
|
||||
private static final Direction[] DIRECTIONS = Direction.values();
|
||||
private static final int SUPPORT_TYPE_COUNT = SupportType.values().length;
|
||||
+ // Leaf start - Cache blockstate cache array
|
||||
+ private static final SupportType[] SUPPORT_TYPE_VALUES = SupportType.values();
|
||||
+ private static final Direction.Axis[] DIRECTION_AXIS_VALUES = Direction.Axis.values();
|
||||
+ // Leaf end - Cache blockstate cache array
|
||||
protected final VoxelShape collisionShape;
|
||||
protected boolean largeCollisionShape;
|
||||
private final boolean[] faceSturdy;
|
||||
@@ -1065,7 +1069,7 @@ public abstract class BlockBehaviour implements FeatureElement {
|
||||
);
|
||||
} else {
|
||||
// Leaf start - Remove stream in BlockBehaviour cache blockstate
|
||||
- for (Direction.Axis axis : Direction.Axis.values()) {
|
||||
+ for (Direction.Axis axis : DIRECTION_AXIS_VALUES) { // Leaf - Cache blockstate cache array
|
||||
if (this.collisionShape.min(axis) < 0.0 || this.collisionShape.max(axis) > 1.0) {
|
||||
this.largeCollisionShape = true;
|
||||
break;
|
||||
@@ -1075,7 +1079,7 @@ public abstract class BlockBehaviour implements FeatureElement {
|
||||
this.faceSturdy = new boolean[DIRECTIONS.length * SUPPORT_TYPE_COUNT];
|
||||
|
||||
for (Direction direction : DIRECTIONS) {
|
||||
- for (SupportType supportType : SupportType.values()) {
|
||||
+ for (SupportType supportType : SUPPORT_TYPE_VALUES) { // Leaf - Cache blockstate cache array
|
||||
this.faceSturdy[getFaceSupportIndex(direction, supportType)] = supportType.isSupporting(
|
||||
state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, direction
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class AsyncChunkSending {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(AsyncChunkSending.class.getSimpleName());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AsyncPlayerDataSaving {
|
||||
|
||||
public static final ExecutorService IO_POOL = new ThreadPoolExecutor(
|
||||
1, 1, 0, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new com.google.common.util.concurrent.ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.setNameFormat("Leaf IO Thread")
|
||||
.setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
|
||||
.build(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
);
|
||||
|
||||
private AsyncPlayerDataSaving() {
|
||||
}
|
||||
|
||||
public static void save(Runnable runnable) {
|
||||
if (!AsyncPlayerDataSave.enabled) {
|
||||
runnable.run();
|
||||
} else {
|
||||
IO_POOL.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package org.dreeam.leaf.async.locate;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// Original project: https://github.com/thebrightspark/AsyncLocator
|
||||
public class AsyncLocator {
|
||||
|
||||
private static final ExecutorService LOCATING_EXECUTOR_SERVICE;
|
||||
|
||||
private AsyncLocator() {
|
||||
}
|
||||
|
||||
public static class AsyncLocatorThread extends TickThread {
|
||||
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
|
||||
|
||||
public AsyncLocatorThread(Runnable run, String name) {
|
||||
super(run, name, THREAD_COUNTER.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads;
|
||||
LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor(
|
||||
1,
|
||||
threads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("Leaf Async Locator Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static void shutdownExecutorService() {
|
||||
if (LOCATING_EXECUTOR_SERVICE != null) {
|
||||
LOCATING_EXECUTOR_SERVICE.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)}
|
||||
* and returns a {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<BlockPos> locate(
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using
|
||||
* {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a
|
||||
* {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<Pair<BlockPos, Holder<Structure>>> locate(
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
private static void doLocateLevel(
|
||||
CompletableFuture<BlockPos> completableFuture,
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPos);
|
||||
}
|
||||
|
||||
private static void doLocateChunkGenerator(
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
Pair<BlockPos, Holder<Structure>> foundPair = level.getChunkSource().getGenerator()
|
||||
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holder of the futures for an async locate task as well as providing some helper functions.
|
||||
* The completableFuture will be completed once the call to
|
||||
* {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the
|
||||
* result of it.
|
||||
* The taskFuture is the future for the {@link Runnable} itself in the executor service.
|
||||
*/
|
||||
public record LocateTask<T>(MinecraftServer server, CompletableFuture<T> completableFuture, Future<?> taskFuture) {
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action.
|
||||
* Bear in mind that the action will be executed from the task's thread. If you intend to change any game data,
|
||||
* it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed
|
||||
* on the main server thread instead.
|
||||
*/
|
||||
public LocateTask<T> then(Consumer<T> action) {
|
||||
completableFuture.thenAccept(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
|
||||
* thread.
|
||||
*/
|
||||
public LocateTask<T> thenOnServerThread(Consumer<T> action) {
|
||||
completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that cancels both completableFuture and taskFuture.
|
||||
*/
|
||||
public void cancel() {
|
||||
taskFuture.cancel(true);
|
||||
completableFuture.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.dreeam.leaf.async.tracker;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class MultithreadedTracker {
|
||||
|
||||
private static final String THREAD_PREFIX = "Leaf Async Tracker";
|
||||
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
getThreadFactory(),
|
||||
getRejectedPolicy()
|
||||
);
|
||||
|
||||
private MultithreadedTracker() {
|
||||
}
|
||||
|
||||
public static Executor getTrackerExecutor() {
|
||||
return trackerExecutor;
|
||||
}
|
||||
|
||||
public static void tick(ChunkSystemServerLevel level) {
|
||||
try {
|
||||
if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
||||
tickAsync(level);
|
||||
} else {
|
||||
tickAsyncWithCompatMode(level);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error occurred while executing async task.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void tickAsync(ChunkSystemServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
|
||||
// Move tracking to off-main
|
||||
trackerExecutor.execute(() -> {
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length];
|
||||
int index = 0;
|
||||
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
}
|
||||
|
||||
// batch submit tasks
|
||||
trackerExecutor.execute(() -> {
|
||||
for (final Runnable sendChanges : sendChangesTasks) {
|
||||
if (sendChanges == null) continue;
|
||||
|
||||
sendChanges.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Original ChunkMap#newTrackerTick of Paper
|
||||
// Just for diff usage for future update
|
||||
private static void tickOriginal(ServerLevel level) {
|
||||
final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup();
|
||||
|
||||
final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
|
||||
final Entity entity = trackerEntitiesRaw[i];
|
||||
final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers);
|
||||
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers()
|
||||
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCorePoolSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int getMaxPoolSize() {
|
||||
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads;
|
||||
}
|
||||
|
||||
private static long getKeepAliveTime() {
|
||||
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive;
|
||||
}
|
||||
|
||||
private static BlockingQueue<Runnable> getQueueImpl() {
|
||||
final int queueCapacity = org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}
|
||||
|
||||
private static @NotNull ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setThreadFactory(MultithreadedTrackerThread::new)
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {
|
||||
return (rejectedTask, executor) -> {
|
||||
BlockingQueue<Runnable> workQueue = executor.getQueue();
|
||||
|
||||
if (!executor.isShutdown()) {
|
||||
if (!workQueue.isEmpty()) {
|
||||
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
|
||||
|
||||
workQueue.drainTo(pendingTasks);
|
||||
|
||||
for (Runnable pendingTask : pendingTasks) {
|
||||
pendingTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
rejectedTask.run();
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
|
||||
LOGGER.warn("Async entity tracker is busy! Tracking tasks will be done in the server thread. Increasing max-threads in Leaf config may help.");
|
||||
lastWarnMillis = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class MultithreadedTrackerThread extends Thread {
|
||||
|
||||
public MultithreadedTrackerThread(Runnable runnable) {
|
||||
super(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.dreeam.leaf.async.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class SparklyPaperServerLevelTickExecutorThreadFactory implements ThreadFactory {
|
||||
|
||||
private final String worldName;
|
||||
|
||||
public SparklyPaperServerLevelTickExecutorThreadFactory(final String worldName) {
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NotNull Runnable runnable) {
|
||||
TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "Leaf World Ticking Thread - " + this.worldName);
|
||||
|
||||
if (tickThread.isDaemon()) {
|
||||
tickThread.setDaemon(false);
|
||||
}
|
||||
|
||||
if (tickThread.getPriority() != 5) {
|
||||
tickThread.setPriority(5);
|
||||
}
|
||||
|
||||
return tickThread;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncBlockFinding extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
public static boolean asyncBlockFindingInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
**Experimental feature**
|
||||
This moves the expensive search calculations to a background thread while
|
||||
keeping the actual block validation on the main thread.""",
|
||||
"""
|
||||
这会将昂贵的搜索计算移至后台线程, 同时在主线程上保持实际的方块验证.""");
|
||||
|
||||
if (!asyncBlockFindingInitialized) {
|
||||
asyncBlockFindingInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncChunkSend extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-chunk-send";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
Makes chunk packet preparation and sending asynchronous to improve server performance.
|
||||
This can significantly reduce main thread load when many players are loading chunks.""",
|
||||
"""
|
||||
使区块数据包准备和发送异步化以提高服务器性能.
|
||||
当许多玩家同时加载区块时, 这可以显著减少主线程负载.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class AsyncLocator extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int asyncLocatorThreads = 0;
|
||||
public static int asyncLocatorKeepalive = 60;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Whether or not asynchronous locator should be enabled.
|
||||
This offloads structure locating to other threads.
|
||||
Only for locate command, dolphin treasure finding and eye of ender currently.""",
|
||||
"""
|
||||
是否启用异步结构搜索.
|
||||
目前可用于 /locate 指令, 海豚寻宝和末影之眼.""");
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads);
|
||||
asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive);
|
||||
|
||||
if (asyncLocatorThreads <= 0)
|
||||
asyncLocatorThreads = 1;
|
||||
if (!enabled)
|
||||
asyncLocatorThreads = 0;
|
||||
else
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class AsyncMobSpawning extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-mob-spawning";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
public static boolean asyncMobSpawningInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Whether or not asynchronous mob spawning should be enabled.
|
||||
On servers with many entities, this can improve performance by up to 15%. You must have
|
||||
paper's per-player-mob-spawns setting set to true for this to work.
|
||||
One quick note - this does not actually spawn mobs async (that would be very unsafe).
|
||||
This just offloads some expensive calculations that are required for mob spawning.""",
|
||||
"""
|
||||
是否异步化生物生成.
|
||||
在实体较多的服务器上, 异步生成可最高带来15%的性能提升.
|
||||
须在Paper配置文件中打开 per-player-mob-spawns 才能生效.""");
|
||||
|
||||
// This prevents us from changing the value during a reload.
|
||||
if (!asyncMobSpawningInitialized) {
|
||||
asyncMobSpawningInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncPlayerDataSave extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
**Experimental feature, may have data lost in some circumstances!**
|
||||
Make PlayerData saving asynchronously.""",
|
||||
"""
|
||||
**实验性功能, 在部分场景下可能丢失玩家数据!**
|
||||
异步保存玩家数据.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class MultithreadedTracker extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static boolean compatModeEnabled = false;
|
||||
public static int asyncEntityTrackerMaxThreads = 0;
|
||||
public static int asyncEntityTrackerKeepalive = 60;
|
||||
public static int asyncEntityTrackerQueueSize = 0;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Make entity tracking saving asynchronously, can improve performance significantly,
|
||||
especially in some massive entities in small area situations.""",
|
||||
"""
|
||||
异步实体跟踪,
|
||||
在实体数量多且密集的情况下效果明显.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased("""
|
||||
Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed,
|
||||
Compat mode fixed visible issue with player type NPCs of Citizens,
|
||||
But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""",
|
||||
"""
|
||||
是否启用兼容模式,
|
||||
如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""));
|
||||
asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads);
|
||||
asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive);
|
||||
asyncEntityTrackerQueueSize = config.getInt(getBasePath() + ".queue-size", asyncEntityTrackerQueueSize);
|
||||
|
||||
if (asyncEntityTrackerMaxThreads < 0)
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
|
||||
else if (asyncEntityTrackerMaxThreads == 0)
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
|
||||
|
||||
if (!enabled)
|
||||
asyncEntityTrackerMaxThreads = 0;
|
||||
else
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads);
|
||||
|
||||
if (asyncEntityTrackerQueueSize <= 0)
|
||||
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking";
|
||||
} // TODO: Correct config key when stable
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
public static int threads = 8;
|
||||
public static boolean logContainerCreationStacktraces = false;
|
||||
public static boolean disableHardThrow = false;
|
||||
public static boolean runAsyncTasksSync = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
**Experimental feature**
|
||||
Enables parallel world ticking to improve performance on multi-core systems..""",
|
||||
"""
|
||||
**实验性功能**
|
||||
启用并行世界处理以提高多核系统的性能.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
threads = config.getInt(getBasePath() + ".threads", threads);
|
||||
threads = enabled ? threads : 0;
|
||||
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
|
||||
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
|
||||
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
|
||||
disableHardThrow = enabled && disableHardThrow;
|
||||
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
|
||||
runAsyncTasksSync = enabled && runAsyncTasksSync;
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,9 @@ public class GaleWorldConfiguration extends ConfigurationPart {
|
||||
public int duration = 100;
|
||||
public int nearbyItemMaxAge = 1200;
|
||||
public int checkForMinecartNearItemInterval = 20;
|
||||
// Still recommend to turn-off `checkForMinecartNearItemWhileActive`
|
||||
// Since `Reduce-hopper-item-checks.patch` will cause lag under massive dropped items
|
||||
public boolean checkForMinecartNearItemWhileActive = false; // Leaf - Reduce active items finding hopper nearby check
|
||||
public boolean checkForMinecartNearItemWhileInactive = true;
|
||||
public double maxItemHorizontalDistance = 24.0;
|
||||
public double maxItemVerticalDistance = 4.0;
|
||||
@@ -132,7 +135,7 @@ public class GaleWorldConfiguration extends ConfigurationPart {
|
||||
|
||||
}
|
||||
|
||||
public boolean arrowMovementResetsDespawnCounter = true; // Gale - Purpur - make arrow movement resetting despawn counter configurable
|
||||
public boolean arrowMovementResetsDespawnCounter = false; // Gale - Purpur - make arrow movement resetting despawn counter configurable // Leaf - KeYi - Disable arrow despawn counter by default
|
||||
public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
|
||||
public double entityWakeUpDurationRatioStandardDeviation = 0.2; // Gale - variable entity wake-up duration
|
||||
public boolean hideFlamesOnEntitiesWithFireResistance = false; // Gale - Slice - hide flames on entities with fire resistance
|
||||
|
||||
@@ -101,10 +101,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
return switch (distance) {
|
||||
case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW);
|
||||
case 0 -> text("You are running the latest version", NamedTextColor.GREEN);
|
||||
case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW);
|
||||
default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
|
||||
case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur - Rebrand
|
||||
case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur - Rebrand
|
||||
case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur - Rebrand
|
||||
default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur - Rebrand
|
||||
.append(Component.newline())
|
||||
.append(text("Download the new version at: ")
|
||||
.append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
|
||||
@@ -149,6 +149,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
|
||||
return text("Previous: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); // Purpur - Rebrand
|
||||
}
|
||||
}
|
||||
|
||||
95
leaf-server/src/main/java/su/plo/matter/Globals.java
Normal file
95
leaf-server/src/main/java/su/plo/matter/Globals.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package su.plo.matter;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Globals {
|
||||
|
||||
public static final int WORLD_SEED_LONGS = 16;
|
||||
public static final int WORLD_SEED_BITS = WORLD_SEED_LONGS * 64;
|
||||
|
||||
public static final long[] worldSeed = new long[WORLD_SEED_LONGS];
|
||||
public static final ThreadLocal<Integer> dimension = ThreadLocal.withInitial(() -> 0);
|
||||
|
||||
public enum Salt {
|
||||
UNDEFINED,
|
||||
BASTION_FEATURE,
|
||||
WOODLAND_MANSION_FEATURE,
|
||||
MINESHAFT_FEATURE,
|
||||
BURIED_TREASURE_FEATURE,
|
||||
NETHER_FORTRESS_FEATURE,
|
||||
PILLAGER_OUTPOST_FEATURE,
|
||||
GEODE_FEATURE,
|
||||
NETHER_FOSSIL_FEATURE,
|
||||
OCEAN_MONUMENT_FEATURE,
|
||||
RUINED_PORTAL_FEATURE,
|
||||
POTENTIONAL_FEATURE,
|
||||
GENERATE_FEATURE,
|
||||
JIGSAW_PLACEMENT,
|
||||
STRONGHOLDS,
|
||||
POPULATION,
|
||||
DECORATION,
|
||||
SLIME_CHUNK
|
||||
}
|
||||
|
||||
public static void setupGlobals(ServerLevel world) {
|
||||
if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) return;
|
||||
|
||||
long[] seed = world.getServer().getWorldData().worldGenOptions().featureSeed();
|
||||
System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS);
|
||||
int worldIndex = Iterables.indexOf(world.getServer().levelKeys(), it -> it == world.dimension());
|
||||
if (worldIndex == -1)
|
||||
worldIndex = world.getServer().levelKeys().size(); // if we are in world construction it may not have been added to the map yet
|
||||
dimension.set(worldIndex);
|
||||
}
|
||||
|
||||
public static long[] createRandomWorldSeed() {
|
||||
long[] seed = new long[WORLD_SEED_LONGS];
|
||||
SecureRandom rand = new SecureRandom();
|
||||
for (int i = 0; i < WORLD_SEED_LONGS; i++) {
|
||||
seed[i] = rand.nextLong();
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
// 1024-bit string -> 16 * 64 long[]
|
||||
public static Optional<long[]> parseSeed(String seedStr) {
|
||||
if (seedStr.isEmpty()) return Optional.empty();
|
||||
|
||||
if (seedStr.length() != WORLD_SEED_BITS) {
|
||||
throw new IllegalArgumentException("Secure seed length must be " + WORLD_SEED_BITS + "-bit but found " + seedStr.length() + "-bit.");
|
||||
}
|
||||
|
||||
long[] seed = new long[WORLD_SEED_LONGS];
|
||||
|
||||
for (int i = 0; i < WORLD_SEED_LONGS; i++) {
|
||||
int start = i * 64;
|
||||
int end = start + 64;
|
||||
String seedSection = seedStr.substring(start, end);
|
||||
|
||||
BigInteger seedInDecimal = new BigInteger(seedSection, 2);
|
||||
seed[i] = seedInDecimal.longValue();
|
||||
}
|
||||
|
||||
return Optional.of(seed);
|
||||
}
|
||||
|
||||
// 16 * 64 long[] -> 1024-bit string
|
||||
public static String seedToString(long[] seed) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (long longV : seed) {
|
||||
// Convert to 64-bit binary string per long
|
||||
// Use format to keep 64-bit length, and use 0 to complete space
|
||||
String binaryStr = String.format("%64s", Long.toBinaryString(longV)).replace(' ', '0');
|
||||
|
||||
sb.append(binaryStr);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
74
leaf-server/src/main/java/su/plo/matter/Hashing.java
Normal file
74
leaf-server/src/main/java/su/plo/matter/Hashing.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package su.plo.matter;
|
||||
|
||||
public class Hashing {
|
||||
|
||||
// https://en.wikipedia.org/wiki/BLAKE_(hash_function)
|
||||
// https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java
|
||||
|
||||
private final static long[] blake2b_IV = {
|
||||
0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL,
|
||||
0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL,
|
||||
0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L
|
||||
};
|
||||
|
||||
private final static byte[][] blake2b_sigma = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||
{7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||
{9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||
{2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||
{6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}
|
||||
};
|
||||
|
||||
public static long[] hashWorldSeed(long[] worldSeed) {
|
||||
long[] result = blake2b_IV.clone();
|
||||
result[0] ^= 0x01010040;
|
||||
hash(worldSeed, result, new long[16], 0, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void hash(long[] message, long[] chainValue, long[] internalState, long messageOffset, boolean isFinal) {
|
||||
assert message.length == 16;
|
||||
assert chainValue.length == 8;
|
||||
assert internalState.length == 16;
|
||||
|
||||
System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
|
||||
System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4);
|
||||
internalState[12] = messageOffset ^ blake2b_IV[4];
|
||||
internalState[13] = blake2b_IV[5];
|
||||
if (isFinal) internalState[14] = ~blake2b_IV[6];
|
||||
internalState[15] = blake2b_IV[7];
|
||||
|
||||
for (int round = 0; round < 12; round++) {
|
||||
G(message[blake2b_sigma[round][0]], message[blake2b_sigma[round][1]], 0, 4, 8, 12, internalState);
|
||||
G(message[blake2b_sigma[round][2]], message[blake2b_sigma[round][3]], 1, 5, 9, 13, internalState);
|
||||
G(message[blake2b_sigma[round][4]], message[blake2b_sigma[round][5]], 2, 6, 10, 14, internalState);
|
||||
G(message[blake2b_sigma[round][6]], message[blake2b_sigma[round][7]], 3, 7, 11, 15, internalState);
|
||||
G(message[blake2b_sigma[round][8]], message[blake2b_sigma[round][9]], 0, 5, 10, 15, internalState);
|
||||
G(message[blake2b_sigma[round][10]], message[blake2b_sigma[round][11]], 1, 6, 11, 12, internalState);
|
||||
G(message[blake2b_sigma[round][12]], message[blake2b_sigma[round][13]], 2, 7, 8, 13, internalState);
|
||||
G(message[blake2b_sigma[round][14]], message[blake2b_sigma[round][15]], 3, 4, 9, 14, internalState);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
chainValue[i] ^= internalState[i] ^ internalState[i + 8];
|
||||
}
|
||||
}
|
||||
|
||||
private static void G(long m1, long m2, int posA, int posB, int posC, int posD, long[] internalState) {
|
||||
internalState[posA] = internalState[posA] + internalState[posB] + m1;
|
||||
internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32);
|
||||
internalState[posC] = internalState[posC] + internalState[posD];
|
||||
internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
|
||||
internalState[posA] = internalState[posA] + internalState[posB] + m2;
|
||||
internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16);
|
||||
internalState[posC] = internalState[posC] + internalState[posD];
|
||||
internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package su.plo.matter;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class WorldgenCryptoRandom extends WorldgenRandom {
|
||||
|
||||
// hash the world seed to guard against badly chosen world seeds
|
||||
private static final long[] HASHED_ZERO_SEED = Hashing.hashWorldSeed(new long[Globals.WORLD_SEED_LONGS]);
|
||||
private static final ThreadLocal<long[]> LAST_SEEN_WORLD_SEED = ThreadLocal.withInitial(() -> new long[Globals.WORLD_SEED_LONGS]);
|
||||
private static final ThreadLocal<long[]> HASHED_WORLD_SEED = ThreadLocal.withInitial(() -> HASHED_ZERO_SEED);
|
||||
|
||||
private final long[] worldSeed = new long[Globals.WORLD_SEED_LONGS];
|
||||
private final long[] randomBits = new long[8];
|
||||
private int randomBitIndex;
|
||||
private static final int MAX_RANDOM_BIT_INDEX = 64 * 8;
|
||||
private static final int LOG2_MAX_RANDOM_BIT_INDEX = 9;
|
||||
private long counter;
|
||||
private final long[] message = new long[16];
|
||||
private final long[] cachedInternalState = new long[16];
|
||||
|
||||
public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) {
|
||||
super(org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L));
|
||||
if (typeSalt != null) {
|
||||
this.setSecureSeed(x, z, typeSalt, salt);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSecureSeed(int x, int z, Globals.Salt typeSalt, long salt) {
|
||||
System.arraycopy(Globals.worldSeed, 0, this.worldSeed, 0, Globals.WORLD_SEED_LONGS);
|
||||
message[0] = ((long) x << 32) | ((long) z & 0xffffffffL);
|
||||
message[1] = ((long) Globals.dimension.get() << 32) | (salt & 0xffffffffL);
|
||||
message[2] = typeSalt.ordinal();
|
||||
message[3] = counter = 0;
|
||||
randomBitIndex = MAX_RANDOM_BIT_INDEX;
|
||||
}
|
||||
|
||||
private long[] getHashedWorldSeed() {
|
||||
if (!Arrays.equals(worldSeed, LAST_SEEN_WORLD_SEED.get())) {
|
||||
HASHED_WORLD_SEED.set(Hashing.hashWorldSeed(worldSeed));
|
||||
System.arraycopy(worldSeed, 0, LAST_SEEN_WORLD_SEED.get(), 0, Globals.WORLD_SEED_LONGS);
|
||||
}
|
||||
return HASHED_WORLD_SEED.get();
|
||||
}
|
||||
|
||||
private void moreRandomBits() {
|
||||
message[3] = counter++;
|
||||
System.arraycopy(getHashedWorldSeed(), 0, randomBits, 0, 8);
|
||||
Hashing.hash(message, randomBits, cachedInternalState, 64, true);
|
||||
}
|
||||
|
||||
private long getBits(int count) {
|
||||
if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) {
|
||||
moreRandomBits();
|
||||
randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
}
|
||||
|
||||
int alignment = randomBitIndex & 63;
|
||||
if ((randomBitIndex >>> 6) == ((randomBitIndex + count) >>> 6)) {
|
||||
long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << count) - 1);
|
||||
randomBitIndex += count;
|
||||
return result;
|
||||
} else {
|
||||
long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << (64 - alignment)) - 1);
|
||||
randomBitIndex += count;
|
||||
if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) {
|
||||
moreRandomBits();
|
||||
randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
}
|
||||
alignment = randomBitIndex & 63;
|
||||
result <<= alignment;
|
||||
result |= (randomBits[randomBitIndex >>> 6] >>> (64 - alignment)) & ((1L << alignment) - 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RandomSource fork() {
|
||||
WorldgenCryptoRandom fork = new WorldgenCryptoRandom(0, 0, null, 0);
|
||||
|
||||
System.arraycopy(Globals.worldSeed, 0, fork.worldSeed, 0, Globals.WORLD_SEED_LONGS);
|
||||
fork.message[0] = this.message[0];
|
||||
fork.message[1] = this.message[1];
|
||||
fork.message[2] = this.message[2];
|
||||
fork.message[3] = this.message[3];
|
||||
fork.randomBitIndex = this.randomBitIndex;
|
||||
fork.counter = this.counter;
|
||||
fork.nextLong();
|
||||
|
||||
return fork;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next(int bits) {
|
||||
return (int) getBits(bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeCount(int count) {
|
||||
randomBitIndex += count;
|
||||
if (randomBitIndex >= MAX_RANDOM_BIT_INDEX * 2) {
|
||||
randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
counter += randomBitIndex >>> LOG2_MAX_RANDOM_BIT_INDEX;
|
||||
randomBitIndex &= MAX_RANDOM_BIT_INDEX - 1;
|
||||
randomBitIndex += MAX_RANDOM_BIT_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
int bits = Mth.ceillog2(bound);
|
||||
int result;
|
||||
do {
|
||||
result = (int) getBits(bits);
|
||||
} while (result >= bound);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
return getBits(64);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextDouble() {
|
||||
return getBits(53) * 0x1.0p-53;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setDecorationSeed(long worldSeed, int blockX, int blockZ) {
|
||||
setSecureSeed(blockX, blockZ, Globals.Salt.POPULATION, 0);
|
||||
return ((long) blockX << 32) | ((long) blockZ & 0xffffffffL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeatureSeed(long populationSeed, int index, int step) {
|
||||
setSecureSeed((int) (populationSeed >> 32), (int) populationSeed, Globals.Salt.DECORATION, index + 10000L * step);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLargeFeatureSeed(long worldSeed, int chunkX, int chunkZ) {
|
||||
super.setLargeFeatureSeed(worldSeed, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLargeFeatureWithSalt(long worldSeed, int regionX, int regionZ, int salt) {
|
||||
super.setLargeFeatureWithSalt(worldSeed, regionX, regionZ, salt);
|
||||
}
|
||||
|
||||
public static RandomSource seedSlimeChunk(int chunkX, int chunkZ) {
|
||||
return new WorldgenCryptoRandom(chunkX, chunkZ, Globals.Salt.SLIME_CHUNK, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user