Files
Purpur/patches/server/0002-Tuinity-Server-Patches.patch
William Blake Galbreath 66aa60f536 Update Tuinity patches
2020-03-21 20:09:52 -05:00

10360 lines
476 KiB
Diff

From 2fb55c1eee17e60d4fb7ed8d7a226e4a33da9c21 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
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 +-
.../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 | 239 ++++
.../server/TickListServerInterval.java | 42 +
.../tuinity/server/TuinityTickList.java | 614 ++++++++++
.../com/tuinity/tuinity/util/ChunkList.java | 119 ++
.../com/tuinity/tuinity/util/EntityList.java | 124 ++
.../tuinity/tuinity/util/IBlockDataList.java | 123 ++
.../tuinity/util/OptimizedSmallEnumSet.java | 65 +
.../tuinity/util/PrimaryThreadList.java | 241 ++++
.../tuinity/util/PrimaryThreadSet.java | 282 +++++
.../util/TickSynchronizationPoint.java | 40 +
.../com/tuinity/tuinity/util/TickThread.java | 40 +
.../java/com/tuinity/tuinity/util/Util.java | 103 ++
.../fastutil/ExtendedAbstractDoubleList.java | 39 +
.../fastutil/ExtendedDoubleArrayList.java | 65 +
.../fastutil/ExtendedObjectAVLTreeSet.java | 90 ++
.../com/tuinity/tuinity/util/map/AreaMap.java | 388 ++++++
.../tuinity/util/map/PlayerAreaMap.java | 25 +
.../util/map/PooledLinkedHashSets.java | 287 +++++
.../util/pool/PooledBlockPositions.java | 40 +
.../tuinity/util/set/LinkedSortedSet.java | 142 +++
.../net/minecraft/server/ArraySetSorted.java | 41 +-
.../net/minecraft/server/AxisAlignedBB.java | 2 +
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 | 141 ++-
.../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 | 120 ++
.../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 | 1044 +++++++++++++++--
.../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 | 7 +-
.../org/bukkit/craftbukkit/CraftWorld.java | 67 +-
.../java/org/bukkit/craftbukkit/Main.java | 7 +
.../craftbukkit/entity/CraftEntity.java | 12 +
.../craftbukkit/entity/CraftPlayer.java | 37 +-
.../java/org/spigotmc/ActivationRange.java | 41 +-
src/main/java/org/spigotmc/AsyncCatcher.java | 2 +-
src/main/java/org/spigotmc/TrackingRange.java | 40 +
97 files changed, 7062 insertions(+), 484 deletions(-)
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java
create mode 100644 src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
create mode 100644 src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java
create mode 100644 src/main/java/com/tuinity/tuinity/server/TuinityTickList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/ChunkList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/EntityList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/IBlockDataList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/TickThread.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/Util.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/AreaMap.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java
create mode 100644 src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java
diff --git a/pom.xml b/pom.xml
index a7ee9396a0..90ca354c46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,4 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>purpur</artifactId>
@@ -16,8 +16,8 @@
<buildtag.prefix>git-Bukkit-</buildtag.prefix>
<buildtag.suffix></buildtag.suffix>
<maven.build.timestamp.format>yyyyMMdd-HHmm</maven.build.timestamp.format>
- <maven.compiler.source>1.8</maven.compiler.source>
- <maven.compiler.target>1.8</maven.compiler.target>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
</properties>
<parent>
@@ -133,6 +133,12 @@
<version>1.3</version>
<scope>test</scope>
</dependency>
+ <!-- Tuinity concurrentutil dependency -->
+ <dependency>
+ <groupId>ca.spottedleaf.concurrentutil</groupId>
+ <artifactId>concurrentutil</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </dependency>
</dependencies>
<repositories>
@@ -296,6 +302,10 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
+ <!-- Tuinity - https://issues.apache.org/jira/browse/MCOMPILER-346 -->
+ <configuration>
+ <forceJavacCompilerUse>true</forceJavacCompilerUse>
+ </configuration>
<dependencies>
<!-- we need our custom version as it fixes some bugs on case sensitive file systems -->
<dependency>
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<T extends PrioritizedTaskQueue.PrioritizedTask
private static final int TOTAL_PRIORITIES = 6;
- final ConcurrentLinkedQueue<T>[] queues = (ConcurrentLinkedQueue<T>[])new ConcurrentLinkedQueue[TOTAL_PRIORITIES];
+ final MultiThreadedQueue<T>[] 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<T extends PrioritizedTaskQueue.PrioritizedTask
*/
public void add(final T task) throws IllegalStateException {
task.onQueue(this);
- this.queues[task.getPriority()].add(task);
- if (this.shutdown.get()) {
- // note: we're not actually sure at this point if our task will go through
+ if (!this.queues[task.getPriority()].add(task)) { // Tuinity - use concurrentutil
+ // note: we're not actually sure at this point if our task will go through // Tuinity - we are certain now
throw new IllegalStateException("Queue has shutdown, refusing to execute task " + IOUtil.genericToString(task));
}
}
@@ -86,7 +86,7 @@ public class PrioritizedTaskQueue<T extends PrioritizedTaskQueue.PrioritizedTask
public T poll() {
T task;
for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
- final ConcurrentLinkedQueue<T> queue = this.queues[i];
+ final MultiThreadedQueue<T> 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<T extends PrioritizedTaskQueue.PrioritizedTask
*/
public boolean hasTasks() {
for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
- final ConcurrentLinkedQueue<T> queue = this.queues[i];
+ final MultiThreadedQueue<T> queue = this.queues[i]; // Tuinity - use concurrentutil
if (queue.peek() != null) {
return true;
@@ -130,6 +130,12 @@ public class PrioritizedTaskQueue<T extends PrioritizedTaskQueue.PrioritizedTask
* @return {@code true} if the queue was shutdown, {@code false} if it has shut down already
*/
public boolean shutdown() {
+ // Tuinity start - use concurrentutil
+ for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
+ final MultiThreadedQueue<T> queue = this.queues[i];
+ queue.preventAdds();
+ }
+ // Tuinity end - use concurrentutil
return this.shutdown.getAndSet(false);
}
diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java
index 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<L, R> implements App<Either.Mu<R>, L> {
private static final class Left<L, R> extends Either<L, R> {
private final L value;
+ private Optional<L> cachedLeft; // Tuinity - reduce allocation of these for chunks...
public Left(final L value) {
this.value = value;
@@ -51,7 +52,7 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
@Override
public Optional<L> 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<L, R> implements App<Either.Mu<R>, L> {
private static final class Right<L, R> extends Either<L, R> {
private final R value;
+ private Optional<R> cachedRight; // Tuinity - reduce allocation of these for chunks...
public Right(final R value) {
this.value = value;
@@ -117,7 +119,7 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
@Override
public Optional<R> 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<ChunkRegionHolder> 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<ChunkRegionHolder> 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<Long2IntMap.Entry> 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<Long2IntMap.Entry> 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<V> {
+
+ protected static final Object REMOVED = new Object();
+
+ protected final Long2ObjectLinkedOpenHashMap<V> updatingMap;
+ protected final Long2ObjectLinkedOpenHashMap<V> visibleMap;
+ protected final Long2ObjectLinkedOpenHashMap<Object> 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<V> getVisibleMap() {
+ return this.visibleMap;
+ }
+
+ public Long2ObjectLinkedOpenHashMap<V> 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<V> getUpdatingValues() {
+ return this.updatingMap.values();
+ }
+
+ public List<V> 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<V> getVisibleValues() {
+ return this.visibleMap.values();
+ }
+
+ public List<V> getVisibleValuesCopy() {
+ return new ArrayList<>(this.visibleMap.values());
+ }
+
+ public boolean performUpdates() {
+ if (this.queuedChanges.isEmpty()) {
+ return false;
+ }
+
+ final ObjectBidirectionalIterator<Long2ObjectMap.Entry<Object>> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator();
+ while (iterator.hasNext()) {
+ final Long2ObjectMap.Entry<Object> 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<Long2ObjectMap.Entry<Object>> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator();
+
+ try {
+ this.updatingMapSeqLock.acquireWrite();
+
+ while (iterator.hasNext()) {
+ final Long2ObjectMap.Entry<Object> 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..39395ee269
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
@@ -0,0 +1,239 @@
+package com.tuinity.tuinity.config;
+
+import ca.spottedleaf.concurrentutil.util.Throw;
+import net.minecraft.server.TicketType;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.logging.Level;
+
+public final class TuinityConfig {
+
+ public static final String CONFIG_HEADER = "Configuration file for Tuinity.";
+ public static final int CURRENT_CONFIG_VERSION = 0;
+
+ private static final Object[] EMPTY = new Object[0];
+
+ private static File configFile;
+ private static YamlConfiguration config;
+ private static int configVersion;
+
+ public static void init(final File file) {
+ // TODO remove this in the future...
+ final File tuinityConfig = new File(file.getParent(), "tuinity.yml");
+ if (!tuinityConfig.exists()) {
+ final File oldConfig = new File(file.getParent(), "concrete.yml");
+ oldConfig.renameTo(tuinityConfig);
+ }
+ TuinityConfig.configFile = file;
+ final YamlConfiguration config = new YamlConfiguration();
+ config.options().header(CONFIG_HEADER);
+ config.options().copyDefaults(true);
+
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ } catch (final Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex);
+ }
+ } else {
+ try {
+ config.load(file);
+ } catch (final Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex);
+ Throw.rethrow(ex); /* Rethrow, this is critical */
+ throw new RuntimeException(ex); // unreachable
+ }
+ }
+
+ TuinityConfig.load(config);
+ }
+
+ public static void load(final YamlConfiguration config) {
+ TuinityConfig.config = config;
+ TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION);
+
+ for (final Method method : TuinityConfig.class.getDeclaredMethods()) {
+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 ||
+ !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+
+ try {
+ method.setAccessible(true);
+ method.invoke(null, EMPTY);
+ } catch (final Exception ex) {
+ Throw.rethrow(ex);
+ throw new RuntimeException(ex); // unreachable
+ }
+ }
+
+ /* We re-save to add new options */
+ try {
+ config.save(TuinityConfig.configFile);
+ } catch (final Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex);
+ }
+ }
+
+ private static boolean getBoolean(final String path, final boolean dfl) {
+ TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl));
+ return TuinityConfig.config.getBoolean(path, dfl);
+ }
+
+ private static int getInt(final String path, final int dfl) {
+ TuinityConfig.config.addDefault(path, Integer.valueOf(dfl));
+ return TuinityConfig.config.getInt(path, dfl);
+ }
+
+ private static double getDouble(final String path, final double dfl) {
+ TuinityConfig.config.addDefault(path, Double.valueOf(dfl));
+ return TuinityConfig.config.getDouble(path, dfl);
+ }
+
+ public static boolean tickWorldsInParallel;
+
+ /**
+ * if tickWorldsInParallel == true, then this value is used as a default only for worlds
+ */
+ public static int tickThreads;
+
+ /*
+ private static void worldticking() {
+ tickWorldsInParallel = TuinityConfig.getBoolean("tick-worlds-in-parallel", false);
+ tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future
+ }*/
+
+ public static double maxChunkSendsPerPlayer; // per second
+
+ public static int[] maxChunkSendsPerPlayerChoice = new int[100];
+
+ private static void maxChunkLoadsPerPlayer() {
+ 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 WorldConnfig {
+
+ public final String worldName;
+ public ConfigurationSection config;
+
+ public WorldConnfig(final String worldName) {
+ this.worldName = worldName;
+ this.init();
+ }
+
+ public void init() {
+ ConfigurationSection section = TuinityConfig.config.getConfigurationSection(this.worldName);
+ if (section == null) {
+ section = TuinityConfig.config.createSection(this.worldName);
+ }
+ TuinityConfig.config.set(this.worldName, section);
+
+ this.load(section);
+ }
+
+ public void load(final ConfigurationSection config) {
+ this.config = config;
+
+ for (final Method method : TuinityConfig.WorldConnfig.class.getDeclaredMethods()) {
+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 ||
+ !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+
+ try {
+ method.setAccessible(true);
+ method.invoke(this, EMPTY);
+ } catch (final Exception ex) {
+ Throw.rethrow(ex);
+ throw new RuntimeException(ex); // unreachable
+ }
+ }
+
+ /* We re-save to add new options */
+ try {
+ TuinityConfig.config.save(TuinityConfig.configFile);
+ } catch (final Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex);
+ }
+ }
+
+ private boolean getBoolean(final String path, final boolean dfl) {
+ this.config.addDefault(path, Boolean.valueOf(dfl));
+ return this.config.getBoolean(path, dfl);
+ }
+
+ private int getInt(final String path, final int dfl) {
+ this.config.addDefault(path, Integer.valueOf(dfl));
+ return this.config.getInt(path, dfl);
+ }
+
+ private double getDouble(final String path, final double dfl) {
+ this.config.addDefault(path, Double.valueOf(dfl));
+ return this.config.getDouble(path, dfl);
+ }
+
+ /** ignored if {@link TuinityConfig#tickWorldsInParallel} == false */
+ public int threads;
+
+ /*
+ private void worldthreading() {
+ final int threads = this.getInt("tick-threads", -1);
+ this.threads = threads == -1 ? TuinityConfig.tickThreads : threads;
+ }*/
+
+ public int noTickViewDistance;
+ private void noTickViewDistance() {
+ this.noTickViewDistance = this.getInt("no-tick-view-distance", -1);
+ }
+
+ public boolean useOptimizedTracker;
+
+ private void optimizetracker() {
+ this.useOptimizedTracker = this.getBoolean("optimized-tracker", true);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java b/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java
new file mode 100644
index 0000000000..bef788ccfa
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/server/TickListServerInterval.java
@@ -0,0 +1,42 @@
+package com.tuinity.tuinity.server;
+
+import com.tuinity.tuinity.util.set.LinkedSortedSet;
+import net.minecraft.server.NextTickListEntry;
+import net.minecraft.server.TickListPriority;
+
+import java.util.Comparator;
+
+// represents a set of entries to tick at a specified time
+public final class TickListServerInterval<T> {
+
+ public static final int TOTAL_PRIORITIES = TickListPriority.values().length;
+ public static final Comparator<NextTickListEntry<?>> ENTRY_COMPARATOR_BY_ID = (entry1, entry2) -> {
+ return Long.compare(entry1.getId(), entry2.getId());
+ };
+ public static final Comparator<NextTickListEntry<?>> ENTRY_COMPARATOR = (Comparator)NextTickListEntry.comparator();
+
+ // we do not record the interval, this class is meant to be used on a ring buffer
+
+ // inlined enum map for TickListPriority
+ public final LinkedSortedSet<NextTickListEntry<T>>[] byPriority = new LinkedSortedSet[TOTAL_PRIORITIES];
+
+ {
+ for (int i = 0, len = this.byPriority.length; i < len; ++i) {
+ this.byPriority[i] = new LinkedSortedSet<>(ENTRY_COMPARATOR_BY_ID);
+ }
+ }
+
+ public void addEntryLast(final NextTickListEntry<T> entry) {
+ this.byPriority[entry.getPriority().ordinal()].addLast(entry);
+ }
+
+ public void addEntryFirst(final NextTickListEntry<T> entry) {
+ this.byPriority[entry.getPriority().ordinal()].addFirst(entry);
+ }
+
+ public void clear() {
+ for (int i = 0, len = this.byPriority.length; i < len; ++i) {
+ this.byPriority[i].clear(); // O(1) clear
+ }
+ }
+}
diff --git a/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java b/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java
new file mode 100644
index 0000000000..4118dd7e1d
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/server/TuinityTickList.java
@@ -0,0 +1,614 @@
+package com.tuinity.tuinity.server;
+
+import com.tuinity.tuinity.util.TickThread;
+import com.tuinity.tuinity.util.Util;
+import ca.spottedleaf.concurrentutil.util.Validate;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
+import net.minecraft.server.BaseBlockPosition;
+import net.minecraft.server.BlockPosition;
+import net.minecraft.server.ChunkCoordIntPair;
+import net.minecraft.server.ChunkProviderServer;
+import net.minecraft.server.CrashReport;
+import net.minecraft.server.CrashReportSystemDetails;
+import net.minecraft.server.IBlockData;
+import net.minecraft.server.MinecraftKey;
+import net.minecraft.server.NBTTagList;
+import net.minecraft.server.NextTickListEntry;
+import net.minecraft.server.ReportedException;
+import net.minecraft.server.StructureBoundingBox;
+import net.minecraft.server.TickListPriority;
+import net.minecraft.server.TickListServer;
+import net.minecraft.server.WorldServer;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+public final class TuinityTickList<T> extends TickListServer<T> { // extend to avoid breaking ABI
+
+ // in the order the state is expected to change (mostly)
+ public static final int STATE_UNSCHEDULED = 1 << 0;
+ public static final int STATE_SCHEDULED = 1 << 1; // scheduled for some tick
+ public static final int STATE_PENDING_TICK = 1 << 2; // for this tick
+ public static final int STATE_TICKING = 1 << 3;
+ public static final int STATE_TICKED = 1 << 4; // after this, it gets thrown back to unscheduled
+ public static final int STATE_CANCELLED_TICK = 1 << 5; // still gets moved to unscheduled after tick
+
+ private static final int SHORT_SCHEDULE_TICK_THRESHOLD = 20 * 5 + 1; // 5 seconds
+
+ private final WorldServer world;
+ private final Predicate<T> excludeFromScheduling;
+ private final Function<T, MinecraftKey> getMinecraftKeyFrom;
+ private final Function<MinecraftKey, T> getObjectFronMinecraftKey;
+ private final Consumer<NextTickListEntry<T>> tickFunction;
+
+ private final co.aikar.timings.Timing timingCleanup; // Paper
+ private final co.aikar.timings.Timing timingTicking; // Paper
+ private final co.aikar.timings.Timing timingFinished;
+
+ // note: remove ops / add ops suck on fastutil, a chained hashtable implementation would work better, but Long...
+ // try to alleviate with a very small load factor
+ private final Long2ObjectOpenHashMap<ArrayList<NextTickListEntry<T>>> entriesByBlock = new Long2ObjectOpenHashMap<>(1024, 0.25f);
+ private final Long2ObjectOpenHashMap<ObjectRBTreeSet<NextTickListEntry<T>>> entriesByChunk = new Long2ObjectOpenHashMap<>(1024, 0.25f);
+ private final Long2ObjectOpenHashMap<ArrayList<NextTickListEntry<T>>> pendingChunkTickLoad = new Long2ObjectOpenHashMap<>(1024, 0.5f);
+
+ // fastutil has O(1) first/last while TreeMap/TreeSet are log(n)
+ private final ObjectRBTreeSet<NextTickListEntry<T>> longScheduled = new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR);
+
+ private final ArrayDeque<NextTickListEntry<T>> toTickThisTick = new ArrayDeque<>();
+
+ private final TickListServerInterval<T>[] shortScheduled = new TickListServerInterval[SHORT_SCHEDULE_TICK_THRESHOLD];
+ {
+ for (int i = 0, len = this.shortScheduled.length; i < len; ++i) {
+ this.shortScheduled[i] = new TickListServerInterval<>();
+ }
+ }
+ private int shortScheduledIndex;
+
+ private long nextTick;
+
+ // assume index < length
+ private static int getWrappedIndex(final int start, final int length, final int index) {
+ final int next = start + index;
+ return next < length ? next : next - length;
+ }
+
+ private static int getNextIndex(final int curr, final int length) {
+ final int next = curr + 1;
+ return next < length ? next : 0;
+ }
+
+ public TuinityTickList(final WorldServer world, final Predicate<T> excludeFromScheduling, final Function<T, MinecraftKey> getMinecraftKeyFrom,
+ final Function<MinecraftKey, T> getObjectFronMinecraftKey, final Consumer<NextTickListEntry<T>> tickFunction, final String timingsType) {
+ super(world, excludeFromScheduling, getMinecraftKeyFrom, getObjectFronMinecraftKey, tickFunction, timingsType);
+ this.world = world;
+ this.excludeFromScheduling = excludeFromScheduling;
+ this.getMinecraftKeyFrom = getMinecraftKeyFrom;
+ this.getObjectFronMinecraftKey = getObjectFronMinecraftKey;
+ this.tickFunction = tickFunction;
+ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Cleanup"); // Paper
+ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Ticking"); // Paper
+ this.timingFinished = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Finish");
+ this.nextTick = this.world.getTime();
+ }
+
+ private void queueEntryForTick(final NextTickListEntry<T> entry, final ChunkProviderServer chunkProvider) {
+ if (entry.tickState == STATE_SCHEDULED) {
+ if (chunkProvider.isTickingReadyMainThread(entry.getPosition())) {
+ this.toTickThisTick.add(entry);
+ entry.tickState = STATE_PENDING_TICK;
+ } else {
+ // we dump them to a map to avoid constantly re-scheduling them
+ this.addToNotTickingReady(entry);
+ }
+ }
+ }
+
+ private void addToNotTickingReady(final NextTickListEntry<T> entry) {
+ this.pendingChunkTickLoad.computeIfAbsent(Util.getCoordinateKey(entry.getPosition()), (long keyInMap) -> {
+ return new ArrayList<>();
+ }).add(entry);
+ }
+
+ private void addToSchedule(final NextTickListEntry<T> entry) {
+ long delay = entry.getTargetTick() - this.nextTick;
+ if (delay < SHORT_SCHEDULE_TICK_THRESHOLD) {
+ if (delay < 0) {
+ // longScheduled orders by tick time, short scheduled does not
+ this.longScheduled.add(entry);
+ } else {
+ this.shortScheduled[getWrappedIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD, (int)delay)].addEntryLast(entry);
+ }
+ } else {
+ this.longScheduled.add(entry);
+ }
+ }
+
+ private void removeEntry(final NextTickListEntry<T> entry) {
+ entry.tickState = STATE_CANCELLED_TICK;
+ // short/long scheduled will skip the entry
+
+ final BlockPosition pos = entry.getPosition();
+ final long blockKey = Util.getBlockKey(pos);
+
+ final ArrayList<NextTickListEntry<T>> currentEntries = this.entriesByBlock.get(blockKey);
+
+ if (currentEntries.size() == 1) {
+ // it should contain our entry
+ this.entriesByBlock.remove(blockKey);
+ } else {
+ // it's more likely that this entry is at the start of the list than the end
+ for (int i = 0, len = currentEntries.size(); i < len; ++i) {
+ final NextTickListEntry<T> currentEntry = currentEntries.get(i);
+ if (currentEntry == entry) {
+ currentEntries.remove(i);
+ break;
+ }
+ }
+ }
+
+ final long chunkKey = Util.getCoordinateKey(entry.getPosition());
+
+ ObjectRBTreeSet<NextTickListEntry<T>> set = this.entriesByChunk.get(chunkKey);
+
+ set.remove(entry);
+
+ if (set.isEmpty()) {
+ this.entriesByChunk.remove(chunkKey);
+ }
+
+ ArrayList<NextTickListEntry<T>> pendingTickingLoad = this.pendingChunkTickLoad.get(chunkKey);
+
+ if (pendingTickingLoad != null) {
+ for (int i = 0, len = pendingTickingLoad.size(); i < len; ++i) {
+ if (pendingTickingLoad.get(i) == entry) {
+ pendingTickingLoad.remove(i);
+ break;
+ }
+ }
+
+ if (pendingTickingLoad.isEmpty()) {
+ this.pendingChunkTickLoad.remove(chunkKey);
+ }
+ }
+ }
+
+ public void onChunkSetTicking(final int chunkX, final int chunkZ) {
+ TickThread.softEnsureTickThread("async tick list chunk ticking update");
+ final ArrayList<NextTickListEntry<T>> pending = this.pendingChunkTickLoad.remove(Util.getCoordinateKey(chunkX, chunkZ));
+ if (pending == null) {
+ return;
+ }
+
+ for (int i = 0, size = pending.size(); i < size; ++i) {
+ final NextTickListEntry<T> entry = pending.get(i);
+ // already in all the relevant reference maps, just need to add to longScheduled or shortScheduled
+ this.addToSchedule(entry);
+ }
+ }
+
+ private void prepare() {
+ final long currentTick = this.world.getTime();
+
+ final ChunkProviderServer chunkProvider = this.world.getChunkProvider();
+
+ // here we setup what's going to tick
+
+ // we don't remove items from shortScheduled (but do from longScheduled) because they're cleared at the end of
+ // this tick
+ if (this.longScheduled.isEmpty() || this.longScheduled.first().getTargetTick() > currentTick) {
+ // nothing in longScheduled to worry about
+ final TickListServerInterval<T> interval = this.shortScheduled[this.shortScheduledIndex];
+ for (int i = 0, len = interval.byPriority.length; i < len; ++i) {
+ for (final Iterator<NextTickListEntry<T>> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) {
+ this.queueEntryForTick(iterator.next(), chunkProvider);
+ }
+ }
+ } else {
+ final TickListServerInterval<T> interval = this.shortScheduled[this.shortScheduledIndex];
+
+ // combine interval and longScheduled, keeping order
+ final Comparator<NextTickListEntry<T>> comparator = (Comparator)TickListServerInterval.ENTRY_COMPARATOR;
+ final Iterator<NextTickListEntry<T>> longScheduledIterator = this.longScheduled.iterator();
+ NextTickListEntry<T> longCurrent = longScheduledIterator.next();
+
+ for (int i = 0, len = interval.byPriority.length; i < len; ++i) {
+ for (final Iterator<NextTickListEntry<T>> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) {
+ final NextTickListEntry<T> shortCurrent = iterator.next();
+ if (longCurrent != null) {
+ // drain longCurrent until we can add shortCurrent
+ while (comparator.compare(longCurrent, shortCurrent) <= 0) {
+ this.queueEntryForTick(longCurrent, chunkProvider);
+ longScheduledIterator.remove();
+ if (longScheduledIterator.hasNext()) {
+ longCurrent = longScheduledIterator.next();
+ if (longCurrent.getTargetTick() > currentTick) {
+ longCurrent = null;
+ break;
+ }
+ } else {
+ longCurrent = null;
+ break;
+ }
+ }
+ }
+ this.queueEntryForTick(shortCurrent, chunkProvider);
+ }
+ }
+
+ // add remaining from long scheduled
+ for (;;) {
+ if (longCurrent == null || longCurrent.getTargetTick() > currentTick) {
+ break;
+ }
+ longScheduledIterator.remove();
+ this.queueEntryForTick(longCurrent, chunkProvider);
+
+ if (longScheduledIterator.hasNext()) {
+ longCurrent = longScheduledIterator.next();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void tick() {
+ TickThread.softEnsureTickThread("async tick list tick");
+ final ChunkProviderServer chunkProvider = this.world.getChunkProvider();
+
+ this.world.getMethodProfiler().enter("cleaning");
+ this.timingCleanup.startTiming();
+
+ this.prepare();
+
+ // this must be done here in case something schedules in the tick code
+ this.shortScheduled[this.shortScheduledIndex].clear();
+ this.shortScheduledIndex = getNextIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD);
+ this.nextTick = this.world.getTime() + 1;
+
+ this.timingCleanup.stopTiming();
+ this.world.getMethodProfiler().exitEnter("ticking");
+ this.timingTicking.startTiming();
+
+ for (final NextTickListEntry<T> toTick : this.toTickThisTick) {
+ if (toTick.tickState != STATE_PENDING_TICK) {
+ // onTickEnd gets called at end of tick
+ continue;
+ }
+ try {
+ if (chunkProvider.isTickingReadyMainThread(toTick.getPosition())) {
+ toTick.tickState = STATE_TICKING;
+ this.tickFunction.accept(toTick);
+ toTick.tickState = STATE_TICKED;
+ } else {
+ // re-schedule eventually
+ toTick.tickState = STATE_SCHEDULED;
+ this.addToNotTickingReady(toTick);
+ }
+ } catch (final Throwable thr) {
+ // start copy from TickListServer // TODO check on update
+ CrashReport crashreport = CrashReport.a(thr, "Exception while ticking");
+ CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being ticked");
+
+ CrashReportSystemDetails.a(crashreportsystemdetails, toTick.getPosition(), (IBlockData) null);
+ throw new ReportedException(crashreport);
+ // end copy from TickListServer
+ }
+ }
+
+ this.timingTicking.stopTiming();
+ this.world.getMethodProfiler().exit();
+ this.timingFinished.startTiming();
+
+ // finished ticking, actual cleanup time
+ for (int i = 0, len = this.toTickThisTick.size(); i < len; ++i) {
+ final NextTickListEntry<T> entry = this.toTickThisTick.poll();
+ if (entry.tickState != STATE_SCHEDULED) {
+ // some entries get re-scheduled due to their chunk not being loaded/at correct status, so do not
+ // call onTickEnd for them
+ this.onTickEnd(entry);
+ }
+ }
+
+ this.timingFinished.stopTiming();
+ }
+
+ private void onTickEnd(final NextTickListEntry<T> entry) {
+ entry.tickState = STATE_UNSCHEDULED;
+
+ final BlockPosition pos = entry.getPosition();
+ final long blockKey = Util.getBlockKey(pos);
+
+ final ArrayList<NextTickListEntry<T>> currentEntries = this.entriesByBlock.get(blockKey);
+
+ if (currentEntries.size() == 1) {
+ // it should contain our entry
+ this.entriesByBlock.remove(blockKey);
+ } else {
+ // it's more likely that this entry is at the start of the list than the end
+ for (int i = 0, len = currentEntries.size(); i < len; ++i) {
+ final NextTickListEntry<T> currentEntry = currentEntries.get(i);
+ if (currentEntry == entry) {
+ currentEntries.remove(i);
+ break;
+ }
+ }
+ }
+
+ final long chunkKey = Util.getCoordinateKey(entry.getPosition());
+
+ ObjectRBTreeSet<NextTickListEntry<T>> set = this.entriesByChunk.get(chunkKey);
+
+ set.remove(entry);
+
+ if (set.isEmpty()) {
+ this.entriesByChunk.remove(chunkKey);
+ }
+
+ // already removed from longScheduled or shortScheduled
+ }
+
+ @Override
+ public boolean isPendingTickThisTick(final BlockPosition blockposition, final T data) {
+ final ArrayList<NextTickListEntry<T>> entries = this.entriesByBlock.get(Util.getBlockKey(blockposition));
+
+ if (entries == null) {
+ return false;
+ }
+
+ for (int i = 0, size = entries.size(); i < size; ++i) {
+ final NextTickListEntry<T> entry = entries.get(i);
+ if (entry.getData() == data && entry.tickState == STATE_PENDING_TICK) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isScheduledForTick(final BlockPosition blockposition, final T data) {
+ final ArrayList<NextTickListEntry<T>> entries = this.entriesByBlock.get(Util.getBlockKey(blockposition));
+
+ if (entries == null) {
+ return false;
+ }
+
+ for (int i = 0, size = entries.size(); i < size; ++i) {
+ final NextTickListEntry<T> entry = entries.get(i);
+ if (entry.getData() == data && entry.tickState == STATE_SCHEDULED) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void schedule(BlockPosition blockPosition, T t, int i, TickListPriority tickListPriority) {
+ this.schedule(blockPosition, t, i + this.world.getTime(), tickListPriority);
+ }
+
+ public void schedule(final NextTickListEntry<T> entry) {
+ this.schedule(entry.getPosition(), entry.getData(), entry.getTargetTick(), entry.getPriority());
+ }
+
+ public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) {
+ TickThread.softEnsureTickThread("async tick list schedule");
+
+ final NextTickListEntry<T> entry = new NextTickListEntry<>(pos, data, targetTick, priority);
+ Validate.notNull(entry, "null entry");
+ if (this.excludeFromScheduling.test(entry.getData())) {
+ return;
+ }
+
+ final long blockKey = Util.getBlockKey(pos);
+
+ final ArrayList<NextTickListEntry<T>> currentEntries = this.entriesByBlock.computeIfAbsent(blockKey, (long keyInMap) -> new ArrayList<>(3));
+
+ if (currentEntries.isEmpty()) {
+ currentEntries.add(entry);
+ } else {
+ for (int i = 0, size = currentEntries.size(); i < size; ++i) {
+ final NextTickListEntry<T> currentEntry = currentEntries.get(i);
+
+ // entries are only blocked from scheduling if currentEntry.equals(toSchedule) && currentEntry is scheduled to tick (NOT including pending)
+ if (currentEntry.getData() == entry.getData() && currentEntry.tickState == STATE_SCHEDULED) {
+ // can't add
+ return;
+ }
+ }
+ currentEntries.add(entry);
+ }
+
+ entry.tickState = STATE_SCHEDULED;
+
+ this.entriesByChunk.computeIfAbsent(Util.getCoordinateKey(entry.getPosition()), (final long keyInMap) -> {
+ return new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR);
+ }).add(entry);
+
+ this.addToSchedule(entry);
+ }
+
+ @Override
+ public void scheduleAll(final Stream<NextTickListEntry<T>> stream) {
+ this.scheduleAll(stream.iterator());
+ }
+ public void scheduleAll(final Iterator<NextTickListEntry<T>> iterator) {
+ while (iterator.hasNext()) {
+ this.schedule(iterator.next());
+ }
+ }
+
+ // this is not the standard interception calculation, but it's the one vanilla uses
+ // i.e the y value is ignored? the x, z calc isn't correct?
+ // however for the copy op they use the correct intersection, after using this one of course...
+ private static boolean isBlockInSortof(final StructureBoundingBox boundingBox, final BlockPosition pos) {
+ return pos.getX() >= boundingBox.getMinX() && pos.getX() < boundingBox.getMaxX() && pos.getZ() >= boundingBox.getMinZ() && pos.getZ() < boundingBox.getMaxZ();
+ }
+
+ @Override
+ public List<NextTickListEntry<T>> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) {
+ TickThread.softEnsureTickThread("async tick list get");
+ if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) {
+ return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above
+ }
+
+ final int lowerChunkX = structureboundingbox.getMinX() >> 4;
+ final int upperChunkX = (structureboundingbox.getMaxX() - 1) >> 4; // subtract 1 since maxX is exclusive
+ final int lowerChunkZ = structureboundingbox.getMinZ() >> 4;
+ final int upperChunkZ = (structureboundingbox.getMaxZ() - 1) >> 4; // subtract 1 since maxZ is exclusive
+
+ final int xChunksLength = (upperChunkX - lowerChunkX + 1);
+ final int zChunksLength = (upperChunkZ - lowerChunkZ + 1);
+
+ final ObjectRBTreeSet<NextTickListEntry<T>>[] containingChunks = new ObjectRBTreeSet[xChunksLength * zChunksLength];
+
+ final int offset = (xChunksLength * -lowerChunkZ - lowerChunkX);
+ int totalEntries = 0;
+ for (int currChunkX = lowerChunkX; currChunkX <= upperChunkX; ++currChunkX) {
+ for (int currChunkZ = lowerChunkZ; currChunkZ <= upperChunkZ; ++currChunkZ) {
+ // todo optimize
+ //final int index = (currChunkX - lowerChunkX) + xChunksLength * (currChunkZ - lowerChunkZ);
+ final int index = offset + currChunkX + xChunksLength * currChunkZ;
+ final ObjectRBTreeSet<NextTickListEntry<T>> set = containingChunks[index] = this.entriesByChunk.get(Util.getCoordinateKey(currChunkX, currChunkZ));
+ if (set != null) {
+ totalEntries += set.size();
+ }
+ }
+ }
+
+ final List<NextTickListEntry<T>> ret = new ArrayList<>(totalEntries);
+
+ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED));
+
+ Util.mergeSortedSets((NextTickListEntry<T> entry) -> {
+ if (!isBlockInSortof(structureboundingbox, entry.getPosition())) {
+ return;
+ }
+ final int tickState = entry.tickState;
+ if ((tickState & matchOne) == 0) {
+ return;
+ }
+
+ ret.add(entry);
+ return;
+ }, TickListServerInterval.ENTRY_COMPARATOR, containingChunks);
+
+ if (removeReturned) {
+ for (NextTickListEntry<T> entry : ret) {
+ this.removeEntry(entry);
+ }
+ }
+
+ return ret;
+ }
+
+ @Override
+ public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) {
+ TickThread.softEnsureTickThread("async tick list copy");
+ // start copy from TickListServer // TODO check on update
+ List<NextTickListEntry<T>> list = this.getEntriesInBoundingBox(structureboundingbox, false, false);
+ Iterator<NextTickListEntry<T>> iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+ NextTickListEntry<T> nextticklistentry = iterator.next();
+
+ if (structureboundingbox.hasPoint( nextticklistentry.getPosition())) {
+ BlockPosition blockposition1 = nextticklistentry.getPosition().add(blockposition);
+ T t0 = nextticklistentry.getData();
+
+ this.schedule(new NextTickListEntry<>(blockposition1, t0, nextticklistentry.getTargetTick(), nextticklistentry.getPriority()));
+ }
+ }
+ // end copy from TickListServer
+ }
+
+ @Override
+ public List<NextTickListEntry<T>> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) {
+ TickThread.softEnsureTickThread("async tick list get");
+ // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks
+ // not at ticking status, and ticking status requires neighbours loaded
+ // so with this method we will reduce scheduler churning
+ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED));
+
+ final ObjectRBTreeSet<NextTickListEntry<T>> entries = this.entriesByChunk.get(Util.getCoordinateKey(chunkPos));
+
+ if (entries == null) {
+ return Collections.emptyList();
+ }
+
+ final List<NextTickListEntry<T>> ret = new ArrayList<>(entries.size());
+
+ for (NextTickListEntry<T> entry : entries) {
+ if ((entry.tickState & matchOne) == 0) {
+ continue;
+ }
+ ret.add(entry);
+ }
+
+ if (removeReturned) {
+ for (NextTickListEntry<T> entry : ret) {
+ this.removeEntry(entry);
+ }
+ }
+
+ return ret;
+ }
+
+ @Override
+ public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) {
+ TickThread.softEnsureTickThread("async tick list serialize");
+ // start copy from TickListServer // TODO check on update
+ List<NextTickListEntry<T>> list = this.getEntriesInChunk(chunkcoordintpair, false, true);
+
+ return TickListServer.serialize(this.getMinecraftKeyFrom, list, this.world.getTime());
+ // end copy from TickListServer
+ }
+
+ @Override
+ public int getTotalScheduledEntries() {
+ TickThread.softEnsureTickThread("async tick list get size");
+ // good thing this is only used in debug reports // TODO check on update
+ int ret = 0;
+
+ for (NextTickListEntry<T> entry : this.longScheduled) {
+ if (entry.tickState == STATE_SCHEDULED) {
+ ++ret;
+ }
+ }
+
+ for (Iterator<Long2ObjectMap.Entry<ArrayList<NextTickListEntry<T>>>> iterator = this.pendingChunkTickLoad.long2ObjectEntrySet().iterator(); iterator.hasNext();) {
+ ArrayList<NextTickListEntry<T>> list = iterator.next().getValue();
+
+ for (NextTickListEntry<T> entry : list) {
+ if (entry.tickState == STATE_SCHEDULED) {
+ ++ret;
+ }
+ }
+ }
+
+ for (TickListServerInterval<T> interval : this.shortScheduled) {
+ for (Iterable<NextTickListEntry<T>> set : interval.byPriority) {
+ for (NextTickListEntry<T> entry : set) {
+ if (entry.tickState == STATE_SCHEDULED) {
+ ++ret;
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+}
diff --git a/src/main/java/com/tuinity/tuinity/util/ChunkList.java b/src/main/java/com/tuinity/tuinity/util/ChunkList.java
new file mode 100644
index 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<Chunk> {
+
+ 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<Chunk> 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<Entity> {
+
+ 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<Entity> 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<IBlockData> 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<E extends Enum> {
+
+ private final Class<E> enumClass;
+ private long backingSet;
+
+ public OptimizedSmallEnumSet(final Class<E> 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<E> 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<E> 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<E> implements List<E> {
+
+ protected final List<E> wrapped;
+
+ public PrimaryThreadList(final List<E> 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<? extends E> collection) {
+ this.check();
+ return this.wrapped.addAll(collection);
+ }
+
+ @Override
+ public boolean addAll(final int index, final Collection<? extends E> 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<? super E> 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<E> operator) {
+ this.check();
+ this.wrapped.replaceAll(operator);
+ }
+
+ @Override
+ public void sort(final Comparator<? super E> 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<? super E> action) {
+ this.check();
+ this.wrapped.forEach(action);
+ }
+
+ @Override
+ public <T> T[] toArray(final T[] array) {
+ this.check();
+ return this.wrapped.toArray(array);
+ }
+
+ @Override
+ public <T> T[] toArray(final IntFunction<T[]> generator) {
+ this.check();
+ return this.wrapped.toArray(generator);
+ }
+
+ @Override
+ public Object[] toArray() {
+ this.check();
+ return this.wrapped.toArray();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ this.check();
+ return this.wrapped.iterator();
+ }
+
+ @Override
+ public ListIterator<E> listIterator() {
+ this.check();
+ return this.wrapped.listIterator();
+ }
+
+ @Override
+ public ListIterator<E> listIterator(final int index) {
+ this.check();
+ return this.wrapped.listIterator(index);
+ }
+
+ @Override
+ public List<E> subList(final int fromIndex, final int toIndex) {
+ this.check();
+ return new PrimaryThreadList<>(this.wrapped.subList(fromIndex, toIndex));
+ }
+
+ @Override
+ public Stream<E> stream() {
+ this.check();
+ return this.wrapped.stream();
+ }
+
+ @Override
+ public Stream<E> parallelStream() {
+ this.check();
+ return this.wrapped.parallelStream();
+ }
+
+ @Override
+ public Spliterator<E> 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<E> extends PrimaryThreadList<E> implements RandomAccess {
+ public RandomAccessPrimaryThreadList(final List<E> wrap) {
+ super(wrap);
+ }
+ }
+
+ public static <E> PrimaryThreadList<E> of(final List<E> 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<E> implements Set<E> {
+
+ protected final Set<E> wrapped;
+
+ public PrimaryThreadSet(final Set<E> 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<? extends E> 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<? super E> 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<? super E> action) {
+ this.check();
+ this.wrapped.forEach(action);
+ }
+
+ @Override
+ public <T> T[] toArray(final T[] array) {
+ this.check();
+ return this.wrapped.toArray(array);
+ }
+
+ @Override
+ public <T> T[] toArray(final IntFunction<T[]> generator) {
+ this.check();
+ return this.wrapped.toArray(generator);
+ }
+
+ @Override
+ public Object[] toArray() {
+ this.check();
+ return this.wrapped.toArray();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ this.check();
+ return this.wrapped.iterator();
+ }
+
+ @Override
+ public Stream<E> stream() {
+ this.check();
+ return this.wrapped.stream();
+ }
+
+ @Override
+ public Stream<E> parallelStream() {
+ this.check();
+ return this.wrapped.parallelStream();
+ }
+
+ @Override
+ public Spliterator<E> 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<E> extends PrimaryThreadSet<E> implements SortedSet<E> {
+
+ public SortedPrimaryThreadSet(final SortedSet<E> set) {
+ super(set);
+ }
+
+ @Override
+ public Comparator<? super E> comparator() {
+ return ((SortedSet<E>)this.wrapped).comparator();
+ }
+
+ @Override
+ public E first() {
+ this.check();
+ return ((SortedSet<E>)this.wrapped).first();
+ }
+
+ @Override
+ public E last() {
+ this.check();
+ return ((SortedSet<E>)this.wrapped).last();
+ }
+
+ @Override
+ public SortedSet<E> headSet(final E toElement) {
+ this.check();
+ return ((SortedSet<E>)this.wrapped).headSet(toElement);
+ }
+
+ @Override
+ public SortedSet<E> subSet(final E fromElement, final E toElement) {
+ this.check();
+ return ((SortedSet<E>)this.wrapped).subSet(fromElement, toElement);
+ }
+
+ @Override
+ public SortedSet<E> tailSet(final E fromElement) {
+ this.check();
+ return ((SortedSet<E>)this.wrapped).tailSet(fromElement);
+ }
+ }
+
+ public static class NavigablePrimaryThreadSet<E> extends SortedPrimaryThreadSet<E> implements NavigableSet<E> {
+
+ public NavigablePrimaryThreadSet(final NavigableSet<E> set) {
+ super(set);
+ }
+
+ @Override
+ public E ceiling(final E element) {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).ceiling(element);
+ }
+
+ @Override
+ public E floor(final E element) {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).floor(element);
+ }
+
+ @Override
+ public E higher(final E element) {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).higher(element);
+ }
+
+ @Override
+ public E lower(final E element) {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).lower(element);
+ }
+
+ @Override
+ public E pollFirst() {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).pollFirst();
+ }
+
+ @Override
+ public E pollLast() {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).pollLast();
+ }
+
+ @Override
+ public Iterator<E> descendingIterator() {
+ this.check();
+ return ((NavigableSet<E>)this.wrapped).descendingIterator();
+ }
+
+ @Override
+ public final NavigableSet<E> descendingSet() {
+ this.check();
+ return new NavigablePrimaryThreadSet<>(((NavigableSet<E>)this.wrapped).descendingSet());
+ }
+
+ @Override
+ public NavigableSet<E> headSet(final E toElement, final boolean inclusive) {
+ this.check();
+ return new NavigablePrimaryThreadSet<>(((NavigableSet<E>)this.wrapped).headSet(toElement, inclusive));
+ }
+
+ @Override
+ public NavigableSet<E> subSet(final E fromElement, final boolean fromInclusive, final E toElement, final boolean toInclusive) {
+ this.check();
+ return new NavigablePrimaryThreadSet<>(((NavigableSet<E>)this.wrapped).subSet(fromElement, fromInclusive, toElement, toInclusive));
+ }
+
+ @Override
+ public NavigableSet<E> tailSet(final E fromElement, final boolean inclusive) {
+ this.check();
+ return new NavigablePrimaryThreadSet<>(((NavigableSet<E>)this.wrapped).tailSet(fromElement, inclusive));
+ }
+ }
+
+ public static <E> PrimaryThreadSet<E> of(final Set<E> set) {
+ if (set instanceof NavigableSet) {
+ return new NavigablePrimaryThreadSet<>((NavigableSet<E>)set);
+ } else if (set instanceof SortedSet) {
+ return new SortedPrimaryThreadSet<>((SortedSet<E>)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 <T> void mergeSortedSets(final Consumer<T> consumer, final Comparator<? super T> comparator, final SortedSet<T>...sets) {
+ final ObjectRBTreeSet<T> 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<T> 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<K> extends ObjectAVLTreeSet<K> {
+
+ 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<K> cachedIterator = this.iterator();
+
+ {
+ this.nullIterator(this.cachedIterator);
+ }
+
+ @Override
+ public boolean removeIf(Predicate<? super K> filter) {
+ if (this.isEmpty()) {
+ return false;
+ }
+
+ if (this.cachedIterator == null) {
+ return super.removeIf(filter); // recursive...?
+ }
+
+ final ObjectBidirectionalIterator<K> 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<K> 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<K> 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<E> {
+
+ /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */
+
+ private final Object2LongOpenHashMap<E> objectToLastCoordinate = new Object2LongOpenHashMap<>();
+ private final Object2IntOpenHashMap<E> 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<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.3f);
+ private final PooledLinkedHashSets<E> pooledHashSets;
+
+ private final ChangeCallback<E> addCallback;
+ private final ChangeCallback<E> removeCallback;
+
+ public AreaMap() {
+ this(new PooledLinkedHashSets<>());
+ }
+
+ // let users define a "global" or "shared" pooled sets if they wish
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
+ this(pooledHashSets, null, null);
+ }
+
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback) {
+ this.pooledHashSets = pooledHashSets;
+ this.addCallback = addCallback;
+ this.removeCallback = removeCallback;
+ }
+
+ @Nullable
+ public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final long key) {
+ return this.areaMap.get(key);
+ }
+
+ @Nullable
+ public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final ChunkCoordIntPair chunkPos) {
+ return this.areaMap.get(Util.getCoordinateKey(chunkPos));
+ }
+
+ @Nullable
+ public PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> 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<Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator();
+ iterator.hasNext();) {
+
+ final Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> entry = iterator.next();
+ final long key = entry.getLongKey();
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> empty = this.getEmptySetFor(object);
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> current = this.areaMap.putIfAbsent(key, empty);
+
+ if (current != null) {
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> current = this.areaMap.get(key);
+
+ if (current == null) {
+ throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")");
+ }
+
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> {
+
+ // 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<E> 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<EntityPlayer> {
+
+ public PlayerAreaMap() {
+ super();
+ }
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets) {
+ super(pooledHashSets);
+ }
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
+ final ChangeCallback<EntityPlayer> removeCallback) {
+ super(pooledHashSets, addCallback, removeCallback);
+ }
+
+ @Override
+ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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<E> {
+
+ /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */
+
+ // we really want to avoid that equals() check as much as possible...
+ protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f);
+
+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> 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<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
+
+ if (cached != null) {
+ decrementReferenceCount(current);
+
+ if (cached.referenceCount == 0) {
+ // bring the map back from the dead
+ PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ if (current.set.size() == 1) {
+ decrementReferenceCount(current);
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
+
+ if (cached != null) {
+ decrementReferenceCount(current);
+
+ if (cached.referenceCount == 0) {
+ // bring the map back from the dead
+ PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> extends ObjectOpenHashSet<E> {
+
+ public RawSetObjectLinkedOpenHashSet() {
+ super();
+ }
+
+ public RawSetObjectLinkedOpenHashSet(final int capacity) {
+ super(capacity);
+ }
+
+ public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) {
+ super(capacity, loadFactor);
+ }
+
+ @Override
+ public RawSetObjectLinkedOpenHashSet<E> clone() {
+ return (RawSetObjectLinkedOpenHashSet<E>)super.clone();
+ }
+
+ public E[] getRawSet() {
+ return this.key;
+ }
+ }
+
+ public static final class PooledObjectLinkedOpenHashSet<E> {
+
+ private static final WeakReference NULL_REFERENCE = new WeakReference<>(null);
+
+ final RawSetObjectLinkedOpenHashSet<E> set;
+ int referenceCount; // -1 if special
+ int hash; // optimize hashcode
+
+ // add cache
+ WeakReference<E> lastAddObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+ // remove cache
+ WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
+
+ public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets<E> pooledSets) {
+ this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final E single) {
+ this((PooledLinkedHashSets<E>)null);
+ this.referenceCount = -1;
+ this.add(single);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> 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<E> map) {
+ this.lastAddObject = new WeakReference<>(element);
+ this.lastAddMap = new WeakReference<>(map);
+ }
+
+ void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> 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/com/tuinity/tuinity/util/set/LinkedSortedSet.java b/src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java
new file mode 100644
index 0000000000..c726ef3c2b
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/set/LinkedSortedSet.java
@@ -0,0 +1,142 @@
+package com.tuinity.tuinity.util.set;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public final class LinkedSortedSet<E> implements Iterable<E> {
+
+ public final Comparator<? super E> comparator;
+
+ protected Link<E> head;
+ protected Link<E> tail;
+
+ public LinkedSortedSet() {
+ this((Comparator)Comparator.naturalOrder());
+ }
+
+ public LinkedSortedSet(final Comparator<? super E> comparator) {
+ this.comparator = comparator;
+ }
+
+ public void clear() {
+ this.head = this.tail = null;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return new Iterator<E>() {
+
+ Link<E> next = LinkedSortedSet.this.head;
+
+ @Override
+ public boolean hasNext() {
+ return this.next != null;
+ }
+
+ @Override
+ public E next() {
+ final Link<E> next = this.next;
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ this.next = next.next;
+ return next.element;
+ }
+ };
+ }
+
+ public boolean addLast(final E element) {
+ final Comparator<? super E> comparator = this.comparator;
+
+ Link<E> curr = this.tail;
+ if (curr != null) {
+ int compare;
+
+ while ((compare = comparator.compare(element, curr.element)) < 0) {
+ Link<E> prev = curr;
+ curr = curr.prev;
+ if (curr != null) {
+ continue;
+ }
+ this.head = prev.prev = new Link<>(element, null, prev);
+ return true;
+ }
+
+ if (compare != 0) {
+ // insert after curr
+ final Link<E> next = curr.next;
+ final Link<E> insert = new Link<>(element, curr, next);
+ curr.next = insert;
+
+ if (next == null) {
+ this.tail = insert;
+ } else {
+ next.prev = insert;
+ }
+ return true;
+ }
+
+ return false;
+ } else {
+ this.head = this.tail = new Link<>(element);
+ return true;
+ }
+ }
+
+ public boolean addFirst(final E element) {
+ final Comparator<? super E> comparator = this.comparator;
+
+ Link<E> curr = this.head;
+ if (curr != null) {
+ int compare;
+
+ while ((compare = comparator.compare(element, curr.element)) > 0) {
+ Link<E> prev = curr;
+ curr = curr.next;
+ if (curr != null) {
+ continue;
+ }
+ this.tail = prev.next = new Link<>(element, prev, null);
+ return true;
+ }
+
+ if (compare != 0) {
+ // insert before curr
+ final Link<E> prev = curr.prev;
+ final Link<E> insert = new Link<>(element, prev, curr);
+ curr.prev = insert;
+
+ if (prev == null) {
+ this.head = insert;
+ } else {
+ prev.next = insert;
+ }
+ return true;
+ }
+
+ return false;
+ } else {
+ this.head = this.tail = new Link<>(element);
+ return true;
+ }
+ }
+
+ protected static final class Link<E> {
+ public E element;
+ public Link<E> prev;
+ public Link<E> next;
+
+ public Link() {}
+
+ public Link(final E element) {
+ this.element = element;
+ }
+
+ public Link(final E element, final Link<E> prev, final Link<E> next) {
+ this.element = element;
+ this.prev = prev;
+ this.next = next;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/ArraySetSorted.java b/src/main/java/net/minecraft/server/ArraySetSorted.java
index 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<T> extends AbstractSet<T> {
private final Comparator<T> 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<T> comparator) {
this.a = comparator;
@@ -22,6 +23,42 @@ public class ArraySetSorted<T> extends AbstractSet<T> {
}
}
+ // Tuinity start - optimise removeIf
+ @Override
+ public boolean removeIf(Predicate<? super T> 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 <T extends Comparable<T>> ArraySetSorted<T> 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<Entity> 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<Entity> list, @Nullable Predicate<? super Entity> 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 <T extends Entity> void a(@Nullable EntityTypes<?> entitytypes, AxisAlignedBB axisalignedbb, List<? super T> list, Predicate<? super T> 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 <T extends Entity> void a(Class<? extends T> oclass, AxisAlignedBB axisalignedbb, List<T> list, @Nullable Predicate<? super T> 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<ObjectSet<EntityPlayer>> c = new Long2ObjectOpenHashMap();
public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> 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<PlayerChunk> 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<PlayerChunk> pendingChunkUpdates = new java.util.ArrayDeque<>(); // PAIL pendingChunkUpdates // Paper - use a queue // Tuinity - use a better queue
private final ChunkTaskQueueSorter i;
private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> j;
private final Mailbox<ChunkTaskQueueSorter.b> 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<Ticket<Long>> delayedChunks = new Long2ObjectOpenHashMap<>();
+ public final void removeTickets(long chunk, TicketType<?> type) {
+ ArraySetSorted<Ticket<?>> 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<Ticket<Long>> computeFuntion = (long key) -> {
+ Ticket<Long> ret = new Ticket<>(TicketType.DELAYED_UNLOAD, -1, ++ChunkMapDistance.this.nextUnloadId);
+ ret.isCached = true;
+ return ret;
+ };
+
+ private void computeDelayedTicketFor(long chunk, int removedLevel, ArraySetSorted<Ticket<?>> tickets) {
+ int lowestLevel = getLowestTicketLevel(tickets);
+ if (removedLevel > lowestLevel) {
+ return;
+ }
+ final Ticket<Long> 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<Runnable> 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<ArraySetSorted<Ticket<?>>> 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<Ticket<?>> arraysetsorted) { return a(arraysetsorted); } // Tuinity - OBFHELPER
private static int a(ArraySetSorted<Ticket<?>> 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<Ticket<?>> 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<Ticket<?>> 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<Ticket<?>> 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<EntityPlayer> 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 <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity
Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
for (java.util.Iterator<ArraySetSorted<Ticket<?>>> 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<EntityPlayer> 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..c39029c3ad 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);
+ public final boolean isTickingReady(final BlockPosition pos) { return this.a(pos); } // Tuinity - OBFHELPER
+ @Override public boolean a(BlockPosition blockposition) { // Tuinity - OBFHELPER
+ // Tuinity start - optimize is ticking ready type functions
+ // is ticking ready
+ PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(blockposition));
+ return playerChunk != null && playerChunk.isTickingReady();
+ // Tuinity end - optimize is ticking ready type functions
}
public boolean b(Entity entity) {
- long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4);
-
- return this.a(i, PlayerChunk::c);
+ // Tuinity start - optimize is ticking ready type functions
+ // is full chunk ready
+ if (Thread.currentThread() == this.serverThread) {
+ return this.getChunkAtIfLoadedMainThreadNoCache(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null;
+ }
+ return this.getChunkAtIfLoadedImmediately(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null;
+ // Tuinity end - optimize is ticking ready type functions
}
private boolean a(long i, Function<PlayerChunk, CompletableFuture<Either<Chunk, PlayerChunk.Failure>>> 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<Chunk> 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<Chunk> 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<EntityPlayer> 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<it.unimi.dsi.fastutil.objects.Object2BooleanMap.Entry<Chunk>> iterator = this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().fastIterator(this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().last()); iterator.hasPrevious(); ) {
+ it.unimi.dsi.fastutil.objects.Object2BooleanMap.Entry<Chunk> 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<HeightMap.Type> z;
+ private final EnumSet<HeightMap.Type> z; public final HeightMap.Type[] heightMaps; // Tuinity
private static CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> 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<ServerCommand> serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<ServerCommand>(); // Paper - use a proper queue
+ private final java.util.Queue<ServerCommand> 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..3cdba5c42a 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -208,6 +208,125 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
}
// CraftBukkit end
+ // Tuinity start
+ boolean isLegacyTrackingEntity = this instanceof EntityPlayer && (this.getClass() != EntityPlayer.class); // TODO temp, fix citizens...
+
+ com.tuinity.tuinity.util.map.PlayerAreaMap trackingAreaMap;
+ com.tuinity.tuinity.util.map.PlayerAreaMap unTrackingAreaMap;
+
+ final void acquireTrackingMap(PlayerChunkMap chunkMap) {
+ int key = org.spigotmc.TrackingRange.getTrackingRangeType(this).ordinal();
+ this.trackingAreaMap = chunkMap.playerEntityTrackerTrackMaps[key];
+ this.unTrackingAreaMap = chunkMap.playerEntityTrackerUntrackMaps[key];
+ }
+
+ final void releaseTrackingMap(PlayerChunkMap chunkMap) {
+ this.trackingAreaMap = null;
+ this.unTrackingAreaMap = null;
+ }
+
+ 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<EntityPlayer> inRange = this.trackingAreaMap.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 +1490,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<EntityPlayer> cachedSingleHashSet; // Paper
+ // Tuinity start
+ public final com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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<Entity> 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<IBlockData> i;
- private static final Map<String, HeightMap.Type> j = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> {
+ private static final Map<String, HeightMap.Type> 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<IBlockData> 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<R extends Runnable> implements Mailbox<R
private final String b;
private static final Logger LOGGER = LogManager.getLogger();
- private final Queue<R> d = Queues.newConcurrentLinkedQueue();
+ private final ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<R> 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<Entity> predicate) {
+ @Nullable default EntityHuman a(double d0, double d1, double d2, double d3, @Nullable Predicate<Entity> 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<Entity> 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<Entity> 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 extends EntityLiving> T a(Class<? extends T> 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 extends EntityLiving> T a(List<? extends T> list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) {
+ @Nullable default <T extends EntityLiving> T getNearestEntityForPathFinding(List<? extends T> 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 extends EntityLiving> T a(List<? extends T> list, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2) { // Tuinity - OBFHELPER
double d3 = -1.0D;
T t0 = null;
Iterator<? extends T> iterator = list.iterator(); // Paper - decompile fix
@@ -187,6 +199,11 @@ public interface IEntityAccess {
}
default List<EntityHuman> a(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) {
+ // Tuinity start - allow overriding in WorldServer
+ return this.getNearestPlayersForPathFinding(pathfindertargetcondition, entityliving, axisalignedbb);
+ }
+ default List<EntityHuman> getNearestPlayersForPathFinding(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) {
+ // Tuinity end - allow overriding in WorldServer
List<EntityHuman> 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<LightEngineStorageB
if (enumdirection == null) {
return 15;
} 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/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<M extends LightEngineStorageArray<M>, 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<LightEngineStorageSky
if (k >= 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<M extends LightEngineStorageArray<M>> 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<NibbleArray> i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap());
+ protected final Long2ObjectOpenHashMap<NibbleArray> synchronized_map_real = new Long2ObjectOpenHashMap<>(); // Tuinity - store wrapped map, we need fastIterator
+ protected final Long2ObjectMap<NibbleArray> 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<M extends LightEngineStorageArray<M>> 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<M extends LightEngineStorageArray<M>> 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<M extends LightEngineStorageArray<M>> 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<M extends LightEngineStorageArray<M>> 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<M extends LightEngineStorageArray<
private final long[] b = new long[2];
private final NibbleArray[] c = new NibbleArray[2];
private boolean d;
- protected final Long2ObjectOpenHashMap<NibbleArray> a;
+ protected final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<NibbleArray> data; // Tuinity - avoid copying light data
+ protected final boolean isVisible; // Tuinity - avoid copying light data
- protected LightEngineStorageArray(Long2ObjectOpenHashMap<NibbleArray> long2objectopenhashmap) {
- this.a = long2objectopenhashmap;
+ // Tuinity start - avoid copying light data
+ protected LightEngineStorageArray(com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<NibbleArray> 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<M extends LightEngineStorageArray<
public abstract M b();
public void a(long i) {
- this.a.put(i, ((NibbleArray) this.a.get(i)).b());
+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Tuinity - avoid copying light data
+ this.data.queueUpdate(i, ((NibbleArray) this.data.getUpdating(i)).b()); // Tuinity - avoid copying light data
this.c();
}
public boolean b(long i) {
- return this.a.containsKey(i);
+ return this.isVisible ? this.data.getVisibleAsync(i) != null : this.data.getUpdating(i) != null; // Tuinity - avoid copying light data
}
@Nullable
@@ -37,7 +45,7 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray<
}
}
- NibbleArray nibblearray = (NibbleArray) this.a.get(i);
+ NibbleArray nibblearray = (NibbleArray) (this.isVisible ? this.data.getVisibleAsync(i) : this.data.getUpdating(i)); // Tuinity - avoid copying light data
if (nibblearray == null) {
return null;
@@ -58,11 +66,13 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray<
@Nullable
public NibbleArray d(long i) {
- return (NibbleArray) this.a.remove(i);
+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Tuinity - avoid copying light data
+ return (NibbleArray) this.data.queueRemove(i); // Tuinity - avoid copying light data
}
public void a(long i, NibbleArray nibblearray) {
- this.a.put(i, nibblearray);
+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Tuinity - avoid copying light data
+ this.data.queueUpdate(i, nibblearray); // Tuinity - avoid copying light data
}
public void c() {
diff --git a/src/main/java/net/minecraft/server/LightEngineStorageBlock.java b/src/main/java/net/minecraft/server/LightEngineStorageBlock.java
index 0f7f4744d9..21a15c3b3a 100644
--- a/src/main/java/net/minecraft/server/LightEngineStorageBlock.java
+++ b/src/main/java/net/minecraft/server/LightEngineStorageBlock.java
@@ -5,7 +5,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public class LightEngineStorageBlock extends LightEngineStorage<LightEngineStorageBlock.a> {
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<LightEngineStora
public static final class a extends LightEngineStorageArray<LightEngineStorageBlock.a> {
- public a(Long2ObjectOpenHashMap<NibbleArray> long2objectopenhashmap) {
- super(long2objectopenhashmap);
+ public a(com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<NibbleArray> 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<LightEngineStorage
private volatile boolean p;
protected LightEngineStorageSky(ILightAccess ilightaccess) {
- super(EnumSkyBlock.SKY, ilightaccess, new LightEngineStorageSky.a(new Long2ObjectOpenHashMap(), new Long2IntOpenHashMap(), Integer.MAX_VALUE));
+ super(EnumSkyBlock.SKY, ilightaccess, new LightEngineStorageSky.a(new com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<>(), 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<LightEngineStorage
long j = SectionPosition.e(i);
int k = SectionPosition.c(j);
LightEngineStorageSky.a lightenginestoragesky_a = (LightEngineStorageSky.a) this.e;
- int l = lightenginestoragesky_a.c.get(SectionPosition.f(j));
+ int l = lightenginestoragesky_a.otherData.getVisibleAsync(SectionPosition.f(j)); // Tuinity - avoid copying light data
if (l != lightenginestoragesky_a.b && k < l) {
NibbleArray nibblearray = this.a(lightenginestoragesky_a, j); // Paper - decompile fix
@@ -54,14 +54,14 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
if (((LightEngineStorageSky.a) this.f).b > 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<LightEngineStorage
int k = SectionPosition.c(i);
- if (((LightEngineStorageSky.a) this.f).c.get(j) == k + 1) {
+ if (((LightEngineStorageSky.a) this.f).otherData.getUpdating(j) == k + 1) { // Tuinity - avoid copying light data
long l;
for (l = i; !this.g(l) && this.a(k); l = SectionPosition.a(l, EnumDirection.DOWN)) {
@@ -109,12 +109,12 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
}
if (this.g(l)) {
- ((LightEngineStorageSky.a) this.f).c.put(j, k + 1);
+ ((LightEngineStorageSky.a) this.f).otherData.queueUpdate(j, k + 1); // Tuinity - avoid copying light data
if (flag) {
this.q(l);
}
} else {
- ((LightEngineStorageSky.a) this.f).c.remove(j);
+ ((LightEngineStorageSky.a) this.f).otherData.queueRemove(j); // Tuinity - avoid copying light data
}
}
@@ -128,7 +128,7 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
protected void b(long i, boolean flag) {
this.d();
if (flag && this.o.add(i)) {
- int j = ((LightEngineStorageSky.a) this.f).c.get(i);
+ int j = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(i); // Tuinity - avoid copying light data
if (j != ((LightEngineStorageSky.a) this.f).b) {
long k = SectionPosition.b(SectionPosition.b(i), j - 1, SectionPosition.d(i));
@@ -155,7 +155,7 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
return nibblearray;
} else {
long j = SectionPosition.a(i, EnumDirection.UP);
- int k = ((LightEngineStorageSky.a) this.f).c.get(SectionPosition.f(i));
+ int k = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(SectionPosition.f(i)); // Tuinity - avoid copying light data
if (k != ((LightEngineStorageSky.a) this.f).b && SectionPosition.c(j) < k) {
NibbleArray nibblearray1;
@@ -298,7 +298,7 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
if (!this.o.contains(l)) {
return false;
} else {
- int i1 = ((LightEngineStorageSky.a) this.f).c.get(l);
+ int i1 = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(l); // Tuinity - avoid copying light data
return SectionPosition.c(i1) == j + 16;
}
@@ -307,7 +307,7 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
protected boolean n(long i) {
long j = SectionPosition.f(i);
- int k = ((LightEngineStorageSky.a) this.f).c.get(j);
+ int k = ((LightEngineStorageSky.a) this.f).otherData.getUpdating(j); // Tuinity - avoid copying light data
return k == ((LightEngineStorageSky.a) this.f).b || SectionPosition.c(i) >= k;
}
@@ -321,18 +321,20 @@ public class LightEngineStorageSky extends LightEngineStorage<LightEngineStorage
public static final class a extends LightEngineStorageArray<LightEngineStorageSky.a> {
private int b;
- private final Long2IntOpenHashMap c;
+ private final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Int otherData; // Tuinity - avoid copying light data
- public a(Long2ObjectOpenHashMap<NibbleArray> 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<NibbleArray> 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<PlayerChunk> visibleChunks = chunkMap.visibleChunks;
+ // Tuinity - replace chunk map
ChunkMapDistance chunkMapDistance = chunkMap.getChunkMapDistanceManager();
- List<PlayerChunk> allChunks = new ArrayList<>(visibleChunks.values());
+ List<PlayerChunk> allChunks = chunkMap.chunkMap.getUpdatingValuesCopy(); // Tuinity - replace chunk map
List<EntityPlayer> 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 <E> boolean hasCommonElement(Set<E> set0, Set<E> set1) {
+ java.util.Iterator<E> iterator;
+ Set<E> 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<TickTas
public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
//public ConsoleReader reader; // Paper
public static int currentTick = 0; // Paper - Further improve tick loop
- public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
+ public final java.util.Queue<Runnable> 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<TickTas
// CraftBukkit start
// Run tasks that are waiting on processing
MinecraftTimings.processQueueTimer.startTiming(); // Spigot
- while (!processQueue.isEmpty()) {
- processQueue.remove().run();
- }
+ ((ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<Runnable>)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<Packet<?>> {
return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
});
private final EnumProtocolDirection h;
- private final Queue<NetworkManager.QueuedPacket> packetQueue = Queues.newConcurrentLinkedQueue(); private final Queue<NetworkManager.QueuedPacket> getPacketQueue() { return this.packetQueue; } // Paper - OBFHELPER
+ private final Queue<NetworkManager.QueuedPacket> packetQueue = new ca.spottedleaf.concurrentutil.queue.MultiThreadedQueue<>(); private final Queue<NetworkManager.QueuedPacket> 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<Packet<?>> {
this.channel.config().setAutoRead(false);
}
+ java.util.List<Packet> 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<Packet> 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<PacketListenerPlayOut> {
// Paper start
private final java.util.List<Packet> 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<Packet> getExtraPackets() {
@@ -73,7 +73,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
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<PacketListenerPlayOut> {
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<T, F> {
public static final class a implements PairedQueue<PairedQueue.b, Runnable> {
- private final List<Queue<Runnable>> a;
+ private final List<Queue<Runnable>> a; private final List<Queue<Runnable>> 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<Runnable> queue = this.getQueues().get(i);
+ Runnable ret = queue.poll();
+ if (ret != null) {
+ return ret;
}
-
- Queue<Runnable> 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<T, F> {
@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<Runnable> 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<PathfinderGoal.Type> a = EnumSet.noneOf(PathfinderGoal.Type.class);
+ private final OptimizedSmallEnumSet<Type> 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<PathfinderGoal.Type> 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<PathfinderGoal.Type> i() {
- return this.a;
+ // Tuinity start - reduce garbage on heap
+ public com.tuinity.tuinity.util.OptimizedSmallEnumSet<PathfinderGoal.Type> 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<PathfinderGoal.Type, PathfinderGoalWrapped> c = new EnumMap(PathfinderGoal.Type.class);
private final Set<PathfinderGoalWrapped> d = Sets.newLinkedHashSet();private Set<PathfinderGoalWrapped> getTasks() { return d; }// Paper - OBFHELPER
private final GameProfilerFiller e;
- private final EnumSet<PathfinderGoal.Type> f = EnumSet.noneOf(PathfinderGoal.Type.class);
+ private final OptimizedSmallEnumSet<PathfinderGoal.Type> 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<PathfinderGoalWrapped> 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<PathfinderGoalWrapped> 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<PathfinderGoalWrapped> iterator = this.d.iterator(); iterator.hasNext();) {
+ PathfinderGoalWrapped wrappedGoal = iterator.next();
+ if (wrappedGoal.g()) {
+ continue;
+ }
+
+ OptimizedSmallEnumSet<PathfinderGoal.Type> 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<PathfinderGoalWrapped> 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<PathfinderGoal.Type> i() {
- return this.a.i();
+ // Tuinity start - reduce garbage on heap
+ public com.tuinity.tuinity.util.OptimizedSmallEnumSet<PathfinderGoal.Type> 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<EntityPlayer> playersInMobSpawnRange;
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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<EntityPlayer> 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<Either<IChunkAccess, PlayerChunk.Failure>> 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..b231af78ec 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<PlayerChunk> updatingChunks = new Long2ObjectLinkedOpenHashMap();
- public volatile Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks;
+ //public final Long2ObjectLinkedOpenHashMap<PlayerChunk> updatingChunks = new Long2ObjectLinkedOpenHashMap(); // Tuinity - replace chunk map
+ //public volatile Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks; // Tuinity - replace chunk map
+ public final com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<PlayerChunk> chunkMap = new com.tuinity.tuinity.chunk.QueuedChangesMapLong2Object<>(8192, 0.7f); // Tuinity - replace chunk map
private final Long2ObjectLinkedOpenHashMap<PlayerChunk> 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<PlayerChunkMap.EntityTracker> trackedEntities;
private final Queue<Runnable> 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<EntityPlayer> 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<EntityPlayer> players = new java.util.ArrayList<>(256);
+
+ protected final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Packet[]> 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<EntityPlayer> 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<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator<?> chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<PlayerChunk> list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList());
+ List<PlayerChunk> 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<PlayerChunk> 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<PlayerChunk> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> 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<EntityPlayer> playersTracking = entity.trackingAreaMap.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,105 @@ 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);
+ }
+ entity.releaseTrackingMap(this);
+ // 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<EntityPlayer> inRange =
+ tracked.unTrackingAreaMap.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<EntityPlayer> list = Lists.newArrayList();
List<EntityPlayer> list1 = this.world.getPlayers();
@@ -1554,6 +2296,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 +2307,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
PacketDebug.a(this.world, chunk.getPos());
List<Entity> list = Lists.newArrayList();
List<Entity> list1 = Lists.newArrayList();
+ if (!this.optimisedTrackerEnabled) { // Tuinity - implement optimized tracker
ObjectIterator objectiterator = this.trackedEntities.values().iterator();
while (objectiterator.hasNext()) {
@@ -1581,6 +2325,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<EntityPlayer> playersInRange
+ = entityInChunk.trackingAreaMap.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 +2387,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 +2453,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 +2468,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 +2510,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<EntityPlayer> 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<HeightMap.Type> enumset = this.getChunkStatus().h();
+ HeightMap.Type[] enumset = this.getChunkStatus().heightMaps; // Tuinity - reduce iterator creation
EnumSet<HeightMap.Type> 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<RegionFileCompression> d = new Int2ObjectOpenHashMap();
+ private static final Int2ObjectMap<RegionFileCompression> d = new Int2ObjectOpenHashMap(); static final Int2ObjectMap<RegionFileCompression> 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<T> implements Mailbox<T>, AutoCloseable, Runnable {
private final String e;
public static ThreadedMailbox<Runnable> 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<? super T, ? extends Runnable> 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<T> implements Comparable<Ticket<?>> {
private final TicketType<T> 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<T> tickettype, int i, T t0) {
this.a = tickettype;
@@ -60,6 +61,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
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<T> {
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
public static final TicketType<Integer> ANTIXRAY = a("antixray", Integer::compareTo); // Paper - Anti-Xray
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
+ public static final TicketType<Long> DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads by 30 seconds
public static <T> TicketType<T> a(String s, Comparator<T> 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..5f69a018d9 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.WorldConnfig 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.WorldConnfig(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<T> 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<T> 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..a438c6e29b 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<Entity> globalEntityList = Lists.newArrayList();
- public final Int2ObjectMap<Entity> entitiesById = new Int2ObjectLinkedOpenHashMap();
+ public final Int2ObjectMap<Entity> entitiesById = new Int2ObjectLinkedOpenHashMap(); // Tuinity - diff on change, we expect Int2ObjectLinkedOpenHashMap
private final Map<UUID, Entity> entitiesByUUID = Maps.newHashMap();
private final Queue<Entity> entitiesToAdd = Queues.newArrayDeque();
public final List<EntityPlayer> 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
+ 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
+
+ // 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<EntityPlayer> 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<Entity> 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<EntityPlayer> 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<EntityPlayer> findPlayersInRange(double fromX, double fromY, double fromZ, double distance) {
+ return this.findPlayersInRange(fromX, fromY, fromZ, distance, new java.util.ArrayList<>(), null);
+ }
+
+ @Nullable
+ public List<EntityPlayer> findPlayersInRange(double fromX, double fromY, double fromZ, double distance, @Nonnull List<EntityPlayer> players, @Nullable Predicate<Entity> predicate) {
+ double maximumDistanceSquared;
+ if (distance < 0) {
+ maximumDistanceSquared = Double.MAX_VALUE;
+ } else {
+ maximumDistanceSquared = distance * distance;
+ }
+
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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<EntityPlayer> 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<Entity> 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<EntityPlayer> 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<EntityPlayer> 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<EntityHuman> getNearestPlayersForPathFinding(PathfinderTargetCondition pathfindertargetcondition, EntityLiving entityliving, AxisAlignedBB axisalignedbb) {
+ List<EntityHuman> 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<EntityPlayer> 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<Entity> 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<Entity> 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<EntityPlayer> oldRange =
+ entity.trackingAreaMap.getObjectsInRange(prevChunkX, prevChunkZ);
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newRange =
+ entity.trackingAreaMap.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.unTrackingAreaMap.getObjectsInRange(prevChunkX, prevChunkZ);
+ newRange = entity.unTrackingAreaMap.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..3a0e027e67 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
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<PlayerChunk> chunks = world.getChunkProvider().playerChunkMap.visibleChunks;
+ Iterable<PlayerChunk> 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<PlayerChunk> 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<PlayerChunk> 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<Entity> getEntities() {
- List<Entity> list = new ArrayList<Entity>();
+ List<Entity> list = new ArrayList<Entity>(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<LivingEntity> getLivingEntities() {
- List<LivingEntity> list = new ArrayList<LivingEntity>();
+ List<LivingEntity> list = new ArrayList<LivingEntity>(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 <T extends Entity> Collection<T> getEntitiesByClass(Class<T> clazz) {
Collection<T> list = new ArrayList<T>();
- 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<Entity> getEntitiesByClasses(Class<?>... classes) {
Collection<Entity> list = new ArrayList<Entity>();
- 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<Entity> slice : chunk.entitySlices )
- {
- for ( Entity entity : (Collection<Entity>) 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