From 5977664fcf04bd13ef0f6bf05a2df8a6116c1c89 Mon Sep 17 00:00:00 2001 From: Samsuik Date: Tue, 20 Feb 2024 16:40:18 +0000 Subject: [PATCH] Refractor merge cannon entities --- patches/server/0002-Sakura-Utils.patch | 31 +++ .../server/0018-Merge-Cannon-Entities.patch | 183 ++++++++++-------- 2 files changed, 138 insertions(+), 76 deletions(-) diff --git a/patches/server/0002-Sakura-Utils.patch b/patches/server/0002-Sakura-Utils.patch index 7b87af0..6be4192 100644 --- a/patches/server/0002-Sakura-Utils.patch +++ b/patches/server/0002-Sakura-Utils.patch @@ -4,6 +4,37 @@ Date: Tue, 23 May 2023 23:07:20 +0100 Subject: [PATCH] Sakura Utils +diff --git a/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java b/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..54292c693f89ee1444f8b22f4c1488e95ef6bcde +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/utils/collections/EntityTable.java +@@ -0,0 +1,25 @@ ++package me.samsuik.sakura.utils.collections; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import net.minecraft.world.entity.Entity; ++ ++public final class EntityTable { ++ ++ private final Entity[] entities; ++ private final int mask; ++ ++ public EntityTable(int size) { ++ int n = HashCommon.nextPowerOfTwo(size - 1); ++ entities = new Entity[n]; ++ mask = n - 1; ++ } ++ ++ public Entity locate(Entity entity) { ++ int pos = entity.blockPosition().hashCode(); ++ int key = pos & mask; ++ Entity found = entities[key]; ++ entities[key] = entity; ++ return found; ++ } ++ ++} diff --git a/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java b/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java new file mode 100644 index 0000000000000000000000000000000000000000..239fc8823b32ae5c8f6e3bfd6ecdde0ccd1e5a8b diff --git a/patches/server/0018-Merge-Cannon-Entities.patch b/patches/server/0018-Merge-Cannon-Entities.patch index 4c9fb8c..e2f75c7 100644 --- a/patches/server/0018-Merge-Cannon-Entities.patch +++ b/patches/server/0018-Merge-Cannon-Entities.patch @@ -6,18 +6,15 @@ Subject: [PATCH] Merge Cannon Entities diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java new file mode 100644 -index 0000000000000000000000000000000000000000..3216f20b93b73277c4ad2ebb054eeee4db4fb8b6 +index 0000000000000000000000000000000000000000..7d899163b308f52b48bf72a1e9cd38d565bbe05d --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java -@@ -0,0 +1,127 @@ +@@ -0,0 +1,56 @@ +package me.samsuik.sakura.entity.merge; + -+import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import it.unimi.dsi.fastutil.longs.LongSet; -+import me.samsuik.sakura.utils.objects.Expiry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.Entity; + @@ -26,13 +23,13 @@ index 0000000000000000000000000000000000000000..3216f20b93b73277c4ad2ebb054eeee4 +public final class MergeHistory { + + // packed position -> known merging information -+ private final Long2ObjectMap mergeDataMap = new Long2ObjectOpenHashMap<>(); -+ private MergeData mergeData = null; ++ private final Long2ObjectMap mergeDataMap = new Long2ObjectOpenHashMap<>(); ++ private SpawnPositionData mergeData = null; + -+ public MergeData retrievePositions(Entity entity) { ++ public SpawnPositionData retrievePositions(Entity entity) { + long origin = entity.getPackedOrigin(); + -+ if (mergeData != null && mergeData.knownPositions().contains(origin)) { ++ if (mergeData != null && mergeData.isPositionKnown(origin)) { + return mergeData; + } + @@ -43,35 +40,20 @@ index 0000000000000000000000000000000000000000..3216f20b93b73277c4ad2ebb054eeee4 + List mergeList = entity.getMergeList(); + long origin = entity.getPackedOrigin(); + -+ // I apologise for the lambda parameter name in advance -+ MergeData data = mergeDataMap.computeIfAbsent(origin, (OwO) -> new MergeData( -+ new LongOpenHashSet(), // Known entity positions -+ new LongOpenHashSet(), // Retained positions -+ new EntityTable(Math.min(mergeList.size() * 2, 512)), -+ new Expiry(MinecraftServer.currentTickLong, 200), -+ // Reasonable threshold to reduce abuse and breakage with on spawn merging. -+ new Threshold(MinecraftServer.currentTickLong, 12, 200) -+ )); -+ -+ data.expiry().refresh(MinecraftServer.currentTickLong); ++ // After a merged entity has been discarded store all the position data ++ SpawnPositionData data = mergeDataMap.computeIfAbsent(origin, p -> new SpawnPositionData(mergeList)); ++ data.getExpiry().refresh(MinecraftServer.currentTickLong); + + // Collect all merge positions + LongOpenHashSet positions = new LongOpenHashSet((mergeList.size() + 1) / 2); -+ + positions.add(entity.getPackedOrigin()); + + for (Entity mergedEntity : mergeList) { + positions.add(mergedEntity.getPackedOrigin()); + } + -+ // Retain existing positions and insert new positions -+ if (!data.knownPositions().isEmpty()) { -+ data.retainedPositions().addAll(positions); -+ } else { -+ data.retainedPositions().retainAll(positions); -+ } -+ -+ data.knownPositions().addAll(positions); ++ // Retain existing positions or insert new positions ++ data.retainOrInsertPositions(positions); + } + + public void expire(long tick) { @@ -80,60 +62,109 @@ index 0000000000000000000000000000000000000000..3216f20b93b73277c4ad2ebb054eeee4 + // only expire every 20 ticks + if (tick % 20 != 0) return; + -+ mergeDataMap.values().removeIf(data -> data.expiry().isExpired(tick)); ++ mergeDataMap.values().removeIf(data -> data.getExpiry().isExpired(tick)); + } + -+ public record MergeData(LongSet knownPositions, LongSet retainedPositions, EntityTable table, Expiry expiry, Threshold threshold) { -+ public boolean hasPassed() { -+ return threshold.hasPassed(MinecraftServer.currentTickLong); -+ } ++} +diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeThreshold.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeThreshold.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4d08af1a7ff0ebc2b1198513c86c08087d9d6e89 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeThreshold.java +@@ -0,0 +1,26 @@ ++package me.samsuik.sakura.entity.merge; + -+ public Entity findFirstAtPosition(Entity entity) { -+ Entity found = table.locate(entity); ++/** ++ * Threshold for AOT/on spawn merging. ++ *

++ * This is determined by the amount of spawn attempts over time. ++ */ ++public final class MergeThreshold { + -+ if (found != null && found.getId() < entity.getId() && knownPositions.contains(found.getPackedOrigin()) && !found.isRemoved() && entity.compareState(found)) { -+ return found; -+ } ++ private final long startingTick; ++ private final int thresholdAttempts; ++ private final long thresholdAge; ++ private int attempts; + -+ return null; -+ } ++ public MergeThreshold(long tick, int attempts, long age) { ++ startingTick = tick; ++ thresholdAttempts = attempts; ++ thresholdAge = age; + } + -+ private static class EntityTable { -+ private final Entity[] entities; -+ private final int mask; ++ public boolean hasPassed(long tick) { ++ return ++attempts >= thresholdAttempts ++ || tick - startingTick >= thresholdAge; ++ } + -+ EntityTable(int size) { -+ int n = HashCommon.nextPowerOfTwo(size - 1); -+ entities = new Entity[n]; -+ mask = n - 1; ++} +diff --git a/src/main/java/me/samsuik/sakura/entity/merge/SpawnPositionData.java b/src/main/java/me/samsuik/sakura/entity/merge/SpawnPositionData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e63935c17e213bf60571d120ad9ce311b5249d45 +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/entity/merge/SpawnPositionData.java +@@ -0,0 +1,64 @@ ++package me.samsuik.sakura.entity.merge; ++ ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import me.samsuik.sakura.utils.collections.EntityTable; ++import me.samsuik.sakura.utils.objects.Expiry; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.entity.Entity; ++ ++import java.util.List; ++ ++/** ++ * Contains all the positions that past entities of the same origin have merged with. ++ */ ++public final class SpawnPositionData { ++ ++ private final LongSet knownPositions = new LongOpenHashSet(); ++ private final LongSet retainedPositions = new LongOpenHashSet(); ++ ++ private final EntityTable table; ++ private final Expiry expiry = new Expiry(MinecraftServer.currentTickLong, 200); ++ private final MergeThreshold threshold = new MergeThreshold(MinecraftServer.currentTickLong, 12, 200); ++ ++ public SpawnPositionData(List mergeList) { ++ this.table = new EntityTable(mergeList.size()); ++ } ++ ++ public Expiry getExpiry() { ++ return expiry; ++ } ++ ++ public void retainOrInsertPositions(LongOpenHashSet positions) { ++ if (!knownPositions.isEmpty()) { ++ retainedPositions.addAll(positions); ++ } else { ++ retainedPositions.retainAll(positions); + } + -+ Entity locate(Entity entity) { -+ int pos = entity.blockPosition().hashCode(); -+ int key = pos & mask; -+ Entity found = entities[key]; -+ entities[key] = entity; ++ knownPositions.addAll(positions); ++ } ++ ++ public boolean isPositionKnown(long pos) { ++ return knownPositions.contains(pos); ++ } ++ ++ public boolean isPositionRetained(long pos) { ++ return retainedPositions.contains(pos); ++ } ++ ++ public boolean isAbleToOnSpawnMerge() { ++ return threshold.hasPassed(MinecraftServer.currentTickLong); ++ } ++ ++ public Entity findFirstEntityInSamePosition(Entity entity) { ++ Entity found = table.locate(entity); ++ ++ if (found != null && found.getId() < entity.getId() && knownPositions.contains(found.getPackedOrigin()) && !found.isRemoved() && entity.compareState(found)) { + return found; + } -+ } + -+ private static class Threshold { -+ private final long startingTick; -+ private final int thresholdAttempts; -+ private final long thresholdAge; -+ private int attempts; -+ -+ Threshold(long tick, int attempts, long age) { -+ startingTick = tick; -+ thresholdAttempts = attempts; -+ thresholdAge = age; -+ } -+ -+ boolean hasPassed(long tick) { -+ return ++attempts >= thresholdAttempts -+ || tick - startingTick >= thresholdAge; -+ } ++ return null; + } + +} @@ -178,7 +209,7 @@ index 4a19da041971d9f9031af70ae39798233287b3c9..3ace813ccce8b836edef76a16b92ca99 this.guardEntityTick(this::tickNonPassenger, entity); gameprofilerfiller.pop(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1bd5db8e0919d126d18e250bb1cb35cb96d63f5c..7e3be09c93927530ca17a21b9db3ea8d84547c5e 100644 +index ad1f23e0d13af3f4cbac49ad6e6f38ea5d229e4b..a33a11791c72cf3468e460097415901852eb3ca3 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -575,6 +575,108 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -188,7 +219,7 @@ index 1bd5db8e0919d126d18e250bb1cb35cb96d63f5c..7e3be09c93927530ca17a21b9db3ea8d + // Sakura start - cannon entity merging + // List of merged entities, should be naturally sorted (oldest -> youngest) + private final List mergeList = new java.util.ArrayList<>(1); -+ private @Nullable me.samsuik.sakura.entity.merge.MergeHistory.MergeData originData = null; ++ private @Nullable me.samsuik.sakura.entity.merge.SpawnPositionData originData = null; + private me.samsuik.sakura.entity.merge.MergeLevel mergeLevel; + protected int stacked = 1; // default + @@ -214,9 +245,9 @@ index 1bd5db8e0919d126d18e250bb1cb35cb96d63f5c..7e3be09c93927530ca17a21b9db3ea8d + + private boolean isSafeToSpawnMerge(Entity entity) { + return tickCount == 1 && originData != null -+ && originData.hasPassed() // on spawn safety delay has passed -+ && originData == entity.originData // make sure it's the same group -+ && originData.retainedPositions().contains(entity.getPackedOrigin()); ++ && originData.isAbleToOnSpawnMerge() // on spawn safety delay has passed ++ && originData == entity.originData // make sure it's the same group ++ && originData.isPositionRetained(entity.getPackedOrigin()); + } + + public boolean isMergeableType(@Nullable Entity previous) { @@ -249,7 +280,7 @@ index 1bd5db8e0919d126d18e250bb1cb35cb96d63f5c..7e3be09c93927530ca17a21b9db3ea8d + // Non strict merging algorithm uses information collected after entities die + // to be able to perform more aggressive merging by already knowing the OOE. + if (mergeLevel.atLeast(me.samsuik.sakura.entity.merge.MergeLevel.NON_STRICT) && mergeEntity == null && originData != null) { -+ mergeEntity = originData.findFirstAtPosition(this); ++ mergeEntity = originData.findFirstEntityInSamePosition(this); + } + } +