9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-27 18:59:06 +00:00

Refractor merge cannon entities

This commit is contained in:
Samsuik
2024-02-20 16:40:18 +00:00
parent 43a0b89f89
commit 5977664fcf
2 changed files with 138 additions and 76 deletions

View File

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

View File

@@ -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<MergeData> mergeDataMap = new Long2ObjectOpenHashMap<>();
+ private MergeData mergeData = null;
+ private final Long2ObjectMap<SpawnPositionData> 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<Entity> 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.
+ * <p>
+ * 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<Entity> 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<Entity> 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);
+ }
+ }
+