diff --git a/leaf-server/minecraft-patches/features/0193-optimize-mob-despawn.patch b/leaf-server/minecraft-patches/features/0193-optimize-mob-despawn.patch index 6df4070b..a352446c 100644 --- a/leaf-server/minecraft-patches/features/0193-optimize-mob-despawn.patch +++ b/leaf-server/minecraft-patches/features/0193-optimize-mob-despawn.patch @@ -38,15 +38,17 @@ index 105423d248df2647d78e6f1a288e9b41003ad437..06294e4036991a803deefc20e35160c5 final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index e563b636a05a299d860a334987594dbd5c7d3511..cddeee3de328ac736a56caad8af1177e0e6e6ad6 100644 +index 6938961cae73d13a12f30a6ea46725af0728e11d..c18f6f3f8307ae1d221a38cd1dc2191cdc4e767a 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5100,6 +5100,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5100,6 +5100,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public void checkDespawn() { } + // Leaf start - optimize despawn -+ public void leafCheckDespawn() { ++ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) { ++ } ++ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) { + } + // Leaf end - optimize despawn + @@ -54,23 +56,54 @@ index e563b636a05a299d860a334987594dbd5c7d3511..cddeee3de328ac736a56caad8af1177e return Leashable.createQuadLeashOffsets(this, 0.0, 0.5, 0.5, 0.0); } diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 751c8c17c3c2968fad358ae8807eaca54de82a34..28523643d0789c6f472dfa92c3c0a32f0bcfc7b5 100644 +index 751c8c17c3c2968fad358ae8807eaca54de82a34..2eafd8453a70e536bea8c125296fc49a7e3677bf 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java -@@ -762,6 +762,22 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -762,6 +762,53 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } } + // Leaf start - optimize despawn + @Override -+ public void leafCheckDespawn() { ++ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) { + if (isRemoved()) { -+ return; ++ } else if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } else if (this.isPersistenceRequired() || this.requiresCustomPersistence()) { ++ this.noActionTime = 0; ++ } else { ++ map.checkDespawn(this); + } -+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { ++ } ++ @Override ++ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) { ++ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldActuallyDespawnInPeaceful()) { //Paper - allow changing despawnInPeaceful + this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { -+ ((ServerLevel) level()).despawnMap.checkDespawn(this); ++ Entity nearestPlayer = map.checkDespawnFallback(this); // Paper - Affects Spawning API ++ if (nearestPlayer != null) { ++ // Paper start - Configurable despawn distances ++ final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); ++ final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape; ++ final double dy = Math.abs(nearestPlayer.getY() - this.getY()); ++ final double dySqr = Mth.square(dy); ++ final double dxSqr = Mth.square(nearestPlayer.getX() - this.getX()); ++ final double dzSqr = Mth.square(nearestPlayer.getZ() - this.getZ()); ++ final double distanceSquared = dxSqr + dzSqr + dySqr; ++ // Despawn if hard/soft limit is exceeded ++ if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } ++ ++ if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) { ++ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } ++ } else { ++ // Paper end - Configurable despawn distances ++ this.noActionTime = 0; ++ } ++ } + } else { + this.noActionTime = 0; + } @@ -81,16 +114,19 @@ index 751c8c17c3c2968fad358ae8807eaca54de82a34..28523643d0789c6f472dfa92c3c0a32f protected final void serverAiStep() { this.noActionTime++; diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index de09a91b89661118e460842453e33f383ea08a94..e9fc2bf959949a8589a9ab87e00c85ffbe598a83 100644 +index de09a91b89661118e460842453e33f383ea08a94..e3cdff55261b2ff2c3d1cb1cf46b633a340458c9 100644 --- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -956,6 +956,12 @@ public class EnderDragon extends Mob implements Enemy { +@@ -956,6 +956,15 @@ public class EnderDragon extends Mob implements Enemy { public void checkDespawn() { } + // Leaf start - optimize despawn + @Override -+ public void leafCheckDespawn() { ++ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) { ++ } ++ @Override ++ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) { + } + // Leaf end - optimize despawn + @@ -98,21 +134,32 @@ index de09a91b89661118e460842453e33f383ea08a94..e9fc2bf959949a8589a9ab87e00c85ff return this.subEntities; } diff --git a/net/minecraft/world/entity/boss/wither/WitherBoss.java b/net/minecraft/world/entity/boss/wither/WitherBoss.java -index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..d8336fc8852b9939f341aedd17403913c7dadcf9 100644 +index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..3171868523bc9b767cb9da5f58c43890fc8214da 100644 --- a/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -695,6 +695,21 @@ public class WitherBoss extends Monster implements RangedAttackMob { +@@ -695,6 +695,32 @@ public class WitherBoss extends Monster implements RangedAttackMob { } } + + // Leaf start - optimize despawn + @Override -+ public void leafCheckDespawn() { ++ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) { + if (isRemoved()) { + return; + } -+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { ++ if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } else { ++ this.noActionTime = 0; ++ } ++ } ++ @Override ++ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) { ++ if (isRemoved()) { ++ return; ++ } ++ if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + this.noActionTime = 0; @@ -124,18 +171,24 @@ index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..d8336fc8852b9939f341aedd17403913 public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) { return false; diff --git a/net/minecraft/world/entity/projectile/ShulkerBullet.java b/net/minecraft/world/entity/projectile/ShulkerBullet.java -index 00154ba80175bcb07b3378f19514fec1700c94e9..46d30c3273d31dbda06275ad098c6c2b9b2eed14 100644 +index 00154ba80175bcb07b3378f19514fec1700c94e9..ec0a30d76df2a20ceb895b2837b0f24bfe887aba 100644 --- a/net/minecraft/world/entity/projectile/ShulkerBullet.java +++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java -@@ -197,6 +197,16 @@ public class ShulkerBullet extends Projectile { +@@ -197,6 +197,22 @@ public class ShulkerBullet extends Projectile { } } + + // Leaf start - optimize despawn + @Override -+ public void leafCheckDespawn() { -+ if (!isRemoved() && this.level().getDifficulty() == Difficulty.PEACEFUL) { ++ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) { ++ if (!isRemoved() && map.difficultyIsPeaceful) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } ++ } ++ @Override ++ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) { ++ if (!isRemoved() && map.difficultyIsPeaceful) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + } diff --git a/leaf-server/minecraft-patches/features/0304-Only-update-frozen-ticks-if-changed.patch b/leaf-server/minecraft-patches/features/0299-Only-update-frozen-ticks-if-changed.patch similarity index 87% rename from leaf-server/minecraft-patches/features/0304-Only-update-frozen-ticks-if-changed.patch rename to leaf-server/minecraft-patches/features/0299-Only-update-frozen-ticks-if-changed.patch index 52e65d9c..c9d64c1d 100644 --- a/leaf-server/minecraft-patches/features/0304-Only-update-frozen-ticks-if-changed.patch +++ b/leaf-server/minecraft-patches/features/0299-Only-update-frozen-ticks-if-changed.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Only update frozen ticks if changed diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index fdb7e7b00dd9223f8dc0ce174203d6835fc3962e..e3d6e802fdc72afa54e22fb1e2dc6030de33f091 100644 +index 1799cdc0b6c1e585e7e1eeab3828ea0252ae2097..20727a5abc7fad5a70eb2b98aa3699b054782a3b 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -3715,7 +3715,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin +@@ -3707,7 +3707,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin if (this.level() instanceof ServerLevel serverLevel) { if ((!this.isInPowderSnow || !this.canFreeze()) && !this.freezeLocked) { // Paper - Freeze Tick Lock API diff --git a/leaf-server/paper-patches/features/0057-optimize-despawn.patch b/leaf-server/paper-patches/features/0057-optimize-despawn.patch index a9c604cd..7ce5033d 100644 --- a/leaf-server/paper-patches/features/0057-optimize-despawn.patch +++ b/leaf-server/paper-patches/features/0057-optimize-despawn.patch @@ -5,7 +5,7 @@ Subject: [PATCH] optimize despawn diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..012c500c4a5a06be92fa17c141b15ca6547c8483 100644 +index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..74d0c6ed2d779b8d02870da74379887e3c460361 100644 --- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java @@ -179,7 +179,7 @@ public class WorldConfiguration extends ConfigurationPart { @@ -13,7 +13,7 @@ index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..012c500c4a5a06be92fa17c141b15ca6 public Reference2IntMap spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); @MergeMap - public Map despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault())); -+ public Map despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - replace EnumMap optimize despawn ++ public Map despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - optimize despawn - replace with EnumMap public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID; @MergeMap public Reference2IntMap ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeDespawn.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeDespawn.java index b7e28d75..4649192b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeDespawn.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeDespawn.java @@ -17,9 +17,8 @@ public class OptimizeDespawn extends ConfigModules { public void onLoaded() { enabled = config.getBoolean(getBasePath(), enabled); if (enabled) { - gg.pufferfish.pufferfish.simd.SIMDDetection.initialize(); - if (!Boolean.getBoolean("Leaf.enableFMA") || !SIMDDetection.isEnabled()) { - LOGGER.info("NOTE: Recommend enabling FMA and Vector API to work with optimize-mob-despawn."); + if (!Boolean.getBoolean("Leaf.enableFMA")) { + LOGGER.info("NOTE: Recommend enabling FMA to work with optimize-mob-despawn."); } } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree2D.java b/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree2D.java new file mode 100644 index 00000000..7b00c178 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree2D.java @@ -0,0 +1,253 @@ +package org.dreeam.leaf.util; + +@org.jspecify.annotations.NullMarked +public final class KDTree2D { + + private static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA"); + + private static final double[] EMPTY_DOUBLES = {}; + private static final int[] EMPTY_INTS = {}; + private static final Node[] EMPTY_NODES = {}; + + private static final int INITIAL_CAPACITY = 8; + + /// indicate empty on [#search] + /// indicate leaf node on [#nrl] + private static final int SENTINEL = -1; + private Node[] stack = EMPTY_NODES; + private int[] search = EMPTY_INTS; + /// Right node index for internal or [#SENTINEL] for leaf + private int[] nrl = EMPTY_INTS; + // split for internal or x coordinate for leaf + private double[] nxl = EMPTY_DOUBLES; + // y coordinate for leaf + private double[] nyl = EMPTY_DOUBLES; + // index for leaf + private int[] nil = EMPTY_INTS; + + public void build(final double[][] coords, final int[] indices) { + if (indices.length == 0 || coords.length != 2) { + ensureSearch(0, 0); + return; + } + + int st = 0; + ensureConstruct(st); + stack[st++] = new Node(SENTINEL, false, 0, indices.length, 0); + int nodeLen = 0; + while (st != 0) { + ensureNode(nodeLen); + final Node n = stack[--st]; + final int curr = nodeLen++; + if (n.len() <= 1) { + final int p = indices[n.offset()]; + nrl[curr] = SENTINEL; + nxl[curr] = coords[0][p]; + nyl[curr] = coords[1][p]; + nil[curr] = p; + } else { + final int axis = (n.depth()) % 2; + final int med = (n.len() - 1) / 2; + final int k = n.offset() + med; + final double[] coord = coords[axis]; + PartialSort.nthElement(indices, coord, n.offset(), n.offset() + n.len() - 1, k); + + nxl[curr] = coord[indices[k]]; + + ensureConstruct(st); + stack[st++] = new Node(curr, false, n.offset() + med + 1, n.len() - med - 1, n.depth() + 1); + stack[st++] = new Node(curr, true, n.offset(), med + 1, n.depth() + 1); + } + if (n.parent() != SENTINEL) { + nrl[n.parent()] = n.left() ? SENTINEL : curr; + } + } + + ensureSearch(indices.length, nodeLen); + } + + private void ensureSearch(final int length, final int nodeLen) { + if (search.length < nodeLen + 8) { + search = new int[nodeLen + 8]; + } + search[0] = length == 0 ? SENTINEL : 0; + } + + private void ensureConstruct(final int st) { + if (st != stack.length && st + 1 != stack.length) { + return; + } + final int newLen = stack.length + 2; + final Node[] b = new Node[Math.max(INITIAL_CAPACITY, newLen + (newLen >> 1))]; + System.arraycopy(stack, 0, b, 0, st); + stack = b; + } + + private void ensureNode(final int preserve) { + int length = preserve + 1; + if (length < nrl.length) { + return; + } + length += length >> 1; + if (length < INITIAL_CAPACITY) { + length = INITIAL_CAPACITY; + } + nrl = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nrl, length, preserve); + nxl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nxl, length, preserve); + nyl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nyl, length, preserve); + nil = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nil, length, preserve); + } + + public double nearestSqr(final double tx, final double ty, double dist) { + final int[] stack = this.search; + final int[] nrl = this.nrl; + final double[] nxl = this.nxl; + final double[] nyl = this.nyl; + if (stack.length == 0 || stack[0] == SENTINEL) { + return Double.POSITIVE_INFINITY; + } + int i = 0, j = 0, curr = 0; + while (true) { + final int right = nrl[j]; + if (right == SENTINEL) { + final double dx = nxl[j] - tx; + final double dy = nyl[j] - ty; + dist = Math.min(dist, FMA + ? Math.fma(dy, dy, dx * dx) + : dx * dx + dy * dy); + break; + } else { + final int next = ((curr + 1) % 2) << 31; + final int left = j + 1; + final double delta = (curr == 0 ? tx : ty) - nxl[j]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + if (push) { + stack[i++] = right | next; + } + j = left; + } else { + if (push) { + stack[i++] = left | next; + } + j = right; + } + curr = ((curr + 1) % 2); + } + } + while (i != 0) { + j = stack[--i]; + final int k = j & 0x7FFF_FFFF; + final int right = nrl[k]; + if (right == SENTINEL) { + final double dx = nxl[k] - tx; + final double dy = nyl[k] - ty; + dist = Math.min(dist, FMA + ? Math.fma(dy, dy, dx * dx) + : dx * dx + dy * dy); + } else { + final int axis = j >>> 31; + final int next = ((axis + 1) % 2) << 31; + final int left = (k + 1) | next; + final double delta = (axis == 0 ? tx : ty) - nxl[k]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + // near = left, far = right, left first + if (push) { + stack[i++] = right | next; + } + stack[i++] = left; + } else { + // near = right, far = left, right first + if (push) { + stack[i++] = left; + } + stack[i++] = right | next; + } + } + } + return dist; + } + + public int nearestIdx(final double tx, final double ty, double dist) { + final int[] stack = this.search; + final int[] nrl = this.nrl; + final double[] nxl = this.nxl; + final double[] nyl = this.nyl; + if (stack.length == 0 || stack[0] == SENTINEL) { + return -1; + } + int i = 0, j = 0, curr = 0, nearest = -1; + while (true) { + final int right = nrl[j]; + if (right == SENTINEL) { + final double dx = nxl[j] - tx; + final double dy = nyl[j] - ty; + final double candidate = FMA + ? Math.fma(dy, dy, dx * dx) + : dx * dx + dy * dy; + if (candidate < dist) { + dist = candidate; + nearest = nil[j]; + } + break; + } else { + final int next = ((curr + 1) % 2) << 31; + final int left = j + 1; + final double delta = (curr == 0 ? tx : ty) - nxl[j]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + if (push) { + stack[i++] = right | next; + } + j = left; + } else { + if (push) { + stack[i++] = left | next; + } + j = right; + } + curr = ((curr + 1) % 2); + } + } + while (i != 0) { + j = stack[--i]; + final int k = j & 0x7FFF_FFFF; + final int right = nrl[k]; + if (right == SENTINEL) { + final double dx = nxl[k] - tx; + final double dy = nyl[k] - ty; + final double candidate = FMA + ? Math.fma(dy, dy, dx * dx) + : dx * dx + dy * dy; + if (candidate < dist) { + dist = candidate; + nearest = nil[k]; + } + } else { + final int axis = j >>> 31; + final int next = ((axis + 1) % 2) << 31; + final int left = (k + 1) | next; + final double delta = (axis == 0 ? tx : ty) - nxl[k]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + // near = left, far = right, left first + if (push) { + stack[i++] = right | next; + } + stack[i++] = left; + } else { + // near = right, far = left, right first + if (push) { + stack[i++] = left; + } + stack[i++] = right | next; + } + } + } + return nearest; + } + + private record Node(int parent, boolean left, int offset, int len, int depth) { + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree3D.java b/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree3D.java new file mode 100644 index 00000000..46c622b2 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/KDTree3D.java @@ -0,0 +1,263 @@ +package org.dreeam.leaf.util; + +@org.jspecify.annotations.NullMarked +public final class KDTree3D { + + private static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA"); + + private static final double[] EMPTY_DOUBLES = {}; + private static final int[] EMPTY_INTS = {}; + private static final Node[] EMPTY_NODES = {}; + + private static final int INITIAL_CAPACITY = 8; + + /// indicate empty on [#search] + /// indicate leaf node on [#nrl] + private static final int SENTINEL = -1; + private Node[] stack = EMPTY_NODES; + private int[] search = EMPTY_INTS; + /// Right node index for internal or [#SENTINEL] for leaf + private int[] nrl = EMPTY_INTS; + // split for internal or x coordinate for leaf + private double[] nxl = EMPTY_DOUBLES; + // y coordinate for leaf + private double[] nyl = EMPTY_DOUBLES; + // z coordinate for leaf + private double[] nzl = EMPTY_DOUBLES; + // index for leaf + private int[] nil = EMPTY_INTS; + + public void build(final double[][] coords, final int[] indices) { + if (indices.length == 0 || coords.length != 3) { + ensureSearch(0, 0); + return; + } + + int st = 0; + ensureConstruct(st); + stack[st++] = new Node(SENTINEL, false, 0, indices.length, 0); + int nodeLen = 0; + while (st != 0) { + ensureNode(nodeLen); + final Node n = stack[--st]; + final int curr = nodeLen++; + if (n.len() <= 1) { + final int p = indices[n.offset()]; + nrl[curr] = SENTINEL; + nxl[curr] = coords[0][p]; + nyl[curr] = coords[1][p]; + nzl[curr] = coords[2][p]; + nil[curr] = p; + } else { + final int axis = (n.depth()) % 3; + final int med = (n.len() - 1) / 2; + final int k = n.offset() + med; + final double[] coord = coords[axis]; + PartialSort.nthElement(indices, coord, n.offset(), n.offset() + n.len() - 1, k); + + nxl[curr] = coord[indices[k]]; + + ensureConstruct(st); + stack[st++] = new Node(curr, false, n.offset() + med + 1, n.len() - med - 1, n.depth() + 1); + stack[st++] = new Node(curr, true, n.offset(), med + 1, n.depth() + 1); + } + if (n.parent() != SENTINEL) { + nrl[n.parent()] = n.left() ? SENTINEL : curr; + } + } + + ensureSearch(indices.length, nodeLen); + } + + private void ensureSearch(final int length, final int nodeLen) { + if (search.length < nodeLen + 8) { + search = new int[nodeLen + 8]; + } + search[0] = length == 0 ? SENTINEL : 0; + } + + private void ensureConstruct(final int st) { + if (st != stack.length && st + 1 != stack.length) { + return; + } + final int newLen = stack.length + 2; + final Node[] b = new Node[Math.max(INITIAL_CAPACITY, newLen + (newLen >> 1))]; + System.arraycopy(stack, 0, b, 0, st); + stack = b; + } + + private void ensureNode(final int preserve) { + int length = preserve + 1; + if (length < nrl.length) { + return; + } + length += length >> 1; + if (length < INITIAL_CAPACITY) { + length = INITIAL_CAPACITY; + } + nrl = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nrl, length, preserve); + nxl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nxl, length, preserve); + nyl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nyl, length, preserve); + nzl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nzl, length, preserve); + nil = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nil, length, preserve); + } + + public double nearestSqr(final double tx, final double ty, final double tz, double dist) { + final int[] stack = this.search; + final int[] nrl = this.nrl; + final double[] nxl = this.nxl; + final double[] nyl = this.nyl; + final double[] nzl = this.nzl; + if (stack.length == 0 || stack[0] == SENTINEL) { + return Double.POSITIVE_INFINITY; + } + int i = 0, j = 0, curr = 0; + while (true) { + final int right = nrl[j]; + if (right == SENTINEL) { + final double dx = nxl[j] - tx; + final double dy = nyl[j] - ty; + final double dz = nzl[j] - tz; + dist = Math.min(dist, FMA + ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) + : dx * dx + dy * dy + dz * dz); + break; + } else { + final int next = ((curr + 1) % 3) << 30; + final int left = j + 1; + final double delta = (curr == 0 ? tx : curr == 1 ? ty : tz) - nxl[j]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + if (push) { + stack[i++] = right | next; + } + j = left; + } else { + if (push) { + stack[i++] = left | next; + } + j = right; + } + curr = ((curr + 1) % 3); + } + } + while (i != 0) { + j = stack[--i]; + final int k = j & 0x3FFF_FFFF; + final int right = nrl[k]; + if (right == SENTINEL) { + final double dx = nxl[k] - tx; + final double dy = nyl[k] - ty; + final double dz = nzl[k] - tz; + dist = Math.min(dist, FMA + ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) + : dx * dx + dy * dy + dz * dz); + } else { + final int axis = j >>> 30; + final int next = ((axis + 1) % 3) << 30; + final int left = (k + 1) | next; + final double delta = (axis == 0 ? tx : axis == 1 ? ty : tz) - nxl[k]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + // near = left, far = right, left first + if (push) { + stack[i++] = right | next; + } + stack[i++] = left; + } else { + // near = right, far = left, right first + if (push) { + stack[i++] = left; + } + stack[i++] = right | next; + } + } + } + return dist; + } + + public int nearestIdx(final double tx, final double ty, final double tz, double dist) { + final int[] stack = this.search; + final int[] nrl = this.nrl; + final double[] nxl = this.nxl; + final double[] nyl = this.nyl; + final double[] nzl = this.nzl; + if (stack.length == 0 || stack[0] == SENTINEL) { + return -1; + } + int i = 0, j = 0, curr = 0, nearest = -1; + while (true) { + final int right = nrl[j]; + if (right == SENTINEL) { + final double dx = nxl[j] - tx; + final double dy = nyl[j] - ty; + final double dz = nzl[j] - tz; + final double candidate = FMA + ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) + : dx * dx + dy * dy + dz * dz; + if (candidate < dist) { + dist = candidate; + nearest = nil[j]; + } + break; + } else { + final int next = ((curr + 1) % 3) << 30; + final int left = j + 1; + final double delta = (curr == 0 ? tx : curr == 1 ? ty : tz) - nxl[j]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + if (push) { + stack[i++] = right | next; + } + j = left; + } else { + if (push) { + stack[i++] = left | next; + } + j = right; + } + curr = ((curr + 1) % 3); + } + } + while (i != 0) { + j = stack[--i]; + final int k = j & 0x3FFF_FFFF; + final int right = nrl[k]; + if (right == SENTINEL) { + final double dx = nxl[k] - tx; + final double dy = nyl[k] - ty; + final double dz = nzl[k] - tz; + final double candidate = FMA + ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) + : dx * dx + dy * dy + dz * dz; + if (candidate < dist) { + dist = candidate; + nearest = nil[k]; + } + } else { + final int axis = j >>> 30; + final int next = ((axis + 1) % 3) << 30; + final int left = (k + 1) | next; + final double delta = (axis == 0 ? tx : axis == 1 ? ty : tz) - nxl[k]; + final boolean push = delta * delta < dist; + if (delta < 0.0) { + // near = left, far = right, left first + if (push) { + stack[i++] = right | next; + } + stack[i++] = left; + } else { + // near = right, far = left, right first + if (push) { + stack[i++] = left; + } + stack[i++] = right | next; + } + } + } + return nearest; + } + + private record Node(int parent, boolean left, int offset, int len, int depth) { + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/PartialSort.java b/leaf-server/src/main/java/org/dreeam/leaf/util/PartialSort.java new file mode 100644 index 00000000..eeb2a353 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/PartialSort.java @@ -0,0 +1,132 @@ +package org.dreeam.leaf.util; + +public final class PartialSort { + + private static final int INSERTION_SORT_THRESHOLD = 12; + private static final int PSEUDO_MEDIAN_REC_THRESHOLD = 64; + + public static void nthElement(final int[] indices, final double[] coord, int left, int right, final int k) { + while (left < right) { + final int len = right + 1 - left; + if (len <= INSERTION_SORT_THRESHOLD) { + insertionSort(indices, coord, left, right); + return; + } + selectPivot(indices, coord, left, len); + final int p = partition(indices, coord, left, right + 1, coord[indices[left]]); + if (k < p) { + right = p - 1; + } else if (k != p) { + left = p + 1; + } else { + return; + } + } + } + + private static void insertionSort(final int[] indices, final double[] coord, final int left, final int right) { + for (int i = left + 1; i <= right; i++) { + final int key = indices[i]; + final double val = coord[key]; + int j = i - 1; + + while (j >= left && coord[indices[j]] > val) { + indices[j + 1] = indices[j]; + j--; + } + indices[j + 1] = key; + } + } + + private static void selectPivot(final int[] indices, final double[] coord, final int a, final int len) { + final int lenDiv8 = len / 8; + final int b = a + lenDiv8 * 4; + final int c = a + lenDiv8 * 7; + if (len < PSEUDO_MEDIAN_REC_THRESHOLD) { + final double va = coord[indices[a]]; + final double vb = coord[indices[b]]; + final double vc = coord[indices[c]]; + final boolean m = va < vb; + final boolean n = va < vc; + final int pivotIdx = m == n ? (vb < vc ^ m) ? c : b : a; + final int tmp = indices[pivotIdx]; + indices[pivotIdx] = indices[a]; + indices[a] = tmp; + } else { + final int pivotIdx = med3Rec(indices, coord, a, b, c, lenDiv8); + final int tmp = indices[pivotIdx]; + indices[pivotIdx] = indices[a]; + indices[a] = tmp; + } + } + + private static int partition(final int[] indices, final double[] x, final int start, final int end, final double pivot) { + if (start >= end) { + return 0; + } + + int lt = start; + int rt = start + 1; + int gap = start; + final int base = indices[start]; + + final int unrollEnd = end - 1; + while (rt < unrollEnd) { + int rtVal = indices[rt]; + + boolean rightIsLt = x[rtVal] < pivot; + + indices[gap] = indices[lt]; + indices[lt] = rtVal; + + gap = rt; + lt += rightIsLt ? 1 : 0; + rt++; + rtVal = indices[rt]; + + rightIsLt = x[rtVal] < pivot; + + indices[gap] = indices[lt]; + indices[lt] = rtVal; + + gap = rt; + lt += rightIsLt ? 1 : 0; + rt++; + } + + while (true) { + final boolean isDone = rt == end; + final int rtVal = isDone ? base : indices[rt]; + + final boolean rightIsLt = x[rtVal] < pivot; + + indices[gap] = indices[lt]; + indices[lt] = rtVal; + + gap = rt; + lt += rightIsLt ? 1 : 0; + rt++; + + if (isDone) { + break; + } + } + + return lt; + } + + private static int med3Rec(final int[] indices, final double[] x, int a, int b, int c, final int n) { + if (n * 8 >= PSEUDO_MEDIAN_REC_THRESHOLD) { + final int n8 = n / 8; + a = med3Rec(indices, x, a, a + n8 * 4, a + n8 * 7, n8); + b = med3Rec(indices, x, b, b + n8 * 4, b + n8 * 7, n8); + c = med3Rec(indices, x, c, c + n8 * 4, c + n8 * 7, n8); + } + final double va = x[indices[a]]; + final double vb = x[indices[b]]; + final double vc = x[indices[c]]; + final boolean m = va < vb; + final boolean n1 = va < vc; + return m == n1 ? (vb < vc ^ m) ? c : b : a; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnMap.java b/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnMap.java index 75ab6518..a9728e19 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnMap.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnMap.java @@ -1,72 +1,38 @@ package org.dreeam.leaf.world; -import gg.pufferfish.pufferfish.simd.SIMDDetection; import io.papermc.paper.configuration.WorldConfiguration; -import it.unimi.dsi.fastutil.doubles.DoubleArrays; -import it.unimi.dsi.fastutil.longs.LongArrays; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Difficulty; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.entity.EntityTickList; +import net.minecraft.world.phys.Vec3; import org.bukkit.event.entity.EntityRemoveEvent; +import org.dreeam.leaf.util.KDTree3D; import java.util.Map; import java.util.OptionalInt; +import java.util.function.Consumer; -public final class DespawnMap { +public final class DespawnMap implements Consumer { private static final ServerPlayer[] EMPTY_PLAYERS = {}; - private static final double[] EMPTY_DOUBLES = {}; - private static final long[] EMPTY_LONGS = {}; - private static final int[] EMPTY_INTS = {}; - static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA"); - private static final boolean SIMD = SIMDDetection.isEnabled(); - private static final int LEAF_THRESHOLD = SIMD ? DespawnVectorAPI.DOUBLE_VECTOR_LENGTH : 4; - private static final int INITIAL_CAP = 8; - private static final int INSERTION_SORT = 16; - static final long LEAF = -1L; - static final long AXIS_X = 0L; - static final long AXIS_Y = 1L; - static final long AXIS_Z = 2L; - static final long LEFT_MASK = 0xfffffffcL; - static final long RIGHT_MASK = 0x3fffffff00000000L; - static final long AXIS_MASK = 0b11L; - static final long SIGN_BIT = 0x8000_0000_0000_0000L; - - /// Stack for tree construction - private final Stack stack = new Stack(INITIAL_CAP); - /// Stack for tree traversal - private int[] search = EMPTY_INTS; - - private int nodeLen = 0; - private int bucketLen = 0; - - /// Node coordinate for each internal node - private double[] nsl = EMPTY_DOUBLES; - /// Offsets(32) Lengths(32) for each player list of leaf nodes - private long[] nbl = EMPTY_LONGS; - /// Left(30) Right(30) Axis(2) for each internal node - private long[] nll = EMPTY_LONGS; - /// Nested player X coordinates of leaf nodes - private double[] bxl = EMPTY_DOUBLES; - /// Nested player Y coordinates of leaf nodes - private double[] byl = EMPTY_DOUBLES; - /// Nested player Z coordinates of leaf nodes - private double[] bzl = EMPTY_DOUBLES; - - private final double[] hard; - private final double[] sort; + private final KDTree3D tree = new KDTree3D(); + private static final MobCategory[] CATEGORIES = MobCategory.values(); + private final double[] hard = new double[CATEGORIES.length]; + private final double[] sort = new double[CATEGORIES.length]; + private final boolean fallback; + public boolean difficultyIsPeaceful = true; + private ServerPlayer[] players = EMPTY_PLAYERS; public DespawnMap(WorldConfiguration worldConfiguration) { - MobCategory[] caps = MobCategory.values(); - hard = new double[caps.length]; - sort = new double[caps.length]; - for (int i = 0; i < caps.length; i++) { - sort[i] = caps[i].getNoDespawnDistance(); - hard[i] = caps[i].getDespawnDistance(); + for (int i = 0; i < CATEGORIES.length; i++) { + sort[i] = CATEGORIES[i].getNoDespawnDistance(); + hard[i] = CATEGORIES[i].getDespawnDistance(); } + boolean fallback = false; for (Map.Entry e : worldConfiguration.entities.spawning.despawnRanges.entrySet()) { OptionalInt a = e.getValue().soft().verticalLimit.value(); OptionalInt b = e.getValue().soft().horizontalLimit.value(); @@ -74,12 +40,16 @@ public final class DespawnMap { OptionalInt d = e.getValue().hard().horizontalLimit.value(); if (a.isPresent() && b.isPresent() && a.getAsInt() == b.getAsInt()) { sort[e.getKey().ordinal()] = a.getAsInt(); + } else if (a.isPresent() || b.isPresent()) { + fallback = true; } if (c.isPresent() && d.isPresent() && c.getAsInt() == d.getAsInt()) { hard[e.getKey().ordinal()] = c.getAsInt(); + } else if (c.isPresent() || d.isPresent()) { + fallback = true; } } - for (int i = 0; i < caps.length; i++) { + for (int i = 0; i < CATEGORIES.length; i++) { if (sort[i] > 0.0) { sort[i] = sort[i] * sort[i]; } @@ -87,274 +57,49 @@ public final class DespawnMap { hard[i] = hard[i] * hard[i]; } } + this.fallback = fallback; } - private void build(double[] coordX, double[] coordY, double[] coordZ) { - final double[][] map = {coordX, coordY, coordZ}; - final int[] data = new int[coordX.length]; - for (int i = 0; i < coordX.length; i++) { - data[i] = i; - } - stack.push(new Node(-1, false, 0, data.length, 0)); - while (!stack.isEmpty()) { - grow(); - - final Node n = stack.pop(); - final int depth = n.depth; - final int offset = n.offset; - final int len = n.length; - final int curr = nodeLen++; - if (len <= LEAF_THRESHOLD) { - - nll[curr] = LEAF; - nbl[curr] = (long) bucketLen << 32 | (long) len; - - growBucket(len); - for (int i = offset, end = offset + len; i < end; i++) { - bxl[bucketLen] = coordX[data[i]]; - byl[bucketLen] = coordY[data[i]]; - bzl[bucketLen] = coordZ[data[i]]; - bucketLen++; - } - } else { - - final int axis = depth % 3 == 0 ? (int) AXIS_X : depth % 3 == 1 ? (int) AXIS_Z : (int) AXIS_Y; - final int median = (len - 1) / 2; - quickSelect(data, map[axis], offset, offset + len - 1, offset + median); - final int pivot = data[offset + median]; - nsl[curr] = axis == AXIS_X ? coordX[pivot] : axis == AXIS_Y ? coordY[pivot] : coordZ[pivot]; - nll[curr] = LEFT_MASK | RIGHT_MASK | (long) axis; - - stack.push(new Node(curr, false, offset + median + 1, len - median - 1, depth + 1)); - stack.push(new Node(curr, true, offset, median + 1, depth + 1)); - } - if (n.parent >= 0) { - if (n.left) { - nll[n.parent] &= AXIS_MASK | RIGHT_MASK; - nll[n.parent] |= (long) curr << 2; - } else { - nll[n.parent] &= AXIS_MASK | LEFT_MASK; - nll[n.parent] |= (long) curr << 32; - } - } - } - } - - private void insertionSort(int[] indices, double[] coord, int left, int right) { - for (int i = left + 1; i <= right; i++) { - int key = indices[i]; - double val = coord[key]; - int j = i - 1; - - while (j >= left && coord[indices[j]] > val) { - indices[j + 1] = indices[j]; - j--; - } - indices[j + 1] = key; - } - } - - private void quickSelect(int[] indices, double[] coord, int left, int right, int k) { - while (left < right) { - if (right - left < INSERTION_SORT) { - insertionSort(indices, coord, left, right); - return; - } - int mid = left + (right - left) / 2; - int a = indices[left], b = indices[mid], c = indices[right]; - double va = coord[a], vb = coord[b], vc = coord[c]; - int pivotIdx = (va < vb) - ? (vb < vc ? mid : (va < vc ? right : left)) - : (va < vc ? left : (vb < vc ? right : mid)); - swap(indices, pivotIdx, left); - double pivot = coord[indices[left]]; - - int i = left; - int j = right + 1; - - while (true) { - while (++i <= right && coord[indices[i]] < pivot) ; - while (--j > left && coord[indices[j]] > pivot) ; - if (i >= j) break; - swap(indices, i, j); - } - - swap(indices, left, j); - int p = j; - if (p == k) { - return; - } else if (k < p) { - right = p - 1; - } else { - left = p + 1; - } - } - } - - private void swap(int[] a, int i, int j) { - int tmp = a[i]; - a[i] = a[j]; - a[j] = tmp; - } - - private void reset() { - nodeLen = 0; - bucketLen = 0; - } - - private void grow() { - int capacity = nodeLen + 1; - if (capacity < nsl.length) { - return; - } - capacity += capacity >> 1; - if (capacity < INITIAL_CAP) { - capacity = INITIAL_CAP; - } - nsl = DoubleArrays.forceCapacity(nsl, capacity, nodeLen); - nll = LongArrays.forceCapacity(nll, capacity, nodeLen); - nbl = LongArrays.forceCapacity(nbl, capacity, nodeLen); - } - - private void growBucket(int capacity) { - capacity = bucketLen + capacity; - if (capacity < bxl.length) { - return; - } - capacity += capacity >> 1; - if (capacity < INITIAL_CAP) { - capacity = INITIAL_CAP; - } - bxl = DoubleArrays.forceCapacity(bxl, capacity, bucketLen); - byl = DoubleArrays.forceCapacity(byl, capacity, bucketLen); - bzl = DoubleArrays.forceCapacity(bzl, capacity, bucketLen); - } - - private double nearest(final double tx, final double ty, final double tz, double dist) { - if (nodeLen == 0) { - return Double.POSITIVE_INFINITY; - } - if (search.length < Math.max(64, nodeLen * 4)) { - search = new int[Math.max(64, nodeLen * 4)]; - } - if (SIMD) { - return DespawnVectorAPI.nearest(search, nsl, nll, nbl, bxl, byl, bzl, tx, ty, tz, dist); - } - final int[] stack = this.search; - final double[] nsl = this.nsl; - final long[] nll = this.nll; - final double[] bxl = this.bxl; - final double[] byl = this.byl; - final double[] bzl = this.bzl; - final long[] nbl = this.nbl; + public void tick(final ServerLevel world, final EntityTickList entityTickList) { + players = world.players().toArray(EMPTY_PLAYERS); + final double[] pxl = new double[players.length]; + final double[] pyl = new double[players.length]; + final double[] pzl = new double[players.length]; int i = 0; - stack[i++] = 0; - while (i != 0) { - final int idx = stack[--i]; - final long data = nll[idx]; - if (data != LEAF) { - final long axis = data & AXIS_MASK; - final double delta = (axis == AXIS_X ? tx : axis == AXIS_Y ? ty : tz) - nsl[idx]; - final long sign = Double.doubleToRawLongBits(delta) & SIGN_BIT; - final long sMask = sign >> 63; // -1L or 0L - final boolean leftValid = (data & LEFT_MASK) != LEFT_MASK; - final boolean rightValid = (data & RIGHT_MASK) != RIGHT_MASK; - final boolean pushNode = (sign == SIGN_BIT & leftValid) | ((sign == 0L) & rightValid); - final boolean pushOther = ((sign == 0L) & leftValid) | (sign == SIGN_BIT & rightValid); - final long node = (sMask & ((data & LEFT_MASK) >>> 2)) | (~sMask & (data >>> 32)); - final long other = (sMask & (data >>> 32)) | (~sMask & ((data & LEFT_MASK) >>> 2)); - if (pushNode) { - stack[i++] = (int) node; - } - if (pushOther && delta * delta < dist) { - stack[i++] = (int) other; - } - } else { - final long bucket = nbl[idx]; - int start = (int) (bucket >>> 32); - final int end = start + (int) (bucket & 0xffffffffL); - for (; start < end; start++) { - final double dx = bxl[start] - tx; - final double dy = byl[start] - ty; - final double dz = bzl[start] - tz; - final double d2 = FMA ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) : dx * dx + dy * dy + dz * dz; - dist = Math.min(dist, d2); - } + for (final ServerPlayer p : players) { + if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(p)) { + pxl[i] = p.getX(); + pyl[i] = p.getY(); + pzl[i] = p.getZ(); + i++; } } - return dist; + final int[] indices = new int[i]; + for (int j = 0; j < i; j++) { + indices[j] = j; + } + tree.build(new double[][]{pxl, pzl, pyl}, indices); + this.difficultyIsPeaceful = world.getDifficulty() == Difficulty.PEACEFUL; + if (fallback) { + entityTickList.forEach(entity -> entity.leaf$checkDespawnFallback(this)); + } else { + entityTickList.forEach(this); + } + players = EMPTY_PLAYERS; } - private record Node(int parent, boolean left, int offset, int length, int depth) { - } - - private static final class Stack { - - private Node[] a; - private int i; - - private Stack(int capacity) { - a = new Node[capacity]; - i = 0; - } - - private boolean isEmpty() { - return i == 0; - } - - private void push(Node value) { - if (i == a.length) { - grow(); - } - a[i++] = value; - } - - private Node pop() { - return a[--i]; - } - - private void grow() { - Node[] b = new Node[a.length << 1]; - System.arraycopy(a, 0, b, 0, i); - a = b; - } - } - - public void tick(ServerLevel world, EntityTickList entityTickList) { - final ServerPlayer[] playerArr = world.players().toArray(EMPTY_PLAYERS); - final ServerPlayer[] list = new ServerPlayer[playerArr.length]; - int newSize = 0; - for (ServerPlayer player1 : playerArr) { - if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player1)) { - list[newSize++] = player1; - } - } - double[] pxl = new double[newSize]; - double[] pyl = new double[newSize]; - double[] pzl = new double[newSize]; - for (int i = 0; i < newSize; i++) { - pxl[i] = list[i].getX(); - pyl[i] = list[i].getY(); - pzl[i] = list[i].getZ(); - } - build(pxl, pyl, pzl); - entityTickList.forEach(Entity::leafCheckDespawn); - reset(); - } - - public void checkDespawn(Mob mob) { - final double x = mob.getX(); - final double y = mob.getY(); - final double z = mob.getZ(); + public void checkDespawn(final Mob mob) { final int i = mob.getType().getCategory().ordinal(); - final double dist = nearest(x, y, z, hard[i]); + final double hardDist = this.hard[i]; + final Vec3 vec3 = mob.position; + final double dist = this.tree.nearestSqr(vec3.x, vec3.z, vec3.y, hardDist); if (dist == Double.POSITIVE_INFINITY) { return; } - if (dist >= hard[i] && mob.removeWhenFarAway(dist)) { + if (dist >= hardDist && mob.removeWhenFarAway(dist)) { mob.discard(EntityRemoveEvent.Cause.DESPAWN); - } else if (dist > sort[i]) { + } else if (dist > this.sort[i]) { if (mob.getNoActionTime() > 600 && mob.random.nextInt(800) == 0 && mob.removeWhenFarAway(dist)) { mob.discard(EntityRemoveEvent.Cause.DESPAWN); } @@ -362,4 +107,15 @@ public final class DespawnMap { mob.setNoActionTime(0); } } + + public ServerPlayer checkDespawnFallback(final Mob mob) { + final Vec3 vec3 = mob.position; + final int i = tree.nearestIdx(vec3.x, vec3.z, vec3.y, Double.POSITIVE_INFINITY); + return i == -1 ? null : this.players[i]; + } + + @Override + public void accept(final Entity entity) { + entity.leaf$checkDespawn(this); + } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnVectorAPI.java b/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnVectorAPI.java deleted file mode 100644 index 7f590838..00000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/world/DespawnVectorAPI.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.dreeam.leaf.world; -import jdk.incubator.vector.*; - -import static org.dreeam.leaf.world.DespawnMap.*; - -public final class DespawnVectorAPI { - - private DespawnVectorAPI() { - } - - private static final VectorSpecies DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED; - static final int DOUBLE_VECTOR_LENGTH = DOUBLE_SPECIES.length(); - - static double nearest(final int[] stack, - final double[] nsl, - final long[] nll, - final long[] nbl, - final double[] bxl, final double[] byl, final double[] bzl, - final double tx, final double ty, final double tz, - double dist) { - final DoubleVector vtx = DoubleVector.broadcast(DOUBLE_SPECIES, tx); - final DoubleVector vty = DoubleVector.broadcast(DOUBLE_SPECIES, ty); - final DoubleVector vtz = DoubleVector.broadcast(DOUBLE_SPECIES, tz); - int i = 0; - stack[i++] = 0; - while (i != 0) { - final int idx = stack[--i]; - final long data = nll[idx]; - if (data != LEAF) { - final long axis = data & AXIS_MASK; - final double delta = (axis == AXIS_X ? tx : axis == AXIS_Y ? ty : tz) - nsl[idx]; - final long sign = Double.doubleToRawLongBits(delta) & SIGN_BIT; - final long sMask = sign >> 63; // -1L or 0L - final boolean leftValid = (data & LEFT_MASK) != LEFT_MASK; - final boolean rightValid = (data & RIGHT_MASK) != RIGHT_MASK; - final boolean pushNode = (sign == SIGN_BIT & leftValid) | ((sign == 0L) & rightValid); - final boolean pushOther = ((sign == 0L) & leftValid) | (sign == SIGN_BIT & rightValid); - final long node = (sMask & ((data & LEFT_MASK) >>> 2)) | (~sMask & (data >>> 32)); - final long other = (sMask & (data >>> 32)) | (~sMask & ((data & LEFT_MASK) >>> 2)); - if (pushNode) { - stack[i++] = (int) node; - } - if (pushOther && delta * delta < dist) { - stack[i++] = (int) other; - } - } else { - final long bucket = nbl[idx]; - final int start = (int) (bucket >>> 32); - final int bucketSize = (int) (bucket & 0xffffffffL); - if (DOUBLE_VECTOR_LENGTH == bucketSize) { - final DoubleVector vdx = DoubleVector.fromArray(DOUBLE_SPECIES, bxl, start).sub(vtx); - final DoubleVector vdy = DoubleVector.fromArray(DOUBLE_SPECIES, byl, start).sub(vty); - final DoubleVector vdz = DoubleVector.fromArray(DOUBLE_SPECIES, bzl, start).sub(vtz); - final DoubleVector vDist = FMA ? - vdz.fma(vdz, vdy.fma(vdy, vdx.mul(vdx))) : - vdx.mul(vdx).add(vdy.mul(vdy)).add(vdz.mul(vdz)); - dist = Math.min(dist, vDist.reduceLanes(VectorOperators.MIN)); - } else if (DOUBLE_VECTOR_LENGTH > 4 && bucketSize >= 4) { - final VectorMask mask = DOUBLE_SPECIES.indexInRange(0, bucketSize); - final DoubleVector vdx = DoubleVector.fromArray(DOUBLE_SPECIES, bxl, start, mask).sub(vtx); - final DoubleVector vdy = DoubleVector.fromArray(DOUBLE_SPECIES, byl, start, mask).sub(vty); - final DoubleVector vdz = DoubleVector.fromArray(DOUBLE_SPECIES, bzl, start, mask).sub(vtz); - final DoubleVector vDist = FMA ? - vdz.fma(vdz, vdy.fma(vdy, vdx.mul(vdx))) : - vdx.mul(vdx).add(vdy.mul(vdy)).add(vdz.mul(vdz)); - dist = Math.min(dist, vDist.reduceLanes(VectorOperators.MIN, mask)); - } else { - final int end = start + bucketSize; - for (int j = start; j < end; j++) { - final double dx = bxl[j] - tx; - final double dy = byl[j] - ty; - final double dz = bzl[j] - tz; - final double d2 = FMA ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) : dx * dx + dy * dy + dz * dz; - dist = Math.min(dist, d2); - } - } - } - } - return dist; - } -}