9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-22 00:09:20 +00:00

Use explicit variables in merge history

This commit is contained in:
Samsuik
2023-12-24 17:53:28 +00:00
parent b838acbf96
commit f13a6dd04d

View File

@@ -6,10 +6,10 @@ 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 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 new file mode 100644
index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d33681b356aa3 index 0000000000000000000000000000000000000000..90f36b2d3847e058cfa2b748838fc6ea3294c159
--- /dev/null --- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java +++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeHistory.java
@@ -0,0 +1,138 @@ @@ -0,0 +1,127 @@
+package me.samsuik.sakura.entity.merge; +package me.samsuik.sakura.entity.merge;
+ +
+import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.HashCommon;
@@ -21,6 +21,8 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+import net.minecraft.server.MinecraftServer; +import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Entity;
+ +
+import java.util.List;
+
+public class MergeHistory { +public class MergeHistory {
+ +
+ // packed position -> known merging information + // packed position -> known merging information
@@ -28,7 +30,7 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+ private MergeData mergeData = null; + private MergeData mergeData = null;
+ +
+ public MergeData retrievePositions(Entity entity) { + public MergeData retrievePositions(Entity entity) {
+ var origin = entity.getPackedOrigin(); + long origin = entity.getPackedOrigin();
+ +
+ if (mergeData != null && mergeData.knownPositions().contains(origin)) { + if (mergeData != null && mergeData.knownPositions().contains(origin)) {
+ return mergeData; + return mergeData;
@@ -38,42 +40,32 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+ } + }
+ +
+ public void markPositions(Entity entity) { + public void markPositions(Entity entity) {
+ var mergeList = entity.getMergeList(); + List<Entity> mergeList = entity.getMergeList();
+ var origin = entity.getPackedOrigin(); + long origin = entity.getPackedOrigin();
+ +
+ // I apologise for the lambda parameter name in advance + // I apologise for the lambda parameter name in advance
+ var data = mergeDataMap.computeIfAbsent(origin, (OwO) -> new MergeData( + MergeData data = mergeDataMap.computeIfAbsent(origin, (OwO) -> new MergeData(
+ // Known entity positions that have been able to merge + new LongOpenHashSet(), // Known entity positions
+ // This is used for non-strict merging. + new LongOpenHashSet(), // Retained positions
+ new LongOpenHashSet(),
+ // First copy of the previous positions that is retained.
+ // This is used for on spawn (aot) merging.
+ // Retaining means if the collection you're comparing doesn't the same elements the rest gets yeeted.
+ // We also make use of a _reasonable_ threshold before on spawn merging to reduce abuse and breakage.
+ new LongOpenHashSet(),
+ new EntityTable(Math.min(mergeList.size() * 2, 512)), + new EntityTable(Math.min(mergeList.size() * 2, 512)),
+ // todo: allow configuring expiry and threshold
+ new Expiry(MinecraftServer.currentTickLong, 200), + new Expiry(MinecraftServer.currentTickLong, 200),
+ // Reasonable threshold to reduce abuse and breakage with on spawn merging.
+ new Threshold(MinecraftServer.currentTickLong, 12, 200) + new Threshold(MinecraftServer.currentTickLong, 12, 200)
+ )); + ));
+ +
+ // Refresh expiry
+ data.expiry().refresh(MinecraftServer.currentTickLong); + data.expiry().refresh(MinecraftServer.currentTickLong);
+ +
+ var insert = data.knownPositions().isEmpty(); + // Collect all merge positions
+ var positions = new LongOpenHashSet((mergeList.size() + 1) / 2); + LongOpenHashSet positions = new LongOpenHashSet((mergeList.size() + 1) / 2);
+ +
+ positions.add(entity.getPackedOrigin()); + positions.add(entity.getPackedOrigin());
+ +
+ for (var mergedEntity : mergeList) { + for (Entity mergedEntity : mergeList) {
+ positions.add(mergedEntity.getPackedOrigin()); + positions.add(mergedEntity.getPackedOrigin());
+ } + }
+ +
+ // todo: if tnt spread is enabled double the threshold above then make the first half of the threshold inserting known positions. + // Retain existing positions and insert new positions
+ // ^ This can allow better merging of randomised tnt for the compromise of it taking longer to merge on spawn. + if (!data.knownPositions().isEmpty()) {
+ // ^ There is an uncommon design that uses a single booster at the back and pushes all the tnt forward.
+ // ^ Using a chest as an offset means tnt alignment doesn't matter so people get away with spread but can make merging difficult.
+ if (insert) {
+ data.retainedPositions().addAll(positions); + data.retainedPositions().addAll(positions);
+ } else { + } else {
+ data.retainedPositions().retainAll(positions); + data.retainedPositions().retainAll(positions);
@@ -83,15 +75,12 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+ } + }
+ +
+ public void expire(long tick) { + public void expire(long tick) {
+ // clear this every tick + mergeData = null; // clear this every tick
+ mergeData = null;
+ +
+ // only expire every 20 ticks + // only expire every 20 ticks
+ if (tick % 20 != 0) return; + if (tick % 20 != 0) return;
+ +
+ // using a linked hashmap isn't applicable here as an optimisation + mergeDataMap.values().removeIf(data -> data.expiry().isExpired(tick));
+ // because we allow the spawn positions to "refresh" this would create a memory leak
+ mergeDataMap.values().removeIf((data) -> data.expiry().isExpired(tick));
+ } + }
+ +
+ public record MergeData(LongSet knownPositions, LongSet retainedPositions, EntityTable table, Expiry expiry, Threshold threshold) { + public record MergeData(LongSet knownPositions, LongSet retainedPositions, EntityTable table, Expiry expiry, Threshold threshold) {
@@ -100,7 +89,7 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+ } + }
+ +
+ public Entity findFirstAtPosition(Entity entity) { + public Entity findFirstAtPosition(Entity entity) {
+ var found = table.locate(entity); + Entity found = table.locate(entity);
+ +
+ if (found != null && found.getId() < entity.getId() && knownPositions.contains(found.getPackedOrigin()) && !found.isRemoved() && entity.compareState(found)) { + if (found != null && found.getId() < entity.getId() && knownPositions.contains(found.getPackedOrigin()) && !found.isRemoved() && entity.compareState(found)) {
+ return found; + return found;
@@ -115,35 +104,35 @@ index 0000000000000000000000000000000000000000..7cb3b0d5a284199cdc117038227d3368
+ private final int mask; + private final int mask;
+ +
+ EntityTable(int size) { + EntityTable(int size) {
+ var n = HashCommon.nextPowerOfTwo(size - 1); + int n = HashCommon.nextPowerOfTwo(size - 1);
+ entities = new Entity[n]; + entities = new Entity[n];
+ mask = n - 1; + mask = n - 1;
+ } + }
+ +
+ Entity locate(Entity entity) { + Entity locate(Entity entity) {
+ var pos = entity.blockPosition().hashCode(); + int pos = entity.blockPosition().hashCode();
+ var key = pos & mask; + int key = pos & mask;
+ var found = entities[key]; + Entity found = entities[key];
+ entities[key] = entity; + entities[key] = entity;
+ return found; + return found;
+ } + }
+ } + }
+ +
+ private static class Threshold { + private static class Threshold {
+ private final long existence; // tick when this was created + private final long startingTick;
+ private final int thresholdAttempts; + private final int thresholdAttempts;
+ private final long thresholdAge; + private final long thresholdAge;
+ private int attempts; + private int attempts;
+ +
+ Threshold(long tick, int attempts, long age) { + Threshold(long tick, int attempts, long age) {
+ existence = tick; + startingTick = tick;
+ thresholdAttempts = attempts; + thresholdAttempts = attempts;
+ thresholdAge = age; + thresholdAge = age;
+ } + }
+ +
+ boolean hasPassed(long tick) { + boolean hasPassed(long tick) {
+ return ++attempts >= thresholdAttempts + return ++attempts >= thresholdAttempts
+ || tick - existence >= thresholdAge; + || tick - startingTick >= thresholdAge;
+ } + }
+ } + }
+ +