9
0
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:
Dreeam
2025-04-04 16:06:47 -04:00
parent 91887de295
commit f96b3e9f76
39 changed files with 135 additions and 224 deletions

View File

@@ -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) {
}

View File

@@ -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() {

View File

@@ -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
}
}
});

View File

@@ -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(

View File

@@ -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()) {

View File

@@ -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()) {

View File

@@ -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);
}

View File

@@ -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
);

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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) {
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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
);

View File

@@ -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());
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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
}
}

View 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();
}
}

View 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
}
}

View File

@@ -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);
}
}