From fe6acadbec2da9de314a4f31eda7b6f0c5d4c64c Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 14 Dec 2018 21:53:58 -0800 Subject: [PATCH] Tuinity Server Patches --- pom.xml | 16 +- .../com/destroystokyo/paper/PaperCommand.java | 2 +- .../destroystokyo/paper/PaperWorldConfig.java | 3 +- .../paper/io/PrioritizedTaskQueue.java | 20 +- .../com/mojang/datafixers/util/Either.java | 6 +- .../tuinity/chunk/ChunkRegionManager.java | 165 ++++ .../chunk/QueuedChangesMapLong2Int.java | 155 ++++ .../chunk/QueuedChangesMapLong2Object.java | 170 ++++ .../tuinity/tuinity/config/TuinityConfig.java | 225 +++++ .../server/TickListServerInterval.java | 42 + .../tuinity/server/TuinityTickList.java | 614 ++++++++++++ .../com/tuinity/tuinity/util/ChunkList.java | 119 +++ .../com/tuinity/tuinity/util/EntityList.java | 124 +++ .../tuinity/tuinity/util/IBlockDataList.java | 123 +++ .../tuinity/util/OptimizedSmallEnumSet.java | 65 ++ .../tuinity/util/PrimaryThreadList.java | 241 +++++ .../tuinity/util/PrimaryThreadSet.java | 282 ++++++ .../util/TickSynchronizationPoint.java | 40 + .../com/tuinity/tuinity/util/TickThread.java | 40 + .../java/com/tuinity/tuinity/util/Util.java | 103 +++ .../fastutil/ExtendedAbstractDoubleList.java | 39 + .../fastutil/ExtendedDoubleArrayList.java | 65 ++ .../fastutil/ExtendedObjectAVLTreeSet.java | 90 ++ .../com/tuinity/tuinity/util/map/AreaMap.java | 388 ++++++++ .../tuinity/util/map/PlayerAreaMap.java | 25 + .../util/map/PooledLinkedHashSets.java | 287 ++++++ .../util/pool/PooledBlockPositions.java | 40 + .../tuinity/util/set/LinkedSortedSet.java | 142 +++ .../net/minecraft/server/ArraySetSorted.java | 41 +- .../net/minecraft/server/AxisAlignedBB.java | 2 + .../net/minecraft/server/BlockPosition.java | 1 + src/main/java/net/minecraft/server/Chunk.java | 134 +++ .../java/net/minecraft/server/ChunkMap.java | 15 +- .../minecraft/server/ChunkMapDistance.java | 387 +++++++- .../minecraft/server/ChunkProviderServer.java | 186 +++- .../minecraft/server/ChunkRegionLoader.java | 12 +- .../net/minecraft/server/ChunkStatus.java | 5 +- .../net/minecraft/server/DedicatedServer.java | 3 +- .../minecraft/server/DoubleListOffset.java | 2 +- .../java/net/minecraft/server/Entity.java | 53 ++ .../minecraft/server/EntityEnderDragon.java | 4 +- .../minecraft/server/EntityInsentient.java | 19 +- .../net/minecraft/server/EntityPlayer.java | 41 +- .../minecraft/server/EntityTrackerEntry.java | 41 +- .../net/minecraft/server/EntityWither.java | 4 +- .../java/net/minecraft/server/HeightMap.java | 5 +- .../minecraft/server/IAsyncTaskHandler.java | 2 +- .../net/minecraft/server/IEntityAccess.java | 33 +- .../minecraft/server/LightEngineBlock.java | 2 +- .../minecraft/server/LightEngineLayer.java | 2 +- .../net/minecraft/server/LightEngineSky.java | 2 +- .../minecraft/server/LightEngineStorage.java | 17 +- .../server/LightEngineStorageArray.java | 26 +- .../server/LightEngineStorageBlock.java | 8 +- .../server/LightEngineStorageSky.java | 38 +- .../java/net/minecraft/server/MCUtil.java | 30 +- .../net/minecraft/server/MinecraftServer.java | 6 +- .../net/minecraft/server/NBTTagCompound.java | 2 +- .../minecraft/server/NavigationAbstract.java | 24 +- .../net/minecraft/server/NetworkManager.java | 2 +- .../minecraft/server/NextTickListEntry.java | 41 +- .../server/PacketPlayOutMapChunk.java | 15 +- .../net/minecraft/server/PairedQueue.java | 44 +- .../net/minecraft/server/PathfinderGoal.java | 15 +- .../server/PathfinderGoalSelector.java | 125 ++- .../server/PathfinderGoalWrapped.java | 6 +- .../minecraft/server/PathfinderNormal.java | 4 +- .../server/PathfinderTargetCondition.java | 1 + .../net/minecraft/server/PlayerChunk.java | 112 ++- .../net/minecraft/server/PlayerChunkMap.java | 870 +++++++++++++++--- .../server/PlayerInteractManager.java | 45 +- .../net/minecraft/server/PlayerInventory.java | 6 +- .../java/net/minecraft/server/PlayerList.java | 6 +- .../java/net/minecraft/server/ProtoChunk.java | 16 +- .../java/net/minecraft/server/RegionFile.java | 420 ++++++++- .../minecraft/server/RegionFileBitSet.java | 26 +- .../net/minecraft/server/RegionFileCache.java | 45 +- .../server/RegionFileCompression.java | 7 +- .../server/StructureBoundingBox.java | 14 +- .../net/minecraft/server/ThreadedMailbox.java | 2 +- .../net/minecraft/server/TickListServer.java | 51 + .../java/net/minecraft/server/Ticket.java | 6 +- .../java/net/minecraft/server/TicketType.java | 1 + .../net/minecraft/server/VoxelShapeArray.java | 2 +- .../minecraft/server/VoxelShapeCubePoint.java | 2 +- .../server/VoxelShapeMergerList.java | 2 +- .../net/minecraft/server/VoxelShapes.java | 2 +- src/main/java/net/minecraft/server/World.java | 23 +- .../net/minecraft/server/WorldServer.java | 478 +++++++++- .../net/minecraft/server/WorldUpgrader.java | 2 +- .../org/bukkit/craftbukkit/CraftServer.java | 7 +- .../org/bukkit/craftbukkit/CraftWorld.java | 67 +- .../java/org/bukkit/craftbukkit/Main.java | 7 + .../craftbukkit/entity/CraftEntity.java | 12 + .../craftbukkit/entity/CraftPlayer.java | 37 +- .../java/org/spigotmc/ActivationRange.java | 41 +- src/main/java/org/spigotmc/AsyncCatcher.java | 2 +- 97 files changed, 6857 insertions(+), 485 deletions(-) create mode 100644 src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java create mode 100644 src/main/java/com/tuinity/tuinity/config/TuinityConfig.java create mode 100644 src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java create mode 100644 src/main/java/com/tuinity/tuinity/server/TuinityTickList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/ChunkList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/EntityList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/IBlockDataList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java create mode 100644 src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java create mode 100644 src/main/java/com/tuinity/tuinity/util/TickThread.java create mode 100644 src/main/java/com/tuinity/tuinity/util/Util.java create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java create mode 100644 src/main/java/com/tuinity/tuinity/util/map/AreaMap.java create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java create mode 100644 src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java create mode 100644 src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java diff --git a/pom.xml b/pom.xml index e6fc4d014..ffac75de2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 purpur @@ -16,8 +16,8 @@ git-Bukkit- yyyyMMdd-HHmm - 1.8 - 1.8 + 11 + 11 @@ -119,6 +119,12 @@ 1.3 test + + + ca.spottedleaf.concurrentutil + concurrentutil + 1.0.0-SNAPSHOT + @@ -282,6 +288,10 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 + + + true + diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java index 132397b3f..4e12f416f 100644 --- a/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java @@ -215,7 +215,7 @@ public class PaperCommand extends Command { int ticking = 0; int entityTicking = 0; - for (PlayerChunk chunk : world.getChunkProvider().playerChunkMap.updatingChunks.values()) { + for (PlayerChunk chunk : world.getChunkProvider().playerChunkMap.chunkMap.getUpdatingValues()) { // Tuinity - replace chunk map if (chunk.getFullChunkIfCached() == null) { continue; } diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index 7d408542e..a61d936fc 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -108,7 +108,7 @@ public class PaperWorldConfig { } public int softDespawnDistance; - public int hardDespawnDistance; + public int hardDespawnDistance; public double hardDespawnDistanceNotSquared; // Tuinity private void despawnDistances() { softDespawnDistance = getInt("despawn-ranges.soft", 32); // 32^2 = 1024, Minecraft Default hardDespawnDistance = getInt("despawn-ranges.hard", 128); // 128^2 = 16384, Minecraft Default @@ -118,6 +118,7 @@ public class PaperWorldConfig { } log("Living Entity Despawn Ranges: Soft: " + softDespawnDistance + " Hard: " + hardDespawnDistance); + hardDespawnDistanceNotSquared = hardDespawnDistance; // Tuinity softDespawnDistance = softDespawnDistance*softDespawnDistance; hardDespawnDistance = hardDespawnDistance*hardDespawnDistance; diff --git a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java index 78bd238f4..8a7893268 100644 --- a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java +++ b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java @@ -1,5 +1,6 @@ package com.destroystokyo.paper.io; +import ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue; // Tuinity - use concurrentutil import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -48,13 +49,13 @@ public class PrioritizedTaskQueue[] queues = (ConcurrentLinkedQueue[])new ConcurrentLinkedQueue[TOTAL_PRIORITIES]; + final MultiThreadedQueue[] queues = new MultiThreadedQueue[TOTAL_PRIORITIES]; // Tuinity - use concurrentutil private final AtomicBoolean shutdown = new AtomicBoolean(); { for (int i = 0; i < TOTAL_PRIORITIES; ++i) { - this.queues[i] = new ConcurrentLinkedQueue<>(); + this.queues[i] = new MultiThreadedQueue<>(); // Tuinity - use concurrentutil } } @@ -73,9 +74,8 @@ public class PrioritizedTaskQueue queue = this.queues[i]; + final MultiThreadedQueue queue = this.queues[i]; // Tuinity - use concurrentutil while ((task = queue.poll()) != null) { final int prevPriority = task.tryComplete(i); @@ -109,7 +109,7 @@ public class PrioritizedTaskQueue queue = this.queues[i]; + final MultiThreadedQueue queue = this.queues[i]; // Tuinity - use concurrentutil if (queue.peek() != null) { return true; @@ -130,6 +130,12 @@ public class PrioritizedTaskQueue queue = this.queues[i]; + queue.preventAdds(); + } + // Tuinity end - use concurrentutil return this.shutdown.getAndSet(false); } diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java index a90adac7b..2e7cbf8bf 100644 --- a/src/main/java/com/mojang/datafixers/util/Either.java +++ b/src/main/java/com/mojang/datafixers/util/Either.java @@ -23,6 +23,7 @@ public abstract class Either implements App, L> { private static final class Left extends Either { private final L value; + private Optional cachedLeft; // Tuinity - reduce allocation of these for chunks... public Left(final L value) { this.value = value; @@ -51,7 +52,7 @@ public abstract class Either implements App, L> { @Override public Optional left() { - return Optional.of(value); + return this.cachedLeft == null ? this.cachedLeft = Optional.of(value) : this.cachedLeft; // Tuinity - cache optional } @Override @@ -84,6 +85,7 @@ public abstract class Either implements App, L> { private static final class Right extends Either { private final R value; + private Optional cachedRight; // Tuinity - reduce allocation of these for chunks... public Right(final R value) { this.value = value; @@ -117,7 +119,7 @@ public abstract class Either implements App, L> { @Override public Optional right() { - return Optional.of(value); + return this.cachedRight == null ? this.cachedRight = Optional.of(value) : this.cachedRight; // Tuinity - cache optional } @Override diff --git a/src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java new file mode 100644 index 000000000..ff17a100b --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java @@ -0,0 +1,165 @@ +package com.tuinity.tuinity.chunk; + +import com.tuinity.tuinity.util.TickSynchronizationPoint; +import com.tuinity.tuinity.util.TickThread; +import com.tuinity.tuinity.util.Util; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.server.World; + +public final class ChunkRegionManager { + + private static final int REGION_MERGE_RADIUS = 1; + + public final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(8192, 0.25f); + public final World world; + public final TickSynchronizationPoint synchronizationPoint; + + private final TickThread[] threads; + + public ChunkRegionManager(final World world, final TickSynchronizationPoint synchronizationPoint, final TickThread[] threads) { + this.world = world; + this.synchronizationPoint = synchronizationPoint; + this.threads = threads; + } + + public void addToRegion(final long coordinate) { + this.addToRegion(Util.getCoordinateX(coordinate), Util.getCoordinateZ(coordinate), coordinate); + } + + public void addToRegion(final int chunkX, final int chunkZ) { + this.addToRegion(chunkX, chunkZ, Util.getCoordinateKey(chunkX, chunkZ)); + } + + // note: for MT, when we want to merge regions we actually need to tell the owning thread (if any) to merge it + // themselves + public void addToRegion(final int chunkX, final int chunkZ, final long coordinate) { + // find the ideal region to merge into + + ChunkRegionHolder regionHolder = null; + ChunkRegion region = null; + int regionHolderChunks = 0; + + for (int dx = -REGION_MERGE_RADIUS; dx <= REGION_MERGE_RADIUS; ++dx) { + for (int dz = -REGION_MERGE_RADIUS; dz <= REGION_MERGE_RADIUS; ++dz) { + final int checkX = dx + chunkX; + final int checkZ = dz + chunkZ; + final long k = Util.getCoordinateKey(checkX, checkZ); + + ChunkRegionHolder currentRegion = this.regions.get(k); + + if (currentRegion != null) { + final int currentSize = currentRegion.region.coordinates.size(); + if (currentSize > regionHolderChunks) { + regionHolderChunks = currentSize; + regionHolder = currentRegion; + region = currentRegion.region; + } + } + } + } + + if (regionHolder == null) { + regionHolder = new ChunkRegionHolder(region = new ChunkRegion()); + } + + // now merge regions in radius + + region.addChunk(chunkX, chunkZ, coordinate); + + for (int dx = -REGION_MERGE_RADIUS; dx <= REGION_MERGE_RADIUS; ++dx) { + for (int dz = -REGION_MERGE_RADIUS; dz <= REGION_MERGE_RADIUS; ++dz) { + final int checkX = dx + chunkX; + final int checkZ = dz + chunkZ; + final long k = Util.getCoordinateKey(checkX, checkZ); + + ChunkRegionHolder currentRegion = this.regions.putIfAbsent(k, regionHolder); + + if (currentRegion != null && currentRegion.region != region) { + currentRegion.region.mergeInto(region); + } + } + } + } + + static final class ChunkRegionHolder { + + public ChunkRegion region; + + public ChunkRegionHolder(final ChunkRegion region) { + this.region = region; + this.region.addRegionHolder(this); + } + } + + static final class ChunkRegion { + + private final LongOpenHashSet coordinates = new LongOpenHashSet(); + private boolean dead; + + private int lowerX; + private int lowerZ; + + private int upperX; + private int upperZ; + + private final ObjectOpenHashSet regionHolders = new ObjectOpenHashSet<>(); + + void addRegionHolder(final ChunkRegionHolder regionHolder) { + this.regionHolders.add(regionHolder); + } + + public void mergeInto(final ChunkRegion region) { + if (region.dead) { + throw new IllegalStateException("Attempting to merge into a dead region"); + } else if (this.dead) { + throw new IllegalStateException("Attempting to merge from a dead region"); + } + + for (LongIterator iterator = this.coordinates.iterator(); iterator.hasNext();) { + region.addChunk(iterator.nextLong()); + } + + // forward our old region holders + for (final ChunkRegionHolder regionHolder : this.regionHolders) { + regionHolder.region = region; + } + + this.dead = true; + } + + void addChunk(final long coordinate) { + this.addChunk(Util.getCoordinateX(coordinate), Util.getCoordinateZ(coordinate), coordinate); + } + + void addChunk(final int chunkX, final int chunkZ) { + this.addChunk(chunkX, chunkZ, Util.getCoordinateKey(chunkX, chunkZ)); + } + + boolean addChunk(final int chunkX, final int chunkZ, final long coordinate) { + if (!this.coordinates.add(coordinate)) { + return false; + } + + if (this.coordinates.size() == 1) { + this.lowerX = this.upperX = chunkX; + this.lowerZ = this.upperZ = chunkZ; + } else { + if (chunkX < this.lowerX) { + this.lowerX = chunkX; + } else if (chunkX > this.upperX) { + this.upperX = chunkX; + } + if (chunkZ < this.lowerZ) { + this.lowerZ = chunkZ; + } else if (chunkZ > this.upperZ) { + this.upperZ = chunkZ; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java b/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java new file mode 100644 index 000000000..d528d08ea --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java @@ -0,0 +1,155 @@ +package com.tuinity.tuinity.chunk; + +import ca.spottedleaf.concurrentutil.lock.WeakSeqLock; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectIterator; + +public class QueuedChangesMapLong2Int { + + protected final Long2IntOpenHashMap updatingMap; + protected final Long2IntOpenHashMap visibleMap; + protected final Long2IntOpenHashMap queuedPuts; + protected final LongOpenHashSet queuedRemove; + + protected int queuedDefaultReturnValue; + + // we use a seqlock as writes are not common. + protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); + + public QueuedChangesMapLong2Int() { + this(16, 0.75f); + } + + public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) { + this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor); + this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor); + this.queuedPuts = new Long2IntOpenHashMap(); + this.queuedRemove = new LongOpenHashSet(); + } + + public void queueDefaultReturnValue(final int dfl) { + this.queuedDefaultReturnValue = dfl; + this.updatingMap.defaultReturnValue(dfl); + } + + public int queueUpdate(final long k, final int v) { + this.queuedRemove.remove(k); + this.queuedPuts.put(k, v); + + return this.updatingMap.put(k, v); + } + + public int queueRemove(final long k) { + this.queuedPuts.remove(k); + this.queuedRemove.add(k); + + return this.updatingMap.remove(k); + } + + public int getUpdating(final long k) { + return this.updatingMap.get(k); + } + + public int getVisible(final long k) { + return this.visibleMap.get(k); + } + + public int getVisibleAsync(final long k) { + int readlock; + int ret = 0; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + try { + ret = this.visibleMap.get(k); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // ignore... + continue; + } + + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + public boolean performUpdates() { + this.updatingMapSeqLock.acquireWrite(); + this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); + this.updatingMapSeqLock.releaseWrite(); + + if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { + return false; + } + + // update puts + final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); + while (iterator0.hasNext()) { + final Long2IntMap.Entry entry = iterator0.next(); + final long key = entry.getLongKey(); + final int val = entry.getValue(); + + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.put(key, val); + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + final LongIterator iterator1 = this.queuedRemove.iterator(); + while (iterator1.hasNext()) { + final long key = iterator1.nextLong(); + + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.remove(key); + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + + return true; + } + + public boolean performUpdatesLockMap() { + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); + + if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { + return false; + } + + // update puts + final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); + while (iterator0.hasNext()) { + final Long2IntMap.Entry entry = iterator0.next(); + final long key = entry.getLongKey(); + final int val = entry.getValue(); + + this.visibleMap.put(key, val); + } + + final LongIterator iterator1 = this.queuedRemove.iterator(); + while (iterator1.hasNext()) { + final long key = iterator1.nextLong(); + + this.visibleMap.remove(key); + } + + + return true; + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + +} diff --git a/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java b/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java new file mode 100644 index 000000000..e5bb56cca --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java @@ -0,0 +1,170 @@ +package com.tuinity.tuinity.chunk; + +import ca.spottedleaf.concurrentutil.lock.WeakSeqLock; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class QueuedChangesMapLong2Object { + + protected static final Object REMOVED = new Object(); + + protected final Long2ObjectLinkedOpenHashMap updatingMap; + protected final Long2ObjectLinkedOpenHashMap visibleMap; + protected final Long2ObjectLinkedOpenHashMap queuedChanges; + + // we use a seqlock as writes are not common. + protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); + + public QueuedChangesMapLong2Object() { + this(16, 0.75f); // dfl for fastutil + } + + public QueuedChangesMapLong2Object(final int capacity, final float loadFactor) { + this.updatingMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); + this.visibleMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); + this.queuedChanges = new Long2ObjectLinkedOpenHashMap<>(); + } + + public V queueUpdate(final long k, final V value) { + this.queuedChanges.put(k, value); + return this.updatingMap.put(k, value); + } + + public V queueRemove(final long k) { + this.queuedChanges.put(k, REMOVED); + return this.updatingMap.remove(k); + } + + public V getUpdating(final long k) { + return this.updatingMap.get(k); + } + + public V getVisible(final long k) { + return this.visibleMap.get(k); + } + + public V getVisibleAsync(final long k) { + int readlock; + V ret = null; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + + try { + ret = this.visibleMap.get(k); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // ignore... + continue; + } + + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + public Long2ObjectLinkedOpenHashMap getVisibleMap() { + return this.visibleMap; + } + + public Long2ObjectLinkedOpenHashMap getUpdatingMap() { + return this.updatingMap; + } + + public int getVisibleSize() { + return this.visibleMap.size(); + } + + public int getVisibleSizeAsync() { + int readlock; + int ret; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + ret = this.visibleMap.size(); + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + // unlike mojang's impl this cannot be used async since it's not a view of an immutable map + public Collection getUpdatingValues() { + return this.updatingMap.values(); + } + + public List getUpdatingValuesCopy() { + return new ArrayList<>(this.updatingMap.values()); + } + + // unlike mojang's impl this cannot be used async since it's not a view of an immutable map + public Collection getVisibleValues() { + return this.visibleMap.values(); + } + + public List getVisibleValuesCopy() { + return new ArrayList<>(this.visibleMap.values()); + } + + public boolean performUpdates() { + if (this.queuedChanges.isEmpty()) { + return false; + } + + final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + final Long2ObjectMap.Entry entry = iterator.next(); + final long key = entry.getLongKey(); + final Object val = entry.getValue(); + + this.updatingMapSeqLock.acquireWrite(); + try { + if (val == REMOVED) { + this.visibleMap.remove(key); + } else { + this.visibleMap.put(key, (V)val); + } + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + this.queuedChanges.clear(); + return true; + } + + public boolean performUpdatesLockMap() { + if (this.queuedChanges.isEmpty()) { + return false; + } + + final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); + + try { + this.updatingMapSeqLock.acquireWrite(); + + while (iterator.hasNext()) { + final Long2ObjectMap.Entry entry = iterator.next(); + final long key = entry.getLongKey(); + final Object val = entry.getValue(); + + if (val == REMOVED) { + this.visibleMap.remove(key); + } else { + this.visibleMap.put(key, (V)val); + } + } + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + + this.queuedChanges.clear(); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java new file mode 100644 index 000000000..5edae85e7 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java @@ -0,0 +1,225 @@ +package com.tuinity.tuinity.config; + +import ca.spottedleaf.concurrentutil.util.Throw; +import net.minecraft.server.TicketType; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.logging.Level; + +public final class TuinityConfig { + + public static final String CONFIG_HEADER = "Configuration file for Tuinity."; + public static final int CURRENT_CONFIG_VERSION = 0; + + private static final Object[] EMPTY = new Object[0]; + + private static File configFile; + private static YamlConfiguration config; + private static int configVersion; + + public static void init(final File file) { + // TODO remove this in the future... + final File tuinityConfig = new File(file.getParent(), "tuinity.yml"); + if (!tuinityConfig.exists()) { + final File oldConfig = new File(file.getParent(), "concrete.yml"); + oldConfig.renameTo(tuinityConfig); + } + TuinityConfig.configFile = file; + final YamlConfiguration config = new YamlConfiguration(); + config.options().header(CONFIG_HEADER); + config.options().copyDefaults(true); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex); + } + } else { + try { + config.load(file); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex); + Throw.rethrow(ex); /* Rethrow, this is critical */ + throw new RuntimeException(ex); // unreachable + } + } + + TuinityConfig.load(config); + } + + public static void load(final YamlConfiguration config) { + TuinityConfig.config = config; + TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); + + for (final Method method : TuinityConfig.class.getDeclaredMethods()) { + if (method.getReturnType() != void.class || method.getParameterCount() != 0 || + !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + try { + method.setAccessible(true); + method.invoke(null, EMPTY); + } catch (final Exception ex) { + Throw.rethrow(ex); + throw new RuntimeException(ex); // unreachable + } + } + + /* We re-save to add new options */ + try { + config.save(TuinityConfig.configFile); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); + } + } + + private static boolean getBoolean(final String path, final boolean dfl) { + TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl)); + return TuinityConfig.config.getBoolean(path, dfl); + } + + private static int getInt(final String path, final int dfl) { + TuinityConfig.config.addDefault(path, Integer.valueOf(dfl)); + return TuinityConfig.config.getInt(path, dfl); + } + + private static double getDouble(final String path, final double dfl) { + TuinityConfig.config.addDefault(path, Double.valueOf(dfl)); + return TuinityConfig.config.getDouble(path, dfl); + } + + public static boolean tickWorldsInParallel; + + /** + * if tickWorldsInParallel == true, then this value is used as a default only for worlds + */ + public static int tickThreads; + + /* + private static void worldticking() { + tickWorldsInParallel = TuinityConfig.getBoolean("tick-worlds-in-parallel", false); + tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future + }*/ + + public static int delayChunkUnloadsBy; + + private static void delayChunkUnloadsBy() { + delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 10) * 20; + if (delayChunkUnloadsBy >= 0) { + TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy; + } + } + + public static int maxChunkLoadsPerPlayer; + + private static void maxChunkLoadsPerPlayer() { + maxChunkLoadsPerPlayer = TuinityConfig.getInt("max-pending-chunk-tickets-per-player", 49); + if (maxChunkLoadsPerPlayer <= -1) { + maxChunkLoadsPerPlayer = Integer.MAX_VALUE; + } + } + + public static final class WorldConnfig { + + public final String worldName; + public ConfigurationSection config; + + public WorldConnfig(final String worldName) { + this.worldName = worldName; + this.init(); + } + + public void init() { + ConfigurationSection section = TuinityConfig.config.getConfigurationSection(this.worldName); + if (section == null) { + section = TuinityConfig.config.createSection(this.worldName); + } + TuinityConfig.config.set(this.worldName, section); + + this.load(section); + } + + public void load(final ConfigurationSection config) { + this.config = config; + + for (final Method method : TuinityConfig.WorldConnfig.class.getDeclaredMethods()) { + if (method.getReturnType() != void.class || method.getParameterCount() != 0 || + !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) { + continue; + } + + try { + method.setAccessible(true); + method.invoke(this, EMPTY); + } catch (final Exception ex) { + Throw.rethrow(ex); + throw new RuntimeException(ex); // unreachable + } + } + + /* We re-save to add new options */ + try { + TuinityConfig.config.save(TuinityConfig.configFile); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); + } + } + + private boolean getBoolean(final String path, final boolean dfl) { + this.config.addDefault(path, Boolean.valueOf(dfl)); + return this.config.getBoolean(path, dfl); + } + + private int getInt(final String path, final int dfl) { + this.config.addDefault(path, Integer.valueOf(dfl)); + return this.config.getInt(path, dfl); + } + + private double getDouble(final String path, final double dfl) { + this.config.addDefault(path, Double.valueOf(dfl)); + return this.config.getDouble(path, dfl); + } + + /** ignored if {@link TuinityConfig#tickWorldsInParallel} == false */ + public int threads; + + /* + private void worldthreading() { + final int threads = this.getInt("tick-threads", -1); + this.threads = threads == -1 ? TuinityConfig.tickThreads : threads; + }*/ + + public int noTickViewDistance; + private void noTickViewDistance() { + this.noTickViewDistance = this.getInt("no-tick-view-distance", -1); + } + + public boolean useOptimizedTracker; + public int optimizedTrackerTrackRange; + public int optimizedTrackerUntrackRange; + + private void optimizetracker() { + this.useOptimizedTracker = this.getBoolean("optimized-tracker", true); + this.optimizedTrackerTrackRange = this.getInt("optimized-tracker-track-range", -1); + this.optimizedTrackerUntrackRange = this.getInt("optimized-tracker-untrack-range", -1); + if (!this.useOptimizedTracker) { + this.optimizedTrackerTrackRange = -1; + this.optimizedTrackerUntrackRange = -1; + return; + } + if (this.optimizedTrackerTrackRange != this.optimizedTrackerUntrackRange && (this.optimizedTrackerTrackRange | this.optimizedTrackerUntrackRange) == -1) { + // TODO error here + this.optimizedTrackerTrackRange = -1; + this.optimizedTrackerUntrackRange = -1; + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java b/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java new file mode 100644 index 000000000..bef788ccf --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java @@ -0,0 +1,42 @@ +package com.tuinity.tuinity.server; + +import com.tuinity.tuinity.util.set.LinkedSortedSet; +import net.minecraft.server.NextTickListEntry; +import net.minecraft.server.TickListPriority; + +import java.util.Comparator; + +// represents a set of entries to tick at a specified time +public final class TickListServerInterval { + + public static final int TOTAL_PRIORITIES = TickListPriority.values().length; + public static final Comparator> ENTRY_COMPARATOR_BY_ID = (entry1, entry2) -> { + return Long.compare(entry1.getId(), entry2.getId()); + }; + public static final Comparator> ENTRY_COMPARATOR = (Comparator)NextTickListEntry.comparator(); + + // we do not record the interval, this class is meant to be used on a ring buffer + + // inlined enum map for TickListPriority + public final LinkedSortedSet>[] byPriority = new LinkedSortedSet[TOTAL_PRIORITIES]; + + { + for (int i = 0, len = this.byPriority.length; i < len; ++i) { + this.byPriority[i] = new LinkedSortedSet<>(ENTRY_COMPARATOR_BY_ID); + } + } + + public void addEntryLast(final NextTickListEntry entry) { + this.byPriority[entry.getPriority().ordinal()].addLast(entry); + } + + public void addEntryFirst(final NextTickListEntry entry) { + this.byPriority[entry.getPriority().ordinal()].addFirst(entry); + } + + public void clear() { + for (int i = 0, len = this.byPriority.length; i < len; ++i) { + this.byPriority[i].clear(); // O(1) clear + } + } +} diff --git a/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java b/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java new file mode 100644 index 000000000..4118dd7e1 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java @@ -0,0 +1,614 @@ +package com.tuinity.tuinity.server; + +import com.tuinity.tuinity.util.TickThread; +import com.tuinity.tuinity.util.Util; +import ca.spottedleaf.concurrentutil.util.Validate; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; +import net.minecraft.server.BaseBlockPosition; +import net.minecraft.server.BlockPosition; +import net.minecraft.server.ChunkCoordIntPair; +import net.minecraft.server.ChunkProviderServer; +import net.minecraft.server.CrashReport; +import net.minecraft.server.CrashReportSystemDetails; +import net.minecraft.server.IBlockData; +import net.minecraft.server.MinecraftKey; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NextTickListEntry; +import net.minecraft.server.ReportedException; +import net.minecraft.server.StructureBoundingBox; +import net.minecraft.server.TickListPriority; +import net.minecraft.server.TickListServer; +import net.minecraft.server.WorldServer; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public final class TuinityTickList extends TickListServer { // extend to avoid breaking ABI + + // in the order the state is expected to change (mostly) + public static final int STATE_UNSCHEDULED = 1 << 0; + public static final int STATE_SCHEDULED = 1 << 1; // scheduled for some tick + public static final int STATE_PENDING_TICK = 1 << 2; // for this tick + public static final int STATE_TICKING = 1 << 3; + public static final int STATE_TICKED = 1 << 4; // after this, it gets thrown back to unscheduled + public static final int STATE_CANCELLED_TICK = 1 << 5; // still gets moved to unscheduled after tick + + private static final int SHORT_SCHEDULE_TICK_THRESHOLD = 20 * 5 + 1; // 5 seconds + + private final WorldServer world; + private final Predicate excludeFromScheduling; + private final Function getMinecraftKeyFrom; + private final Function getObjectFronMinecraftKey; + private final Consumer> tickFunction; + + private final co.aikar.timings.Timing timingCleanup; // Paper + private final co.aikar.timings.Timing timingTicking; // Paper + private final co.aikar.timings.Timing timingFinished; + + // note: remove ops / add ops suck on fastutil, a chained hashtable implementation would work better, but Long... + // try to alleviate with a very small load factor + private final Long2ObjectOpenHashMap>> entriesByBlock = new Long2ObjectOpenHashMap<>(1024, 0.25f); + private final Long2ObjectOpenHashMap>> entriesByChunk = new Long2ObjectOpenHashMap<>(1024, 0.25f); + private final Long2ObjectOpenHashMap>> pendingChunkTickLoad = new Long2ObjectOpenHashMap<>(1024, 0.5f); + + // fastutil has O(1) first/last while TreeMap/TreeSet are log(n) + private final ObjectRBTreeSet> longScheduled = new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); + + private final ArrayDeque> toTickThisTick = new ArrayDeque<>(); + + private final TickListServerInterval[] shortScheduled = new TickListServerInterval[SHORT_SCHEDULE_TICK_THRESHOLD]; + { + for (int i = 0, len = this.shortScheduled.length; i < len; ++i) { + this.shortScheduled[i] = new TickListServerInterval<>(); + } + } + private int shortScheduledIndex; + + private long nextTick; + + // assume index < length + private static int getWrappedIndex(final int start, final int length, final int index) { + final int next = start + index; + return next < length ? next : next - length; + } + + private static int getNextIndex(final int curr, final int length) { + final int next = curr + 1; + return next < length ? next : 0; + } + + public TuinityTickList(final WorldServer world, final Predicate excludeFromScheduling, final Function getMinecraftKeyFrom, + final Function getObjectFronMinecraftKey, final Consumer> tickFunction, final String timingsType) { + super(world, excludeFromScheduling, getMinecraftKeyFrom, getObjectFronMinecraftKey, tickFunction, timingsType); + this.world = world; + this.excludeFromScheduling = excludeFromScheduling; + this.getMinecraftKeyFrom = getMinecraftKeyFrom; + this.getObjectFronMinecraftKey = getObjectFronMinecraftKey; + this.tickFunction = tickFunction; + this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Cleanup"); // Paper + this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Ticking"); // Paper + this.timingFinished = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Finish"); + this.nextTick = this.world.getTime(); + } + + private void queueEntryForTick(final NextTickListEntry entry, final ChunkProviderServer chunkProvider) { + if (entry.tickState == STATE_SCHEDULED) { + if (chunkProvider.isTickingReadyMainThread(entry.getPosition())) { + this.toTickThisTick.add(entry); + entry.tickState = STATE_PENDING_TICK; + } else { + // we dump them to a map to avoid constantly re-scheduling them + this.addToNotTickingReady(entry); + } + } + } + + private void addToNotTickingReady(final NextTickListEntry entry) { + this.pendingChunkTickLoad.computeIfAbsent(Util.getCoordinateKey(entry.getPosition()), (long keyInMap) -> { + return new ArrayList<>(); + }).add(entry); + } + + private void addToSchedule(final NextTickListEntry entry) { + long delay = entry.getTargetTick() - this.nextTick; + if (delay < SHORT_SCHEDULE_TICK_THRESHOLD) { + if (delay < 0) { + // longScheduled orders by tick time, short scheduled does not + this.longScheduled.add(entry); + } else { + this.shortScheduled[getWrappedIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD, (int)delay)].addEntryLast(entry); + } + } else { + this.longScheduled.add(entry); + } + } + + private void removeEntry(final NextTickListEntry entry) { + entry.tickState = STATE_CANCELLED_TICK; + // short/long scheduled will skip the entry + + final BlockPosition pos = entry.getPosition(); + final long blockKey = Util.getBlockKey(pos); + + final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); + + if (currentEntries.size() == 1) { + // it should contain our entry + this.entriesByBlock.remove(blockKey); + } else { + // it's more likely that this entry is at the start of the list than the end + for (int i = 0, len = currentEntries.size(); i < len; ++i) { + final NextTickListEntry currentEntry = currentEntries.get(i); + if (currentEntry == entry) { + currentEntries.remove(i); + break; + } + } + } + + final long chunkKey = Util.getCoordinateKey(entry.getPosition()); + + ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); + + set.remove(entry); + + if (set.isEmpty()) { + this.entriesByChunk.remove(chunkKey); + } + + ArrayList> pendingTickingLoad = this.pendingChunkTickLoad.get(chunkKey); + + if (pendingTickingLoad != null) { + for (int i = 0, len = pendingTickingLoad.size(); i < len; ++i) { + if (pendingTickingLoad.get(i) == entry) { + pendingTickingLoad.remove(i); + break; + } + } + + if (pendingTickingLoad.isEmpty()) { + this.pendingChunkTickLoad.remove(chunkKey); + } + } + } + + public void onChunkSetTicking(final int chunkX, final int chunkZ) { + TickThread.softEnsureTickThread("async tick list chunk ticking update"); + final ArrayList> pending = this.pendingChunkTickLoad.remove(Util.getCoordinateKey(chunkX, chunkZ)); + if (pending == null) { + return; + } + + for (int i = 0, size = pending.size(); i < size; ++i) { + final NextTickListEntry entry = pending.get(i); + // already in all the relevant reference maps, just need to add to longScheduled or shortScheduled + this.addToSchedule(entry); + } + } + + private void prepare() { + final long currentTick = this.world.getTime(); + + final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); + + // here we setup what's going to tick + + // we don't remove items from shortScheduled (but do from longScheduled) because they're cleared at the end of + // this tick + if (this.longScheduled.isEmpty() || this.longScheduled.first().getTargetTick() > currentTick) { + // nothing in longScheduled to worry about + final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; + for (int i = 0, len = interval.byPriority.length; i < len; ++i) { + for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { + this.queueEntryForTick(iterator.next(), chunkProvider); + } + } + } else { + final TickListServerInterval interval = this.shortScheduled[this.shortScheduledIndex]; + + // combine interval and longScheduled, keeping order + final Comparator> comparator = (Comparator)TickListServerInterval.ENTRY_COMPARATOR; + final Iterator> longScheduledIterator = this.longScheduled.iterator(); + NextTickListEntry longCurrent = longScheduledIterator.next(); + + for (int i = 0, len = interval.byPriority.length; i < len; ++i) { + for (final Iterator> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) { + final NextTickListEntry shortCurrent = iterator.next(); + if (longCurrent != null) { + // drain longCurrent until we can add shortCurrent + while (comparator.compare(longCurrent, shortCurrent) <= 0) { + this.queueEntryForTick(longCurrent, chunkProvider); + longScheduledIterator.remove(); + if (longScheduledIterator.hasNext()) { + longCurrent = longScheduledIterator.next(); + if (longCurrent.getTargetTick() > currentTick) { + longCurrent = null; + break; + } + } else { + longCurrent = null; + break; + } + } + } + this.queueEntryForTick(shortCurrent, chunkProvider); + } + } + + // add remaining from long scheduled + for (;;) { + if (longCurrent == null || longCurrent.getTargetTick() > currentTick) { + break; + } + longScheduledIterator.remove(); + this.queueEntryForTick(longCurrent, chunkProvider); + + if (longScheduledIterator.hasNext()) { + longCurrent = longScheduledIterator.next(); + } else { + break; + } + } + } + } + + @Override + public void tick() { + TickThread.softEnsureTickThread("async tick list tick"); + final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); + + this.world.getMethodProfiler().enter("cleaning"); + this.timingCleanup.startTiming(); + + this.prepare(); + + // this must be done here in case something schedules in the tick code + this.shortScheduled[this.shortScheduledIndex].clear(); + this.shortScheduledIndex = getNextIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD); + this.nextTick = this.world.getTime() + 1; + + this.timingCleanup.stopTiming(); + this.world.getMethodProfiler().exitEnter("ticking"); + this.timingTicking.startTiming(); + + for (final NextTickListEntry toTick : this.toTickThisTick) { + if (toTick.tickState != STATE_PENDING_TICK) { + // onTickEnd gets called at end of tick + continue; + } + try { + if (chunkProvider.isTickingReadyMainThread(toTick.getPosition())) { + toTick.tickState = STATE_TICKING; + this.tickFunction.accept(toTick); + toTick.tickState = STATE_TICKED; + } else { + // re-schedule eventually + toTick.tickState = STATE_SCHEDULED; + this.addToNotTickingReady(toTick); + } + } catch (final Throwable thr) { + // start copy from TickListServer // TODO check on update + CrashReport crashreport = CrashReport.a(thr, "Exception while ticking"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being ticked"); + + CrashReportSystemDetails.a(crashreportsystemdetails, toTick.getPosition(), (IBlockData) null); + throw new ReportedException(crashreport); + // end copy from TickListServer + } + } + + this.timingTicking.stopTiming(); + this.world.getMethodProfiler().exit(); + this.timingFinished.startTiming(); + + // finished ticking, actual cleanup time + for (int i = 0, len = this.toTickThisTick.size(); i < len; ++i) { + final NextTickListEntry entry = this.toTickThisTick.poll(); + if (entry.tickState != STATE_SCHEDULED) { + // some entries get re-scheduled due to their chunk not being loaded/at correct status, so do not + // call onTickEnd for them + this.onTickEnd(entry); + } + } + + this.timingFinished.stopTiming(); + } + + private void onTickEnd(final NextTickListEntry entry) { + entry.tickState = STATE_UNSCHEDULED; + + final BlockPosition pos = entry.getPosition(); + final long blockKey = Util.getBlockKey(pos); + + final ArrayList> currentEntries = this.entriesByBlock.get(blockKey); + + if (currentEntries.size() == 1) { + // it should contain our entry + this.entriesByBlock.remove(blockKey); + } else { + // it's more likely that this entry is at the start of the list than the end + for (int i = 0, len = currentEntries.size(); i < len; ++i) { + final NextTickListEntry currentEntry = currentEntries.get(i); + if (currentEntry == entry) { + currentEntries.remove(i); + break; + } + } + } + + final long chunkKey = Util.getCoordinateKey(entry.getPosition()); + + ObjectRBTreeSet> set = this.entriesByChunk.get(chunkKey); + + set.remove(entry); + + if (set.isEmpty()) { + this.entriesByChunk.remove(chunkKey); + } + + // already removed from longScheduled or shortScheduled + } + + @Override + public boolean isPendingTickThisTick(final BlockPosition blockposition, final T data) { + final ArrayList> entries = this.entriesByBlock.get(Util.getBlockKey(blockposition)); + + if (entries == null) { + return false; + } + + for (int i = 0, size = entries.size(); i < size; ++i) { + final NextTickListEntry entry = entries.get(i); + if (entry.getData() == data && entry.tickState == STATE_PENDING_TICK) { + return true; + } + } + + return false; + } + + @Override + public boolean isScheduledForTick(final BlockPosition blockposition, final T data) { + final ArrayList> entries = this.entriesByBlock.get(Util.getBlockKey(blockposition)); + + if (entries == null) { + return false; + } + + for (int i = 0, size = entries.size(); i < size; ++i) { + final NextTickListEntry entry = entries.get(i); + if (entry.getData() == data && entry.tickState == STATE_SCHEDULED) { + return true; + } + } + + return false; + } + + @Override + public void schedule(BlockPosition blockPosition, T t, int i, TickListPriority tickListPriority) { + this.schedule(blockPosition, t, i + this.world.getTime(), tickListPriority); + } + + public void schedule(final NextTickListEntry entry) { + this.schedule(entry.getPosition(), entry.getData(), entry.getTargetTick(), entry.getPriority()); + } + + public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) { + TickThread.softEnsureTickThread("async tick list schedule"); + + final NextTickListEntry entry = new NextTickListEntry<>(pos, data, targetTick, priority); + Validate.notNull(entry, "null entry"); + if (this.excludeFromScheduling.test(entry.getData())) { + return; + } + + final long blockKey = Util.getBlockKey(pos); + + final ArrayList> currentEntries = this.entriesByBlock.computeIfAbsent(blockKey, (long keyInMap) -> new ArrayList<>(3)); + + if (currentEntries.isEmpty()) { + currentEntries.add(entry); + } else { + for (int i = 0, size = currentEntries.size(); i < size; ++i) { + final NextTickListEntry currentEntry = currentEntries.get(i); + + // entries are only blocked from scheduling if currentEntry.equals(toSchedule) && currentEntry is scheduled to tick (NOT including pending) + if (currentEntry.getData() == entry.getData() && currentEntry.tickState == STATE_SCHEDULED) { + // can't add + return; + } + } + currentEntries.add(entry); + } + + entry.tickState = STATE_SCHEDULED; + + this.entriesByChunk.computeIfAbsent(Util.getCoordinateKey(entry.getPosition()), (final long keyInMap) -> { + return new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR); + }).add(entry); + + this.addToSchedule(entry); + } + + @Override + public void scheduleAll(final Stream> stream) { + this.scheduleAll(stream.iterator()); + } + public void scheduleAll(final Iterator> iterator) { + while (iterator.hasNext()) { + this.schedule(iterator.next()); + } + } + + // this is not the standard interception calculation, but it's the one vanilla uses + // i.e the y value is ignored? the x, z calc isn't correct? + // however for the copy op they use the correct intersection, after using this one of course... + private static boolean isBlockInSortof(final StructureBoundingBox boundingBox, final BlockPosition pos) { + return pos.getX() >= boundingBox.getMinX() && pos.getX() < boundingBox.getMaxX() && pos.getZ() >= boundingBox.getMinZ() && pos.getZ() < boundingBox.getMaxZ(); + } + + @Override + public List> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { + TickThread.softEnsureTickThread("async tick list get"); + if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { + return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above + } + + final int lowerChunkX = structureboundingbox.getMinX() >> 4; + final int upperChunkX = (structureboundingbox.getMaxX() - 1) >> 4; // subtract 1 since maxX is exclusive + final int lowerChunkZ = structureboundingbox.getMinZ() >> 4; + final int upperChunkZ = (structureboundingbox.getMaxZ() - 1) >> 4; // subtract 1 since maxZ is exclusive + + final int xChunksLength = (upperChunkX - lowerChunkX + 1); + final int zChunksLength = (upperChunkZ - lowerChunkZ + 1); + + final ObjectRBTreeSet>[] containingChunks = new ObjectRBTreeSet[xChunksLength * zChunksLength]; + + final int offset = (xChunksLength * -lowerChunkZ - lowerChunkX); + int totalEntries = 0; + for (int currChunkX = lowerChunkX; currChunkX <= upperChunkX; ++currChunkX) { + for (int currChunkZ = lowerChunkZ; currChunkZ <= upperChunkZ; ++currChunkZ) { + // todo optimize + //final int index = (currChunkX - lowerChunkX) + xChunksLength * (currChunkZ - lowerChunkZ); + final int index = offset + currChunkX + xChunksLength * currChunkZ; + final ObjectRBTreeSet> set = containingChunks[index] = this.entriesByChunk.get(Util.getCoordinateKey(currChunkX, currChunkZ)); + if (set != null) { + totalEntries += set.size(); + } + } + } + + final List> ret = new ArrayList<>(totalEntries); + + final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); + + Util.mergeSortedSets((NextTickListEntry entry) -> { + if (!isBlockInSortof(structureboundingbox, entry.getPosition())) { + return; + } + final int tickState = entry.tickState; + if ((tickState & matchOne) == 0) { + return; + } + + ret.add(entry); + return; + }, TickListServerInterval.ENTRY_COMPARATOR, containingChunks); + + if (removeReturned) { + for (NextTickListEntry entry : ret) { + this.removeEntry(entry); + } + } + + return ret; + } + + @Override + public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { + TickThread.softEnsureTickThread("async tick list copy"); + // start copy from TickListServer // TODO check on update + List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); + Iterator> iterator = list.iterator(); + + while (iterator.hasNext()) { + NextTickListEntry nextticklistentry = iterator.next(); + + if (structureboundingbox.hasPoint( nextticklistentry.getPosition())) { + BlockPosition blockposition1 = nextticklistentry.getPosition().add(blockposition); + T t0 = nextticklistentry.getData(); + + this.schedule(new NextTickListEntry<>(blockposition1, t0, nextticklistentry.getTargetTick(), nextticklistentry.getPriority())); + } + } + // end copy from TickListServer + } + + @Override + public List> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) { + TickThread.softEnsureTickThread("async tick list get"); + // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks + // not at ticking status, and ticking status requires neighbours loaded + // so with this method we will reduce scheduler churning + final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED)); + + final ObjectRBTreeSet> entries = this.entriesByChunk.get(Util.getCoordinateKey(chunkPos)); + + if (entries == null) { + return Collections.emptyList(); + } + + final List> ret = new ArrayList<>(entries.size()); + + for (NextTickListEntry entry : entries) { + if ((entry.tickState & matchOne) == 0) { + continue; + } + ret.add(entry); + } + + if (removeReturned) { + for (NextTickListEntry entry : ret) { + this.removeEntry(entry); + } + } + + return ret; + } + + @Override + public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { + TickThread.softEnsureTickThread("async tick list serialize"); + // start copy from TickListServer // TODO check on update + List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); + + return TickListServer.serialize(this.getMinecraftKeyFrom, list, this.world.getTime()); + // end copy from TickListServer + } + + @Override + public int getTotalScheduledEntries() { + TickThread.softEnsureTickThread("async tick list get size"); + // good thing this is only used in debug reports // TODO check on update + int ret = 0; + + for (NextTickListEntry entry : this.longScheduled) { + if (entry.tickState == STATE_SCHEDULED) { + ++ret; + } + } + + for (Iterator>>> iterator = this.pendingChunkTickLoad.long2ObjectEntrySet().iterator(); iterator.hasNext();) { + ArrayList> list = iterator.next().getValue(); + + for (NextTickListEntry entry : list) { + if (entry.tickState == STATE_SCHEDULED) { + ++ret; + } + } + } + + for (TickListServerInterval interval : this.shortScheduled) { + for (Iterable> set : interval.byPriority) { + for (NextTickListEntry entry : set) { + if (entry.tickState == STATE_SCHEDULED) { + ++ret; + } + } + } + } + + return ret; + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/ChunkList.java b/src/main/java/com/tuinity/tuinity/util/ChunkList.java new file mode 100644 index 000000000..66c64be8f --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/ChunkList.java @@ -0,0 +1,119 @@ +package com.tuinity.tuinity.util; + +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.server.Chunk; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// list with O(1) remove & contains +public final class ChunkList implements Iterable { + + protected final Long2IntOpenHashMap chunkToIndex = new Long2IntOpenHashMap(); + { + this.chunkToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + protected Chunk[] chunks = new Chunk[16]; + protected int count; + + public int size() { + return this.count; + } + + public boolean contains(final Chunk chunk) { + return this.chunkToIndex.containsKey(Util.getCoordinateKey(chunk.getPos())); + } + + public boolean remove(final Chunk chunk) { + final int index = this.chunkToIndex.remove(Util.getCoordinateKey(chunk.getPos())); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entity at the end to this index + final int endIndex = --this.count; + final Chunk end = this.chunks[endIndex]; + if (index != endIndex) { + // not empty after this call + this.chunkToIndex.put(Util.getCoordinateKey(end.getPos()), index); // update index + } + this.chunks[index] = end; + this.chunks[endIndex] = null; + + return true; + } + + public boolean add(final Chunk chunk) { + final int count = this.count; + final int currIndex = this.chunkToIndex.putIfAbsent(Util.getCoordinateKey(chunk.getPos()), count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + Chunk[] list = this.chunks; + + if (list.length == count) { + // resize required + list = this.chunks = Arrays.copyOf(list, count * 2); // overflow results in negative + } + + list[count] = chunk; + this.count = count + 1; + + return true; + } + + public Chunk getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.chunks[index]; + } + + public Chunk getUnchecked(final int index) { + return this.chunks[index]; + } + + public Chunk[] getRawData() { + return this.chunks; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + + Chunk lastRet; + int current; + + @Override + public boolean hasNext() { + return this.current < ChunkList.this.count; + } + + @Override + public Chunk next() { + if (this.current >= ChunkList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = ChunkList.this.chunks[this.current++]; + } + + @Override + public void remove() { + final Chunk lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + ChunkList.this.remove(lastRet); + --this.current; + } + }; + } + +} diff --git a/src/main/java/com/tuinity/tuinity/util/EntityList.java b/src/main/java/com/tuinity/tuinity/util/EntityList.java new file mode 100644 index 000000000..570c44e26 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/EntityList.java @@ -0,0 +1,124 @@ +package com.tuinity.tuinity.util; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import net.minecraft.server.Entity; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// list with O(1) remove & contains +public final class EntityList implements Iterable { + + protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + protected Entity[] entities = new Entity[16]; + protected int count; + + public int size() { + return this.count; + } + + public boolean contains(final Entity entity) { + return this.entityToIndex.containsKey(entity.getId()); + } + + public boolean remove(final Entity entity) { + final int index = this.entityToIndex.remove(entity.getId()); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entity at the end to this index + final int endIndex = --this.count; + final Entity end = this.entities[endIndex]; + if (index != endIndex) { + // not empty after this call + this.entityToIndex.put(end.getId(), index); // update index + } + this.entities[index] = end; + this.entities[endIndex] = null; + + return true; + } + + public boolean add(final Entity entity) { + final int count = this.count; + final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + Entity[] list = this.entities; + + if (list.length == count) { + // resize required + list = this.entities = Arrays.copyOf(list, count * 2); // overflow results in negative + } + + list[count] = entity; + this.count = count + 1; + + return true; + } + + public Entity getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.entities[index]; + } + + public Entity getUnchecked(final int index) { + return this.entities[index]; + } + + public Entity[] getRawData() { + return this.entities; + } + + public void clear() { + this.entityToIndex.clear(); + Arrays.fill(this.entities, 0, this.count, null); + this.count = 0; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + + Entity lastRet; + int current; + + @Override + public boolean hasNext() { + return this.current < EntityList.this.count; + } + + @Override + public Entity next() { + if (this.current >= EntityList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = EntityList.this.entities[this.current++]; + } + + @Override + public void remove() { + final Entity lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + EntityList.this.remove(lastRet); + --this.current; + } + }; + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/IBlockDataList.java b/src/main/java/com/tuinity/tuinity/util/IBlockDataList.java new file mode 100644 index 000000000..ce15ce532 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/IBlockDataList.java @@ -0,0 +1,123 @@ +package com.tuinity.tuinity.util; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; +import net.minecraft.server.ChunkSection; +import net.minecraft.server.DataPaletteGlobal; +import net.minecraft.server.IBlockData; +import java.util.Arrays; + +public final class IBlockDataList { + + static final DataPaletteGlobal GLOBAL_PALETTE = (DataPaletteGlobal)ChunkSection.GLOBAL_PALETTE; + + // map of location -> (index | (location << 16) | (palette id << 32)) + private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(16, 0.7f); + { + this.map.defaultReturnValue(Long.MAX_VALUE); + } + + private long[] byIndex = new long[16]; + private int size; + + public static int getLocationKey(final int x, final int y, final int z) { + return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); + } + + public static IBlockData getBlockDataFromRaw(final long raw) { + return GLOBAL_PALETTE.getObject((int)(raw >>> 32)); + } + + public static int getIndexFromRaw(final long raw) { + return (int)(raw & 0xFFFF); + } + + public static int getLocationFromRaw(final long raw) { + return (int)((raw >>> 16) & 0xFFFF); + } + + public static long getRawFromValues(final int index, final int location, final IBlockData data) { + return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.getOrCreateIdFor(data)) << 32); + } + + public static long setIndexRawValues(final long value, final int index) { + return value & ~(0xFFFF) | (index); + } + + public long add(final int x, final int y, final int z, final IBlockData data) { + return this.add(getLocationKey(x, y, z), data); + } + + public long add(final int location, final IBlockData data) { + final long curr = this.map.get((short)location); + + if (curr == Long.MAX_VALUE) { + final int index = this.size++; + final long raw = getRawFromValues(index, location, data); + this.map.put((short)location, raw); + + if (index >= this.byIndex.length) { + this.byIndex = Arrays.copyOf(this.byIndex, this.byIndex.length * 2); + } + + this.byIndex[index] = raw; + return raw; + } else { + final int index = getIndexFromRaw(curr); + final long raw = this.byIndex[index] = getRawFromValues(index, location, data); + + this.map.put((short)location, raw); + + return raw; + } + } + + public long remove(final int x, final int y, final int z) { + return this.remove(getLocationKey(x, y, z)); + } + + public long remove(final int location) { + final long ret = this.map.remove((short)location); + final int index = getIndexFromRaw(ret); + if (ret == Long.MAX_VALUE) { + return ret; + } + + // move the entry at the end to this index + final int endIndex = --this.size; + final long end = this.byIndex[endIndex]; + if (index != endIndex) { + // not empty after this call + this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); + } + this.byIndex[index] = end; + this.byIndex[endIndex] = 0L; + + return ret; + } + + public int size() { + return this.size; + } + + public long getRaw(final int index) { + return this.byIndex[index]; + } + + public int getLocation(final int index) { + return getLocationFromRaw(this.getRaw(index)); + } + + public IBlockData getData(final int index) { + return getBlockDataFromRaw(this.getRaw(index)); + } + + public void clear() { + this.size = 0; + this.map.clear(); + } + + public LongIterator getRawIterator() { + return this.map.values().iterator(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java b/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java new file mode 100644 index 000000000..934792ad3 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java @@ -0,0 +1,65 @@ +package com.tuinity.tuinity.util; + +import java.util.Collection; + +// containing utils to work on small numbers of enums +public final class OptimizedSmallEnumSet { + + private final Class enumClass; + private long backingSet; + + public OptimizedSmallEnumSet(final Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("Null class"); + } + if (!clazz.isEnum()) { + throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); + } + this.enumClass = clazz; + } + + public boolean addUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev | key; + + return (prev & key) == 0; + } + + public boolean removeUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev & ~key; + + return (prev & key) != 0; + } + + public void clear() { + this.backingSet = 0L; + } + + public int size() { + return Long.bitCount(this.backingSet); + } + + public void addAllUnchecked(final Collection enums) { + for (final E element : enums) { + if (element == null) { + throw new NullPointerException("Null element"); + } + this.backingSet |= (1L << element.ordinal()); + } + } + + public long getBackingSet() { + return this.backingSet; + } + + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java b/src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java new file mode 100644 index 000000000..de930aef9 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java @@ -0,0 +1,241 @@ +package com.tuinity.tuinity.util; + +import org.spigotmc.AsyncCatcher; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +public class PrimaryThreadList implements List { + + protected final List wrapped; + + public PrimaryThreadList(final List wrap) { + if (wrap == null) { + throw new NullPointerException("Wrapped list may not be null"); + } + + this.wrapped = wrap; + } + + protected void check() { + AsyncCatcher.catchOp("collection access"); + } + + @Override + public boolean add(final E element) { + this.check(); + return this.wrapped.add(element); + } + + @Override + public void add(final int index, final E element) { + this.check(); + this.wrapped.add(index, element); + } + + @Override + public boolean addAll(final Collection collection) { + this.check(); + return this.wrapped.addAll(collection); + } + + @Override + public boolean addAll(final int index, final Collection collection) { + this.check(); + return this.wrapped.addAll(index, collection); + } + + @Override + public E set(final int index, final E element) { + this.check(); + return this.wrapped.set(index, element); + } + + @Override + public boolean remove(final Object element) { + this.check(); + return this.wrapped.remove(element); + } + + @Override + public E remove(final int index) { + this.check(); + return this.wrapped.remove(index); + } + + @Override + public boolean removeIf(final Predicate filter) { + this.check(); + return this.wrapped.removeIf(filter); + } + + @Override + public boolean removeAll(final Collection collection) { + this.check(); + return this.wrapped.removeAll(collection); + } + + @Override + public boolean retainAll(final Collection collection) { + this.check(); + return this.wrapped.retainAll(collection); + } + + @Override + public void replaceAll(final UnaryOperator operator) { + this.check(); + this.wrapped.replaceAll(operator); + } + + @Override + public void sort(final Comparator comparator) { + this.check(); + this.wrapped.sort(comparator); + } + + @Override + public void clear() { + this.check(); + this.wrapped.clear(); + } + + @Override + public int size() { + this.check(); + return this.wrapped.size(); + } + + @Override + public boolean isEmpty() { + this.check(); + return this.wrapped.isEmpty(); + } + + @Override + public E get(final int index) { + this.check(); + return this.wrapped.get(index); + } + + @Override + public boolean containsAll(final Collection collection) { + this.check(); + return this.wrapped.containsAll(collection); + } + + @Override + public boolean contains(final Object object) { + this.check(); + return this.wrapped.contains(object); + } + + @Override + public int indexOf(final Object object) { + this.check(); + return this.wrapped.indexOf(object); + } + + @Override + public int lastIndexOf(final Object object) { + this.check(); + return this.wrapped.lastIndexOf(object); + } + + @Override + public void forEach(final Consumer action) { + this.check(); + this.wrapped.forEach(action); + } + + @Override + public T[] toArray(final T[] array) { + this.check(); + return this.wrapped.toArray(array); + } + + @Override + public T[] toArray(final IntFunction generator) { + this.check(); + return this.wrapped.toArray(generator); + } + + @Override + public Object[] toArray() { + this.check(); + return this.wrapped.toArray(); + } + + @Override + public Iterator iterator() { + this.check(); + return this.wrapped.iterator(); + } + + @Override + public ListIterator listIterator() { + this.check(); + return this.wrapped.listIterator(); + } + + @Override + public ListIterator listIterator(final int index) { + this.check(); + return this.wrapped.listIterator(index); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + this.check(); + return new PrimaryThreadList<>(this.wrapped.subList(fromIndex, toIndex)); + } + + @Override + public Stream stream() { + this.check(); + return this.wrapped.stream(); + } + + @Override + public Stream parallelStream() { + this.check(); + return this.wrapped.parallelStream(); + } + + @Override + public Spliterator spliterator() { + this.check(); + return this.wrapped.spliterator(); + } + + @Override + public String toString() { + this.check(); + return this.wrapped.toString(); + } + + @Override + public int hashCode() { + this.check(); + return this.wrapped.hashCode(); + } + + @Override + public boolean equals(final Object object) { + this.check(); + return this.wrapped.equals(object); + } + + public static class RandomAccessPrimaryThreadList extends PrimaryThreadList implements RandomAccess { + public RandomAccessPrimaryThreadList(final List wrap) { + super(wrap); + } + } + + public static PrimaryThreadList of(final List list) { + return list instanceof RandomAccess ? new RandomAccessPrimaryThreadList<>(list) : new PrimaryThreadList<>(list); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java b/src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java new file mode 100644 index 000000000..0249c78c1 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java @@ -0,0 +1,282 @@ +package com.tuinity.tuinity.util; + +import org.spigotmc.AsyncCatcher; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class PrimaryThreadSet implements Set { + + protected final Set wrapped; + + public PrimaryThreadSet(final Set wrap) { + if (wrap == null) { + throw new NullPointerException("Wrapped list may not be null"); + } + + this.wrapped = wrap; + } + + protected void check() { + AsyncCatcher.catchOp("collection access"); + } + + @Override + public boolean add(final E element) { + this.check(); + return this.wrapped.add(element); + } + + @Override + public boolean addAll(final Collection collection) { + this.check(); + return this.wrapped.addAll(collection); + } + + @Override + public boolean remove(final Object element) { + this.check(); + return this.wrapped.remove(element); + } + + @Override + public boolean removeIf(final Predicate filter) { + this.check(); + return this.wrapped.removeIf(filter); + } + + @Override + public boolean removeAll(final Collection collection) { + this.check(); + return this.wrapped.removeAll(collection); + } + + @Override + public boolean retainAll(final Collection collection) { + this.check(); + return this.wrapped.retainAll(collection); + } + + @Override + public void clear() { + this.check(); + this.wrapped.clear(); + } + + @Override + public int size() { + this.check(); + return this.wrapped.size(); + } + + @Override + public boolean isEmpty() { + this.check(); + return this.wrapped.isEmpty(); + } + + @Override + public boolean containsAll(final Collection collection) { + this.check(); + return this.wrapped.containsAll(collection); + } + + @Override + public boolean contains(final Object object) { + this.check(); + return this.wrapped.contains(object); + } + + @Override + public void forEach(final Consumer action) { + this.check(); + this.wrapped.forEach(action); + } + + @Override + public T[] toArray(final T[] array) { + this.check(); + return this.wrapped.toArray(array); + } + + @Override + public T[] toArray(final IntFunction generator) { + this.check(); + return this.wrapped.toArray(generator); + } + + @Override + public Object[] toArray() { + this.check(); + return this.wrapped.toArray(); + } + + @Override + public Iterator iterator() { + this.check(); + return this.wrapped.iterator(); + } + + @Override + public Stream stream() { + this.check(); + return this.wrapped.stream(); + } + + @Override + public Stream parallelStream() { + this.check(); + return this.wrapped.parallelStream(); + } + + @Override + public Spliterator spliterator() { + this.check(); + return this.wrapped.spliterator(); + } + + @Override + public String toString() { + this.check(); + return this.wrapped.toString(); + } + + @Override + public int hashCode() { + this.check(); + return this.wrapped.hashCode(); + } + + @Override + public boolean equals(final Object object) { + this.check(); + return this.wrapped.equals(object); + } + + public static class SortedPrimaryThreadSet extends PrimaryThreadSet implements SortedSet { + + public SortedPrimaryThreadSet(final SortedSet set) { + super(set); + } + + @Override + public Comparator comparator() { + return ((SortedSet)this.wrapped).comparator(); + } + + @Override + public E first() { + this.check(); + return ((SortedSet)this.wrapped).first(); + } + + @Override + public E last() { + this.check(); + return ((SortedSet)this.wrapped).last(); + } + + @Override + public SortedSet headSet(final E toElement) { + this.check(); + return ((SortedSet)this.wrapped).headSet(toElement); + } + + @Override + public SortedSet subSet(final E fromElement, final E toElement) { + this.check(); + return ((SortedSet)this.wrapped).subSet(fromElement, toElement); + } + + @Override + public SortedSet tailSet(final E fromElement) { + this.check(); + return ((SortedSet)this.wrapped).tailSet(fromElement); + } + } + + public static class NavigablePrimaryThreadSet extends SortedPrimaryThreadSet implements NavigableSet { + + public NavigablePrimaryThreadSet(final NavigableSet set) { + super(set); + } + + @Override + public E ceiling(final E element) { + this.check(); + return ((NavigableSet)this.wrapped).ceiling(element); + } + + @Override + public E floor(final E element) { + this.check(); + return ((NavigableSet)this.wrapped).floor(element); + } + + @Override + public E higher(final E element) { + this.check(); + return ((NavigableSet)this.wrapped).higher(element); + } + + @Override + public E lower(final E element) { + this.check(); + return ((NavigableSet)this.wrapped).lower(element); + } + + @Override + public E pollFirst() { + this.check(); + return ((NavigableSet)this.wrapped).pollFirst(); + } + + @Override + public E pollLast() { + this.check(); + return ((NavigableSet)this.wrapped).pollLast(); + } + + @Override + public Iterator descendingIterator() { + this.check(); + return ((NavigableSet)this.wrapped).descendingIterator(); + } + + @Override + public final NavigableSet descendingSet() { + this.check(); + return new NavigablePrimaryThreadSet<>(((NavigableSet)this.wrapped).descendingSet()); + } + + @Override + public NavigableSet headSet(final E toElement, final boolean inclusive) { + this.check(); + return new NavigablePrimaryThreadSet<>(((NavigableSet)this.wrapped).headSet(toElement, inclusive)); + } + + @Override + public NavigableSet subSet(final E fromElement, final boolean fromInclusive, final E toElement, final boolean toInclusive) { + this.check(); + return new NavigablePrimaryThreadSet<>(((NavigableSet)this.wrapped).subSet(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet tailSet(final E fromElement, final boolean inclusive) { + this.check(); + return new NavigablePrimaryThreadSet<>(((NavigableSet)this.wrapped).tailSet(fromElement, inclusive)); + } + } + + public static PrimaryThreadSet of(final Set set) { + if (set instanceof NavigableSet) { + return new NavigablePrimaryThreadSet<>((NavigableSet)set); + } else if (set instanceof SortedSet) { + return new SortedPrimaryThreadSet<>((SortedSet)set); + } + return new PrimaryThreadSet<>(set); + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java b/src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java new file mode 100644 index 000000000..37adae900 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java @@ -0,0 +1,40 @@ +package com.tuinity.tuinity.util; + +import ca.spottedleaf.concurrentutil.misc.SynchronizationPoint; + +public final class TickSynchronizationPoint { + + private final SynchronizationPoint lock; + + public TickSynchronizationPoint(final TickThread[] threads) { + this.lock = new SynchronizationPoint(threads); + } + + private static int getIdForCurrentThread() { + return ((TickThread)Thread.currentThread()).id; + } + + public void start() { + this.lock.start(getIdForCurrentThread()); + } + + public void end() { + this.lock.end(getIdForCurrentThread()); + } + + public void enter() { + this.lock.enter(getIdForCurrentThread()); + } + + public void weakEnter() { + this.lock.weakEnter(getIdForCurrentThread()); + } + + public void enterAlone() { + this.lock.enterAlone(getIdForCurrentThread()); + } + + public void endAloneExecution() { + this.lock.endAloneExecution(getIdForCurrentThread()); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java new file mode 100644 index 000000000..d5688a734 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java @@ -0,0 +1,40 @@ +package com.tuinity.tuinity.util; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; + +public final class TickThread extends Thread { + + public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("tuinity.strict-thread-checks"); + + static { + if (STRICT_THREAD_CHECKS) { + MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer"); + } + } + + public static void softEnsureTickThread(final String reason) { + if (!STRICT_THREAD_CHECKS) { + return; + } + ensureTickThread(reason); + } + + + public static void ensureTickThread(final String reason) { + if (!Bukkit.isPrimaryThread()) { + throw new IllegalStateException(reason); + } + } + + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + public TickThread(final Runnable run, final String name, final int id) { + super(run, name); + this.id = id; + } + + public static TickThread getCurrentTickThread() { + return (TickThread)Thread.currentThread(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/Util.java b/src/main/java/com/tuinity/tuinity/util/Util.java new file mode 100644 index 000000000..a69fc1488 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/Util.java @@ -0,0 +1,103 @@ +package com.tuinity.tuinity.util; + +import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; +import net.minecraft.server.BlockPosition; +import net.minecraft.server.ChunkCoordIntPair; +import net.minecraft.server.Entity; +import org.bukkit.Bukkit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public final class Util { + + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + + public static void ensureTickThread(final String reason) { + if (!Bukkit.isPrimaryThread()) { + throw new IllegalStateException(reason); + } + } + + public static long getCoordinateKey(final BlockPosition blockPos) { + return getCoordinateKey(blockPos.getX() >> 4, blockPos.getZ() >> 4); + } + + public static long getCoordinateKey(final Entity entity) { + return getCoordinateKey(getChunkCoordinate(entity.locX()), getChunkCoordinate(entity.locZ())); + } + + public static int fastFloor(double x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; + } + + public static int fastFloor(float x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; + } + + public static long getCoordinateKey(final ChunkCoordIntPair pair) { + return getCoordinateKey(pair.x, pair.z); + } + + public static long getCoordinateKey(final int x, final int z) { + return ((long)z << 32) | (x & 0xFFFFFFFFL); + } + + public static int getCoordinateX(final long key) { + return (int)key; + } + + public static int getCoordinateZ(final long key) { + return (int)(key >>> 32); + } + + public static int getChunkCoordinate(final double coordinate) { + return Util.fastFloor(coordinate) >> 4; + } + + public static int getBlockCoordinate(final double coordinate) { + return Util.fastFloor(coordinate); + } + + public static long getBlockKey(final int x, final int y, final int z) { + return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); + } + + public static long getBlockKey(final BlockPosition pos) { + return getBlockKey(pos.getX(), pos.getY(), pos.getZ()); + } + + public static long getBlockKey(final Entity entity) { + return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ())); + } + + // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable + public static void mergeSortedSets(final Consumer consumer, final Comparator comparator, final SortedSet...sets) { + final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); + // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimick what mergesort does. + for (SortedSet set : sets) { + if (set != null) { + all.addAll(set); + } + } + all.forEach(consumer); + } + + // Taken from + // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c + // Original license is public domain + public static int fastRandomBounded(final long randomInteger, final long limit) { + // randomInteger must be [0, pow(2, 32)) + // limit must be [0, pow(2, 32)) + return (int)((randomInteger * limit) >>> 32); + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java new file mode 100644 index 000000000..88b198fd7 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java @@ -0,0 +1,39 @@ +package com.tuinity.tuinity.util.fastutil; + +import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; +import it.unimi.dsi.fastutil.doubles.DoubleList; + +import java.util.List; + +public abstract class ExtendedAbstractDoubleList extends AbstractDoubleList { + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof DoubleList)) { + if (other instanceof List) { + return super.equals(other); + } + return false; + } + + final DoubleList otherList = (DoubleList)other; + + final int otherSize = otherList.size(); + final int thisSize = this.size(); + + if (otherSize != thisSize) { + return false; + } + + for (int i = 0; i < thisSize; ++i) { + if (this.getDouble(i) != otherList.getDouble(i)) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java new file mode 100644 index 000000000..36457981e --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java @@ -0,0 +1,65 @@ +package com.tuinity.tuinity.util.fastutil; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; + +import java.util.Arrays; +import java.util.List; + +public class ExtendedDoubleArrayList extends DoubleArrayList { + + public ExtendedDoubleArrayList() { + super(); + } + + public ExtendedDoubleArrayList(final int capacity) { + super(capacity); + } + + public ExtendedDoubleArrayList(final double[] array) { + this(array, array.length, true); + } + + public ExtendedDoubleArrayList(final double[] array, final int size, final boolean copy) { + super(copy ? array.clone() : array, false); + this.size = size; + } + + public static ExtendedDoubleArrayList getList(final double[] list, final int requiredLength) { + if (list.length == requiredLength) { + return new ExtendedDoubleArrayList(list, requiredLength, false); + } else { + return new ExtendedDoubleArrayList(Arrays.copyOf(list, requiredLength), requiredLength, false); + } + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof DoubleList)) { + if (other instanceof List) { + return super.equals(other); + } + return false; + } + + final DoubleList otherList = (DoubleList)other; + + final int otherSize = otherList.size(); + final int thisSize = this.size(); + + if (otherSize != thisSize) { + return false; + } + + for (int i = 0; i < thisSize; ++i) { + if (this.getDouble(i) != otherList.getDouble(i)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java new file mode 100644 index 000000000..1a3f59633 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java @@ -0,0 +1,90 @@ +package com.tuinity.tuinity.util.fastutil; + +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; +import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; + +import java.lang.reflect.Field; +import java.util.function.Predicate; + +public class ExtendedObjectAVLTreeSet extends ObjectAVLTreeSet { + + private static final Field PREV_FIELD; + private static final Field NEXT_FIELD; + private static final Field CURR_FIELD; + private static final Field INDEX_FIELD; + + private static final Integer ZERO = Integer.valueOf(0); + + static { + try { + final Class clazz = Class.forName(ObjectAVLTreeSet.class.getCanonicalName() + "$SetIterator"); + PREV_FIELD = clazz.getDeclaredField("prev"); + PREV_FIELD.setAccessible(true); + + NEXT_FIELD = clazz.getDeclaredField("next"); + NEXT_FIELD.setAccessible(true); + + CURR_FIELD = clazz.getDeclaredField("curr"); + CURR_FIELD.setAccessible(true); + + INDEX_FIELD = clazz.getDeclaredField("index"); + INDEX_FIELD.setAccessible(true); + } catch (final Throwable thr) { + throw new RuntimeException(thr); + } + } + + private ObjectBidirectionalIterator cachedIterator = this.iterator(); + + { + this.nullIterator(this.cachedIterator); + } + + @Override + public boolean removeIf(Predicate filter) { + if (this.isEmpty()) { + return false; + } + + if (this.cachedIterator == null) { + return super.removeIf(filter); // recursive...? + } + + final ObjectBidirectionalIterator iterator = this.cachedIterator; + this.cachedIterator = null; + this.startIterator(iterator); + + boolean ret = false; + + while (iterator.hasNext()) { + if (filter.test(iterator.next())) { + ret = true; + iterator.remove(); + } + } + + this.nullIterator(iterator); + this.cachedIterator = iterator; + return ret; + } + + private void startIterator(final ObjectBidirectionalIterator iterator) { + // assume iterator is null'd + try { + NEXT_FIELD.set(iterator, this.firstEntry); + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex); // not going to occur + } + } + + private void nullIterator(final ObjectBidirectionalIterator iterator) { + try { + PREV_FIELD.set(iterator, null); + NEXT_FIELD.set(iterator, null); + CURR_FIELD.set(iterator, null); + INDEX_FIELD.set(iterator, ZERO); + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex); // not going to occur + } + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/map/AreaMap.java b/src/main/java/com/tuinity/tuinity/util/map/AreaMap.java new file mode 100644 index 000000000..dfb7a417e --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/map/AreaMap.java @@ -0,0 +1,388 @@ +package com.tuinity.tuinity.util.map; + +import com.tuinity.tuinity.util.Util; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.minecraft.server.ChunkCoordIntPair; +import net.minecraft.server.MinecraftServer; + +import javax.annotation.Nullable; +import java.util.Iterator; + +/** @author Spottedleaf */ +public abstract class AreaMap { + + /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */ + + private final Object2LongOpenHashMap objectToLastCoordinate = new Object2LongOpenHashMap<>(); + private final Object2IntOpenHashMap objectToViewDistance = new Object2IntOpenHashMap<>(); + + { + this.objectToViewDistance.defaultReturnValue(-1); + this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE); + } + + // we use linked for better iteration. + // map of: coordinate to set of objects in coordinate + private final Long2ObjectOpenHashMap> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.3f); + private final PooledLinkedHashSets pooledHashSets; + + private final ChangeCallback addCallback; + private final ChangeCallback removeCallback; + + public AreaMap() { + this(new PooledLinkedHashSets<>()); + } + + // let users define a "global" or "shared" pooled sets if they wish + public AreaMap(final PooledLinkedHashSets pooledHashSets) { + this(pooledHashSets, null, null); + } + + public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback) { + this.pooledHashSets = pooledHashSets; + this.addCallback = addCallback; + this.removeCallback = removeCallback; + } + + @Nullable + public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { + return this.areaMap.get(key); + } + + @Nullable + public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkCoordIntPair chunkPos) { + return this.getObjectsInRange(chunkPos.x, chunkPos.z); + } + + @Nullable + public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { + return this.getObjectsInRange(Util.getCoordinateKey(chunkX, chunkZ)); + } + + // Long.MIN_VALUE indicates the object is not mapped + public long getLastCoordinate(final E object) { + return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); + } + + // -1 indicates the object is not mapped + public int getLastViewDistance(final E object) { + return this.objectToViewDistance.getOrDefault(object, -1); + } + + // returns the total number of mapped chunks + public int size() { + return this.areaMap.size(); + } + + public void update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int oldDistance = this.objectToViewDistance.put(object, viewDistance); + final long newPos = Util.getCoordinateKey(chunkX, chunkZ); + if (oldDistance == -1) { + this.objectToLastCoordinate.put(object, newPos); + this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); + } else { + this.updateObject(object, this.objectToLastCoordinate.put(object, newPos), newPos, oldDistance, viewDistance); + } + //this.validate(object, viewDistance); + } + + public boolean remove(final E object) { + final long position = this.objectToLastCoordinate.removeLong(object); + final int viewDistance = this.objectToViewDistance.removeInt(object); + + if (viewDistance == -1) { + return false; + } + + final int currentX = Util.getCoordinateX(position); + final int currentZ = Util.getCoordinateZ(position); + + this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance); + //this.validate(object, -1); + return true; + } + + protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final E object); + + // expensive op, only for debug + private void validate(final E object, final int viewDistance) { + int entiesGot = 0; + int expectedEntries = (2 * viewDistance + 1); + expectedEntries *= expectedEntries; + if (viewDistance < 0) { + expectedEntries = 0; + } + + final long currPosition = this.objectToLastCoordinate.getLong(object); + + final int centerX = Util.getCoordinateX(currPosition); + final int centerZ = Util.getCoordinateZ(currPosition); + + for (Iterator>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator(); + iterator.hasNext();) { + + final Long2ObjectLinkedOpenHashMap.Entry> entry = iterator.next(); + final long key = entry.getLongKey(); + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); + + if (map.referenceCount == 0) { + throw new IllegalStateException("Invalid map"); + } + + if (map.contains(object)) { + ++entiesGot; + + final int chunkX = Util.getCoordinateX(key); + final int chunkZ = Util.getCoordinateZ(key); + + final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ)); + + if (dist > viewDistance) { + throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); + } + } + } + + if (entiesGot != expectedEntries) { + throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); + } + } + + protected final void addObjectTo(final E object, final int chunkX, final int chunkZ, final int currChunkX, + final int currChunkZ, final int prevChunkX, final int prevChunkZ) { + final long key = Util.getCoordinateKey(chunkX, chunkZ); + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty = this.getEmptySetFor(object); + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.putIfAbsent(key, empty); + + if (current != null) { + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWith(current, object); + if (next == current) { + throw new IllegalStateException("Expected different map: got " + next.toString()); + } + this.areaMap.put(key, next); + + current = next; + // fall through to callback + } else { + current = empty; + } + + if (this.addCallback != null) { + try { + this.addCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, current); + } catch (final Throwable ex) { + if (ex instanceof ThreadDeath) { + throw (ThreadDeath)ex; + } + MinecraftServer.LOGGER.error("Add callback for map threw exception ", ex); + } + } + } + + protected final void removeObjectFrom(final E object, final int chunkX, final int chunkZ, final int currChunkX, + final int currChunkZ, final int prevChunkX, final int prevChunkZ) { + final long key = Util.getCoordinateKey(chunkX, chunkZ); + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.get(key); + + if (current == null) { + throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")"); + } + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWithout(current, object); + + if (next == current) { + throw new IllegalStateException("Current map [" + next.toString() + "] should have contained " + object + ", (" + chunkX + "," + chunkZ + ")"); + } + + if (next != null) { + this.areaMap.put(key, next); + } else { + this.areaMap.remove(key); + } + + if (this.removeCallback != null) { + try { + this.removeCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, next); + } catch (final Throwable ex) { + if (ex instanceof ThreadDeath) { + throw (ThreadDeath)ex; + } + MinecraftServer.LOGGER.error("Remove callback for map threw exception ", ex); + } + } + } + + private void addObject(final E object, final int chunkX, final int chunkZ, final int prevChunkX, final int prevChunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + for (int x = chunkX - viewDistance; x <= maxX; ++x) { + for (int z = chunkZ - viewDistance; z <= maxZ; ++z) { + this.addObjectTo(object, x, z, chunkX, chunkZ, prevChunkX, prevChunkZ); + } + } + } + + private void removeObject(final E object, final int chunkX, final int chunkZ, final int currentChunkX, final int currentChunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + for (int x = chunkX - viewDistance; x <= maxX; ++x) { + for (int z = chunkZ - viewDistance; z <= maxZ; ++z) { + this.removeObjectFrom(object, x, z, currentChunkX, currentChunkZ, chunkX, chunkZ); + } + } + } + + /* math sign function except 0 returns 1 */ + protected static int sign(int val) { + return 1 | (val >> (Integer.SIZE - 1)); + } + + protected final void updateObject(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { + final int toX = Util.getCoordinateX(newPosition); + final int toZ = Util.getCoordinateZ(newPosition); + final int fromX = Util.getCoordinateX(oldPosition); + final int fromZ = Util.getCoordinateZ(oldPosition); + + final int dx = toX - fromX; + final int dz = toZ - fromZ; + + final int totalX = Math.abs(fromX - toX); + final int totalZ = Math.abs(fromZ - toZ); + + if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { + // teleported? + this.removeObject(object, fromX, fromZ, fromX, fromZ, oldViewDistance); + this.addObject(object, toX, toZ, fromX, fromZ, newViewDistance); + return; + } + + if (oldViewDistance != newViewDistance) { + // remove loop + + final int oldMaxX = fromX + oldViewDistance; + final int oldMaxZ = fromZ + oldViewDistance; + for (int currX = fromX - oldViewDistance; currX <= oldMaxX; ++currX) { + for (int currZ = fromZ - oldViewDistance; currZ <= oldMaxZ; ++currZ) { + + // only remove if we're outside the new view distance... + if (Math.max(Math.abs(currX - toX), Math.abs(currZ - toZ)) > newViewDistance) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + // add loop + + final int newMaxX = toX + newViewDistance; + final int newMaxZ = toZ + newViewDistance; + for (int currX = toX - newViewDistance; currX <= newMaxX; ++currX) { + for (int currZ = toZ - newViewDistance; currZ <= newMaxZ; ++currZ) { + + // only add if we're outside the old view distance... + if (Math.max(Math.abs(currX - fromX), Math.abs(currZ - fromZ)) > oldViewDistance) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + return; + } + + // x axis is width + // z axis is height + // right refers to the x axis of where we moved + // top refers to the z axis of where we moved + + // same view distance + + // used for relative positioning + final int up = sign(dz); // 1 if dz >= 0, -1 otherwise + final int right = sign(dx); // 1 if dx >= 0, -1 otherwise + + // The area excluded by overlapping the two view distance squares creates four rectangles: + // Two on the left, and two on the right. The ones on the left we consider the "removed" section + // and on the right the "added" section. + // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually + // exclusive to the regions they surround. + + // 4 points of the rectangle + int maxX; // exclusive + int minX; // inclusive + int maxZ; // exclusive + int minZ; // inclusive + + if (dx != 0) { + // handle right addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = fromX + (oldViewDistance * right) + right; // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dz != 0) { + // handle up addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = toX - (oldViewDistance * right); // inclusive + maxZ = toZ + (oldViewDistance * up) + up; // exclusive + minZ = fromZ + (oldViewDistance * up) + up; // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dx != 0) { + // handle left removal + + maxX = toX - (oldViewDistance * right); // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dz != 0) { + // handle down removal + + maxX = fromX + (oldViewDistance * right) + right; // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = toZ - (oldViewDistance * up); // exclusive + minZ = fromZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + } + + @FunctionalInterface + public static interface ChangeCallback { + + // if there is no previous position, then prevPos = Integer.MIN_VALUE + void accept(final E object, final int rangeX, final int rangeZ, final int currPosX, final int currPosZ, final int prevPosX, final int prevPosZ, + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState); + + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java b/src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java new file mode 100644 index 000000000..a29fdcbbc --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java @@ -0,0 +1,25 @@ +package com.tuinity.tuinity.util.map; + + +import net.minecraft.server.EntityPlayer; + +public final class PlayerAreaMap extends AreaMap { + + public PlayerAreaMap() { + super(); + } + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets) { + super(pooledHashSets); + } + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, + final ChangeCallback removeCallback) { + super(pooledHashSets, addCallback, removeCallback); + } + + @Override + protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final EntityPlayer player) { + return player.cachedSingleHashSetTuinity; + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java b/src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java new file mode 100644 index 000000000..cf888454c --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java @@ -0,0 +1,287 @@ +package com.tuinity.tuinity.util.map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.lang.ref.WeakReference; + +/** @author Spottedleaf */ +public class PooledLinkedHashSets { + + /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */ + + // we really want to avoid that equals() check as much as possible... + protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f); + + protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { + if (current.referenceCount == 0) { + throw new IllegalStateException("Cannot decrement reference count for " + current); + } + if (current.referenceCount == -1 || --current.referenceCount > 0) { + return; + } + + this.mapPool.remove(current); + return; + } + + public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { + final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); + + if (cached != null) { + decrementReferenceCount(current); + + if (cached.referenceCount == 0) { + // bring the map back from the dead + PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); + if (contending != null) { + // a map already exists with the elements we want + if (contending.referenceCount != -1) { + ++contending.referenceCount; + } + current.updateAddCache(object, contending); + return contending; + } + + cached.referenceCount = 1; + } else if (cached.referenceCount != -1) { + ++cached.referenceCount; + } + + return cached; + } + + if (!current.add(object)) { + return current; + } + + // we use get/put since we use a different key on put + PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); + + if (ret == null) { + ret = new PooledObjectLinkedOpenHashSet<>(current); + current.remove(object); + this.mapPool.put(ret, ret); + ret.referenceCount = 1; + } else { + if (ret.referenceCount != -1) { + ++ret.referenceCount; + } + current.remove(object); + } + + current.updateAddCache(object, ret); + + decrementReferenceCount(current); + return ret; + } + + // rets null if current.size() == 1 + public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { + if (current.set.size() == 1) { + decrementReferenceCount(current); + return null; + } + + final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); + + if (cached != null) { + decrementReferenceCount(current); + + if (cached.referenceCount == 0) { + // bring the map back from the dead + PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); + if (contending != null) { + // a map already exists with the elements we want + if (contending.referenceCount != -1) { + ++contending.referenceCount; + } + current.updateRemoveCache(object, contending); + return contending; + } + + cached.referenceCount = 1; + } else if (cached.referenceCount != -1) { + ++cached.referenceCount; + } + + return cached; + } + + if (!current.remove(object)) { + return current; + } + + // we use get/put since we use a different key on put + PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); + + if (ret == null) { + ret = new PooledObjectLinkedOpenHashSet<>(current); + current.add(object); + this.mapPool.put(ret, ret); + ret.referenceCount = 1; + } else { + if (ret.referenceCount != -1) { + ++ret.referenceCount; + } + current.add(object); + } + + current.updateRemoveCache(object, ret); + + decrementReferenceCount(current); + return ret; + } + + static final class RawSetObjectLinkedOpenHashSet extends ObjectOpenHashSet { + + public RawSetObjectLinkedOpenHashSet() { + super(); + } + + public RawSetObjectLinkedOpenHashSet(final int capacity) { + super(capacity); + } + + public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) { + super(capacity, loadFactor); + } + + @Override + public RawSetObjectLinkedOpenHashSet clone() { + return (RawSetObjectLinkedOpenHashSet)super.clone(); + } + + public E[] getRawSet() { + return this.key; + } + } + + public static final class PooledObjectLinkedOpenHashSet { + + private static final WeakReference NULL_REFERENCE = new WeakReference<>(null); + + final RawSetObjectLinkedOpenHashSet set; + int referenceCount; // -1 if special + int hash; // optimize hashcode + + // add cache + WeakReference lastAddObject = NULL_REFERENCE; + WeakReference> lastAddMap = NULL_REFERENCE; + + // remove cache + WeakReference lastRemoveObject = NULL_REFERENCE; + WeakReference> lastRemoveMap = NULL_REFERENCE; + + public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets pooledSets) { + this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f); + } + + public PooledObjectLinkedOpenHashSet(final E single) { + this((PooledLinkedHashSets)null); + this.referenceCount = -1; + this.add(single); + } + + public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { + this.set = other.set.clone(); + this.hash = other.hash; + } + + // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java + // generated by https://github.com/skeeto/hash-prospector + private static int hash0(int x) { + x *= 0x36935555; + x ^= x >>> 16; + return x; + } + + PooledObjectLinkedOpenHashSet getAddCache(final E element) { + final E currentAdd = this.lastAddObject.get(); + + if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { + return null; + } + + return this.lastAddMap.get(); + } + + PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { + final E currentRemove = this.lastRemoveObject.get(); + + if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { + return null; + } + + return this.lastRemoveMap.get(); + } + + void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { + this.lastAddObject = new WeakReference<>(element); + this.lastAddMap = new WeakReference<>(map); + } + + void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { + this.lastRemoveObject = new WeakReference<>(element); + this.lastRemoveMap = new WeakReference<>(map); + } + + boolean add(final E element) { + boolean added = this.set.add(element); + + if (added) { + this.hash += hash0(element.hashCode()); + } + + return added; + } + + boolean remove(Object element) { + boolean removed = this.set.remove(element); + + if (removed) { + this.hash -= hash0(element.hashCode()); + } + + return removed; + } + + public boolean contains(final Object element) { + return this.set.contains(element); + } + + public E[] getBackingSet() { + return this.set.getRawSet(); + } + + public int size() { + return this.set.size(); + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof PooledObjectLinkedOpenHashSet)) { + return false; + } + if (this.referenceCount == 0) { + return other == this; + } else { + if (other == this) { + // Unfortunately we are never equal to our own instance while in use! + return false; + } + return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); + } + } + + @Override + public String toString() { + return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + + this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java b/src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java new file mode 100644 index 000000000..815974682 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java @@ -0,0 +1,40 @@ +package com.tuinity.tuinity.util.pool; + +import net.minecraft.server.BlockPosition; +import net.minecraft.server.MinecraftServer; + +public class PooledBlockPositions { + + private static final int BLOCK_POOL_SIZE = 8192; + + private static final BlockPosition.MutableBlockPosition[] POOL = new BlockPosition.MutableBlockPosition[BLOCK_POOL_SIZE]; + private static int used = 0; // exclusive index of used positions + + static { + for (int i = 0; i < BLOCK_POOL_SIZE; ++i) { + POOL[i] = new BlockPosition.MutableBlockPosition(); + } + } + + public static BlockPosition.MutableBlockPosition get(final int x, final int y, final int z) { + final int currentUsed = used; + if (Thread.currentThread() != MinecraftServer.getServer().serverThread || currentUsed >= POOL.length) { + return new BlockPosition.MutableBlockPosition(x, y, z); + } + used = currentUsed + 1; + + final BlockPosition.MutableBlockPosition ret = POOL[currentUsed]; + POOL[currentUsed] = null; + + return ret.setValues(x, y, z); + } + + public static void ret(final BlockPosition.MutableBlockPosition position) { + final int currentUsed = used; + if (Thread.currentThread() != MinecraftServer.getServer().serverThread || currentUsed == 0) { + return; + } + + POOL[used = currentUsed - 1] = position; + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java b/src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java new file mode 100644 index 000000000..c726ef3c2 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java @@ -0,0 +1,142 @@ +package com.tuinity.tuinity.util.set; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class LinkedSortedSet implements Iterable { + + public final Comparator comparator; + + protected Link head; + protected Link tail; + + public LinkedSortedSet() { + this((Comparator)Comparator.naturalOrder()); + } + + public LinkedSortedSet(final Comparator comparator) { + this.comparator = comparator; + } + + public void clear() { + this.head = this.tail = null; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + Link next = LinkedSortedSet.this.head; + + @Override + public boolean hasNext() { + return this.next != null; + } + + @Override + public E next() { + final Link next = this.next; + if (next == null) { + throw new NoSuchElementException(); + } + this.next = next.next; + return next.element; + } + }; + } + + public boolean addLast(final E element) { + final Comparator comparator = this.comparator; + + Link curr = this.tail; + if (curr != null) { + int compare; + + while ((compare = comparator.compare(element, curr.element)) < 0) { + Link prev = curr; + curr = curr.prev; + if (curr != null) { + continue; + } + this.head = prev.prev = new Link<>(element, null, prev); + return true; + } + + if (compare != 0) { + // insert after curr + final Link next = curr.next; + final Link insert = new Link<>(element, curr, next); + curr.next = insert; + + if (next == null) { + this.tail = insert; + } else { + next.prev = insert; + } + return true; + } + + return false; + } else { + this.head = this.tail = new Link<>(element); + return true; + } + } + + public boolean addFirst(final E element) { + final Comparator comparator = this.comparator; + + Link curr = this.head; + if (curr != null) { + int compare; + + while ((compare = comparator.compare(element, curr.element)) > 0) { + Link prev = curr; + curr = curr.next; + if (curr != null) { + continue; + } + this.tail = prev.next = new Link<>(element, prev, null); + return true; + } + + if (compare != 0) { + // insert before curr + final Link prev = curr.prev; + final Link insert = new Link<>(element, prev, curr); + curr.prev = insert; + + if (prev == null) { + this.head = insert; + } else { + prev.next = insert; + } + return true; + } + + return false; + } else { + this.head = this.tail = new Link<>(element); + return true; + } + } + + protected static final class Link { + public E element; + public Link prev; + public Link next; + + public Link() {} + + public Link(final E element) { + this.element = element; + } + + public Link(final E element, final Link prev, final Link next) { + this.element = element; + this.prev = prev; + this.next = next; + } + } +} diff --git a/src/main/java/net/minecraft/server/ArraySetSorted.java b/src/main/java/net/minecraft/server/ArraySetSorted.java index 85f799a71..5fa6f75e2 100644 --- a/src/main/java/net/minecraft/server/ArraySetSorted.java +++ b/src/main/java/net/minecraft/server/ArraySetSorted.java @@ -6,12 +6,13 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.function.Predicate; // Tuinity public class ArraySetSorted extends AbstractSet { private final Comparator a; - private T[] b; - private int c; + private T[] b; private final T[] getBackingArray() { return this.b; } // Tuinity - OBFHELPER + private int c; private final int getSize() { return this.c; } private final void setSize(int value) { this.c = value; } // Tuinity - OBFHELPER private ArraySetSorted(int i, Comparator comparator) { this.a = comparator; @@ -22,6 +23,42 @@ public class ArraySetSorted extends AbstractSet { } } + // Tuinity start - optimise removeIf + @Override + public boolean removeIf(Predicate filter) { + // prev. impl used an iterator, which could be n^2 + int i = 0, len = this.getSize(); + T[] backingArray = this.getBackingArray(); + + for (;;) { + if (i >= len) { + return false; + } + if (!filter.test(backingArray[i])) { + ++i; + continue; + } + break; + } + + // we only want to write back to backingArray if we really need to + + int lastIndex = i; // this is where new elements are shifted to + + for (; i < len; ++i) { + T curr = backingArray[i]; + if (!filter.test(curr)) { // if test throws we're screwed + backingArray[lastIndex++] = curr; + } + } + + // cleanup end + Arrays.fill(backingArray, lastIndex, len, null); + this.setSize(lastIndex); + return true; + } + // Tuinity end - optimise removeIf + public static > ArraySetSorted a(int i) { return new ArraySetSorted<>(i, (Comparator)Comparator.naturalOrder()); // Paper - decompile fix } diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java index c950139c0..1a3234bb4 100644 --- a/src/main/java/net/minecraft/server/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java @@ -193,6 +193,7 @@ public class AxisAlignedBB { return this.d(vec3d.x, vec3d.y, vec3d.z); } + public final boolean intersects(AxisAlignedBB axisalignedbb) { return this.c(axisalignedbb); } // Paper - OBFHELPER public boolean c(AxisAlignedBB axisalignedbb) { return this.a(axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.maxY, axisalignedbb.maxZ); } @@ -206,6 +207,7 @@ public class AxisAlignedBB { return this.e(vec3d.x, vec3d.y, vec3d.z); } + public final boolean contains(double x, double y, double z) { return this.e(x, y, z); } // Tuinity - OBFHELPER public boolean e(double d0, double d1, double d2) { return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; } diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java index e650a2e48..83c5c8329 100644 --- a/src/main/java/net/minecraft/server/BlockPosition.java +++ b/src/main/java/net/minecraft/server/BlockPosition.java @@ -125,6 +125,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k); } + public final BlockPosition add(BaseBlockPosition baseblockposition) { return this.a(baseblockposition); } // Tuinity - OBFHELPER public BlockPosition a(BaseBlockPosition baseblockposition) { return this.b(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); } diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 92065fe02..cd7ada615 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -162,6 +162,94 @@ public class Chunk implements IChunkAccess { public PlayerChunk playerChunk; // Paper end + // Tuinity start + public final void forEachEntity(final Consumer consumer) { + Entity[] entities = this.entities.getRawData(); + for (int i = 0, len = this.entities.size(); i < len; ++i) { + consumer.accept(entities[i]); + } + } + + public static final int NEIGHBOUR_CACHE_RADIUS = 3; + // note that it also includes this chunk, just in case we want to check from a getIfCached result... + boolean loadedTicketLevel; + + private long neighbourChunksLoadedBitset; + private final Chunk[] loadedNeighbourChunks = new Chunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; + + private static int getNeighbourIndex(final int relativeX, final int relativeZ) { + // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) + // optimised variant of the above by moving some of the ops to compile time + return relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))); + } + + public final Chunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) { + return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)]; + } + + public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) { + return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0; + } + + public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final Chunk chunk) { + if (chunk == null) { + throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.loc); + } + final int index = getNeighbourIndex(relativeX, relativeZ); + this.loadedNeighbourChunks[index] = chunk; + this.neighbourChunksLoadedBitset |= (1L << index); + } + + public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) { + final int index = getNeighbourIndex(relativeX, relativeZ); + this.loadedNeighbourChunks[index] = null; + this.neighbourChunksLoadedBitset &= ~(1L << index); + } + + public final void resetNeighbours() { + this.neighbourChunksLoadedBitset = 0; + java.util.Arrays.fill(this.loadedNeighbourChunks, null); + } + + public final boolean areNeighboursLoaded(final int radius) { + // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) + switch (radius) { + case 0: + return this.loadedTicketLevel; + case 1: { + long mask = 0; + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + mask |= getNeighbourIndex(dx, dz); + } + } + return (this.neighbourChunksLoadedBitset & mask) == mask; + } + case 2: { + long mask = 0; + for (int dx = -2; dx <= 2; ++dx) { + for (int dz = -2; dz <= 2; ++dz) { + mask |= getNeighbourIndex(dx, dz); + } + } + return (this.neighbourChunksLoadedBitset & mask) == mask; + } + case 3: { + long mask = 0; + for (int dx = -3; dx <= 3; ++dx) { + for (int dz = -3; dz <= 3; ++dz) { + mask |= getNeighbourIndex(dx, dz); + } + } + return (this.neighbourChunksLoadedBitset & mask) == mask; + } + + default: + throw new IllegalArgumentException("Radius not recognized: " + radius); + } + } + // Tuinity end + public Chunk(World world, ProtoChunk protochunk) { this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null); Iterator iterator = protochunk.y().iterator(); @@ -407,6 +495,7 @@ public class Chunk implements IChunkAccess { @Override public void a(Entity entity) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async addEntity call"); // Tuinity this.q = true; int i = MathHelper.floor(entity.locX() / 16.0D); int j = MathHelper.floor(entity.locZ() / 16.0D); @@ -476,6 +565,7 @@ public class Chunk implements IChunkAccess { } public void a(Entity entity, int i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async removeEntity call"); // Tuinity if (i < 0) { i = 0; } @@ -508,6 +598,12 @@ public class Chunk implements IChunkAccess { return ((HeightMap) this.heightMap.get(heightmap_type)).a(i & 15, j & 15) - 1; } + // Tuinity start + public final int getHighestBlockYAt(HeightMap.Type type, int x, int z) { + return this.heightMap.get(type).get(x & 15, z & 15); // TODO obfhelper + } + // Tuinity end + @Nullable private TileEntity j(BlockPosition blockposition) { IBlockData iblockdata = this.getType(blockposition); @@ -649,6 +745,24 @@ public class Chunk implements IChunkAccess { // CraftBukkit start public void loadCallback() { + // Tuinity start - neighbour cache + int chunkX = this.loc.x; + int chunkZ = this.loc.z; + ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + Chunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); + if (neighbour != null) { + neighbour.setNeighbourLoaded(-dx, -dz, this); + // should be in cached already + this.setNeighbourLoaded(dx, dz, neighbour); + } + } + } + this.loadedTicketLevel = true; + // Tuinity end - neighbour cache + + ((WorldServer)this.world).onChunkLoad(this); // Tuinity - optimise entity list iteration org.bukkit.Server server = this.world.getServer(); ((WorldServer)this.world).getChunkProvider().addLoadedChunk(this); // Paper if (server != null) { @@ -692,6 +806,23 @@ public class Chunk implements IChunkAccess { // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); ((WorldServer)this.world).getChunkProvider().removeLoadedChunk(this); // Paper + // Tuinity start - neighbour cache + int chunkX = this.loc.x; + int chunkZ = this.loc.z; + ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + Chunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); + if (neighbour != null) { + neighbour.setNeighbourUnloaded(-dx, -dz); + } + } + } + this.loadedTicketLevel = false; + this.resetNeighbours(); + // Tuinity end - neighbour cache + + ((WorldServer)this.world).onChunkUnload(this); // Tuinity - optimise entity list iteration } // CraftBukkit end @@ -700,6 +831,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); @@ -739,6 +871,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable EntityTypes entitytypes, AxisAlignedBB axisalignedbb, List list, Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); @@ -761,6 +894,7 @@ public class Chunk implements IChunkAccess { } public void a(Class oclass, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java index 55f9f4e6e..d3c616e72 100644 --- a/src/main/java/net/minecraft/server/ChunkMap.java +++ b/src/main/java/net/minecraft/server/ChunkMap.java @@ -13,9 +13,10 @@ public abstract class ChunkMap extends LightEngineGraph { @Override protected void a(long i, int j, boolean flag) { - ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i); - int k = chunkcoordintpair.x; - int l = chunkcoordintpair.z; + // Tuinity start - remove allocation of ChunkCoordIntPair + int k = ChunkCoordIntPair.getX(i); + int l = ChunkCoordIntPair.getZ(i); + // Tuinity end for (int i1 = -1; i1 <= 1; ++i1) { for (int j1 = -1; j1 <= 1; ++j1) { @@ -32,9 +33,10 @@ public abstract class ChunkMap extends LightEngineGraph { @Override protected int a(long i, long j, int k) { int l = k; - ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i); - int i1 = chunkcoordintpair.x; - int j1 = chunkcoordintpair.z; + // Tuinity start - remove allocation of ChunkCoordIntPair + int i1 = ChunkCoordIntPair.getX(i); + int j1 = ChunkCoordIntPair.getZ(i); + // Tuinity end for (int k1 = -1; k1 <= 1; ++k1) { for (int l1 = -1; l1 <= 1; ++l1) { @@ -68,6 +70,7 @@ public abstract class ChunkMap extends LightEngineGraph { protected abstract int b(long i); + public final void update(long coordinate, int level, boolean increaseInLevel) { this.b(coordinate, level, increaseInLevel); } // Tuinity - OBFHELPER public void b(long i, int j, boolean flag) { this.a(ChunkCoordIntPair.a, i, j, flag); } diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java index 73d157076..cb2e67d15 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java @@ -1,5 +1,6 @@ package net.minecraft.server; +import com.tuinity.tuinity.util.Util; // Tuinity import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.mojang.datafixers.util.Either; @@ -27,13 +28,13 @@ import org.apache.logging.log4j.Logger; public abstract class ChunkMapDistance { private static final Logger LOGGER = LogManager.getLogger(); - private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; + private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; public static int getPlayerTicketLevel() { return ChunkMapDistance.b; } // Tuinity - OBFHELPER private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); - private final ChunkMapDistance.a e = new ChunkMapDistance.a(); - private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); - private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); - private final java.util.Queue pendingChunkUpdates = new java.util.LinkedList<>(); // PAIL pendingChunkUpdates // Paper - use a queue + private final ChunkMapDistance.a e = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.e; } // Tuinity - OBFHELPER + public static final int MOB_SPAWN_RANGE = 8; //private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Tuinity - no longer used + //private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); // Tuinity - no longer used + private final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque<>(); // PAIL pendingChunkUpdates // Paper - use a queue // Tuinity - use a better queue private final ChunkTaskQueueSorter i; private final Mailbox> j; private final Mailbox k; @@ -41,6 +42,96 @@ public abstract class ChunkMapDistance { private final Executor m; private long currentTick; + // Tuinity start + protected PlayerChunkMap chunkMap; + protected final ChunkMapDistance.TicketTracker playerTickViewDistanceHandler = new TicketTracker(ChunkMapDistance.getPlayerTicketLevel()) { + @Override + protected int tryQueueChunk(int chunkX, int chunkZ, EntityPlayer player) { + long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + PlayerChunk currentChunk = ChunkMapDistance.this.chunkMap.chunkMap.getUpdating(coordinate); + if (currentChunk != null) { + Chunk fullChunk = currentChunk.getFullReadyChunk(); + if (fullChunk != null && fullChunk.areNeighboursLoaded(2)) { + this.chunkReferenceMap.putIfAbsent(coordinate, LOADED_PLAYER_REFERENCE); + ChunkMapDistance.this.addTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ))); + return QUEUED; + } + } + + return FAILED; + } + + @Override + protected int getMaxChunkLoads(EntityPlayer player) { + return Integer.MAX_VALUE; + } + }; + + protected final ChunkMapDistance.TicketTracker playerNoTickViewDistanceHandler = new TicketTracker(33) { // loaded ticket level + @Override + protected int tryQueueChunk(int chunkX, int chunkZ, EntityPlayer player) { + long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + PlayerChunk currentChunk = ChunkMapDistance.this.chunkMap.chunkMap.getUpdating(coordinate); + if (currentChunk != null && currentChunk.getTicketLevel() <= this.ticketLevel) { + this.chunkReferenceMap.putIfAbsent(coordinate, LOADED_PLAYER_REFERENCE); + ChunkMapDistance.this.addTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ))); + return ALREADY_QUEUED; + } + if (this.chunkReferenceMap.putIfAbsent(coordinate, player.getId()) == -1) { + ChunkMapDistance.this.addTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ))); + return QUEUED; + } + return ALREADY_QUEUED; + } + + @Override + protected int getMaxChunkLoads(EntityPlayer player) { + return com.tuinity.tuinity.config.TuinityConfig.maxChunkLoadsPerPlayer; // per-player limits instead of global? + } + }; + // Tuinity end + + // Tuinity start - delay chunk unloads + private long nextUnloadId; // delay chunk unloads + private final Long2ObjectOpenHashMap> delayedChunks = new Long2ObjectOpenHashMap<>(); + public final void removeTickets(long chunk, TicketType type) { + ArraySetSorted> tickets = this.tickets.get(chunk); + if (tickets == null) { + return; + } + if (type == TicketType.DELAYED_UNLOAD) { + this.delayedChunks.remove(chunk); + } + boolean changed = tickets.removeIf((Ticket ticket) -> { + return ticket.getTicketType() == type; + }); + if (changed) { + this.getTicketTracker().update(chunk, getLowestTicketLevel(tickets), false); + } + } + + private final java.util.function.LongFunction> computeFuntion = (long key) -> { + Ticket ret = new Ticket<>(TicketType.DELAYED_UNLOAD, -1, ++ChunkMapDistance.this.nextUnloadId); + ret.isCached = true; + return ret; + }; + + private void computeDelayedTicketFor(long chunk, int removedLevel, ArraySetSorted> tickets) { + int lowestLevel = getLowestTicketLevel(tickets); + if (removedLevel > lowestLevel) { + return; + } + final Ticket ticket = this.delayedChunks.computeIfAbsent(chunk, this.computeFuntion); + if (ticket.getTicketLevel() != -1) { + // since we modify data used in sorting, we need to remove before + tickets.remove(ticket); + } + ticket.setCreationTick(this.currentTick); + ticket.setTicketLevel(removedLevel); + tickets.add(ticket); // re-add with new expire time and ticket level + } + // Tuinity end - delay chunk unloads + protected ChunkMapDistance(Executor executor, Executor executor1) { executor1.getClass(); Mailbox mailbox = Mailbox.a("player ticket throttler", executor1::execute); @@ -53,15 +144,34 @@ public abstract class ChunkMapDistance { } protected void purgeTickets() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async purge tickets"); // Tuinity ++this.currentTick; ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + int[] tempLevel = new int[] { PlayerChunkMap.GOLDEN_TICKET + 1 }; // Tuinity - delay chunk unloads while (objectiterator.hasNext()) { Entry>> entry = (Entry) objectiterator.next(); if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error - return ticket.b(this.currentTick); + // Tuinity start - delay chunk unloads + boolean ret = ticket.isExpired(this.currentTick); + if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy <= 0) { + return ret; + } + if (ret && ticket.getTicketType() != TicketType.DELAYED_UNLOAD && ticket.getTicketLevel() < tempLevel[0]) { + tempLevel[0] = ticket.getTicketLevel(); + } + if (ticket.getTicketType() == TicketType.DELAYED_UNLOAD && ticket.isCached) { + this.delayedChunks.remove(entry.getLongKey(), ticket); // clean up ticket... + } + return ret; + // Tuinity end - delay chunk unloads })) { + // Tuinity start - delay chunk unloads + if (tempLevel[0] < (PlayerChunkMap.GOLDEN_TICKET + 1)) { + this.computeDelayedTicketFor(entry.getLongKey(), tempLevel[0], entry.getValue()); + } + // Tuinity end - delay chunk unloads this.e.b(entry.getLongKey(), a((ArraySetSorted) entry.getValue()), false); } @@ -72,6 +182,7 @@ public abstract class ChunkMapDistance { } + private static int getLowestTicketLevel(ArraySetSorted> arraysetsorted) { return a(arraysetsorted); } // Tuinity - OBFHELPER private static int a(ArraySetSorted> arraysetsorted) { return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1; } @@ -85,8 +196,8 @@ public abstract class ChunkMapDistance { protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k); public boolean a(PlayerChunkMap playerchunkmap) { - this.f.a(); - this.g.a(); + //this.f.a(); // Tuinity - no longer used + //this.g.a(); // Tuinity - no longer used int i = Integer.MAX_VALUE - this.e.a(Integer.MAX_VALUE); boolean flag = i != 0; @@ -136,6 +247,7 @@ public abstract class ChunkMapDistance { } private boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket add"); // Tuinity ArraySetSorted> arraysetsorted = this.e(i); int j = a(arraysetsorted); Ticket ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error @@ -149,11 +261,17 @@ public abstract class ChunkMapDistance { } private boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity ArraySetSorted> arraysetsorted = this.e(i); boolean removed = false; // CraftBukkit if (arraysetsorted.remove(ticket)) { removed = true; // CraftBukkit + // Tuinity start - delay chunk unloads + if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy > 0 && ticket.getTicketType() != TicketType.DELAYED_UNLOAD) { + this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted); + } + // Tuinity end - delay chunk unloads } if (arraysetsorted.isEmpty()) { @@ -197,6 +315,7 @@ public abstract class ChunkMapDistance { } private ArraySetSorted> e(long i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async tickets compute"); // Tuinity return (ArraySetSorted) this.tickets.computeIfAbsent(i, (j) -> { return ArraySetSorted.a(4); }); @@ -214,24 +333,26 @@ public abstract class ChunkMapDistance { } public void a(SectionPosition sectionposition, EntityPlayer entityplayer) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player add"); // Tuinity long i = sectionposition.u().pair(); ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { return new ObjectOpenHashSet(); })).add(entityplayer); - this.f.b(i, 0, true); - this.g.b(i, 0, true); + //this.f.b(i, 0, true); // Tuinity - no longer used + //this.g.b(i, 0, true); // Tuinity - no longer used } public void b(SectionPosition sectionposition, EntityPlayer entityplayer) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player remove"); // Tuinity long i = sectionposition.u().pair(); ObjectSet objectset = (ObjectSet) this.c.get(i); objectset.remove(entityplayer); if (objectset.isEmpty()) { this.c.remove(i); - this.f.b(i, Integer.MAX_VALUE, false); - this.g.b(i, Integer.MAX_VALUE, false); + //this.f.b(i, Integer.MAX_VALUE, false); // Tuinity - no longer used + //this.g.b(i, Integer.MAX_VALUE, false); // Tuinity - no longer used } } @@ -249,18 +370,29 @@ public abstract class ChunkMapDistance { return s; } + protected void setViewDistance(int viewDistance) { this.a(viewDistance); } // Tuinity - OBFHELPER protected void a(int i) { - this.g.a(i); + //this.g.a(i); // Tuinity - no longer used + } + // Tuinity start - per player view distance + protected void setGlobalViewDistance(int viewDistance, PlayerChunkMap chunkMap) { + this.chunkMap = chunkMap; + this.setViewDistance(viewDistance); } + // Tuinity end public int b() { - this.f.a(); - return this.f.a.size(); + // Tuinity start - use distance map to implement + // note: this is the spawn chunk count + return this.chunkMap.playerChunkTickRangeMap.size(); + // Tuinity end - use distance map to implement } public boolean d(long i) { - this.f.a(); - return this.f.a.containsKey(i); + // Tuinity start - use distance map to implement + // note: this is the is spawn chunk method + return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(i) != null; + // Tuinity end - use distance map to implement } public String c() { @@ -269,6 +401,7 @@ public abstract class ChunkMapDistance { // CraftBukkit start public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); for (java.util.Iterator>> iterator = this.tickets.values().iterator(); iterator.hasNext();) { @@ -327,6 +460,222 @@ public abstract class ChunkMapDistance { } } + // Tuinity start - Per player view distance + abstract class TicketTracker { + + static final int LOADED_PLAYER_REFERENCE = -2; + + protected final it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap chunkReferenceMap = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(8192, 0.25f); + { + this.chunkReferenceMap.defaultReturnValue(-1); + } + protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap lastLoadedRadiusByPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f); + { + this.lastLoadedRadiusByPlayer.defaultReturnValue(-1); + } + + protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap pendingChunkLoadsByPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f); + protected final it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap lastChunkPositionByPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f); + { + this.lastChunkPositionByPlayer.defaultReturnValue(Long.MIN_VALUE); + } + + protected final int ticketLevel; + + public TicketTracker(int ticketLevel) { + this.ticketLevel = ticketLevel; + } + + protected final java.util.List players = new java.util.ArrayList<>(256); + + protected com.tuinity.tuinity.util.map.PlayerAreaMap areaMap; + + static final int ALREADY_QUEUED = 0; + static final int QUEUED = 1; + static final int FAILED = 2; + + protected abstract int tryQueueChunk(int chunkX, int chunkZ, EntityPlayer player); + + protected abstract int getMaxChunkLoads(EntityPlayer player); + + public void tick() { + for (EntityPlayer player : this.players) { + int playerId = player.getId(); + int lastLoadedRadius = this.lastLoadedRadiusByPlayer.get(playerId); + int pendingChunkLoads = this.pendingChunkLoadsByPlayer.get(playerId); + long lastChunkPos = this.lastChunkPositionByPlayer.get(playerId); + long currentChunkPos = this.areaMap.getLastCoordinate(player); + + if (currentChunkPos == Long.MIN_VALUE) { + // not tracking for whatever reason... + continue; + } + + int newX = Util.getCoordinateX(currentChunkPos); + int newZ = Util.getCoordinateZ(currentChunkPos); + + // handle movement + if (currentChunkPos != lastChunkPos) { + this.lastChunkPositionByPlayer.put(playerId, currentChunkPos); + if (lastChunkPos != Long.MIN_VALUE) { + int oldX = Util.getCoordinateX(lastChunkPos); + int oldZ = Util.getCoordinateZ(lastChunkPos); + + int radiusDiff = Math.max(Math.abs(newX - oldX), Math.abs(newZ - oldZ)); + lastLoadedRadius = Math.max(-1, lastLoadedRadius - radiusDiff); + this.lastLoadedRadiusByPlayer.put(playerId, lastLoadedRadius); + } + } + + int maxChunkLoads = this.getMaxChunkLoads(player); + + int radius = lastLoadedRadius + 1; + int viewDistance = this.areaMap.getLastViewDistance(player); + + if (radius > viewDistance) { + // distance map will unload our chunks + this.lastLoadedRadiusByPlayer.put(playerId, viewDistance); + continue; + } + + if (pendingChunkLoads >= maxChunkLoads) { + continue; + } + + radius_loop: + for (; radius <= viewDistance; ++radius) { + for (int offset = 0; offset <= radius; ++offset) { + // try to load the chunks closest to the player by distance + // so instead of going left->right on the x axis, we start at the center of the view distance square + // and go left and right at the same time + + // try top 2 chunks + // top left + int attempt = 0; + if ((attempt = this.tryQueueChunk(newX - offset, newZ + radius, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // top right + if ((attempt = this.tryQueueChunk(newX + offset, newZ + radius, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try bottom 2 chunks + + // bottom left + if ((attempt = this.tryQueueChunk(newX - offset, newZ - radius, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // bottom right + if ((attempt = this.tryQueueChunk(newX + offset, newZ - radius, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try left 2 chunks + + // left down + if ((attempt = this.tryQueueChunk(newX - radius, newZ - offset, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // left up + if ((attempt = this.tryQueueChunk(newX - radius, newZ + offset, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try right 2 chunks + + // right down + if ((attempt = this.tryQueueChunk(newX + radius, newZ - offset, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // right up + if ((attempt = this.tryQueueChunk(newX + radius, newZ + offset, player)) == QUEUED) { + if (++pendingChunkLoads >= maxChunkLoads) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + } + } + + int newLoadedRadius = radius - 1; + if (newLoadedRadius != lastLoadedRadius) { + this.lastLoadedRadiusByPlayer.put(playerId, newLoadedRadius); + } + this.pendingChunkLoadsByPlayer.put(playerId, pendingChunkLoads); + } + } + + public void addPlayer(EntityPlayer player) { + this.players.add(player); + } + + public void removePlayer(EntityPlayer player) { + this.players.remove(player); + this.lastLoadedRadiusByPlayer.remove(player.getId()); + this.pendingChunkLoadsByPlayer.remove(player.getId()); + this.lastChunkPositionByPlayer.remove(player.getId()); + } + + public void onChunkLoad(int chunkX, int chunkZ) { + long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + int playerReference = this.chunkReferenceMap.replace(coordinate, LOADED_PLAYER_REFERENCE); + if (playerReference != -1) { + this.pendingChunkLoadsByPlayer.computeIfPresent(playerReference, (Integer keyInMap, Integer valueInMap) -> { + return valueInMap - 1; + }); + } + } + + // this is invoked if and only if there are no other players in range of the chunk. + public void playerMoveOutOfRange(int chunkX, int chunkZ) { + long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + int playerReference = this.chunkReferenceMap.remove(coordinate); + if (playerReference != -1) { + if (playerReference != LOADED_PLAYER_REFERENCE) { + this.pendingChunkLoadsByPlayer.computeIfPresent(playerReference, (Integer keyInMap, Integer valueInMap) -> { + return valueInMap - 1; + }); + } + ChunkMapDistance.this.removeTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ))); + } + } + } + // Tuinity end - per player view distance + class c extends ChunkMapDistance.b { private int e = 0; @@ -344,7 +693,7 @@ public abstract class ChunkMapDistance { } public void a(int i) { - ObjectIterator objectiterator = this.a.long2ByteEntrySet().iterator(); + ObjectIterator objectiterator = this.a.long2ByteEntrySet().fastIterator(); // Tuinity - use fast iterator (reduces entry creation) while (objectiterator.hasNext()) { it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next(); @@ -425,7 +774,7 @@ public abstract class ChunkMapDistance { class b extends ChunkMap { - protected final Long2ByteMap a = new Long2ByteOpenHashMap(); + protected final Long2ByteOpenHashMap a = new Long2ByteOpenHashMap(); // Tuinity - change type for fast iterator protected final int b; protected b(int i) { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 76c7f4a50..516f27fd2 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -119,6 +119,12 @@ public class ChunkProviderServer extends IChunkProvider { } // Paper + // Tuinity start - rewrite ticklistserver + public final boolean isTickingReadyMainThread(BlockPosition pos) { + PlayerChunk chunk = this.playerChunkMap.chunkMap.getUpdating(com.tuinity.tuinity.util.Util.getCoordinateKey(pos)); + return chunk != null && chunk.isTickingReady(); + } + // Tuinity end - rewrite ticklistserver public ChunkProviderServer(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, WorldLoadListener worldloadlistener, Supplier supplier) { this.world = worldserver; @@ -308,7 +314,16 @@ public class ChunkProviderServer extends IChunkProvider { } private void bringToStatusAsync(int x, int z, ChunkCoordIntPair chunkPos, ChunkStatus status, java.util.function.Consumer onComplete) { - CompletableFuture> future = this.getChunkFutureMainThread(x, z, status, true); + // Tuinity start - add createFuture param + this.bringToStatusAsync(x, z, chunkPos, status, onComplete, true); + } + private void bringToStatusAsync(int x, int z, ChunkCoordIntPair chunkPos, ChunkStatus status, java.util.function.Consumer onComplete, boolean createFuture) { + CompletableFuture> future = this.getChunkFutureMainThread(x, z, status, createFuture); + + if (future.isDone() && future.getNow(null).right().isPresent() && !createFuture) { + return; + } + // Tuinity end Long identifier = Long.valueOf(this.asyncLoadSeqCounter++); int ticketLevel = MCUtil.getTicketLevelFor(status); this.addTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier); @@ -350,9 +365,18 @@ public class ChunkProviderServer extends IChunkProvider { public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { final int x = i; final int z = j; // Paper - conflict on variable change if (Thread.currentThread() != this.serverThread) { - return (IChunkAccess) CompletableFuture.supplyAsync(() -> { - return this.getChunkAt(i, j, chunkstatus, flag); - }, this.serverThreadQueue).join(); + // Tuinity start - improve async access - prevents plugins from sync loading chunks + Chunk chunk = this.getChunkAtIfLoadedImmediately(x, z); + if (chunk != null) { + return chunk; + } + + CompletableFuture future = new CompletableFuture<>(); + this.serverThreadQueue.execute(() -> { + this.bringToStatusAsync(x, z, new ChunkCoordIntPair(x, z), chunkstatus, future::complete, flag); + }); + return future.join(); + // Tuinity end } else { // Paper start - optimise for loaded chunks Chunk ifLoaded = this.getChunkAtIfLoadedMainThread(i, j); @@ -384,6 +408,19 @@ public class ChunkProviderServer extends IChunkProvider { this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); // Paper end + // Tuinity start - improve sync loading prioritisation + // lighting requires neighbours in a 1 radius to load + if (chunkstatus.isAtLeast(ChunkStatus.LIGHT)) { + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + int chunkLightX = dx + x; + int chunkLightZ = dz + z; + this.bringToStatusAsync(chunkLightX, chunkLightZ, new ChunkCoordIntPair(chunkLightX, chunkLightZ), ChunkStatus.LIGHT.getPreviousStatus(), (c) -> {}); + this.world.asyncChunkTaskManager.raisePriority(chunkLightX, chunkLightZ, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + } + } + } + // Tuinity end com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.world, x, z); // Paper - sync load info this.world.timings.chunkAwait.startTiming(); // Paper this.serverThreadQueue.awaitTasks(completablefuture::isDone); @@ -513,27 +550,39 @@ public class ChunkProviderServer extends IChunkProvider { @Override public boolean a(Entity entity) { - long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4); - - return this.a(i, PlayerChunk::b); + // Tuinity start - optimize is ticking ready type functions + // is entity ticking ready + PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(entity)); + return playerChunk != null && playerChunk.isEntityTickingReady(); + // Tuinity end - optimize is ticking ready type functions } @Override public boolean a(ChunkCoordIntPair chunkcoordintpair) { - return this.a(chunkcoordintpair.pair(), PlayerChunk::b); + // Tuinity start - optimize is ticking ready type functions + // is entity ticking ready + PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(chunkcoordintpair)); + return playerChunk != null && playerChunk.isEntityTickingReady(); + // Tuinity end - optimize is ticking ready type functions } - @Override - public boolean a(BlockPosition blockposition) { - long i = ChunkCoordIntPair.pair(blockposition.getX() >> 4, blockposition.getZ() >> 4); - - return this.a(i, PlayerChunk::a); + public final boolean isTickingReady(final BlockPosition pos) { return this.a(pos); } // Tuinity - OBFHELPER + @Override public boolean a(BlockPosition blockposition) { // Tuinity - OBFHELPER + // Tuinity start - optimize is ticking ready type functions + // is ticking ready + PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(blockposition)); + return playerChunk != null && playerChunk.isTickingReady(); + // Tuinity end - optimize is ticking ready type functions } public boolean b(Entity entity) { - long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4); - - return this.a(i, PlayerChunk::c); + // Tuinity start - optimize is ticking ready type functions + // is full chunk ready + if (Thread.currentThread() == this.serverThread) { + return this.getChunkAtIfLoadedMainThreadNoCache(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null; + } + return this.getChunkAtIfLoadedImmediately(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null; + // Tuinity end - optimize is ticking ready type functions } private boolean a(long i, Function>> function) { @@ -597,6 +646,10 @@ public class ChunkProviderServer extends IChunkProvider { this.chunkMapDistance.purgeTickets(); this.tickDistanceManager(); this.world.timings.doChunkMap.stopTiming(); // Spigot + // Tuinity start + this.playerChunkMap.getChunkMapDistanceManager().playerNoTickViewDistanceHandler.tick(); + this.playerChunkMap.getChunkMapDistanceManager().playerTickViewDistanceHandler.tick(); + // Tuinity end this.world.getMethodProfiler().exitEnter("chunks"); this.world.timings.chunks.startTiming(); // Paper - timings this.tickChunks(); @@ -609,6 +662,12 @@ public class ChunkProviderServer extends IChunkProvider { this.clearCache(); } + // Tuinity start + final com.tuinity.tuinity.util.ChunkList entityTickingChunks = new com.tuinity.tuinity.util.ChunkList(); + boolean isTickingChunks; + final it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap pendingEntityTickingChunkChanges = new it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap<>(16, 0.8f); + // Tuinity end + private void tickChunks() { long i = this.world.getTime(); long j = i - this.lastTickTime; @@ -619,6 +678,36 @@ public class ChunkProviderServer extends IChunkProvider { boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !world.getPlayers().isEmpty(); // CraftBukkit if (!flag) { + // Tuinity start - optimize isOutisdeRange + PlayerChunkMap playerChunkMap = this.playerChunkMap; + for (EntityPlayer player : this.world.players) { + if (!player.affectsSpawning || player.isSpectator()) { + playerChunkMap.playerMobSpawnMap.remove(player); + continue; + } + + int viewDistance = player.getEffectiveViewDistance(playerChunkMap); + + // copied and modified from isOutisdeRange + int chunkRange = world.spigotConfig.mobSpawnRange; + chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; + chunkRange = (chunkRange > ChunkMapDistance.MOB_SPAWN_RANGE) ? ChunkMapDistance.MOB_SPAWN_RANGE : chunkRange; + + com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); + event.callEvent(); + if (event.isCancelled() || event.getSpawnRadius() < 0) { + playerChunkMap.playerMobSpawnMap.remove(player); + continue; + } + + int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance + int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locX()); + int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locZ()); + + playerChunkMap.playerMobSpawnMap.update(player, chunkX, chunkZ, range); + player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in isOutsideRange + } + // Tuinity end - optimize isOutisdeRange this.world.getMethodProfiler().enter("pollingChunks"); int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); BlockPosition blockposition = this.world.getSpawn(); @@ -630,11 +719,10 @@ public class ChunkProviderServer extends IChunkProvider { EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values(); // Paper start - per player mob spawning int[] worldMobCount; - if (this.playerChunkMap.playerMobDistanceMap != null) { + // Tuinity start - use view distance map + if (this.world.paperConfig.perPlayerMobSpawns) { // update distance map - this.world.timings.playerMobDistanceMapUpdate.startTiming(); - this.playerChunkMap.playerMobDistanceMap.update(this.world.players, this.playerChunkMap.viewDistance); - this.world.timings.playerMobDistanceMapUpdate.stopTiming(); + // Tuinity end - use view distance map // re-set mob counts for (EntityPlayer player : this.world.players) { Arrays.fill(player.mobCounts, 0); @@ -647,20 +735,13 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings this.world.getMethodProfiler().exit(); - //Paper start - call player naturally spawn event - int chunkRange = world.spigotConfig.mobSpawnRange; - chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; - chunkRange = Math.min(chunkRange, 8); - for (EntityPlayer entityPlayer : this.world.players) { - entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); - entityPlayer.playerNaturallySpawnedEvent.callEvent(); - }; - // Paper end - this.playerChunkMap.f().forEach((playerchunk) -> { - Optional optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); - - if (optional.isPresent()) { - Chunk chunk = (Chunk) optional.get(); + // Tuinity - optimize isOutisdeRange - already done above + // Tuinity start - replace chunk map + this.isTickingChunks = true; + for (Chunk chunk : this.entityTickingChunks) { + PlayerChunk playerchunk = chunk.playerChunk; + if (playerchunk != null) { // make sure load event has been called along with the load logic we put there + // Tuinity end this.world.getMethodProfiler().enter("broadcast"); this.world.timings.broadcastChunkUpdates.startTiming(); // Paper - timings @@ -670,10 +751,10 @@ public class ChunkProviderServer extends IChunkProvider { ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); this.world.timings.chunkInhibitedRangeCheck.startTiming(); - if (!this.playerChunkMap.isOutsideOfRange(chunkcoordintpair)) { + if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Tuinity - optimise isOutsideOfRange // Paper end chunk.setInhabitedTime(chunk.getInhabitedTime() + j); - if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot + if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Tuinity - optimise isOutsideOfRange this.world.getMethodProfiler().enter("spawner"); this.world.timings.mobSpawn.startTiming(); // Spigot EnumCreatureType[] aenumcreaturetype1 = aenumcreaturetype; @@ -713,9 +794,23 @@ public class ChunkProviderServer extends IChunkProvider { if (this.world.paperConfig.perPlayerMobSpawns) { int minDiff = Integer.MAX_VALUE; - for (EntityPlayer entityplayer : this.playerChunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) { + // Tuinity start - use view distance map + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerChunkMap.playerViewDistanceMap.getObjectsInRange(chunk.getPos()); + if (players != null) { + Object[] backingSet = players.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer entityplayer = (EntityPlayer)temp; + if (entityplayer.isSpectator() || !entityplayer.affectsSpawning) { + continue; + } + // Tuinity end - use view distance map minDiff = Math.min(limit - this.playerChunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff); } + } // Tuinity - use view distance map difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; } @@ -738,7 +833,22 @@ public class ChunkProviderServer extends IChunkProvider { } this.world.timings.chunkInhibitedRangeCheck.stopTiming(); // Paper } - }); + }; // Tuinity + // Tuinity start - replace chunk map + this.isTickingChunks = false; + if (!this.pendingEntityTickingChunkChanges.isEmpty()) { + for (it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator> iterator = this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().fastIterator(this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().last()); iterator.hasPrevious(); ) { + it.unimi.dsi.fastutil.objects.Object2BooleanMap.Entry entry = iterator.previous(); + + if (entry.getBooleanValue()) { + this.entityTickingChunks.add(entry.getKey()); + } else { + this.entityTickingChunks.remove(entry.getKey()); + } + iterator.remove(); + } + } + // Tuinity end - replace chunk map this.world.getMethodProfiler().enter("customSpawners"); if (flag1) { try (co.aikar.timings.Timing ignored = this.world.timings.miscMobSpawning.startTiming()) { // Paper - timings diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index 4349d22cc..d529b795c 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -23,6 +23,14 @@ public class ChunkRegionLoader { private static final Logger LOGGER = LogManager.getLogger(); + // Tuinity start + // TODO: Check on update + public static long getLastWorldSaveTime(NBTTagCompound chunkData) { + NBTTagCompound levelData = chunkData.getCompound("Level"); + return levelData.getLong("LastUpdate"); + } + // Tuinity end + // Paper start - guard against serializing mismatching coordinates // TODO Note: This needs to be re-checked each update public static ChunkCoordIntPair getChunkCoordinate(NBTTagCompound chunkData) { @@ -344,10 +352,10 @@ public class ChunkRegionLoader { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound.setInt("DataVersion", SharedConstants.getGameVersion().getWorldVersion()); - nbttagcompound.set("Level", nbttagcompound1); + nbttagcompound.set("Level", nbttagcompound1); // Tuinity - diff on change nbttagcompound1.setInt("xPos", chunkcoordintpair.x); nbttagcompound1.setInt("zPos", chunkcoordintpair.z); - nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading + nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading // Tuinity - diff on change nbttagcompound1.setLong("InhabitedTime", ichunkaccess.getInhabitedTime()); nbttagcompound1.setString("Status", ichunkaccess.getChunkStatus().d()); ChunkConverter chunkconverter = ichunkaccess.p(); diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java index 88f167461..fd7b10429 100644 --- a/src/main/java/net/minecraft/server/ChunkStatus.java +++ b/src/main/java/net/minecraft/server/ChunkStatus.java @@ -103,7 +103,7 @@ public class ChunkStatus { private final ChunkStatus.c w; private final int x; private final ChunkStatus.Type y; - private final EnumSet z; + private final EnumSet z; public final HeightMap.Type[] heightMaps; // Tuinity private static CompletableFuture> a(ChunkStatus chunkstatus, LightEngineThreaded lightenginethreaded, IChunkAccess ichunkaccess) { boolean flag = a(chunkstatus, ichunkaccess); @@ -165,7 +165,7 @@ public class ChunkStatus { this.w = chunkstatus_c; this.x = i; this.y = chunkstatus_type; - this.z = enumset; + this.z = enumset; this.heightMaps = new java.util.ArrayList<>(this.z).toArray(new HeightMap.Type[0]); // Tuinity this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; } @@ -217,6 +217,7 @@ public class ChunkStatus { return this.z; } + public final boolean isAtLeast(ChunkStatus status) { return this.b(status); } // Tuinity - OBFHELPER public boolean b(ChunkStatus chunkstatus) { return this.c() >= chunkstatus.c(); } diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java index 349a0ea21..ede436939 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -44,7 +44,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer private static final Logger LOGGER = LogManager.getLogger(); private static final Pattern i = Pattern.compile("^[a-fA-F0-9]{40}$"); - private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue(); // Paper - use a proper queue + private final java.util.Queue serverCommandQueue = new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>(); // Paper - use a proper queue // Tuinity - Use a better queue private RemoteStatusListener remoteStatusListener; public final RemoteControlCommandListener remoteControlCommandListener; private RemoteControlListener remoteControlListener; @@ -194,6 +194,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer com.destroystokyo.paper.PaperConfig.registerCommands(); com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now // Paper end + com.tuinity.tuinity.config.TuinityConfig.init((File) options.valueOf("tuinity-settings")); // Tuinity - Server Config this.setSpawnAnimals(dedicatedserverproperties.spawnAnimals); this.setSpawnNPCs(dedicatedserverproperties.spawnNpcs); diff --git a/src/main/java/net/minecraft/server/DoubleListOffset.java b/src/main/java/net/minecraft/server/DoubleListOffset.java index 73657f740..9ff09b02d 100644 --- a/src/main/java/net/minecraft/server/DoubleListOffset.java +++ b/src/main/java/net/minecraft/server/DoubleListOffset.java @@ -3,7 +3,7 @@ package net.minecraft.server; import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; import it.unimi.dsi.fastutil.doubles.DoubleList; -public class DoubleListOffset extends AbstractDoubleList { +public class DoubleListOffset extends com.tuinity.tuinity.util.fastutil.ExtendedAbstractDoubleList { // Tuinity - remove iterator allocation private final DoubleList a; private final double b; diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 8974c16bf..23e91183e 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -208,6 +208,58 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // CraftBukkit end + // Tuinity start + // ender dragon is special cased for its large range, player is special cased for spectator mode + npc plugins + // non-final, let plugins set at their discretion + protected boolean isLegacyTrackingEntity = this instanceof EntityEnderDragon || this instanceof EntityPlayer; + + // We queue changes to tracker here because when adding to a chunk we do not know if the entity is in a trackable state + public final it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet trackQueue = new it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet(8); + public final it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet unTrackQueue = new it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet(8); + + public final void addToTrackQueue(EntityPlayer player) { + if (player == this) { + return; + } + int id = player.getId(); + this.trackQueue.add(id); + this.unTrackQueue.remove(id); + ((WorldServer)this.world).trackingUpdateQueue.add(this); + } + + public final void addToUntrackQueue(EntityPlayer player) { + if (player == this) { + return; + } + int id = player.getId(); + this.trackQueue.remove(id); + this.unTrackQueue.add(id); + ((WorldServer)this.world).trackingUpdateQueue.add(this); + } + + public final void clearTrackingQueues(EntityPlayer player) { + if (player == this) { + return; + } + int id = player.getId(); + this.trackQueue.remove(id); + this.unTrackQueue.remove(id); + if (this.trackQueue.isEmpty() && this.unTrackQueue.isEmpty()) { + ((WorldServer)this.world).trackingUpdateQueue.remove(this); + } + } + + + // Tuinity end + // Tuinity start + public final double getDistanceXZSquared(double x, double z) { + double diffX = x - this.locX(); + double diffZ = z - this.locZ(); + + return (diffX * diffX) + (diffZ * diffZ); + } + // Tuinity end + public Entity(EntityTypes entitytypes, World world) { this.id = Entity.entityCount.incrementAndGet(); this.passengers = Lists.newArrayList(); @@ -1371,6 +1423,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return MathHelper.c(f * f + f1 * f1 + f2 * f2); } + public final double getDistanceSquared(double x, double y, double z) { return this.g(x, y, z); } // Tuinity - OBFHELPER public double g(double d0, double d1, double d2) { double d3 = this.locX() - d0; double d4 = this.locY() - d1; diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java index af10fc36e..bf14d33c0 100644 --- a/src/main/java/net/minecraft/server/EntityEnderDragon.java +++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java @@ -579,9 +579,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { // CraftBukkit start - Use relative location for far away sounds // this.world.b(1028, new BlockPosition(this), 0); // Paper start - int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - per player view distance for (EntityPlayer player : ((WorldServer)world).getPlayers()) { - //final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + final int viewDistance = player.getEffectiveViewDistance(player.getWorldServer().getChunkProvider().playerChunkMap) << 4; // Tuinity - per player view distance // Paper end double deltaX = this.locX() - player.locX(); double deltaZ = this.locZ() - player.locZ(); diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java index 1991cee43..27ef47600 100644 --- a/src/main/java/net/minecraft/server/EntityInsentient.java +++ b/src/main/java/net/minecraft/server/EntityInsentient.java @@ -641,20 +641,27 @@ public abstract class EntityInsentient extends EntityLiving { if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.J()) { this.die(); } else if (!this.isPersistent() && !this.I()) { - EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D); + EntityHuman entityhuman = this.world.findClosestPlayer(this.locX(), this.locY(), this.locZ(), this.world.paperConfig.hardDespawnDistanceNotSquared, (Entity player) -> (((EntityHuman)player).affectsSpawning && !((EntityHuman)player).isSpectator())); // Tuinity - fix this function to properly handle spawning api - if (entityhuman != null && entityhuman.affectsSpawning) { // Paper - Affects Spawning API + if (entityhuman != null) { // Paper - Affects Spawning API // Tuinity - check not needed anymore double d0 = entityhuman.h(this); if (d0 > world.paperConfig.hardDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances this.die(); - } - - if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > world.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances + } else if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > world.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances // Tuinity this.die(); - } else if (d0 < 1024.0D) { + } else if (d0 < world.paperConfig.softDespawnDistance) { // Tuinity this.ticksFarFromPlayer = 0; } + } else { // Tuinity start + // no player in range, try all players + for (EntityHuman player : this.world.getPlayers()) { + if (player.affectsSpawning && !player.isSpectator()) { + this.die(); + break; + } + } + /* Concret eend */ } } else { diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index e7bfbc330..937e513d8 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -104,6 +104,37 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + // Tuinity start + public final com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSetTuinity; + // Tuinity end + + // Tuinity start - view distance api + public boolean needsChunkCenterUpdate; + int viewDistance = -1; + public final int getRawViewDistance() { + return this.viewDistance; + } + public final int getEffectiveViewDistance() { + return this.getEffectiveViewDistance(((WorldServer)this.world).getChunkProvider().playerChunkMap); + } + public final int getEffectiveViewDistance(PlayerChunkMap chunkMap) { + return this.viewDistance == -1 ? chunkMap.viewDistance : this.viewDistance; + } + + int noTickViewDistance = -1; + public final int getRawNoTickViewDistance() { + return this.noTickViewDistance; + } + public final int getEffectiveNoTickViewDistance() { + return this.getEffectiveNoTickViewDistance(((WorldServer)this.world).getChunkProvider().playerChunkMap); + } + public final int getEffectiveNoTickViewDistance(PlayerChunkMap chunkMap) { + return this.noTickViewDistance == -1 ? chunkMap.noTickViewDistance : this.noTickViewDistance; + } + // Tuinity end - view distance api + + double lastEntitySpawnRadiusSquared; // Tuinity - optimise isOutsideRange, this field is in blocks + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { super((World) worldserver, gameprofile); playerinteractmanager.player = this; @@ -122,6 +153,9 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.canPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + // Tuinity start + this.cachedSingleHashSetTuinity = new com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); + // Tuinity end } // Yes, this doesn't match Vanilla, but it's the best we can do for now. @@ -1773,8 +1807,13 @@ public class EntityPlayer extends EntityHuman implements ICrafting { } public void a(ChunkCoordIntPair chunkcoordintpair) { + // Tuinity start - remove ChunkCoordIntPair allocation + this.sendChunkUnload(chunkcoordintpair.x, chunkcoordintpair.z); + } + public final void sendChunkUnload(int x, int z) { + // Tuinity end - remove ChunkCoordIntPair allocation if (this.isAlive()) { - this.playerConnection.sendPacket(new PacketPlayOutUnloadChunk(chunkcoordintpair.x, chunkcoordintpair.z)); + this.playerConnection.sendPacket(new PacketPlayOutUnloadChunk(x, z)); // Tuinity } } diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 3a88c9a67..b4d3c4a4f 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -18,7 +18,7 @@ import org.bukkit.event.player.PlayerVelocityEvent; public class EntityTrackerEntry { private static final Logger LOGGER = LogManager.getLogger(); - private final WorldServer b; + private final WorldServer b; private WorldServer getWorld() { return this.b; } // Tuinity - OBFHELPER private final Entity tracker; private final int d; private final boolean e; @@ -70,7 +70,9 @@ public class EntityTrackerEntry { this.r = entity.onGround; } + public final void tick() { this.a(); } // Tuinity - OBFHELPER public void a() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity List list = this.tracker.getPassengers(); if (!list.equals(this.p)) { @@ -107,8 +109,8 @@ public class EntityTrackerEntry { int j; if (this.tracker.isPassenger()) { - i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F); - j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F); + i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F); // Tuinity - diff on change, used in forceStaleMeta + j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F); // Tuinity - diff on change, used in forceStaleMeta boolean flag = Math.abs(i - this.yRot) >= 1 || Math.abs(j - this.xRot) >= 1; if (flag) { @@ -124,8 +126,10 @@ public class EntityTrackerEntry { ++this.o; i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F); j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F); - Vec3D vec3d = this.tracker.getPositionVector().d(PacketPlayOutEntity.a(this.xLoc, this.yLoc, this.zLoc)); - boolean flag1 = vec3d.g() >= 7.62939453125E-6D; + double vec3d_dx = this.tracker.locX() - 2.44140625E-4D*(this.xLoc); + double vec3d_dy = this.tracker.locY() - 2.44140625E-4D*(this.yLoc); + double vec3d_dz = this.tracker.locZ() - 2.44140625E-4D*(this.zLoc); + boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; Packet packet1 = null; boolean flag2 = flag1 || this.tickCounter % 60 == 0; boolean flag3 = Math.abs(i - this.yRot) >= 1 || Math.abs(j - this.xRot) >= 1; @@ -142,9 +146,11 @@ public class EntityTrackerEntry { // CraftBukkit end if (this.tickCounter > 0 || this.tracker instanceof EntityArrow) { - long k = PacketPlayOutEntity.a(vec3d.x); - long l = PacketPlayOutEntity.a(vec3d.y); - long i1 = PacketPlayOutEntity.a(vec3d.z); + // Tuinity start - remove allocation of Vec3d here + long k = PacketPlayOutEntity.a(vec3d_dx); + long l = PacketPlayOutEntity.a(vec3d_dy); + long i1 = PacketPlayOutEntity.a(vec3d_dz); + // Tuinity end - remove allocation of Vec3d here boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; if (!flag4 && this.o <= 400 && !this.q && this.r == this.tracker.onGround) { @@ -248,11 +254,28 @@ public class EntityTrackerEntry { } + // Tuinity end - optimise entity tracking + // TODO make use of this in second pass rewrite + final void resetState() { + // required since we do not tick all tracker entries unless they have trackers - so data can and will get stale + if (this.trackedPlayers.size() != 0) { + return; + } + + this.synchronizeLocation(); + this.writeStaleData(); + this.xRot = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F); // Tuinity - diff on change, used in forceStaleMeta + this.yRot = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F); // Tuinity - diff on change, used in forceStaleMeta + } + // Tuinity end - optimise entity tracking + + public final void onUntrack(EntityPlayer player) { this.a(player); } // Tuinity - OBFHELPER public void a(EntityPlayer entityplayer) { this.tracker.c(entityplayer); entityplayer.c(this.tracker); } + public final void onTrack(EntityPlayer player) { this.b(player); } // Tuinity - OBFHELPER public void b(EntityPlayer entityplayer) { PlayerConnection playerconnection = entityplayer.playerConnection; @@ -352,6 +375,7 @@ public class EntityTrackerEntry { } + private final void writeStaleData() { this.c(); } // Tuinity - OBFHELPER private void c() { DataWatcher datawatcher = this.tracker.getDataWatcher(); @@ -377,6 +401,7 @@ public class EntityTrackerEntry { } + private final void synchronizeLocation() { this.d(); } // Tuinity - OBFHELPER private void d() { this.xLoc = PacketPlayOutEntity.a(this.tracker.locX()); this.yLoc = PacketPlayOutEntity.a(this.tracker.locY()); diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java index 8977c3516..bace6cf36 100644 --- a/src/main/java/net/minecraft/server/EntityWither.java +++ b/src/main/java/net/minecraft/server/EntityWither.java @@ -208,9 +208,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity { // CraftBukkit start - Use relative location for far away sounds // this.world.b(1023, new BlockPosition(this), 0); // Paper start - int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API + //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - per player view distance for (EntityPlayer player : ((WorldServer)world).getPlayers()) { - //final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch + final int viewDistance = player.getEffectiveViewDistance(player.getWorldServer().getChunkProvider().playerChunkMap) << 4; // Tuinity - per player view distance // Paper end double deltaX = this.locX() - player.locX(); double deltaZ = this.locZ() - player.locZ(); diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java index 29cb545a8..aa7339610 100644 --- a/src/main/java/net/minecraft/server/HeightMap.java +++ b/src/main/java/net/minecraft/server/HeightMap.java @@ -119,6 +119,7 @@ public class HeightMap { } } + public final int get(int x, int z) { return this.a(x, z); } // Tuinity - OBFHELPER public int a(int i, int j) { return this.a(c(i, j)); } @@ -154,7 +155,7 @@ public class HeightMap { private final String g; private final HeightMap.Use h; private final Predicate i; - private static final Map j = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { + private static final Map j = SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix HeightMap.Type[] aheightmap_type = values(); int i = aheightmap_type.length; @@ -166,7 +167,7 @@ public class HeightMap { }); - private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { + private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { // Tuinity - decompile fix this.g = s; this.h = heightmap_use; this.i = predicate; diff --git a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java index cfe43e882..e7a58989d 100644 --- a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java +++ b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java @@ -13,7 +13,7 @@ public abstract class IAsyncTaskHandler implements Mailbox d = Queues.newConcurrentLinkedQueue(); + private final ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue d = new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>(); // Tuinity - Use a better queue private int e; protected IAsyncTaskHandler(String s) { diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java index 4157e50e4..c522a7c2a 100644 --- a/src/main/java/net/minecraft/server/IEntityAccess.java +++ b/src/main/java/net/minecraft/server/IEntityAccess.java @@ -59,8 +59,8 @@ public interface IEntityAccess { } } - @Nullable - default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { + @Nullable default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { return this.findClosestPlayer(d0, d1, d2, d3, predicate); } // Tuinity - allow overriding with OBFHELPER + @Nullable default EntityHuman findClosestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { // Tuinity - OBFHELPER double d4 = -1.0D; EntityHuman entityhuman = null; Iterator iterator = this.getPlayers().iterator(); @@ -95,6 +95,11 @@ public interface IEntityAccess { @Nullable default EntityHuman a(double d0, double d1, double d2) { + // Tuinity start - add predicate parameter and allow for WorldServer to override + return this.findClosestPlayerXZ(d0, d1, d2, IEntitySelector.notSpectator()); + } + default EntityHuman findClosestPlayerXZ(double d0, double d1, double d2, @Nullable Predicate predicate) { + // Tuinity end - add predicate parameter and allow for WorldServer to override double d3 = -1.0D; EntityHuman entityhuman = null; Iterator iterator = this.getPlayers().iterator(); @@ -102,7 +107,7 @@ public interface IEntityAccess { while (iterator.hasNext()) { EntityHuman entityhuman1 = (EntityHuman) iterator.next(); - if (IEntitySelector.f.test(entityhuman1)) { + if ((predicate == null || predicate.test(entityhuman1))) { // Tuinity - add predicate parameter double d4 = entityhuman1.g(d0, entityhuman1.locY(), d1); if ((d2 < 0.0D || d4 < d2 * d2) && (d3 == -1.0D || d4 < d3)) { @@ -141,19 +146,26 @@ public interface IEntityAccess { @Nullable default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving) { - return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, entityliving, entityliving.locX(), entityliving.locY(), entityliving.locZ()); + return (EntityHuman) this.getNearestPlayerForPathFinding(pathfindertargetcondition, entityliving, entityliving.locX(), entityliving.locY(), entityliving.locZ()); // Tuinity - allow overriding in WorldServer for find nearest player optimisation } @Nullable default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, double d0, double d1, double d2) { - return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, entityliving, d0, d1, d2); + return (EntityHuman) this.getNearestPlayerForPathFinding(pathfindertargetcondition, entityliving, d0, d1, d2); // Tuinity - allow overriding in WorldServer for find nearest player optimisation } @Nullable default EntityHuman a(PathfinderTargetCondition pathfindertargetcondition, double d0, double d1, double d2) { - return (EntityHuman) this.a(this.getPlayers(), pathfindertargetcondition, (EntityLiving) null, d0, d1, d2); + return (EntityHuman) this.getNearestPlayerForPathFinding(pathfindertargetcondition, (EntityLiving) null, d0, d1, d2); // Tuinity - allow overriding in WorldServer for find nearest player optimisation } + // Tuinity start - allow overriding in WorldServer for find nearest player optimisation + @Nullable + default EntityHuman getNearestPlayerForPathFinding(PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { + return this.getNearestEntityForPathFinding(this.getPlayers(), pathfindertargetcondition, entityliving, d0, d1, d2); + } + // Tuinity end - allow overriding in WorldServer + @Nullable default T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { return this.a(this.a(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix @@ -164,8 +176,8 @@ public interface IEntityAccess { return this.a(this.b(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix } - @Nullable - default T a(List list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { + @Nullable default T getNearestEntityForPathFinding(List list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { return this.a(list, pathfindertargetcondition, entityliving, d0, d1, d2); } // Tuinity - OBFHELPER + @Nullable default T a(List list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { // Tuinity - OBFHELPER double d3 = -1.0D; T t0 = null; Iterator iterator = list.iterator(); // Paper - decompile fix @@ -187,6 +199,11 @@ public interface IEntityAccess { } default List a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { + // Tuinity start - allow overriding in WorldServer + return this.getNearestPlayersForPathFinding(pathfindertargetcondition, entityliving, axisalignedbb); + } + default List getNearestPlayersForPathFinding(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { + // Tuinity end - allow overriding in WorldServer List list = Lists.newArrayList(); Iterator iterator = this.getPlayers().iterator(); diff --git a/src/main/java/net/minecraft/server/LightEngineBlock.java b/src/main/java/net/minecraft/server/LightEngineBlock.java index 93a972605..43424c88a 100644 --- a/src/main/java/net/minecraft/server/LightEngineBlock.java +++ b/src/main/java/net/minecraft/server/LightEngineBlock.java @@ -37,7 +37,7 @@ public final class LightEngineBlock extends LightEngineLayer= 15) { diff --git a/src/main/java/net/minecraft/server/LightEngineLayer.java b/src/main/java/net/minecraft/server/LightEngineLayer.java index f72ff8495..f27a14873 100644 --- a/src/main/java/net/minecraft/server/LightEngineLayer.java +++ b/src/main/java/net/minecraft/server/LightEngineLayer.java @@ -11,7 +11,7 @@ public abstract class LightEngineLayer, S e protected final EnumSkyBlock b; protected final S c; private boolean f; - protected final BlockPosition.MutableBlockPosition d = new BlockPosition.MutableBlockPosition(); + protected final BlockPosition.MutableBlockPosition d = new BlockPosition.MutableBlockPosition(); protected final MutableInt lastLevel = new MutableInt(); // Tuinity - avoid allocating MutableInt private final long[] g = new long[2]; private final IBlockAccess[] h = new IBlockAccess[2]; diff --git a/src/main/java/net/minecraft/server/LightEngineSky.java b/src/main/java/net/minecraft/server/LightEngineSky.java index 2301a982e..488d4f289 100644 --- a/src/main/java/net/minecraft/server/LightEngineSky.java +++ b/src/main/java/net/minecraft/server/LightEngineSky.java @@ -27,7 +27,7 @@ public final class LightEngineSky extends LightEngineLayer= 15) { return k; } else { - MutableInt mutableint = new MutableInt(); + MutableInt mutableint = this.lastLevel; // Tuinity - avoid allocation of MutableInt IBlockData iblockdata = this.a(j, mutableint); if (mutableint.getValue() >= 15) { diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java index a3f919816..dacc96414 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorage.java +++ b/src/main/java/net/minecraft/server/LightEngineStorage.java @@ -19,11 +19,12 @@ public abstract class LightEngineStorage> e protected final LongSet b = new LongOpenHashSet(); protected final LongSet c = new LongOpenHashSet(); protected final LongSet d = new LongOpenHashSet(); - protected volatile M e; - protected final M f; + protected volatile M e; // Tuinity - diff on change, should be "visible" + protected final M f; // Tuinity - diff on change, should be "updating" protected final LongSet g = new LongOpenHashSet(); protected final LongSet h = new LongOpenHashSet(); - protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); + protected final Long2ObjectOpenHashMap synchronized_map_real = new Long2ObjectOpenHashMap<>(); // Tuinity - store wrapped map, we need fastIterator + protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(this.synchronized_map_real); // Tuinity - store wrapped map, we need fastIterator private final LongSet n = new LongOpenHashSet(); private final LongSet o = new LongOpenHashSet(); protected volatile boolean j; @@ -178,7 +179,7 @@ public abstract class LightEngineStorage> e NibbleArray nibblearray; while (longiterator.hasNext()) { - i = (Long) longiterator.next(); + i = longiterator.nextLong(); // Tuinity - use nextLong this.a(lightenginelayer, i); NibbleArray nibblearray1 = (NibbleArray) this.i.remove(i); @@ -196,13 +197,13 @@ public abstract class LightEngineStorage> e longiterator = this.o.iterator(); while (longiterator.hasNext()) { - i = (Long) longiterator.next(); + i = longiterator.nextLong(); // Tuinity - use nextLong this.l(i); } this.o.clear(); this.j = false; - ObjectIterator objectiterator = this.i.long2ObjectEntrySet().iterator(); + ObjectIterator objectiterator = this.synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation Entry entry; long j; @@ -225,7 +226,7 @@ public abstract class LightEngineStorage> e longiterator = this.i.keySet().iterator(); while (longiterator.hasNext()) { - i = (Long) longiterator.next(); + i = longiterator.nextLong(); // Tuinity - use nextLong if (this.g(i)) { int k = SectionPosition.c(SectionPosition.b(i)); int l = SectionPosition.c(SectionPosition.c(i)); @@ -279,7 +280,7 @@ public abstract class LightEngineStorage> e } } - objectiterator = this.i.long2ObjectEntrySet().iterator(); + objectiterator = this.synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to avoid entry creation while (objectiterator.hasNext()) { entry = (Entry) objectiterator.next(); diff --git a/src/main/java/net/minecraft/server/LightEngineStorageArray.java b/src/main/java/net/minecraft/server/LightEngineStorageArray.java index b978723a6..5e2051ee1 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorageArray.java +++ b/src/main/java/net/minecraft/server/LightEngineStorageArray.java @@ -8,10 +8,17 @@ public abstract class LightEngineStorageArray a; + protected final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object data; // Tuinity - avoid copying light data + protected final boolean isVisible; // Tuinity - avoid copying light data - protected LightEngineStorageArray(Long2ObjectOpenHashMap long2objectopenhashmap) { - this.a = long2objectopenhashmap; + // Tuinity start - avoid copying light data + protected LightEngineStorageArray(com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object data, boolean isVisible) { + if (isVisible) { + data.performUpdatesLockMap(); + } + this.data = data; + this.isVisible = isVisible; + // Tuinity end - avoid copying light data this.c(); this.d = true; } @@ -19,12 +26,13 @@ public abstract class LightEngineStorageArray { protected LightEngineStorageBlock(ILightAccess ilightaccess) { - super(EnumSkyBlock.BLOCK, ilightaccess, new LightEngineStorageBlock.a(new Long2ObjectOpenHashMap())); + super(EnumSkyBlock.BLOCK, ilightaccess, new LightEngineStorageBlock.a(new com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<>(), false)); // Tuinity - avoid copying light data } @Override @@ -18,13 +18,13 @@ public class LightEngineStorageBlock extends LightEngineStorage { - public a(Long2ObjectOpenHashMap long2objectopenhashmap) { - super(long2objectopenhashmap); + public a(com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object long2objectopenhashmap, boolean isVisible) { // Tuinity - avoid copying light data + super(long2objectopenhashmap, isVisible); // Tuinity - avoid copying light data } @Override public LightEngineStorageBlock.a b() { - return new LightEngineStorageBlock.a(this.a.clone()); + return new a(this.data, true); // Tuinity - avoid copying light data } } } diff --git a/src/main/java/net/minecraft/server/LightEngineStorageSky.java b/src/main/java/net/minecraft/server/LightEngineStorageSky.java index 75d9065b3..4669f31b0 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorageSky.java +++ b/src/main/java/net/minecraft/server/LightEngineStorageSky.java @@ -17,7 +17,7 @@ public class LightEngineStorageSky extends LightEngineStorage(), new com.tuinity.tuinity.chunk.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Tuinity - avoid copying light data } @Override @@ -25,7 +25,7 @@ public class LightEngineStorageSky extends LightEngineStorage j) { ((LightEngineStorageSky.a) this.f).b = j; - ((LightEngineStorageSky.a) this.f).c.defaultReturnValue(((LightEngineStorageSky.a) this.f).b); + ((LightEngineStorageSky.a) this.f).otherData.queueDefaultReturnValue(((LightEngineStorageSky.a) this.f).b); // Tuinity - avoid copying light data } long k = SectionPosition.f(i); - int l = ((LightEngineStorageSky.a) this.f).c.get(k); + int l = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(k); // Tuinity - avoid copying light data if (l < j + 1) { - ((LightEngineStorageSky.a) this.f).c.put(k, j + 1); + ((LightEngineStorageSky.a) this.f).otherData.queueUpdate(k, j + 1); // Tuinity - avoid copying light data if (this.o.contains(k)) { this.q(i); if (l > ((LightEngineStorageSky.a) this.f).b) { @@ -101,7 +101,7 @@ public class LightEngineStorageSky extends LightEngineStorage= k; } @@ -321,18 +321,20 @@ public class LightEngineStorageSky extends LightEngineStorage { private int b; - private final Long2IntOpenHashMap c; + private final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Int otherData; // Tuinity - avoid copying light data - public a(Long2ObjectOpenHashMap long2objectopenhashmap, Long2IntOpenHashMap long2intopenhashmap, int i) { - super(long2objectopenhashmap); - this.c = long2intopenhashmap; - long2intopenhashmap.defaultReturnValue(i); + // Tuinity start - avoid copying light data + public a(com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object data, com.tuinity.tuinity.chunk.QueuedChangesMapLong2Int otherData, int i, boolean isVisible) { + super(data, isVisible); + this.otherData = otherData; + // Tuinity end - avoid copying light data this.b = i; } @Override public LightEngineStorageSky.a b() { - return new LightEngineStorageSky.a(this.a.clone(), this.c.clone(), this.b); + this.otherData.performUpdatesLockMap(); // Tuinity - avoid copying light data + return new LightEngineStorageSky.a(this.data, this.otherData, this.b, true); // Tuinity - avoid copying light data } } } diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index bcd5cc9f2..dd07c67bc 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -502,9 +502,9 @@ public final class MCUtil { WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; - Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunks; + // Tuinity - replace chunk map ChunkMapDistance chunkMapDistance = chunkMap.getChunkMapDistanceManager(); - List allChunks = new ArrayList<>(visibleChunks.values()); + List allChunks = chunkMap.chunkMap.getUpdatingValuesCopy(); // Tuinity - replace chunk map List players = world.players; int fullLoadedChunks = 0; @@ -527,7 +527,7 @@ public final class MCUtil { worldData.addProperty("view-distance", world.spigotConfig.viewDistance); worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); - worldData.addProperty("visible-chunk-count", visibleChunks.size()); + worldData.addProperty("visible-chunk-count", allChunks.size()); // Tuinity - replace chunk map worldData.addProperty("loaded-chunk-count", chunkMap.loadedChunks.size()); worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks); @@ -605,4 +605,28 @@ public final class MCUtil { // TODO make sure the constant `33` is correct on future updates. See getChunkAt(int, int, ChunkStatus, boolean) return 33 + ChunkStatus.getTicketLevelOffset(status); } + + public static boolean hasCommonElement(Set set0, Set set1) { + java.util.Iterator iterator; + Set target; + + // optimize by iterating over fewest entries possible + if (set0.size() <= set1.size()) { + target = set1; + iterator = set0.iterator(); + } else { + target = set0; + iterator = set1.iterator(); + } + + while (iterator.hasNext()) { + E element = iterator.next(); + if (target.contains(element)) { + return true; + } + } + + return false; + + } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index b31a9ac78..af5dd9f2d 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -166,7 +166,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public final java.util.Queue processQueue = new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>(); // Tuinity - Use CLL public int autosavePeriod; public boolean serverAutoSave = false; // Paper public File bukkitDataPackFolder; @@ -1194,9 +1194,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant)processQueue).drain(Runnable::run); // Tuinity - Use efficient drain method MinecraftTimings.processQueueTimer.stopTiming(); // Spigot MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java index 98deaba12..fcc3b7c36 100644 --- a/src/main/java/net/minecraft/server/NBTTagCompound.java +++ b/src/main/java/net/minecraft/server/NBTTagCompound.java @@ -67,7 +67,7 @@ public class NBTTagCompound implements NBTBase { } public NBTTagCompound() { - this(Maps.newHashMap()); + this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(16, 0.8f)); // Tuinity - reduce memory footprint of NBTTagCompound } @Override diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java index f06764973..4393ea6dc 100644 --- a/src/main/java/net/minecraft/server/NavigationAbstract.java +++ b/src/main/java/net/minecraft/server/NavigationAbstract.java @@ -11,7 +11,7 @@ public abstract class NavigationAbstract { protected final EntityInsentient a; public Entity getEntity() { return a; } // Paper - OBFHELPER protected final World b; @Nullable - protected PathEntity c; + protected PathEntity c; protected final PathEntity getCurrentPath() { return this.c; } // Tuinity - OBFHELPER protected double d; private final AttributeInstance p; protected int e; @@ -158,10 +158,30 @@ public abstract class NavigationAbstract { return this.a(this.a(d0, d1, d2, 1), d3); } + // Tuinity start - optimise pathfinding + private int lastFailure = 0; + private int pathfindFailures = 0; + // Tuinity end + public boolean a(Entity entity, double d0) { + // Tuinity start - Pathfinding optimizations + if (this.pathfindFailures > 10 && this.getCurrentPath() == null && MinecraftServer.currentTick < this.lastFailure + 40) { + return false; + } + // Tuinity end PathEntity pathentity = this.a(entity, 1); - return pathentity != null && this.a(pathentity, d0); + // Tuinity start - Pathfinding optimizations + if (pathentity != null && this.a(pathentity, d0)) { + this.lastFailure = 0; + this.pathfindFailures = 0; + return true; + } else { + this.pathfindFailures++; + this.lastFailure = MinecraftServer.currentTick; + return false; + } + // Tuinity end } public boolean setDestination(@Nullable PathEntity pathentity, double speed) { return a(pathentity, speed); } // Paper - OBFHELPER diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java index 96a785af2..211a6d720 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -42,7 +42,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); }); private final EnumProtocolDirection h; - private final Queue packetQueue = Queues.newConcurrentLinkedQueue(); private final Queue getPacketQueue() { return this.packetQueue; } // Paper - OBFHELPER + private final Queue packetQueue = new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>(); private final Queue getPacketQueue() { return this.packetQueue; } // Paper - OBFHELPER // Tuinity - Use CLL public Channel channel; public SocketAddress socketAddress; public void setSpoofedRemoteAddress(SocketAddress address) { this.socketAddress = address; } // Paper - OBFHELPER // Spigot Start diff --git a/src/main/java/net/minecraft/server/NextTickListEntry.java b/src/main/java/net/minecraft/server/NextTickListEntry.java index 33cfeabde..11a4bb882 100644 --- a/src/main/java/net/minecraft/server/NextTickListEntry.java +++ b/src/main/java/net/minecraft/server/NextTickListEntry.java @@ -5,11 +5,13 @@ import java.util.Comparator; public class NextTickListEntry { private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(); // Paper - async chunk loading - private final T e; - public final BlockPosition a; - public final long b; - public final TickListPriority c; - private final long f; + private final T e; public final T getData() { return this.e; } // Tuinity - OBFHELPER + public final BlockPosition a; public final BlockPosition getPosition() { return this.a; } // Tuinity - OBFHELPER + public final long b; public final long getTargetTick() { return this.b; } // Tuinity - OBFHELPER + public final TickListPriority c; public final TickListPriority getPriority() { return this.c; } // Tuinity - OBFHELPER + private final long f; public final long getId() { return this.f; } // Tuinity - OBFHELPER + private final int hash; // Tuinity + public int tickState; // Tuinity public NextTickListEntry(BlockPosition blockposition, T t0) { this(blockposition, t0, 0L, TickListPriority.NORMAL); @@ -21,6 +23,7 @@ public class NextTickListEntry { this.e = t0; this.b = i; this.c = ticklistpriority; + this.hash = this.computeHash(); // Tuinity } public boolean equals(Object object) { @@ -33,19 +36,31 @@ public class NextTickListEntry { } } + // Tuinity start - optimize hashcode + @Override public int hashCode() { + return this.hash; + } + public final int computeHash() { + // Tuinity end - optimize hashcode return this.a.hashCode(); } - public static Comparator a() { // Paper - decompile fix - return Comparator.comparingLong((nextticklistentry) -> { - return ((NextTickListEntry) nextticklistentry).b; // Paper - decompile fix - }).thenComparing((nextticklistentry) -> { - return ((NextTickListEntry) nextticklistentry).c; // Paper - decompile fix - }).thenComparingLong((nextticklistentry) -> { - return ((NextTickListEntry) nextticklistentry).f; // Paper - decompile fix - }); + // Tuinity start - let's not use more functional code for no reason. + public static Comparator comparator() { return NextTickListEntry.a(); } // Tuinity - OBFHELPER + public static Comparator a() { + return (Comparator)(Comparator)(NextTickListEntry nextticklistentry, NextTickListEntry nextticklistentry1) -> { + int i = Long.compare(nextticklistentry.getTargetTick(), nextticklistentry1.getTargetTick()); + + if (i != 0) { + return i; + } else { + i = nextticklistentry.getPriority().compareTo(nextticklistentry1.getPriority()); + return i != 0 ? i : Long.compare(nextticklistentry.getId(), nextticklistentry1.getId()); + } + }; } + // Tuinity end - let's not use more functional code for no reason. public String toString() { return this.e + ": " + this.a + ", " + this.b + ", " + this.c + ", " + this.f; diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java index ef7ade797..185ed1db3 100644 --- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java +++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java @@ -29,7 +29,7 @@ public class PacketPlayOutMapChunk implements Packet { // Paper start private final java.util.List extraPackets = new java.util.ArrayList<>(); - private static final int SKIP_EXCESSIVE_SIGNS_LIMIT = Integer.getInteger("Paper.excessiveSignsLimit", 500); + private static final int TE_LIMIT = Integer.getInteger("tuinity.excessive-te-limit", 250); // Tuinity - handle oversized chunk data packets more robustly @Override public java.util.List getExtraPackets() { @@ -73,7 +73,7 @@ public class PacketPlayOutMapChunk implements Packet { this.c = this.writeChunk(new PacketDataSerializer(this.j()), chunk, i, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info this.g = Lists.newArrayList(); iterator = chunk.getTileEntities().entrySet().iterator(); - int totalSigns = 0; // Paper + int totalTileEntities = 0; // Paper // Tuinity while (iterator.hasNext()) { entry = (Entry) iterator.next(); @@ -83,12 +83,15 @@ public class PacketPlayOutMapChunk implements Packet { if (this.f() || (i & 1 << j) != 0) { // Paper start - send signs separately - if (tileentity instanceof TileEntitySign) { - if (SKIP_EXCESSIVE_SIGNS_LIMIT < 0 || ++totalSigns < SKIP_EXCESSIVE_SIGNS_LIMIT) { - this.extraPackets.add(tileentity.getUpdatePacket()); + // Tuinity start - improve oversized chunk data packet handling + if (++totalTileEntities > TE_LIMIT) { + PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); + if (updatePacket != null) { + this.extraPackets.add(updatePacket); + continue; } - continue; } + // Tuinity end // Paper end NBTTagCompound nbttagcompound = tileentity.b(); if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper diff --git a/src/main/java/net/minecraft/server/PairedQueue.java b/src/main/java/net/minecraft/server/PairedQueue.java index 85bb22e4b..1e618446a 100644 --- a/src/main/java/net/minecraft/server/PairedQueue.java +++ b/src/main/java/net/minecraft/server/PairedQueue.java @@ -20,32 +20,30 @@ public interface PairedQueue { public static final class a implements PairedQueue { - private final List> a; + private final List> a; private final List> getQueues() { return this.a; } // Tuinity - OBFHELPER public a(int i) { - this.a = (List) IntStream.range(0, i).mapToObj((j) -> { - return Queues.newConcurrentLinkedQueue(); - }).collect(Collectors.toList()); + // Tuinity start - reduce streams + this.a = new java.util.ArrayList<>(i); // queues + for (int j = 0; j < i; ++j) { + this.getQueues().add(new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>()); // use MT queue + } + // Tuinity end - reduce streams } @Nullable @Override public Runnable a() { - Iterator iterator = this.a.iterator(); - - Runnable runnable; - - do { - if (!iterator.hasNext()) { - return null; + // Tuinity start - reduce iterator creation + for (int i = 0, len = this.getQueues().size(); i < len; ++i) { + Queue queue = this.getQueues().get(i); + Runnable ret = queue.poll(); + if (ret != null) { + return ret; } - - Queue queue = (Queue) iterator.next(); - - runnable = (Runnable) queue.poll(); - } while (runnable == null); - - return runnable; + } + return null; + // Tuinity end - reduce iterator creation } public boolean a(PairedQueue.b pairedqueue_b) { @@ -57,7 +55,15 @@ public interface PairedQueue { @Override public boolean b() { - return this.a.stream().allMatch(Collection::isEmpty); + // Tuinity start - reduce streams + for (int i = 0, len = this.getQueues().size(); i < len; ++i) { + Queue queue = this.getQueues().get(i); + if (!queue.isEmpty()) { + return false; + } + } + return true; + // Tuinity end - reduce streams } } diff --git a/src/main/java/net/minecraft/server/PathfinderGoal.java b/src/main/java/net/minecraft/server/PathfinderGoal.java index bdb90a346..738c51070 100644 --- a/src/main/java/net/minecraft/server/PathfinderGoal.java +++ b/src/main/java/net/minecraft/server/PathfinderGoal.java @@ -1,10 +1,11 @@ package net.minecraft.server; +import com.tuinity.tuinity.util.OptimizedSmallEnumSet; // Tuinity import java.util.EnumSet; public abstract class PathfinderGoal { - private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Tuinity - reduce garbage on heap public PathfinderGoal() {} @@ -28,16 +29,20 @@ public abstract class PathfinderGoal { public void e() {} public void a(EnumSet enumset) { - this.a.clear(); - this.a.addAll(enumset); + // Tuinity start - reduce garbage on heap + this.goalTypes.clear(); + this.goalTypes.addAllUnchecked(enumset); + // Tuinity end - reduce garbage on heap } public String toString() { return this.getClass().getSimpleName(); } - public EnumSet i() { - return this.a; + // Tuinity start - reduce garbage on heap + public com.tuinity.tuinity.util.OptimizedSmallEnumSet getGoalTypes() { + return this.goalTypes; + // Tuinity end - reduce garbage on heap } public static enum Type { diff --git a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java index 935136771..782edcb63 100644 --- a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +++ b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java @@ -1,8 +1,10 @@ package net.minecraft.server; +import com.tuinity.tuinity.util.OptimizedSmallEnumSet; import com.google.common.collect.Sets; import java.util.EnumMap; import java.util.EnumSet; +import java.util.Iterator; // Tuinity import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -26,7 +28,7 @@ public class PathfinderGoalSelector { private final Map c = new EnumMap(PathfinderGoal.Type.class); private final Set d = Sets.newLinkedHashSet();private Set getTasks() { return d; }// Paper - OBFHELPER private final GameProfilerFiller e; - private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Tuinity - reduce garbage on heap private int g = 3;private int getTickRate() { return g; } // Paper - OBFHELPER private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO @@ -58,33 +60,38 @@ public class PathfinderGoalSelector { // Paper end public void a(PathfinderGoal pathfindergoal) { - this.d.stream().filter((pathfindergoalwrapped) -> { - return pathfindergoalwrapped.j() == pathfindergoal; - }).filter(PathfinderGoalWrapped::g).forEach(PathfinderGoalWrapped::d); - this.d.removeIf((pathfindergoalwrapped) -> { - return pathfindergoalwrapped.j() == pathfindergoal; - }); + // Tuinity start - remove streams + for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped goalWrapped = iterator.next(); + if (goalWrapped.j() != pathfindergoal) { + continue; + } + if (goalWrapped.g()) { + goalWrapped.d(); + } + iterator.remove(); + } + // Tuinity end } + private static final PathfinderGoal.Type[] PATHFINDER_GOAL_TYPES = PathfinderGoal.Type.values(); + public void doTick() { this.e.enter("goalCleanup"); - this.c().filter((pathfindergoalwrapped) -> { - boolean flag; - - if (pathfindergoalwrapped.g()) { - Stream stream = pathfindergoalwrapped.i().stream(); - EnumSet enumset = this.f; + // Tuinity start - remove streams + for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped wrappedGoal = iterator.next(); + if (!wrappedGoal.g()) { + continue; + } - this.f.getClass(); - if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.b()) { - flag = false; - return flag; - } + if (!this.goalTypes.hasCommonElements(wrappedGoal.getGoalTypes()) && wrappedGoal.b()) { + continue; } - flag = true; - return flag; - }).forEach(PathfinderGoal::d); + wrappedGoal.d(); + } + // Tuinity end this.c.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { if (!pathfindergoalwrapped.g()) { this.c.remove(pathfindergoal_type); @@ -93,30 +100,58 @@ public class PathfinderGoalSelector { }); this.e.exit(); this.e.enter("goalUpdate"); - this.d.stream().filter((pathfindergoalwrapped) -> { - return !pathfindergoalwrapped.g(); - }).filter((pathfindergoalwrapped) -> { - Stream stream = pathfindergoalwrapped.i().stream(); - EnumSet enumset = this.f; - - this.f.getClass(); - return stream.noneMatch(enumset::contains); - }).filter((pathfindergoalwrapped) -> { - return pathfindergoalwrapped.i().stream().allMatch((pathfindergoal_type) -> { - return ((PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b)).a(pathfindergoalwrapped); - }); - }).filter(PathfinderGoalWrapped::a).forEach((pathfindergoalwrapped) -> { - pathfindergoalwrapped.i().forEach((pathfindergoal_type) -> { - PathfinderGoalWrapped pathfindergoalwrapped1 = (PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b); - - pathfindergoalwrapped1.d(); - this.c.put(pathfindergoal_type, pathfindergoalwrapped); - }); - pathfindergoalwrapped.c(); - }); + // Tuinity start - remove streams + goal_update_loop: for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped wrappedGoal = iterator.next(); + if (wrappedGoal.g()) { + continue; + } + + OptimizedSmallEnumSet wrappedGoalSet = wrappedGoal.getGoalTypes(); + + if (this.goalTypes.hasCommonElements(wrappedGoalSet)) { + continue; + } + + long iterator1 = wrappedGoalSet.getBackingSet(); + int wrappedGoalSize = wrappedGoalSet.size(); + for (int i = 0; i < wrappedGoalSize; ++i) { + PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; + iterator1 ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(iterator1); + PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); + if (!wrapped.a(wrappedGoal)) { + continue goal_update_loop; + } + } + + if (!wrappedGoal.a()) { + continue; + } + + iterator1 = wrappedGoalSet.getBackingSet(); + wrappedGoalSize = wrappedGoalSet.size(); + for (int i = 0; i < wrappedGoalSize; ++i) { + PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; + iterator1 ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(iterator1); + PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); + + wrapped.d(); + this.c.put(type, wrappedGoal); + } + + wrappedGoal.c(); + } + // Tuinity end this.e.exit(); this.e.enter("goalTick"); - this.c().forEach(PathfinderGoalWrapped::e); + // Tuinity start - remove streams + for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped wrappedGoal = iterator.next(); + if (wrappedGoal.g()) { + wrappedGoal.e(); + } + } + // Tuinity end this.e.exit(); } @@ -125,11 +160,11 @@ public class PathfinderGoalSelector { } public void a(PathfinderGoal.Type pathfindergoal_type) { - this.f.add(pathfindergoal_type); + this.goalTypes.addUnchecked(pathfindergoal_type); // Tuinity - reduce streams } public void b(PathfinderGoal.Type pathfindergoal_type) { - this.f.remove(pathfindergoal_type); + this.goalTypes.removeUnchecked(pathfindergoal_type); // Tuinity - reduce streams } public void a(PathfinderGoal.Type pathfindergoal_type, boolean flag) { diff --git a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java index 29657fed7..71919adc0 100644 --- a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +++ b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java @@ -60,8 +60,10 @@ public class PathfinderGoalWrapped extends PathfinderGoal { } @Override - public EnumSet i() { - return this.a.i(); + // Tuinity start - reduce garbage on heap + public com.tuinity.tuinity.util.OptimizedSmallEnumSet getGoalTypes() { + return this.a.getGoalTypes(); + // Tuinity end - reduce garbage on heap } public boolean isRunning() { return this.g(); } // Paper - OBFHELPER diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java index 4240ca81c..69fd3cc8e 100644 --- a/src/main/java/net/minecraft/server/PathfinderNormal.java +++ b/src/main/java/net/minecraft/server/PathfinderNormal.java @@ -443,8 +443,10 @@ public class PathfinderNormal extends PathfinderAbstract { return pathtype; } + private static final BlockPosition.MutableBlockPosition PATH_TYPE_BLOCKPOSITION = new BlockPosition.MutableBlockPosition(); // Tuinity - this shows to be a high allocator + protected static PathType c(IBlockAccess iblockaccess, int i, int j, int k) { - BlockPosition blockposition = new BlockPosition(i, j, k); + BlockPosition blockposition = PATH_TYPE_BLOCKPOSITION.setValues(i, j, k); // Tuinity - this shows to be a high allocator IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper if (iblockdata == null) return PathType.BLOCKED; // Paper Block block = iblockdata.getBlock(); diff --git a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java index e35ec2db0..e7dfe22ac 100644 --- a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java +++ b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java @@ -51,6 +51,7 @@ public class PathfinderTargetCondition { return this; } + public final boolean test(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { return this.a(entityliving, entityliving1); } // Tuinity - OBFHELPER public boolean a(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { if (entityliving == entityliving1) { return false; diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java index b38bc6775..729795238 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -43,6 +43,18 @@ public class PlayerChunk { long lastAutoSaveTime; // Paper - incremental autosave long inactiveTimeStart; // Paper - incremental autosave + // Tuinity start - optimise isOutsideOfRange + // cached here to avoid a map lookup + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; + + void updateRanges() { + long key = com.tuinity.tuinity.util.Util.getCoordinateKey(this.location); + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + } + // Tuinity end + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -59,7 +71,30 @@ public class PlayerChunk { this.n = this.oldTicketLevel; this.a(i); this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper + this.updateRanges(); // Tuinity - optimise isOutsideOfRange + } + + // Tuinity start - per player view distance + void broadcastChunk() { + Chunk chunk = this.getFullReadyChunk(); + Packet[] apacket = new Packet[2]; + + // there can be potential desync with player's last mapped section and the view distance map, so use the + // view distance map here. + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(chunk.getPos()); + if (players != null) { + Object[] backingSet = players.getBackingSet(); + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)temp; + this.chunkMap.sendChunk(player, apacket, chunk); + } + } } + // Tuinity end // Paper start @Nullable @@ -194,7 +229,7 @@ public class PlayerChunk { } public void a(int i, int j, int k) { - Chunk chunk = this.getChunk(); + Chunk chunk = this.getFullReadyChunk(); // Tuinity - per player view distance - allow block updates in non-ticking chunks if (chunk != null) { this.r |= 1 << (j >> 4); @@ -214,7 +249,7 @@ public class PlayerChunk { } public void a(EnumSkyBlock enumskyblock, int i) { - Chunk chunk = this.getChunk(); + Chunk chunk = this.getFullReadyChunk(); // Tuinity - per player view distance - allow block updates in non-ticking chunks if (chunk != null) { chunk.setNeedsSaving(true); @@ -304,6 +339,50 @@ public class PlayerChunk { } private void a(Packet packet, boolean flag) { + // Tuinity start - per player view distance + // there can be potential desync with player's last mapped section and the view distance map, so use the + // view distance map here. + if (this.players instanceof PlayerChunkMap) { + PlayerChunkMap chunkMap = ((PlayerChunkMap)this.players); + com.tuinity.tuinity.util.map.PlayerAreaMap viewDistanceMap = ((PlayerChunkMap)this.players).playerViewDistanceBroadcastMap; + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.location); + if (players == null) { + return; + } + + if (flag) { // flag -> border only + Object[] backingSet = players.getBackingSet(); + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)temp; + int viewDistance = viewDistanceMap.getLastViewDistance(player); + long lastPosition = viewDistanceMap.getLastCoordinate(player); + + int distX = Math.abs(com.tuinity.tuinity.util.Util.getCoordinateX(lastPosition) - this.location.x); + int distZ = Math.abs(com.tuinity.tuinity.util.Util.getCoordinateZ(lastPosition) - this.location.z); + + if (Math.max(distX, distZ) == viewDistance) { + player.playerConnection.sendPacket(packet); + } + } + } else { + Object[] backingSet = players.getBackingSet(); + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)temp; + player.playerConnection.sendPacket(packet); + } + } + + return; + } + // Tuinity end - per player view distance this.players.a(this.location, flag).forEach((entityplayer) -> { entityplayer.playerConnection.sendPacket(packet); }); @@ -445,6 +524,10 @@ public class PlayerChunk { fullChunk.playerChunk = PlayerChunk.this; + // Tuinity start - per player view distance implementation + PlayerChunk.this.broadcastChunk(); + PlayerChunk.this.chunkMap.getChunkMapDistanceManager().playerNoTickViewDistanceHandler.onChunkLoad(this.location.x, this.location.z); + // Tuinity end - per player view distance implementation } }); // Paper end @@ -473,7 +556,7 @@ public class PlayerChunk { Chunk tickingChunk = either.left().get(); PlayerChunk.this.isTickingReady = true; - + PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z); // Tuinity - rewrite ticklistserver } @@ -503,8 +586,19 @@ public class PlayerChunk { PlayerChunk.this.isEntityTickingReady = true; + // Tuinity start - stop throwing garbage on the heap + ChunkProviderServer chunkProvider = PlayerChunk.this.chunkMap.world.getChunkProvider(); + if (chunkProvider.isTickingChunks) { + chunkProvider.pendingEntityTickingChunkChanges.put(entityTickingChunk, true); + } else { + chunkProvider.entityTickingChunks.add(entityTickingChunk); + } + // Tuinity end - stop throwing garbage on the heap + // Tuinity start - per player view distance implementation + PlayerChunk.this.chunkMap.getChunkMapDistanceManager().playerTickViewDistanceHandler.onChunkLoad(this.location.x, this.location.z); + // Tuinity end - per player view distance implementation } }); // Paper end @@ -513,6 +607,18 @@ public class PlayerChunk { if (flag6 && !flag7) { this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + + // Tuinity start - stop throwing garbage on the heap + ChunkProviderServer chunkProvider = PlayerChunk.this.chunkMap.world.getChunkProvider(); + Chunk chunk = this.getFullChunkIfCached(); + if (chunk != null) { + if (chunkProvider.isTickingChunks) { + chunkProvider.pendingEntityTickingChunkChanges.put(chunk, false); + } else { + chunkProvider.entityTickingChunks.remove(chunk); + } + } + // Tuinity end - stop throwing garbage on the heap this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index 57bea926a..aa625ce2a 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -55,8 +55,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private static final Logger LOGGER = LogManager.getLogger(); public static final int GOLDEN_TICKET = 33 + ChunkStatus.b(); - public final Long2ObjectLinkedOpenHashMap updatingChunks = new Long2ObjectLinkedOpenHashMap(); - public volatile Long2ObjectLinkedOpenHashMap visibleChunks; + //public final Long2ObjectLinkedOpenHashMap updatingChunks = new Long2ObjectLinkedOpenHashMap(); // Tuinity - replace chunk map + //public volatile Long2ObjectLinkedOpenHashMap visibleChunks; // Tuinity - replace chunk map + public final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object chunkMap = new com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<>(8192, 0.7f); // Tuinity - replace chunk map private final Long2ObjectLinkedOpenHashMap pendingUnload; final LongSet loadedChunks; // Paper - private -> package public final WorldServer world; @@ -78,8 +79,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private final PlayerMap playerMap; public final Int2ObjectMap trackedEntities; private final Queue z; - int viewDistance; // Paper - private -> package private - public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper + int viewDistance; public final int getViewDistance() { return this.viewDistance; } // Tuinity - OBFHELPER // Paper - private -> package private + //public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper // Tuinity - replaced by view distance map // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() public final CallbackExecutor callbackExecutor = new CallbackExecutor(); @@ -109,6 +110,93 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // Paper start - distance maps private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + // Tuinity start - per player view distance + int noTickViewDistance; + public final int getNoTickViewDistance() { + return this.noTickViewDistance; + } + // we use this map to broadcast chunks to clients + // they do not render chunks without having at least neighbours in a 1 chunk radius loaded + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerViewDistanceBroadcastMap; + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerViewDistanceTickMap; + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerViewDistanceNoTickMap; + + public void updateViewDistance(EntityPlayer player, int viewDistance, int noTickViewDistance) { + player.viewDistance = viewDistance; + player.noTickViewDistance = noTickViewDistance; + + int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locX()); + int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locZ()); + + int effectiveViewDistance = viewDistance == -1 ? this.viewDistance : viewDistance; + int effectiveNoTickViewDistance = Math.max(effectiveViewDistance, noTickViewDistance == -1 ? this.noTickViewDistance : noTickViewDistance); + + player.playerConnection.sendPacket(new PacketPlayOutViewDistance(effectiveNoTickViewDistance)); + + if (!this.cannotLoadChunks(player)) { + this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveViewDistance); + this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours + } + this.playerViewDistanceMap.update(player, chunkX, chunkZ, effectiveViewDistance); + player.needsChunkCenterUpdate = true; + this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours + player.needsChunkCenterUpdate = false; + // Tuinity start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); + // Tuinity end - optimise PlayerChunkMap#isOutsideRange + + // Tuinity start - use distance map to optimise entity tracker + // force propagate tracker changes + if (this.playerEntityTrackerTrackMap != null) { + this.playerEntityTrackerTrackMap.update(player, chunkX, chunkZ, Math.min(this.entityTrackerTrackRange, effectiveViewDistance)); + this.playerEntityTrackerUntrackMap.update(player, chunkX, chunkZ, Math.min(this.entityTrackerUntrackRange, effectiveViewDistance)); + this.playerEntityTrackerLegacyMap.update(player, chunkX, chunkZ, this.entityTrackerLegacyMapRange); + } + // Tuinity end - use distance map to optimise entity tracker + } + // Tuinity end - per player view distance + + // Tuinity start - optimise PlayerChunkMap#isOutsideRange + // A note about the naming used here: + // Previously, mojang used a "spawn range" of 8 for controlling both ticking and + // mob spawn range. However, spigot makes the spawn range configurable by + // checking if the chunk is in the tick range (8) and the spawn range + // obviously this means a spawn range > 8 cannot be implemented + + // these maps are named after spigot's uses + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of a tick + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerChunkTickRangeMap; + + // Tuinity end - optimise PlayerChunkMap#isOutsideRange + + // Tuinity start - use distance map to optimise entity tracker + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerEntityTrackerTrackMap; + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerEntityTrackerUntrackMap; + public final int entityTrackerTrackRange; + public final int entityTrackerUntrackRange; + + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerEntityTrackerLegacyMap; + public final int entityTrackerLegacyMapRange; + + //public final com.tuinity.tuinity.util.EntityList activelyTrackedEntities; // TODO not yet + public final com.tuinity.tuinity.util.EntityList activelyTrackedEntitiesLegacy; + + public static boolean isLegacyTrackingEntity(Entity entity) { + return entity.isLegacyTrackingEntity; + } + + private static int getEntityTrackingChunkRange(int blockRange) { + int centerChunkRange = (blockRange - 8); // on average, players are in the middle of a chunk, so subtract 8 + return centerChunkRange >>> 4 + ((centerChunkRange & 15) != 0 ? 1 : 0); + } + // Tuinity end - use distance map to optimise entity tracker + + // Tuinity start - optimise getPlayersInRange type functions + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerGeneralAreaMap; + public static final int PLAYER_GENERAL_AREA_MAP_DISTANCE = (32 + 3) + 1; + public static final int PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS = (16 * PLAYER_GENERAL_AREA_MAP_DISTANCE) * (16 * PLAYER_GENERAL_AREA_MAP_DISTANCE); + // Tuinity end - optimise getPlayersInRange type functions + void addPlayerToDistanceMaps(EntityPlayer player) { this.updateMaps(player); @@ -134,10 +222,91 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // Paper end + // Tuinity start - distance maps + final com.tuinity.tuinity.util.map.PooledLinkedHashSets pooledEntityPlayerSets = new com.tuinity.tuinity.util.map.PooledLinkedHashSets<>(); + public final com.tuinity.tuinity.util.map.PlayerAreaMap playerViewDistanceMap; + + void addPlayerToDistanceMapsTuinity(EntityPlayer player) { + this.updateMapsTuinity(player); + + // Tuinity start - per player view distance + this.getChunkMapDistanceManager().playerTickViewDistanceHandler.addPlayer(player); + this.getChunkMapDistanceManager().playerNoTickViewDistanceHandler.addPlayer(player); + // Tuinity end - per player view distance + } + + void removePlayerFromDistanceMapsTuinity(EntityPlayer player) { + this.playerViewDistanceMap.remove(player); + // Tuinity start - per player view distance + this.playerViewDistanceBroadcastMap.remove(player); + this.playerViewDistanceTickMap.remove(player); + this.playerViewDistanceNoTickMap.remove(player); + this.getChunkMapDistanceManager().playerTickViewDistanceHandler.removePlayer(player); + this.getChunkMapDistanceManager().playerNoTickViewDistanceHandler.removePlayer(player); + // Tuinity end - per player view distance + + // Tuinity start - optimise PlayerChunkMap#isOutsideRange + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Tuinity end - optimise PlayerChunkMap#isOutsideRange + + // Tuinity start - use distance map to optimise entity tracker + if (this.playerEntityTrackerTrackMap != null) { + this.playerEntityTrackerTrackMap.remove(player); + this.playerEntityTrackerUntrackMap.remove(player); + this.playerEntityTrackerLegacyMap.remove(player); + } + // Tuinity end - use distance map to optimise entity tracker + + // Tuinity start - optimise getPlayersInRange type functions + this.playerGeneralAreaMap.remove(player); + // Tuinity end - optimise getPlayersInRange type functions + } + + void updateDistanceMapsTuinity(EntityPlayer player) { + this.updateMapsTuinity(player); + } + + private void updateMapsTuinity(EntityPlayer player) { + int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locX()); + int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locZ()); + + this.playerViewDistanceMap.update(player, chunkX, chunkZ, player.getEffectiveViewDistance(this)); // Tuinity - per player view distance + + // Tuinity start - per player view distance + int effectiveViewDistance = player.getEffectiveViewDistance(this); + int effectiveNoTickViewDistance = Math.max(effectiveViewDistance, player.getEffectiveNoTickViewDistance(this)); + + if (!this.cannotLoadChunks(player)) { + this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveViewDistance); + this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours + } + player.needsChunkCenterUpdate = true; + this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours + player.needsChunkCenterUpdate = false; + // Tuinity end - per player view distance + + // Tuinity start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); + // Tuinity end - optimise PlayerChunkMap#isOutsideRange + + // Tuinity start - use distance map to optimise entity tracker + if (this.playerEntityTrackerTrackMap != null) { + this.playerEntityTrackerTrackMap.update(player, chunkX, chunkZ, Math.min(this.entityTrackerTrackRange, effectiveViewDistance)); + this.playerEntityTrackerUntrackMap.update(player, chunkX, chunkZ, Math.min(this.entityTrackerUntrackRange, effectiveViewDistance)); + this.playerEntityTrackerLegacyMap.update(player, chunkX, chunkZ, this.entityTrackerLegacyMapRange); + } + // Tuinity end - use distance map to optimise entity tracker + + // Tuinity start - optimise getPlayersInRange type functions + this.playerGeneralAreaMap.update(player, chunkX, chunkZ, PLAYER_GENERAL_AREA_MAP_DISTANCE); + // Tuinity end - optimise getPlayersInRange type functions + } + // Tuinity end public PlayerChunkMap(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i) { super(new File(worldserver.getWorldProvider().getDimensionManager().a(file), "region"), datafixer); - this.visibleChunks = this.updatingChunks.clone(); + //this.visibleChunks = this.updatingChunks.clone(); // Tuinity - replace chunk map this.pendingUnload = new Long2ObjectLinkedOpenHashMap(); this.loadedChunks = new LongOpenHashSet(); this.unloadQueue = new LongOpenHashSet(); @@ -166,7 +335,188 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.l = supplier; this.m = new VillagePlace(new File(this.w, "poi"), datafixer, this.world); // Paper this.setViewDistance(i); - this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + // Tuinity start - distance maps + //this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + com.tuinity.tuinity.util.map.PooledLinkedHashSets sets = this.pooledEntityPlayerSets; + this.playerViewDistanceMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets); + // Tuinity end - distance maps + // Tuinity start - per player view distance + this.setNoTickViewDistance(this.world.tuinityConfig.noTickViewDistance < 0 ? this.viewDistance : this.world.tuinityConfig.noTickViewDistance); + this.playerViewDistanceTickMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + null, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + if (newState != null) { + return; + } + PlayerChunkMap.this.chunkDistanceManager.playerTickViewDistanceHandler.playerMoveOutOfRange(rangeX, rangeZ); + }); + this.chunkDistanceManager.playerTickViewDistanceHandler.areaMap = this.playerViewDistanceTickMap; + this.playerViewDistanceNoTickMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + null, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + if (newState != null) { + return; + } + PlayerChunkMap.this.chunkDistanceManager.playerNoTickViewDistanceHandler.playerMoveOutOfRange(rangeX, rangeZ); + }); + this.chunkDistanceManager.playerNoTickViewDistanceHandler.areaMap = this.playerViewDistanceNoTickMap; + final Packet[] tempPacket = new Packet[2]; + this.playerViewDistanceBroadcastMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + if (player.needsChunkCenterUpdate) { + player.needsChunkCenterUpdate = false; + player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ)); + } + PlayerChunkMap.this.sendChunk(player, rangeX, rangeZ, tempPacket, false, true); // unloaded, loaded + java.util.Arrays.fill(tempPacket, null); + }, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + PlayerChunkMap.this.sendChunk(player, rangeX, rangeZ, tempPacket, true, false); // unloaded, loaded + java.util.Arrays.fill(tempPacket, null); + }); + // Tuinity end - per player view distance + + // Tuinity start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ)); + if (playerChunk != null) { + playerChunk.playersInChunkTickRange = newState; + } + }, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ)); + if (playerChunk != null) { + playerChunk.playersInChunkTickRange = newState; + } + }); + this.playerMobSpawnMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ)); + if (playerChunk != null) { + playerChunk.playersInMobSpawnRange = newState; + } + }, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ)); + if (playerChunk != null) { + playerChunk.playersInMobSpawnRange = newState; + } + }); + // Tuinity end + + // Tuinity start - use distance map to optimise entity tracker + if (!this.world.tuinityConfig.useOptimizedTracker) { + this.playerEntityTrackerTrackMap = null; + this.playerEntityTrackerUntrackMap = null; + this.playerEntityTrackerLegacyMap = null; + this.entityTrackerTrackRange = -1; + this.entityTrackerUntrackRange = -1; + this.entityTrackerLegacyMapRange = -1; + + this.activelyTrackedEntitiesLegacy = null; + } else { + this.activelyTrackedEntitiesLegacy = new com.tuinity.tuinity.util.EntityList(); + + // avoid player range, that's special-cased + int maxEntityTrackRange = this.world.spigotConfig.animalTrackingRange; + if (this.world.spigotConfig.monsterTrackingRange > maxEntityTrackRange) { + maxEntityTrackRange = this.world.spigotConfig.monsterTrackingRange; + } + if (this.world.spigotConfig.miscTrackingRange > maxEntityTrackRange) { + maxEntityTrackRange = this.world.spigotConfig.miscTrackingRange; + } + if (this.world.spigotConfig.otherTrackingRange > maxEntityTrackRange) { + maxEntityTrackRange = this.world.spigotConfig.otherTrackingRange; + } + maxEntityTrackRange = (maxEntityTrackRange >> 4) + ((maxEntityTrackRange & 15) != 0 ? 2 : 1); + + if (this.world.tuinityConfig.optimizedTrackerTrackRange == -1) { + this.entityTrackerTrackRange = Math.max(1, maxEntityTrackRange - 2); + this.entityTrackerUntrackRange = Math.max(2, maxEntityTrackRange - 1); + } else { + this.entityTrackerTrackRange = this.world.tuinityConfig.optimizedTrackerTrackRange; + this.entityTrackerUntrackRange = this.world.tuinityConfig.optimizedTrackerUntrackRange; + } + + this.playerEntityTrackerTrackMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ); + if (chunk == null) { + return; + } + Entity[] entities = chunk.entities.getRawData(); + for (int index = 0, len = chunk.entities.size(); index < len; ++index) { + Entity entity = entities[index]; + if (entity.tracker == null) { + entity.addToTrackQueue(player); + } else { + entity.tracker.updateTrackingPlayer(player); + entity.clearTrackingQueues(player); + } + } + }, + null); + this.playerEntityTrackerUntrackMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + null, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ); + if (chunk == null) { + return; + } + Entity[] entities = chunk.entities.getRawData(); + for (int index = 0, len = chunk.entities.size(); index < len; ++index) { + Entity entity = entities[index]; + if (entity.tracker == null) { + return; // not tracked by player for sure + } + entity.tracker.removeTrackingPlayer(player); + entity.clearTrackingQueues(player); + } + }); + + // legacy tracker + + // TODO check on update + int legacyTrackerRange = 10 * 16; // Ender dragon range + if (legacyTrackerRange < this.world.spigotConfig.playerTrackingRange) { + legacyTrackerRange = this.world.spigotConfig.playerTrackingRange; + } + + int legacyTrackerChunkRange = legacyTrackerRange >>> 4 + ((legacyTrackerRange & 15) != 0 ? 1 : 0); + this.entityTrackerLegacyMapRange = legacyTrackerChunkRange; + this.playerEntityTrackerLegacyMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); + if (chunk == null || newState.size() != 1) { + return; + } + Entity[] entities = chunk.entities.getRawData(); + for (int index = 0, len = chunk.entities.size(); index < len; ++index) { + Entity entity = entities[index]; + if (!PlayerChunkMap.isLegacyTrackingEntity(entity)) { + continue; + } + PlayerChunkMap.this.activelyTrackedEntitiesLegacy.add(entity); // tracker tick propagates entity + } + }, + null); // tracker tick handles entities moving out of range + } + // Tuinity end - use distance map to optimise entity tracker + // Tuinity start - optimise getPlayersInRange type functions + this.playerGeneralAreaMap = new com.tuinity.tuinity.util.map.PlayerAreaMap(sets); + // Tuinity end - optimise getPlayersInRange type functions } public void updatePlayerMobTypeMap(Entity entity) { @@ -177,15 +527,30 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { int chunkZ = (int)Math.floor(entity.locZ()) >> 4; int index = entity.getEntityType().getEnumCreatureType().ordinal(); - for (EntityPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) { + // Tuinity start - use view distance map + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerViewDistanceMap.getObjectsInRange(chunkX, chunkZ); + if (players != null) { + Object[] backingSet = players.getBackingSet(); + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)temp; + if (player.isSpectator() || !player.affectsSpawning) { + continue; + } + // Tuinity end - use view distance map ++player.mobCounts[index]; } + } // Tuinity - use view distance map } public int getMobCountNear(EntityPlayer entityPlayer, EnumCreatureType enumCreatureType) { return entityPlayer.mobCounts[enumCreatureType.ordinal()]; } + private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Tuinity - OBFHELPER private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { double d0 = (double) (chunkcoordintpair.x * 16 + 8); double d1 = (double) (chunkcoordintpair.z * 16 + 8); @@ -213,8 +578,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } private static int a(ChunkCoordIntPair chunkcoordintpair, int i, int j) { - int k = chunkcoordintpair.x - i; - int l = chunkcoordintpair.z - j; + // Tuinity start - remove ChunkCoordIntPair allocation + return getSquareRadiusDistance(chunkcoordintpair.x, chunkcoordintpair.z, i, j); + } + private static int getSquareRadiusDistance(int chunkX0, int chunkZ0, int i, int j) { + int k = chunkX0 - i; + int l = chunkZ0 - j; + // Tuinity end return Math.max(Math.abs(k), Math.abs(l)); } @@ -225,12 +595,17 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @Nullable protected PlayerChunk getUpdatingChunk(long i) { - return (PlayerChunk) this.updatingChunks.get(i); + return (PlayerChunk) this.chunkMap.getUpdating(i); // Tuinity - replace chunk map } @Nullable public PlayerChunk getVisibleChunk(long i) { // Paper - protected -> public - return (PlayerChunk) this.visibleChunks.get(i); + // Tuinity start - replace chunk map + if (MinecraftServer.getServer().serverThread == Thread.currentThread()) { + return this.chunkMap.getVisible(i); + } + return (PlayerChunk) this.chunkMap.getVisibleAsync(i); + // Tuinity end - replace chunk map } protected IntSupplier c(long i) { @@ -308,6 +683,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @Nullable private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Chunk holder update"); // Tuinity if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) { return playerchunk; } else { @@ -327,11 +703,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunk = (PlayerChunk) this.pendingUnload.remove(i); if (playerchunk != null) { playerchunk.a(j); + playerchunk.updateRanges(); // Tuinity - optimise isOutsideOfRange } else { playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this); } - this.updatingChunks.put(i, playerchunk); + this.chunkMap.queueUpdate(i, playerchunk); // Tuinity - replace chunk map this.updatingChunksModified = true; } @@ -411,7 +788,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { protected void save(boolean flag) { if (flag) { - List list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); + List list = (List) this.chunkMap.getVisibleValues().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); // Tuinity - replace chunk map MutableBoolean mutableboolean = new MutableBoolean(); do { @@ -439,7 +816,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // this.i(); // Paper - nuke IOWorker PlayerChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.w.getName()); } else { - this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { + this.chunkMap.getVisibleValues().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { // Tuinity - replace chunk map IChunkAccess ichunkaccess = (IChunkAccess) playerchunk.getChunkSave().getNow(null); // CraftBukkit - decompile error if (ichunkaccess instanceof ProtoChunkExtension || ichunkaccess instanceof Chunk) { @@ -482,7 +859,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { while (longiterator.hasNext()) { // Spigot long j = longiterator.nextLong(); longiterator.remove(); // Spigot - PlayerChunk playerchunk = (PlayerChunk) this.updatingChunks.remove(j); + PlayerChunk playerchunk = (PlayerChunk) this.chunkMap.queueRemove(j); // Tuinity - replace chunk map if (playerchunk != null) { this.pendingUnload.put(j, playerchunk); @@ -610,7 +987,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (!this.updatingChunksModified) { return false; } else { - this.visibleChunks = this.updatingChunks.clone(); + this.chunkMap.performUpdates(); // Tuinity - replace chunk map this.updatingChunksModified = false; return true; } @@ -903,11 +1280,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { completablefuture1.thenAcceptAsync((either) -> { either.mapLeft((chunk) -> { this.u.getAndIncrement(); - Packet[] apacket = new Packet[2]; - - this.a(chunkcoordintpair, false).forEach((entityplayer) -> { - this.a(entityplayer, apacket, chunk); - }); + // Tuinity - per player view distance - moved to full chunk load, instead of ticking load return Either.left(chunk); }); }, (runnable) -> { @@ -1011,58 +1384,70 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } // Paper } - protected void setViewDistance(int i) { - int j = MathHelper.clamp(i + 1, 3, 33); + public void setViewDistance(int i) { // Tuinity - make public + int j = MathHelper.clamp(i + 1, 3, 33) - 1; // Tuinity - we correctly handle view distance, no need to add 1 if (j != this.viewDistance) { int k = this.viewDistance; this.viewDistance = j; - this.chunkDistanceManager.a(this.viewDistance); - ObjectIterator objectiterator = this.updatingChunks.values().iterator(); - - while (objectiterator.hasNext()) { - PlayerChunk playerchunk = (PlayerChunk) objectiterator.next(); - ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); - Packet[] apacket = new Packet[2]; + this.chunkDistanceManager.setGlobalViewDistance(this.viewDistance, this); // Tuinity - per player view distance + // Tuinity start - view distance map handles this + if (this.world != null && this.world.players != null) { // ... called inside constructor, where these may not be initialized + for (EntityPlayer player : this.world.players) { + this.updateViewDistance(player, player.getRawViewDistance(), player.getRawNoTickViewDistance()); + } + } + // Tuinity end - view distance map handles this + } - this.a(chunkcoordintpair, false).forEach((entityplayer) -> { - int l = b(chunkcoordintpair, entityplayer, true); - boolean flag = l <= k; - boolean flag1 = l <= this.viewDistance; + } - this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1); - }); + // Tuinity start - no ticket view distance + public void setNoTickViewDistance(int noTickViewDistance) { + // modeled after the above + noTickViewDistance = MathHelper.clamp(noTickViewDistance, 2, 32); + if (this.noTickViewDistance != noTickViewDistance) { + this.noTickViewDistance = noTickViewDistance; + if (this.world != null && this.world.players != null) { // ... called inside constructor, where these may not be initialized + for (EntityPlayer player : this.world.players) { + this.updateViewDistance(player, player.getRawViewDistance(), player.getRawNoTickViewDistance()); + } } } - } + // Tuinity end protected void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet[] apacket, boolean flag, boolean flag1) { + // Tuinity start - remove ChunkCoordIntPair allocation, use two ints instead of ChunkCoordIntPair + this.sendChunk(entityplayer, chunkcoordintpair.x, chunkcoordintpair.z, apacket, flag, flag1); + } + protected void sendChunk(EntityPlayer entityplayer, int chunkX, int chunkZ, Packet[] apacket, boolean flag, boolean flag1) { + // Tuinity end if (entityplayer.world == this.world) { if (flag1 && !flag) { - PlayerChunk playerchunk = this.getVisibleChunk(chunkcoordintpair.pair()); + PlayerChunk playerchunk = this.getVisibleChunk(ChunkCoordIntPair.pair(chunkX, chunkZ)); // Tuinity - remove ChunkCoordIntPair allocation if (playerchunk != null) { - Chunk chunk = playerchunk.getChunk(); + Chunk chunk = playerchunk.getFullReadyChunk(); // Tuinity - per player view distance if (chunk != null) { this.a(entityplayer, apacket, chunk); } - PacketDebug.a(this.world, chunkcoordintpair); + //PacketDebug.a(this.world, chunkcoordintpair); // Tuinity - remove ChunkCoordIntPair allocation (this function is a no-op) } } if (!flag1 && flag) { - entityplayer.a(chunkcoordintpair); + entityplayer.sendChunkUnload(chunkX, chunkZ); // Tuinity - remove ChunkCoordIntPair allocation } } } public int d() { - return this.visibleChunks.size(); + return this.chunkMap.getVisibleSizeAsync(); // Tuinity - replace chunk map } protected PlayerChunkMap.a e() { @@ -1070,12 +1455,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } protected Iterable f() { - return Iterables.unmodifiableIterable(this.visibleChunks.values()); + return Iterables.unmodifiableIterable(this.chunkMap.getUpdatingValuesCopy()); // Tuinity - replace chunk map } void a(Writer writer) throws IOException { CSVWriter csvwriter = CSVWriter.a().a("x").a("z").a("level").a("in_memory").a("status").a("full_status").a("accessible_ready").a("ticking_ready").a("entity_ticking_ready").a("ticket").a("spawning").a("entity_count").a("block_entity_count").a(writer); - ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunks.long2ObjectEntrySet().iterator(); + ObjectBidirectionalIterator objectbidirectionaliterator = this.chunkMap.getVisibleMap().long2ObjectEntrySet().iterator(); // Tuinity - replace chunk map while (objectbidirectionaliterator.hasNext()) { Entry entry = (Entry) objectbidirectionaliterator.next(); @@ -1265,31 +1650,53 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { return isOutsideOfRange(chunkcoordintpair, false); } - boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { - int chunkRange = world.spigotConfig.mobSpawnRange; - chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; - chunkRange = (chunkRange > 8) ? 8 : chunkRange; + // Tuinity start + final boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { + return this.isOutsideOfRange(this.getUpdatingChunk(chunkcoordintpair.pair()), chunkcoordintpair, reducedRange); + } - final int finalChunkRange = chunkRange; // Paper for lambda below - //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event - // Spigot end - long i = chunkcoordintpair.pair(); + final boolean isOutsideOfRange(PlayerChunk playerchunk, ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; - return !this.chunkDistanceManager.d(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { - // Paper start - - com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; - double blockRange = 16384.0D; - if (reducedRange) { - event = entityplayer.playerNaturallySpawnedEvent; - if (event == null || event.isCancelled()) return false; - blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); - } + if (playersInRange == null) { + return true; + } - return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot - // Paper end - }); + Object[] backingSet = playersInRange.getBackingSet(); + + if (reducedRange) { + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) raw; + // don't check spectator and whatnot, already handled by mob spawn map update + if (player.lastEntitySpawnRadiusSquared > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { + return false; // in range + } + } + } else { + final double range = (ChunkMapDistance.MOB_SPAWN_RANGE * 16) * (ChunkMapDistance.MOB_SPAWN_RANGE * 16); + // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) raw; + // don't check spectator and whatnot, already handled by mob spawn map update + if (range > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { + return false; // in range + } + } + } + // no players in range + return true; } + // Tuinity end + private boolean cannotLoadChunks(EntityPlayer entityplayer) { return this.b(entityplayer); } // Tuinity - OBFHELPER private boolean b(EntityPlayer entityplayer) { return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS); } @@ -1315,13 +1722,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } } - for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { - for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) { - ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k, l); - - this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], !flag, flag); - } + // Tuinity start - view distance map handles this + if (flag) { + this.updateMaps(entityplayer); } + // Tuinity end - view distance map handles this } @@ -1329,11 +1734,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { SectionPosition sectionposition = SectionPosition.a((Entity) entityplayer); entityplayer.a(sectionposition); - entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c())); + //entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c())); // Tuinity - distance map handles this now return sectionposition; } public void movePlayer(EntityPlayer entityplayer) { + if (this.playerEntityTrackerTrackMap == null) { // Tuinity - optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1345,6 +1751,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker.updatePlayer(entityplayer); } } + } // Tuinity - optimized tracker int i = MathHelper.floor(entityplayer.locX()) >> 4; int j = MathHelper.floor(entityplayer.locZ()) >> 4; @@ -1384,56 +1791,53 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { int k1; int l1; - if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) { - k1 = Math.min(i, i1) - this.viewDistance; - l1 = Math.min(j, j1) - this.viewDistance; - int i2 = Math.max(i, i1) + this.viewDistance; - int j2 = Math.max(j, j1) + this.viewDistance; + this.updateMaps(entityplayer); // Paper - distance maps + this.updateDistanceMapsTuinity(entityplayer); // Tuinity - distance maps + } - for (int k2 = k1; k2 <= i2; ++k2) { - for (int l2 = l1; l2 <= j2; ++l2) { - ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k2, l2); - boolean flag3 = a(chunkcoordintpair, i1, j1) <= this.viewDistance; - boolean flag4 = a(chunkcoordintpair, i, j) <= this.viewDistance; + @Override + public Stream a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { + // Tuinity start - per player view distance + // there can be potential desync with player's last mapped section and the view distance map, so use the + // view distance map here. + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkcoordintpair); + + if (inRange == null) { + return Stream.empty(); + } + // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it + List players = new ArrayList<>(); + Object[] backingSet = inRange.getBackingSet(); + + if (flag) { // flag -> border only + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)temp; + int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player); + long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player); + + int distX = Math.abs(com.tuinity.tuinity.util.Util.getCoordinateX(lastPosition) - chunkcoordintpair.x); + int distZ = Math.abs(com.tuinity.tuinity.util.Util.getCoordinateZ(lastPosition) - chunkcoordintpair.z); - this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], flag3, flag4); + if (Math.max(distX, distZ) == viewDistance) { + players.add(player); } } } else { - ChunkCoordIntPair chunkcoordintpair1; - boolean flag5; - boolean flag6; - - for (k1 = i1 - this.viewDistance; k1 <= i1 + this.viewDistance; ++k1) { - for (l1 = j1 - this.viewDistance; l1 <= j1 + this.viewDistance; ++l1) { - chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1); - flag5 = true; - flag6 = false; - this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], true, false); - } - } - - for (k1 = i - this.viewDistance; k1 <= i + this.viewDistance; ++k1) { - for (l1 = j - this.viewDistance; l1 <= j + this.viewDistance; ++l1) { - chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1); - flag5 = false; - flag6 = true; - this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], false, true); + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object temp = backingSet[i]; + if (!(temp instanceof EntityPlayer)) { + continue; } + EntityPlayer player = (EntityPlayer)temp; + players.add(player); } } - this.updateMaps(entityplayer); // Paper - distance maps - - } - - @Override - public Stream a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { - return this.playerMap.a(chunkcoordintpair.pair()).filter((entityplayer) -> { - int i = b(chunkcoordintpair, entityplayer, true); - - return i > this.viewDistance ? false : !flag || i == this.viewDistance; - }); + return players.stream(); } protected void addEntity(Entity entity) { @@ -1452,11 +1856,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker this.trackedEntities.put(entity.getId(), playerchunkmap_entitytracker); + if (this.playerEntityTrackerTrackMap == null) { // Tuinity - implement optimized tracker playerchunkmap_entitytracker.track(this.world.getPlayers()); + // Tuinity start - implement optimized tracker + } else { + if (PlayerChunkMap.isLegacyTrackingEntity(entity)) { + if (this.playerEntityTrackerLegacyMap.getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(entity)) != null) { + this.activelyTrackedEntitiesLegacy.add(entity); + // tracker tick will propagate updates + } + } else { + int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()); + int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ()); + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersTracking = this.playerEntityTrackerTrackMap.getObjectsInRange(chunkX, chunkZ); + if (playersTracking != null) { + Object[] backingSet = playersTracking.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer trackingPlayer = (EntityPlayer) temp; + playerchunkmap_entitytracker.updateTrackingPlayer(trackingPlayer); + } + } + } + } + // Tuinity end - implement optimized tracker if (entity instanceof EntityPlayer) { EntityPlayer entityplayer = (EntityPlayer) entity; this.a(entityplayer, true); + if (this.playerEntityTrackerTrackMap == null) { // Tuinity - implement optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1466,6 +1897,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker1.updatePlayer(entityplayer); } } + } // Tuinity - implement optimized tracker } } @@ -1494,9 +1926,112 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker1.a(); } entity.tracker = null; // Paper - We're no longer tracked + // Tuinity start - optimise entity tracking - we're no longer tracked + if (this.activelyTrackedEntitiesLegacy != null) { + this.activelyTrackedEntitiesLegacy.remove(entity); + } + // Tuinity end - optimise entity tracking - we're no longer tracked } + // Tuinity start - optimized tracker + private void processTrackQueue() { + // handle queued changes + + this.world.timings.tracker1.startTiming(); + for (Entity tracked : this.world.trackingUpdateQueue) { + EntityTracker tracker = tracked.tracker; + if (tracker == null) { + continue; + } + // queued tracks + for (it.unimi.dsi.fastutil.ints.IntIterator iterator = tracked.trackQueue.iterator(); iterator.hasNext();) { + int id = iterator.nextInt(); + Entity player = this.world.entitiesById.get(id); + + if (!(player instanceof EntityPlayer)) { + continue; + } + + // double-check to make sure we're in range... + int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locX()); + int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locZ()); + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = + this.playerEntityTrackerUntrackMap.getObjectsInRange(chunkX, chunkZ); + + if (inRange != null && inRange.contains(player)) { + tracker.updateTrackingPlayer((EntityPlayer)player); + } else { + tracker.removeTrackingPlayer((EntityPlayer)player); + } + } + tracked.trackQueue.clear(); + + // queued untracks + for (it.unimi.dsi.fastutil.ints.IntIterator iterator = tracked.unTrackQueue.iterator(); iterator.hasNext();) { + int id = iterator.nextInt(); + Entity player = this.world.entitiesById.get(id); + + if (!(player instanceof EntityPlayer)) { + continue; + } + + tracker.removeTrackingPlayer((EntityPlayer)player); + } + tracked.unTrackQueue.clear(); + } + this.world.trackingUpdateQueue.clear(); + this.world.timings.tracker1.stopTiming(); + + // broadcast updates + + this.world.timings.tracker2.startTiming(); + for (Entity tracked : this.world.loadedEntities) { + EntityTracker tracker = tracked.tracker; + if (tracker != null) { + tracker.trackerEntry.tick(); + } + } + this.world.timings.tracker2.stopTiming(); + + // legacy tracker + + Entity[] legacyEntities = this.activelyTrackedEntitiesLegacy.getRawData(); + for (int i = 0, size = this.activelyTrackedEntitiesLegacy.size(); i < size; ++i) { + Entity entity = legacyEntities[i]; + EntityTracker tracker = this.trackedEntities.get(entity.getId()); + if (tracker == null) { + MinecraftServer.LOGGER.error("Entity has no tracker!" + entity); + this.activelyTrackedEntitiesLegacy.remove(entity); + --i; + --size; + continue; + } + + EntityTrackerEntry entry = tracker.trackerEntry; + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerEntityTrackerLegacyMap.getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(entity)); + if (players != null) { + tracker.updateTrackingPlayers(this, players); + } else { + // nothing is tracking it now, remove it + tracker.clearAll(); + this.activelyTrackedEntitiesLegacy.remove(entity); + --i; + --size; + continue; + } + entry.tick(); + } + } + // Tuinity end - optimized tracker + protected void g() { + // Tuinity start - optimized tracker + if (this.playerEntityTrackerTrackMap != null) { + this.processTrackQueue(); + return; + } + // Tuinity end - optimized tracker List list = Lists.newArrayList(); List list1 = this.world.getPlayers(); @@ -1554,6 +2089,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } + final void sendChunk(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Tuinity - OBFHELPER private void a(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { if (apacket[0] == null) { apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, true); // Paper - Anti-Xray @@ -1564,6 +2100,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { PacketDebug.a(this.world, chunk.getPos()); List list = Lists.newArrayList(); List list1 = Lists.newArrayList(); + if (this.playerEntityTrackerTrackMap == null) { // Tuinity - implement optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1581,6 +2118,32 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } } } + // Tuinity start- implement optimized tracker + } else { + // Tuinity - implement optimized tracker + // Tuinity start - implement optimized tracker + // It's important to note that this is ONLY called when the chunk is at ticking level. + // At this point, the entities should be added in the chunk. + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange + = this.playerEntityTrackerTrackMap.getObjectsInRange(chunk.getPos()); + // only send entities when they're in tracking range... + if (playersInRange != null && playersInRange.contains(entityplayer)) { + chunk.forEachEntity((Entity entityInChunk) -> { + PlayerChunkMap.EntityTracker tracker = entityInChunk.tracker; + if (tracker == null) { + return; // when added to tracker, this entity will propagate to players + } + + if (entityInChunk == entityplayer) { + return; // can't track himself + } + + // Note: We don't add to the lists because the track logic will handle it + tracker.updateTrackingPlayer(entityplayer); + }); + } + } + // Tuinity end - implement optimized tracker Iterator iterator; Entity entity1; @@ -1618,7 +2181,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { public class EntityTracker { - private final EntityTrackerEntry trackerEntry; + final EntityTrackerEntry trackerEntry; // Tuinity - private -> package private private final Entity tracker; private final int trackingDistance; private SectionPosition e; @@ -1635,6 +2198,36 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.e = SectionPosition.a(entity); } + // Tuinity start - optimise entity tracker tracking + public void clearAll() { + for (EntityPlayer player : this.trackedPlayers) { + this.trackerEntry.onUntrack(player); + } + this.trackedPlayers.clear(); + } + + void updateTrackingPlayers(PlayerChunkMap chunkMap, com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet fromPlayers) { + EntityTrackerEntry entry = this.trackerEntry; + for (Iterator iterator = this.trackedPlayers.iterator(); iterator.hasNext();) { + EntityPlayer tracker = iterator.next(); + if (!fromPlayers.contains(tracker)) { + // definitely out of range + iterator.remove(); + entry.onUntrack(tracker); + } + } + + Object[] backingArray = fromPlayers.getBackingSet(); + for (int i = 0, len = backingArray.length; i < len; ++i) { + if (backingArray[i] == null) { + continue; + } + EntityPlayer player = (EntityPlayer)backingArray[i]; + this.updatePlayer(player); + } + } + // Tuinity end - optimise entity tracker tracking + public boolean equals(Object object) { return object instanceof PlayerChunkMap.EntityTracker ? ((PlayerChunkMap.EntityTracker) object).tracker.getId() == this.tracker.getId() : false; } @@ -1684,10 +2277,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { public void updatePlayer(EntityPlayer entityplayer) { org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot if (entityplayer != this.tracker) { - Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113 - int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16); - boolean flag = vec3d.x >= (double) (-i) && vec3d.x <= (double) i && vec3d.z >= (double) (-i) && vec3d.z <= (double) i && this.tracker.a(entityplayer); - + // Tuinity start - remove allocation of Vec3d here + double vec3d_dx = entityplayer.locX() - this.tracker.locX(); + double vec3d_dy = entityplayer.locY() - this.tracker.locY(); + double vec3d_dz = entityplayer.locZ() - this.tracker.locZ(); + // Tuinity end - remove allocation of Vec3d here + int i = Math.min(this.b(), (entityplayer.getEffectiveViewDistance(PlayerChunkMap.this)) * 16); // Tuinity - per player view distance + boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.tracker.a(entityplayer); // Tuinity start - remove allocation of Vec3d here if (flag) { boolean flag1 = this.tracker.attachedToPlayer; @@ -1696,7 +2292,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair()); if (playerchunk != null && playerchunk.getChunk() != null) { - flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance; + flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.playerViewDistanceBroadcastMap.getLastViewDistance(entityplayer); // Tuinity - per player view distance } } @@ -1738,6 +2334,44 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { return i; } + // Tuinity start - optimized tracker + public final void updateTrackingPlayer(EntityPlayer entityplayer) { + if (entityplayer == this.tracker) { + return; + } + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity + // the same as updatePlayer except without a distance check + // we also add a world check since we queue tracking changes + // TODO check on update + // CraftBukkit start - respect vanish API + boolean shouldTrack = entityplayer.world == tracker.world; + if (this.tracker instanceof EntityPlayer) { + Player player = ((EntityPlayer)this.tracker).getBukkitEntity(); + if (!entityplayer.getBukkitEntity().canSee(player)) { + shouldTrack = false; + } + } + + entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId())); + // CraftBukkit end + + if (shouldTrack) { + if (this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper + this.trackerEntry.onTrack(entityplayer); + } + } else { + this.removeTrackingPlayer(entityplayer); + } + } + + public final void removeTrackingPlayer(EntityPlayer player) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity + if (this.trackedPlayers.remove(player)) { + this.trackerEntry.onUntrack(player); + } + } + // Tuinity end - optimized tracker + public void track(List list) { Iterator iterator = list.iterator(); diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java index ce4340a47..1b60310bb 100644 --- a/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -20,14 +20,29 @@ public class PlayerInteractManager { public EntityPlayer player; private EnumGamemode gamemode; private boolean e; - private int lastDigTick; + private int lastDigTick; private long lastDigTime; // Tuinity - lag compensate block breaking private BlockPosition g; private int currentTick; - private boolean i; + private boolean i; private final boolean hasDestroyedTooFast() { return this.i; } // Tuinity - OBFHELPER private BlockPosition j; - private int k; + private int k; private final int getHasDestroyedTooFastStartTick() { return this.k; } // Tuinity - OBFHELPER + private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking private int l; + // Tuinity start - lag compensate block breaking + private int getTimeDiggingLagCompensate() { + int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L)); + int tickDiff = this.currentTick - this.lastDigTick; + return lagCompensated > (tickDiff + 1) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to + } + + private int getTimeDiggingTooFastLagCompensate() { + int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L)); + int tickDiff = this.currentTick - this.getHasDestroyedTooFastStartTick(); + return lagCompensated > (tickDiff + 1) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to + } + // Tuinity end + public PlayerInteractManager(WorldServer worldserver) { this.gamemode = EnumGamemode.NOT_SET; this.g = BlockPosition.ZERO; @@ -73,7 +88,7 @@ public class PlayerInteractManager { if (iblockdata.isAir()) { this.i = false; } else { - float f = this.a(iblockdata, this.j, this.k); + float f = this.updateBlockBreakAnimation(iblockdata, this.j, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks if (f >= 1.0F) { this.i = false; @@ -87,7 +102,7 @@ public class PlayerInteractManager { this.l = -1; this.e = false; } else { - this.a(iblockdata, this.g, this.lastDigTick); + this.updateBlockBreakAnimation(iblockdata, this.g, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying blocks } } @@ -95,6 +110,12 @@ public class PlayerInteractManager { private float a(IBlockData iblockdata, BlockPosition blockposition, int i) { int j = this.currentTick - i; + // Tuinity start - change i (startTime) to totalTime + return this.updateBlockBreakAnimation(iblockdata, blockposition, j); + } + private float updateBlockBreakAnimation(IBlockData iblockdata, BlockPosition blockposition, int totalTime) { + int j = totalTime; + // Tuinity end float f = iblockdata.getDamage(this.player, this.player.world, blockposition) * (float) (j + 1); int k = (int) (f * 10.0F); @@ -168,7 +189,7 @@ public class PlayerInteractManager { } // this.world.douseFire((EntityHuman) null, blockposition, enumdirection); // CraftBukkit - Moved down - this.lastDigTick = this.currentTick; + this.lastDigTick = this.currentTick; this.lastDigTime = System.nanoTime(); // Tuinity - lag compensate block breaking float f = 1.0F; iblockdata = this.world.getType(blockposition); @@ -223,12 +244,12 @@ public class PlayerInteractManager { int j = (int) (f * 10.0F); this.world.a(this.player.getId(), blockposition, j); - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); + //this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); // Tuinity - on lagging servers this can cause the client to think it's only just started to destroy a block when it already has/will this.l = j; } } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.STOP_DESTROY_BLOCK) { if (blockposition.equals(this.g)) { - int k = this.currentTick - this.lastDigTick; + int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking iblockdata = this.world.getType(blockposition); if (!iblockdata.isAir()) { @@ -245,12 +266,12 @@ public class PlayerInteractManager { this.e = false; this.i = true; this.j = blockposition; - this.k = this.lastDigTick; + this.k = this.lastDigTick; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Tuinity - lag compensate block breaking } } } - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "stopped destroying")); + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // Tuinity - this can cause clients on a lagging server to think they're not currently destroying a block } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.ABORT_DESTROY_BLOCK) { this.e = false; if (!Objects.equals(this.g, blockposition)) { @@ -260,7 +281,7 @@ public class PlayerInteractManager { } this.world.a(this.player.getId(), blockposition, -1); - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); + //this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); // Tuinity - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying } } @@ -270,7 +291,7 @@ public class PlayerInteractManager { public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) { if (this.breakBlock(blockposition)) { - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, s)); + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // Tuinity - this can cause clients on a lagging server to think they're not currently destroying a block } else { this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // CraftBukkit - SPIGOT-5196 } diff --git a/src/main/java/net/minecraft/server/PlayerInventory.java b/src/main/java/net/minecraft/server/PlayerInventory.java index d103cfaac..1b8cb3fc6 100644 --- a/src/main/java/net/minecraft/server/PlayerInventory.java +++ b/src/main/java/net/minecraft/server/PlayerInventory.java @@ -559,8 +559,10 @@ public class PlayerInventory implements IInventory, INamableTileEntity { NonNullList nonnulllist; - for (Iterator iterator = this.f.iterator(); iterator.hasNext(); i -= nonnulllist.size()) { - nonnulllist = (NonNullList) iterator.next(); + // Tuinity start - reduce iterator creation + for (int index = 0, len = this.f.size(); index < len; ++index, i -= nonnulllist.size()) { + nonnulllist = this.f.get(index); + // Tuinity end - reduce iterator creation if (i < nonnulllist.size()) { list = nonnulllist; break; diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index 7b79ee4fe..253ee52eb 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -156,7 +156,7 @@ public abstract class PlayerList { // CraftBukkit - getType() // Spigot - view distance - playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), worldserver.spigotConfig.viewDistance, flag1, !flag)); + playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), Math.max(entityplayer.getEffectiveViewDistance(worldserver.getChunkProvider().playerChunkMap), entityplayer.getEffectiveNoTickViewDistance(worldserver.getChunkProvider().playerChunkMap)), flag1, !flag)); // Tuinity - per player view distance entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit playerconnection.sendPacket(new PacketPlayOutCustomPayload(PacketPlayOutCustomPayload.a, (new PacketDataSerializer(Unpooled.buffer())).a(this.getServer().getServerModName()))); playerconnection.sendPacket(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); @@ -700,7 +700,7 @@ public abstract class PlayerList { WorldData worlddata = worldserver.getWorldData(); entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver.worldProvider.getDimensionManager().getType(), WorldData.c(worldserver.getWorldData().getSeed()), worldserver.getWorldData().getType(), entityplayer1.playerInteractManager.getGameMode())); - entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver.spigotConfig.viewDistance)); // Spigot + entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(Math.max(entityplayer1.getEffectiveViewDistance(worldserver.getChunkProvider().playerChunkMap), entityplayer1.getEffectiveNoTickViewDistance(worldserver.getChunkProvider().playerChunkMap)))); // Spigot // Tuinity - per player view distance entityplayer1.spawnIn(worldserver); entityplayer1.dead = false; entityplayer1.playerConnection.teleport(new Location(worldserver.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch)); @@ -1178,7 +1178,7 @@ public abstract class PlayerList { public void a(int i) { this.viewDistance = i; - this.sendAll(new PacketPlayOutViewDistance(i)); + //this.sendAll(new PacketPlayOutViewDistance(i)); // Tuinity - move into setViewDistance Iterator iterator = this.server.getWorlds().iterator(); while (iterator.hasNext()) { diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java index f376e2106..5a883aac1 100644 --- a/src/main/java/net/minecraft/server/ProtoChunk.java +++ b/src/main/java/net/minecraft/server/ProtoChunk.java @@ -180,14 +180,11 @@ public class ProtoChunk implements IChunkAccess { lightengine.a(blockposition); } - EnumSet enumset = this.getChunkStatus().h(); + HeightMap.Type[] enumset = this.getChunkStatus().heightMaps; // Tuinity - reduce iterator creation EnumSet enumset1 = null; - Iterator iterator = enumset.iterator(); + // Tuinity - reduce iterator creation - HeightMap.Type heightmap_type; - - while (iterator.hasNext()) { - heightmap_type = (HeightMap.Type) iterator.next(); + for (HeightMap.Type heightmap_type : enumset) { // Tuinity - reduce iterator creation HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); if (heightmap == null) { @@ -203,10 +200,9 @@ public class ProtoChunk implements IChunkAccess { HeightMap.a(this, enumset1); } - iterator = enumset.iterator(); - - while (iterator.hasNext()) { - heightmap_type = (HeightMap.Type) iterator.next(); + // Tuinity start - reduce iterator creation + for (HeightMap.Type heightmap_type : enumset) { + // Tuinity end ((HeightMap) this.f.get(heightmap_type)).a(i & 15, j, k & 15, iblockdata); } diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java index df728e2c0..5bd7b8f55 100644 --- a/src/main/java/net/minecraft/server/RegionFile.java +++ b/src/main/java/net/minecraft/server/RegionFile.java @@ -28,14 +28,342 @@ public class RegionFile implements AutoCloseable { private static final Logger LOGGER = LogManager.getLogger(); private static final ByteBuffer b = ByteBuffer.allocateDirect(1); private final FileChannel dataFile; - private final java.nio.file.Path d; - private final RegionFileCompression e; + private final java.nio.file.Path d; private final java.nio.file.Path getContainingDataFolder() { return this.d; } // Tuinity - OBFHELPER + private final RegionFileCompression e; private final RegionFileCompression getRegionFileCompression() { return this.e; } // Tuinity - OBFHELPER private final ByteBuffer f; - private final IntBuffer g; - private final IntBuffer h; + private final IntBuffer g; private final IntBuffer getOffsets() { return this.g; } // Tuinity - OBFHELPER + private final IntBuffer h; private final IntBuffer getTimestamps() { return this.h; } // Tuinity - OBFHELPER private final RegionFileBitSet freeSectors; public final File file; + // Tuinity start - try to recover from RegionFile header corruption + private static long roundToSectors(long bytes) { + long sectors = bytes >>> 12; // 4096 = 2^12 + long remainingBytes = bytes & 4095; + long sign = -remainingBytes; // sign is 1 if nonzero + return sectors + (sign >>> 63); + } + + private static final NBTTagCompound OVERSIZED_COMPOUND = new NBTTagCompound(); + + private NBTTagCompound attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { + try { + if (chunkDataLength < 0) { + return null; + } + + long offset = sector * 4096L + 4L; // offset for chunk data + + if ((offset + chunkDataLength) > fileLength) { + return null; + } + + ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); + if (chunkDataLength != this.dataFile.read(chunkData, offset)) { + return null; + } + + chunkData.flip(); + + byte compressionType = chunkData.get(); + if (compressionType < 0) { // compressionType & 128 != 0 + // oversized chunk + return OVERSIZED_COMPOUND; + } + + RegionFileCompression compression = RegionFileCompression.getByType(compressionType); + if (compression == null) { + return null; + } + + InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); + + return NBTCompressedStreamTools.readNBT(new DataInputStream(new BufferedInputStream(input))); + } catch (Exception ex) { + return null; + } + } + + private int getLength(long sector) throws IOException { + ByteBuffer length = ByteBuffer.allocate(4); + if (4 != this.dataFile.read(length, sector * 4096L)) { + return -1; + } + + return length.getInt(0); + } + + // note: only call for CHUNK regionfiles + void recalculateHeader() throws IOException { + if (!this.canRecalcHeader) { + return; + } + synchronized (this) { + MinecraftServer.LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.file.getAbsolutePath(), new Throwable()); + + // try to backup file so maybe it could be sent to us for further investigation + + File backup = new File(this.file.getParent(), this.file.getName() + "." + new java.util.Random().nextLong() + ".backup"); + try { + this.dataFile.force(true); + MinecraftServer.LOGGER.warn("Backing up to " + backup.getAbsolutePath()); + java.nio.file.Files.copy(this.file.toPath(), backup.toPath()); + MinecraftServer.LOGGER.warn("Backed up the regionfile " + backup.getAbsolutePath()); + } catch (IOException ex) { + MinecraftServer.LOGGER.error("Failed to backup to " + backup.getAbsolutePath(), ex); + } + + NBTTagCompound[] compounds = new NBTTagCompound[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) + int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes + int[] sectorOffsets = new int[32 * 32]; // in sectors + boolean[] hasAikarOversized = new boolean[32 * 32]; + + long fileLength = this.dataFile.size(); + long totalSectors = roundToSectors(fileLength); + + // search the regionfile from start to finish for the most up-to-date chunk data + + for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip + int chunkDataLength = this.getLength(i); + NBTTagCompound compound = this.attemptRead(i, chunkDataLength, fileLength); + if (compound == null || compound == OVERSIZED_COMPOUND) { + continue; + } + + ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(compound); + int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); + + NBTTagCompound otherCompound = compounds[location]; + + if (otherCompound != null && ChunkRegionLoader.getLastWorldSaveTime(otherCompound) > ChunkRegionLoader.getLastWorldSaveTime(compound)) { + continue; // don't overwrite newer data. + } + + // aikar oversized? + File aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); + boolean isAikarOversized = false; + if (aikarOversizedFile.exists()) { + try { + NBTTagCompound aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); + if (ChunkRegionLoader.getLastWorldSaveTime(compound) == ChunkRegionLoader.getLastWorldSaveTime(aikarOversizedCompound)) { + // best we got for an id. hope it's good enough + isAikarOversized = true; + } + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.file.getAbsolutePath() + ", oversized data for this chunk will be lost", ex); + // fall through, if we can't read aikar oversized we can't risk corrupting chunk data + } + } + + hasAikarOversized[location] = isAikarOversized; + compounds[location] = compound; + rawLengths[location] = chunkDataLength + 4; + sectorOffsets[location] = (int)i; + + int chunkSectorLength = (int)roundToSectors(rawLengths[location]); + i += chunkSectorLength; + --i; // gets incremented next iteration + } + + // forge style oversized data is already handled by the local search, and aikar data we just hope + // we get it right as aikar data has no identifiers we could use to try and find its corresponding + // local data compound + + java.nio.file.Path containingFolder = this.getContainingDataFolder(); + File[] regionFiles = containingFolder.toFile().listFiles(); + boolean[] oversized = new boolean[32 * 32]; + RegionFileCompression[] oversizedCompressionTypes = new RegionFileCompression[32 * 32]; + + if (regionFiles != null) { + ChunkCoordIntPair ourLowerLeftPosition = RegionFileCache.getRegionFileCoordinates(this.file); + + if (ourLowerLeftPosition == null) { + MinecraftServer.LOGGER.fatal("Unable to get chunk location of regionfile " + this.file.getAbsolutePath() + ", cannot recover oversized chunks"); + } else { + int lowerXBound = ourLowerLeftPosition.x; // inclusive + int lowerZBound = ourLowerLeftPosition.z; // inclusive + int upperXBound = lowerXBound + 32 - 1; // inclusive + int upperZBound = lowerZBound + 32 - 1; // inclusive + + // read mojang oversized data + for (File regionFile : regionFiles) { + ChunkCoordIntPair oversizedCoords = getOversizedChunkPair(regionFile); + if (oversizedCoords == null) { + continue; + } + + if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { + continue; // not in our regionfile + } + + // ensure oversized data is valid & is newer than data in the regionfile + + int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); + + byte[] chunkData; + try { + chunkData = Files.readAllBytes(regionFile.toPath()); + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath(), ex); + continue; + } + + NBTTagCompound compound = null; + + // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them + RegionFileCompression compression = null; + for (RegionFileCompression compressionType : RegionFileCompression.getCompressionTypes().values()) { + try { + DataInputStream in = new DataInputStream(new BufferedInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData)))); // typical java + compound = NBTCompressedStreamTools.readNBT(in); + compression = compressionType; + break; // reaches here iff readNBT does not throw + } catch (Exception ex) { + continue; + } + } + + if (compound == null) { + MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", it's corrupt. Its data will be lost"); + continue; + } + + if (compounds[location] != null && ChunkRegionLoader.getLastWorldSaveTime(compound) > ChunkRegionLoader.getLastWorldSaveTime(compounds[location])) { + oversized[location] = true; + oversizedCompressionTypes[location] = compression; + } + } + } + } + + // now we need to calculate a new offset header + + int[] calculatedOffsets = new int[32 * 32]; + RegionFileBitSet newSectorAllocations = new RegionFileBitSet(); + newSectorAllocations.allocate(0, 2); // make space for header + + // allocate sectors for normal chunks + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + if (oversized[location]) { + continue; + } + + int rawLength = rawLengths[location]; // bytes + int sectorOffset = sectorOffsets[location]; // sectors + int sectorLength = (int)roundToSectors(rawLength); + + if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { + calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized + } else { + MinecraftServer.LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + ", chunk will be regenerated"); + } + } + } + + // allocate sectors for oversized chunks + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + if (!oversized[location]) { + continue; + } + + int sectorOffset = newSectorAllocations.allocateNewSpace(1); + int sectorLength = 1; + + try { + this.dataFile.write(this.getOversizedChunkHolderData(oversizedCompressionTypes[location]), sectorOffset * 4096); + // only allocate in the new offsets if the write succeeds + calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized + } catch (IOException ex) { + newSectorAllocations.free(sectorOffset, sectorLength); + MinecraftServer.LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + " will be regenerated"); + } + } + } + + // rewrite aikar oversized data + + this.oversizedCount = 0; + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + int isAikarOversized = hasAikarOversized[location] ? 1 : 0; + + this.oversizedCount += isAikarOversized; + this.oversized[location] = (byte)isAikarOversized; + } + } + + if (this.oversizedCount > 0) { + try { + this.writeOversizedMeta(); + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.file.getAbsolutePath(), ex); + this.getOversizedMetaFile().delete(); + } + } else { + this.getOversizedMetaFile().delete(); + } + + this.freeSectors.copyFrom(newSectorAllocations); + + // before we overwrite the old sectors, print a summary of the chunks that got changed. + + MinecraftServer.LOGGER.info("Starting summary of changes for regionfile " + this.file.getAbsolutePath()); + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + int oldOffset = this.getOffsets().get(location); + int newOffset = calculatedOffsets[location]; + + if (oldOffset == newOffset) { + continue; + } + + this.getOffsets().put(location, newOffset); // overwrite incorrect offset + + if (oldOffset == 0) { + // found lost data + MinecraftServer.LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath()); + } else if (newOffset == 0) { + MinecraftServer.LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.file.getAbsolutePath() + ", it will be regenerated"); + } else { + MinecraftServer.LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.file.getAbsolutePath()); + } + } + } + + MinecraftServer.LOGGER.info("End of change summary for regionfile " + this.file.getAbsolutePath()); + + // simply destroy the timestamp header, it's not used + + for (int i = 0; i < 32 * 32; ++i) { + this.getTimestamps().put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this + } + + // write new header + try { + this.flushHeader(); + this.dataFile.force(true); // try to ensure it goes through... + MinecraftServer.LOGGER.info("Successfully wrote new header to disk for regionfile " + this.file.getAbsolutePath()); + } catch (IOException ex) { + MinecraftServer.LOGGER.fatal("Failed to write new header to disk for regionfile " + this.file.getAbsolutePath(), ex); + } + } + } + + final boolean canRecalcHeader; // final forces compile fail on new constructor + // Tuinity end + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper // Paper start - Cache chunk status @@ -63,10 +391,21 @@ public class RegionFile implements AutoCloseable { // Paper end public RegionFile(File file, File file1) throws IOException { - this(file.toPath(), file1.toPath(), RegionFileCompression.b); + // Tuinity start - add header recalculation boolean + this(file, file1, false); + } + public RegionFile(File file, File file1, boolean canRecalcHeader) throws IOException { + this(file.toPath(), file1.toPath(), RegionFileCompression.b, canRecalcHeader); + // Tuinity end } public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression) throws IOException { + // Tuinity start - add header recalculation boolean + this(java_nio_file_path, java_nio_file_path1, regionfilecompression, false); + } + public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean canRecalcHeader) throws IOException { + this.canRecalcHeader = canRecalcHeader; + // Tuinity end this.file = java_nio_file_path.toFile(); // Paper this.f = ByteBuffer.allocateDirect(8192); initOversizedState(); @@ -90,6 +429,8 @@ public class RegionFile implements AutoCloseable { RegionFile.LOGGER.warn("Region file {} has truncated header: {}", java_nio_file_path, i); } + boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption + for (int j = 0; j < 1024; ++j) { int k = this.g.get(j); @@ -105,20 +446,53 @@ public class RegionFile implements AutoCloseable { } // Spigot end - this.freeSectors.a(l, i1); + needsHeaderRecalc |= !this.freeSectors.tryAllocate(l, i1); // Tuinity - recalculate header on header corruption } } + + // Tuinity start - recalculate header on header corruption + // we move the recalc here so comparison to old header is correct when logging to console + if (needsHeaderRecalc) { // true if header gave us overlapping allocations + this.recalculateHeader(); + } + // Tuinity end } } } + private final java.nio.file.Path getOversizedChunkPath(ChunkCoordIntPair chunkcoordintpair) { return this.e(chunkcoordintpair); } // Tuinity - OBFHELPER private java.nio.file.Path e(ChunkCoordIntPair chunkcoordintpair) { - String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; + String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; // Tuinity - diff on change return this.d.resolve(s); } + // Tuinity start + private static ChunkCoordIntPair getOversizedChunkPair(File file) { + String fileName = file.getName(); + + if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { + return null; + } + + String[] split = fileName.split("\\."); + + if (split.length != 4) { + return null; + } + + try { + int x = Integer.parseInt(split[1]); + int z = Integer.parseInt(split[2]); + + return new ChunkCoordIntPair(x, z); + } catch (NumberFormatException ex) { + return null; + } + } + // Tuinity end + @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER @Nullable public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException { @@ -142,6 +516,12 @@ public class RegionFile implements AutoCloseable { this.dataFile.read(bytebuffer, (long) (j * 4096)); ((java.nio.Buffer) bytebuffer).flip(); if (bytebuffer.remaining() < 5) { + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkcoordintpair, l, bytebuffer.remaining()); return null; } else { @@ -150,6 +530,12 @@ public class RegionFile implements AutoCloseable { if (i1 == 0) { RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", chunkcoordintpair); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else { int j1 = i1 - 1; @@ -162,9 +548,21 @@ public class RegionFile implements AutoCloseable { return this.a(chunkcoordintpair, b(b0)); } else if (j1 > bytebuffer.remaining()) { RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", chunkcoordintpair, j1, bytebuffer.remaining()); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else if (j1 < 0) { RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, chunkcoordintpair); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else { return this.a(chunkcoordintpair, b0, a(bytebuffer, j1)); @@ -323,10 +721,15 @@ public class RegionFile implements AutoCloseable { } private ByteBuffer a() { + // Tuinity start - add compressionType param + return this.getOversizedChunkHolderData(this.getRegionFileCompression()); + } + private ByteBuffer getOversizedChunkHolderData(RegionFileCompression compressionType) { + // Tuinity end ByteBuffer bytebuffer = ByteBuffer.allocate(5); bytebuffer.putInt(1); - bytebuffer.put((byte) (this.e.a() | 128)); + bytebuffer.put((byte) (compressionType.a() | 128)); // Tuinity - replace with compressionType ((java.nio.Buffer) bytebuffer).flip(); return bytebuffer; } @@ -363,6 +766,7 @@ public class RegionFile implements AutoCloseable { }; } + private final void flushHeader() throws IOException { this.b(); } // Tuinity - OBFHELPER private void b() throws IOException { ((java.nio.Buffer) this.f).position(0); this.dataFile.write(this.f, 0L); diff --git a/src/main/java/net/minecraft/server/RegionFileBitSet.java b/src/main/java/net/minecraft/server/RegionFileBitSet.java index 1ebdf73cc..cfa3ecb03 100644 --- a/src/main/java/net/minecraft/server/RegionFileBitSet.java +++ b/src/main/java/net/minecraft/server/RegionFileBitSet.java @@ -4,18 +4,42 @@ import java.util.BitSet; public class RegionFileBitSet { - private final BitSet a = new BitSet(); + private final BitSet a = new BitSet(); private final BitSet getBitset() { return this.a; } // Tuinity - OBFHELPER public RegionFileBitSet() {} + public final void allocate(int from, int length) { this.a(from, length); } // Tuinity - OBFHELPER public void a(int i, int j) { this.a.set(i, i + j); } + public final void free(int from, int length) { this.b(from, length); } // Tuinity - OBFHELPER public void b(int i, int j) { this.a.clear(i, i + j); } + // Tuinity start + public final void copyFrom(RegionFileBitSet other) { + BitSet thisBitset = this.getBitset(); + BitSet otherBitset = other.getBitset(); + + for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { + thisBitset.set(i, otherBitset.get(i)); + } + } + + public final boolean tryAllocate(int from, int length) { + BitSet bitset = this.getBitset(); + int firstSet = bitset.nextSetBit(from); + if (firstSet > 0 && firstSet < (from + length)) { + return false; + } + bitset.set(from, from + length); + return true; + } + // Tuinity end + + public final int allocateNewSpace(final int requiredLength) { return this.a(requiredLength); } // Tuinity - OBFHELPER public int a(int i) { int j = 0; diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java index 0f201000f..c88ad8de0 100644 --- a/src/main/java/net/minecraft/server/RegionFileCache.java +++ b/src/main/java/net/minecraft/server/RegionFileCache.java @@ -18,6 +18,30 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final this.b = file; } + // Tuinity start + public static ChunkCoordIntPair getRegionFileCoordinates(File file) { + String fileName = file.getName(); + if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { + return null; + } + + String[] split = fileName.split("\\."); + + if (split.length != 4) { + return null; + } + + try { + int x = Integer.parseInt(split[1]); + int z = Integer.parseInt(split[2]); + + return new ChunkCoordIntPair(x << 5, z << 5); + } catch (NumberFormatException ex) { + return null; + } + } + // Tuinity end + // Paper start public synchronized RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { // Paper - synchronize for async io @@ -51,9 +75,9 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final this.b.mkdirs(); } - File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); + File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Tuinity - diff on change if (existingOnly && !file.exists()) return null; // CraftBukkit - RegionFile regionfile1 = new RegionFile(file, this.b); + RegionFile regionfile1 = new RegionFile(file, this.b, this instanceof IChunkLoader); // Tuinity - allow for chunk regionfiles to regen header this.cache.putAndMoveToFirst(i, regionfile1); // Paper start @@ -137,6 +161,13 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final @Nullable public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException { RegionFile regionfile = this.getFile(chunkcoordintpair, false, true); // CraftBukkit // Paper + // Tuinity start - Add regionfile parameter + return this.readFromRegionFile(regionfile, chunkcoordintpair); + } + private NBTTagCompound readFromRegionFile(RegionFile regionfile, ChunkCoordIntPair chunkcoordintpair) throws IOException { + // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile + // if we decide to re-read + // Tuinity end try { // Paper DataInputStream datainputstream = regionfile.a(chunkcoordintpair); // Paper start @@ -152,6 +183,16 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final try { if (datainputstream != null) { nbttagcompound = NBTCompressedStreamTools.a(datainputstream); + // Tuinity start - recover from corrupt regionfile header + if (this instanceof IChunkLoader) { + ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(nbttagcompound); + if (!chunkPos.equals(chunkcoordintpair)) { + regionfile.recalculateHeader(); + regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. + return this.readFromRegionFile(regionfile, chunkcoordintpair); + } + } + // Tuinity end return nbttagcompound; } diff --git a/src/main/java/net/minecraft/server/RegionFileCompression.java b/src/main/java/net/minecraft/server/RegionFileCompression.java index 3382d678e..29137f495 100644 --- a/src/main/java/net/minecraft/server/RegionFileCompression.java +++ b/src/main/java/net/minecraft/server/RegionFileCompression.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public class RegionFileCompression { - private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); + private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); static final Int2ObjectMap getCompressionTypes() { return RegionFileCompression.d; } // Tuinity - OBFHELPER public static final RegionFileCompression a = a(new RegionFileCompression(1, GZIPInputStream::new, GZIPOutputStream::new)); public static final RegionFileCompression b = a(new RegionFileCompression(2, InflaterInputStream::new, DeflaterOutputStream::new)); public static final RegionFileCompression c = a(new RegionFileCompression(3, (inputstream) -> { @@ -36,8 +36,8 @@ public class RegionFileCompression { return regionfilecompression; } - @Nullable - public static RegionFileCompression a(int i) { + @Nullable public static RegionFileCompression getByType(int type) { return RegionFileCompression.a(type); } // Tuinity - OBFHELPER + @Nullable public static RegionFileCompression a(int i) { // Tuinity - OBFHELPER return (RegionFileCompression) RegionFileCompression.d.get(i); } @@ -53,6 +53,7 @@ public class RegionFileCompression { return (OutputStream) this.g.wrap(outputstream); } + public final InputStream wrap(InputStream inputstream) throws IOException { return this.a(inputstream); } // Tuinity - OBFHELPER public InputStream a(InputStream inputstream) throws IOException { return (InputStream) this.f.wrap(inputstream); } diff --git a/src/main/java/net/minecraft/server/StructureBoundingBox.java b/src/main/java/net/minecraft/server/StructureBoundingBox.java index dbb565e74..118762778 100644 --- a/src/main/java/net/minecraft/server/StructureBoundingBox.java +++ b/src/main/java/net/minecraft/server/StructureBoundingBox.java @@ -4,12 +4,12 @@ import com.google.common.base.MoreObjects; public class StructureBoundingBox { - public int a; - public int b; - public int c; - public int d; - public int e; - public int f; + public int a; public final int getMinX() { return this.a; } // Tuinity - OBFHELPER + public int b; public final int getMinY() { return this.b; } // Tuinity - OBFHELPER + public int c; public final int getMinZ() { return this.c; } // Tuinity - OBFHELPER + public int d; public final int getMaxX() { return this.d; } // Tuinity - OBFHELPER + public int e; public final int getMaxY() { return this.e; } // Tuinity - OBFHELPER + public int f; public final int getMaxZ() { return this.f; } // Tuinity - OBFHELPER public StructureBoundingBox() {} @@ -84,6 +84,7 @@ public class StructureBoundingBox { this.e = 512; } + public final boolean intersects(StructureBoundingBox boundingBox) { return this.b(boundingBox); } // Tuinity - OBFHELPER public boolean b(StructureBoundingBox structureboundingbox) { return this.d >= structureboundingbox.a && this.a <= structureboundingbox.d && this.f >= structureboundingbox.c && this.c <= structureboundingbox.f && this.e >= structureboundingbox.b && this.b <= structureboundingbox.e; } @@ -114,6 +115,7 @@ public class StructureBoundingBox { return new StructureBoundingBox(this.a + i, this.b + j, this.c + k, this.d + i, this.e + j, this.f + k); } + public final boolean hasPoint(BaseBlockPosition baseblockposition) { return this.b(baseblockposition); } // Tuinity - OBFHELPER public boolean b(BaseBlockPosition baseblockposition) { return baseblockposition.getX() >= this.a && baseblockposition.getX() <= this.d && baseblockposition.getZ() >= this.c && baseblockposition.getZ() <= this.f && baseblockposition.getY() >= this.b && baseblockposition.getY() <= this.e; } diff --git a/src/main/java/net/minecraft/server/ThreadedMailbox.java b/src/main/java/net/minecraft/server/ThreadedMailbox.java index 808256902..8b1a3f3f9 100644 --- a/src/main/java/net/minecraft/server/ThreadedMailbox.java +++ b/src/main/java/net/minecraft/server/ThreadedMailbox.java @@ -17,7 +17,7 @@ public class ThreadedMailbox implements Mailbox, AutoCloseable, Runnable { private final String e; public static ThreadedMailbox a(Executor executor, String s) { - return new ThreadedMailbox<>(new PairedQueue.c<>(new ConcurrentLinkedQueue()), executor, s); + return new ThreadedMailbox<>(new PairedQueue.c<>(new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>()), executor, s); // Tuinity - use concurrentutil } public ThreadedMailbox(PairedQueue pairedqueue, Executor executor, String s) { diff --git a/src/main/java/net/minecraft/server/TickListServer.java b/src/main/java/net/minecraft/server/TickListServer.java index f533860bb..a54d36d90 100644 --- a/src/main/java/net/minecraft/server/TickListServer.java +++ b/src/main/java/net/minecraft/server/TickListServer.java @@ -42,6 +42,11 @@ public class TickListServer implements TickList { // Paper end public void b() { + // Tuinity start - allow overriding + this.tick(); + } + public void tick() { + // Tuinity end int i = this.nextTickList.size(); if (false) { // CraftBukkit @@ -109,15 +114,30 @@ public class TickListServer implements TickList { @Override public boolean b(BlockPosition blockposition, T t0) { + // Tuinity start - allow overriding + return this.isPendingTickThisTick(blockposition, t0); + } + public boolean isPendingTickThisTick(BlockPosition blockposition, T t0) { + // Tuinity end return this.g.contains(new NextTickListEntry<>(blockposition, t0)); } @Override public void a(Stream> stream) { + // Tuinity start - allow overriding + this.scheduleAll(stream); + } + public void scheduleAll(Stream> stream) { + // Tuinity end stream.forEach(this::a); } public List> a(ChunkCoordIntPair chunkcoordintpair, boolean flag, boolean flag1) { + // Tuinity start - allow overriding + return this.getEntriesInChunk(chunkcoordintpair, flag, flag1); + } + public List> getEntriesInChunk(ChunkCoordIntPair chunkcoordintpair, boolean flag, boolean flag1) { + // Tuinity end int i = (chunkcoordintpair.x << 4) - 2; int j = i + 16 + 2; int k = (chunkcoordintpair.z << 4) - 2; @@ -127,6 +147,11 @@ public class TickListServer implements TickList { } public List> a(StructureBoundingBox structureboundingbox, boolean flag, boolean flag1) { + // Tuinity start - allow overriding + return this.getEntriesInBoundingBox(structureboundingbox, flag, flag1); + } + public List> getEntriesInBoundingBox(StructureBoundingBox structureboundingbox, boolean flag, boolean flag1) { + // Tuinity end List> list = this.a((List) null, this.nextTickList, structureboundingbox, flag); if (flag && list != null) { @@ -166,6 +191,11 @@ public class TickListServer implements TickList { } public void a(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { + // Tuinity start - allow overriding + this.copy(structureboundingbox, blockposition); + } + public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { + // Tuinity end List> list = this.a(structureboundingbox, false, false); Iterator iterator = list.iterator(); @@ -183,11 +213,17 @@ public class TickListServer implements TickList { } public NBTTagList a(ChunkCoordIntPair chunkcoordintpair) { + // Tuinity start - allow overriding + return this.serialize(chunkcoordintpair); + } + public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { + // Tuinity end List> list = this.a(chunkcoordintpair, false, true); return a(this.b, list, this.f.getTime()); } + public static NBTTagList serialize(Function function, Iterable> iterable, long i) { return TickListServer.a(function, iterable, i); } // Tuinity - OBFHELPER public static NBTTagList a(Function function, Iterable> iterable, long i) { NBTTagList nbttaglist = new NBTTagList(); Iterator iterator = iterable.iterator(); @@ -210,11 +246,21 @@ public class TickListServer implements TickList { @Override public boolean a(BlockPosition blockposition, T t0) { + // Tuinity start - allow overriding + return this.isScheduledForTick(blockposition, t0); + } + public boolean isScheduledForTick(BlockPosition blockposition, T t0) { + // Tuinity end return this.nextTickListHash.contains(new NextTickListEntry<>(blockposition, t0)); } @Override public void a(BlockPosition blockposition, T t0, int i, TickListPriority ticklistpriority) { + // Tuinity start - allow overriding + this.schedule(blockposition, t0, i, ticklistpriority); + } + public void schedule(BlockPosition blockposition, T t0, int i, TickListPriority ticklistpriority) { + // Tuinity end if (!this.a.test(t0)) { this.a(new NextTickListEntry<>(blockposition, t0, (long) i + this.f.getTime(), ticklistpriority)); } @@ -230,6 +276,11 @@ public class TickListServer implements TickList { } public int a() { + // Tuinity start - allow overriding + return this.getTotalScheduledEntries(); + } + public int getTotalScheduledEntries() { + // Tuinity end return this.nextTickListHash.size(); } } diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java index 7a8397815..0d5b1a0b7 100644 --- a/src/main/java/net/minecraft/server/Ticket.java +++ b/src/main/java/net/minecraft/server/Ticket.java @@ -5,9 +5,10 @@ import java.util.Objects; public final class Ticket implements Comparable> { private final TicketType a; - private final int b; + private int b; public final void setTicketLevel(final int value) { this.b = value; } // Tuinity - remove final, add set OBFHELPER public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER - private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER + private long d; public final long getCreationTick() { return this.d; } public final void setCreationTick(final long value) { this.d = value; } // Paper - OBFHELPER // Tuinity - OBFHELPER + boolean isCached; // Tuinity - delay chunk unloads, this defends against really stupid plugins protected Ticket(TicketType tickettype, int i, T t0) { this.a = tickettype; @@ -60,6 +61,7 @@ public final class Ticket implements Comparable> { this.d = i; } + protected final boolean isExpired(long time) { return this.b(time); } // Tuinity - OBFHELPER protected boolean b(long i) { long j = this.a.b(); diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java index 4b87ca2ec..141664b55 100644 --- a/src/main/java/net/minecraft/server/TicketType.java +++ b/src/main/java/net/minecraft/server/TicketType.java @@ -23,6 +23,7 @@ public class TicketType { public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit public static final TicketType ANTIXRAY = a("antixray", Integer::compareTo); // Paper - Anti-Xray public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper + public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads by 30 seconds public static TicketType a(String s, Comparator comparator) { return new TicketType<>(s, comparator, 0L); diff --git a/src/main/java/net/minecraft/server/VoxelShapeArray.java b/src/main/java/net/minecraft/server/VoxelShapeArray.java index caf297fe9..3161e3b97 100644 --- a/src/main/java/net/minecraft/server/VoxelShapeArray.java +++ b/src/main/java/net/minecraft/server/VoxelShapeArray.java @@ -11,7 +11,7 @@ public final class VoxelShapeArray extends VoxelShape { private final DoubleList d; protected VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, double[] adouble, double[] adouble1, double[] adouble2) { - this(voxelshapediscrete, (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble, voxelshapediscrete.b() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble1, voxelshapediscrete.c() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble2, voxelshapediscrete.d() + 1))); + this(voxelshapediscrete, com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList.getList(adouble, voxelshapediscrete.b() + 1), com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList.getList(adouble1, voxelshapediscrete.c() + 1), com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList.getList(adouble2, voxelshapediscrete.d() + 1)); // Tuinity - remove iterator allocation } VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2) { diff --git a/src/main/java/net/minecraft/server/VoxelShapeCubePoint.java b/src/main/java/net/minecraft/server/VoxelShapeCubePoint.java index 9e09671dc..b69a1ed46 100644 --- a/src/main/java/net/minecraft/server/VoxelShapeCubePoint.java +++ b/src/main/java/net/minecraft/server/VoxelShapeCubePoint.java @@ -2,7 +2,7 @@ package net.minecraft.server; import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; -public class VoxelShapeCubePoint extends AbstractDoubleList { +public class VoxelShapeCubePoint extends com.tuinity.tuinity.util.fastutil.ExtendedAbstractDoubleList { // Tuinity - remove iterator allocation private final int a; diff --git a/src/main/java/net/minecraft/server/VoxelShapeMergerList.java b/src/main/java/net/minecraft/server/VoxelShapeMergerList.java index 71d2ae2a9..9129eaa64 100644 --- a/src/main/java/net/minecraft/server/VoxelShapeMergerList.java +++ b/src/main/java/net/minecraft/server/VoxelShapeMergerList.java @@ -18,7 +18,7 @@ public final class VoxelShapeMergerList implements VoxelShapeMerger { int l = doublelist1.size(); int i1 = k + l; - this.a = new DoubleArrayList(i1); + this.a = new com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList(i1); // Tuinity - remove iterator creation this.b = new IntArrayList(i1); this.c = new IntArrayList(i1); diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java index 08c83c62d..d5da9f582 100644 --- a/src/main/java/net/minecraft/server/VoxelShapes.java +++ b/src/main/java/net/minecraft/server/VoxelShapes.java @@ -19,7 +19,7 @@ public final class VoxelShapes { return new VoxelShapeCube(voxelshapebitset); }); public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); + private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList(new double[]{0.0D}), new com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList(new double[]{0.0D}), new com.tuinity.tuinity.util.fastutil.ExtendedDoubleArrayList(new double[]{0.0D})); // Tuinity - remove iterator allocation public static VoxelShape a() { return VoxelShapes.c; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index baad98517..c0c83d433 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -80,6 +80,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final com.tuinity.tuinity.config.TuinityConfig.WorldConnfig tuinityConfig; // Tuinity - Server Config + public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPosition lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; @@ -129,6 +131,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConnfig(worlddata.getName()); // Tuinity - Server Config this.generator = gen; this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit @@ -329,6 +332,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { @Override public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async set type call"); // Tuinity // CraftBukkit start - tree generation if (this.captureTreeGeneration) { CraftBlockState blockstate = capturedBlockStates.get(blockposition); @@ -425,6 +429,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { // CraftBukkit start - Split off from above in order to directly send client and physic updates public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity IBlockData iblockdata = newBlock; IBlockData iblockdata1 = oldBlock; IBlockData iblockdata2 = actualBlock; @@ -433,8 +438,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { this.b(blockposition, iblockdata1, iblockdata2); } - if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement + if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Tuinity - diff on change, see below this.notify(blockposition, iblockdata1, iblockdata, i); + // Tuinity start - per player view distance - allow block updates for non-ticking chunks in player view distance + // if copied from above + } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(blockposition)) != null)) { + ((WorldServer)this).getChunkProvider().flagDirty(blockposition); + // Tuinity end - per player view distance } if (!this.isClientSide && (i & 1) != 0) { @@ -1175,9 +1185,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D); int l = MathHelper.floor((axisalignedbb.maxZ + 2.0D) / 16.0D); + ChunkProviderServer chunkProvider = ((ChunkProviderServer)this.chunkProvider); // Tuinity - optimize for loaded chunks + for (int i1 = i; i1 <= j; ++i1) { for (int j1 = k; j1 <= l; ++j1) { - Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1); // Paper // Tuinity - optimize for loaded chunks if (chunk != null) { chunk.a(entity, axisalignedbb, list, predicate); @@ -1196,9 +1208,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { int l = MathHelper.f((axisalignedbb.maxZ + 2.0D) / 16.0D); List list = Lists.newArrayList(); + ChunkProviderServer chunkProvider = ((ChunkProviderServer)this.chunkProvider); // Tuinity - optimize for loaded chunks + for (int i1 = i; i1 < j; ++i1) { for (int j1 = k; j1 < l; ++j1) { - Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1); // Paper // Tuinity - optimize for loaded chunks if (chunk != null) { chunk.a(entitytypes, axisalignedbb, list, predicate); @@ -1218,10 +1232,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { int l = MathHelper.f((axisalignedbb.maxZ + 2.0D) / 16.0D); List list = Lists.newArrayList(); IChunkProvider ichunkprovider = this.getChunkProvider(); + ChunkProviderServer chunkProvider = ((ChunkProviderServer)this.chunkProvider); // Tuinity - optimize for loaded chunks for (int i1 = i; i1 < j; ++i1) { for (int j1 = k; j1 < l; ++j1) { - Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1); // Paper // Tuinity - optimize for loaded chunks if (chunk != null) { chunk.a(oclass, axisalignedbb, list, predicate); diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index c74b85917..214b555e7 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -55,7 +55,7 @@ public class WorldServer extends World { private static final Logger LOGGER = LogManager.getLogger(); private final List globalEntityList = Lists.newArrayList(); - public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); + public final Int2ObjectLinkedOpenHashMap entitiesById = new Int2ObjectLinkedOpenHashMap(); // Tuinity - change type for fast iterator private final Map entitiesByUUID = Maps.newHashMap(); private final Queue entitiesToAdd = Queues.newArrayDeque(); public final List players = Lists.newArrayList(); // Paper - private -> public @@ -82,6 +82,8 @@ public class WorldServer extends World { return new Throwable(entity + " Added to world at " + new java.util.Date()); } + final com.tuinity.tuinity.util.EntityList trackingUpdateQueue = new com.tuinity.tuinity.util.EntityList(); // Tuinity - optimise tracker + // Paper start - optimise getPlayerByUUID @Nullable @Override @@ -170,6 +172,373 @@ public class WorldServer extends World { } // Paper end + + // Tuinity start + public final com.tuinity.tuinity.util.EntityList loadedEntities = new com.tuinity.tuinity.util.EntityList(); + void onChunkLoad(final Chunk chunk) { + final com.destroystokyo.paper.util.maplist.EntityList list = chunk.entities; + final Entity[] entities = list.getRawData(); + for (int i = 0, size = list.size(); i < size; ++i) { + this.loadedEntities.add(entities[i]); + } + } + + void onChunkUnload(final Chunk chunk) { + final com.destroystokyo.paper.util.maplist.EntityList list = chunk.entities; + final Entity[] entities = list.getRawData(); + for (int i = 0, size = list.size(); i < size; ++i) { + this.loadedEntities.remove(entities[i]); + } + } + + void onChunkSetTicking(int chunkX, int chunkZ) { + ((com.tuinity.tuinity.server.TuinityTickList)this.nextTickListBlock).onChunkSetTicking(chunkX, chunkZ); + ((com.tuinity.tuinity.server.TuinityTickList)this.nextTickListFluid).onChunkSetTicking(chunkX, chunkZ); + } + // Tuinity end + + // Tuinity start + // TODO the general area map is too large, use a smaller one depending on distance + @Nullable + public EntityPlayer findClosestPlayer(double fromX, double fromY, double fromZ, double distance) { + double maximumDistanceSquared; + if (distance < 0) { + maximumDistanceSquared = Double.MAX_VALUE; + } else { + maximumDistanceSquared = distance * distance; + } + + EntityPlayer closestPlayer = null; + double closestDistanceSquared = maximumDistanceSquared; + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if (currDistanceSquared <= closestDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + } + + if (closestPlayer != null) { + return closestPlayer; + } else if (maximumDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return null; + } + + // fall back to iteration over the world players + + for (int i = 0, len = this.players.size(); i < len; ++i) { + EntityPlayer currPlayer = this.players.get(i); + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if (currDistanceSquared <= closestDistanceSquared) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + + return closestPlayer; + } + + @Nullable + public EntityPlayer findClosestPlayer(double fromX, double fromY, double fromZ, double distance, @Nullable Predicate predicate) { + if (predicate == null) { + return this.findClosestPlayer(fromX, fromY, fromZ, distance); + } + + double maximumDistanceSquared; + if (distance < 0) { + maximumDistanceSquared = Double.MAX_VALUE; + } else { + maximumDistanceSquared = distance * distance; + } + + EntityPlayer closestPlayer = null; + double closestDistanceSquared = maximumDistanceSquared; + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if (predicate.test(currPlayer) && currDistanceSquared <= closestDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + } + + if (closestPlayer != null) { + return closestPlayer; + } else if (maximumDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return null; + } + + // fall back to iteration over the world players + + for (int i = 0, len = this.players.size(); i < len; ++i) { + EntityPlayer currPlayer = this.players.get(i); + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if (predicate.test(currPlayer) && currDistanceSquared <= closestDistanceSquared) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + + return closestPlayer; + } + + @Nullable + public List findPlayersInRange(double fromX, double fromY, double fromZ, double distance) { + return this.findPlayersInRange(fromX, fromY, fromZ, distance, new java.util.ArrayList<>(), null); + } + + @Nullable + public List findPlayersInRange(double fromX, double fromY, double fromZ, double distance, @Nonnull List players, @Nullable Predicate predicate) { + double maximumDistanceSquared; + if (distance < 0) { + maximumDistanceSquared = Double.MAX_VALUE; + } else { + maximumDistanceSquared = distance * distance; + } + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if ((predicate == null || predicate.test(currPlayer)) && currDistanceSquared <= maximumDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + players.add(currPlayer); + } + } + } + + if (maximumDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return players; + } + + // fall back to iteration over the world players + + for (int i = 0, len = this.players.size(); i < len; ++i) { + EntityPlayer currPlayer = this.players.get(i); + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if ((predicate == null || predicate.test(currPlayer)) && currDistanceSquared <= maximumDistanceSquared) { + players.add(currPlayer); + } + } + + return players; + } + + @Override + public boolean isPlayerNearby(double fromX, double fromY, double fromZ, double distance) { + double maximumDistanceSquared; + if (distance < 0) { + maximumDistanceSquared = Double.MAX_VALUE; + } else { + maximumDistanceSquared = distance * distance; + } + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + // TODO check on update + if ((currPlayer.isAlive() && !currPlayer.isSpectator()) && currDistanceSquared <= maximumDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return true; + } + } + } + + if (maximumDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return false; + } + + // fall back to iteration over the world players + + for (int i = 0, len = this.players.size(); i < len; ++i) { + EntityPlayer currPlayer = this.players.get(i); + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + // TODO check on update + if ((currPlayer.isAlive() && !currPlayer.isSpectator()) && currDistanceSquared <= maximumDistanceSquared) { + return true; + } + } + + return false; + } + + @Override + public EntityPlayer findClosestPlayerXZ(double fromX, double fromZ, double distanceXZ, @Nullable Predicate predicate) { + double maximumDistanceSquared; + if (distanceXZ < 0) { + maximumDistanceSquared = Double.MAX_VALUE; + } else { + maximumDistanceSquared = distanceXZ * distanceXZ; + } + + EntityPlayer closestPlayer = null; + double closestDistanceSquared = maximumDistanceSquared; + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceXZSquared(fromX, fromZ); + + if ((predicate == null || predicate.test(currPlayer)) && currDistanceSquared <= closestDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + } + + if (closestPlayer != null) { + return closestPlayer; + } else if (maximumDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + return null; + } + + // fall back to iteration over the world players + + for (int i = 0, len = this.players.size(); i < len; ++i) { + EntityPlayer currPlayer = this.players.get(i); + double currDistanceSquared = currPlayer.getDistanceXZSquared(fromX, fromZ); + + if ((predicate == null || predicate.test(currPlayer)) && currDistanceSquared <= closestDistanceSquared) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + + return closestPlayer; + } + + @Nullable + @Override + public EntityPlayer getNearestPlayerForPathFinding(PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double fromX, double fromY, double fromZ) { + double maximumDistanceSquared = PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS; + // it isn't mojang behaviour to straight up limit to 576 blocks or less, but there should be nothing + // that tracks outside that range. Besides, not hard limiting at some point is really stupid. + + EntityPlayer closestPlayer = null; + double closestDistanceSquared = maximumDistanceSquared; + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(fromX), com.tuinity.tuinity.util.Util.getChunkCoordinate(fromZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + double currDistanceSquared = currPlayer.getDistanceSquared(fromX, fromY, fromZ); + + if (pathfindertargetcondition.test(entityliving, currPlayer) && currDistanceSquared <= closestDistanceSquared && currDistanceSquared <= PlayerChunkMap.PLAYER_GENERAL_AREA_MAP_DISTANCE_SQUARED_BLOCKS) { + closestPlayer = currPlayer; + closestDistanceSquared = currDistanceSquared; + } + } + } + + return closestPlayer; + } + + @Override + public List getNearestPlayersForPathFinding(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) { + List ret = new java.util.ArrayList<>(); + + double centerX = (axisalignedbb.minX + axisalignedbb.maxX) / 2.0; + double centerZ = (axisalignedbb.minZ + axisalignedbb.maxZ) / 2.0; + + // we make the assumption that the bounding box isn't greater-than the general area map's chunk radius (~576 blocks) + + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearestPlayers = + this.getChunkProvider().playerChunkMap.playerGeneralAreaMap + .getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(com.tuinity.tuinity.util.Util.getChunkCoordinate(centerX), com.tuinity.tuinity.util.Util.getChunkCoordinate(centerZ))); + + if (nearestPlayers != null) { + Object[] backingSet = nearestPlayers.getBackingSet(); + + for (int i = 0, len = backingSet.length; i < len; ++i) { + Object raw = backingSet[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer currPlayer = (EntityPlayer)raw; + + if (axisalignedbb.contains(currPlayer.locX(), currPlayer.locY(), currPlayer.locZ()) && pathfindertargetcondition.test(entityliving, currPlayer)) { + ret.add(currPlayer); + } + } + } + + return ret; + } + // Tuinity end + // Add env and gen to constructor public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { super(worlddata, dimensionmanager, (world, worldprovider) -> { @@ -190,10 +559,10 @@ public class WorldServer extends World { this.pvpMode = minecraftserver.getPVP(); worlddata.world = this; // CraftBukkit end - this.nextTickListBlock = new TickListServer<>(this, (block) -> { + this.nextTickListBlock = new com.tuinity.tuinity.server.TuinityTickList<>(this, (block) -> { // Tuinity - optimise TickListServer return block == null || block.getBlockData().isAir(); }, IRegistry.BLOCK::getKey, IRegistry.BLOCK::get, this::b, "Blocks"); // Paper - Timings - this.nextTickListFluid = new TickListServer<>(this, (fluidtype) -> { + this.nextTickListFluid = new com.tuinity.tuinity.server.TuinityTickList<>(this, (fluidtype) -> { // Tuinity - optimise TickListServer return fluidtype == null || fluidtype == FluidTypes.EMPTY; }, IRegistry.FLUID::getKey, IRegistry.FLUID::get, this::a, "Fluids"); // Paper - Timings this.navigators = Sets.newHashSet(); @@ -458,14 +827,13 @@ public class WorldServer extends World { gameprofilerfiller.exitEnter("regular"); this.tickingEntities = true; - ObjectIterator objectiterator = this.entitiesById.int2ObjectEntrySet().iterator(); + Iterator objectiterator = this.loadedEntities.iterator(); // Tuinity - use fast iterator to reduce entry creation // Tuinity - use loaded entity list org.spigotmc.ActivationRange.activateEntities(this); // Spigot timings.entityTick.startTiming(); // Spigot TimingHistory.entityTicks += this.globalEntityList.size(); // Paper while (objectiterator.hasNext()) { - Entry entry = (Entry) objectiterator.next(); - Entity entity1 = (Entity) entry.getValue(); + Entity entity1 = (Entity) objectiterator.next(); // Tuinity - use loaded entity list Entity entity2 = entity1.getVehicle(); /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed @@ -502,7 +870,7 @@ public class WorldServer extends World { gameprofilerfiller.enter("remove"); if (entity1.dead) { this.removeEntityFromChunk(entity1); - objectiterator.remove(); + objectiterator.remove(); this.entitiesById.remove(entity1.getId()); // Tuinity - use loaded entity list this.unregisterEntity(entity1); } @@ -818,6 +1186,10 @@ public class WorldServer extends World { int k = MathHelper.floor(entity.locZ() / 16.0D); if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) { + // Tuinity start - optimized tracker + int prevChunkX = entity.chunkX; + int prevChunkZ = entity.chunkZ; + // Tuinity end - optimized tracker if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) { this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY); } @@ -827,6 +1199,90 @@ public class WorldServer extends World { } else { this.getChunkAt(i, k).a(entity); } + // Tuinity start - optimized tracker + int newChunkX = entity.chunkX; + int newChunkZ = entity.chunkZ; + if (entity.valid && (prevChunkX != newChunkX || prevChunkZ != newChunkZ)) { + PlayerChunkMap chunkMap = this.getChunkProvider().playerChunkMap; + if (chunkMap.playerEntityTrackerTrackMap != null) { + if (PlayerChunkMap.isLegacyTrackingEntity(entity)) { + if (chunkMap.playerEntityTrackerLegacyMap != null) { + if (chunkMap.playerEntityTrackerLegacyMap.getObjectsInRange(newChunkX, newChunkZ) != null) { + chunkMap.activelyTrackedEntitiesLegacy.add(entity); // ensure tracked + // untracking is handled in tracker tick + } + } + } else { + // handle tracking + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldRange = + chunkMap.playerEntityTrackerTrackMap.getObjectsInRange(prevChunkX, prevChunkZ); + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newRange = + chunkMap.playerEntityTrackerTrackMap.getObjectsInRange(newChunkX, newChunkZ); + + if (oldRange != newRange) { // optimization: equal pooled sets are identity equal + if (newRange != null) { + if (oldRange == null) { + Object[] backingSet = newRange.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) temp; + entity.addToTrackQueue(player); + } + } else { + Object[] backingSet = newRange.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) temp; + if (oldRange.contains(player)) { + continue; + } + entity.addToTrackQueue(player); + } + } + } + } + + // handle untracking + oldRange = chunkMap.playerEntityTrackerUntrackMap.getObjectsInRange(prevChunkX, prevChunkZ); + newRange = chunkMap.playerEntityTrackerUntrackMap.getObjectsInRange(newChunkX, newChunkZ); + if (oldRange != newRange) { // optimization: equal pooled sets are identity equal + if (oldRange != null) { + if (newRange == null) { + Object[] backingSet = oldRange.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) temp; + entity.addToUntrackQueue(player); + } + } else { + Object[] backingSet = oldRange.getBackingSet(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + Object temp = backingSet[index]; + if (!(temp instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) temp; + if (newRange.contains(player)) { + continue; + } + entity.addToUntrackQueue(player); + } + } + } + } + } + } + } + // Tuinity end - optimized tracker } this.getMethodProfiler().exit(); @@ -1178,6 +1634,7 @@ public class WorldServer extends World { this.registerEntity(entityplayer); this.getChunkProvider().playerChunkMap.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps + this.getChunkProvider().playerChunkMap.addPlayerToDistanceMapsTuinity(entityplayer); // Tuinity - distance maps } // CraftBukkit start @@ -1360,6 +1817,7 @@ public class WorldServer extends World { this.players.remove(entityplayer); this.getChunkProvider().playerChunkMap.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps + this.getChunkProvider().playerChunkMap.removePlayerFromDistanceMapsTuinity(entityplayer); // Tuinity - distance maps } this.getScoreboard().a(entity); @@ -1372,6 +1830,7 @@ public class WorldServer extends World { if (entity instanceof EntityInsentient) { this.navigators.remove(((EntityInsentient) entity).getNavigation()); } + this.loadedEntities.remove(entity); // Tuinity - loaded entity list new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid entity.valid = false; // CraftBukkit } @@ -1428,6 +1887,11 @@ public class WorldServer extends World { } // Paper end entity.shouldBeRemoved = false; // Paper - shouldn't be removed after being re-added + // Tuinity start - loaded entity list + if (this.isChunkLoaded(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ()))) { + this.loadedEntities.add(entity); + } + // Tuinity end - loaded entity list new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid } diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java index 3030c347e..76f0f258e 100644 --- a/src/main/java/net/minecraft/server/WorldUpgrader.java +++ b/src/main/java/net/minecraft/server/WorldUpgrader.java @@ -220,7 +220,7 @@ public class WorldUpgrader { int l = Integer.parseInt(matcher.group(2)) << 5; try { - RegionFile regionfile = new RegionFile(file2, file1); + RegionFile regionfile = new RegionFile(file2, file1, true); // Tuinity - allow for chunk regionfiles to regen header Throwable throwable = null; try { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index fc074a66c..23c3a69fa 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -796,6 +796,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper + com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config for (WorldServer world : console.getWorlds()) { world.worldData.setDifficulty(config.difficulty); world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); @@ -812,6 +813,7 @@ public final class CraftServer implements Server { } world.spigotConfig.init(); // Spigot world.paperConfig.init(); // Paper + world.tuinityConfig.init(); // Tuinity - Server Config } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper @@ -1683,7 +1685,10 @@ public final class CraftServer implements Server { @Override public boolean isPrimaryThread() { - return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly + // Tuinity start + final Thread currThread = Thread.currentThread(); + return currThread == console.serverThread || currThread instanceof com.tuinity.tuinity.util.TickThread; + // Tuinity End } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index a14b8cb98..7aed24c8f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -3,6 +3,7 @@ package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.tuinity.tuinity.util.TickThread; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import java.io.File; import java.io.IOException; @@ -287,9 +288,9 @@ public class CraftWorld implements World { } public int getTileEntityCount() { // We don't use the full world tile entity list, so we must iterate chunks - Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; + Iterable chunks = world.getChunkProvider().playerChunkMap.chunkMap.getVisibleValues(); // Tuinity - replace chunk map int size = 0; - for (net.minecraft.server.PlayerChunk playerchunk : chunks.values()) { + for (net.minecraft.server.PlayerChunk playerchunk : chunks) { // Tuinity - replace chunk map net.minecraft.server.Chunk chunk = playerchunk.getChunk(); if (chunk == null) { continue; @@ -304,7 +305,7 @@ public class CraftWorld implements World { public int getChunkCount() { int ret = 0; - for (PlayerChunk chunkHolder : world.getChunkProvider().playerChunkMap.visibleChunks.values()) { + for (PlayerChunk chunkHolder : world.getChunkProvider().playerChunkMap.chunkMap.getVisibleValues()) { // Tuinity - replace chunk map if (chunkHolder.getChunk() != null) { ++ret; } @@ -428,8 +429,8 @@ public class CraftWorld implements World { @Override public Chunk[] getLoadedChunks() { - Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; - return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.server.Chunk::getBukkitChunk).toArray(Chunk[]::new); + Collection chunks = world.getChunkProvider().playerChunkMap.chunkMap.getVisibleValues(); // Tuinity - replace chunk map + return chunks.stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.server.Chunk::getBukkitChunk).toArray(Chunk[]::new); // Tuinity - replace chunk map } @Override @@ -458,6 +459,7 @@ public class CraftWorld implements World { net.minecraft.server.IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, false); if (chunk != null) { world.getChunkProvider().removeTicket(TicketType.PLUGIN, chunk.getPos(), 1, Unit.INSTANCE); + ((ChunkMapDistance)world.getChunkProvider().playerChunkMap.getChunkMapDistanceManager()).removeTickets(ChunkCoordIntPair.pair(x, z), TicketType.DELAYED_UNLOAD); // Tuinity - delay chunk unloads - let plugins override } return true; @@ -1090,16 +1092,16 @@ public class CraftWorld implements World { @Override public List getEntities() { - List list = new ArrayList(); + List list = new ArrayList(world.loadedEntities.size()); // Tuinity - optimize this call - for (Object o : world.entitiesById.values()) { + for (Object o : world.loadedEntities) { // Tuinity - optimize this call if (o instanceof net.minecraft.server.Entity) { net.minecraft.server.Entity mcEnt = (net.minecraft.server.Entity) o; if (mcEnt.shouldBeRemoved) continue; // Paper Entity bukkitEntity = mcEnt.getBukkitEntity(); // Assuming that bukkitEntity isn't null - if (bukkitEntity != null && bukkitEntity.isValid()) { + if (bukkitEntity != null && CraftEntity.canBeSeenByPlugins(bukkitEntity)) { // Tuinity - optimize this call list.add(bukkitEntity); } } @@ -1110,16 +1112,16 @@ public class CraftWorld implements World { @Override public List getLivingEntities() { - List list = new ArrayList(); + List list = new ArrayList(world.loadedEntities.size()); // Tuinity - optimize this call - for (Object o : world.entitiesById.values()) { + for (Object o : world.loadedEntities) { // Tuinity - optimize this call if (o instanceof net.minecraft.server.Entity) { net.minecraft.server.Entity mcEnt = (net.minecraft.server.Entity) o; if (mcEnt.shouldBeRemoved) continue; // Paper Entity bukkitEntity = mcEnt.getBukkitEntity(); // Assuming that bukkitEntity isn't null - if (bukkitEntity != null && bukkitEntity instanceof LivingEntity && bukkitEntity.isValid()) { + if (bukkitEntity != null && bukkitEntity instanceof LivingEntity && CraftEntity.canBeSeenByPlugins(bukkitEntity)) { // Tuinity - optimize this call list.add((LivingEntity) bukkitEntity); } } @@ -1140,7 +1142,7 @@ public class CraftWorld implements World { public Collection getEntitiesByClass(Class clazz) { Collection list = new ArrayList(); - for (Object entity: world.entitiesById.values()) { + for (Object entity: world.loadedEntities) { // Tuinity - optimize this call if (entity instanceof net.minecraft.server.Entity) { if (((net.minecraft.server.Entity) entity).shouldBeRemoved) continue; // Paper Entity bukkitEntity = ((net.minecraft.server.Entity) entity).getBukkitEntity(); @@ -1151,7 +1153,7 @@ public class CraftWorld implements World { Class bukkitClass = bukkitEntity.getClass(); - if (clazz.isAssignableFrom(bukkitClass) && bukkitEntity.isValid()) { + if (clazz.isAssignableFrom(bukkitClass) && CraftEntity.canBeSeenByPlugins(bukkitEntity)) { // Tuinity - optimize this call list.add((T) bukkitEntity); } } @@ -1164,7 +1166,7 @@ public class CraftWorld implements World { public Collection getEntitiesByClasses(Class... classes) { Collection list = new ArrayList(); - for (Object entity: world.entitiesById.values()) { + for (Object entity: world.loadedEntities) { // Tuinity - optimize this call if (entity instanceof net.minecraft.server.Entity) { if (((net.minecraft.server.Entity) entity).shouldBeRemoved) continue; // Paper Entity bukkitEntity = ((net.minecraft.server.Entity) entity).getBukkitEntity(); @@ -1177,7 +1179,7 @@ public class CraftWorld implements World { for (Class clazz : classes) { if (clazz.isAssignableFrom(bukkitClass)) { - if (bukkitEntity.isValid()) { + if (CraftEntity.canBeSeenByPlugins(bukkitEntity)) { // Tuinity - optimize this call list.add(bukkitEntity); } break; @@ -2434,10 +2436,43 @@ public class CraftWorld implements World { // Spigot start @Override public int getViewDistance() { - return world.spigotConfig.viewDistance; + return getHandle().getChunkProvider().playerChunkMap.getViewDistance(); // Tuinity start - per player view distance } // Spigot end + // Tuinity start - per player view distance + + + @Override + public void setViewDistance(int viewDistance) { + TickThread.ensureTickThread("Cannot update view distance safely off of the main thread"); + if (viewDistance < 2 || viewDistance > 32) { + throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); + } + net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap; + if (viewDistance != chunkMap.getViewDistance()) { + chunkMap.setViewDistance(viewDistance); + } + } + + @Override + public int getNoTickViewDistance() { + return getHandle().getChunkProvider().playerChunkMap.getNoTickViewDistance(); + } + + @Override + public void setNoTickViewDistance(int viewDistance) { + TickThread.ensureTickThread("Cannot update view distance safely off of the main thread"); + if (viewDistance < 2 || viewDistance > 32) { + throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); + } + net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap; + if (viewDistance != chunkMap.getNoTickViewDistance()) { + chunkMap.setNoTickViewDistance(viewDistance); + } + } + // Tuinity end - per player view distance + // Spigot start private final Spigot spigot = new Spigot() { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index cb60310e6..2666940e8 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -137,6 +137,13 @@ public class Main { .defaultsTo(new File("paper.yml")) .describedAs("Yml file"); // Paper end + // Tuinity Start - Server Config + acceptsAll(asList("tuinity", "tuinity-settings"), "File for tuinity settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("tuinity.yml")) + .describedAs("Yml file"); + /* Conctete End - Server Config */ // Paper start acceptsAll(asList("server-name"), "Name of the server") diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index dfa15372b..949acb1d3 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -180,6 +180,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { this.entity = entity; } + // Tuinity start + // note: this does not check isChunkLoaded, use Entity#isValid to do that + public static boolean canBeSeenByPlugins(org.bukkit.entity.Entity entity) { + Entity handle = ((CraftEntity)entity).getHandle(); + // TODO + // isAlive is a dumb choice, given living entities aren't alive (but are in the world) if health < 0 + // this needs to be brought up to spigot to fix though, we are NOT breaking api implementation, especially + // if no-one's complained. + return !handle.shouldBeRemoved && handle.isAlive() && handle.valid; + } + // Tuinity end + @Override public Chunk getChunk() { net.minecraft.server.Chunk currentChunk = entity.getCurrentChunk(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 60f62f19c..516a01648 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import com.tuinity.tuinity.util.TickThread; // Tuinity import com.destroystokyo.paper.Title; import com.destroystokyo.paper.profile.CraftPlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile; @@ -1274,7 +1275,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { PlayerChunkMap.EntityTracker entry = tracker.trackedEntities.get(other.getId()); if (entry != null && !entry.trackedPlayers.contains(getHandle())) { + if (getHandle().getWorldServer().getChunkProvider().playerChunkMap.playerEntityTrackerTrackMap == null) { // Tuinity - optimized tracker entry.updatePlayer(getHandle()); + // Tuinity start - optimized tracker + } else { + other.addToTrackQueue(other); + } + // Tuinity end - optimized tracker } } // Paper start @@ -1997,13 +2004,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public int getViewDistance() { - throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement"); // TODO + return getHandle().getEffectiveViewDistance(); // Tuinity - per player view distance } @Override public void setViewDistance(int viewDistance) { - throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement"); // TODO + // Tuinity start - per player view distance + TickThread.ensureTickThread("Cannot update view distance safely off of the main thread"); + if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) { + throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); + } + if (viewDistance != getHandle().getRawViewDistance()) { + ((WorldServer)getHandle().world).getChunkProvider().playerChunkMap.updateViewDistance(getHandle(), viewDistance, getHandle().getRawNoTickViewDistance()); + } + // Tuinity end - per player view distance + } + + // Tuinity start - per player view distance + @Override + public int getNoTickViewDistance() { + return getHandle().getEffectiveNoTickViewDistance(); + } + + @Override + public void setNoTickViewDistance(int viewDistance) { + TickThread.ensureTickThread("Cannot update view distance safely off of the main thread"); + if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) { + throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); + } + if (viewDistance != getHandle().getRawNoTickViewDistance()) { + ((WorldServer)getHandle().world).getChunkProvider().playerChunkMap.updateViewDistance(getHandle(), getHandle().getRawViewDistance(), viewDistance); + } } + // Tuinity end - per player view distance // Paper end // Spigot start diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java index 6e165a164..e160f03c1 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.List; import net.minecraft.server.AxisAlignedBB; import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkProviderServer; // Tuinity import net.minecraft.server.Entity; import net.minecraft.server.EntityAmbient; import net.minecraft.server.EntityAnimal; @@ -38,6 +39,7 @@ import net.minecraft.server.EntityInsentient; import net.minecraft.server.EntityLlama; import net.minecraft.server.EntityWaterAnimal; // Paper end +import net.minecraft.server.WorldServer; // Tuinity public class ActivationRange { @@ -128,14 +130,17 @@ public class ActivationRange final int monsterActivationRange = world.spigotConfig.monsterActivationRange; final int waterActivationRange = world.spigotConfig.waterActivationRange; // Paper - int maxRange = Math.max( monsterActivationRange, animalActivationRange ); - maxRange = Math.max( maxRange, raiderActivationRange ); - maxRange = Math.max( maxRange, miscActivationRange ); - maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); + // Tuinity start - per player view distance + int maxRangeTemp = Math.max( monsterActivationRange, animalActivationRange ); + maxRangeTemp = Math.max( maxRangeTemp, raiderActivationRange ); + maxRangeTemp = Math.max( maxRangeTemp, miscActivationRange ); + + ChunkProviderServer chunkProviderServer = (ChunkProviderServer)world.getChunkProvider(); for ( EntityHuman player : world.getPlayers() ) { - + final int maxRange = Math.min( ( ( player instanceof net.minecraft.server.EntityPlayer ? ((net.minecraft.server.EntityPlayer)player).getEffectiveViewDistance(((WorldServer)world).getChunkProvider().playerChunkMap) : world.spigotConfig.viewDistance ) << 4 ) - 8, maxRangeTemp ); + // Tuinity end - per player view distance player.activatedTick = MinecraftServer.currentTick; maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange ); ActivationType.MISC.boundingBox = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange ); @@ -154,7 +159,7 @@ public class ActivationRange { for ( int j1 = k; j1 <= l; ++j1 ) { - Chunk chunk = (Chunk) world.getChunkIfLoadedImmediately( i1, j1 ); + Chunk chunk = chunkProviderServer.getChunkAtIfLoadedMainThreadNoCache( i1, j1 ); // Tuinity if ( chunk != null ) { activateChunkEntities( chunk ); @@ -172,24 +177,20 @@ public class ActivationRange */ private static void activateChunkEntities(Chunk chunk) { - for ( List slice : chunk.entitySlices ) - { - for ( Entity entity : (Collection) slice ) + // Tuinity start - optimise this call + com.destroystokyo.paper.util.maplist.EntityList entityList = chunk.entities; + Entity[] rawData = entityList.getRawData(); + for (int i = 0, len = entityList.size(); i < len; ++i) { + Entity entity = rawData[i]; + if ( MinecraftServer.currentTick > entity.activatedTick ) { - if ( MinecraftServer.currentTick > entity.activatedTick ) + if ( entity.defaultActivationState || entity.activationType.boundingBox.intersects(entity.getBoundingBox())) // Tuinity - optimise this call { - if ( entity.defaultActivationState ) - { - entity.activatedTick = MinecraftServer.currentTick; - continue; - } - if ( entity.activationType.boundingBox.c( entity.getBoundingBox() ) ) - { - entity.activatedTick = MinecraftServer.currentTick; - } - } + entity.activatedTick = MinecraftServer.currentTick; + } // Tuinity - optimise this call } } + // Tuinity end - optimise this call } /** diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java index 9f7d2ef93..c3ac1a46c 100644 --- a/src/main/java/org/spigotmc/AsyncCatcher.java +++ b/src/main/java/org/spigotmc/AsyncCatcher.java @@ -10,7 +10,7 @@ public class AsyncCatcher public static void catchOp(String reason) { - if ( enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) + if ( ( enabled || com.tuinity.tuinity.util.TickThread.STRICT_THREAD_CHECKS ) && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity { throw new IllegalStateException( "Asynchronous " + reason + "!" ); } -- 2.24.0