mirror of
https://github.com/PurpurMC/Purpur.git
synced 2026-02-17 16:37:43 +01:00
Upstream has released updates that appears to apply and compile correctly Paper Changes: 9a7ca3db Updated Upstream (Bukkit/CraftBukkit/Spigot)
9702 lines
452 KiB
Diff
9702 lines
452 KiB
Diff
From d0753522c812ed12669f90b13afa74d3a83f4f36 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 +-
|
|
.../paper/server/ticklist/PaperTickList.java | 8 +
|
|
.../com/mojang/datafixers/util/Either.java | 6 +-
|
|
.../tuinity/chunk/ChunkRegionManager.java | 165 +++
|
|
.../chunk/QueuedChangesMapLong2Int.java | 155 +++
|
|
.../chunk/QueuedChangesMapLong2Object.java | 170 +++
|
|
.../tuinity/tuinity/config/TuinityConfig.java | 305 +++++
|
|
.../com/tuinity/tuinity/util/ChunkList.java | 119 ++
|
|
.../com/tuinity/tuinity/util/EntityList.java | 124 ++
|
|
.../tuinity/tuinity/util/IBlockDataList.java | 123 ++
|
|
.../tuinity/util/OptimizedSmallEnumSet.java | 65 +
|
|
.../tuinity/util/PrimaryThreadList.java | 241 ++++
|
|
.../tuinity/util/PrimaryThreadSet.java | 282 +++++
|
|
.../util/TickSynchronizationPoint.java | 40 +
|
|
.../com/tuinity/tuinity/util/TickThread.java | 40 +
|
|
.../java/com/tuinity/tuinity/util/Util.java | 103 ++
|
|
.../fastutil/ExtendedAbstractDoubleList.java | 39 +
|
|
.../fastutil/ExtendedDoubleArrayList.java | 65 +
|
|
.../fastutil/ExtendedObjectAVLTreeSet.java | 90 ++
|
|
.../com/tuinity/tuinity/util/map/AreaMap.java | 388 ++++++
|
|
.../tuinity/util/map/PlayerAreaMap.java | 25 +
|
|
.../util/map/PooledLinkedHashSets.java | 287 +++++
|
|
.../util/pool/PooledBlockPositions.java | 40 +
|
|
.../net/minecraft/server/ArraySetSorted.java | 41 +-
|
|
.../net/minecraft/server/AxisAlignedBB.java | 2 +
|
|
src/main/java/net/minecraft/server/Chunk.java | 135 +++
|
|
.../java/net/minecraft/server/ChunkMap.java | 15 +-
|
|
.../minecraft/server/ChunkMapDistance.java | 401 ++++++-
|
|
.../minecraft/server/ChunkProviderServer.java | 137 ++-
|
|
.../minecraft/server/ChunkRegionLoader.java | 12 +-
|
|
.../net/minecraft/server/ChunkStatus.java | 4 +-
|
|
.../net/minecraft/server/DedicatedServer.java | 3 +-
|
|
.../minecraft/server/DoubleListOffset.java | 2 +-
|
|
src/main/java/net/minecraft/server/EULA.java | 2 +-
|
|
.../java/net/minecraft/server/Entity.java | 137 +++
|
|
.../minecraft/server/EntityEnderDragon.java | 4 +-
|
|
.../minecraft/server/EntityInsentient.java | 19 +-
|
|
.../net/minecraft/server/EntityPlayer.java | 43 +-
|
|
.../minecraft/server/EntityTrackerEntry.java | 24 +-
|
|
.../net/minecraft/server/EntityWither.java | 4 +-
|
|
.../java/net/minecraft/server/HeightMap.java | 5 +-
|
|
.../minecraft/server/IAsyncTaskHandler.java | 2 +-
|
|
.../net/minecraft/server/IEntityAccess.java | 33 +-
|
|
.../minecraft/server/LightEngineBlock.java | 2 +-
|
|
.../minecraft/server/LightEngineLayer.java | 2 +-
|
|
.../net/minecraft/server/LightEngineSky.java | 2 +-
|
|
.../minecraft/server/LightEngineStorage.java | 17 +-
|
|
.../server/LightEngineStorageArray.java | 26 +-
|
|
.../server/LightEngineStorageBlock.java | 8 +-
|
|
.../server/LightEngineStorageSky.java | 38 +-
|
|
.../java/net/minecraft/server/MCUtil.java | 30 +-
|
|
.../net/minecraft/server/MinecraftServer.java | 6 +-
|
|
.../net/minecraft/server/NBTTagCompound.java | 2 +-
|
|
.../minecraft/server/NavigationAbstract.java | 24 +-
|
|
.../net/minecraft/server/NetworkManager.java | 36 +-
|
|
.../server/PacketPlayOutMapChunk.java | 15 +-
|
|
.../net/minecraft/server/PairedQueue.java | 44 +-
|
|
.../net/minecraft/server/PathfinderGoal.java | 15 +-
|
|
.../server/PathfinderGoalSelector.java | 125 +-
|
|
.../server/PathfinderGoalWrapped.java | 6 +-
|
|
.../minecraft/server/PathfinderNormal.java | 4 +-
|
|
.../server/PathfinderTargetCondition.java | 1 +
|
|
.../net/minecraft/server/PlayerChunk.java | 94 +-
|
|
.../net/minecraft/server/PlayerChunkMap.java | 1043 +++++++++++++++--
|
|
.../server/PlayerInteractManager.java | 45 +-
|
|
.../net/minecraft/server/PlayerInventory.java | 6 +-
|
|
.../java/net/minecraft/server/PlayerList.java | 6 +-
|
|
.../java/net/minecraft/server/ProtoChunk.java | 16 +-
|
|
.../java/net/minecraft/server/RegionFile.java | 468 +++++++-
|
|
.../minecraft/server/RegionFileBitSet.java | 26 +-
|
|
.../net/minecraft/server/RegionFileCache.java | 45 +-
|
|
.../server/RegionFileCompression.java | 7 +-
|
|
.../net/minecraft/server/ThreadedMailbox.java | 2 +-
|
|
.../java/net/minecraft/server/Ticket.java | 6 +-
|
|
.../java/net/minecraft/server/TicketType.java | 1 +
|
|
.../net/minecraft/server/VoxelShapeArray.java | 2 +-
|
|
.../minecraft/server/VoxelShapeCubePoint.java | 2 +-
|
|
.../server/VoxelShapeMergerList.java | 2 +-
|
|
.../net/minecraft/server/VoxelShapes.java | 2 +-
|
|
src/main/java/net/minecraft/server/World.java | 23 +-
|
|
.../net/minecraft/server/WorldServer.java | 461 +++++++-
|
|
.../net/minecraft/server/WorldUpgrader.java | 2 +-
|
|
.../org/bukkit/craftbukkit/CraftServer.java | 15 +-
|
|
.../org/bukkit/craftbukkit/CraftWorld.java | 67 +-
|
|
.../java/org/bukkit/craftbukkit/Main.java | 7 +
|
|
.../craftbukkit/entity/CraftEntity.java | 12 +
|
|
.../craftbukkit/entity/CraftPlayer.java | 37 +-
|
|
.../java/org/spigotmc/ActivationRange.java | 41 +-
|
|
src/main/java/org/spigotmc/AsyncCatcher.java | 2 +-
|
|
src/main/java/org/spigotmc/TrackingRange.java | 40 +
|
|
95 files changed, 6360 insertions(+), 482 deletions(-)
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/ChunkRegionManager.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Int.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/chunk/QueuedChangesMapLong2Object.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/ChunkList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/EntityList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/IBlockDataList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/PrimaryThreadSet.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/TickSynchronizationPoint.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/TickThread.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/Util.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedAbstractDoubleList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedDoubleArrayList.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/fastutil/ExtendedObjectAVLTreeSet.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/AreaMap.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PlayerAreaMap.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/map/PooledLinkedHashSets.java
|
|
create mode 100644 src/main/java/com/tuinity/tuinity/util/pool/PooledBlockPositions.java
|
|
|
|
diff --git a/pom.xml b/pom.xml
|
|
index a7ee9396a0..90ca354c46 100644
|
|
--- a/pom.xml
|
|
+++ b/pom.xml
|
|
@@ -1,4 +1,4 @@
|
|
-<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/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
index e948012d5b..4ace0d8d78 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
@@ -189,6 +189,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
}
|
|
|
|
public void onChunkSetTicking(final int chunkX, final int chunkZ) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list chunk ticking update"); // Tuinity - soft async catcher
|
|
final ArrayList<NextTickListEntry<T>> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ));
|
|
if (pending == null) {
|
|
return;
|
|
@@ -269,6 +270,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public void tick() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher
|
|
final ChunkProviderServer chunkProvider = this.world.getChunkProvider();
|
|
|
|
this.world.getMethodProfiler().enter("cleaning");
|
|
@@ -409,6 +411,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
}
|
|
|
|
public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list schedule"); // Tuinity - soft async catcher
|
|
final NextTickListEntry<T> entry = new NextTickListEntry<>(pos, data, targetTick, priority);
|
|
if (this.excludeFromScheduling.test(entry.getData())) {
|
|
return;
|
|
@@ -468,6 +471,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public List<NextTickListEntry<T>> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get in bounding box"); // Tuinity - soft async catcher
|
|
if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) {
|
|
return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above
|
|
}
|
|
@@ -524,6 +528,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list copy"); // Tuinity - soft async catcher
|
|
// start copy from TickListServer // TODO check on update
|
|
List<NextTickListEntry<T>> list = this.getEntriesInBoundingBox(structureboundingbox, false, false);
|
|
Iterator<NextTickListEntry<T>> iterator = list.iterator();
|
|
@@ -543,6 +548,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public List<NextTickListEntry<T>> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get"); // Tuinity - soft async catcher
|
|
// Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks
|
|
// not at ticking status, and ticking status requires neighbours loaded
|
|
// so with this method we will reduce scheduler churning
|
|
@@ -574,6 +580,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list serialize"); // Tuinity - soft async catcher
|
|
// start copy from TickListServer // TODO check on update
|
|
List<NextTickListEntry<T>> list = this.getEntriesInChunk(chunkcoordintpair, false, true);
|
|
|
|
@@ -583,6 +590,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
public int getTotalScheduledEntries() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get size"); // Tuinity - soft async catcher
|
|
// good thing this is only used in debug reports // TODO check on update
|
|
int ret = 0;
|
|
|
|
diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java
|
|
index a90adac7bd..2e7cbf8bf5 100644
|
|
--- a/src/main/java/com/mojang/datafixers/util/Either.java
|
|
+++ b/src/main/java/com/mojang/datafixers/util/Either.java
|
|
@@ -23,6 +23,7 @@ public abstract class Either<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..78c011309a
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
@@ -0,0 +1,305 @@
|
|
+package com.tuinity.tuinity.config;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.util.Throw;
|
|
+import net.minecraft.server.TicketType;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.configuration.ConfigurationSection;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+
|
|
+import java.io.File;
|
|
+import java.lang.reflect.Method;
|
|
+import java.lang.reflect.Modifier;
|
|
+import java.util.List;
|
|
+import java.util.logging.Level;
|
|
+
|
|
+public final class TuinityConfig {
|
|
+
|
|
+ public static final String CONFIG_HEADER = "Configuration file for Tuinity.";
|
|
+ public static final int CURRENT_CONFIG_VERSION = 1;
|
|
+
|
|
+ private static final Object[] EMPTY = new Object[0];
|
|
+
|
|
+ private static File configFile;
|
|
+ public static YamlConfiguration config;
|
|
+ private static int configVersion;
|
|
+
|
|
+ public static void init(final File file) {
|
|
+ // TODO remove this in the future...
|
|
+ final File tuinityConfig = new File(file.getParent(), "tuinity.yml");
|
|
+ if (!tuinityConfig.exists()) {
|
|
+ final File oldConfig = new File(file.getParent(), "concrete.yml");
|
|
+ oldConfig.renameTo(tuinityConfig);
|
|
+ }
|
|
+ TuinityConfig.configFile = file;
|
|
+ final YamlConfiguration config = new YamlConfiguration();
|
|
+ config.options().header(CONFIG_HEADER);
|
|
+ config.options().copyDefaults(true);
|
|
+
|
|
+ if (!file.exists()) {
|
|
+ try {
|
|
+ file.createNewFile();
|
|
+ } catch (final Exception ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex);
|
|
+ }
|
|
+ } else {
|
|
+ try {
|
|
+ config.load(file);
|
|
+ } catch (final Exception ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex);
|
|
+ Throw.rethrow(ex); /* Rethrow, this is critical */
|
|
+ throw new RuntimeException(ex); // unreachable
|
|
+ }
|
|
+ }
|
|
+
|
|
+ TuinityConfig.load(config);
|
|
+ }
|
|
+
|
|
+ public static void load(final YamlConfiguration config) {
|
|
+ TuinityConfig.config = config;
|
|
+ TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION);
|
|
+ TuinityConfig.set("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION);
|
|
+
|
|
+ for (final Method method : TuinityConfig.class.getDeclaredMethods()) {
|
|
+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 ||
|
|
+ !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ method.setAccessible(true);
|
|
+ method.invoke(null, EMPTY);
|
|
+ } catch (final Exception ex) {
|
|
+ Throw.rethrow(ex);
|
|
+ throw new RuntimeException(ex); // unreachable
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* We re-save to add new options */
|
|
+ try {
|
|
+ config.save(TuinityConfig.configFile);
|
|
+ } catch (final Exception ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static void set(final String path, final Object value) {
|
|
+ TuinityConfig.config.set(path, value);
|
|
+ }
|
|
+
|
|
+ static boolean getBoolean(final String path, final boolean dfl) {
|
|
+ TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl));
|
|
+ return TuinityConfig.config.getBoolean(path, dfl);
|
|
+ }
|
|
+
|
|
+ static int getInt(final String path, final int dfl) {
|
|
+ TuinityConfig.config.addDefault(path, Integer.valueOf(dfl));
|
|
+ return TuinityConfig.config.getInt(path, dfl);
|
|
+ }
|
|
+
|
|
+ static long getLong(final String path, final long dfl) {
|
|
+ TuinityConfig.config.addDefault(path, Long.valueOf(dfl));
|
|
+ return TuinityConfig.config.getLong(path, dfl);
|
|
+ }
|
|
+
|
|
+ static double getDouble(final String path, final double dfl) {
|
|
+ TuinityConfig.config.addDefault(path, Double.valueOf(dfl));
|
|
+ return TuinityConfig.config.getDouble(path, dfl);
|
|
+ }
|
|
+
|
|
+ public static boolean tickWorldsInParallel;
|
|
+
|
|
+ /**
|
|
+ * if tickWorldsInParallel == true, then this value is used as a default only for worlds
|
|
+ */
|
|
+ public static int tickThreads;
|
|
+
|
|
+ /*
|
|
+ private static void worldticking() {
|
|
+ tickWorldsInParallel = TuinityConfig.getBoolean("tick-worlds-in-parallel", false);
|
|
+ tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future
|
|
+ }*/
|
|
+
|
|
+ public static double maxChunkSendsPerPlayer; // per second
|
|
+
|
|
+ public static int[] maxChunkSendsPerPlayerChoice = new int[100];
|
|
+
|
|
+ private static void maxChunkLoadsPerPlayer() {
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ TuinityConfig.set("max-pending-chunk-tickets-per-player", null);
|
|
+ }
|
|
+ maxChunkSendsPerPlayer = TuinityConfig.getDouble("target-chunk-sends-per-player-per-second", 40.0);
|
|
+ if (maxChunkSendsPerPlayer <= -1.0) {
|
|
+ maxChunkSendsPerPlayer = Integer.MAX_VALUE;
|
|
+ } else if (maxChunkSendsPerPlayer <= 1.0) {
|
|
+ maxChunkSendsPerPlayer = 1.0;
|
|
+ } else if (maxChunkSendsPerPlayer > Integer.MAX_VALUE) {
|
|
+ maxChunkSendsPerPlayer = Integer.MAX_VALUE;
|
|
+ }
|
|
+
|
|
+ double rateTick = maxChunkSendsPerPlayer / 20.0;
|
|
+ double a = Math.floor(rateTick);
|
|
+ double b = Math.ceil(rateTick);
|
|
+
|
|
+ // we want to spread out a and b over the interval so it's smooth
|
|
+
|
|
+ int aInt = (int)a;
|
|
+ int bInt = (int)b;
|
|
+ double total = b;
|
|
+ maxChunkSendsPerPlayerChoice[0] = bInt;
|
|
+
|
|
+ for (int i = 1, len = maxChunkSendsPerPlayerChoice.length; i < len; ++i) {
|
|
+ if (total / (double)i >= rateTick) {
|
|
+ total += a;
|
|
+ maxChunkSendsPerPlayerChoice[i] = aInt;
|
|
+ } else {
|
|
+ total += b;
|
|
+ maxChunkSendsPerPlayerChoice[i] = bInt;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static int delayChunkUnloadsBy;
|
|
+
|
|
+ private static void delayChunkUnloadsBy() {
|
|
+ delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 10) * 20;
|
|
+ if (delayChunkUnloadsBy >= 0) {
|
|
+ TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class WorldConfig {
|
|
+
|
|
+ public final String worldName;
|
|
+ public ConfigurationSection config;
|
|
+ ConfigurationSection worldDefaults;
|
|
+
|
|
+ public WorldConfig(final String worldName) {
|
|
+ this.worldName = worldName;
|
|
+ this.init();
|
|
+ }
|
|
+
|
|
+ public void init() {
|
|
+ this.worldDefaults = TuinityConfig.config.getConfigurationSection("world-settings.default");
|
|
+ if (this.worldDefaults == null) {
|
|
+ this.worldDefaults = TuinityConfig.config.createSection("world-settings.default");
|
|
+ }
|
|
+
|
|
+ String worldSectionPath = TuinityConfig.configVersion < 1 ? this.worldName : "world-settings.".concat(this.worldName);
|
|
+ ConfigurationSection section = TuinityConfig.config.getConfigurationSection(worldSectionPath);
|
|
+ if (section == null) {
|
|
+ section = TuinityConfig.config.createSection(worldSectionPath);
|
|
+ }
|
|
+ TuinityConfig.config.set(worldSectionPath, section);
|
|
+
|
|
+ this.load(section);
|
|
+ }
|
|
+
|
|
+ public void load(final ConfigurationSection config) {
|
|
+ this.config = config;
|
|
+
|
|
+ for (final Method method : TuinityConfig.WorldConfig.class.getDeclaredMethods()) {
|
|
+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 ||
|
|
+ !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ method.setAccessible(true);
|
|
+ method.invoke(this, EMPTY);
|
|
+ } catch (final Exception ex) {
|
|
+ Throw.rethrow(ex);
|
|
+ throw new RuntimeException(ex); // unreachable
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ ConfigurationSection oldSection = TuinityConfig.config.getConfigurationSection(this.worldName);
|
|
+ TuinityConfig.config.set("world-settings.".concat(this.worldName), oldSection);
|
|
+ TuinityConfig.config.set(this.worldName, null);
|
|
+ }
|
|
+
|
|
+ /* We re-save to add new options */
|
|
+ try {
|
|
+ TuinityConfig.config.save(TuinityConfig.configFile);
|
|
+ } catch (final Exception ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * update world defaults for the specified path, but also sets this world's config value for the path
|
|
+ * if it exists
|
|
+ */
|
|
+ void set(final String path, final Object val) {
|
|
+ this.worldDefaults.set(path, val);
|
|
+ if (this.config.get(path) != null) {
|
|
+ this.config.set(path, val);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ boolean getBoolean(final String path, final boolean dfl) {
|
|
+ this.worldDefaults.addDefault(path, Boolean.valueOf(dfl));
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ if (this.config.getBoolean(path) == dfl) {
|
|
+ this.config.set(path, null);
|
|
+ }
|
|
+ }
|
|
+ return this.config.getBoolean(path, this.worldDefaults.getBoolean(path));
|
|
+ }
|
|
+
|
|
+ int getInt(final String path, final int dfl) {
|
|
+ this.worldDefaults.addDefault(path, Integer.valueOf(dfl));
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ if (this.config.getInt(path) == dfl) {
|
|
+ this.config.set(path, null);
|
|
+ }
|
|
+ }
|
|
+ return this.config.getInt(path, this.worldDefaults.getInt(path));
|
|
+ }
|
|
+
|
|
+ long getLong(final String path, final long dfl) {
|
|
+ this.worldDefaults.addDefault(path, Long.valueOf(dfl));
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ if (this.config.getLong(path) == dfl) {
|
|
+ this.config.set(path, null);
|
|
+ }
|
|
+ }
|
|
+ return this.config.getLong(path, this.worldDefaults.getLong(path));
|
|
+ }
|
|
+
|
|
+ double getDouble(final String path, final double dfl) {
|
|
+ this.worldDefaults.addDefault(path, Double.valueOf(dfl));
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ if (this.config.getDouble(path) == dfl) {
|
|
+ this.config.set(path, null);
|
|
+ }
|
|
+ }
|
|
+ return this.config.getDouble(path, this.worldDefaults.getDouble(path));
|
|
+ }
|
|
+
|
|
+ /** ignored if {@link TuinityConfig#tickWorldsInParallel} == false */
|
|
+ public int threads;
|
|
+
|
|
+ /*
|
|
+ private void worldthreading() {
|
|
+ final int threads = this.getInt("tick-threads", -1);
|
|
+ this.threads = threads == -1 ? TuinityConfig.tickThreads : threads;
|
|
+ }*/
|
|
+
|
|
+ public int noTickViewDistance;
|
|
+ private void noTickViewDistance() {
|
|
+ this.noTickViewDistance = this.getInt("no-tick-view-distance", -1);
|
|
+ }
|
|
+
|
|
+ public boolean useOptimizedTracker;
|
|
+
|
|
+ private void optimizetracker() {
|
|
+ if (TuinityConfig.configVersion < 1) {
|
|
+ this.set("optimized-tracker-track-range", null);
|
|
+ this.set("optimized-tracker-untrack-range", null);
|
|
+ }
|
|
+ this.useOptimizedTracker = this.getBoolean("optimized-tracker", true);
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/com/tuinity/tuinity/util/ChunkList.java b/src/main/java/com/tuinity/tuinity/util/ChunkList.java
|
|
new file mode 100644
|
|
index 0000000000..66c64be8f3
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/ChunkList.java
|
|
@@ -0,0 +1,119 @@
|
|
+package com.tuinity.tuinity.util;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
+import net.minecraft.server.Chunk;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+import java.util.NoSuchElementException;
|
|
+
|
|
+// list with O(1) remove & contains
|
|
+public final class ChunkList implements Iterable<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/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 0244768f76..a56cffbbb2 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<Entry<ArraySetSorted<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
@@ -332,6 +479,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;
|
|
@@ -349,7 +712,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();
|
|
@@ -430,7 +793,7 @@ public abstract class ChunkMapDistance {
|
|
|
|
class b extends ChunkMap {
|
|
|
|
- protected final Long2ByteMap a = new Long2ByteOpenHashMap();
|
|
+ protected final Long2ByteOpenHashMap a = new Long2ByteOpenHashMap(); // Tuinity - change type for fast iterator
|
|
protected final int b;
|
|
|
|
protected b(int i) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 1dcd0980ec..c0264e130f 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -520,27 +520,39 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
|
|
public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER
|
|
@Override public boolean a(Entity entity) {
|
|
- long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4);
|
|
-
|
|
- return this.a(i, PlayerChunk::b);
|
|
+ // Tuinity start - optimize is ticking ready type functions
|
|
+ // is entity ticking ready
|
|
+ PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(entity));
|
|
+ return playerChunk != null && playerChunk.isEntityTickingReady();
|
|
+ // Tuinity end - optimize is ticking ready type functions
|
|
}
|
|
|
|
public final boolean isEntityTickingChunk(ChunkCoordIntPair chunkcoordintpair) { return this.a(chunkcoordintpair); } // Paper - OBFHELPER
|
|
@Override public boolean a(ChunkCoordIntPair chunkcoordintpair) {
|
|
- return this.a(chunkcoordintpair.pair(), PlayerChunk::b);
|
|
+ // Tuinity start - optimize is ticking ready type functions
|
|
+ // is entity ticking ready
|
|
+ PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(chunkcoordintpair));
|
|
+ return playerChunk != null && playerChunk.isEntityTickingReady();
|
|
+ // Tuinity end - optimize is ticking ready type functions
|
|
}
|
|
|
|
@Override
|
|
public boolean a(BlockPosition blockposition) {
|
|
- long i = ChunkCoordIntPair.pair(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
-
|
|
- return this.a(i, PlayerChunk::a);
|
|
+ // Tuinity start - optimize is ticking ready type functions
|
|
+ // is ticking ready
|
|
+ PlayerChunk playerChunk = this.getChunk(com.tuinity.tuinity.util.Util.getCoordinateKey(blockposition));
|
|
+ return playerChunk != null && playerChunk.isTickingReady();
|
|
+ // Tuinity end - optimize is ticking ready type functions
|
|
}
|
|
|
|
public boolean b(Entity entity) {
|
|
- long i = ChunkCoordIntPair.pair(MathHelper.floor(entity.locX()) >> 4, MathHelper.floor(entity.locZ()) >> 4);
|
|
-
|
|
- return this.a(i, PlayerChunk::c);
|
|
+ // Tuinity start - optimize is ticking ready type functions
|
|
+ // is full chunk ready
|
|
+ if (Thread.currentThread() == this.serverThread) {
|
|
+ return this.getChunkAtIfLoadedMainThreadNoCache(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null;
|
|
+ }
|
|
+ return this.getChunkAtIfLoadedImmediately(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ())) != null;
|
|
+ // Tuinity end - optimize is ticking ready type functions
|
|
}
|
|
|
|
private boolean a(long i, Function<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..223ad3965c 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -208,6 +208,142 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
// CraftBukkit end
|
|
|
|
+ // Tuinity start
|
|
+ boolean isLegacyTrackingEntity = this instanceof EntityPlayer; // TODO temp, fix citizens...
|
|
+
|
|
+ private com.tuinity.tuinity.util.map.PlayerAreaMap trackingAreaMap;
|
|
+ private com.tuinity.tuinity.util.map.PlayerAreaMap unTrackingAreaMap;
|
|
+
|
|
+ final com.tuinity.tuinity.util.map.PlayerAreaMap getTrackingAreaMap() {
|
|
+ if (this.trackingAreaMap == null) {
|
|
+ // stupid plugins incorrectly adding entities into the world.
|
|
+ if (this.world == null) {
|
|
+ throw new IllegalStateException("Entity " + this + " has incorrectly been added to the world.");
|
|
+ }
|
|
+ this.acquireTrackingMap(((WorldServer)this.world).getChunkProvider().playerChunkMap);
|
|
+ }
|
|
+ return this.trackingAreaMap;
|
|
+ }
|
|
+
|
|
+ final com.tuinity.tuinity.util.map.PlayerAreaMap getUnTrackingAreaMap() {
|
|
+ if (this.unTrackingAreaMap == null) {
|
|
+ // stupid plugins incorrectly adding entities into the world.
|
|
+ if (this.world == null) {
|
|
+ throw new IllegalStateException("Entity " + this + " has incorrectly been added to the world.");
|
|
+ }
|
|
+ this.acquireTrackingMap(((WorldServer)this.world).getChunkProvider().playerChunkMap);
|
|
+ }
|
|
+ return this.unTrackingAreaMap;
|
|
+ }
|
|
+
|
|
+ final void acquireTrackingMap(PlayerChunkMap chunkMap) {
|
|
+ int key = org.spigotmc.TrackingRange.getTrackingRangeType(this).ordinal();
|
|
+ this.trackingAreaMap = chunkMap.playerEntityTrackerTrackMaps[key];
|
|
+ this.unTrackingAreaMap = chunkMap.playerEntityTrackerUntrackMaps[key];
|
|
+ }
|
|
+
|
|
+ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) {
|
|
+ if (this.isLegacyTrackingEntity == isLegacyTrackingEntity) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (this.world == null) {
|
|
+ this.isLegacyTrackingEntity = isLegacyTrackingEntity;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ WorldServer world = (WorldServer)this.world;
|
|
+ PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap;
|
|
+
|
|
+ if (!chunkMap.optimisedTrackerEnabled) {
|
|
+ this.isLegacyTrackingEntity = isLegacyTrackingEntity;
|
|
+ return;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot update legacy tracking off of the main thread");
|
|
+
|
|
+ if (this.isLegacyTrackingEntity) {
|
|
+ this.isLegacyTrackingEntity = false;
|
|
+ chunkMap.activelyTrackedEntitiesLegacy.remove(this);
|
|
+
|
|
+ PlayerChunkMap.EntityTracker tracker = this.tracker;
|
|
+ if (tracker != null) {
|
|
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = this.getTrackingAreaMap().getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(this));
|
|
+
|
|
+ for (EntityPlayer player : world.getPlayers()) {
|
|
+ tracker.clear(player);
|
|
+ if (inRange != null && inRange.contains(player)) {
|
|
+ this.addToTrackQueue(player);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ this.isLegacyTrackingEntity = true;
|
|
+ chunkMap.activelyTrackedEntitiesLegacy.add(this);
|
|
+
|
|
+ PlayerChunkMap.EntityTracker tracker = chunkMap.trackedEntities.get(this.getId());
|
|
+ if (tracker != null) {
|
|
+ for (EntityPlayer player : world.getPlayers()) {
|
|
+ this.clearTrackingQueues(player);
|
|
+ tracker.clear(player);
|
|
+ tracker.updatePlayer(player);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // We queue changes to tracker here because when adding to a chunk we do not know if the entity is in a trackable state
|
|
+ public final it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet trackQueue = new it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet(8);
|
|
+ public final it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet unTrackQueue = new it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet(8);
|
|
+
|
|
+ public final void addToTrackQueue(EntityPlayer player) {
|
|
+ if (player == this) {
|
|
+ return;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Adding to track queue from off-main thread");
|
|
+ int id = player.getId();
|
|
+ this.unTrackQueue.remove(id);
|
|
+ this.trackQueue.add(id);
|
|
+ ((WorldServer)this.world).trackingUpdateQueue.add(this);
|
|
+ }
|
|
+
|
|
+ public final void addToUntrackQueue(EntityPlayer player) {
|
|
+ if (player == this) {
|
|
+ return;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Removing from track queue from off-main thread");
|
|
+ int id = player.getId();
|
|
+ this.trackQueue.remove(id);
|
|
+ // don't queue untrack if we're not tracked
|
|
+ if (this.tracker == null || !this.tracker.trackedPlayers.contains(player)) {
|
|
+ return;
|
|
+ }
|
|
+ this.unTrackQueue.add(id);
|
|
+ ((WorldServer)this.world).trackingUpdateQueue.add(this);
|
|
+ }
|
|
+
|
|
+ public final void clearTrackingQueues(EntityPlayer player) {
|
|
+ if (player == this) {
|
|
+ return;
|
|
+ }
|
|
+ int id = player.getId();
|
|
+ this.trackQueue.remove(id);
|
|
+ this.unTrackQueue.remove(id);
|
|
+ if (this.trackQueue.isEmpty() && this.unTrackQueue.isEmpty()) {
|
|
+ ((WorldServer)this.world).trackingUpdateQueue.remove(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ // Tuinity end
|
|
+ // Tuinity start
|
|
+ public final double getDistanceXZSquared(double x, double z) {
|
|
+ double diffX = x - this.locX();
|
|
+ double diffZ = z - this.locZ();
|
|
+
|
|
+ return (diffX * diffX) + (diffZ * diffZ);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public Entity(EntityTypes<?> entitytypes, World world) {
|
|
this.id = Entity.entityCount.incrementAndGet();
|
|
this.passengers = Lists.newArrayList();
|
|
@@ -1371,6 +1507,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
return MathHelper.c(f * f + f1 * f1 + f2 * f2);
|
|
}
|
|
|
|
+ public final double getDistanceSquared(double x, double y, double z) { return this.g(x, y, z); } // Tuinity - OBFHELPER
|
|
public double g(double d0, double d1, double d2) {
|
|
double d3 = this.locX() - d0;
|
|
double d4 = this.locY() - d1;
|
|
diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java
|
|
index af10fc36e0..bf14d33c0d 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java
|
|
@@ -579,9 +579,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// this.world.b(1028, new BlockPosition(this), 0);
|
|
// Paper start
|
|
- int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
|
|
+ //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - per player view distance
|
|
for (EntityPlayer player : ((WorldServer)world).getPlayers()) {
|
|
- //final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
|
|
+ final int viewDistance = player.getEffectiveViewDistance(player.getWorldServer().getChunkProvider().playerChunkMap) << 4; // Tuinity - per player view distance
|
|
// Paper end
|
|
double deltaX = this.locX() - player.locX();
|
|
double deltaZ = this.locZ() - player.locZ();
|
|
diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
index 1991cee43d..27ef476001 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
@@ -641,20 +641,27 @@ public abstract class EntityInsentient extends EntityLiving {
|
|
if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.J()) {
|
|
this.die();
|
|
} else if (!this.isPersistent() && !this.I()) {
|
|
- EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D);
|
|
+ EntityHuman entityhuman = this.world.findClosestPlayer(this.locX(), this.locY(), this.locZ(), this.world.paperConfig.hardDespawnDistanceNotSquared, (Entity player) -> (((EntityHuman)player).affectsSpawning && !((EntityHuman)player).isSpectator())); // Tuinity - fix this function to properly handle spawning api
|
|
|
|
- if (entityhuman != null && entityhuman.affectsSpawning) { // Paper - Affects Spawning API
|
|
+ if (entityhuman != null) { // Paper - Affects Spawning API // Tuinity - check not needed anymore
|
|
double d0 = entityhuman.h(this);
|
|
|
|
if (d0 > world.paperConfig.hardDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances
|
|
this.die();
|
|
- }
|
|
-
|
|
- if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > world.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances
|
|
+ } else if (this.ticksFarFromPlayer > 600 && this.random.nextInt(800) == 0 && d0 > world.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances // Tuinity
|
|
this.die();
|
|
- } else if (d0 < 1024.0D) {
|
|
+ } else if (d0 < world.paperConfig.softDespawnDistance) { // Tuinity
|
|
this.ticksFarFromPlayer = 0;
|
|
}
|
|
+ } else { // Tuinity start
|
|
+ // no player in range, try all players
|
|
+ for (EntityHuman player : this.world.getPlayers()) {
|
|
+ if (player.affectsSpawning && !player.isSpectator()) {
|
|
+ this.die();
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* Concret eend */
|
|
}
|
|
|
|
} else {
|
|
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
index e7bfbc3307..d49c45ce7f 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -104,6 +104,39 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
|
|
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<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..9570747eab 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -55,8 +55,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
public static final int GOLDEN_TICKET = 33 + ChunkStatus.b();
|
|
- public final Long2ObjectLinkedOpenHashMap<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.getTrackingAreaMap().getObjectsInRange(chunkX, chunkZ);
|
|
+ if (playersTracking != null) {
|
|
+ Object[] backingSet = playersTracking.getBackingSet();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ Object temp = backingSet[index];
|
|
+ if (!(temp instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer trackingPlayer = (EntityPlayer)temp;
|
|
+ playerchunkmap_entitytracker.updateTrackingPlayer(trackingPlayer);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - implement optimized tracker
|
|
if (entity instanceof EntityPlayer) {
|
|
EntityPlayer entityplayer = (EntityPlayer) entity;
|
|
|
|
this.a(entityplayer, true);
|
|
+ if (!this.optimisedTrackerEnabled) { // Tuinity - implement optimized tracker
|
|
ObjectIterator objectiterator = this.trackedEntities.values().iterator();
|
|
|
|
while (objectiterator.hasNext()) {
|
|
@@ -1466,6 +2111,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
playerchunkmap_entitytracker1.updatePlayer(entityplayer);
|
|
}
|
|
}
|
|
+ } // Tuinity - implement optimized tracker
|
|
}
|
|
|
|
}
|
|
@@ -1494,9 +2140,104 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
playerchunkmap_entitytracker1.a();
|
|
}
|
|
entity.tracker = null; // Paper - We're no longer tracked
|
|
+ // Tuinity start - optimise entity tracking - we're no longer tracked
|
|
+ if (this.activelyTrackedEntitiesLegacy != null) {
|
|
+ this.activelyTrackedEntitiesLegacy.remove(entity);
|
|
+ }
|
|
+ // Tuinity end - optimise entity tracking - we're no longer tracked
|
|
}
|
|
|
|
+ // Tuinity start - optimized tracker
|
|
+ private void processTrackQueue() {
|
|
+ // handle queued changes
|
|
+
|
|
+ this.world.timings.tracker1.startTiming();
|
|
+ for (Entity tracked : this.world.trackingUpdateQueue) {
|
|
+ EntityTracker tracker = tracked.tracker;
|
|
+ if (tracker == null) {
|
|
+ continue;
|
|
+ }
|
|
+ // queued tracks
|
|
+ for (it.unimi.dsi.fastutil.ints.IntIterator iterator = tracked.trackQueue.iterator(); iterator.hasNext();) {
|
|
+ int id = iterator.nextInt();
|
|
+ Entity player = this.world.entitiesById.get(id);
|
|
+
|
|
+ if (!(player instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // double-check to make sure we're in range...
|
|
+ int chunkX = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locX());
|
|
+ int chunkZ = com.tuinity.tuinity.util.Util.getChunkCoordinate(player.locZ());
|
|
+
|
|
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange =
|
|
+ tracked.getUnTrackingAreaMap().getObjectsInRange(chunkX, chunkZ);
|
|
+
|
|
+ if (inRange != null && inRange.contains(player)) {
|
|
+ tracker.updateTrackingPlayer((EntityPlayer)player);
|
|
+ } else {
|
|
+ tracker.removeTrackingPlayer((EntityPlayer)player);
|
|
+ }
|
|
+ }
|
|
+ tracked.trackQueue.clear();
|
|
+
|
|
+ // queued untracks
|
|
+ for (it.unimi.dsi.fastutil.ints.IntIterator iterator = tracked.unTrackQueue.iterator(); iterator.hasNext();) {
|
|
+ int id = iterator.nextInt();
|
|
+ Entity player = this.world.entitiesById.get(id);
|
|
+
|
|
+ if (!(player instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ tracker.removeTrackingPlayer((EntityPlayer)player);
|
|
+ }
|
|
+ tracked.unTrackQueue.clear();
|
|
+ }
|
|
+ this.world.trackingUpdateQueue.clear();
|
|
+ this.world.timings.tracker1.stopTiming();
|
|
+
|
|
+ // broadcast updates
|
|
+
|
|
+ this.world.timings.tracker2.startTiming();
|
|
+ for (Entity tracked : this.world.loadedEntities) {
|
|
+ EntityTracker tracker = tracked.tracker;
|
|
+ if (tracker != null) {
|
|
+ tracker.trackerEntry.tick();
|
|
+ }
|
|
+ }
|
|
+ this.world.timings.tracker2.stopTiming();
|
|
+
|
|
+ // legacy tracker
|
|
+
|
|
+ this.world.timings.tracker3.startTiming();
|
|
+ Entity[] legacyEntities = this.activelyTrackedEntitiesLegacy.getRawData();
|
|
+ for (int i = 0, size = this.activelyTrackedEntitiesLegacy.size(); i < size; ++i) {
|
|
+ Entity entity = legacyEntities[i];
|
|
+ EntityTracker tracker = this.trackedEntities.get(entity.getId());
|
|
+ if (tracker == null) {
|
|
+ MinecraftServer.LOGGER.error("Legacy tracking entity has no tracker! No longer tracking entity " + entity);
|
|
+ this.activelyTrackedEntitiesLegacy.remove(entity);
|
|
+ --i;
|
|
+ --size;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ EntityTrackerEntry entry = tracker.trackerEntry;
|
|
+ tracker.track(this.world.getPlayers());
|
|
+ entry.tick(); // always tick the entry, even if no player is tracking
|
|
+ }
|
|
+ this.world.timings.tracker3.stopTiming();
|
|
+ }
|
|
+ // Tuinity end - optimized tracker
|
|
+
|
|
protected void g() {
|
|
+ // Tuinity start - optimized tracker
|
|
+ if (this.optimisedTrackerEnabled) {
|
|
+ this.processTrackQueue();
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - optimized tracker
|
|
List<EntityPlayer> list = Lists.newArrayList();
|
|
List<EntityPlayer> list1 = this.world.getPlayers();
|
|
|
|
@@ -1554,6 +2295,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
}
|
|
|
|
+ final void sendChunk(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Tuinity - OBFHELPER
|
|
private void a(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) {
|
|
if (apacket[0] == null) {
|
|
apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, true); // Paper - Anti-Xray
|
|
@@ -1564,6 +2306,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
PacketDebug.a(this.world, chunk.getPos());
|
|
List<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 +2324,31 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
}
|
|
}
|
|
+ // Tuinity start- implement optimized tracker
|
|
+ } else {
|
|
+ // Tuinity - implement optimized tracker
|
|
+ // Tuinity start - implement optimized tracker
|
|
+ // It's important to note that this is ONLY called when the chunk is at ticking level.
|
|
+ // At this point, the entities should be added in the chunk.
|
|
+ // only send entities when they're in tracking range...
|
|
+ Entity[] entities = chunk.entities.getRawData();
|
|
+ for (int i = 0, len = chunk.entities.size(); i < len; ++i) {
|
|
+ Entity entityInChunk = entities[i];
|
|
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInRange
|
|
+ = entityInChunk.getTrackingAreaMap().getObjectsInRange(chunk.getPos());
|
|
+ if (playersInRange == null || !playersInRange.contains(entityplayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ PlayerChunkMap.EntityTracker tracker = entityInChunk.tracker;
|
|
+ if (tracker == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // Note: We don't add to the lists because the track logic will handle it
|
|
+ tracker.updateTrackingPlayer(entityplayer);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - implement optimized tracker
|
|
|
|
Iterator iterator;
|
|
Entity entity1;
|
|
@@ -1618,7 +2386,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
public class EntityTracker {
|
|
|
|
- private final EntityTrackerEntry trackerEntry;
|
|
+ final EntityTrackerEntry trackerEntry; // Tuinity - private -> package private
|
|
private final Entity tracker;
|
|
private final int trackingDistance;
|
|
private SectionPosition e;
|
|
@@ -1684,10 +2452,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
public void updatePlayer(EntityPlayer entityplayer) {
|
|
org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
|
|
if (entityplayer != this.tracker) {
|
|
- Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113
|
|
- int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16);
|
|
- boolean flag = vec3d.x >= (double) (-i) && vec3d.x <= (double) i && vec3d.z >= (double) (-i) && vec3d.z <= (double) i && this.tracker.a(entityplayer);
|
|
-
|
|
+ // Tuinity start - remove allocation of Vec3d here
|
|
+ double vec3d_dx = entityplayer.locX() - this.tracker.locX();
|
|
+ double vec3d_dy = entityplayer.locY() - this.tracker.locY();
|
|
+ double vec3d_dz = entityplayer.locZ() - this.tracker.locZ();
|
|
+ // Tuinity end - remove allocation of Vec3d here
|
|
+ int i = Math.min(this.b(), (entityplayer.getEffectiveViewDistance(PlayerChunkMap.this)) * 16); // Tuinity - per player view distance
|
|
+ boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.tracker.a(entityplayer); // Tuinity start - remove allocation of Vec3d here
|
|
if (flag) {
|
|
boolean flag1 = this.tracker.attachedToPlayer;
|
|
|
|
@@ -1696,7 +2467,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair());
|
|
|
|
if (playerchunk != null && playerchunk.getChunk() != null) {
|
|
- flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance;
|
|
+ flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= (1 + PlayerChunkMap.this.playerViewDistanceTickMap.getLastViewDistance(entityplayer)) && entityplayer.loadedChunks.contains(com.tuinity.tuinity.util.Util.getCoordinateKey(this.tracker)); // Tuinity - per player view distance
|
|
}
|
|
}
|
|
|
|
@@ -1738,6 +2509,42 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
return i;
|
|
}
|
|
|
|
+ // Tuinity start - optimized tracker
|
|
+ final void updateTrackingPlayer(EntityPlayer entityplayer) {
|
|
+ if (entityplayer == this.tracker) {
|
|
+ return;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
|
|
+ // the same as updatePlayer except without a distance check
|
|
+ // we also add a world check since we queue tracking changes
|
|
+ // TODO check on update
|
|
+ // CraftBukkit start - respect vanish API
|
|
+ boolean shouldTrack = entityplayer.world == tracker.world && entityplayer.loadedChunks.contains(com.tuinity.tuinity.util.Util.getCoordinateKey(this.tracker));
|
|
+ if (this.tracker instanceof EntityPlayer) {
|
|
+ Player player = ((EntityPlayer)this.tracker).getBukkitEntity();
|
|
+ if (!entityplayer.getBukkitEntity().canSee(player)) {
|
|
+ shouldTrack = false;
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
+ if (shouldTrack) {
|
|
+ if (this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper
|
|
+ this.trackerEntry.onTrack(entityplayer);
|
|
+ }
|
|
+ } else {
|
|
+ this.removeTrackingPlayer(entityplayer);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final void removeTrackingPlayer(EntityPlayer player) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
|
|
+ if (this.trackedPlayers.remove(player)) {
|
|
+ this.trackerEntry.onUntrack(player);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimized tracker
|
|
+
|
|
public void track(List<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..2b5d0ecd0d 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -82,6 +82,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper
|
|
public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
|
|
|
|
+ public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config
|
|
+
|
|
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
|
|
public static BlockPosition lastPhysicsProblem; // Spigot
|
|
private org.spigotmc.TickLimiter entityLimiter;
|
|
@@ -131,6 +133,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot
|
|
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper
|
|
this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
|
|
+ this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(worlddata.getName()); // Tuinity - Server Config
|
|
this.generator = gen;
|
|
this.world = new CraftWorld((WorldServer) this, gen, env);
|
|
this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
|
|
@@ -333,6 +336,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
|
|
@Override
|
|
public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async set type call"); // Tuinity
|
|
// CraftBukkit start - tree generation
|
|
if (this.captureTreeGeneration) {
|
|
CraftBlockState blockstate = capturedBlockStates.get(blockposition);
|
|
@@ -429,6 +433,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
|
|
// CraftBukkit start - Split off from above in order to directly send client and physic updates
|
|
public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity
|
|
IBlockData iblockdata = newBlock;
|
|
IBlockData iblockdata1 = oldBlock;
|
|
IBlockData iblockdata2 = actualBlock;
|
|
@@ -437,8 +442,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
this.b(blockposition, iblockdata1, iblockdata2);
|
|
}
|
|
|
|
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
|
|
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Tuinity - diff on change, see below
|
|
this.notify(blockposition, iblockdata1, iblockdata, i);
|
|
+ // Tuinity start - per player view distance - allow block updates for non-ticking chunks in player view distance
|
|
+ // if copied from above
|
|
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(com.tuinity.tuinity.util.Util.getCoordinateKey(blockposition)) != null)) {
|
|
+ ((WorldServer)this).getChunkProvider().flagDirty(blockposition);
|
|
+ // Tuinity end - per player view distance
|
|
}
|
|
|
|
if (!this.isClientSide && (i & 1) != 0) {
|
|
@@ -1179,9 +1189,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D);
|
|
int l = MathHelper.floor((axisalignedbb.maxZ + 2.0D) / 16.0D);
|
|
|
|
+ ChunkProviderServer chunkProvider = ((ChunkProviderServer)this.chunkProvider); // Tuinity - optimize for loaded chunks
|
|
+
|
|
for (int i1 = i; i1 <= j; ++i1) {
|
|
for (int j1 = k; j1 <= l; ++j1) {
|
|
- Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
|
|
+ Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1); // Paper // Tuinity - optimize for loaded chunks
|
|
|
|
if (chunk != null) {
|
|
chunk.a(entity, axisalignedbb, list, predicate);
|
|
@@ -1200,9 +1212,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
int l = MathHelper.f((axisalignedbb.maxZ + 2.0D) / 16.0D);
|
|
List<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..92b79e3e71 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -55,7 +55,7 @@ public class WorldServer extends World {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private final List<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 - Optimize entity list iteration requiring entities be in loaded chunks
|
|
+ public final com.tuinity.tuinity.util.EntityList loadedEntities = new com.tuinity.tuinity.util.EntityList();
|
|
+ void onChunkLoad(final Chunk chunk) {
|
|
+ final com.destroystokyo.paper.util.maplist.EntityList list = chunk.entities;
|
|
+ final Entity[] entities = list.getRawData();
|
|
+ for (int i = 0, size = list.size(); i < size; ++i) {
|
|
+ this.loadedEntities.add(entities[i]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void onChunkUnload(final Chunk chunk) {
|
|
+ final com.destroystokyo.paper.util.maplist.EntityList list = chunk.entities;
|
|
+ final Entity[] entities = list.getRawData();
|
|
+ for (int i = 0, size = list.size(); i < size; ++i) {
|
|
+ this.loadedEntities.remove(entities[i]);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - Optimize entity list iteration requiring entities be in loaded chunks
|
|
+
|
|
+ // Tuinity start
|
|
+ // TODO the general area map is too large, use a smaller one depending on distance
|
|
+ @Nullable
|
|
+ public EntityPlayer findClosestPlayer(double fromX, double fromY, double fromZ, double distance) {
|
|
+ double maximumDistanceSquared;
|
|
+ if (distance < 0) {
|
|
+ maximumDistanceSquared = Double.MAX_VALUE;
|
|
+ } else {
|
|
+ maximumDistanceSquared = distance * distance;
|
|
+ }
|
|
+
|
|
+ EntityPlayer closestPlayer = null;
|
|
+ double closestDistanceSquared = maximumDistanceSquared;
|
|
+
|
|
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<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.getTrackingAreaMap().getObjectsInRange(prevChunkX, prevChunkZ);
|
|
+ com.tuinity.tuinity.util.map.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newRange =
|
|
+ entity.getTrackingAreaMap().getObjectsInRange(newChunkX, newChunkZ);
|
|
+
|
|
+ if (oldRange != newRange) { // optimization: equal pooled sets are identity equal
|
|
+ if (newRange != null) {
|
|
+ if (oldRange == null) {
|
|
+ Object[] backingSet = newRange.getBackingSet();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ Object temp = backingSet[index];
|
|
+ if (!(temp instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer player = (EntityPlayer) temp;
|
|
+ entity.addToTrackQueue(player);
|
|
+ }
|
|
+ } else {
|
|
+ Object[] backingSet = newRange.getBackingSet();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ Object temp = backingSet[index];
|
|
+ if (!(temp instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer player = (EntityPlayer) temp;
|
|
+ if (oldRange.contains(player)) {
|
|
+ continue;
|
|
+ }
|
|
+ entity.addToTrackQueue(player);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // handle untracking
|
|
+ oldRange = entity.getUnTrackingAreaMap().getObjectsInRange(prevChunkX, prevChunkZ);
|
|
+ newRange = entity.getUnTrackingAreaMap().getObjectsInRange(newChunkX, newChunkZ);
|
|
+ if (oldRange != newRange) { // optimization: equal pooled sets are identity equal
|
|
+ if (oldRange != null) {
|
|
+ if (newRange == null) {
|
|
+ Object[] backingSet = oldRange.getBackingSet();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ Object temp = backingSet[index];
|
|
+ if (!(temp instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer player = (EntityPlayer) temp;
|
|
+ entity.addToUntrackQueue(player);
|
|
+ }
|
|
+ } else {
|
|
+ Object[] backingSet = oldRange.getBackingSet();
|
|
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
+ Object temp = backingSet[index];
|
|
+ if (!(temp instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ EntityPlayer player = (EntityPlayer) temp;
|
|
+ if (newRange.contains(player)) {
|
|
+ continue;
|
|
+ }
|
|
+ entity.addToUntrackQueue(player);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimized tracker
|
|
}
|
|
|
|
this.getMethodProfiler().exit();
|
|
@@ -1189,6 +1632,7 @@ public class WorldServer extends World {
|
|
|
|
this.registerEntity(entityplayer);
|
|
this.getChunkProvider().playerChunkMap.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps
|
|
+ this.getChunkProvider().playerChunkMap.addPlayerToDistanceMapsTuinity(entityplayer); // Tuinity - distance maps
|
|
}
|
|
|
|
// CraftBukkit start
|
|
@@ -1371,6 +1815,7 @@ public class WorldServer extends World {
|
|
|
|
this.players.remove(entityplayer);
|
|
this.getChunkProvider().playerChunkMap.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps
|
|
+ this.getChunkProvider().playerChunkMap.removePlayerFromDistanceMapsTuinity(entityplayer); // Tuinity - distance maps
|
|
}
|
|
|
|
this.getScoreboard().a(entity);
|
|
@@ -1383,6 +1828,7 @@ public class WorldServer extends World {
|
|
if (entity instanceof EntityInsentient) {
|
|
this.navigators.remove(((EntityInsentient) entity).getNavigation());
|
|
}
|
|
+ this.loadedEntities.remove(entity); // Tuinity - loaded entity list
|
|
new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
|
|
entity.valid = false; // CraftBukkit
|
|
}
|
|
@@ -1439,6 +1885,11 @@ public class WorldServer extends World {
|
|
}
|
|
// Paper end
|
|
entity.shouldBeRemoved = false; // Paper - shouldn't be removed after being re-added
|
|
+ // Tuinity start - loaded entity list
|
|
+ if (this.isChunkLoaded(com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locX()), com.tuinity.tuinity.util.Util.getChunkCoordinate(entity.locZ()))) {
|
|
+ this.loadedEntities.add(entity);
|
|
+ }
|
|
+ // Tuinity end - loaded entity list
|
|
new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
index 3030c347ef..76f0f258e1 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
@@ -220,7 +220,7 @@ public class WorldUpgrader {
|
|
int l = Integer.parseInt(matcher.group(2)) << 5;
|
|
|
|
try {
|
|
- RegionFile regionfile = new RegionFile(file2, file1);
|
|
+ RegionFile regionfile = new RegionFile(file2, file1, true); // Tuinity - allow for chunk regionfiles to regen header
|
|
Throwable throwable = null;
|
|
|
|
try {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index f70468adab..2be7962bc5 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -806,6 +806,7 @@ public final class CraftServer implements Server {
|
|
|
|
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
|
|
com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper
|
|
+ com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config
|
|
for (WorldServer world : console.getWorlds()) {
|
|
world.worldData.setDifficulty(config.difficulty);
|
|
world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals);
|
|
@@ -834,6 +835,7 @@ public final class CraftServer implements Server {
|
|
}
|
|
world.spigotConfig.init(); // Spigot
|
|
world.paperConfig.init(); // Paper
|
|
+ world.tuinityConfig.init(); // Tuinity - Server Config
|
|
}
|
|
|
|
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
|
|
@@ -1705,7 +1707,10 @@ public final class CraftServer implements Server {
|
|
|
|
@Override
|
|
public boolean isPrimaryThread() {
|
|
- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly
|
|
+ // Tuinity start
|
|
+ final Thread currThread = Thread.currentThread();
|
|
+ return currThread == console.serverThread || currThread instanceof com.tuinity.tuinity.util.TickThread;
|
|
+ // Tuinity End
|
|
}
|
|
|
|
@Override
|
|
@@ -2082,6 +2087,14 @@ public final class CraftServer implements Server {
|
|
return com.destroystokyo.paper.PaperConfig.config;
|
|
}
|
|
|
|
+ // Tuinity start - add config to timings report
|
|
+ @Override
|
|
+ public YamlConfiguration getTuinityConfig()
|
|
+ {
|
|
+ return com.tuinity.tuinity.config.TuinityConfig.config;
|
|
+ }
|
|
+ // Tuinity end - add config to timings report
|
|
+
|
|
@Override
|
|
public void restart() {
|
|
org.spigotmc.RestartCommand.restart();
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 051506fce8..4db397d9f9 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;
|
|
@@ -291,9 +292,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;
|
|
@@ -308,7 +309,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;
|
|
}
|
|
@@ -432,8 +433,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
|
|
@@ -462,6 +463,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;
|
|
@@ -1094,16 +1096,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);
|
|
}
|
|
}
|
|
@@ -1114,16 +1116,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);
|
|
}
|
|
}
|
|
@@ -1144,7 +1146,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();
|
|
@@ -1155,7 +1157,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);
|
|
}
|
|
}
|
|
@@ -1168,7 +1170,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();
|
|
@@ -1181,7 +1183,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;
|
|
@@ -2467,10 +2469,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
|
|
|