From 846acefc7a33c752d574f2ca0876f917afda04ba 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 +- .../co/aikar/timings/WorldTimingsHandler.java | 2 + .../com/destroystokyo/paper/PaperCommand.java | 2 +- .../paper/PaperVersionFetcher.java | 11 +- .../destroystokyo/paper/PaperWorldConfig.java | 3 +- .../paper/io/PrioritizedTaskQueue.java | 20 +- .../paper/server/ticklist/PaperTickList.java | 8 + .../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 | 305 +++++ .../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 + .../net/minecraft/server/ArraySetSorted.java | 41 +- .../net/minecraft/server/AxisAlignedBB.java | 2 + src/main/java/net/minecraft/server/Chunk.java | 135 +++ .../java/net/minecraft/server/ChunkMap.java | 15 +- .../minecraft/server/ChunkMapDistance.java | 401 ++++++- .../minecraft/server/ChunkProviderServer.java | 137 ++- .../minecraft/server/ChunkRegionLoader.java | 12 +- .../net/minecraft/server/ChunkStatus.java | 4 +- .../net/minecraft/server/DedicatedServer.java | 3 +- .../minecraft/server/DoubleListOffset.java | 2 +- src/main/java/net/minecraft/server/EULA.java | 2 +- .../java/net/minecraft/server/Entity.java | 137 +++ .../minecraft/server/EntityEnderDragon.java | 4 +- .../minecraft/server/EntityInsentient.java | 19 +- .../net/minecraft/server/EntityPlayer.java | 43 +- .../minecraft/server/EntityTrackerEntry.java | 24 +- .../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 | 36 +- .../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 | 94 +- .../net/minecraft/server/PlayerChunkMap.java | 1043 +++++++++++++++-- .../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 | 468 +++++++- .../minecraft/server/RegionFileBitSet.java | 26 +- .../net/minecraft/server/RegionFileCache.java | 45 +- .../server/RegionFileCompression.java | 7 +- .../net/minecraft/server/ThreadedMailbox.java | 2 +- .../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 | 461 +++++++- .../net/minecraft/server/WorldUpgrader.java | 2 +- .../org/bukkit/craftbukkit/CraftServer.java | 15 +- .../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 +- src/main/java/org/spigotmc/TrackingRange.java | 40 + 95 files changed, 6360 insertions(+), 482 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/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 diff --git a/pom.xml b/pom.xml index a7ee9396a0..90ca354c46 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 @@ -133,6 +133,12 @@ 1.3 test + + + ca.spottedleaf.concurrentutil + concurrentutil + 1.0.0-SNAPSHOT + @@ -296,6 +302,10 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 + + + true + diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java index c9a3ba4bfb..af24eb0bcf 100644 --- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java +++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java @@ -34,6 +34,7 @@ public class WorldTimingsHandler { public final Timing tileEntityPending; public final Timing tracker1; public final Timing tracker2; + public final Timing tracker3; // Tuinity - legacy tracker public final Timing doTick; public final Timing tickEntities; public final Timing chunks; @@ -118,6 +119,7 @@ public class WorldTimingsHandler { tracker1 = Timings.ofSafe(name + "tracker stage 1"); tracker2 = Timings.ofSafe(name + "tracker stage 2"); + tracker3 = Timings.ofSafe(name + "tracker stage 3"); // Tuinity - legacy tracker doTick = Timings.ofSafe(name + "doTick"); tickEntities = Timings.ofSafe(name + "tickEntities"); diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java index dfe92780ad..c088cf51ff 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/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java index 49a38c6608..255bbd6e48 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -24,8 +24,8 @@ public class PaperVersionFetcher implements VersionFetcher { @Nonnull @Override public String getVersionMessage(@Nonnull String serverVersion) { - String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); - String updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); + String[] parts = serverVersion.substring("git-Tuinity-".length()).split("[-\\s]"); // Tuinity + String updateMessage = getUpdateStatusMessage("Spottedleaf/Tuinity", GITHUB_BRANCH_NAME, parts[0]); // Tuinity String history = getHistory(); return history != null ? history + "\n" + updateMessage : updateMessage; @@ -49,13 +49,10 @@ public class PaperVersionFetcher implements VersionFetcher { private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { int distance; - try { - int jenkinsBuild = Integer.parseInt(versionInfo); - distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); - } catch (NumberFormatException ignored) { + // Tuinity - we don't have jenkins setup versionInfo = versionInfo.replace("\"", ""); distance = fetchDistanceFromGitHub(repo, branch, versionInfo); - } + // Tuinity - we don't have jenkins setup switch (distance) { case -1: diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index 7ca67a4aa5..e76d5fd8df 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 78bd238f4c..8a78932688 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/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java index e948012d5b..4ace0d8d78 100644 --- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java @@ -189,6 +189,7 @@ public final class PaperTickList extends TickListServer { // extend to avo } public void onChunkSetTicking(final int chunkX, final int chunkZ) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list chunk ticking update"); // Tuinity - soft async catcher final ArrayList> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ)); if (pending == null) { return; @@ -269,6 +270,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public void tick() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); this.world.getMethodProfiler().enter("cleaning"); @@ -409,6 +411,7 @@ public final class PaperTickList extends TickListServer { // extend to avo } public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list schedule"); // Tuinity - soft async catcher final NextTickListEntry entry = new NextTickListEntry<>(pos, data, targetTick, priority); if (this.excludeFromScheduling.test(entry.getData())) { return; @@ -468,6 +471,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public List> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get in bounding box"); // Tuinity - soft async catcher if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above } @@ -524,6 +528,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list copy"); // Tuinity - soft async catcher // start copy from TickListServer // TODO check on update List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); Iterator> iterator = list.iterator(); @@ -543,6 +548,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public List> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get"); // Tuinity - soft async catcher // 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 @@ -574,6 +580,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list serialize"); // Tuinity - soft async catcher // start copy from TickListServer // TODO check on update List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); @@ -583,6 +590,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public int getTotalScheduledEntries() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get size"); // Tuinity - soft async catcher // good thing this is only used in debug reports // TODO check on update int ret = 0; diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java index a90adac7bd..2e7cbf8bf5 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 0000000000..ff17a100ba --- /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 0000000000..d528d08ea5 --- /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 0000000000..e5bb56cca9 --- /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 0000000000..78c011309a --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java @@ -0,0 +1,305 @@ +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.List; +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 = 1; + + private static final Object[] EMPTY = new Object[0]; + + private static File configFile; + public 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); + TuinityConfig.set("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); + } + } + + static void set(final String path, final Object value) { + TuinityConfig.config.set(path, value); + } + + static boolean getBoolean(final String path, final boolean dfl) { + TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl)); + return TuinityConfig.config.getBoolean(path, dfl); + } + + static int getInt(final String path, final int dfl) { + TuinityConfig.config.addDefault(path, Integer.valueOf(dfl)); + return TuinityConfig.config.getInt(path, dfl); + } + + static long getLong(final String path, final long dfl) { + TuinityConfig.config.addDefault(path, Long.valueOf(dfl)); + return TuinityConfig.config.getLong(path, dfl); + } + + 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 double maxChunkSendsPerPlayer; // per second + + public static int[] maxChunkSendsPerPlayerChoice = new int[100]; + + private static void maxChunkLoadsPerPlayer() { + if (TuinityConfig.configVersion < 1) { + TuinityConfig.set("max-pending-chunk-tickets-per-player", null); + } + maxChunkSendsPerPlayer = TuinityConfig.getDouble("target-chunk-sends-per-player-per-second", 40.0); + if (maxChunkSendsPerPlayer <= -1.0) { + maxChunkSendsPerPlayer = Integer.MAX_VALUE; + } else if (maxChunkSendsPerPlayer <= 1.0) { + maxChunkSendsPerPlayer = 1.0; + } else if (maxChunkSendsPerPlayer > Integer.MAX_VALUE) { + maxChunkSendsPerPlayer = Integer.MAX_VALUE; + } + + double rateTick = maxChunkSendsPerPlayer / 20.0; + double a = Math.floor(rateTick); + double b = Math.ceil(rateTick); + + // we want to spread out a and b over the interval so it's smooth + + int aInt = (int)a; + int bInt = (int)b; + double total = b; + maxChunkSendsPerPlayerChoice[0] = bInt; + + for (int i = 1, len = maxChunkSendsPerPlayerChoice.length; i < len; ++i) { + if (total / (double)i >= rateTick) { + total += a; + maxChunkSendsPerPlayerChoice[i] = aInt; + } else { + total += b; + maxChunkSendsPerPlayerChoice[i] = bInt; + } + } + } + + 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 final class WorldConfig { + + public final String worldName; + public ConfigurationSection config; + ConfigurationSection worldDefaults; + + public WorldConfig(final String worldName) { + this.worldName = worldName; + this.init(); + } + + public void init() { + this.worldDefaults = TuinityConfig.config.getConfigurationSection("world-settings.default"); + if (this.worldDefaults == null) { + this.worldDefaults = TuinityConfig.config.createSection("world-settings.default"); + } + + String worldSectionPath = TuinityConfig.configVersion < 1 ? this.worldName : "world-settings.".concat(this.worldName); + ConfigurationSection section = TuinityConfig.config.getConfigurationSection(worldSectionPath); + if (section == null) { + section = TuinityConfig.config.createSection(worldSectionPath); + } + TuinityConfig.config.set(worldSectionPath, section); + + this.load(section); + } + + public void load(final ConfigurationSection config) { + this.config = config; + + for (final Method method : TuinityConfig.WorldConfig.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 + } + } + + if (TuinityConfig.configVersion < 1) { + ConfigurationSection oldSection = TuinityConfig.config.getConfigurationSection(this.worldName); + TuinityConfig.config.set("world-settings.".concat(this.worldName), oldSection); + TuinityConfig.config.set(this.worldName, null); + } + + /* 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); + } + } + + /** + * update world defaults for the specified path, but also sets this world's config value for the path + * if it exists + */ + void set(final String path, final Object val) { + this.worldDefaults.set(path, val); + if (this.config.get(path) != null) { + this.config.set(path, val); + } + } + + boolean getBoolean(final String path, final boolean dfl) { + this.worldDefaults.addDefault(path, Boolean.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getBoolean(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getBoolean(path, this.worldDefaults.getBoolean(path)); + } + + int getInt(final String path, final int dfl) { + this.worldDefaults.addDefault(path, Integer.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getInt(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getInt(path, this.worldDefaults.getInt(path)); + } + + long getLong(final String path, final long dfl) { + this.worldDefaults.addDefault(path, Long.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getLong(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getLong(path, this.worldDefaults.getLong(path)); + } + + double getDouble(final String path, final double dfl) { + this.worldDefaults.addDefault(path, Double.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getDouble(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getDouble(path, this.worldDefaults.getDouble(path)); + } + + /** 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; + + private void optimizetracker() { + if (TuinityConfig.configVersion < 1) { + this.set("optimized-tracker-track-range", null); + this.set("optimized-tracker-untrack-range", null); + } + this.useOptimizedTracker = this.getBoolean("optimized-tracker", true); + } + } + +} \ No newline at end of file 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 0000000000..66c64be8f3 --- /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 0000000000..570c44e26a --- /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 0000000000..ce15ce532b --- /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 0000000000..934792ad39 --- /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 0000000000..de930aef9b --- /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 0000000000..0249c78c1a --- /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 0000000000..37adae9007 --- /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 0000000000..d5688a734e --- /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 0000000000..92f43870fb --- /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 ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final Entity entity) { + return ((long)(Util.fastFloor(entity.locZ()) >> 4) << 32) | ((Util.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL); + } + + 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 ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL); + } + + 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 0000000000..88b198fd7b --- /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 0000000000..36457981ee --- /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 0000000000..1a3f596330 --- /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 0000000000..a350e4204d --- /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.areaMap.get(Util.getCoordinateKey(chunkPos)); + } + + @Nullable + public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { + return this.areaMap.get(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 0000000000..a29fdcbbcb --- /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 0000000000..cf888454c1 --- /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 0000000000..815974682a --- /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/net/minecraft/server/ArraySetSorted.java b/src/main/java/net/minecraft/server/ArraySetSorted.java index 85f799a713..5fa6f75e2e 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 c950139c0f..1a3234bb47 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/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 07073af991..8b8e1d900e 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 |= (1L << 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 |= (1L << 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 |= (1L << 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(); @@ -411,6 +499,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); @@ -480,6 +569,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; } @@ -512,6 +602,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); @@ -653,6 +749,25 @@ 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.setNeighbourLoaded(0, 0, this); + 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) { @@ -696,6 +811,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 @@ -704,6 +836,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); @@ -743,6 +876,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); @@ -765,6 +899,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 55f9f4e6e7..d3c616e72d 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 73d1570765..6826c0369c 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,110 @@ 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; + } + }; + + // this is copied from ChunkMapDistance.a(long, int, boolean, boolean), TODO check on update + // this is invoked if and only if there are no other players in range of the chunk. + public void playerMoveInRange(final int chunkX, final int chunkZ, final int fromX, final int fromZ) { + final long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + + final int dist = Math.max(Math.abs(chunkX - fromX), Math.abs(chunkZ - fromZ)); + Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(chunkX, chunkZ)); + + ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { + if (ChunkMapDistance.this.chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(coordinate) != null) { + ChunkMapDistance.this.addTicket(coordinate, ticket); + ChunkMapDistance.this.l.add(coordinate); + } else { + ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + }, coordinate, false)); + } + }); + }, coordinate, () -> { + return dist; + })); + } + + // this is copied from ChunkMapDistance.a(long, int, boolean, boolean), TODO check on update + // this is invoked if and only if there are no other players in range of the chunk. + public void playerMoveOutOfRange(final int chunkX, final int chunkZ, final int fromX, final int fromZ) { + final long coordinate = Util.getCoordinateKey(chunkX, chunkZ); + + Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(chunkX, chunkZ)); + + ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { + ChunkMapDistance.this.removeTicket(coordinate, ticket); + }); + }, coordinate, true)); + } + // 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 +158,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 +196,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 +210,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 +261,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 +275,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 +329,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 +347,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 +384,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 +415,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 +474,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 +707,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 +788,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 1dcd0980ec..c0264e130f 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -520,27 +520,39 @@ public class ChunkProviderServer extends IChunkProvider { public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER @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 } public final boolean isEntityTickingChunk(ChunkCoordIntPair chunkcoordintpair) { return this.a(chunkcoordintpair); } // Paper - OBFHELPER @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); + // 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) { @@ -604,6 +616,10 @@ public class ChunkProviderServer extends IChunkProvider { this.chunkMapDistance.purgeTickets(); this.tickDistanceManager(); this.world.timings.doChunkMap.stopTiming(); // Spigot + // Tuinity start + this.playerChunkMap.getChunkMapDistanceManager().playerTickViewDistanceHandler.tick(); + this.playerChunkMap.chunkSendThrottler.tick(); + // Tuinity end this.world.getMethodProfiler().exitEnter("chunks"); this.world.timings.chunks.startTiming(); // Paper - timings this.tickChunks(); @@ -616,6 +632,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; @@ -626,6 +648,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(); @@ -643,11 +695,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); @@ -660,20 +711,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 @@ -682,10 +726,10 @@ public class ChunkProviderServer extends IChunkProvider { this.world.getMethodProfiler().exit(); ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); - 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; @@ -730,9 +774,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; } @@ -754,7 +812,22 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.chunkTicks.stopTiming(); // Spigot // 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 4349d22cc8..d529b795c5 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 88f1674616..25654520e7 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; } diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java index 349a0ea213..ede4369399 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 73657f7407..9ff09b02d6 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/EULA.java b/src/main/java/net/minecraft/server/EULA.java index cf00f35a5b..e54730f097 100644 --- a/src/main/java/net/minecraft/server/EULA.java +++ b/src/main/java/net/minecraft/server/EULA.java @@ -70,7 +70,7 @@ public class EULA { Properties properties = new Properties(); properties.setProperty("eula", "false"); - properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; + properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting } catch (Throwable throwable1) { throwable = throwable1; throw throwable1; diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 8974c16bf9..223ad3965c 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -208,6 +208,142 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // CraftBukkit end + // Tuinity start + boolean isLegacyTrackingEntity = this instanceof EntityPlayer; // TODO temp, fix citizens... + + private com.tuinity.tuinity.util.map.PlayerAreaMap trackingAreaMap; + private com.tuinity.tuinity.util.map.PlayerAreaMap unTrackingAreaMap; + + final com.tuinity.tuinity.util.map.PlayerAreaMap getTrackingAreaMap() { + if (this.trackingAreaMap == null) { + // stupid plugins incorrectly adding entities into the world. + if (this.world == null) { + throw new IllegalStateException("Entity " + this + " has incorrectly been added to the world."); + } + this.acquireTrackingMap(((WorldServer)this.world).getChunkProvider().playerChunkMap); + } + return this.trackingAreaMap; + } + + final com.tuinity.tuinity.util.map.PlayerAreaMap getUnTrackingAreaMap() { + if (this.unTrackingAreaMap == null) { + // stupid plugins incorrectly adding entities into the world. + if (this.world == null) { + throw new IllegalStateException("Entity " + this + " has incorrectly been added to the world."); + } + this.acquireTrackingMap(((WorldServer)this.world).getChunkProvider().playerChunkMap); + } + return this.unTrackingAreaMap; + } + + final void acquireTrackingMap(PlayerChunkMap chunkMap) { + int key = org.spigotmc.TrackingRange.getTrackingRangeType(this).ordinal(); + this.trackingAreaMap = chunkMap.playerEntityTrackerTrackMaps[key]; + this.unTrackingAreaMap = chunkMap.playerEntityTrackerUntrackMaps[key]; + } + + public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { + if (this.isLegacyTrackingEntity == isLegacyTrackingEntity) { + return; + } + + if (this.world == null) { + this.isLegacyTrackingEntity = isLegacyTrackingEntity; + return; + } + + WorldServer world = (WorldServer)this.world; + PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; + + if (!chunkMap.optimisedTrackerEnabled) { + this.isLegacyTrackingEntity = isLegacyTrackingEntity; + return; + } + com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot update legacy tracking off of the main thread"); + + if (this.isLegacyTrackingEntity) { + this.isLegacyTrackingEntity = false; + chunkMap.activelyTrackedEntitiesLegacy.remove(this); + + PlayerChunkMap.EntityTracker tracker = this.tracker; + if (tracker != null) { + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.getTrackingAreaMap().getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(this)); + + for (EntityPlayer player : world.getPlayers()) { + tracker.clear(player); + if (inRange != null && inRange.contains(player)) { + this.addToTrackQueue(player); + } + } + } + } else { + this.isLegacyTrackingEntity = true; + chunkMap.activelyTrackedEntitiesLegacy.add(this); + + PlayerChunkMap.EntityTracker tracker = chunkMap.trackedEntities.get(this.getId()); + if (tracker != null) { + for (EntityPlayer player : world.getPlayers()) { + this.clearTrackingQueues(player); + tracker.clear(player); + tracker.updatePlayer(player); + } + } + } + } + + // 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; + } + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Adding to track queue from off-main thread"); + int id = player.getId(); + this.unTrackQueue.remove(id); + this.trackQueue.add(id); + ((WorldServer)this.world).trackingUpdateQueue.add(this); + } + + public final void addToUntrackQueue(EntityPlayer player) { + if (player == this) { + return; + } + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Removing from track queue from off-main thread"); + int id = player.getId(); + this.trackQueue.remove(id); + // don't queue untrack if we're not tracked + if (this.tracker == null || !this.tracker.trackedPlayers.contains(player)) { + return; + } + 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 +1507,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 af10fc36e0..bf14d33c0d 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 1991cee43d..27ef476001 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 e7bfbc3307..d49c45ce7f 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -104,6 +104,39 @@ 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 + final it.unimi.dsi.fastutil.longs.LongOpenHashSet loadedChunks = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(); + + 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 +155,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 +1809,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 3a88c9a670..0323055c68 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,13 @@ public class EntityTrackerEntry { } + 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; diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java index 8977c3516b..bace6cf36a 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 29cb545a86..aa73396100 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 cfe43e882e..e7a58989dd 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 4157e50e4d..c522a7c2a7 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 93a972605c..43424c88ab 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 f72ff8495b..f27a148731 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 2301a982e1..488d4f289f 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 a3f919816e..dacc96414f 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 b978723a66..5e2051ee1a 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 75d9065b32..4669f31b04 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 b9d5844520..d1412760dd 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -500,9 +500,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; @@ -525,7 +525,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); @@ -603,4 +603,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 b31a9ac78c..af5dd9f2d3 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 98deaba12c..fcc3b7c36b 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 f06764973f..4393ea6dc3 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 96a785af27..77aa911c98 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 @@ -184,42 +184,54 @@ public class NetworkManager extends SimpleChannelInboundHandler> { this.channel.config().setAutoRead(false); } + java.util.List extraPackets = packet.getExtraPackets(); // Tuinity - make only one flush call for writing packets + if (this.channel.eventLoop().inEventLoop()) { if (enumprotocol != enumprotocol1) { this.setProtocol(enumprotocol); } - ChannelFuture channelfuture = this.channel.writeAndFlush(packet); + ChannelFuture channelfuture = (extraPackets == null || extraPackets.isEmpty()) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - make only one flush call for writing packets if (genericfuturelistener != null) { channelfuture.addListener(genericfuturelistener); } channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + // Tuinity start - make only one flush call for writing packets + if (extraPackets != null && !extraPackets.isEmpty()) { + for (Packet extraPacket : extraPackets) { + // note: don't add the genericfuturelistener, it's only expected to be called once... + this.channel.write(extraPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + this.channel.flush(); + } + // Tuinity end - make only one flush call for writing packets } else { this.channel.eventLoop().execute(() -> { if (enumprotocol != enumprotocol1) { this.setProtocol(enumprotocol); } - ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); + ChannelFuture channelfuture1 = (extraPackets == null || extraPackets.isEmpty()) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - make only one flush call for writing packets if (genericfuturelistener != null) { channelfuture1.addListener(genericfuturelistener); } channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + // Tuinity start - make only one flush call for writing packets + if (extraPackets != null && !extraPackets.isEmpty()) { + for (Packet extraPacket : extraPackets) { + // note: don't add the genericfuturelistener, it's only expected to be called once... + this.channel.write(extraPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + this.channel.flush(); + } + // Tuinity end - make only one flush call for writing packets }); } - - // Paper start - java.util.List extraPackets = packet.getExtraPackets(); - if (extraPackets != null && !extraPackets.isEmpty()) { - for (Packet extraPacket : extraPackets) { - this.dispatchPacket(extraPacket, genericfuturelistener); - } - } - // Paper end + // Tuinity start - make only one flush call for writing packets } diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java index ef7ade797b..e1ef70210a 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", 750); // 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 85bb22e4b7..1e618446a4 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 bdb90a3466..738c510706 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 935136771e..782edcb63b 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 29657fed75..71919adc0d 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 4240ca81cb..69fd3cc8ec 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 e35ec2db07..e7dfe22acd 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 9f8818c2d4..cc5ae6eef4 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,6 +71,7 @@ public class PlayerChunk { this.n = this.oldTicketLevel; this.a(i); this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper + this.updateRanges(); // Tuinity - optimise isOutsideOfRange } // Paper start @@ -194,7 +207,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 +227,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,9 +317,57 @@ public class PlayerChunk { } private void a(Packet packet, boolean flag) { - this.players.a(this.location, flag).forEach((entityplayer) -> { - entityplayer.playerConnection.sendPacket(packet); - }); + // 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. + PlayerChunkMap chunkMap = ((PlayerChunkMap)this.players); + com.tuinity.tuinity.util.map.PlayerAreaMap viewDistanceMap = chunkMap.playerViewDistanceBroadcastMap; + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.location); + if (players == null) { + return; + } + + long coordinate = com.tuinity.tuinity.util.Util.getCoordinateKey(this.location); + + 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; + if (!player.loadedChunks.contains(coordinate)) { + continue; + } + + 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; + if (!player.loadedChunks.contains(coordinate)) { + continue; + } + player.playerConnection.sendPacket(packet); + } + } + + return; + // Tuinity end - per player view distance } public CompletableFuture> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { @@ -505,8 +566,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 @@ -515,6 +587,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 57bea926a6..9570747eab 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,302 @@ 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; + + final ChunkSendThrottler chunkSendThrottler = new ChunkSendThrottler(); + + 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 + 2); // clients need chunk neighbours // add an extra one for antixray + } + 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.optimisedTrackerEnabled) { + for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { + com.tuinity.tuinity.util.map.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; + com.tuinity.tuinity.util.map.PlayerAreaMap untrackMap = this.playerEntityTrackerUntrackMaps[i]; + int trackRange = this.entityTrackerTrackRanges[i]; + int untrackRange = this.entityTrackerUntrackRanges[i]; + + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, effectiveViewDistance)); + untrackMap.update(player, chunkX, chunkZ, Math.min(untrackRange, effectiveViewDistance)); + } + } + // Tuinity end - use distance map to optimise entity tracker + } + + final class ChunkSendThrottler { + + static final int ALREADY_QUEUED = 0; + static final int QUEUED = 1; + static final int FAILED = 2; + + 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.Int2LongOpenHashMap lastChunkPositionByPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f); + + { + this.lastChunkPositionByPlayer.defaultReturnValue(Long.MIN_VALUE); + } + + protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap chunkSendCountPerPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f); + + protected final it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap lastChunkSendStartTimePerPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f); + + protected final java.util.List players = new java.util.ArrayList<>(256); + + protected final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap cachedChunkPackets = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); + + void addPlayer(EntityPlayer player) { + this.players.add(player); + } + + void removePlayer(EntityPlayer player) { + this.players.remove(player); + this.lastLoadedRadiusByPlayer.remove(player.getId()); + this.chunkSendCountPerPlayer.remove(player.getId()); + this.lastChunkPositionByPlayer.remove(player.getId()); + player.loadedChunks.clear(); + } + + int trySendChunk(int chunkX, int chunkZ, EntityPlayer player) { + long coordinate = com.tuinity.tuinity.util.Util.getCoordinateKey(chunkX, chunkZ); + PlayerChunk playerChunk = PlayerChunkMap.this.chunkMap.getUpdating(coordinate); + + if (playerChunk == null) { + return FAILED; + } + Chunk chunk = playerChunk.getFullReadyChunk(); + if (chunk == null || !chunk.areNeighboursLoaded(1)) { + return FAILED; + } + + if (!player.loadedChunks.add(coordinate)) { + return ALREADY_QUEUED; + } + + Packet[] chunkPackets = this.cachedChunkPackets.computeIfAbsent(coordinate, (long keyInMap) -> new Packet[2]); + PlayerChunkMap.this.sendChunk(player, chunkPackets, chunk); + + return QUEUED; + } + + void tick() { + int maxChunkSends = com.tuinity.tuinity.config.TuinityConfig.maxChunkSendsPerPlayerChoice[MinecraftServer.currentTick % com.tuinity.tuinity.config.TuinityConfig.maxChunkSendsPerPlayerChoice.length]; + for (EntityPlayer player : this.players) { + int playerId = player.getId(); + int lastLoadedRadius = this.lastLoadedRadiusByPlayer.get(playerId); + long lastChunkPos = this.lastChunkPositionByPlayer.get(playerId); + long currentChunkPos = PlayerChunkMap.this.playerViewDistanceBroadcastMap.getLastCoordinate(player); + + if (currentChunkPos == Long.MIN_VALUE) { + // not tracking for whatever reason... + continue; + } + + int newX = com.tuinity.tuinity.util.Util.getCoordinateX(currentChunkPos); + int newZ = com.tuinity.tuinity.util.Util.getCoordinateZ(currentChunkPos); + + // handle movement + if (currentChunkPos != lastChunkPos) { + this.lastChunkPositionByPlayer.put(playerId, currentChunkPos); + if (lastChunkPos != Long.MIN_VALUE) { + int oldX = com.tuinity.tuinity.util.Util.getCoordinateX(lastChunkPos); + int oldZ = com.tuinity.tuinity.util.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 radius = lastLoadedRadius + 1; + int viewDistance = PlayerChunkMap.this.playerViewDistanceBroadcastMap.getLastViewDistance(player); + + if (radius > viewDistance) { + // distance map will unload our chunks + this.lastLoadedRadiusByPlayer.put(playerId, viewDistance); + continue; + } + + int totalChunkSends = 0; + + if (totalChunkSends >= maxChunkSends) { + 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.trySendChunk(newX - offset, newZ + radius, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // top right + if ((attempt = this.trySendChunk(newX + offset, newZ + radius, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try bottom 2 chunks + + // bottom left + if ((attempt = this.trySendChunk(newX - offset, newZ - radius, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // bottom right + if ((attempt = this.trySendChunk(newX + offset, newZ - radius, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try left 2 chunks + + // left down + if ((attempt = this.trySendChunk(newX - radius, newZ - offset, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // left up + if ((attempt = this.trySendChunk(newX - radius, newZ + offset, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // try right 2 chunks + + // right down + if ((attempt = this.trySendChunk(newX + radius, newZ - offset, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + + // right up + if ((attempt = this.trySendChunk(newX + radius, newZ + offset, player)) == QUEUED) { + if (++totalChunkSends >= maxChunkSends) { + break radius_loop; + } + } else if (attempt == FAILED) { + break radius_loop; + } + } + } + int newLoadedRadius = radius - 1; + if (newLoadedRadius != lastLoadedRadius) { + this.lastLoadedRadiusByPlayer.put(playerId, newLoadedRadius); + } + } + this.cachedChunkPackets.clear(); + } + } + + // 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 boolean optimisedTrackerEnabled; + + // inlined EnumMap, TrackingRange.TrackingRangeType + static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); + final com.tuinity.tuinity.util.map.PlayerAreaMap[] playerEntityTrackerTrackMaps; + final com.tuinity.tuinity.util.map.PlayerAreaMap[] playerEntityTrackerUntrackMaps; + final int[] entityTrackerTrackRanges; + final int[] entityTrackerUntrackRanges; + + final com.tuinity.tuinity.util.EntityList activelyTrackedEntitiesLegacy; + + public static boolean isLegacyTrackingEntity(Entity entity) { + return entity.isLegacyTrackingEntity; + } + + private static int getEntityTrackingChunkRange(int blockRange) { + return blockRange >>> 4 + ((blockRange & 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 +431,100 @@ 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.chunkSendThrottler.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.chunkSendThrottler.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.optimisedTrackerEnabled) { + for (com.tuinity.tuinity.util.map.PlayerAreaMap trackMap : this.playerEntityTrackerTrackMaps) { + trackMap.remove(player); + } + for (com.tuinity.tuinity.util.map.PlayerAreaMap trackMap : this.playerEntityTrackerUntrackMaps) { + trackMap.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 + 2); // clients need chunk neighbours // add an extra one for antixray + } + 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.optimisedTrackerEnabled) { + for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { + com.tuinity.tuinity.util.map.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; + com.tuinity.tuinity.util.map.PlayerAreaMap untrackMap = this.playerEntityTrackerUntrackMaps[i]; + int trackRange = this.entityTrackerTrackRanges[i]; + int untrackRange = this.entityTrackerUntrackRanges[i]; + + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, effectiveViewDistance)); + untrackMap.update(player, chunkX, chunkZ, Math.min(untrackRange, effectiveViewDistance)); + } + } + // 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 +553,185 @@ 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, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { + if (newState.size() != 1) { + return; + } + PlayerChunkMap.this.chunkDistanceManager.playerMoveInRange(rangeX, rangeZ, currPosX, currPosZ); + }, + (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.playerMoveOutOfRange(rangeX, rangeZ, currPosX, currPosZ); + }); + 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)); + } + }, + (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, null, true, false); // unloaded, loaded + player.loadedChunks.remove(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ)); + }); + // 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.activelyTrackedEntitiesLegacy = null; + this.playerEntityTrackerTrackMaps = null; + this.playerEntityTrackerUntrackMaps = null; + this.entityTrackerTrackRanges = null; + this.entityTrackerUntrackRanges = null; + this.optimisedTrackerEnabled = false; + } else { + this.optimisedTrackerEnabled = true; + this.activelyTrackedEntitiesLegacy = new com.tuinity.tuinity.util.EntityList(); + + this.playerEntityTrackerTrackMaps = new com.tuinity.tuinity.util.map.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; + this.playerEntityTrackerUntrackMaps = new com.tuinity.tuinity.util.map.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; + this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; + this.entityTrackerUntrackRanges = new int[TRACKING_RANGE_TYPES.length]; + + org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.world.spigotConfig; + + for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { + org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; + int configuredSpigotValue; + switch (trackingRangeType) { + case PLAYER: + configuredSpigotValue = spigotWorldConfig.playerTrackingRange; + break; + case ANIMAL: + configuredSpigotValue = spigotWorldConfig.animalTrackingRange; + break; + case MONSTER: + configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; + break; + case MISC: + configuredSpigotValue = spigotWorldConfig.miscTrackingRange; + break; + case OTHER: + configuredSpigotValue = spigotWorldConfig.otherTrackingRange; + break; + case ENDERDRAGON: + configuredSpigotValue = 10 * 16; // default is 10 chunk range // TODO check on update + break; + default: + throw new IllegalStateException("Missing case for enum " + trackingRangeType); + } + + int untrackRange = Math.max(1, getEntityTrackingChunkRange(configuredSpigotValue)); + int trackRange = untrackRange - 1; + this.entityTrackerTrackRanges[ordinal] = trackRange; + this.entityTrackerUntrackRanges[ordinal] = untrackRange; + + this.playerEntityTrackerTrackMaps[ordinal] = 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 || !player.loadedChunks.contains(com.tuinity.tuinity.util.Util.getCoordinateKey(rangeX, rangeZ))) { + return; + } + Entity[] entities = chunk.entities.getRawData(); + for (int index = 0, length = chunk.entities.size(); index < length; ++index) { + Entity entity = entities[index]; + if (org.spigotmc.TrackingRange.getTrackingRangeType(entity) != trackingRangeType) { + continue; + } + if (entity.tracker == null) { + entity.addToTrackQueue(player); + } else { + entity.tracker.updateTrackingPlayer(player); + entity.clearTrackingQueues(player); + } + } + }, + null); + this.playerEntityTrackerUntrackMaps[ordinal] = 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, length = chunk.entities.size(); index < length; ++index) { + Entity entity = entities[index]; + if (entity.tracker == null) { + return; // not tracked by player for sure + } + if (org.spigotmc.TrackingRange.getTrackingRangeType(entity) != trackingRangeType) { + continue; + } + entity.tracker.removeTrackingPlayer(player); + entity.clearTrackingQueues(player); + } + }); + } + } + // 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 +742,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 +793,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 +810,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 +898,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 +918,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 +1003,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 +1031,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 +1074,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 +1202,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 +1495,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 +1599,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 +1670,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 +1865,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 +1937,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 +1949,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.optimisedTrackerEnabled) { // Tuinity - optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1345,6 +1966,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 +2006,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 +2071,37 @@ 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.optimisedTrackerEnabled) { // Tuinity - implement optimized tracker playerchunkmap_entitytracker.track(this.world.getPlayers()); + // Tuinity start - implement optimized tracker + } else { + entity.acquireTrackingMap(this); + if (PlayerChunkMap.isLegacyTrackingEntity(entity)) { + 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 = entity.getTrackingAreaMap().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.optimisedTrackerEnabled) { // Tuinity - implement optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1466,6 +2111,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker1.updatePlayer(entityplayer); } } + } // Tuinity - implement optimized tracker } } @@ -1494,9 +2140,104 @@ 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 = + tracked.getUnTrackingAreaMap().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 + + this.world.timings.tracker3.startTiming(); + 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("Legacy tracking entity has no tracker! No longer tracking entity " + entity); + this.activelyTrackedEntitiesLegacy.remove(entity); + --i; + --size; + continue; + } + + EntityTrackerEntry entry = tracker.trackerEntry; + tracker.track(this.world.getPlayers()); + entry.tick(); // always tick the entry, even if no player is tracking + } + this.world.timings.tracker3.stopTiming(); + } + // Tuinity end - optimized tracker + protected void g() { + // Tuinity start - optimized tracker + if (this.optimisedTrackerEnabled) { + this.processTrackQueue(); + return; + } + // Tuinity end - optimized tracker List list = Lists.newArrayList(); List list1 = this.world.getPlayers(); @@ -1554,6 +2295,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 +2306,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.optimisedTrackerEnabled) { // Tuinity - implement optimized tracker ObjectIterator objectiterator = this.trackedEntities.values().iterator(); while (objectiterator.hasNext()) { @@ -1581,6 +2324,31 @@ 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. + // only send entities when they're in tracking range... + Entity[] entities = chunk.entities.getRawData(); + for (int i = 0, len = chunk.entities.size(); i < len; ++i) { + Entity entityInChunk = entities[i]; + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange + = entityInChunk.getTrackingAreaMap().getObjectsInRange(chunk.getPos()); + if (playersInRange == null || !playersInRange.contains(entityplayer)) { + continue; + } + PlayerChunkMap.EntityTracker tracker = entityInChunk.tracker; + if (tracker == null) { + continue; + } + + // 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 +2386,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; @@ -1684,10 +2452,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 +2467,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) <= (1 + PlayerChunkMap.this.playerViewDistanceTickMap.getLastViewDistance(entityplayer)) && entityplayer.loadedChunks.contains(com.tuinity.tuinity.util.Util.getCoordinateKey(this.tracker)); // Tuinity - per player view distance } } @@ -1738,6 +2509,42 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { return i; } + // Tuinity start - optimized tracker + 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 && entityplayer.loadedChunks.contains(com.tuinity.tuinity.util.Util.getCoordinateKey(this.tracker)); + if (this.tracker instanceof EntityPlayer) { + Player player = ((EntityPlayer)this.tracker).getBukkitEntity(); + if (!entityplayer.getBukkitEntity().canSee(player)) { + shouldTrack = false; + } + } + // CraftBukkit end + + if (shouldTrack) { + if (this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper + this.trackerEntry.onTrack(entityplayer); + } + } else { + this.removeTrackingPlayer(entityplayer); + } + } + + 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 ce4340a476..1b60310bb0 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 d103cfaace..1b8cb3fc61 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 7b79ee4fe5..253ee52eb5 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 f376e21068..5a883aac14 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 df728e2c0a..99eb8bb059 100644 --- a/src/main/java/net/minecraft/server/RegionFile.java +++ b/src/main/java/net/minecraft/server/RegionFile.java @@ -28,14 +28,349 @@ 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); + } + + private void backupRegionFile() { + File backup = new File(this.file.getParent(), this.file.getName() + "." + new java.util.Random().nextLong() + ".backup"); + this.backupRegionFile(backup); + } + + private void backupRegionFile(File to) { + try { + this.dataFile.force(true); + MinecraftServer.LOGGER.warn("Backing up regionfile \"" + this.file.getAbsolutePath() + "\" to " + to.getAbsolutePath()); + java.nio.file.Files.copy(this.file.toPath(), to.toPath()); + MinecraftServer.LOGGER.warn("Backed up the regionfile to " + to.getAbsolutePath()); + } catch (IOException ex) { + MinecraftServer.LOGGER.error("Failed to backup to " + to.getAbsolutePath(), ex); + } + } + + // 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 + + this.backupRegionFile(); + 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 +398,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,12 +436,15 @@ public class RegionFile implements AutoCloseable { RegionFile.LOGGER.warn("Region file {} has truncated header: {}", java_nio_file_path, i); } - for (int j = 0; j < 1024; ++j) { + boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption + boolean hasBackedUp = false; // Tuinity - recalculate header on header corruption + + for (int j = 0; j < 1024; ++j) { // Tuinity - diff on change, we expect j to be the header location int k = this.g.get(j); if (k != 0) { - int l = b(k); - int i1 = a(k); + int l = b(k); // Tuinity - diff on change, we expect l to be offset in file + int i1 = a(k); // Tuinity - diff on change, we expect i1 to be sector length of region // Spigot start if (i1 == 255) { // We're maxed out, so we need to read the proper length from the section @@ -105,20 +454,87 @@ public class RegionFile implements AutoCloseable { } // Spigot end - this.freeSectors.a(l, i1); + // Tuinity start - recalculate header on header corruption + if (l < 0 || i1 < 0 || (l + i1) < 0) { + if (canRecalcHeader) { + MinecraftServer.LOGGER.error("Detected invalid header for regionfile " + this.file.getAbsolutePath() + "! Recalculating header..."); + needsHeaderRecalc = true; + break; + } else { + // location = chunkX | (chunkZ << 5); + MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + + "! Cannot recalculate, removing local chunk (" + (j & 31) + "," + (j >>> 5) + ") from header"); + if (!hasBackedUp) { + hasBackedUp = true; + this.backupRegionFile(); + } + this.getTimestamps().put(j, 0); // be consistent, delete the timestamp too + this.getOffsets().put(j, 0); // delete the entry from header + continue; + } + } + boolean failedToAllocate = !this.freeSectors.tryAllocate(l, i1); + if (failedToAllocate && !canRecalcHeader) { + // location = chunkX | (chunkZ << 5); + MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + + "! Cannot recalculate, removing local chunk (" + (j & 31) + "," + (j >>> 5) + ") from header"); + if (!hasBackedUp) { + hasBackedUp = true; + this.backupRegionFile(); + } + this.getTimestamps().put(j, 0); // be consistent, delete the timestamp too + this.getOffsets().put(j, 0); // delete the entry from header + continue; + } + needsHeaderRecalc |= failedToAllocate; + // Tuinity end - 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 + MinecraftServer.LOGGER.error("Recalculating regionfile " + this.file.getAbsolutePath() + ", header gave conflicting offsets & locations"); + 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 +558,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 +572,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 +590,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 +763,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 +808,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 1ebdf73cc9..cfa3ecb031 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 0f201000f6..c88ad8de0a 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 3382d678e6..29137f4959 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/ThreadedMailbox.java b/src/main/java/net/minecraft/server/ThreadedMailbox.java index 8082569022..8b1a3f3f98 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/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java index 7a8397815a..0d5b1a0b7b 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 4b87ca2ecb..141664b554 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 caf297fe97..3161e3b977 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 9e09671dc2..b69a1ed46a 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 71d2ae2a9c..9129eaa642 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 08c83c62df..d5da9f5825 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 5117dafbcf..2b5d0ecd0d 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -82,6 +82,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.WorldConfig tuinityConfig; // Tuinity - Server Config + public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPosition lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; @@ -131,6 +133,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.WorldConfig(worlddata.getName()); // Tuinity - Server Config this.generator = gen; this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit @@ -333,6 +336,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); @@ -429,6 +433,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; @@ -437,8 +442,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) { @@ -1179,9 +1189,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); @@ -1200,9 +1212,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); @@ -1222,10 +1236,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 2de48e7537..92b79e3e71 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 Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); // Tuinity - diff on change, we expect Int2ObjectLinkedOpenHashMap 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 @@ -177,6 +179,367 @@ public class WorldServer extends World { } // Paper end - rewrite ticklistserver + // Tuinity start - Optimize entity list iteration requiring entities be in loaded chunks + 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]); + } + } + // Tuinity end - Optimize entity list iteration requiring entities be in loaded chunks + + // 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) -> { @@ -465,14 +828,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 @@ -508,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); } @@ -829,6 +1191,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); } @@ -838,6 +1204,83 @@ 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.optimisedTrackerEnabled) { + if (!PlayerChunkMap.isLegacyTrackingEntity(entity)) { + // handle tracking + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldRange = + entity.getTrackingAreaMap().getObjectsInRange(prevChunkX, prevChunkZ); + com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newRange = + entity.getTrackingAreaMap().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 = entity.getUnTrackingAreaMap().getObjectsInRange(prevChunkX, prevChunkZ); + newRange = entity.getUnTrackingAreaMap().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(); @@ -1189,6 +1632,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 @@ -1371,6 +1815,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); @@ -1383,6 +1828,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 } @@ -1439,6 +1885,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 3030c347ef..76f0f258e1 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 f70468adab..2be7962bc5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -806,6 +806,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); @@ -834,6 +835,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 @@ -1705,7 +1707,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 @@ -2082,6 +2087,14 @@ public final class CraftServer implements Server { return com.destroystokyo.paper.PaperConfig.config; } + // Tuinity start - add config to timings report + @Override + public YamlConfiguration getTuinityConfig() + { + return com.tuinity.tuinity.config.TuinityConfig.config; + } + // Tuinity end - add config to timings report + @Override public void restart() { org.spigotmc.RestartCommand.restart(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 1a5ee34103..339ec053c7 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; @@ -2454,10 +2456,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 cb60310e63..2666940e86 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 dfa15372b8..949acb1d32 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 60f62f19cb..b5ffd14413 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.optimisedTrackerEnabled || PlayerChunkMap.isLegacyTrackingEntity(other)) { // Tuinity - optimized tracker entry.updatePlayer(getHandle()); + // Tuinity start - optimized tracker + } else { + other.addToTrackQueue(getHandle()); + } + // 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 6e165a1649..e160f03c1e 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 9f7d2ef932..c3ac1a46c3 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 + "!" ); } diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java index 46c33e6917..76a4d43152 100644 --- a/src/main/java/org/spigotmc/TrackingRange.java +++ b/src/main/java/org/spigotmc/TrackingRange.java @@ -47,4 +47,44 @@ public class TrackingRange return config.otherTrackingRange; } } + + // Tuinity start - optimise entity tracking + // copied from above, TODO check on update + public static TrackingRangeType getTrackingRangeType(Entity entity) + { + SpigotWorldConfig config = entity.world.spigotConfig; + if ( entity instanceof EntityPlayer ) + { + return TrackingRangeType.PLAYER; + // Paper start - Simplify and set water mobs to animal tracking range + } + switch (entity.activationType) { + case RAIDER: + case MONSTER: + return TrackingRangeType.MONSTER; + case WATER: + case ANIMAL: + return TrackingRangeType.ANIMAL; + case MISC: + } + if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) + // Paper end + { + return TrackingRangeType.MISC; + } else + { + if (entity instanceof EntityEnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt + return TrackingRangeType.OTHER; + } + } + + public static enum TrackingRangeType { + PLAYER, + ANIMAL, + MONSTER, + MISC, + OTHER, + ENDERDRAGON; + } + // Tuinity end } -- 2.24.0