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: 9788250b1 Clean up a lot of obfuscation helpers and impls 58a745eba Fix scoreboard vanilla colors not working in chat
7865 lines
385 KiB
Diff
7865 lines
385 KiB
Diff
From 0000000000000000000000000000000000000000 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 Changes
|
|
|
|
|
|
diff --git a/pom.xml b/pom.xml
|
|
index ef8ee637a8..6fd5968178 100644
|
|
--- a/pom.xml
|
|
+++ b/pom.xml
|
|
@@ -1,11 +1,11 @@
|
|
-<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>paper</artifactId>
|
|
+ <artifactId>tuinity</artifactId>
|
|
<packaging>jar</packaging>
|
|
<version>1.16.1-R0.1-SNAPSHOT</version>
|
|
- <name>Paper</name>
|
|
- <url>https://papermc.io</url>
|
|
+ <name>Tuinity-Server</name>
|
|
+ <url>https://github.com/Spottedleaf/Tuinity</url>
|
|
|
|
<properties>
|
|
<!-- <skipTests>true</skipTests> Paper - This [was] not going to end well -->
|
|
@@ -18,16 +18,16 @@
|
|
</properties>
|
|
|
|
<parent>
|
|
- <groupId>com.destroystokyo.paper</groupId>
|
|
- <artifactId>paper-parent</artifactId>
|
|
+ <groupId>com.tuinity</groupId>
|
|
+ <artifactId>tuinity-parent</artifactId>
|
|
<version>dev-SNAPSHOT</version>
|
|
<relativePath>../pom.xml</relativePath>
|
|
</parent>
|
|
|
|
<dependencies>
|
|
<dependency>
|
|
- <groupId>com.destroystokyo.paper</groupId>
|
|
- <artifactId>paper-api</artifactId>
|
|
+ <groupId>com.tuinity</groupId>
|
|
+ <artifactId>tuinity-api</artifactId>
|
|
<version>${project.version}</version>
|
|
<scope>compile</scope>
|
|
</dependency>
|
|
@@ -164,15 +164,15 @@
|
|
|
|
<!-- This builds a completely 'ready to start' jar with all dependencies inside -->
|
|
<build>
|
|
- <finalName>paper-${minecraft.version}</finalName>
|
|
- <defaultGoal>clean install</defaultGoal> <!-- Paper -->
|
|
+ <finalName>tuinity-${minecraft.version}</finalName>
|
|
+ <defaultGoal>install</defaultGoal> <!-- Paper -->
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>com.lukegb.mojo</groupId>
|
|
<artifactId>gitdescribe-maven-plugin</artifactId>
|
|
<version>1.3</version>
|
|
<configuration>
|
|
- <outputPrefix>git-Paper-</outputPrefix>
|
|
+ <outputPrefix>git-Tuinity-</outputPrefix> <!-- Tuinity -->
|
|
<scmDirectory>..</scmDirectory>
|
|
</configuration>
|
|
<executions>
|
|
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
index dd07223978..85b25eace7 100644
|
|
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
@@ -43,6 +43,9 @@ public final class MinecraftTimings {
|
|
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
|
|
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
|
|
|
|
+ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Tuinity - add timings for scoreboard search
|
|
+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Tuinity - add timings for distance manager
|
|
+
|
|
private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
|
|
|
|
private MinecraftTimings() {}
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index e33e889c29..5dfa065883 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -229,7 +229,8 @@ public class TimingsExport extends Thread {
|
|
parent.put("config", createObject(
|
|
pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
|
|
pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
|
|
- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
|
|
+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report
|
|
+ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report
|
|
));
|
|
|
|
new TimingsExport(listeners, parent, history).start();
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
|
|
index 49a38c6608..60d6469b81 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
|
|
@@ -13,7 +13,7 @@ import java.net.URL;
|
|
|
|
public class PaperVersionFetcher implements VersionFetcher {
|
|
private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end
|
|
- private static final String GITHUB_BRANCH_NAME = "master";
|
|
+ private static final String GITHUB_BRANCH_NAME = "ver/1.16"; // TODO TEMPORARY, REMOVE ONCE MASTER
|
|
private static @Nullable String mcVer;
|
|
|
|
@Override
|
|
@@ -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/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
index e7624948ea..77df688880 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
|
|
@@ -186,6 +186,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;
|
|
@@ -268,6 +269,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
|
|
@Override
|
|
protected void nextTick() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher
|
|
++this.currentTick;
|
|
if (this.currentTick != this.world.getTime()) {
|
|
if (!this.warnedAboutDesync) {
|
|
@@ -280,6 +282,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");
|
|
@@ -307,6 +310,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
|
|
if (toTick.tickState == STATE_TICKING) {
|
|
toTick.tickState = STATE_TICKED;
|
|
} // else it's STATE_CANCELLED_TICK
|
|
+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick
|
|
} else {
|
|
// re-schedule eventually
|
|
toTick.tickState = STATE_SCHEDULED;
|
|
@@ -424,6 +428,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;
|
|
@@ -479,6 +484,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
|
|
}
|
|
@@ -535,6 +541,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();
|
|
@@ -554,6 +561,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
|
|
@@ -585,6 +593,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);
|
|
|
|
@@ -594,6 +603,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/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
|
|
new file mode 100644
|
|
index 0000000000..f599725e17
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
|
|
@@ -0,0 +1,329 @@
|
|
+package com.tuinity.tuinity.chunk;
|
|
+
|
|
+import co.aikar.timings.MinecraftTimings;
|
|
+import co.aikar.timings.Timing;
|
|
+import com.google.common.collect.Iterators;
|
|
+import com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
|
+import net.minecraft.server.MCUtil;
|
|
+import net.minecraft.server.WorldServer;
|
|
+import java.util.ArrayList;
|
|
+import java.util.EnumMap;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.function.Function;
|
|
+import java.util.function.LongFunction;
|
|
+
|
|
+public final class SingleThreadChunkRegionManager<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
|
|
+
|
|
+ static final int REGION_SECTION_MERGE_RADIUS = 1;
|
|
+ // if this becomes > 8, then the RegionSection needs to be properly modified (see bitset)
|
|
+ public static final int REGION_CHUNK_SIZE = 8;
|
|
+ public static final int REGION_CHUNK_SIZE_SHIFT = 3; // log2(REGION_CHUNK_SIZE)
|
|
+
|
|
+ public final WorldServer world;
|
|
+ public final Class<T> dataClass;
|
|
+ public final String name;
|
|
+
|
|
+ public final Timing addChunkTimings;
|
|
+ public final Timing removeChunkTimings;
|
|
+ public final Timing regionRecalculateTimings;
|
|
+
|
|
+ protected final Long2ObjectOpenHashMap<RegionSection<T>> regionsBySection = new Long2ObjectOpenHashMap<>();
|
|
+ protected final ReferenceLinkedOpenHashSet<Region<T>> needsRecalculation = new ReferenceLinkedOpenHashSet<>();
|
|
+ protected final int minSectionRecalcCount;
|
|
+ protected final double maxDeadRegionPercent;
|
|
+
|
|
+ public SingleThreadChunkRegionManager(final WorldServer world, final Class<T> enumClass,
|
|
+ final int minSectionRecalcCount, final double maxDeadRegionPercent,
|
|
+ final String name) {
|
|
+ this.world = world;
|
|
+ this.dataClass = enumClass;
|
|
+ this.name = name;
|
|
+ this.minSectionRecalcCount = Math.max(1, minSectionRecalcCount);
|
|
+ this.maxDeadRegionPercent = maxDeadRegionPercent;
|
|
+
|
|
+ String prefix = world.getWorld().getName() + " - Region Manager - " + name + " - ";
|
|
+ this.addChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("add"));
|
|
+ this.removeChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("remove"));
|
|
+ this.regionRecalculateTimings = MinecraftTimings.getInternalTaskName(prefix.concat("recalculate"));
|
|
+ }
|
|
+
|
|
+ protected void addToRecalcQueue(final Region<T> region) {
|
|
+ this.needsRecalculation.add(region);
|
|
+ }
|
|
+
|
|
+ protected void removeFromRecalcQueue(final Region<T> region) {
|
|
+ this.needsRecalculation.remove(region);
|
|
+ }
|
|
+
|
|
+ public RegionSection<T> getRegionSection(final int chunkX, final int chunkZ) {
|
|
+ return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT));
|
|
+ }
|
|
+
|
|
+ public Region<T> getRegion(final int chunkX, final int chunkZ) {
|
|
+ final RegionSection<T> section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT));
|
|
+ return section != null ? section.region : null;
|
|
+ }
|
|
+
|
|
+ private final List<Region<T>> toMerge = new ArrayList<>((2 * REGION_SECTION_MERGE_RADIUS + 1) * (2 * REGION_SECTION_MERGE_RADIUS + 1));
|
|
+ protected final LongFunction<RegionSection<T>> createRegionIfAbsent = (final long keyInMap) -> {
|
|
+ return new RegionSection<>(keyInMap, SingleThreadChunkRegionManager.this);
|
|
+ };
|
|
+
|
|
+ protected RegionSection<T> getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection<T> ifAbsent) {
|
|
+ // find optimal candidate to merge into
|
|
+ final int minX = sectionX - REGION_SECTION_MERGE_RADIUS;
|
|
+ final int maxX = sectionX + REGION_SECTION_MERGE_RADIUS;
|
|
+ final int minZ = sectionZ - REGION_SECTION_MERGE_RADIUS;
|
|
+ final int maxZ = sectionZ + REGION_SECTION_MERGE_RADIUS;
|
|
+
|
|
+ int mergeCandidateSectionSize = -1;
|
|
+ Region<T> mergeIntoCandidate = null;
|
|
+
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ final RegionSection<T> section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ));
|
|
+ if (section == null) {
|
|
+ continue;
|
|
+ }
|
|
+ final Region<T> region = section.region;
|
|
+ final int sections = region.sections.size();
|
|
+
|
|
+ if (sections > mergeCandidateSectionSize) {
|
|
+ mergeCandidateSectionSize = sections;
|
|
+ mergeIntoCandidate = region;
|
|
+ }
|
|
+ this.toMerge.add(region);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // merge
|
|
+ if (mergeIntoCandidate != null) {
|
|
+ for (int len = this.toMerge.size(), i = len - 1; i >= 0; --i) {
|
|
+ final Region<T> region = this.toMerge.remove(i);
|
|
+ if (region.dead || mergeIntoCandidate == region) {
|
|
+ continue;
|
|
+ }
|
|
+ region.mergeInto(mergeIntoCandidate);
|
|
+ }
|
|
+ } else {
|
|
+ mergeIntoCandidate = new Region<>(this);
|
|
+ }
|
|
+
|
|
+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ);
|
|
+ final RegionSection<T> section;
|
|
+ if (ifAbsent == null) {
|
|
+ section = this.regionsBySection.computeIfAbsent(sectionKey, this.createRegionIfAbsent);
|
|
+ } else {
|
|
+ final RegionSection<T> existing = this.regionsBySection.putIfAbsent(sectionKey, ifAbsent);
|
|
+ section = existing == null ? ifAbsent : existing;
|
|
+ }
|
|
+
|
|
+ section.region = mergeIntoCandidate;
|
|
+ mergeIntoCandidate.sections.add(section);
|
|
+
|
|
+ return section;
|
|
+ }
|
|
+
|
|
+ public void addChunk(final int chunkX, final int chunkZ) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async region manager add chunk"); // Tuinity
|
|
+ this.addChunkTimings.startTiming();
|
|
+ try {
|
|
+ this.getOrCreateAndMergeSection(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT, null).addChunk(chunkX, chunkZ);
|
|
+ } finally {
|
|
+ this.addChunkTimings.stopTiming();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removeChunk(final int chunkX, final int chunkZ) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async region manager remove chunk"); // Tuinity
|
|
+ this.removeChunkTimings.startTiming();
|
|
+ try {
|
|
+ final RegionSection<T> section = this.regionsBySection.get(
|
|
+ MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT));
|
|
+ if (section != null) {
|
|
+ section.removeChunk(chunkX, chunkZ);
|
|
+ }
|
|
+ } finally {
|
|
+ this.removeChunkTimings.stopTiming();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void recalculateRegions() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async region recalculation"); // Tuinity
|
|
+ for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) {
|
|
+ final Region<T> region = this.needsRecalculation.removeFirst();
|
|
+
|
|
+ this.recalculateRegion(region);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void recalculateRegion(final Region<T> region) {
|
|
+ this.regionRecalculateTimings.startTiming();
|
|
+ try {
|
|
+ // clear unused regions
|
|
+ for (final Iterator<RegionSection<T>> iterator = region.deadSections.iterator(); iterator.hasNext(); ) {
|
|
+ final RegionSection<T> deadRegion = iterator.next();
|
|
+ this.regionsBySection.remove(deadRegion.regionCoordinate);
|
|
+ region.sections.remove(deadRegion);
|
|
+
|
|
+ iterator.remove();
|
|
+ }
|
|
+
|
|
+ // implicitly cover cases where size == 0
|
|
+ if (region.sections.size() < this.minSectionRecalcCount) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // run a test to see if we actually need to recalculate
|
|
+ // TODO
|
|
+
|
|
+ // destroy and rebuild the region
|
|
+
|
|
+ // destroy region state
|
|
+ for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(); iterator.hasNext(); ) {
|
|
+ final RegionSection<T> aliveRegion = iterator.next();
|
|
+ this.regionsBySection.remove(aliveRegion.regionCoordinate);
|
|
+ }
|
|
+
|
|
+ // rebuild regions
|
|
+ for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(); iterator.hasNext(); ) {
|
|
+ final RegionSection<T> aliveRegion = iterator.next();
|
|
+ this.getOrCreateAndMergeSection(aliveRegion.getSectionX(), aliveRegion.getSectionZ(), aliveRegion);
|
|
+ }
|
|
+ } finally {
|
|
+ this.regionRecalculateTimings.stopTiming();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class Region<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
|
|
+ protected final IteratorSafeOrderedReferenceSet<RegionSection<T>> sections = new IteratorSafeOrderedReferenceSet<>();
|
|
+ protected final ReferenceOpenHashSet<RegionSection<T>> deadSections = new ReferenceOpenHashSet<>(16, 0.7f);
|
|
+ protected boolean dead;
|
|
+ protected boolean markedForRecalc;
|
|
+
|
|
+ public final SingleThreadChunkRegionManager<T> regionManager;
|
|
+
|
|
+ protected Region(final SingleThreadChunkRegionManager<T> regionManager) {
|
|
+ this.regionManager = regionManager;
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet.Iterator<RegionSection<T>> getSections() {
|
|
+ return this.sections.iterator();
|
|
+ }
|
|
+
|
|
+ protected final double getDeadSectionPercent() {
|
|
+ return (double)this.deadSections.size() / (double)this.sections.size();
|
|
+ }
|
|
+
|
|
+ protected void mergeInto(final Region<T> mergeTarget) {
|
|
+ if (this.dead) {
|
|
+ throw new IllegalStateException("Cannot merge from dead region");
|
|
+ } else if (mergeTarget.dead) {
|
|
+ throw new IllegalStateException("Attempting to merge into a dead region");
|
|
+ }
|
|
+ this.dead = true;
|
|
+
|
|
+ for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(); iterator.hasNext();) {
|
|
+ final RegionSection<T> section = iterator.next();
|
|
+
|
|
+ if (!mergeTarget.sections.add(section)) {
|
|
+ throw new IllegalStateException("Target region cannot contain source's sections");
|
|
+ }
|
|
+
|
|
+ section.region = mergeTarget;
|
|
+ }
|
|
+
|
|
+ mergeTarget.deadSections.addAll(this.deadSections);
|
|
+ }
|
|
+
|
|
+ protected void markRegionAlive(final RegionSection<T> section) {
|
|
+ this.deadSections.remove(section);
|
|
+ if (this.markedForRecalc && (this.sections.size() < this.regionManager.minSectionRecalcCount || this.getDeadSectionPercent() < this.regionManager.maxDeadRegionPercent)) {
|
|
+ this.regionManager.removeFromRecalcQueue(this);
|
|
+ this.markedForRecalc = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void markRegionDead(final RegionSection<T> section) {
|
|
+ this.deadSections.add(section);
|
|
+ if (!this.markedForRecalc && this.sections.size() >= this.regionManager.minSectionRecalcCount && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) {
|
|
+ this.regionManager.addToRecalcQueue(this);
|
|
+ this.markedForRecalc = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class RegionSection<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
|
|
+ protected final long regionCoordinate;
|
|
+ protected long chunksBitset;
|
|
+ protected Region<T> region;
|
|
+ protected final EnumMap<T, Object> data;
|
|
+ protected final Function<? super T, Object> createIfAbsentFunction;
|
|
+
|
|
+ public final SingleThreadChunkRegionManager<T> regionManager;
|
|
+
|
|
+ protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager<T> regionManager) {
|
|
+ this.regionCoordinate = regionCoordinate;
|
|
+ this.data = new EnumMap<>(regionManager.dataClass);
|
|
+ this.regionManager = regionManager;
|
|
+ this.createIfAbsentFunction = (final T keyInMap) -> {
|
|
+ return keyInMap.createData(RegionSection.this, regionManager);
|
|
+ };
|
|
+ }
|
|
+
|
|
+ public int getSectionX() {
|
|
+ return MCUtil.getCoordinateX(this.regionCoordinate);
|
|
+ }
|
|
+
|
|
+ public int getSectionZ() {
|
|
+ return MCUtil.getCoordinateZ(this.regionCoordinate);
|
|
+ }
|
|
+
|
|
+ public Region<T> getRegion() {
|
|
+ return this.region;
|
|
+ }
|
|
+
|
|
+ public Object getData(final T key) {
|
|
+ return this.data.get(key);
|
|
+ }
|
|
+
|
|
+ public Object getOrCreateData(final T key) {
|
|
+ return this.data.computeIfAbsent(key, this.createIfAbsentFunction);
|
|
+ }
|
|
+
|
|
+ public Object removeData(final T key) {
|
|
+ return this.data.remove(key);
|
|
+ }
|
|
+
|
|
+ public void setData(final T key, final Object data) {
|
|
+ this.data.put(key, data);
|
|
+ }
|
|
+
|
|
+ protected void addChunk(final int chunkX, final int chunkZ) {
|
|
+ final long bitset = this.chunksBitset;
|
|
+ this.chunksBitset = bitset | (1L << ((chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT)));
|
|
+ if (bitset != 0L) {
|
|
+ return;
|
|
+ }
|
|
+ this.region.markRegionAlive(this);
|
|
+ }
|
|
+
|
|
+ protected void removeChunk(final int chunkX, final int chunkZ) {
|
|
+ final long bitset = this.chunksBitset |= ~(1L << ((chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT)));
|
|
+ if (bitset != 0L) {
|
|
+ return;
|
|
+ }
|
|
+ this.region.markRegionDead(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static interface RegionDataCreator<E extends Enum<E> & RegionDataCreator<E>> {
|
|
+
|
|
+ Object createData(final RegionSection<E> section,
|
|
+ final SingleThreadChunkRegionManager<E> regionManager);
|
|
+ }
|
|
+}
|
|
\ 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..1c7b858ed5
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
@@ -0,0 +1,279 @@
|
|
+package com.tuinity.tuinity.config;
|
|
+
|
|
+import com.destroystokyo.paper.util.SneakyThrow;
|
|
+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 = 2;
|
|
+
|
|
+ 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);
|
|
+ SneakyThrow.sneaky(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) {
|
|
+ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */
|
|
+ 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 int delayChunkUnloadsBy;
|
|
+
|
|
+ private static void delayChunkUnloadsBy() {
|
|
+ delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 1) * 20;
|
|
+ if (delayChunkUnloadsBy >= 0) {
|
|
+ TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static boolean lagCompensateBlockBreaking;
|
|
+
|
|
+ private static void lagCompensateBlockBreaking() {
|
|
+ lagCompensateBlockBreaking = TuinityConfig.getBoolean("lag-compensate-block-breaking", true);
|
|
+ }
|
|
+
|
|
+ public static boolean pistonsCanPushTileEntities;
|
|
+
|
|
+ private static void pistonsCanPushTileEntities() {
|
|
+ pistonsCanPushTileEntities = TuinityConfig.getBoolean("pistons-can-push-tile-entities", false);
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
+ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */
|
|
+ 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 spawnLimitMonsters;
|
|
+ public int spawnLimitAnimals;
|
|
+ public int spawnLimitWaterAmbient;
|
|
+ public int spawnLimitWaterAnimals;
|
|
+ public int spawnLimitAmbient;
|
|
+
|
|
+ private void perWorldSpawnLimit() {
|
|
+ final String path = "spawn-limits";
|
|
+
|
|
+ this.spawnLimitMonsters = this.getInt(path + ".monsters", -1);
|
|
+ this.spawnLimitAnimals = this.getInt(path + ".animals", -1);
|
|
+ this.spawnLimitWaterAmbient = this.getInt(path + ".water-ambient", -1);
|
|
+ this.spawnLimitWaterAnimals = this.getInt(path + ".water-animals", -1);
|
|
+ this.spawnLimitAmbient = this.getInt(path + ".ambient", -1);
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java
|
|
new file mode 100644
|
|
index 0000000000..a54f516ba7
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java
|
|
@@ -0,0 +1,53 @@
|
|
+package com.tuinity.tuinity.util;
|
|
+
|
|
+import net.minecraft.server.AxisAlignedBB;
|
|
+import net.minecraft.server.Entity;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.craftbukkit.util.UnsafeList;
|
|
+import java.util.List;
|
|
+
|
|
+public class CachedLists {
|
|
+
|
|
+ static final UnsafeList<AxisAlignedBB> TEMP_COLLISION_LIST = new UnsafeList<>(1024);
|
|
+ static boolean tempCollisionListInUse;
|
|
+
|
|
+ public static List<AxisAlignedBB> getTempCollisionList() {
|
|
+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) {
|
|
+ return new UnsafeList<>(16);
|
|
+ }
|
|
+ tempCollisionListInUse = true;
|
|
+ return TEMP_COLLISION_LIST;
|
|
+ }
|
|
+
|
|
+ public static void returnTempCollisionList(List<AxisAlignedBB> list) {
|
|
+ if (list != TEMP_COLLISION_LIST) {
|
|
+ return;
|
|
+ }
|
|
+ ((UnsafeList)list).setSize(0);
|
|
+ tempCollisionListInUse = false;
|
|
+ }
|
|
+
|
|
+ static final UnsafeList<Entity> TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024);
|
|
+ static boolean tempGetEntitiesListInUse;
|
|
+
|
|
+ public static List<Entity> getTempGetEntitiesList() {
|
|
+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) {
|
|
+ return new UnsafeList<>(16);
|
|
+ }
|
|
+ tempGetEntitiesListInUse = true;
|
|
+ return TEMP_GET_ENTITIES_LIST;
|
|
+ }
|
|
+
|
|
+ public static void returnTempGetEntitiesList(List<Entity> list) {
|
|
+ if (list != TEMP_GET_ENTITIES_LIST) {
|
|
+ return;
|
|
+ }
|
|
+ ((UnsafeList)list).setSize(0);
|
|
+ tempGetEntitiesListInUse = false;
|
|
+ }
|
|
+
|
|
+ public static void reset() {
|
|
+ TEMP_COLLISION_LIST.completeReset();
|
|
+ TEMP_GET_ENTITIES_LIST.completeReset();
|
|
+ }
|
|
+}
|
|
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..08ed243259
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java
|
|
@@ -0,0 +1,41 @@
|
|
+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()) {
|
|
+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ 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/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
new file mode 100644
|
|
index 0000000000..b0f1e21e6e
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
@@ -0,0 +1,271 @@
|
|
+package com.tuinity.tuinity.util.maplist;
|
|
+
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
|
+import java.util.Arrays;
|
|
+import java.util.NoSuchElementException;
|
|
+
|
|
+public final class IteratorSafeOrderedReferenceSet<E> {
|
|
+
|
|
+ protected final Reference2IntLinkedOpenHashMap<E> indexMap;
|
|
+ protected int firstInvalidIndex = -1;
|
|
+
|
|
+ protected final ReferenceLinkedOpenHashSet<E> pendingAdditions;
|
|
+
|
|
+ /* list impl */
|
|
+ protected E[] listElements;
|
|
+ protected int listSize;
|
|
+
|
|
+ protected final double maxFragFactor;
|
|
+
|
|
+ protected int iteratorCount;
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet() {
|
|
+ this(16, 0.75f, 16, 0.2);
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, final double maxFragFactor) {
|
|
+ this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor);
|
|
+ this.indexMap.defaultReturnValue(-1);
|
|
+ this.pendingAdditions = new ReferenceLinkedOpenHashSet<>();
|
|
+ this.maxFragFactor = maxFragFactor;
|
|
+ this.listElements = (E[])new Object[arrayCapacity];
|
|
+ }
|
|
+
|
|
+ protected final double getFragFactor() {
|
|
+ return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
|
|
+ }
|
|
+
|
|
+ public int createRawIterator() {
|
|
+ ++this.iteratorCount;
|
|
+ if (this.indexMap.isEmpty()) {
|
|
+ return -1;
|
|
+ } else {
|
|
+ return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int advanceRawIterator(final int index) {
|
|
+ final E[] elements = this.listElements;
|
|
+ int ret = index + 1;
|
|
+ for (int len = this.listSize; ret < len; ++ret) {
|
|
+ if (elements[ret] != null) {
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ public void finishRawIterator() {
|
|
+ if (--this.iteratorCount == 0) {
|
|
+ if (this.getFragFactor() >= this.maxFragFactor) {
|
|
+ this.defrag();
|
|
+ }
|
|
+ if (!this.pendingAdditions.isEmpty()) {
|
|
+ int index = this.listSize;
|
|
+ int neededLen = index + this.pendingAdditions.size();
|
|
+
|
|
+ if (neededLen < 0) {
|
|
+ throw new IllegalStateException("Too large");
|
|
+ }
|
|
+
|
|
+ if (neededLen > this.listElements.length) {
|
|
+ this.listElements = Arrays.copyOf(this.listElements, neededLen * 2);
|
|
+ }
|
|
+
|
|
+ final E[] elements = this.listElements;
|
|
+ java.util.Iterator<E> iterator = this.pendingAdditions.iterator();
|
|
+ for (int i = index; i < neededLen; ++i) {
|
|
+ final E element = iterator.next();
|
|
+ elements[i] = element;
|
|
+ this.indexMap.put(element, i);
|
|
+ }
|
|
+
|
|
+ this.pendingAdditions.clear();
|
|
+ this.listSize = neededLen;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean remove(final E element) {
|
|
+ final int index = this.indexMap.removeInt(element);
|
|
+ if (index >= 0) {
|
|
+ if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) {
|
|
+ this.firstInvalidIndex = index;
|
|
+ }
|
|
+ this.listElements[index] = null;
|
|
+ return true;
|
|
+ } else {
|
|
+ return this.pendingAdditions.remove(element);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean add(final E element) {
|
|
+ if (this.iteratorCount > 0) {
|
|
+ if (this.indexMap.containsKey(element)) {
|
|
+ return true;
|
|
+ }
|
|
+ return this.pendingAdditions.add(element);
|
|
+ } else {
|
|
+ final int listSize = this.listSize;
|
|
+
|
|
+ final int previous = this.indexMap.putIfAbsent(element, listSize);
|
|
+ if (previous != -1) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (listSize >= this.listElements.length) {
|
|
+ this.listElements = Arrays.copyOf(this.listElements, listSize * 2);
|
|
+ }
|
|
+ this.listElements[listSize] = element;
|
|
+ this.listSize = listSize + 1;
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void defrag() {
|
|
+ if (this.firstInvalidIndex < 0) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ if (this.indexMap.isEmpty()) {
|
|
+ Arrays.fill(this.listElements, 0, this.listSize, null);
|
|
+ this.listSize = 0;
|
|
+ this.firstInvalidIndex = -1;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final E[] backingArray = this.listElements;
|
|
+
|
|
+ int lastValidIndex;
|
|
+ java.util.Iterator<Reference2IntMap.Entry<E>> iterator;
|
|
+
|
|
+ if (this.firstInvalidIndex == 0) {
|
|
+ iterator = this.indexMap.reference2IntEntrySet().fastIterator();
|
|
+ lastValidIndex = 0;
|
|
+ } else {
|
|
+ lastValidIndex = this.firstInvalidIndex;
|
|
+ final E key = backingArray[lastValidIndex - 1];
|
|
+ iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry<E>() {
|
|
+ @Override
|
|
+ public int getIntValue() {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int setValue(int i) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E getKey() {
|
|
+ return key;
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ while (iterator.hasNext()) {
|
|
+ final Reference2IntMap.Entry<E> entry = iterator.next();
|
|
+
|
|
+ final int newIndex = lastValidIndex++;
|
|
+ backingArray[newIndex] = entry.getKey();
|
|
+ entry.setValue(newIndex);
|
|
+ }
|
|
+
|
|
+ // cleanup end
|
|
+ Arrays.fill(backingArray, lastValidIndex, this.listSize, null);
|
|
+ this.listSize = lastValidIndex;
|
|
+ this.firstInvalidIndex = -1;
|
|
+ }
|
|
+
|
|
+ public E rawGet(final int index) {
|
|
+ return this.listElements[index];
|
|
+ }
|
|
+
|
|
+ public int size() {
|
|
+ // always returns the correct amount - listSize can be different
|
|
+ return this.indexMap.size();
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
|
|
+ ++this.iteratorCount;
|
|
+ return new BaseIterator<>(this, true);
|
|
+ }
|
|
+
|
|
+ public java.util.Iterator<E> unsafeIterator() {
|
|
+ return new BaseIterator<>(this, false);
|
|
+ }
|
|
+
|
|
+ public static interface Iterator<E> extends java.util.Iterator<E> {
|
|
+
|
|
+ public void finishedIterating();
|
|
+
|
|
+ }
|
|
+
|
|
+ protected static final class BaseIterator<E> implements IteratorSafeOrderedReferenceSet.Iterator<E> {
|
|
+
|
|
+ protected final IteratorSafeOrderedReferenceSet<E> set;
|
|
+ protected final boolean canFinish;
|
|
+ protected int nextIndex;
|
|
+ protected E currentValue;
|
|
+ protected boolean finished;
|
|
+
|
|
+ protected BaseIterator(final IteratorSafeOrderedReferenceSet<E> set, final boolean canFinish) {
|
|
+ this.set = set;
|
|
+ this.canFinish = canFinish;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasNext() {
|
|
+ if (this.finished) {
|
|
+ return false;
|
|
+ }
|
|
+ if (this.currentValue != null) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ final E[] elements = this.set.listElements;
|
|
+ int index, len;
|
|
+ for (index = this.nextIndex, len = this.set.listSize; index < len; ++index) {
|
|
+ final E element = elements[index];
|
|
+ if (element != null) {
|
|
+ this.currentValue = element;
|
|
+ this.nextIndex = index + 1;
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.nextIndex = index;
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E next() {
|
|
+ if (!this.hasNext()) {
|
|
+ throw new NoSuchElementException();
|
|
+ }
|
|
+ final E ret = this.currentValue;
|
|
+
|
|
+ this.currentValue = null;
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove() {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void finishedIterating() {
|
|
+ if (this.finished || !this.canFinish) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ this.finished = true;
|
|
+ this.set.finishRawIterator();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java
|
|
new file mode 100644
|
|
index 0000000000..76593df295
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java
|
|
@@ -0,0 +1,246 @@
|
|
+package com.tuinity.tuinity.voxel;
|
|
+
|
|
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
|
+import it.unimi.dsi.fastutil.doubles.DoubleList;
|
|
+import net.minecraft.server.AxisAlignedBB;
|
|
+import net.minecraft.server.EnumDirection;
|
|
+import net.minecraft.server.VoxelShape;
|
|
+import net.minecraft.server.VoxelShapes;
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+
|
|
+public final class AABBVoxelShape extends VoxelShape {
|
|
+
|
|
+ public final AxisAlignedBB aabb;
|
|
+ private boolean isEmpty;
|
|
+
|
|
+ public AABBVoxelShape(AxisAlignedBB aabb) {
|
|
+ super(VoxelShapes.getFullUnoptimisedCube().getShape());
|
|
+ this.aabb = aabb;
|
|
+ this.isEmpty = (fuzzyEquals(aabb.minX, aabb.maxX) && fuzzyEquals(aabb.minY, aabb.maxY) && fuzzyEquals(aabb.minZ, aabb.maxZ));
|
|
+ }
|
|
+
|
|
+ static boolean fuzzyEquals(double d0, double d1) {
|
|
+ return Math.abs(d0 - d1) <= 1.0e-7;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isEmpty() {
|
|
+ return this.isEmpty;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double b(EnumDirection.EnumAxis enumdirection_enumaxis) { // getMin
|
|
+ switch (enumdirection_enumaxis.ordinal()) {
|
|
+ case 0:
|
|
+ return this.aabb.minX;
|
|
+ case 1:
|
|
+ return this.aabb.minY;
|
|
+ case 2:
|
|
+ return this.aabb.minZ;
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double c(EnumDirection.EnumAxis enumdirection_enumaxis) { //getMax
|
|
+ switch (enumdirection_enumaxis.ordinal()) {
|
|
+ case 0:
|
|
+ return this.aabb.maxX;
|
|
+ case 1:
|
|
+ return this.aabb.maxY;
|
|
+ case 2:
|
|
+ return this.aabb.maxZ;
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public AxisAlignedBB getBoundingBox() { // rets bounding box enclosing this entire shape
|
|
+ return this.aabb;
|
|
+ }
|
|
+
|
|
+ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis.
|
|
+ @Override
|
|
+ protected double a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { // getPointFromIndex
|
|
+ switch (enumdirection_enumaxis.ordinal() | (i << 2)) {
|
|
+ case (0 | (0 << 2)):
|
|
+ return this.aabb.minX;
|
|
+ case (1 | (0 << 2)):
|
|
+ return this.aabb.minY;
|
|
+ case (2 | (0 << 2)):
|
|
+ return this.aabb.minZ;
|
|
+ case (0 | (1 << 2)):
|
|
+ return this.aabb.maxX;
|
|
+ case (1 | (1 << 2)):
|
|
+ return this.aabb.maxY;
|
|
+ case (2 | (1 << 2)):
|
|
+ return this.aabb.maxZ;
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private DoubleList cachedListX;
|
|
+ private DoubleList cachedListY;
|
|
+ private DoubleList cachedListZ;
|
|
+
|
|
+ @Override
|
|
+ protected DoubleList a(EnumDirection.EnumAxis enumdirection_enumaxis) { // getPoints
|
|
+ switch (enumdirection_enumaxis.ordinal()) {
|
|
+ case 0:
|
|
+ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX;
|
|
+ case 1:
|
|
+ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY;
|
|
+ case 2:
|
|
+ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ;
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public VoxelShape a(double d0, double d1, double d2) { // createOffset
|
|
+ return new AABBVoxelShape(this.aabb.offset(d0, d1, d2));
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public VoxelShape c() { // simplify
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void b(VoxelShapes.a voxelshapes_a) { // forEachAABB
|
|
+ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<AxisAlignedBB> d() { // getAABBs
|
|
+ List<AxisAlignedBB> ret = new ArrayList<>(1);
|
|
+ ret.add(this.aabb);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected int a(EnumDirection.EnumAxis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset
|
|
+ switch (enumdirection_enumaxis.ordinal()) {
|
|
+ case 0:
|
|
+ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1;
|
|
+ case 1:
|
|
+ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1;
|
|
+ case 2:
|
|
+ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1;
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected boolean b(double d0, double d1, double d2) { // containsPoint
|
|
+ return this.aabb.contains(d0, d1, d2);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public VoxelShape a(EnumDirection enumdirection) { // unknown
|
|
+ return super.a(enumdirection);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, double d0) { // collide
|
|
+ if (this.isEmpty) {
|
|
+ return d0;
|
|
+ }
|
|
+ if (Math.abs(d0) < 1.0e-7) {
|
|
+ return 0.0;
|
|
+ }
|
|
+ switch (enumdirection_enumaxis.ordinal()) {
|
|
+ case 0:
|
|
+ return this.collideX(axisalignedbb, d0);
|
|
+ case 1:
|
|
+ return this.collideY(axisalignedbb, d0);
|
|
+ case 2:
|
|
+ return this.collideZ(axisalignedbb, d0);
|
|
+ default:
|
|
+ throw new IllegalStateException("Unknown axis requested");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // collideX, collideY, collideZ are copied from 1.12 src and remapped
|
|
+ // so the code all belongs to mojang
|
|
+
|
|
+ public double collideX(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxY > this.aabb.minY && axisalignedbb.minY < this.aabb.maxY
|
|
+ && axisalignedbb.maxZ > this.aabb.minZ && axisalignedbb.minZ < this.aabb.maxZ) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxX <= this.aabb.minX) {
|
|
+ d1 = this.aabb.minX - axisalignedbb.maxX;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minX >= this.aabb.maxX) {
|
|
+ d1 = this.aabb.maxX - axisalignedbb.minX;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double collideY(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxX > this.aabb.minX && axisalignedbb.minX < this.aabb.maxX
|
|
+ && axisalignedbb.maxZ > this.aabb.minZ && axisalignedbb.minZ < this.aabb.maxZ) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxY <= this.aabb.minY) {
|
|
+ d1 = this.aabb.minY - axisalignedbb.maxY;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minY >= this.aabb.maxY) {
|
|
+ d1 = this.aabb.maxY - axisalignedbb.minY;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double collideZ(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxX > this.aabb.minX && axisalignedbb.minX < this.aabb.maxX
|
|
+ && axisalignedbb.maxY > this.aabb.minY && axisalignedbb.minY < this.aabb.maxY) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxZ <= this.aabb.minZ) {
|
|
+ d1 = this.aabb.minZ - axisalignedbb.maxZ;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minZ >= this.aabb.maxZ) {
|
|
+ d1 = this.aabb.maxZ - axisalignedbb.minZ;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean intersects(AxisAlignedBB axisalingedbb) {
|
|
+ return this.aabb.intersects(axisalingedbb);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
index ed9b2f9adf..d54bf71409 100644
|
|
--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
+++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
@@ -13,6 +13,119 @@ public class AxisAlignedBB {
|
|
public final double maxY;
|
|
public final double maxZ;
|
|
|
|
+ // Tuinity start
|
|
+ public final boolean isEmpty() {
|
|
+ return (this.maxX - this.minX) < 1.0E-7 && (this.maxY - this.minY) < 1.0E-7 && (this.maxZ - this.minZ) < 1.0E-7;
|
|
+ }
|
|
+
|
|
+ public static AxisAlignedBB getBoxForChunk(int chunkX, int chunkZ) {
|
|
+ double x = (double)(chunkX << 4);
|
|
+ double z = (double)(chunkZ << 4);
|
|
+ // use a bounding box bigger than the chunk to prevent entities from entering it on move
|
|
+ return new AxisAlignedBB(x - 1.0E-7, 0.0, z - 1.0E-7, x + (16.0 + 1.0E-7), 255.0, z + (16.0 + 1.0E-7), false);
|
|
+ }
|
|
+
|
|
+ // collideX, collideY, collideZ are copied from 1.12 src
|
|
+ // so the code all belongs to mojang
|
|
+ public double collideX(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY
|
|
+ && axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxX <= this.minX) {
|
|
+ d1 = this.minX - axisalignedbb.maxX;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minX >= this.maxX) {
|
|
+ d1 = this.maxX - axisalignedbb.minX;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double collideY(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX
|
|
+ && axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxY <= this.minY) {
|
|
+ d1 = this.minY - axisalignedbb.maxY;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minY >= this.maxY) {
|
|
+ d1 = this.maxY - axisalignedbb.minY;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double collideZ(AxisAlignedBB axisalignedbb, double d0) {
|
|
+ if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX
|
|
+ && axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY) {
|
|
+ double d1;
|
|
+
|
|
+ if (d0 > 0.0D && axisalignedbb.maxZ <= this.minZ) {
|
|
+ d1 = this.minZ - axisalignedbb.maxZ;
|
|
+ if (d1 < d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ } else if (d0 < 0.0D && axisalignedbb.minZ >= this.maxZ) {
|
|
+ d1 = this.maxZ - axisalignedbb.minZ;
|
|
+ if (d1 > d0) {
|
|
+ d0 = d1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return d0;
|
|
+ } else {
|
|
+ return d0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB offsetX(double dx) {
|
|
+ return new AxisAlignedBB(this.minX + dx, this.minY, this.minZ, this.maxX + dx, this.maxY, this.maxZ, false);
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB offsetY(double dy) {
|
|
+ return new AxisAlignedBB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false);
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB offsetZ(double dz) {
|
|
+ return new AxisAlignedBB(this.minX, this.minY, this.minZ + dz, this.maxX, this.maxY, this.maxZ + dz, false);
|
|
+ }
|
|
+
|
|
+ public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5, boolean dummy) {
|
|
+ this.minX = d0;
|
|
+ this.minY = d1;
|
|
+ this.minZ = d2;
|
|
+ this.maxX = d3;
|
|
+ this.maxY = d4;
|
|
+ this.maxZ = d5;
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB expandUpwards(double dy) {
|
|
+ return new AxisAlignedBB(this.minX, this.minY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false);
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB expandUpwardsAndCutBelow(double dy) {
|
|
+ return new AxisAlignedBB(this.minX, this.maxY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) {
|
|
this.minX = Math.min(d0, d3);
|
|
this.minY = Math.min(d1, d4);
|
|
@@ -185,6 +298,7 @@ public class AxisAlignedBB {
|
|
return new AxisAlignedBB(d0, d1, d2, d3, d4, d5);
|
|
}
|
|
|
|
+ public final AxisAlignedBB offset(double d0, double d1, double d2) { return this.d(d0, d1, d2); } // Tuinity - OBFHELPER
|
|
public AxisAlignedBB d(double d0, double d1, double d2) {
|
|
return new AxisAlignedBB(this.minX + d0, this.minY + d1, this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2);
|
|
}
|
|
@@ -193,6 +307,7 @@ public class AxisAlignedBB {
|
|
return new AxisAlignedBB(this.minX + (double) blockposition.getX(), this.minY + (double) blockposition.getY(), this.minZ + (double) blockposition.getZ(), this.maxX + (double) blockposition.getX(), this.maxY + (double) blockposition.getY(), this.maxZ + (double) blockposition.getZ());
|
|
}
|
|
|
|
+ public final AxisAlignedBB offset(Vec3D vec3d) { return this.b(vec3d); } // Tuinity - OBFHELPER
|
|
public AxisAlignedBB c(Vec3D vec3d) {
|
|
return this.d(vec3d.x, vec3d.y, vec3d.z);
|
|
}
|
|
@@ -212,6 +327,7 @@ public class AxisAlignedBB {
|
|
return this.e(vec3d.x, vec3d.y, vec3d.z);
|
|
}
|
|
|
|
+ public final boolean contains(double d0, double d1, double d2) { return this.e(d0, d1, d2); } // 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/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
index 3b0c8971c6..4816493c6a 100644
|
|
--- a/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
@@ -16,9 +16,9 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
|
|
return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()});
|
|
});
|
|
public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0);
|
|
- private int a;public final void setX(final int x) { this.a = x; } // Paper - OBFHELPER
|
|
- private int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER
|
|
- private int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER
|
|
+ protected int a;public final void setX(final int x) { this.a = x; } // Paper - OBFHELPER // Tuinity - private->protected
|
|
+ protected int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER // Tuinity - private->protected
|
|
+ protected int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER // Tuinity - private->protected
|
|
|
|
// Paper start
|
|
public boolean isValidLocation() {
|
|
@@ -71,15 +71,15 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
|
|
return this.e;
|
|
}
|
|
|
|
- protected void o(int i) {
|
|
+ protected void o_unused(int i) { // Tuinity - not needed here
|
|
this.a = i;
|
|
}
|
|
|
|
- protected void p(int i) {
|
|
+ protected void p_unused(int i) { // Tuinity - not needed here
|
|
this.b = i;
|
|
}
|
|
|
|
- protected void q(int i) {
|
|
+ protected void q_unused(int i) { // Tuinity - not needed here
|
|
this.e = i;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java
|
|
index db198811dd..52ebdfcc03 100644
|
|
--- a/src/main/java/net/minecraft/server/BiomeBase.java
|
|
+++ b/src/main/java/net/minecraft/server/BiomeBase.java
|
|
@@ -92,6 +92,18 @@ public class BiomeBase {
|
|
return new WorldGenCarverWrapper<>(worldgencarverabstract, c0);
|
|
}
|
|
|
|
+ // Tuinity start - optimise biome conversion
|
|
+ private org.bukkit.block.Biome bukkitBiome;
|
|
+
|
|
+ public final org.bukkit.block.Biome getBukkitBiome() {
|
|
+ if (this.bukkitBiome == null) {
|
|
+ this.bukkitBiome = org.bukkit.block.Biome.valueOf(IRegistry.BIOME.getKey(this).getKey().toUpperCase(java.util.Locale.ENGLISH));
|
|
+ }
|
|
+
|
|
+ return this.bukkitBiome;
|
|
+ }
|
|
+ // Tuinity end - optimise biome conversion
|
|
+
|
|
protected BiomeBase(BiomeBase.a biomebase_a) {
|
|
if (biomebase_a.a != null && biomebase_a.b != null && biomebase_a.c != null && biomebase_a.d != null && biomebase_a.e != null && biomebase_a.f != null && biomebase_a.g != null && biomebase_a.j != null) {
|
|
this.m = biomebase_a.a;
|
|
diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java
|
|
index 7b0cc78334..a53e096515 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockBase.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockBase.java
|
|
@@ -176,8 +176,8 @@ public abstract class BlockBase {
|
|
return VoxelShapes.a();
|
|
}
|
|
|
|
- @Deprecated
|
|
- public int f(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) {
|
|
+ @Deprecated public final int getOpacity(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { return this.f(iblockdata, iblockaccess, blockposition); } // Tuinity - OBFHELPER
|
|
+ @Deprecated public int f(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { // Tuinity - OBFHELPER
|
|
return iblockdata.i(iblockaccess, blockposition) ? iblockaccess.H() : (iblockdata.a(iblockaccess, blockposition) ? 0 : 1);
|
|
}
|
|
|
|
@@ -289,14 +289,14 @@ public abstract class BlockBase {
|
|
|
|
public abstract static class BlockData extends IBlockDataHolder<Block, IBlockData> {
|
|
|
|
- private final int b;
|
|
- private final boolean e;
|
|
+ private final int b; public final int getEmittedLight() { return this.b; } // Tuinity - OBFHELPER
|
|
+ private final boolean e; public final boolean isTransparentOnSomeFaces() { return this.e; } // Tuinity - OBFHELPER
|
|
private final boolean f;
|
|
private final Material g;
|
|
private final MaterialMapColor h;
|
|
public final float strength;
|
|
private final boolean j;
|
|
- private final boolean k;
|
|
+ private final boolean k; public final boolean isOpaque() { return this.k; } // Tuinity - OBFHELPER
|
|
private final BlockBase.e l;
|
|
private final BlockBase.e m;
|
|
private final BlockBase.e n;
|
|
@@ -332,10 +332,25 @@ public abstract class BlockBase {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start - micro the hell out of this call
|
|
+ protected boolean shapeExceedsCube = true;
|
|
+ public final boolean shapeExceedsCube() {
|
|
+ return this.shapeExceedsCube;
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
+ // Tuinity start
|
|
+ protected boolean isTicking;
|
|
+ protected Fluid fluid;
|
|
+ // Tuinity end
|
|
+
|
|
public void a() {
|
|
+ this.fluid = this.getBlock().d(this.p()); // Tuinity - moved from getFluid()
|
|
+ this.isTicking = this.getBlock().isTicking(this.p()); // Tuinity - moved from isTicking()
|
|
if (!this.getBlock().o()) {
|
|
this.a = new BlockBase.BlockData.a(this.p());
|
|
}
|
|
+ this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here
|
|
|
|
}
|
|
|
|
@@ -359,10 +374,12 @@ public abstract class BlockBase {
|
|
return this.a != null ? this.a.g : this.getBlock().b(this.p(), iblockaccess, blockposition);
|
|
}
|
|
|
|
+ public final int getOpacity(IBlockAccess iblockaccess, BlockPosition blockposition) { return this.b(iblockaccess, blockposition); } // Tuinity - OBFHELPER
|
|
public int b(IBlockAccess iblockaccess, BlockPosition blockposition) {
|
|
return this.a != null ? this.a.h : this.getBlock().f(this.p(), iblockaccess, blockposition);
|
|
}
|
|
|
|
+ public final VoxelShape getCullingFace(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a(iblockaccess, blockposition, enumdirection); } // Tuinity - OBFHELPER
|
|
public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) {
|
|
return this.a != null && this.a.i != null ? this.a.i[enumdirection.ordinal()] : VoxelShapes.a(this.c(iblockaccess, blockposition), enumdirection);
|
|
}
|
|
@@ -371,19 +388,19 @@ public abstract class BlockBase {
|
|
return this.getBlock().d(this.p(), iblockaccess, blockposition);
|
|
}
|
|
|
|
- public boolean d() {
|
|
- return this.a == null || this.a.c;
|
|
+ public final boolean d() { // Tuinity
|
|
+ return this.shapeExceedsCube; // Tuinity - moved into shape cache init
|
|
}
|
|
|
|
- public boolean e() {
|
|
+ public final boolean e() { // Tuinity
|
|
return this.e;
|
|
}
|
|
|
|
- public int f() {
|
|
+ public final int f() { // Tuinity
|
|
return this.b;
|
|
}
|
|
|
|
- public boolean isAir() {
|
|
+ public final boolean isAir() { // Tuinity
|
|
return this.f;
|
|
}
|
|
|
|
@@ -449,7 +466,7 @@ public abstract class BlockBase {
|
|
}
|
|
}
|
|
|
|
- public boolean l() {
|
|
+ public final boolean l() { // Tuinity
|
|
return this.k;
|
|
}
|
|
|
|
@@ -621,12 +638,12 @@ public abstract class BlockBase {
|
|
return this.getBlock().a(block);
|
|
}
|
|
|
|
- public Fluid getFluid() {
|
|
- return this.getBlock().d(this.p());
|
|
+ public final Fluid getFluid() { // Tuinity
|
|
+ return this.fluid; // Tuinity - moved into init
|
|
}
|
|
|
|
- public boolean isTicking() {
|
|
- return this.getBlock().isTicking(this.p());
|
|
+ public final boolean isTicking() { // Tuinity
|
|
+ return this.isTicking; // Tuinity - moved into init
|
|
}
|
|
|
|
public SoundEffectType getStepSound() {
|
|
diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java
|
|
index 44b9bfcdc7..dba774018c 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockChest.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockChest.java
|
|
@@ -10,7 +10,7 @@ import javax.annotation.Nullable;
|
|
public class BlockChest extends BlockChestAbstract<TileEntityChest> implements IBlockWaterlogged {
|
|
|
|
public static final BlockStateDirection FACING = BlockFacingHorizontal.FACING;
|
|
- public static final BlockStateEnum<BlockPropertyChestType> c = BlockProperties.aF;
|
|
+ public static final BlockStateEnum<BlockPropertyChestType> c = BlockProperties.aF; public static final BlockStateEnum<BlockPropertyChestType> getChestTypeEnum() { return BlockChest.c; } // Tuinity - OBFHELPER
|
|
public static final BlockStateBoolean d = BlockProperties.C; public static final BlockStateBoolean waterlogged() { return d; } // Paper OBFHELPER
|
|
protected static final VoxelShape e = Block.a(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D);
|
|
protected static final VoxelShape f = Block.a(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D);
|
|
@@ -195,7 +195,7 @@ public class BlockChest extends BlockChestAbstract<TileEntityChest> implements I
|
|
@Override
|
|
public void remove(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) {
|
|
if (!iblockdata.a(iblockdata1.getBlock())) {
|
|
- TileEntity tileentity = world.getTileEntity(blockposition);
|
|
+ TileEntity tileentity = world.getTileEntity(blockposition, false); // Tuinity - block has since changed.
|
|
|
|
if (tileentity instanceof IInventory) {
|
|
InventoryUtils.dropInventory(world, blockposition, (IInventory) tileentity);
|
|
diff --git a/src/main/java/net/minecraft/server/BlockPiston.java b/src/main/java/net/minecraft/server/BlockPiston.java
|
|
index c3133814f1..4f10ca5ada 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPiston.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPiston.java
|
|
@@ -270,7 +270,10 @@ public class BlockPiston extends BlockDirectional {
|
|
return false;
|
|
}
|
|
|
|
- return !iblockdata.getBlock().isTileEntity();
|
|
+ // Tuinity start - pushable TE's
|
|
+ TileEntity tileEntity;
|
|
+ return !iblockdata.getBlock().isTileEntity() || ((tileEntity = world.getTileEntity(blockposition)) != null && tileEntity.isPushable());
|
|
+ // Tuinity end - pushable TE's
|
|
} else {
|
|
return false;
|
|
}
|
|
@@ -369,7 +372,7 @@ public class BlockPiston extends BlockDirectional {
|
|
|
|
for (k = list.size() - 1; k >= 0; --k) {
|
|
// Paper start - fix a variety of piston desync dupes
|
|
- boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication;
|
|
+ boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication && !list1.get(k).getBlock().isTileEntity(); // Tuinity - pushable TE's
|
|
BlockPosition oldPos = blockposition3 = (BlockPosition) list.get(k);
|
|
iblockdata1 = allowDesync ? world.getType(oldPos) : null;
|
|
// Paper end - fix a variety of piston desync dupes
|
|
@@ -381,10 +384,29 @@ public class BlockPiston extends BlockDirectional {
|
|
iblockdata1 = world.getType(oldPos);
|
|
map.replace(oldPos, iblockdata1);
|
|
}
|
|
- world.setTileEntity(blockposition3, BlockPistonMoving.a(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false));
|
|
+ // Tuinity start - pushable TE's
|
|
+ TileEntity tileEntity = world.getTileEntity(oldPos);
|
|
+ if (tileEntity != null) {
|
|
+ if (!tileEntity.isPushable()) {
|
|
+ tileEntity = null;
|
|
+ } else {
|
|
+ // ensure the death of world tied state
|
|
+ if (tileEntity instanceof IInventory) {
|
|
+ MCUtil.closeInventory((IInventory)tileEntity, org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE);
|
|
+ }
|
|
+ if (tileEntity instanceof TileEntityLectern) {
|
|
+ MCUtil.closeInventory(((TileEntityLectern)tileEntity).inventory, org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE);
|
|
+ }
|
|
+
|
|
+ // now copy
|
|
+ tileEntity = tileEntity.createCopyForPush((WorldServer)world, oldPos, blockposition3, iblockdata1);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
if (!allowDesync) {
|
|
- world.setTypeAndData(oldPos, Blocks.AIR.getBlockData(), 2 | 4 | 16 | 1024); // set air to prevent later physics updates from seeing this block
|
|
+ world.setTypeAndDataRaw(oldPos, Blocks.AIR.getBlockData(), null); // Tuinity - don't fire logic for removing the old block
|
|
}
|
|
+ world.setTileEntity(blockposition3, BlockPistonMoving.createPistonTile(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false, tileEntity)); // Tuinity - pushable TE's
|
|
// Paper end - fix a variety of piston desync dupes
|
|
aiblockdata[j++] = iblockdata1;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/BlockPistonMoving.java b/src/main/java/net/minecraft/server/BlockPistonMoving.java
|
|
index 4bf66420f5..bf76615d72 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPistonMoving.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPistonMoving.java
|
|
@@ -21,7 +21,12 @@ public class BlockPistonMoving extends BlockTileEntity {
|
|
}
|
|
|
|
public static TileEntity a(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1) {
|
|
- return new TileEntityPiston(iblockdata, enumdirection, flag, flag1);
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ return createPistonTile(iblockdata, enumdirection, flag, flag1, null);
|
|
+ }
|
|
+ public static TileEntity createPistonTile(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1, TileEntity tileEntity) {
|
|
+ return new TileEntityPiston(iblockdata, enumdirection, flag, flag1, tileEntity);
|
|
+ // Tuinity end - add tileEntity parameter
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
index a2ee39b7ef..7bdefff433 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
@@ -387,10 +387,10 @@ public class BlockPosition extends BaseBlockPosition {
|
|
}
|
|
|
|
public final BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER
|
|
- public BlockPosition.MutableBlockPosition d(int i, int j, int k) {
|
|
- this.o(i);
|
|
- this.p(j);
|
|
- this.q(k);
|
|
+ public final BlockPosition.MutableBlockPosition d(int i, int j, int k) { // Tuinity
|
|
+ ((BaseBlockPosition)this).a = i; // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).b = j; // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).e = k; // Tuinity - force inline
|
|
return this;
|
|
}
|
|
|
|
@@ -400,12 +400,18 @@ public class BlockPosition extends BaseBlockPosition {
|
|
}
|
|
|
|
public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER
|
|
- public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) {
|
|
- return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ());
|
|
+ public final BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { // Tuinity
|
|
+ ((BaseBlockPosition)this).a = baseblockposition.a; // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).b = baseblockposition.b; // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).e = baseblockposition.e; // Tuinity - force inline
|
|
+ return this;
|
|
}
|
|
|
|
- public BlockPosition.MutableBlockPosition g(long i) {
|
|
- return this.d(b(i), c(i), d(i));
|
|
+ public final BlockPosition.MutableBlockPosition g(long i) { // Tuinity
|
|
+ ((BaseBlockPosition)this).a = (int)(i >> 38); // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).b = (int)((i << 52) >> 52); // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).e = (int)((i << 26) >> 38); // Tuinity - force inline
|
|
+ return this;
|
|
}
|
|
|
|
public BlockPosition.MutableBlockPosition a(EnumAxisCycle enumaxiscycle, int i, int j, int k) {
|
|
@@ -420,8 +426,11 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return this.d(baseblockposition.getX() + i, baseblockposition.getY() + j, baseblockposition.getZ() + k);
|
|
}
|
|
|
|
- public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) {
|
|
- return this.c(enumdirection, 1);
|
|
+ public final BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { // Tuinity
|
|
+ ((BaseBlockPosition)this).a += enumdirection.getAdjacentX(); // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).b += enumdirection.getAdjacentY(); // Tuinity - force inline
|
|
+ ((BaseBlockPosition)this).e += enumdirection.getAdjacentZ(); // Tuinity - force inline
|
|
+ return this;
|
|
}
|
|
|
|
public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection, int i) {
|
|
@@ -445,21 +454,19 @@ public class BlockPosition extends BaseBlockPosition {
|
|
}
|
|
}
|
|
|
|
- /* // Paper start - comment out useless overrides @Override
|
|
- @Override
|
|
+ // Tuinity start
|
|
public void o(int i) {
|
|
- super.o(i);
|
|
+ setX(i);
|
|
}
|
|
|
|
- @Override
|
|
public void p(int i) {
|
|
- super.p(i);
|
|
+ setY(i);
|
|
}
|
|
|
|
public void q(int i) {
|
|
- super.q(i);
|
|
+ setZ(i);
|
|
}
|
|
- */ // Paper end
|
|
+ // Tuinity end
|
|
|
|
@Override
|
|
public BlockPosition immutableCopy() {
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 7e8e154733..9d313fdefe 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -375,7 +375,7 @@ public class Chunk implements IChunkAccess {
|
|
Entry<HeightMap.Type, HeightMap> entry = (Entry) iterator.next();
|
|
|
|
if (ChunkStatus.FULL.h().contains(entry.getKey())) {
|
|
- this.a((HeightMap.Type) entry.getKey()).a(((HeightMap) entry.getValue()).a());
|
|
+ this.a((HeightMap.Type) entry.getKey()).copyFrom(((HeightMap) entry.getValue())); // Tuinity
|
|
}
|
|
}
|
|
|
|
@@ -510,8 +510,35 @@ public class Chunk implements IChunkAccess {
|
|
return this.setType(blockposition, iblockdata, flag, true);
|
|
}
|
|
|
|
+ // Tuinity start
|
|
+ final void setTypeAndDataRaw(BlockPosition blockposition, IBlockData iblockdata) {
|
|
+ // copied from setType
|
|
+ int i = blockposition.getX() & 15;
|
|
+ int j = blockposition.getY();
|
|
+ int k = blockposition.getZ() & 15;
|
|
+ ChunkSection chunksection = this.sections[j >> 4];
|
|
+
|
|
+ if (chunksection == Chunk.a) {
|
|
+ if (iblockdata.isAir()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ chunksection = new ChunkSection(j >> 4 << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters
|
|
+ this.sections[j >> 4] = chunksection;
|
|
+ }
|
|
+
|
|
+ chunksection.setType(i, j & 15, k, iblockdata);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
@Nullable
|
|
public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) {
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ return this.setType(blockposition, iblockdata, flag, doPlace, null);
|
|
+ }
|
|
+ @Nullable
|
|
+ public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace, TileEntity newTileEntity) {
|
|
+ // Tuinity end - add tileEntity parameter
|
|
// CraftBukkit end
|
|
int i = blockposition.getX() & 15;
|
|
int j = blockposition.getY();
|
|
@@ -570,6 +597,10 @@ public class Chunk implements IChunkAccess {
|
|
}
|
|
|
|
if (block instanceof ITileEntity) {
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ if (newTileEntity != null) {
|
|
+ this.world.setTileEntity(blockposition, newTileEntity);
|
|
+ } else { // Tuinity end - add tileEntity parameter
|
|
tileentity = this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
|
|
if (tileentity == null) {
|
|
tileentity = ((ITileEntity) block).createTile(this.world);
|
|
@@ -577,6 +608,7 @@ public class Chunk implements IChunkAccess {
|
|
} else {
|
|
tileentity.invalidateBlockCache();
|
|
}
|
|
+ } // Tuinity - add tileEntity parameter
|
|
}
|
|
|
|
this.s = true;
|
|
@@ -592,6 +624,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);
|
|
@@ -661,6 +694,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;
|
|
}
|
|
@@ -918,6 +952,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);
|
|
|
|
@@ -957,6 +992,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);
|
|
|
|
@@ -987,6 +1023,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/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
index eaed04f786..25086275f4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
|
|
@@ -31,7 +31,7 @@ public abstract class ChunkMapDistance {
|
|
private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2;
|
|
private final Long2ObjectMap<ObjectSet<EntityPlayer>> c = new Long2ObjectOpenHashMap();
|
|
public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
|
|
- private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a();
|
|
+ private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.ticketLevelTracker; } // Tuinity - OBFHELPER
|
|
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
|
|
private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
|
|
// Paper start use a queue, but still keep unique requirement
|
|
@@ -53,6 +53,47 @@ public abstract class ChunkMapDistance {
|
|
|
|
PlayerChunkMap chunkMap; // Paper
|
|
|
|
+ // 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);
|
|
@@ -65,15 +106,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.ticketLevelTracker.update(entry.getLongKey(), getLowestTicketLevel((ArraySetSorted) entry.getValue()), false);
|
|
}
|
|
|
|
@@ -98,6 +158,7 @@ public abstract class ChunkMapDistance {
|
|
protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k);
|
|
|
|
public boolean a(PlayerChunkMap playerchunkmap) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot tick ChunkMapDistance off of the main-thread");// Tuinity
|
|
//this.f.a(); // Paper - no longer used
|
|
AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
|
|
this.g.a();
|
|
@@ -176,27 +237,11 @@ public abstract class ChunkMapDistance {
|
|
boolean removed = false; // CraftBukkit
|
|
if (arraysetsorted.remove(ticket)) {
|
|
removed = true; // CraftBukkit
|
|
- // Paper start - delay chunk unloads for player tickets
|
|
- long delayChunkUnloadsBy = chunkMap.world.paperConfig.delayChunkUnloadsBy;
|
|
- if (ticket.getTicketType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) {
|
|
- boolean hasPlayer = false;
|
|
- for (Ticket<?> ticket1 : arraysetsorted) {
|
|
- if (ticket1.getTicketType() == TicketType.PLAYER) {
|
|
- hasPlayer = true;
|
|
- break;
|
|
- }
|
|
- }
|
|
- PlayerChunk playerChunk = chunkMap.getUpdatingChunk(i);
|
|
- if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) {
|
|
- Ticket<Long> delayUnload = new Ticket<Long>(TicketType.DELAY_UNLOAD, 33, i);
|
|
- delayUnload.delayUnloadBy = delayChunkUnloadsBy;
|
|
- delayUnload.setCurrentTick(this.currentTick);
|
|
- arraysetsorted.remove(delayUnload);
|
|
- // refresh ticket
|
|
- arraysetsorted.add(delayUnload);
|
|
- }
|
|
+ // Tuinity start - delay chunk unloads
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy > 0 && ticket.getTicketType() != TicketType.DELAYED_UNLOAD) {
|
|
+ this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted);
|
|
}
|
|
- // Paper end
|
|
+ // Tuinity end - delay chunk unloads
|
|
}
|
|
|
|
if (arraysetsorted.isEmpty()) {
|
|
@@ -370,6 +415,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);
|
|
});
|
|
@@ -387,6 +433,7 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
|
|
public void a(SectionPosition sectionposition, EntityPlayer entityplayer) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player add"); // Tuinity
|
|
long i = sectionposition.r().pair();
|
|
|
|
((ObjectSet) this.c.computeIfAbsent(i, (j) -> {
|
|
@@ -397,6 +444,7 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
|
|
public void b(SectionPosition sectionposition, EntityPlayer entityplayer) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player remove"); // Tuinity
|
|
long i = sectionposition.r().pair();
|
|
ObjectSet<EntityPlayer> objectset = (ObjectSet) this.c.get(i);
|
|
|
|
@@ -446,6 +494,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();) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 3a5a9aba11..6b954db2e4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -120,7 +120,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true);
|
|
}
|
|
|
|
- private long chunkFutureAwaitCounter;
|
|
+ long chunkFutureAwaitCounter; // Tuinity - private -> package private
|
|
|
|
public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer<Chunk> onLoad) {
|
|
if (Thread.currentThread() != this.serverThread) {
|
|
@@ -182,9 +182,9 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
|
|
try {
|
|
if (onLoad != null) {
|
|
- playerChunkMap.callbackExecutor.execute(() -> {
|
|
+ // Tuinity - revert incorrect use of callback executor
|
|
onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
|
|
- });
|
|
+ // Tuinity - revert incorrect use of callback executor
|
|
}
|
|
} catch (Throwable thr) {
|
|
if (thr instanceof ThreadDeath) {
|
|
@@ -209,6 +209,164 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
}
|
|
// Paper end - rewrite ticklistserver
|
|
|
|
+ // Tuinity start
|
|
+ // this will try to avoid chunk neighbours for lighting
|
|
+ public final IChunkAccess getFullStatusChunkAt(int chunkX, int chunkZ) {
|
|
+ Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
|
|
+ if (ifLoaded != null) {
|
|
+ return ifLoaded;
|
|
+ }
|
|
+
|
|
+ IChunkAccess empty = this.getChunkAt(chunkX, chunkZ, ChunkStatus.EMPTY, true);
|
|
+ if (empty != null && empty.getChunkStatus() == ChunkStatus.FULL) {
|
|
+ return empty;
|
|
+ }
|
|
+ return this.getChunkAt(chunkX, chunkZ, ChunkStatus.FULL, true);
|
|
+ }
|
|
+
|
|
+ public final IChunkAccess getFullStatusChunkAtIfLoaded(int chunkX, int chunkZ) {
|
|
+ Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
|
|
+ if (ifLoaded != null) {
|
|
+ return ifLoaded;
|
|
+ }
|
|
+
|
|
+ IChunkAccess ret = this.getChunkAtImmediately(chunkX, chunkZ);
|
|
+ if (ret != null && ret.getChunkStatus() == ChunkStatus.FULL) {
|
|
+ return ret;
|
|
+ } else {
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
|
|
+ java.util.function.Consumer<IChunkAccess> consumer) {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (PlayerChunk playerChunk) -> {
|
|
+ if (ticketLevel <= 33) {
|
|
+ return (CompletableFuture)playerChunk.getFullChunkFuture();
|
|
+ } else {
|
|
+ return playerChunk.getOrCreateFuture(PlayerChunk.getChunkStatus(ticketLevel), ChunkProviderServer.this.playerChunkMap);
|
|
+ }
|
|
+ }, consumer);
|
|
+ }
|
|
+
|
|
+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
|
|
+ java.util.function.Function<PlayerChunk, CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> function,
|
|
+ java.util.function.Consumer<IChunkAccess> consumer) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(chunkX, chunkZ);
|
|
+ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++);
|
|
+ this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ this.tickDistanceManager();
|
|
+
|
|
+ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair());
|
|
+
|
|
+ if (chunk == null) {
|
|
+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'");
|
|
+ }
|
|
+
|
|
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = function.apply(chunk);
|
|
+
|
|
+ future.whenCompleteAsync((either, throwable) -> {
|
|
+ try {
|
|
+ if (throwable != null) {
|
|
+ if (throwable instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)throwable;
|
|
+ }
|
|
+ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable);
|
|
+ } else if (either.right().isPresent()) {
|
|
+ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString());
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ if (consumer != null) {
|
|
+ consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
|
|
+ }
|
|
+ } catch (Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr);
|
|
+ return;
|
|
+ }
|
|
+ } finally {
|
|
+ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback.
|
|
+ ChunkProviderServer.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
|
|
+ ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ }
|
|
+ }, this.serverThreadQueue);
|
|
+ }
|
|
+
|
|
+ void chunkLoadAccept(int chunkX, int chunkZ, IChunkAccess chunk, java.util.function.Consumer<IChunkAccess> consumer) {
|
|
+ try {
|
|
+ consumer.accept(chunk);
|
|
+ } catch (Throwable throwable) {
|
|
+ if (throwable instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)throwable;
|
|
+ }
|
|
+ MinecraftServer.LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.world.getWorld().getName() + "' threw an exception", throwable);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer<IChunkAccess> onLoad) {
|
|
+ // try to fire sync
|
|
+ int chunkStatusTicketLevel = 33 + ChunkStatus.getTicketLevelOffset(status);
|
|
+ IChunkAccess immediate = this.getChunkAtImmediately(chunkX, chunkZ);
|
|
+ if (immediate != null) {
|
|
+ if (allowSubTicketLevel || this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ)).getTicketLevel() <= chunkStatusTicketLevel) {
|
|
+ if (immediate.getChunkStatus().isAtLeastStatus(status)) {
|
|
+ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad);
|
|
+ } else {
|
|
+ if (gen) {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
|
|
+ } else {
|
|
+ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ if (gen || immediate.getChunkStatus().isAtLeastStatus(status)) {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
|
|
+ } else {
|
|
+ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
|
|
+ }
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // need to fire async
|
|
+
|
|
+ if (gen && !allowSubTicketLevel) {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, 33 + ChunkStatus.getTicketLevelOffset(ChunkStatus.EMPTY), (IChunkAccess chunk) -> {
|
|
+ if (chunk == null) {
|
|
+ throw new IllegalStateException("Chunk cannot be null");
|
|
+ }
|
|
+
|
|
+ if (!chunk.getChunkStatus().isAtLeastStatus(status)) {
|
|
+ if (gen) {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
|
|
+ return;
|
|
+ } else {
|
|
+ ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
|
|
+ return;
|
|
+ }
|
|
+ } else {
|
|
+ if (allowSubTicketLevel) {
|
|
+ ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad);
|
|
+ return;
|
|
+ } else {
|
|
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
|
|
this.world = worldserver;
|
|
this.serverThreadQueue = new ChunkProviderServer.a(worldserver);
|
|
@@ -544,6 +702,8 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
Arrays.fill(this.cacheChunk, (Object) null);
|
|
}
|
|
|
|
+ private long syncLoadCounter; // Tuinity - prevent plugin unloads from removing our ticket
|
|
+
|
|
private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) {
|
|
// Paper start - add isUrgent - old sig left in place for dirty nms plugins
|
|
return getChunkFutureMainThread(i, j, chunkstatus, flag, false);
|
|
@@ -562,9 +722,12 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
PlayerChunk.State currentChunkState = PlayerChunk.getChunkState(playerchunk.getTicketLevel());
|
|
currentlyUnloading = (oldChunkState.isAtLeast(PlayerChunk.State.BORDER) && !currentChunkState.isAtLeast(PlayerChunk.State.BORDER));
|
|
}
|
|
+ final Long identifier; // Tuinity - prevent plugin unloads from removing our ticket
|
|
if (flag && !currentlyUnloading) {
|
|
// CraftBukkit end
|
|
this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
|
|
+ identifier = Long.valueOf(this.syncLoadCounter++); // Tuinity - prevent plugin unloads from removing our ticket
|
|
+ this.chunkMapDistance.addTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity - prevent plugin unloads from removing our ticket
|
|
if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper
|
|
if (this.a(playerchunk, l)) {
|
|
GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler();
|
|
@@ -575,12 +738,20 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
playerchunk = this.getChunk(k);
|
|
gameprofilerfiller.exit();
|
|
if (this.a(playerchunk, l)) {
|
|
+ this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity
|
|
throw (IllegalStateException) SystemUtils.c(new IllegalStateException("No chunk holder after ticket has been added"));
|
|
}
|
|
}
|
|
- }
|
|
+ } else { identifier = null; } // Tuinity - prevent plugin unloads from removing our ticket
|
|
// Paper start
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
|
|
+ // Tuinity start - prevent plugin unloads from removing our ticket
|
|
+ if (flag && !currentlyUnloading) {
|
|
+ future.thenAcceptAsync((either) -> {
|
|
+ ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier);
|
|
+ }, ChunkProviderServer.this.serverThreadQueue);
|
|
+ }
|
|
+ // Tuinity end - prevent plugin unloads from removing our ticket
|
|
if (isUrgent) {
|
|
future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair));
|
|
}
|
|
@@ -599,8 +770,8 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
return !this.a(playerchunk, k);
|
|
}
|
|
|
|
- @Override
|
|
- public IBlockAccess c(int i, int j) {
|
|
+ public final IBlockAccess getFeaturesReadyChunk(int x, int z) { return this.c(x, z); } // Tuinity - OBFHELPER
|
|
+ @Override public IBlockAccess c(int i, int j) { // Tuinity - OBFHELPER
|
|
long k = ChunkCoordIntPair.pair(i, j);
|
|
PlayerChunk playerchunk = this.getChunk(k);
|
|
|
|
@@ -637,6 +808,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
|
|
public boolean tickDistanceManager() { // Paper - private -> public
|
|
if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper
|
|
+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Tuinity - add timings for distance manager
|
|
boolean flag = this.chunkMapDistance.a(this.playerChunkMap);
|
|
boolean flag1 = this.playerChunkMap.b();
|
|
|
|
@@ -646,6 +818,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.clearCache();
|
|
return true;
|
|
}
|
|
+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Tuinity - add timings for distance manager
|
|
}
|
|
|
|
public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER
|
|
@@ -734,7 +907,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.getMethodProfiler().enter("purge");
|
|
this.world.timings.doChunkMap.startTiming(); // Spigot
|
|
this.chunkMapDistance.purgeTickets();
|
|
- this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.tickDistanceManager();
|
|
this.world.timings.doChunkMap.stopTiming(); // Spigot
|
|
this.world.getMethodProfiler().exitEnter("chunks");
|
|
@@ -744,12 +917,22 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.doChunkUnload.startTiming(); // Spigot
|
|
this.world.getMethodProfiler().exitEnter("unload");
|
|
this.playerChunkMap.unloadChunks(booleansupplier);
|
|
- this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.world.timings.doChunkUnload.stopTiming(); // Spigot
|
|
this.world.getMethodProfiler().exit();
|
|
this.clearCache();
|
|
}
|
|
|
|
+ // Tuinity start - optimise chunk tick iteration
|
|
+ // We need this here because since we remove the COW op for chunk map, we also remove
|
|
+ // the iterator safety of the visible map - meaning the only way for us to still
|
|
+ // iterate is to use a copy. Not acceptable at all, so here we hack in an iterable safe
|
|
+ // chunk map that will give the same behaviour as previous - without COW.
|
|
+ final com.destroystokyo.paper.util.maplist.ChunkList entityTickingChunks = new com.destroystokyo.paper.util.maplist.ChunkList();
|
|
+ boolean isTickingChunks;
|
|
+ final it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap<Chunk> pendingEntityTickingChunkChanges = new it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap<>(16, 0.8f);
|
|
+ // Tuinity end - optimise chunk tick iteration
|
|
+
|
|
private void tickChunks() {
|
|
long i = this.world.getTime();
|
|
long j = i - this.lastTickTime;
|
|
@@ -821,19 +1004,21 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
//List<PlayerChunk> list = Lists.newArrayList(this.playerChunkMap.f()); // Paper
|
|
//Collections.shuffle(list); // Paper
|
|
// Paper - moved up
|
|
- final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
|
|
- Optional<Chunk> optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
|
|
-
|
|
- if (optional.isPresent()) {
|
|
+ // Tuinity start - optimise chunk tick iteration
|
|
+ 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 - optimise chunk tick iteration
|
|
this.world.getMethodProfiler().enter("broadcast");
|
|
this.world.timings.broadcastChunkUpdates.startTiming(); // Paper - timings
|
|
- playerchunk.a((Chunk) optional.get());
|
|
+ playerchunk.a(chunk); // Tuinity
|
|
this.world.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings
|
|
this.world.getMethodProfiler().exit();
|
|
- Optional<Chunk> optional1 = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
|
|
+ // Tuinity
|
|
|
|
- if (optional1.isPresent()) {
|
|
- Chunk chunk = (Chunk) optional1.get();
|
|
+ if (true) { // Tuinity
|
|
+ // Tuinity
|
|
ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
|
|
|
|
if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange
|
|
@@ -845,11 +1030,27 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.chunkTicks.startTiming(); // Spigot // Paper
|
|
this.world.a(chunk, k);
|
|
this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper
|
|
- if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick
|
|
}
|
|
}
|
|
}
|
|
- });
|
|
+ } // Tuinity start - optimise chunk tick iteration
|
|
+ this.isTickingChunks = false;
|
|
+ if (!this.pendingEntityTickingChunkChanges.isEmpty()) {
|
|
+ // iterate backwards: fastutil maps have better remove times when iterating backwards
|
|
+ // (this is due to the fact that we likely wont shift entries on remove calls)
|
|
+ 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 - optimise chunk tick iteration
|
|
this.world.getMethodProfiler().enter("customSpawners");
|
|
if (flag1) {
|
|
try (co.aikar.timings.Timing ignored = this.world.timings.miscMobSpawning.startTiming()) { // Paper - timings
|
|
@@ -861,7 +1062,25 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.getMethodProfiler().exit();
|
|
}
|
|
|
|
+ // Tuinity start - controlled flush for entity tracker packets
|
|
+ List<NetworkManager> disabledFlushes = new java.util.ArrayList<>(this.world.getPlayers().size());
|
|
+ for (EntityPlayer player : this.world.getPlayers()) {
|
|
+ PlayerConnection connection = player.playerConnection;
|
|
+ if (connection != null) {
|
|
+ connection.networkManager.disableAutomaticFlush();
|
|
+ disabledFlushes.add(connection.networkManager);
|
|
+ }
|
|
+ }
|
|
+ try {
|
|
+ // Tuinity end - controlled flush for entity tracker packets
|
|
this.playerChunkMap.g();
|
|
+ // Tuinity start - controlled flush for entity tracker packets
|
|
+ } finally {
|
|
+ for (NetworkManager networkManager : disabledFlushes) {
|
|
+ networkManager.enableAutomaticFlush();
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - controlled flush for entity tracker packets
|
|
}
|
|
|
|
private void a(long i, Consumer<Chunk> consumer) {
|
|
@@ -1001,44 +1220,11 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
ChunkProviderServer.this.world.getMethodProfiler().c("runTask");
|
|
super.executeTask(runnable);
|
|
}
|
|
-
|
|
- // Paper start
|
|
- private long lastMidTickChunkTask = 0;
|
|
- public boolean pollChunkLoadTasks() {
|
|
- if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) {
|
|
- try {
|
|
- ChunkProviderServer.this.tickDistanceManager();
|
|
- } finally {
|
|
- // from below: process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
- playerChunkMap.callbackExecutor.run();
|
|
- }
|
|
- return true;
|
|
- }
|
|
- return false;
|
|
- }
|
|
- public void midTickLoadChunks() {
|
|
- MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer();
|
|
- // always try to load chunks, restrain generation/other updates only. don't count these towards tick count
|
|
- //noinspection StatementWithEmptyBody
|
|
- while (pollChunkLoadTasks()) {}
|
|
-
|
|
- if (System.nanoTime() - lastMidTickChunkTask < 200000) {
|
|
- return;
|
|
- }
|
|
-
|
|
- for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) {
|
|
- if (this.executeNext()) {
|
|
- server.midTickChunksTasksRan++;
|
|
- lastMidTickChunkTask = System.nanoTime();
|
|
- } else {
|
|
- break;
|
|
- }
|
|
- }
|
|
- }
|
|
- // Paper end
|
|
+ // Tuinity - replace logic
|
|
|
|
@Override
|
|
protected boolean executeNext() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot execute chunk tasks off-main thread");// Tuinity
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
try {
|
|
boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
index ac58fcb798..742c59cb05 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
|
|
@@ -24,6 +24,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) {
|
|
@@ -374,10 +382,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/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index 3588fe1001..cf54336b86 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -96,6 +96,7 @@ public class ChunkSection {
|
|
return iblockdata1;
|
|
}
|
|
|
|
+ public final boolean isFullOfAir() { return this.c(); } // Tuinity - OBFHELPER
|
|
public boolean c() {
|
|
return this.nonEmptyBlockCount == 0;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java
|
|
index 7948b915bf..7e32a2eab9 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkStatus.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkStatus.java
|
|
@@ -109,7 +109,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);
|
|
@@ -171,7 +171,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/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
|
|
index e65fe633f5..ed817893e6 100644
|
|
--- a/src/main/java/net/minecraft/server/DataBits.java
|
|
+++ b/src/main/java/net/minecraft/server/DataBits.java
|
|
@@ -52,6 +52,7 @@ public class DataBits {
|
|
return (int) ((long) i * j + k >> 32 >> this.i);
|
|
}
|
|
|
|
+ public final int getAndSet(final int index, final int value) { return this.a(index, value); } // Tuinity - OBFHELPER
|
|
public int a(int i, int j) {
|
|
//Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper
|
|
//Validate.inclusiveBetween(0L, this.d, (long) j); // Paper
|
|
@@ -64,6 +65,7 @@ public class DataBits {
|
|
return j1;
|
|
}
|
|
|
|
+ public final void set(final int index, final int value) { this.b(index, value); } // Tuinity - OBFHELPER
|
|
public void b(int i, int j) {
|
|
//Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper
|
|
//Validate.inclusiveBetween(0L, this.d, (long) j); // Paper
|
|
@@ -74,6 +76,7 @@ public class DataBits {
|
|
this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1;
|
|
}
|
|
|
|
+ public final int get(final int index) { return this.a(index); } // Tuinity - OBFHELPER
|
|
public int a(int i) {
|
|
//Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper
|
|
int j = this.b(i);
|
|
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
index 95ef962868..73163b417a 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
@@ -163,6 +163,7 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
return this.a(j << 8 | k << 4 | i); // Paper - inline
|
|
}
|
|
|
|
+ public final T rawGet(int index) { return this.a(index); } // Tuinity - OBFHELPER
|
|
protected T a(int i) {
|
|
T t0 = this.h.a(this.a.a(i));
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
index 8b2755a3b9..578f7809cb 100644
|
|
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
@@ -170,6 +170,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((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config
|
|
|
|
this.setPVP(dedicatedserverproperties.pvp);
|
|
this.setAllowFlight(dedicatedserverproperties.allowFlight);
|
|
diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java
|
|
index 550232cb38..229c3b0f0c 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 5a8bcd91dc..db62601ea7 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -136,7 +136,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
public double D;
|
|
public double E;
|
|
public double F;
|
|
- public float G;
|
|
+ public float G; public final float getStepHeight() { return this.G; } // Tuinity - OBFHELPER
|
|
public boolean noclip;
|
|
public float I;
|
|
protected final Random random;
|
|
@@ -212,6 +212,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
// CraftBukkit end
|
|
|
|
+ // Tuinity start
|
|
+ public final AxisAlignedBB getBoundingBoxAt(double x, double y, double z) {
|
|
+ double widthHalf = (double)this.size.width / 2.0;
|
|
+ double height = (double)this.size.height;
|
|
+ return new AxisAlignedBB(x - widthHalf, y, z - widthHalf, x + widthHalf, y + height, z + widthHalf);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
// Paper start
|
|
/**
|
|
* Overriding this field will cause memory leaks.
|
|
@@ -625,7 +633,40 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
return this.onGround;
|
|
}
|
|
|
|
+ // Tuinity start - detailed watchdog information
|
|
+ private Vec3D moveVector;
|
|
+ private double moveStartX;
|
|
+ private double moveStartY;
|
|
+ private double moveStartZ;
|
|
+
|
|
+ public final Vec3D getMoveVector() {
|
|
+ return this.moveVector;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartX() {
|
|
+ return this.moveStartX;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartY() {
|
|
+ return this.moveStartY;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartZ() {
|
|
+ return this.moveStartZ;
|
|
+ }
|
|
+ // Tuinity end - detailed watchdog information
|
|
+
|
|
public void move(EnumMoveType enummovetype, Vec3D vec3d) {
|
|
+ // Tuinity start - detailed watchdog information
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot move an entity off-main");
|
|
+ synchronized (this.posLock) {
|
|
+ this.moveStartX = this.locX();
|
|
+ this.moveStartY = this.locY();
|
|
+ this.moveStartZ = this.locZ();
|
|
+ this.moveVector = vec3d;
|
|
+ }
|
|
+ try {
|
|
+ // Tuinity end - detailed watchdog information
|
|
if (this.noclip) {
|
|
this.a(this.getBoundingBox().c(vec3d));
|
|
this.recalcPosition();
|
|
@@ -653,7 +694,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
// Paper end
|
|
|
|
vec3d = this.a(vec3d, enummovetype);
|
|
- Vec3D vec3d1 = this.f(vec3d);
|
|
+ Vec3D vec3d1 = this.performCollision(vec3d); // Tuinity - optimise collisions
|
|
|
|
if (vec3d1.g() > 1.0E-7D) {
|
|
this.a(this.getBoundingBox().c(vec3d1));
|
|
@@ -770,6 +811,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
|
|
this.world.getMethodProfiler().exit();
|
|
}
|
|
+ // Tuinity start - detailed watchdog information
|
|
+ } finally {
|
|
+ synchronized (this.posLock) { // Tuinity
|
|
+ this.moveVector = null;
|
|
+ } // Tuinity
|
|
+ }
|
|
+ // Tuinity end - detailed watchdog information
|
|
}
|
|
|
|
protected BlockPosition ak() {
|
|
@@ -850,6 +898,132 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
return d0;
|
|
}
|
|
|
|
+ // Tuinity start - optimise entity movement
|
|
+ private static double performCollisionsX(AxisAlignedBB currentBoundingBox, double value, List<AxisAlignedBB> potentialCollisions) {
|
|
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
|
|
+ if (Math.abs(value) < 1.0E-7) {
|
|
+ return 0.0;
|
|
+ }
|
|
+ AxisAlignedBB target = potentialCollisions.get(i);
|
|
+ value = target.collideX(currentBoundingBox, value);
|
|
+ }
|
|
+
|
|
+ return value;
|
|
+ }
|
|
+
|
|
+ private static double performCollisionsY(AxisAlignedBB currentBoundingBox, double value, List<AxisAlignedBB> potentialCollisions) {
|
|
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
|
|
+ if (Math.abs(value) < 1.0E-7) {
|
|
+ return 0.0;
|
|
+ }
|
|
+ AxisAlignedBB target = potentialCollisions.get(i);
|
|
+ value = target.collideY(currentBoundingBox, value);
|
|
+ }
|
|
+
|
|
+ return value;
|
|
+ }
|
|
+
|
|
+ private static double performCollisionsZ(AxisAlignedBB currentBoundingBox, double value, List<AxisAlignedBB> potentialCollisions) {
|
|
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
|
|
+ if (Math.abs(value) < 1.0E-7) {
|
|
+ return 0.0;
|
|
+ }
|
|
+ AxisAlignedBB target = potentialCollisions.get(i);
|
|
+ value = target.collideZ(currentBoundingBox, value);
|
|
+ }
|
|
+
|
|
+ return value;
|
|
+ }
|
|
+
|
|
+ private static Vec3D performCollisions(Vec3D moveVector, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> potentialCollisions) {
|
|
+ double x = moveVector.x;
|
|
+ double y = moveVector.y;
|
|
+ double z = moveVector.z;
|
|
+
|
|
+ if (y != 0.0) {
|
|
+ y = Entity.performCollisionsY(axisalignedbb, y, potentialCollisions);
|
|
+ if (y != 0.0) {
|
|
+ axisalignedbb = axisalignedbb.offsetY(y);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ boolean xSmaller = Math.abs(x) < Math.abs(z);
|
|
+
|
|
+ if (xSmaller && z != 0.0) {
|
|
+ z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions);
|
|
+ if (z != 0.0) {
|
|
+ axisalignedbb = axisalignedbb.offsetZ(z);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (x != 0.0) {
|
|
+ x = Entity.performCollisionsX(axisalignedbb, x, potentialCollisions);
|
|
+ if (!xSmaller && x != 0.0) {
|
|
+ axisalignedbb = axisalignedbb.offsetX(x);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!xSmaller && z != 0.0) {
|
|
+ z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions);
|
|
+ }
|
|
+
|
|
+ return new Vec3D(x, y, z);
|
|
+ }
|
|
+
|
|
+ Vec3D performCollision(Vec3D moveVector) {
|
|
+ if (moveVector.getX() == 0.0 && moveVector.getY() == 0.0 && moveVector.getZ() == 0.0) {
|
|
+ return moveVector;
|
|
+ }
|
|
+
|
|
+ WorldServer world = ((WorldServer)this.world);
|
|
+ AxisAlignedBB currBoundingBox = this.getBoundingBox();
|
|
+
|
|
+ List<AxisAlignedBB> potentialCollisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList();
|
|
+ try {
|
|
+ AxisAlignedBB collisionBox;
|
|
+ double stepHeight = (double)this.getStepHeight();
|
|
+ if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) {
|
|
+ // don't bother getting the collisions if we don't need them.
|
|
+ if (moveVector.y <= 0.0) {
|
|
+ collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z).expandUpwards(stepHeight);
|
|
+ } else {
|
|
+ collisionBox = currBoundingBox.expand(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z);
|
|
+ }
|
|
+ } else {
|
|
+ collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z);
|
|
+ }
|
|
+ world.getCollisions(this, collisionBox, potentialCollisions, false);
|
|
+
|
|
+ Vec3D limitedMoveVector = Entity.performCollisions(moveVector, currBoundingBox, potentialCollisions);
|
|
+
|
|
+ if (stepHeight > 0.0
|
|
+ && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0))
|
|
+ && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) {
|
|
+ Vec3D vec3d2 = Entity.performCollisions(new Vec3D(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions);
|
|
+ Vec3D vec3d3 = Entity.performCollisions(new Vec3D(0.0, stepHeight, 0.0), currBoundingBox.expand(moveVector.x, 0.0, moveVector.z), potentialCollisions);
|
|
+
|
|
+ if (vec3d3.y < stepHeight) {
|
|
+ Vec3D vec3d4 = Entity.performCollisions(new Vec3D(moveVector.x, 0.0D, moveVector.z), currBoundingBox.offset(vec3d3), potentialCollisions);
|
|
+
|
|
+ if (Entity.getXZSquared(vec3d4) > Entity.getXZSquared(vec3d2)) {
|
|
+ vec3d2 = vec3d4;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (Entity.getXZSquared(vec3d2) > Entity.getXZSquared(limitedMoveVector)) {
|
|
+ return vec3d2.add(Entity.performCollisions(new Vec3D(0.0D, -vec3d2.y + moveVector.y, 0.0D), currBoundingBox.offset(vec3d2), potentialCollisions));
|
|
+ }
|
|
+
|
|
+ return limitedMoveVector;
|
|
+ } else {
|
|
+ return limitedMoveVector;
|
|
+ }
|
|
+ } finally {
|
|
+ com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(potentialCollisions);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise entity movement
|
|
+
|
|
private Vec3D f(Vec3D vec3d) {
|
|
AxisAlignedBB axisalignedbb = this.getBoundingBox();
|
|
VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this);
|
|
@@ -885,6 +1059,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
return vec3d1;
|
|
}
|
|
|
|
+ public static double getXZSquared(Vec3D vec3d) { return Entity.b(vec3d); } // Tuinity - OBFHELPER
|
|
public static double b(Vec3D vec3d) {
|
|
return vec3d.x * vec3d.x + vec3d.z * vec3d.z;
|
|
}
|
|
@@ -1091,6 +1266,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
|
|
|
|
@Nullable public final AxisAlignedBB getCollisionBox(){return ay();} //Paper - OBFHELPER
|
|
+ @Nullable public final AxisAlignedBB getHardCollisionBox() { return this.ay(); } // Tuinity - OBFHELPER
|
|
@Nullable public AxisAlignedBB ay() {
|
|
return null;
|
|
}
|
|
@@ -1974,8 +2150,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
|
|
public final AxisAlignedBB getHardCollisionBox(Entity entity){ return j(entity);}//Paper - OBFHELPER
|
|
- @Nullable
|
|
- public AxisAlignedBB j(Entity entity) {
|
|
+ @Nullable public AxisAlignedBB getHardCollisionBoxWith(Entity entity) { return this.j(entity); } // Tuinity - OBFHELPER
|
|
+ @Nullable public AxisAlignedBB j(Entity entity) { // Tuinity - OBFHELPER
|
|
return null;
|
|
}
|
|
|
|
@@ -3299,12 +3475,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
return this.locBlock;
|
|
}
|
|
|
|
+ public final Object posLock = new Object(); // Tuinity - log detailed entity tick information
|
|
+
|
|
public Vec3D getMot() {
|
|
return this.mot;
|
|
}
|
|
|
|
public void setMot(Vec3D vec3d) {
|
|
+ synchronized (this.posLock) { // Tuinity
|
|
this.mot = vec3d;
|
|
+ } // Tuinity
|
|
}
|
|
|
|
public void setMot(double d0, double d1, double d2) {
|
|
@@ -3361,7 +3541,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
// Paper end
|
|
if (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2) {
|
|
+ synchronized (this.posLock) { // Tuinity
|
|
this.loc = new Vec3D(d0, d1, d2);
|
|
+ } // Tuinity
|
|
int i = MathHelper.floor(d0);
|
|
int j = MathHelper.floor(d1);
|
|
int k = MathHelper.floor(d2);
|
|
diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
index 1a61bc7c8a..b114346837 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityLiving.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
@@ -2832,7 +2832,11 @@ public abstract class EntityLiving extends Entity {
|
|
return;
|
|
}
|
|
// Paper - end don't run getEntities if we're not going to use its result
|
|
- List<Entity> list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this));
|
|
+ // Tuinity start - reduce memory allocation from collideNearby
|
|
+ List<Entity> list = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
|
|
+ this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this), list);
|
|
+ try {
|
|
+ // Tuinity end - reduce memory allocation from collideNearby
|
|
|
|
if (!list.isEmpty()) {
|
|
// Paper - move up
|
|
@@ -2861,6 +2865,9 @@ public abstract class EntityLiving extends Entity {
|
|
this.C(entity);
|
|
}
|
|
}
|
|
+ } finally { // Tuinity start - reduce memory allocation from collideNearby
|
|
+ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(list);
|
|
+ } // Tuinity end - reduce memory allocation from collideNearby
|
|
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
|
|
index f75c09d44a..bfb931268d 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
|
|
@@ -74,6 +74,7 @@ public class EntityTrackerEntry {
|
|
|
|
public final void tick() { this.a(); } // Paper - OBFHELPER
|
|
public void a() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
|
|
List<Entity> list = this.tracker.getPassengers();
|
|
|
|
if (!list.equals(this.p)) {
|
|
diff --git a/src/main/java/net/minecraft/server/Fluid.java b/src/main/java/net/minecraft/server/Fluid.java
|
|
index 05fa52c0b1..8ffc5db509 100644
|
|
--- a/src/main/java/net/minecraft/server/Fluid.java
|
|
+++ b/src/main/java/net/minecraft/server/Fluid.java
|
|
@@ -9,8 +9,12 @@ public final class Fluid extends IBlockDataHolder<FluidType, Fluid> {
|
|
|
|
public static final Codec<Fluid> a = a((Codec) IRegistry.FLUID, FluidType::h).stable();
|
|
|
|
+ // Tuinity start
|
|
+ protected final boolean isEmpty;
|
|
+ // Tuinity end
|
|
public Fluid(FluidType fluidtype, ImmutableMap<IBlockState<?>, Comparable<?>> immutablemap, MapCodec<Fluid> mapcodec) {
|
|
super(fluidtype, immutablemap, mapcodec);
|
|
+ this.isEmpty = fluidtype.b(); // Tuinity - moved from isEmpty()
|
|
}
|
|
|
|
public FluidType getType() {
|
|
@@ -22,7 +26,7 @@ public final class Fluid extends IBlockDataHolder<FluidType, Fluid> {
|
|
}
|
|
|
|
public boolean isEmpty() {
|
|
- return this.getType().b();
|
|
+ return this.isEmpty; // Tuinity - moved into constructor
|
|
}
|
|
|
|
public float getHeight(IBlockAccess iblockaccess, BlockPosition blockposition) {
|
|
diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java
|
|
index 068b92c5c4..a43c4ca3ea 100644
|
|
--- a/src/main/java/net/minecraft/server/HeightMap.java
|
|
+++ b/src/main/java/net/minecraft/server/HeightMap.java
|
|
@@ -19,7 +19,25 @@ public class HeightMap {
|
|
private static final Predicate<IBlockData> b = (iblockdata) -> {
|
|
return iblockdata.getMaterial().isSolid();
|
|
};
|
|
- private final DataBits c = new DataBits(9, 256);
|
|
+ // Tuinity start
|
|
+ private final char[] heightmap = new char[16 * 16]; // Tuinity - replace with faster access
|
|
+ public DataBits toDataBits() {
|
|
+ final DataBits ret = new DataBits(9, 256);
|
|
+
|
|
+ for (int i = 0, len = this.heightmap.length; i < len; ++i) {
|
|
+ ret.set(i, this.heightmap[i]);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public void copyFrom(HeightMap other) {
|
|
+ if (other.heightmap.length != this.heightmap.length) {
|
|
+ throw new IllegalStateException("Heightmap lengths must match");
|
|
+ }
|
|
+ System.arraycopy(other.heightmap, 0, this.heightmap, 0, this.heightmap.length);
|
|
+ }
|
|
+ // Tuinity end
|
|
private final Predicate<IBlockData> d;
|
|
private final IChunkAccess e;
|
|
|
|
@@ -101,24 +119,30 @@ 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));
|
|
}
|
|
|
|
private int a(int i) {
|
|
- return this.c.a(i);
|
|
+ return this.heightmap[i]; // Tuinity
|
|
}
|
|
|
|
private void a(int i, int j, int k) {
|
|
- this.c.b(c(i, j), k);
|
|
+ this.heightmap[c(i, j)] = (char)k; // Tuinity
|
|
}
|
|
|
|
public void a(long[] along) {
|
|
- System.arraycopy(along, 0, this.c.a(), 0, along.length);
|
|
+ // Tuinity start
|
|
+ final DataBits databits = new DataBits(9, 256, along);
|
|
+ for (int i = 0, len = this.heightmap.length; i < len; ++i) {
|
|
+ this.heightmap[i] = (char)databits.get(i);
|
|
+ }
|
|
+ // Tuinity end
|
|
}
|
|
|
|
public long[] a() {
|
|
- return this.c.a();
|
|
+ return this.toDataBits().a(); // Tuinity
|
|
}
|
|
|
|
private static int c(int i, int j) {
|
|
@@ -137,7 +161,7 @@ public class HeightMap {
|
|
private final String h;
|
|
private final HeightMap.Use i;
|
|
private final Predicate<IBlockData> j;
|
|
- private static final Map<String, HeightMap.Type> k = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> {
|
|
+ private static final Map<String, HeightMap.Type> k = (Map) SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix
|
|
HeightMap.Type[] aheightmap_type = values();
|
|
int i = aheightmap_type.length;
|
|
|
|
@@ -149,7 +173,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.h = s;
|
|
this.i = heightmap_use;
|
|
this.j = predicate;
|
|
diff --git a/src/main/java/net/minecraft/server/IBlockData.java b/src/main/java/net/minecraft/server/IBlockData.java
|
|
index 10a5901db1..9117504767 100644
|
|
--- a/src/main/java/net/minecraft/server/IBlockData.java
|
|
+++ b/src/main/java/net/minecraft/server/IBlockData.java
|
|
@@ -8,6 +8,19 @@ public class IBlockData extends BlockBase.BlockData {
|
|
|
|
public static final Codec<IBlockData> b = a((Codec) IRegistry.BLOCK, Block::getBlockData).stable();
|
|
|
|
+
|
|
+ // Tuinity start - optimise getType calls
|
|
+ org.bukkit.Material cachedMaterial;
|
|
+
|
|
+ public final org.bukkit.Material getBukkitMaterial() {
|
|
+ if (this.cachedMaterial == null) {
|
|
+ this.cachedMaterial = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getBlock());
|
|
+ }
|
|
+
|
|
+ return this.cachedMaterial;
|
|
+ }
|
|
+ // Tuinity end - optimise getType calls
|
|
+
|
|
public IBlockData(Block block, ImmutableMap<IBlockState<?>, Comparable<?>> immutablemap, MapCodec<IBlockData> mapcodec) {
|
|
super(block, immutablemap, mapcodec);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
index 582a5695ba..5601088cd5 100644
|
|
--- a/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
@@ -21,7 +21,7 @@ public class IChunkLoader implements AutoCloseable {
|
|
protected final RegionFileCache regionFileCache;
|
|
|
|
public IChunkLoader(File file, DataFixer datafixer, boolean flag) {
|
|
- this.regionFileCache = new RegionFileCache(file, flag); // Paper - nuke IOWorker
|
|
+ this.regionFileCache = new RegionFileCache(file, flag, true); // Paper - nuke IOWorker // Tuinity
|
|
this.b = datafixer;
|
|
// Paper - nuke IOWorker
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java
|
|
index 1cc40b1f0a..3ce2f7497a 100644
|
|
--- a/src/main/java/net/minecraft/server/ICollisionAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/ICollisionAccess.java
|
|
@@ -46,6 +46,11 @@ public interface ICollisionAccess extends IBlockAccess {
|
|
}
|
|
|
|
default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
|
|
+ // Tuinity start - allow overriding in WorldServer
|
|
+ return this.getCubes(entity, axisalignedbb, predicate);
|
|
+ }
|
|
+ default boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
|
|
+ // Tuinity end - allow overriding in WorldServer
|
|
try { if (entity != null) entity.collisionLoadChunks = true; // Paper
|
|
return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty);
|
|
} finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
|
|
diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
|
|
index 267a6baae8..0edcb775e9 100644
|
|
--- a/src/main/java/net/minecraft/server/IEntityAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
|
|
@@ -69,6 +69,7 @@ public interface IEntityAccess {
|
|
AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
|
|
|
|
// Paper start
|
|
+ if (predicate == null) predicate = (e) -> true; // Tuinity - allow nullable
|
|
Predicate<Entity> effectivePredicate = predicate.and((entity1) -> {
|
|
return entity == null || !entity.isSameVehicle(entity1);
|
|
});
|
|
diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java
|
|
index b98e60772b..e0bbfe1422 100644
|
|
--- a/src/main/java/net/minecraft/server/LightEngineStorage.java
|
|
+++ b/src/main/java/net/minecraft/server/LightEngineStorage.java
|
|
@@ -23,7 +23,8 @@ public abstract class LightEngineStorage<M extends LightEngineStorageArray<M>> e
|
|
protected final M f; protected final M updating; // Paper - diff on change, should be "updating"
|
|
protected final LongSet g = new LongOpenHashSet();
|
|
protected final LongSet h = new LongOpenHashSet(); LongSet dirty = h; // Paper - OBFHELPER
|
|
- protected final Long2ObjectMap<NibbleArray> i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap());
|
|
+ protected final Long2ObjectOpenHashMap<NibbleArray> i_synchronized_map_real = new Long2ObjectOpenHashMap<>(); // Tuinity - store wrapped map, we need fastIterator
|
|
+ protected final Long2ObjectMap<NibbleArray> i = Long2ObjectMaps.synchronize(this.i_synchronized_map_real); // Tuinity - store wrapped map, we need fastIterator
|
|
private final LongSet n = new LongOpenHashSet();
|
|
private final LongSet o = new LongOpenHashSet();
|
|
private final LongSet p = new LongOpenHashSet();
|
|
@@ -247,7 +248,7 @@ public abstract class LightEngineStorage<M extends LightEngineStorageArray<M>> e
|
|
|
|
this.p.clear();
|
|
this.j = false;
|
|
- ObjectIterator objectiterator = this.i.long2ObjectEntrySet().iterator();
|
|
+ ObjectIterator objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation
|
|
|
|
Entry entry;
|
|
long j;
|
|
@@ -284,7 +285,7 @@ public abstract class LightEngineStorage<M extends LightEngineStorageArray<M>> e
|
|
}
|
|
|
|
this.n.clear();
|
|
- objectiterator = this.i.long2ObjectEntrySet().iterator();
|
|
+ objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation;
|
|
|
|
while (objectiterator.hasNext()) {
|
|
entry = (Entry) objectiterator.next();
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
index 87810f007a..8238306d9b 100644
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -48,6 +48,20 @@ public final class MCUtil {
|
|
new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build()
|
|
);
|
|
|
|
+ // Tuinity start
|
|
+ private static org.bukkit.entity.HumanEntity[] EMPTY_HUMAN_ARRAY = new org.bukkit.entity.HumanEntity[0];
|
|
+ public static void closeInventory(IInventory inventory, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
|
|
+ List<org.bukkit.entity.HumanEntity> viewers = inventory.getViewers();
|
|
+ if (viewers.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (org.bukkit.entity.HumanEntity viewer : viewers.toArray(EMPTY_HUMAN_ARRAY)) {
|
|
+ viewer.closeInventory(reason);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 3130c68e73..d6b1cb8c48 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -987,7 +987,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper end
|
|
tickSection = curTime;
|
|
}
|
|
- midTickChunksTasksRan = 0; // Paper
|
|
+ // Tuinity - replace logic
|
|
// Spigot end
|
|
|
|
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
|
|
@@ -1080,6 +1080,76 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start - execute chunk tasks mid tick
|
|
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
|
|
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
|
|
+
|
|
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
|
|
+
|
|
+ private static long lastMidTickExecute;
|
|
+ private static long lastMidTickExecuteFailure;
|
|
+
|
|
+ private boolean tickMidTickTasks() {
|
|
+ // give all worlds a fair chance at by targetting them all.
|
|
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
|
|
+ boolean executed = false;
|
|
+ for (WorldServer world : this.getWorlds()) {
|
|
+ long currTime = System.nanoTime();
|
|
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
|
+ continue;
|
|
+ }
|
|
+ if (!world.getChunkProvider().runTasks()) {
|
|
+ // we need to back off if this fails
|
|
+ world.lastMidTickExecuteFailure = currTime;
|
|
+ } else {
|
|
+ executed = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return executed;
|
|
+ }
|
|
+
|
|
+ public final void executeMidTickTasks() {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
|
|
+ long startTime = System.nanoTime();
|
|
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
|
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
|
|
+ // so, backoff to prevent this
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
|
|
+ try {
|
|
+ for (;;) {
|
|
+ boolean moreTasks = this.tickMidTickTasks();
|
|
+ long currTime = System.nanoTime();
|
|
+ long diff = currTime - startTime;
|
|
+
|
|
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
|
|
+ if (!moreTasks) {
|
|
+ lastMidTickExecuteFailure = currTime;
|
|
+ }
|
|
+
|
|
+ // note: negative values reduce the time
|
|
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
|
|
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
|
|
+ // make sure something like a GC or dumb plugin doesn't screw us over...
|
|
+ overuse = 10L * 1000L * 1000L; // 10ms
|
|
+ }
|
|
+
|
|
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
|
|
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
|
|
+
|
|
+ lastMidTickExecute = currTime + extraSleep;
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - execute chunk tasks mid tick
|
|
+
|
|
private void executeModerately() {
|
|
this.executeAll();
|
|
java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
|
@@ -1093,22 +1163,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
});
|
|
}
|
|
|
|
- // Paper start
|
|
- public int midTickChunksTasksRan = 0;
|
|
- private long midTickLastRan = 0;
|
|
- public void midTickLoadChunks() {
|
|
- if (!isMainThread() || System.nanoTime() - midTickLastRan < 1000000) {
|
|
- // only check once per 0.25ms incase this code is called in a hot method
|
|
- return;
|
|
- }
|
|
- try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
|
|
- for (WorldServer value : this.getWorlds()) {
|
|
- value.getChunkProvider().serverThreadQueue.midTickLoadChunks();
|
|
- }
|
|
- midTickLastRan = System.nanoTime();
|
|
- }
|
|
- }
|
|
- // Paper end
|
|
+ // Tuinity - replace logic
|
|
|
|
@Override
|
|
protected TickTask postToMainThread(Runnable runnable) {
|
|
@@ -1135,6 +1190,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
|
|
private boolean aZ() {
|
|
if (super.executeNext()) {
|
|
+ this.executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
|
|
return true;
|
|
} else {
|
|
if (this.canSleepForTick()) {
|
|
@@ -1202,7 +1258,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper start - move oversleep into full server tick
|
|
isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
|
|
this.awaitTasks(() -> {
|
|
- midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick
|
|
+ // Tuinity - replace logic
|
|
return !this.canOversleep();
|
|
});
|
|
isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
|
|
@@ -1261,6 +1317,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
// Paper end
|
|
|
|
+ com.tuinity.tuinity.util.CachedLists.reset(); // Tuinity
|
|
+
|
|
// Paper start
|
|
long endTime = System.nanoTime();
|
|
long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
|
|
@@ -1287,16 +1345,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
|
|
protected void b(BooleanSupplier booleansupplier) {
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
|
|
this.server.getScheduler().mainThreadHeartbeat(this.ticks); // CraftBukkit
|
|
MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.methodProfiler.enter("commandFunctions");
|
|
MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
|
|
this.getFunctionData().tick();
|
|
MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.methodProfiler.exitEnter("levels");
|
|
Iterator iterator = this.getWorlds().iterator();
|
|
|
|
@@ -1307,7 +1365,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
processQueue.remove().run();
|
|
}
|
|
MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
|
|
// Send time updates to everyone, it will get the right time from the world the player is in.
|
|
// Paper start - optimize time updates
|
|
@@ -1349,11 +1407,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
this.methodProfiler.enter("tick");
|
|
|
|
try {
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
worldserver.timings.doTick.startTiming(); // Spigot
|
|
worldserver.doTick(booleansupplier);
|
|
+ worldserver.getChunkProvider().playerChunkMap.dataRegionManager.recalculateRegions(); // Tuinity
|
|
worldserver.timings.doTick.stopTiming(); // Spigot
|
|
- midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
} catch (Throwable throwable) {
|
|
// Spigot Start
|
|
CrashReport crashreport;
|
|
@@ -1447,7 +1506,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
|
|
public String getServerModName() {
|
|
- return "Paper"; //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
|
|
+ return "Tuinity"; // Tuinity //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
|
|
}
|
|
|
|
public CrashReport b(CrashReport crashreport) {
|
|
diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java
|
|
index e6dc5b9ce1..345e3abdd9 100644
|
|
--- a/src/main/java/net/minecraft/server/NavigationAbstract.java
|
|
+++ b/src/main/java/net/minecraft/server/NavigationAbstract.java
|
|
@@ -21,7 +21,7 @@ public abstract class NavigationAbstract {
|
|
protected long j;
|
|
protected double k;
|
|
protected float l;
|
|
- protected boolean m;
|
|
+ protected boolean m; protected final boolean needsPathRecalculation() { return this.m; } // Tuinity - OBFHELPER
|
|
protected long n;
|
|
protected PathfinderAbstract o;
|
|
private BlockPosition p;
|
|
@@ -29,6 +29,13 @@ public abstract class NavigationAbstract {
|
|
private float r;
|
|
private final Pathfinder s; public Pathfinder getPathfinder() { return this.s; } // Paper - OBFHELPER
|
|
|
|
+ // Tuinity start
|
|
+ public boolean isViableForPathRecalculationChecking() {
|
|
+ return !this.needsPathRecalculation() &&
|
|
+ (this.c != null && !this.c.b() && this.c.e() != 0);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public NavigationAbstract(EntityInsentient entityinsentient, World world) {
|
|
this.g = Vec3D.a;
|
|
this.h = BaseBlockPosition.ZERO;
|
|
@@ -384,7 +391,7 @@ public abstract class NavigationAbstract {
|
|
}
|
|
|
|
public void b(BlockPosition blockposition) {
|
|
- if (this.c != null && !this.c.b() && this.c.e() != 0) {
|
|
+ if (this.c != null && !this.c.b() && this.c.e() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking()
|
|
PathPoint pathpoint = this.c.c();
|
|
Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
index eb28c4df8d..54e374cc94 100644
|
|
--- a/src/main/java/net/minecraft/server/NetworkManager.java
|
|
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
@@ -71,6 +71,39 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
EnumProtocol protocol;
|
|
// Paper end
|
|
|
|
+ // Tuinity start - allow controlled flushing
|
|
+ volatile boolean canFlush = true;
|
|
+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger();
|
|
+ private int flushPacketsStart;
|
|
+ private final Object flushLock = new Object();
|
|
+
|
|
+ void disableAutomaticFlush() {
|
|
+ synchronized (this.flushLock) {
|
|
+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false
|
|
+ this.canFlush = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void enableAutomaticFlush() {
|
|
+ synchronized (this.flushLock) {
|
|
+ this.canFlush = true;
|
|
+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true
|
|
+ this.flush(); // only make the flush call if we need to
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private final void flush() {
|
|
+ if (this.channel.eventLoop().inEventLoop()) {
|
|
+ this.channel.flush();
|
|
+ } else {
|
|
+ this.channel.eventLoop().execute(() -> {
|
|
+ this.channel.flush();
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - allow controlled flushing
|
|
+
|
|
public NetworkManager(EnumProtocolDirection enumprotocoldirection) {
|
|
this.h = enumprotocoldirection;
|
|
}
|
|
@@ -217,7 +250,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() &&
|
|
(packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
|
|
))) {
|
|
- this.dispatchPacket(packet, genericfuturelistener);
|
|
+ this.writePacket(packet, genericfuturelistener, null); // Tuinity
|
|
return;
|
|
}
|
|
// write the packets to the queue, then flush - antixray hooks there already
|
|
@@ -243,6 +276,14 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
|
|
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
|
|
private void b(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
|
+ // Tuinity start - add flush parameter
|
|
+ this.writePacket(packet, genericfuturelistener, Boolean.TRUE);
|
|
+ }
|
|
+ private void writePacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener, Boolean flushConditional) {
|
|
+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush
|
|
+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue();
|
|
+ final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive || packet instanceof PacketPlayOutKickDisconnect; // no delay for certain packets
|
|
+ // Tuinity end - add flush parameter
|
|
EnumProtocol enumprotocol = EnumProtocol.a(packet);
|
|
EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get();
|
|
|
|
@@ -265,7 +306,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
try {
|
|
// Paper end
|
|
|
|
- ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
|
|
+ ChannelFuture channelfuture = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter
|
|
|
|
if (genericfuturelistener != null) {
|
|
channelfuture.addListener(genericfuturelistener);
|
|
@@ -297,7 +338,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
}
|
|
try {
|
|
// Paper end
|
|
- ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet);
|
|
+ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter
|
|
|
|
|
|
if (genericfuturelistener != null) {
|
|
@@ -340,6 +381,8 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
}
|
|
private boolean processQueue() {
|
|
if (this.packetQueue.isEmpty()) return true;
|
|
+ final boolean needsFlush = this.canFlush; // Tuinity - make only one flush call per sendPacketQueue() call
|
|
+ boolean hasWrotePacket = false;
|
|
// If we are on main, we are safe here in that nothing else should be processing queue off main anymore
|
|
// But if we are not on main due to login/status, the parent is synchronized on packetQueue
|
|
java.util.Iterator<QueuedPacket> iterator = this.packetQueue.iterator();
|
|
@@ -347,16 +390,22 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
NetworkManager.QueuedPacket queued = iterator.next(); // poll -> peek
|
|
|
|
// Fix NPE (Spigot bug caused by handleDisconnection())
|
|
- if (queued == null) {
|
|
+ if (false && queued == null) { // Tuinity - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here
|
|
return true;
|
|
}
|
|
|
|
Packet<?> packet = queued.getPacket();
|
|
if (!packet.isReady()) {
|
|
+ // Tuinity start - make only one flush call per sendPacketQueue() call
|
|
+ if (hasWrotePacket && (needsFlush || this.canFlush)) {
|
|
+ this.flush();
|
|
+ }
|
|
+ // Tuinity end - make only one flush call per sendPacketQueue() call
|
|
return false;
|
|
} else {
|
|
iterator.remove();
|
|
- this.dispatchPacket(packet, queued.getGenericFutureListener());
|
|
+ this.writePacket(packet, queued.getGenericFutureListener(), (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Tuinity - make only one flush call per sendPacketQueue() call
|
|
+ hasWrotePacket = true; // Tuinity - make only one flush call per sendPacketQueue() call
|
|
}
|
|
}
|
|
return true;
|
|
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
index 8335d00336..e0ee524093 100644
|
|
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
@@ -19,7 +19,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
@Nullable
|
|
private BiomeStorage e;
|
|
private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER
|
|
- private List<NBTTagCompound> g;
|
|
+ private List<NBTTagCompound> g; private List<NBTTagCompound> getTileEntityData() { return this.g; } // Tuinity - OBFHELPER
|
|
private boolean h;
|
|
private boolean i;
|
|
|
|
@@ -32,14 +32,16 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
|
|
// Paper start
|
|
private final java.util.List<Packet> extraPackets = new java.util.ArrayList<>();
|
|
- private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
|
|
+ private static final int TE_LIMIT = Integer.getInteger("tuinity.excessive-te-limit", 750); // Tuinity - handle oversized chunk data packets more robustly
|
|
+ private static final int TE_SPLIT_LIMIT = Math.max(4096 + 1, Integer.getInteger("tuinity.te-split-limit", 15_000)); // Tuinity - handle oversized chunk data packets more robustly
|
|
+ private boolean mustSplit; // Tuinity - handle oversized chunk data packets more robustly
|
|
|
|
@Override
|
|
public java.util.List<Packet> getExtraPackets() {
|
|
return extraPackets;
|
|
}
|
|
// Paper end
|
|
- public PacketPlayOutMapChunk(Chunk chunk, int i, boolean flag) {
|
|
+ public PacketPlayOutMapChunk(Chunk chunk, int i, boolean flag) { final int chunkSectionBitSet = i; // Tuinity - handle oversized chunk data packets more robustly
|
|
ChunkPacketInfo<IBlockData> chunkPacketInfo = chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i); // Paper - Anti-Xray - Add chunk packet info
|
|
ChunkCoordIntPair chunkcoordintpair = chunk.getPos();
|
|
|
|
@@ -48,29 +50,10 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
this.h = i == 65535;
|
|
this.i = flag;
|
|
this.d = new NBTTagCompound();
|
|
- Iterator iterator = chunk.f().iterator();
|
|
-
|
|
- Entry entry;
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- entry = (Entry) iterator.next();
|
|
- if (((HeightMap.Type) entry.getKey()).c()) {
|
|
- this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a()));
|
|
- }
|
|
- }
|
|
-
|
|
- if (this.h) {
|
|
- this.e = chunk.getBiomeIndex().b();
|
|
- }
|
|
-
|
|
- this.f = new byte[this.a(chunk, i)];
|
|
- // Paper start - Anti-Xray - Add chunk packet info
|
|
- if (chunkPacketInfo != null) {
|
|
- chunkPacketInfo.setData(this.getData());
|
|
- }
|
|
- this.c = this.writeChunk(new PacketDataSerializer(this.k()), chunk, i, chunkPacketInfo);
|
|
- // Paper end
|
|
this.g = Lists.newArrayList();
|
|
+ // Tuinity start - moved code up (moved declaration of Iterator + Entry up)
|
|
+ Iterator iterator;
|
|
+ Entry entry;
|
|
iterator = chunk.getTileEntities().entrySet().iterator();
|
|
int totalTileEntities = 0; // Paper
|
|
|
|
@@ -82,7 +65,15 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
|
|
if (this.f() || (i & 1 << j) != 0) {
|
|
// Paper start - improve oversized chunk data packet handling
|
|
- if (++totalTileEntities > TE_LIMIT) {
|
|
+ // Tuinity start - improve oversized chunk data packet handling
|
|
+ ++totalTileEntities;
|
|
+ if (totalTileEntities > TE_SPLIT_LIMIT) {
|
|
+ this.mustSplit = true;
|
|
+ this.getTileEntityData().clear();
|
|
+ this.extraPackets.clear();
|
|
+ break;
|
|
+ }
|
|
+ if (totalTileEntities > TE_LIMIT) { // Tuinity end
|
|
PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket();
|
|
if (updatePacket != null) {
|
|
this.extraPackets.add(updatePacket);
|
|
@@ -96,7 +87,69 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
this.g.add(nbttagcompound);
|
|
}
|
|
}
|
|
+ iterator = chunk.f().iterator(); // Tuinity - moved declaration of Iterator and Entry up
|
|
+ // Tuinity - moved this all up
|
|
+
|
|
+ while (iterator.hasNext()) {
|
|
+ entry = (Entry) iterator.next();
|
|
+ if (((HeightMap.Type) entry.getKey()).c()) {
|
|
+ this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a()));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (this.h) {
|
|
+ this.e = chunk.getBiomeIndex().b();
|
|
+ }
|
|
+
|
|
+ this.f = new byte[this.a(chunk, i)];
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ if (chunkPacketInfo != null) {
|
|
+ chunkPacketInfo.setData(this.getData());
|
|
+ }
|
|
+ this.c = this.writeChunk(new PacketDataSerializer(this.k()), chunk, i, chunkPacketInfo);
|
|
+ // Paper end
|
|
+ // Tuinity start - move this all up
|
|
+// this.g = Lists.newArrayList();
|
|
+// iterator = chunk.getTileEntities().entrySet().iterator();
|
|
+// int totalTileEntities = 0; // Paper
|
|
+//
|
|
+// while (iterator.hasNext()) {
|
|
+// entry = (Entry) iterator.next();
|
|
+// BlockPosition blockposition = (BlockPosition) entry.getKey();
|
|
+// TileEntity tileentity = (TileEntity) entry.getValue();
|
|
+// int j = blockposition.getY() >> 4;
|
|
+//
|
|
+// if (this.f() || (i & 1 << j) != 0) {
|
|
+// // Paper start - improve oversized chunk data packet handling
|
|
+// if (++totalTileEntities > TE_LIMIT) {
|
|
+// PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket();
|
|
+// if (updatePacket != null) {
|
|
+// this.extraPackets.add(updatePacket);
|
|
+// continue;
|
|
+// }
|
|
+// }
|
|
+// // Paper end
|
|
+// NBTTagCompound nbttagcompound = tileentity.b();
|
|
+// if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper
|
|
+//
|
|
+// this.g.add(nbttagcompound);
|
|
+// }
|
|
+// }
|
|
+ // Tuinity end - move this all up
|
|
chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
|
|
+ // Tuinity start - improve oversized chunk data packet handling
|
|
+ if (this.mustSplit) {
|
|
+ int chunkSectionBitSetCopy = chunkSectionBitSet;
|
|
+ for (int a = 0, len = Integer.bitCount(chunkSectionBitSet); a < len; ++a) {
|
|
+ int trailingBit = com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(chunkSectionBitSetCopy);
|
|
+ int sectionIndex = Integer.numberOfTrailingZeros(trailingBit);
|
|
+ chunkSectionBitSetCopy ^= trailingBit; // move on to the next
|
|
+
|
|
+ if (chunk.getSections()[sectionIndex] != null) {
|
|
+ this.extraPackets.add(new PacketPlayOutMapChunk(chunk, trailingBit, false));
|
|
+ }
|
|
+ }
|
|
+ } // Tuinity end - improve oversized chunk data packet handling
|
|
}
|
|
|
|
// Paper start - Async-Anti-Xray - Getter and Setter for the ready flag
|
|
@@ -189,7 +242,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
for (int l = achunksection.length; k < l; ++k) {
|
|
ChunkSection chunksection = achunksection[k];
|
|
|
|
- if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) {
|
|
+ if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { // Tuinity - improve oversized chunk data packet handling
|
|
j |= 1 << k;
|
|
chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info
|
|
}
|
|
@@ -206,7 +259,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
for (int l = achunksection.length; k < l; ++k) {
|
|
ChunkSection chunksection = achunksection[k];
|
|
|
|
- if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) {
|
|
+ if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) {
|
|
j += chunksection.j();
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java
|
|
index d7f0df123b..ec55785af2 100644
|
|
--- a/src/main/java/net/minecraft/server/PathfinderNormal.java
|
|
+++ b/src/main/java/net/minecraft/server/PathfinderNormal.java
|
|
@@ -538,7 +538,7 @@ public class PathfinderNormal extends PathfinderAbstract {
|
|
if (!iblockdata.a(iblockaccess, blockposition, PathMode.LAND)) {
|
|
return PathType.BLOCKED;
|
|
} else {
|
|
- Fluid fluid = iblockaccess.getFluid(blockposition);
|
|
+ Fluid fluid = iblockdata.getFluid(); // Tuinity - remove another getType call
|
|
|
|
return fluid.a((Tag) TagsFluid.WATER) ? PathType.WATER : (fluid.a((Tag) TagsFluid.LAVA) ? PathType.LAVA : PathType.OPEN);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index c14cdb6024..8ec21eb955 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -494,6 +494,7 @@ public class PlayerChunk {
|
|
// Paper end - per player view distance
|
|
}
|
|
|
|
+ public final CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getOrCreateFuture(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { return this.a(chunkstatus, playerchunkmap); } // Tuinity - OBFHELPER
|
|
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) {
|
|
int i = chunkstatus.c();
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(i);
|
|
@@ -549,6 +550,7 @@ public class PlayerChunk {
|
|
}
|
|
|
|
protected void a(PlayerChunkMap playerchunkmap) {
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticket level update"); // Tuinity
|
|
ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel);
|
|
ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel);
|
|
boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET;
|
|
@@ -558,7 +560,8 @@ public class PlayerChunk {
|
|
// CraftBukkit start
|
|
// ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
|
if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main
|
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity
|
|
Chunk chunk = (Chunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
playerchunkmap.callbackExecutor.execute(() -> {
|
|
@@ -623,7 +626,8 @@ public class PlayerChunk {
|
|
if (!flag2 && flag3) {
|
|
// Paper start - cache ticking ready status
|
|
int expectCreateCount = ++this.fullChunkCreateCount;
|
|
- this.fullChunkFuture = playerchunkmap.b(this); ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main
|
|
+ this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always fired on main
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full chunk future completion"); // Tuinity
|
|
if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk fullChunk = either.left().get();
|
|
@@ -654,7 +658,8 @@ public class PlayerChunk {
|
|
|
|
if (!flag4 && flag5) {
|
|
// Paper start - cache ticking ready status
|
|
- this.tickingFuture = playerchunkmap.a(this); ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main
|
|
+ this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always completed on main
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticking chunk future completion"); // Tuinity
|
|
if (either.left().isPresent()) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk tickingChunk = either.left().get();
|
|
@@ -685,12 +690,20 @@ public class PlayerChunk {
|
|
}
|
|
|
|
// Paper start - cache ticking ready status
|
|
- this.entityTickingFuture = playerchunkmap.b(this.location); ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain
|
|
+ this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { // Paper ensureMain // Tuinity - always completed on main
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async entity ticking chunk future completion"); // Tuinity
|
|
if (either.left().isPresent()) {
|
|
// note: Here is a very good place to add callbacks to logic waiting on this.
|
|
Chunk entityTickingChunk = either.left().get();
|
|
PlayerChunk.this.isEntityTickingReady = true;
|
|
-
|
|
+ // Tuinity start - optimise chunk tick iteration
|
|
+ ChunkProviderServer chunkProvider = PlayerChunk.this.chunkMap.world.getChunkProvider();
|
|
+ if (chunkProvider.isTickingChunks) {
|
|
+ chunkProvider.pendingEntityTickingChunkChanges.put(entityTickingChunk, true);
|
|
+ } else {
|
|
+ chunkProvider.entityTickingChunks.add(entityTickingChunk);
|
|
+ }
|
|
+ // Tuinity end - optimise chunk tick iteration
|
|
|
|
|
|
|
|
@@ -702,6 +715,17 @@ public class PlayerChunk {
|
|
|
|
if (flag6 && !flag7) {
|
|
this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
|
|
+ // Tuinity start - optimise chunk tick iteration
|
|
+ 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 - optimise chunk tick iteration
|
|
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
|
|
@@ -728,7 +752,8 @@ public class PlayerChunk {
|
|
// CraftBukkit start
|
|
// ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
|
if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main
|
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity
|
|
Chunk chunk = (Chunk)either.left().orElse(null);
|
|
if (chunk != null) {
|
|
playerchunkmap.callbackExecutor.execute(() -> {
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 5c5ee28f23..46d8beae87 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -119,31 +119,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
|
|
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
|
|
public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
|
|
-
|
|
- // Paper start - replace impl with recursive safe multi entry queue
|
|
- // it's possible to schedule multiple tasks currently, so it's vital we change this impl
|
|
- // If we recurse into the executor again, we will append to another queue, ensuring task order consistency
|
|
- private java.util.ArrayDeque<Runnable> queued = new java.util.ArrayDeque<>();
|
|
+ // Tuinity start - revert paper's change
|
|
+ private Runnable queued;
|
|
|
|
@Override
|
|
public void execute(Runnable runnable) {
|
|
AsyncCatcher.catchOp("Callback Executor execute");
|
|
- if (queued == null) {
|
|
- queued = new java.util.ArrayDeque<>();
|
|
+ if (queued != null) {
|
|
+ MinecraftServer.LOGGER.fatal("Failed to schedule runnable", new IllegalStateException("Already queued")); // Paper - make sure this is printed
|
|
+ throw new IllegalStateException("Already queued");
|
|
}
|
|
- queued.add(runnable);
|
|
+ queued = runnable;
|
|
}
|
|
+ // Tuinity end - revert paper's change
|
|
|
|
@Override
|
|
public void run() {
|
|
AsyncCatcher.catchOp("Callback Executor run");
|
|
- if (queued == null) {
|
|
- return;
|
|
- }
|
|
- java.util.ArrayDeque<Runnable> queue = queued;
|
|
+ // Tuinity start - revert paper's change
|
|
+ Runnable task = queued;
|
|
queued = null;
|
|
- Runnable task;
|
|
- while ((task = queue.pollFirst()) != null) {
|
|
+ if (task != null) {
|
|
+ // Tuinity end - revert paper's change
|
|
task.run();
|
|
}
|
|
}
|
|
@@ -198,6 +195,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper end - no-tick view distance
|
|
|
|
void addPlayerToDistanceMaps(EntityPlayer player) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
|
|
int chunkX = MCUtil.getChunkCoordinate(player.locX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
@@ -228,6 +226,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
void removePlayerFromDistanceMaps(EntityPlayer player) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
|
|
// Paper start - use distance map to optimise tracker
|
|
for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
|
|
this.playerEntityTrackerTrackMaps[i].remove(player);
|
|
@@ -245,6 +244,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
void updateMaps(EntityPlayer player) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
|
|
int chunkX = MCUtil.getChunkCoordinate(player.locX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
@@ -275,6 +275,29 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start
|
|
+ public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator<RegionData> {
|
|
+ // Tuinity start - optimise notify()
|
|
+ PATHING_NAVIGATORS() {
|
|
+ @Override
|
|
+ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<RegionData> section,
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> regionManager) {
|
|
+ return new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>();
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+ ;
|
|
+
|
|
+ @Override
|
|
+ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<RegionData> section,
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> regionManager) {
|
|
+ throw new AbstractMethodError();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> dataRegionManager;
|
|
+ // Tuiniy end
|
|
+
|
|
public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i, boolean flag) {
|
|
super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag);
|
|
//this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning
|
|
@@ -441,6 +464,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded
|
|
});
|
|
// Paper end - no-tick view distance
|
|
+ // Tuinity start
|
|
+ this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 10, (1.0 / 3.0), "Data");
|
|
+ // Tuinity end
|
|
}
|
|
// Paper start - Chunk Prioritization
|
|
public void queueHolderUpdate(PlayerChunk playerchunk) {
|
|
@@ -753,6 +779,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 {
|
|
@@ -775,6 +802,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
playerchunk.a(j);
|
|
} else {
|
|
playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this);
|
|
+ this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity
|
|
}
|
|
|
|
this.updatingChunks.put(i, playerchunk);
|
|
@@ -966,7 +994,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkPos.x, chunkPos.z,
|
|
- poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY);
|
|
+ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority
|
|
|
|
if (!chunk.isNeedsSaving()) {
|
|
return;
|
|
@@ -1000,7 +1028,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
asyncSaveData = ChunkRegionLoader.getAsyncSaveData(this.world, chunk);
|
|
}
|
|
|
|
- this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY,
|
|
+ this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, // Tuinity - use normal priority
|
|
asyncSaveData, chunk);
|
|
|
|
chunk.setLastSaved(this.world.getTime());
|
|
@@ -1039,6 +1067,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
this.lightEngine.a(ichunkaccess.getPos());
|
|
this.lightEngine.queueUpdate();
|
|
this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null);
|
|
+ this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity
|
|
}
|
|
|
|
}
|
|
@@ -1055,6 +1084,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
protected boolean b() {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update visibleChunks off of the main thread"); // Tuinity
|
|
if (!this.updatingChunksModified) {
|
|
return false;
|
|
} else {
|
|
@@ -1242,7 +1272,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
// Paper end
|
|
this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable));
|
|
- });
|
|
+ }).thenComposeAsync((either) -> { // Tuinity start - force competion on the main thread
|
|
+ return CompletableFuture.completedFuture(either);
|
|
+ }, this.mainInvokingExecutor);
|
|
+ // Tuinity end - force competion on the main thread
|
|
}
|
|
|
|
protected void c(ChunkCoordIntPair chunkcoordintpair) {
|
|
@@ -1494,6 +1527,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
public void setViewDistance(int i) { // Paper - public
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity
|
|
int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
|
|
|
|
if (j != this.viewDistance) {
|
|
@@ -1507,6 +1541,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
// Paper start - no-tick view distance
|
|
public final void setNoTickViewDistance(int viewDistance) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity
|
|
viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32);
|
|
|
|
this.noTickViewDistance = viewDistance;
|
|
@@ -2033,23 +2068,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
private final void processTrackQueue() {
|
|
this.world.timings.tracker1.startTiming();
|
|
try {
|
|
- for (EntityTracker tracker : this.trackedEntities.values()) {
|
|
- // update tracker entry
|
|
- tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange());
|
|
+ for (Chunk chunk : this.world.getChunkProvider().entityTickingChunks) {
|
|
+ Entity[] entities = chunk.entities.getRawData();
|
|
+ for (int i = 0, len = chunk.entities.size(); i < len; ++i) {
|
|
+ Entity entity = entities[i];
|
|
+ EntityTracker tracker = this.trackedEntities.get(entity.getId());
|
|
+ if (tracker != null) {
|
|
+ tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange());
|
|
+ tracker.trackerEntry.tick();
|
|
+ }
|
|
+ }
|
|
}
|
|
} finally {
|
|
this.world.timings.tracker1.stopTiming();
|
|
}
|
|
-
|
|
-
|
|
- this.world.timings.tracker2.startTiming();
|
|
- try {
|
|
- for (EntityTracker tracker : this.trackedEntities.values()) {
|
|
- tracker.trackerEntry.tick();
|
|
- }
|
|
- } finally {
|
|
- this.world.timings.tracker2.stopTiming();
|
|
- }
|
|
}
|
|
// Paper end - optimised tracker
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
index 84b9c31cc1..82a5658425 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
@@ -322,19 +322,24 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
|
|
if (entity != this.player && entity.getRidingPassenger() == this.player && entity == this.r) {
|
|
WorldServer worldserver = this.player.getWorldServer();
|
|
- double d0 = entity.locX();
|
|
- double d1 = entity.locY();
|
|
- double d2 = entity.locZ();
|
|
- double d3 = packetplayinvehiclemove.getX();
|
|
- double d4 = packetplayinvehiclemove.getY();
|
|
- double d5 = packetplayinvehiclemove.getZ();
|
|
+ double d0 = entity.locX();double fromX = d0; // Tuinity - OBFHELPER
|
|
+ double d1 = entity.locY();double fromY = d1; // Tuinity - OBFHELPER
|
|
+ double d2 = entity.locZ();double fromZ = d2; // Tuinity - OBFHELPER
|
|
+ double d3 = packetplayinvehiclemove.getX();double toX = d3; // Tuinity - OBFHELPER
|
|
+ double d4 = packetplayinvehiclemove.getY();double toY = d4; // Tuinity - OBFHELPER
|
|
+ double d5 = packetplayinvehiclemove.getZ();double toZ = d5; // Tuinity - OBFHELPER
|
|
float f = packetplayinvehiclemove.getYaw();
|
|
float f1 = packetplayinvehiclemove.getPitch();
|
|
double d6 = d3 - this.s;
|
|
double d7 = d4 - this.t;
|
|
double d8 = d5 - this.u;
|
|
double d9 = entity.getMot().g();
|
|
- double d10 = d6 * d6 + d7 * d7 + d8 * d8;
|
|
+ // Tuinity start - fix large move vectors killing the server
|
|
+ double currDeltaX = toX - fromX;
|
|
+ double currDeltaY = toY - fromY;
|
|
+ double currDeltaZ = toZ - fromZ;
|
|
+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
|
|
+ // Tuinity end - fix large move vectors killing the server
|
|
|
|
|
|
// CraftBukkit start - handle custom speeds and skipped ticks
|
|
@@ -363,7 +368,9 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
speed *= 2f; // TODO: Get the speed of the vehicle instead of the player
|
|
|
|
// Paper start - Prevent moving into unloaded chunks
|
|
- if (player.world.paperConfig.preventMovingIntoUnloadedChunks && worldserver.getChunkIfLoadedImmediately((int) Math.floor(packetplayinvehiclemove.getX()) >> 4, (int) Math.floor(packetplayinvehiclemove.getZ()) >> 4) == null) {
|
|
+ if (player.world.paperConfig.preventMovingIntoUnloadedChunks // Tuinity - improve this check
|
|
+ && (!worldserver.areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) // Tuinity - improve this check
|
|
+ || !worldserver.areChunksLoadedForMove(entity.getBoundingBoxAt(entity.locX(), entity.locY(), entity.locZ()).expand(toX - entity.locX(), toY - entity.locY(), toZ - entity.locZ()))) { // Tuinity - improve this check
|
|
this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity));
|
|
return;
|
|
}
|
|
@@ -976,7 +983,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
}
|
|
|
|
if (this.teleportPos != null) {
|
|
- if (this.e - this.A > 20) {
|
|
+ if (false && this.e - this.A > 20) { // Tuinity - this will greatly screw with clients with > 1000ms RTT
|
|
this.A = this.e;
|
|
this.a(this.teleportPos.x, this.teleportPos.y, this.teleportPos.z, this.player.yaw, this.player.pitch);
|
|
}
|
|
@@ -1000,7 +1007,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
double d2 = this.player.locZ();
|
|
double d3 = this.player.locY();
|
|
double d4 = packetplayinflying.a(this.player.locX());double toX = d4; // Paper - OBFHELPER
|
|
- double d5 = packetplayinflying.b(this.player.locY());
|
|
+ double d5 = packetplayinflying.b(this.player.locY());double toY = d5; // Tuinity - OBFHELPER
|
|
double d6 = packetplayinflying.c(this.player.locZ());double toZ = d6; // Paper - OBFHELPER
|
|
float f = packetplayinflying.a(this.player.yaw);
|
|
float f1 = packetplayinflying.b(this.player.pitch);
|
|
@@ -1008,7 +1015,12 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
double d8 = d5 - this.m;
|
|
double d9 = d6 - this.n;
|
|
double d10 = this.player.getMot().g();
|
|
- double d11 = d7 * d7 + d8 * d8 + d9 * d9;
|
|
+ // Tuinity start - fix large move vectors killing the server
|
|
+ double currDeltaX = toX - prevX;
|
|
+ double currDeltaY = toY - prevY;
|
|
+ double currDeltaZ = toZ - prevZ;
|
|
+ double d11 = Math.max(d7 * d7 + d8 * d8 + d9 * d9, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
|
|
+ // Tuinity end - fix large move vectors killing the server
|
|
|
|
if (this.player.isSleeping()) {
|
|
if (d11 > 1.0D) {
|
|
@@ -1041,7 +1053,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
speed = player.abilities.walkSpeed * 10f;
|
|
}
|
|
// Paper start - Prevent moving into unloaded chunks
|
|
- if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately
|
|
+ if (player.world.paperConfig.preventMovingIntoUnloadedChunks && !((WorldServer)this.player.world).areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) { // Paper - use getIfLoadedImmediately // Tuinity - improve this check
|
|
this.internalTeleport(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch, Collections.emptySet());
|
|
return;
|
|
}
|
|
@@ -1097,6 +1109,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
}
|
|
|
|
this.player.move(EnumMoveType.PLAYER, new Vec3D(d7, d8, d9));
|
|
+ boolean didCollide = toX != this.player.locX() || toY != this.player.locY() || toZ != this.player.locZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
|
|
this.player.c(packetplayinflying.b()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move
|
|
// Paper start - prevent position desync
|
|
if (this.teleportPos != null) {
|
|
@@ -1121,7 +1134,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
}
|
|
|
|
this.player.setLocation(d4, d5, d6, f, f1);
|
|
- if (!this.player.noclip && !this.player.isSleeping() && (flag1 && worldserver.getCubes(this.player, axisalignedbb) || this.a((IWorldReader) worldserver, axisalignedbb))) {
|
|
+ if (!this.player.noclip && !this.player.isSleeping() && (flag1 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)))) { // Tuinity - optimise out the extra getCubes-like call most of the time
|
|
this.a(d0, d1, d2, f, f1);
|
|
} else {
|
|
// CraftBukkit start - fire PlayerMoveEvent
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java
|
|
index 7ea293f38d..e698dd2260 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java
|
|
@@ -13,10 +13,30 @@ public class PlayerConnectionUtils {
|
|
ensureMainThread(packet, t0, (IAsyncTaskHandler) worldserver.getMinecraftServer());
|
|
}
|
|
|
|
+ // Tuinity start - detailed watchdog information
|
|
+ private static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
|
|
+ private static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
|
|
+
|
|
+ public static long getTotalProcessedPackets() {
|
|
+ return totalMainThreadPacketsProcessed.get();
|
|
+ }
|
|
+
|
|
+ public static java.util.List<PacketListener> getCurrentPacketProcessors() {
|
|
+ java.util.List<PacketListener> ret = new java.util.ArrayList<>(4);
|
|
+ for (PacketListener listener : packetProcessing) {
|
|
+ ret.add(listener);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // Tuinity end - detailed watchdog information
|
|
+
|
|
public static <T extends PacketListener> void ensureMainThread(Packet<T> packet, T t0, IAsyncTaskHandler<?> iasynctaskhandler) throws CancelledPacketHandleException {
|
|
if (!iasynctaskhandler.isMainThread()) {
|
|
Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings
|
|
iasynctaskhandler.execute(() -> {
|
|
+ packetProcessing.push(t0); // Tuinity - detailed watchdog information
|
|
+ try { // Tuinity - detailed watchdog information
|
|
if (MinecraftServer.getServer().hasStopped() || (t0 instanceof PlayerConnection && ((PlayerConnection) t0).processedDisconnect)) return; // CraftBukkit, MC-142590
|
|
if (t0.a().isConnected()) {
|
|
try (Timing ignored = timing.startTiming()) { // Paper - timings
|
|
@@ -40,6 +60,12 @@ public class PlayerConnectionUtils {
|
|
} else {
|
|
PlayerConnectionUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet);
|
|
}
|
|
+ // Tuinity start - detailed watchdog information
|
|
+ } finally {
|
|
+ totalMainThreadPacketsProcessed.getAndIncrement();
|
|
+ packetProcessing.pop();
|
|
+ }
|
|
+ // Tuinity end - detailed watchdog information
|
|
|
|
});
|
|
throw CancelledPacketHandleException.INSTANCE;
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java
|
|
index 6d192b2744..f5de49e3e3 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerInteractManager.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java
|
|
@@ -21,14 +21,29 @@ public class PlayerInteractManager {
|
|
private EnumGamemode gamemode;
|
|
private EnumGamemode e;
|
|
private boolean f;
|
|
- private int lastDigTick;
|
|
+ private int lastDigTick; private long lastDigTime; // Tuinity - lag compensate block breaking
|
|
private BlockPosition h;
|
|
private int currentTick;
|
|
- private boolean j;
|
|
+ private boolean j; private final boolean hasDestroyedTooFast() { return this.j; } // Tuinity - OBFHELPER
|
|
private BlockPosition k;
|
|
- private int l;
|
|
+ private int l; private final int getHasDestroyedTooFastStartTick() { return this.l; } // Tuinity - OBFHELPER
|
|
+ private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking
|
|
private int m;
|
|
|
|
+ // 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 (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && 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 (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && 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.e = EnumGamemode.NOT_SET;
|
|
@@ -84,7 +99,7 @@ public class PlayerInteractManager {
|
|
if (iblockdata == null || iblockdata.isAir()) { // Paper
|
|
this.j = false;
|
|
} else {
|
|
- float f = this.a(iblockdata, this.k, this.l);
|
|
+ float f = this.updateBlockBreakAnimation(iblockdata, this.k, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks
|
|
|
|
if (f >= 1.0F) {
|
|
this.j = false;
|
|
@@ -104,7 +119,7 @@ public class PlayerInteractManager {
|
|
this.m = -1;
|
|
this.f = false;
|
|
} else {
|
|
- this.a(iblockdata, this.h, this.lastDigTick);
|
|
+ this.updateBlockBreakAnimation(iblockdata, this.h, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying blocks
|
|
}
|
|
}
|
|
|
|
@@ -112,6 +127,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);
|
|
|
|
@@ -179,7 +200,7 @@ public class PlayerInteractManager {
|
|
return;
|
|
}
|
|
|
|
- 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);
|
|
@@ -232,12 +253,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"));
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) 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.m = j;
|
|
}
|
|
} else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.STOP_DESTROY_BLOCK) {
|
|
if (blockposition.equals(this.h)) {
|
|
- int k = this.currentTick - this.lastDigTick;
|
|
+ int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking
|
|
|
|
iblockdata = this.world.getType(blockposition);
|
|
if (!iblockdata.isAir()) {
|
|
@@ -254,12 +275,18 @@ public class PlayerInteractManager {
|
|
this.f = false;
|
|
this.j = true;
|
|
this.k = blockposition;
|
|
- this.l = this.lastDigTick;
|
|
+ this.l = this.lastDigTick; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Tuinity - lag compensate block breaking
|
|
}
|
|
}
|
|
}
|
|
|
|
+ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) {
|
|
+ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
|
|
+ } else {
|
|
this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "stopped destroying"));
|
|
+ }
|
|
+ // Tuinity end - 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.f = false;
|
|
if (!Objects.equals(this.h, blockposition) && !BlockPosition.ZERO.equals(this.h)) {
|
|
@@ -271,7 +298,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"));
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) 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
|
|
}
|
|
|
|
}
|
|
@@ -281,7 +308,13 @@ public class PlayerInteractManager {
|
|
|
|
public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) {
|
|
if (this.breakBlock(blockposition)) {
|
|
+ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) {
|
|
+ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition));
|
|
+ } else {
|
|
this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, s));
|
|
+ }
|
|
+ // Tuinity end - 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/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
index 3b03c28ee5..6ac9f437e8 100644
|
|
--- a/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
@@ -179,14 +179,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) {
|
|
@@ -202,10 +199,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 - reduce iterator creation
|
|
((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 939b518266..7f2382a895 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;
|
|
+ }
|
|
+
|
|
+ ((java.nio.Buffer)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, boolean flag) throws IOException {
|
|
+ // Tuinity start - add can recalc flag
|
|
this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag);
|
|
}
|
|
+ public RegionFile(File file, File file1, boolean flag, boolean canRecalcHeader) throws IOException {
|
|
+ this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag, canRecalcHeader);
|
|
+ // Tuinity end - add can recalc flag
|
|
+ }
|
|
|
|
public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag) throws IOException {
|
|
+ // Tuinity start - add can recalc flag
|
|
+ this(java_nio_file_path, java_nio_file_path1, regionfilecompression, flag, false);
|
|
+ }
|
|
+ public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag, boolean canRecalcHeader) throws IOException {
|
|
+ this.canRecalcHeader = canRecalcHeader;
|
|
+ // Tuinity end - add can recalc flag
|
|
this.file = java_nio_file_path.toFile(); // Paper
|
|
this.f = ByteBuffer.allocateDirect(8192);
|
|
initOversizedState();
|
|
@@ -95,12 +441,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
|
|
@@ -110,20 +459,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 {
|
|
@@ -147,6 +563,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 {
|
|
@@ -155,6 +577,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;
|
|
@@ -167,9 +595,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));
|
|
@@ -332,10 +772,15 @@ public class RegionFile implements AutoCloseable {
|
|
}
|
|
|
|
private ByteBuffer b() {
|
|
+ // 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;
|
|
}
|
|
@@ -372,6 +817,7 @@ public class RegionFile implements AutoCloseable {
|
|
};
|
|
}
|
|
|
|
+ private final void flushHeader() throws IOException { this.b(); } // Tuinity - OBFHELPER
|
|
private void c() 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 867dc074bc..60b4171a3a 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFileCache.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFileCache.java
|
|
@@ -14,12 +14,43 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final
|
|
public final Long2ObjectLinkedOpenHashMap<RegionFile> cache = new Long2ObjectLinkedOpenHashMap();
|
|
private final File b;
|
|
private final boolean c;
|
|
+ private final boolean isChunkData; // Tuinity
|
|
|
|
RegionFileCache(File file, boolean flag) {
|
|
+ // Tuinity start - add isChunkData param
|
|
+ this(file, flag, false);
|
|
+ }
|
|
+ RegionFileCache(File file, boolean flag, boolean isChunkData) {
|
|
+ this.isChunkData = isChunkData;
|
|
+ // Tuinity end - add isChunkData param
|
|
this.b = file;
|
|
this.c = flag;
|
|
}
|
|
|
|
+ // 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
|
|
@@ -53,9 +84,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, this.c);
|
|
+ RegionFile regionfile1 = new RegionFile(file, this.b, this.c, this.isChunkData); // Tuinity - allow for chunk regionfiles to regen header
|
|
|
|
this.cache.putAndMoveToFirst(i, regionfile1);
|
|
// Paper start
|
|
@@ -144,6 +175,13 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final
|
|
return null;
|
|
}
|
|
// CraftBukkit end
|
|
+ // 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
|
|
@@ -159,6 +197,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.isChunkData) {
|
|
+ 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/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java
|
|
index e41cb8613e..c19ffb925a 100644
|
|
--- a/src/main/java/net/minecraft/server/Ticket.java
|
|
+++ b/src/main/java/net/minecraft/server/Ticket.java
|
|
@@ -5,17 +5,17 @@ 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
|
|
public int priority = 0; // Paper
|
|
- public long delayUnloadBy; // Paper
|
|
+ boolean isCached; // Tuinity - delay chunk unloads, this defends against really stupid plugins
|
|
|
|
protected Ticket(TicketType<T> tickettype, int i, T t0) {
|
|
this.a = tickettype;
|
|
this.b = i;
|
|
this.identifier = t0;
|
|
- this.delayUnloadBy = tickettype.loadPeriod; // Paper
|
|
+ // Tuinity - delay chunk unloads
|
|
}
|
|
|
|
public int compareTo(Ticket<?> ticket) {
|
|
@@ -64,8 +64,9 @@ 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 = delayUnloadBy; // Paper
|
|
+ long j = this.a.b(); // Tuinity - delay chunk unloads
|
|
|
|
return j != 0L && i - this.d > j;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
|
|
index 5c789b25f1..4657b05a42 100644
|
|
--- a/src/main/java/net/minecraft/server/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/TicketType.java
|
|
@@ -26,7 +26,8 @@ public class TicketType<T> {
|
|
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
|
|
public static final TicketType<ChunkCoordIntPair> PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
public static final TicketType<ChunkCoordIntPair> URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
- public static final TicketType<Long> DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper
|
|
+ public static final TicketType<Long> DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads
|
|
+ public static final TicketType<Long> REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail
|
|
|
|
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/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
|
|
index 67fda8bd5a..e1f1d6e33f 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntity.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntity.java
|
|
@@ -12,7 +12,7 @@ import org.bukkit.inventory.InventoryHolder;
|
|
import co.aikar.timings.MinecraftTimings; // Paper
|
|
import co.aikar.timings.Timing; // Paper
|
|
|
|
-public abstract class TileEntity implements KeyedObject { // Paper
|
|
+public abstract class TileEntity implements KeyedObject, Cloneable { // Paper // Tuinity
|
|
|
|
public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper
|
|
// CraftBukkit start - data containers
|
|
@@ -27,7 +27,7 @@ public abstract class TileEntity implements KeyedObject { // Paper
|
|
protected BlockPosition position;
|
|
protected boolean f;
|
|
@Nullable
|
|
- private IBlockData c;
|
|
+ private IBlockData c; protected final IBlockData getBlockDataCache() { return this.c; } public final void setBlockDataCache(final IBlockData value) { this.c = value; } // Tuinity - OBFHELPER
|
|
private boolean g;
|
|
|
|
public TileEntity(TileEntityTypes<?> tileentitytypes) {
|
|
@@ -35,6 +35,51 @@ public abstract class TileEntity implements KeyedObject { // Paper
|
|
this.tileType = tileentitytypes;
|
|
}
|
|
|
|
+ // Tuinity start - pushable TE's
|
|
+ public boolean isPushable() {
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.pistonsCanPushTileEntities) {
|
|
+ return false;
|
|
+ }
|
|
+ IBlockData block = this.getBlock();
|
|
+ if (this.isRemoved() || !this.tileType.isValidBlock(block.getBlock())) {
|
|
+ return false;
|
|
+ }
|
|
+ EnumPistonReaction reaction = block.getPushReaction();
|
|
+ return reaction == EnumPistonReaction.NORMAL || reaction == EnumPistonReaction.PUSH_ONLY;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected final TileEntity clone() {
|
|
+ try {
|
|
+ return (TileEntity)super.clone();
|
|
+ } catch (final Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ throw new InternalError(thr);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // this method presumes the old TE has been completely dropped from worldstate and has no ties to it anymore (this
|
|
+ // includes players interacting with them)
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ final TileEntity copy = this.clone();
|
|
+
|
|
+ copy.world = world;
|
|
+ copy.position = newPos;
|
|
+
|
|
+ // removed is manually set to false after placing the entity into the world.
|
|
+ copy.setBlockDataCache(blockData);
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+
|
|
+ // updates TE state to its new position
|
|
+ public void onPostPush() {
|
|
+ this.update();
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
// Paper start
|
|
private String tileEntityKeyString = null;
|
|
private MinecraftKey tileEntityKey = null;
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java
|
|
index 2858ea1f3e..453f1301b4 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityBeacon.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java
|
|
@@ -35,7 +35,7 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic
|
|
@Nullable
|
|
public IChatBaseComponent customName;
|
|
public ChestLock chestLock;
|
|
- private final IContainerProperties containerProperties;
|
|
+ private IContainerProperties containerProperties; // Tuinity - need non-final for `createCopyForPush`
|
|
// CraftBukkit start - add fields and methods
|
|
public PotionEffect getPrimaryEffect() {
|
|
return (this.primaryEffect != null) ? CraftPotionUtil.toBukkit(new MobEffect(this.primaryEffect, getLevel(), getAmplification(), true, true)) : null;
|
|
@@ -46,10 +46,10 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic
|
|
}
|
|
// CraftBukkit end
|
|
|
|
- public TileEntityBeacon() {
|
|
- super(TileEntityTypes.BEACON);
|
|
- this.chestLock = ChestLock.a;
|
|
- this.containerProperties = new IContainerProperties() {
|
|
+ // Tuinity start - pushable TE's
|
|
+ protected final IContainerProperties getNewContainerProperties() {
|
|
+ // moved from constructor - this should be re-copied if it changes
|
|
+ return new IContainerProperties() {
|
|
@Override
|
|
public int getProperty(int i) {
|
|
switch (i) {
|
|
@@ -90,6 +90,22 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic
|
|
};
|
|
}
|
|
|
|
+ @Override
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ TileEntityBeacon copy = (TileEntityBeacon)super.createCopyForPush(world, oldPos, newPos, blockData);
|
|
+
|
|
+ copy.containerProperties = copy.getNewContainerProperties(); // old properties retains reference to old te
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
+ public TileEntityBeacon() {
|
|
+ super(TileEntityTypes.BEACON);
|
|
+ this.chestLock = ChestLock.a;
|
|
+ this.containerProperties = this.getNewContainerProperties(); // Tuinity - move into function
|
|
+ }
|
|
+
|
|
@Override
|
|
public void tick() {
|
|
int i = this.position.getX();
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityBeehive.java b/src/main/java/net/minecraft/server/TileEntityBeehive.java
|
|
index 6dc91d985a..9da356de6b 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityBeehive.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityBeehive.java
|
|
@@ -12,6 +12,13 @@ public class TileEntityBeehive extends TileEntity implements ITickable {
|
|
public BlockPosition flowerPos = null;
|
|
public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
|
|
|
|
+ // Tuinity start - pushable TE's
|
|
+ @Override
|
|
+ public boolean isPushable() {
|
|
+ return false; // TODO until there is a good solution to making the already existing bees in the world re-acquire this position, this cannot be done.
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
public TileEntityBeehive() {
|
|
super(TileEntityTypes.BEEHIVE);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityBrewingStand.java b/src/main/java/net/minecraft/server/TileEntityBrewingStand.java
|
|
index 276eba954b..e38a494d7b 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityBrewingStand.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityBrewingStand.java
|
|
@@ -24,7 +24,7 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
|
|
private boolean[] j;
|
|
private Item k;
|
|
public int fuelLevel;
|
|
- protected final IContainerProperties a;
|
|
+ protected IContainerProperties a; protected final void setContainerProperties(IContainerProperties value) { this.a = value; } // Tuinity - OBFHELPER // Tuinity - need non-final for `createCopyForPush`
|
|
// CraftBukkit start - add fields and methods
|
|
private int lastTick = MinecraftServer.currentTick;
|
|
public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
|
|
@@ -56,10 +56,10 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
|
|
}
|
|
// CraftBukkit end
|
|
|
|
- public TileEntityBrewingStand() {
|
|
- super(TileEntityTypes.BREWING_STAND);
|
|
- this.items = NonNullList.a(5, ItemStack.b);
|
|
- this.a = new IContainerProperties() {
|
|
+ // Tuinity start - pushable TE's
|
|
+ protected final IContainerProperties getNewContainerProperties() {
|
|
+ // moved from constructor - this should be re-copied if it changes
|
|
+ return new IContainerProperties() {
|
|
@Override
|
|
public int getProperty(int i) {
|
|
switch (i) {
|
|
@@ -91,6 +91,22 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl
|
|
};
|
|
}
|
|
|
|
+ @Override
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ TileEntityBrewingStand copy = (TileEntityBrewingStand)super.createCopyForPush(world, oldPos, newPos, blockData);
|
|
+
|
|
+ copy.setContainerProperties(copy.getNewContainerProperties()); // old properties retains reference to old te
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
+ public TileEntityBrewingStand() {
|
|
+ super(TileEntityTypes.BREWING_STAND);
|
|
+ this.items = NonNullList.a(5, ItemStack.b);
|
|
+ this.a = this.getNewContainerProperties();
|
|
+ }
|
|
+
|
|
@Override
|
|
protected IChatBaseComponent getContainerName() {
|
|
return new ChatMessage("container.brewing");
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java
|
|
index 17498eae50..0a73895999 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityChest.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityChest.java
|
|
@@ -46,6 +46,22 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic
|
|
}
|
|
// CraftBukkit end
|
|
|
|
+ // Tuinity start
|
|
+ @Override
|
|
+ public boolean isPushable() {
|
|
+ if (!super.isPushable()) {
|
|
+ return false;
|
|
+ }
|
|
+ // what should happen when a double chest is moved is generally just a mess to deal with in the current
|
|
+ // codebase.
|
|
+ IBlockData type = this.getBlock();
|
|
+ if (type.getBlock() == Blocks.CHEST || type.getBlock() == Blocks.TRAPPED_CHEST) {
|
|
+ return type.get(BlockChest.getChestTypeEnum()) == BlockPropertyChestType.SINGLE;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
protected TileEntityChest(TileEntityTypes<?> tileentitytypes) {
|
|
super(tileentitytypes);
|
|
this.items = NonNullList.a(27, ItemStack.b);
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityConduit.java b/src/main/java/net/minecraft/server/TileEntityConduit.java
|
|
index ade8301227..7e9470caa5 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityConduit.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityConduit.java
|
|
@@ -16,15 +16,32 @@ public class TileEntityConduit extends TileEntity implements ITickable {
|
|
private static final Block[] b = new Block[]{Blocks.PRISMARINE, Blocks.PRISMARINE_BRICKS, Blocks.SEA_LANTERN, Blocks.DARK_PRISMARINE};
|
|
public int a;
|
|
private float c;
|
|
- private boolean g;
|
|
+ private boolean g; private final void setActive(boolean value) { this.g = value; } // Tuinity - OBFHELPER
|
|
private boolean h;
|
|
- private final List<BlockPosition> i;
|
|
+ private final List<BlockPosition> i; private final List<BlockPosition> getPositionsActivating() { return this.i; } // Tuinity - OBFHELPER
|
|
@Nullable
|
|
private EntityLiving target;
|
|
@Nullable
|
|
private UUID k;
|
|
private long l;
|
|
|
|
+ // Tuinity start - make TE's pushable
|
|
+ @Override
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ final TileEntityConduit copy = (TileEntityConduit)super.createCopyForPush(world, oldPos, newPos, blockData);
|
|
+
|
|
+ // the following states need to be re-calculated
|
|
+ copy.getPositionsActivating().clear();
|
|
+ copy.setActive(false);
|
|
+ copy.target = null;
|
|
+ // also set our state because the copy and this share the same activating block list
|
|
+ this.setActive(false);
|
|
+ this.target = null;
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+ // Tuinity end - make TE's pushable
|
|
+
|
|
public TileEntityConduit() {
|
|
this(TileEntityTypes.CONDUIT);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityFurnace.java b/src/main/java/net/minecraft/server/TileEntityFurnace.java
|
|
index f4f50fb837..59aa2f8a79 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityFurnace.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityFurnace.java
|
|
@@ -32,14 +32,14 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I
|
|
public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API
|
|
public int cookTime;
|
|
public int cookTimeTotal;
|
|
- protected final IContainerProperties b;
|
|
+ protected IContainerProperties b; protected final void setContainerProperties(IContainerProperties value) { this.b = value; } // Tuinity - OBFHELPER // Tuinity - need non-final for `createCopyForPush`
|
|
private final Object2IntOpenHashMap<MinecraftKey> n;
|
|
protected final Recipes<? extends RecipeCooking> c;
|
|
|
|
- protected TileEntityFurnace(TileEntityTypes<?> tileentitytypes, Recipes<? extends RecipeCooking> recipes) {
|
|
- super(tileentitytypes);
|
|
- this.items = NonNullList.a(3, ItemStack.b);
|
|
- this.b = new IContainerProperties() {
|
|
+ // Tuinity start - pushable TE's
|
|
+ protected final IContainerProperties getNewContainerProperties() {
|
|
+ // moved from constructor - this should be re-copied if it changes
|
|
+ return new IContainerProperties() {
|
|
@Override
|
|
public int getProperty(int i) {
|
|
switch (i) {
|
|
@@ -79,7 +79,23 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I
|
|
return 4;
|
|
}
|
|
};
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ TileEntityFurnace copy = (TileEntityFurnace)super.createCopyForPush(world, oldPos, newPos, blockData);
|
|
+
|
|
+ copy.setContainerProperties(copy.getNewContainerProperties()); // old properties retains reference to old te
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
+ protected TileEntityFurnace(TileEntityTypes<?> tileentitytypes, Recipes<? extends RecipeCooking> recipes) {
|
|
+ super(tileentitytypes);
|
|
+ this.items = NonNullList.a(3, ItemStack.b);
|
|
this.n = new Object2IntOpenHashMap();
|
|
+ this.b = this.getNewContainerProperties();
|
|
this.c = recipes;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityJukeBox.java b/src/main/java/net/minecraft/server/TileEntityJukeBox.java
|
|
index 33c7dc56da..75eb1b8b29 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityJukeBox.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityJukeBox.java
|
|
@@ -4,6 +4,13 @@ public class TileEntityJukeBox extends TileEntity implements Clearable {
|
|
|
|
private ItemStack a;
|
|
|
|
+ // Tuinity start - pushable TE's
|
|
+ @Override
|
|
+ public boolean isPushable() {
|
|
+ return false; // disabled due to buggy sound
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
public TileEntityJukeBox() {
|
|
super(TileEntityTypes.JUKEBOX);
|
|
this.a = ItemStack.b;
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityLectern.java b/src/main/java/net/minecraft/server/TileEntityLectern.java
|
|
index b2ceb6c179..b955d5d661 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityLectern.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityLectern.java
|
|
@@ -17,7 +17,7 @@ import org.bukkit.inventory.InventoryHolder;
|
|
public class TileEntityLectern extends TileEntity implements Clearable, ITileInventory, ICommandListener { // CraftBukkit - ICommandListener
|
|
|
|
// CraftBukkit start - add fields and methods
|
|
- public final IInventory inventory = new LecternInventory();
|
|
+ public IInventory inventory = new LecternInventory(); // Tuinity - need non-final for `createCopyForPush`
|
|
public class LecternInventory implements IInventory {
|
|
|
|
public List<HumanEntity> transaction = new ArrayList<>();
|
|
@@ -137,29 +137,48 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv
|
|
@Override
|
|
public void clear() {}
|
|
};
|
|
- private final IContainerProperties containerProperties = new IContainerProperties() {
|
|
- @Override
|
|
- public int getProperty(int i) {
|
|
- return i == 0 ? TileEntityLectern.this.page : 0;
|
|
- }
|
|
+ // Tuinity start - pushable TE's
|
|
+ private IContainerProperties containerProperties = this.getNewContainerProperties(); // Tuinity - need non-final for `createCopyForPush`
|
|
+
|
|
+ protected final IContainerProperties getNewContainerProperties() {
|
|
+ return new IContainerProperties() {
|
|
+ @Override
|
|
+ public int getProperty(int i) {
|
|
+ return i == 0 ? TileEntityLectern.this.page : 0;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setProperty(int i, int j) {
|
|
+ if (i == 0) {
|
|
+ TileEntityLectern.this.setPage(j);
|
|
+ }
|
|
|
|
- @Override
|
|
- public void setProperty(int i, int j) {
|
|
- if (i == 0) {
|
|
- TileEntityLectern.this.setPage(j);
|
|
}
|
|
|
|
- }
|
|
+ @Override
|
|
+ public int a() {
|
|
+ return 1;
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
|
|
- @Override
|
|
- public int a() {
|
|
- return 1;
|
|
- }
|
|
- };
|
|
private ItemStack book;
|
|
private int page;
|
|
private int maxPage;
|
|
|
|
+ // Tuinity start - pushable TE's
|
|
+ @Override
|
|
+ public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) {
|
|
+ TileEntityLectern copy = (TileEntityLectern)super.createCopyForPush(world, oldPos, newPos, blockData);
|
|
+
|
|
+ copy.inventory = copy.new LecternInventory();
|
|
+ copy.containerProperties = copy.getNewContainerProperties(); // old properties retains reference to old te
|
|
+
|
|
+ return copy;
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
public TileEntityLectern() {
|
|
super(TileEntityTypes.LECTERN);
|
|
this.book = ItemStack.b;
|
|
diff --git a/src/main/java/net/minecraft/server/TileEntityPiston.java b/src/main/java/net/minecraft/server/TileEntityPiston.java
|
|
index e7b7e468fc..faf5e4aff0 100644
|
|
--- a/src/main/java/net/minecraft/server/TileEntityPiston.java
|
|
+++ b/src/main/java/net/minecraft/server/TileEntityPiston.java
|
|
@@ -5,10 +5,10 @@ import java.util.List;
|
|
|
|
public class TileEntityPiston extends TileEntity implements ITickable {
|
|
|
|
- private IBlockData a;
|
|
+ private IBlockData a; protected final IBlockData getBlockData() { return this.a; } // Tuinity - OBFHELPER
|
|
private EnumDirection b;
|
|
private boolean c;
|
|
- private boolean g;
|
|
+ private boolean g; protected final boolean isSource() { return this.g; } // Tuinity - OBFHELPER
|
|
private static final ThreadLocal<EnumDirection> h = ThreadLocal.withInitial(() -> {
|
|
return null;
|
|
});
|
|
@@ -16,12 +16,27 @@ public class TileEntityPiston extends TileEntity implements ITickable {
|
|
private float j;
|
|
private long k;
|
|
|
|
+ // Tuinity start - pushable TE's
|
|
+ private TileEntity tileEntity;
|
|
+
|
|
+ @Override
|
|
+ public boolean isPushable() {
|
|
+ return false; // fuck no.
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
+
|
|
public TileEntityPiston() {
|
|
super(TileEntityTypes.PISTON);
|
|
}
|
|
|
|
public TileEntityPiston(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1) {
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ this(iblockdata, enumdirection, flag, flag1, null);
|
|
+ }
|
|
+ public TileEntityPiston(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1, TileEntity tileEntity) {
|
|
this();
|
|
+ this.tileEntity = tileEntity;
|
|
+ // Tuinity end - add tileEntity parameter
|
|
this.a = iblockdata;
|
|
this.b = enumdirection;
|
|
this.c = flag;
|
|
@@ -30,7 +45,7 @@ public class TileEntityPiston extends TileEntity implements ITickable {
|
|
|
|
@Override
|
|
public NBTTagCompound b() {
|
|
- return this.save(new NBTTagCompound());
|
|
+ return this.save(new NBTTagCompound(), false); // Tuinity - clients don't need the copied tile entity.
|
|
}
|
|
|
|
public boolean d() {
|
|
@@ -238,7 +253,27 @@ public class TileEntityPiston extends TileEntity implements ITickable {
|
|
iblockdata = Block.b(this.a, (GeneratorAccess) this.world, this.position);
|
|
}
|
|
|
|
- this.world.setTypeAndData(this.position, iblockdata, 3);
|
|
+ // Tuinity start - pushable TE's
|
|
+ if ((iblockdata.isAir() && !this.isSource()) && !this.getBlockData().isAir()) {
|
|
+ // if the block can't exist at the location anymore, we need to fire drops for it, as
|
|
+ // setTypeAndData wont.
|
|
+
|
|
+ // careful - the previous pos is moving_piston, which wont fire drops. So we're safe from dupes.
|
|
+ // but the setAir should be before the drop.
|
|
+ this.world.setAir(this.position, false);
|
|
+ Block.dropItems(this.getBlockData(), this.world, this.position, null, null, ItemStack.NULL_ITEM);
|
|
+ } else {
|
|
+ if (iblockdata.getBlock() == this.getBlockData().getBlock() && this.tileEntity != null) {
|
|
+ // need to set to air before else the setTypeAndData call will create a new TE and override
|
|
+ // the old one
|
|
+ this.world.setAir(this.position, false);
|
|
+ }
|
|
+ this.world.setTypeAndData(this.position, iblockdata, 3, iblockdata.getBlock() == this.getBlockData().getBlock() ? this.tileEntity : null);
|
|
+ }
|
|
+ if (this.tileEntity != null && this.world.getType(this.position).getBlock() == this.getBlockData().getBlock()) {
|
|
+ this.tileEntity.onPostPush();
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
this.world.a(this.position, iblockdata.getBlock(), this.position);
|
|
}
|
|
}
|
|
@@ -263,7 +298,12 @@ public class TileEntityPiston extends TileEntity implements ITickable {
|
|
iblockdata = (IBlockData) iblockdata.set(BlockProperties.C, false);
|
|
}
|
|
|
|
- this.world.setTypeAndData(this.position, iblockdata, 67);
|
|
+ // Tuinity start - pushable TE's
|
|
+ this.world.setTypeAndData(this.position, iblockdata, 67, this.tileEntity);
|
|
+ if (this.tileEntity != null && this.world.getType(this.position).getBlock() == this.getBlockData().getBlock()) {
|
|
+ this.tileEntity.onPostPush();
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
this.world.a(this.position, iblockdata.getBlock(), this.position);
|
|
}
|
|
}
|
|
@@ -290,16 +330,34 @@ public class TileEntityPiston extends TileEntity implements ITickable {
|
|
this.j = this.i;
|
|
this.c = nbttagcompound.getBoolean("extending");
|
|
this.g = nbttagcompound.getBoolean("source");
|
|
+ // Tuinity start - pushable TE's
|
|
+ if (nbttagcompound.hasKey("Tuinity.tileEntity")) {
|
|
+ NBTTagCompound compound = nbttagcompound.getCompound("Tuinity.tileEntity");
|
|
+ if (!compound.isEmpty()) {
|
|
+ this.tileEntity = TileEntity.create(this.getBlockData(), compound);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
}
|
|
|
|
@Override
|
|
public NBTTagCompound save(NBTTagCompound nbttagcompound) {
|
|
+ // Tuinity start - add saveTile param
|
|
+ return this.save(nbttagcompound, true);
|
|
+ }
|
|
+ public NBTTagCompound save(NBTTagCompound nbttagcompound, boolean saveTile) {
|
|
+ // Tuinity end - add saveTile param
|
|
super.save(nbttagcompound);
|
|
nbttagcompound.set("blockState", GameProfileSerializer.a(this.a));
|
|
nbttagcompound.setInt("facing", this.b.c());
|
|
nbttagcompound.setFloat("progress", this.j);
|
|
nbttagcompound.setBoolean("extending", this.c);
|
|
nbttagcompound.setBoolean("source", this.g);
|
|
+ // Tuinity start - pushable TE's
|
|
+ if (saveTile && this.tileEntity != null) {
|
|
+ nbttagcompound.set("Tuinity.tileEntity", this.tileEntity.save(new NBTTagCompound()));
|
|
+ }
|
|
+ // Tuinity end - pushable TE's
|
|
return nbttagcompound;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java
|
|
index a038397028..2cdc6cf8ce 100644
|
|
--- a/src/main/java/net/minecraft/server/UserCache.java
|
|
+++ b/src/main/java/net/minecraft/server/UserCache.java
|
|
@@ -51,6 +51,10 @@ public class UserCache {
|
|
private final File h;
|
|
private static final TypeToken<List<UserCache.UserCacheEntry>> i = new TypeToken<List<UserCache.UserCacheEntry>>() {
|
|
};
|
|
+ // Tuinity start
|
|
+ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock();
|
|
+ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock();
|
|
+ // Tuinity end
|
|
|
|
public UserCache(GameProfileRepository gameprofilerepository, File file) {
|
|
this.g = gameprofilerepository;
|
|
@@ -98,7 +102,7 @@ public class UserCache {
|
|
this.a(gameprofile, (Date) null);
|
|
}
|
|
|
|
- private synchronized void a(GameProfile gameprofile, Date date) { // Paper - synchronize
|
|
+ private void a(GameProfile gameprofile, Date date) { // Paper - synchronize // Tuinity - allow better concurrency
|
|
UUID uuid = gameprofile.getId();
|
|
|
|
if (date == null) {
|
|
@@ -111,6 +115,7 @@ public class UserCache {
|
|
|
|
UserCache.UserCacheEntry usercache_usercacheentry = new UserCache.UserCacheEntry(gameprofile, date);
|
|
|
|
+ try { this.stateLock.lock(); // Tuinity - allow better concurrency
|
|
//if (this.e.containsKey(uuid)) { // Paper
|
|
UserCache.UserCacheEntry usercache_usercacheentry1 = (UserCache.UserCacheEntry) this.e.get(uuid);
|
|
if (usercache_usercacheentry1 != null) { // Paper
|
|
@@ -122,12 +127,14 @@ public class UserCache {
|
|
this.d.put(gameprofile.getName().toLowerCase(Locale.ROOT), usercache_usercacheentry);
|
|
this.e.put(uuid, usercache_usercacheentry);
|
|
this.f.addFirst(gameprofile);
|
|
+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency
|
|
if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.c(); // Spigot - skip saving if disabled
|
|
}
|
|
|
|
@Nullable
|
|
- public synchronized GameProfile getProfile(String s) { // Paper - synchronize
|
|
+ public GameProfile getProfile(String s) { // Paper - synchronize // Tuinity start - allow better concurrency
|
|
String s1 = s.toLowerCase(Locale.ROOT);
|
|
+ boolean stateLocked = true; try { this.stateLock.lock(); // Tuinity - allow better concurrency
|
|
UserCache.UserCacheEntry usercache_usercacheentry = (UserCache.UserCacheEntry) this.d.get(s1);
|
|
|
|
if (usercache_usercacheentry != null && (new Date()).getTime() >= usercache_usercacheentry.c.getTime()) {
|
|
@@ -143,8 +150,12 @@ public class UserCache {
|
|
gameprofile = usercache_usercacheentry.a();
|
|
this.f.remove(gameprofile);
|
|
this.f.addFirst(gameprofile);
|
|
+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency
|
|
} else {
|
|
+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency
|
|
+ try { this.lookupLock.lock(); // Tuinity - allow better concurrency
|
|
gameprofile = a(this.g, s); // Spigot - use correct case for offline players
|
|
+ } finally { this.lookupLock.unlock(); } // Tuinity - allow better concurrency
|
|
if (gameprofile != null) {
|
|
this.a(gameprofile);
|
|
usercache_usercacheentry = (UserCache.UserCacheEntry) this.d.get(s1);
|
|
@@ -153,6 +164,7 @@ public class UserCache {
|
|
|
|
if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.c(); // Spigot - skip saving if disabled
|
|
return usercache_usercacheentry == null ? null : usercache_usercacheentry.a();
|
|
+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Tuinity - allow better concurrency
|
|
}
|
|
|
|
// Paper start
|
|
@@ -252,6 +264,7 @@ public class UserCache {
|
|
}
|
|
|
|
private List<UserCache.UserCacheEntry> a(int i) {
|
|
+ try { this.stateLock.lock(); // Tuinity - allow better concurrency
|
|
List<UserCache.UserCacheEntry> list = Lists.newArrayList();
|
|
List<GameProfile> list1 = Lists.newArrayList(Iterators.limit(this.f.iterator(), i));
|
|
Iterator iterator = list1.iterator();
|
|
@@ -266,6 +279,7 @@ public class UserCache {
|
|
}
|
|
|
|
return list;
|
|
+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency
|
|
}
|
|
|
|
class UserCacheEntry {
|
|
diff --git a/src/main/java/net/minecraft/server/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java
|
|
index 3048ba0081..84858ba392 100644
|
|
--- a/src/main/java/net/minecraft/server/Vec3D.java
|
|
+++ b/src/main/java/net/minecraft/server/Vec3D.java
|
|
@@ -4,7 +4,7 @@ import java.util.EnumSet;
|
|
|
|
public class Vec3D implements IPosition {
|
|
|
|
- public static final Vec3D a = new Vec3D(0.0D, 0.0D, 0.0D);
|
|
+ public static final Vec3D a = new Vec3D(0.0D, 0.0D, 0.0D); public static Vec3D getZeroVector() { return Vec3D.a; } // Tuinity - OBFHELPER
|
|
public final double x;
|
|
public final double y;
|
|
public final double z;
|
|
@@ -61,6 +61,7 @@ public class Vec3D implements IPosition {
|
|
return this.add(-d0, -d1, -d2);
|
|
}
|
|
|
|
+ public final Vec3D add(Vec3D vec3d) { return this.e(vec3d); } // Tuinity - OBFHELPER
|
|
public Vec3D e(Vec3D vec3d) {
|
|
return this.add(vec3d.x, vec3d.y, vec3d.z);
|
|
}
|
|
@@ -109,10 +110,12 @@ public class Vec3D implements IPosition {
|
|
return new Vec3D(this.x * d0, this.y * d1, this.z * d2);
|
|
}
|
|
|
|
+ public final double magnitude() { return this.f(); } // Tuinity - OBFHELPER
|
|
public double f() {
|
|
return (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
|
}
|
|
|
|
+ public final double magnitudeSquared() { return this.g(); } // Tuinity - OBFHELPER
|
|
public double g() {
|
|
return this.x * this.x + this.y * this.y + this.z * this.z;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java
|
|
index 303f6b0953..c1e149f204 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlace.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlace.java
|
|
@@ -155,7 +155,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
data = this.getData(chunkcoordintpair);
|
|
}
|
|
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world,
|
|
- chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY);
|
|
+ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority
|
|
}
|
|
}
|
|
// Paper end
|
|
diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java
|
|
index c2b8c98206..3a98e242e0 100644
|
|
--- a/src/main/java/net/minecraft/server/VoxelShape.java
|
|
+++ b/src/main/java/net/minecraft/server/VoxelShape.java
|
|
@@ -8,11 +8,11 @@ import javax.annotation.Nullable;
|
|
|
|
public abstract class VoxelShape {
|
|
|
|
- protected final VoxelShapeDiscrete a;
|
|
+ protected final VoxelShapeDiscrete a; public final VoxelShapeDiscrete getShape() { return this.a; } // Tuinity - OBFHELPER
|
|
@Nullable
|
|
private VoxelShape[] b;
|
|
|
|
- VoxelShape(VoxelShapeDiscrete voxelshapediscrete) {
|
|
+ protected VoxelShape(VoxelShapeDiscrete voxelshapediscrete) { // Tuinity
|
|
this.a = voxelshapediscrete;
|
|
}
|
|
|
|
@@ -51,6 +51,12 @@ public abstract class VoxelShape {
|
|
return (VoxelShape) (this.isEmpty() ? VoxelShapes.a() : new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2)));
|
|
}
|
|
|
|
+ // Tuinity start - optimise multi-aabb shapes
|
|
+ public boolean intersects(final AxisAlignedBB axisalingedbb) {
|
|
+ return VoxelShapes.applyOperation(this, new com.tuinity.tuinity.voxel.AABBVoxelShape(axisalingedbb), OperatorBoolean.AND);
|
|
+ }
|
|
+ // Tuinity end - optimise multi-aabb shapes
|
|
+
|
|
public VoxelShape c() {
|
|
VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()};
|
|
|
|
@@ -70,6 +76,7 @@ public abstract class VoxelShape {
|
|
}, true);
|
|
}
|
|
|
|
+ public final List<AxisAlignedBB> getBoundingBoxesRepresentation() { return this.d(); } // Tuinity - OBFHELPER
|
|
public List<AxisAlignedBB> d() {
|
|
List<AxisAlignedBB> list = Lists.newArrayList();
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/VoxelShapeArray.java b/src/main/java/net/minecraft/server/VoxelShapeArray.java
|
|
index caf297fe97..8d68c783f6 100644
|
|
--- a/src/main/java/net/minecraft/server/VoxelShapeArray.java
|
|
+++ b/src/main/java/net/minecraft/server/VoxelShapeArray.java
|
|
@@ -3,6 +3,7 @@ package net.minecraft.server;
|
|
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
|
import it.unimi.dsi.fastutil.doubles.DoubleList;
|
|
import java.util.Arrays;
|
|
+import java.util.List;
|
|
|
|
public final class VoxelShapeArray extends VoxelShape {
|
|
|
|
@@ -10,11 +11,25 @@ public final class VoxelShapeArray extends VoxelShape {
|
|
private final DoubleList c;
|
|
private final DoubleList d;
|
|
|
|
+ // Tuinity start - optimise multi-aabb shapes
|
|
+ static final AxisAlignedBB[] EMPTY = new AxisAlignedBB[0];
|
|
+ final AxisAlignedBB[] boundingBoxesRepresentation;
|
|
+
|
|
+ final double offsetX;
|
|
+ final double offsetY;
|
|
+ final double offsetZ;
|
|
+ // Tuinity end - optimise multi-aabb shapes
|
|
+
|
|
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)));
|
|
}
|
|
|
|
VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2) {
|
|
+ // Tuinity start - optimise multi-aabb shapes
|
|
+ this(voxelshapediscrete, doublelist, doublelist1, doublelist2, null, null, 0.0, 0.0, 0.0);
|
|
+ }
|
|
+ VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2, VoxelShapeArray original, AxisAlignedBB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) {
|
|
+ // Tuinity end - optimise multi-aabb shapes
|
|
super(voxelshapediscrete);
|
|
int i = voxelshapediscrete.b() + 1;
|
|
int j = voxelshapediscrete.c() + 1;
|
|
@@ -27,6 +42,18 @@ public final class VoxelShapeArray extends VoxelShape {
|
|
} else {
|
|
throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."));
|
|
}
|
|
+ // Tuinity start - optimise multi-aabb shapes
|
|
+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.getBoundingBoxesRepresentation().toArray(EMPTY) : boundingBoxesRepresentation; // Tuinity - optimise multi-aabb shapes
|
|
+ if (original == null) {
|
|
+ this.offsetX = offsetX;
|
|
+ this.offsetY = offsetY;
|
|
+ this.offsetZ = offsetZ;
|
|
+ } else {
|
|
+ this.offsetX = offsetX + original.offsetX;
|
|
+ this.offsetY = offsetY + original.offsetY;
|
|
+ this.offsetZ = offsetZ + original.offsetZ;
|
|
+ }
|
|
+ // Tuinity end - optimise multi-aabb shapes
|
|
}
|
|
|
|
@Override
|
|
@@ -42,4 +69,49 @@ public final class VoxelShapeArray extends VoxelShape {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
+
|
|
+ // Tuinity start - optimise multi-aabb shapes
|
|
+ @Override
|
|
+ public VoxelShape a(double d0, double d1, double d2) {
|
|
+ if (this == VoxelShapes.getEmptyShape() || this.boundingBoxesRepresentation.length == 0) {
|
|
+ return this;
|
|
+ }
|
|
+ return new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2), this, this.boundingBoxesRepresentation, d0, d1, d2);
|
|
+ }
|
|
+
|
|
+ public final AxisAlignedBB[] getBoundingBoxesRepresentationRaw() {
|
|
+ return this.boundingBoxesRepresentation;
|
|
+ }
|
|
+
|
|
+ public final double getOffsetX() {
|
|
+ return this.offsetX;
|
|
+ }
|
|
+
|
|
+ public final double getOffsetY() {
|
|
+ return this.offsetY;
|
|
+ }
|
|
+
|
|
+ public final double getOffsetZ() {
|
|
+ return this.offsetZ;
|
|
+ }
|
|
+
|
|
+ public final boolean intersects(AxisAlignedBB axisalingedbb) {
|
|
+ double minX = axisalingedbb.minX - this.offsetX;
|
|
+ double maxX = axisalingedbb.maxX - this.offsetX;
|
|
+ double minY = axisalingedbb.minY - this.offsetY;
|
|
+ double maxY = axisalingedbb.maxY - this.offsetY;
|
|
+ double minZ = axisalingedbb.minZ - this.offsetZ;
|
|
+ double maxZ = axisalingedbb.maxZ - this.offsetZ;
|
|
+
|
|
+ // this can be optimised by checking an "overall shape"
|
|
+
|
|
+ for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) {
|
|
+ if (boundingBox.intersects(minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+ // Tuinity end - optimise multi-aabb shapes
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
index 1fa7061f7a..52aee91d2d 100644
|
|
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
@@ -17,18 +17,81 @@ public final class VoxelShapes {
|
|
|
|
voxelshapebitset.a(0, 0, 0, true, true);
|
|
return new VoxelShapeCube(voxelshapebitset);
|
|
- });
|
|
+ }); public static final VoxelShape getFullUnoptimisedCube() { return VoxelShapes.b; } // Tuinity - OBFHELPER
|
|
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 DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); static final VoxelShape getEmptyShape() { return VoxelShapes.c; } // Tuinity - OBFHELPER
|
|
+
|
|
+ // Tuinity start - optimise voxelshapes
|
|
+ public static boolean isEmpty(VoxelShape voxelshape) {
|
|
+ // helper function for determining empty shapes fast
|
|
+ return voxelshape == getEmptyShape() || voxelshape.isEmpty();
|
|
+ }
|
|
+ // Tuinity end - optimise voxelshapes
|
|
|
|
public static final VoxelShape empty() {return a();} // Paper - OBFHELPER
|
|
public static VoxelShape a() {
|
|
return VoxelShapes.c;
|
|
}
|
|
|
|
+ static final com.tuinity.tuinity.voxel.AABBVoxelShape optimisedFullCube = new com.tuinity.tuinity.voxel.AABBVoxelShape(new AxisAlignedBB(0, 0, 0, 1.0, 1.0, 1.0)); // Tuinity - optimise voxelshape
|
|
+
|
|
+ // Tuinity start - optimise voxelshapes
|
|
+ public static void addBoxesToIfIntersects(VoxelShape shape, AxisAlignedBB aabb, java.util.List<AxisAlignedBB> list) {
|
|
+ if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) {
|
|
+ com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape;
|
|
+ if (shapeCasted.aabb.intersects(aabb)) {
|
|
+ list.add(shapeCasted.aabb);
|
|
+ }
|
|
+ } else if (shape instanceof VoxelShapeArray) {
|
|
+ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape;
|
|
+ double minX = aabb.minX - shapeCasted.offsetX;
|
|
+ double maxX = aabb.maxX - shapeCasted.offsetX;
|
|
+ double minY = aabb.minY - shapeCasted.offsetY;
|
|
+ double maxY = aabb.maxY - shapeCasted.offsetY;
|
|
+ double minZ = aabb.minZ - shapeCasted.offsetZ;
|
|
+ double maxZ = aabb.maxZ - shapeCasted.offsetZ;
|
|
+
|
|
+ // this can be optimised by checking an "overall shape"
|
|
+
|
|
+ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) {
|
|
+ if (boundingBox.intersects(minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
+ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ));
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ java.util.List<AxisAlignedBB> boxes = shape.getBoundingBoxesRepresentation();
|
|
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
|
|
+ AxisAlignedBB box = boxes.get(i);
|
|
+ if (box.intersects(aabb)) {
|
|
+ list.add(box);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void addBoxesTo(VoxelShape shape, java.util.List<AxisAlignedBB> list) {
|
|
+ if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) {
|
|
+ com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape;
|
|
+ list.add(shapeCasted.aabb);
|
|
+ } else if (shape instanceof VoxelShapeArray) {
|
|
+ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape;
|
|
+
|
|
+ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) {
|
|
+ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ));
|
|
+ }
|
|
+ } else {
|
|
+ java.util.List<AxisAlignedBB> boxes = shape.getBoundingBoxesRepresentation();
|
|
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
|
|
+ AxisAlignedBB box = boxes.get(i);
|
|
+ list.add(box);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise voxelshapes
|
|
+
|
|
public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER
|
|
public static VoxelShape b() {
|
|
- return VoxelShapes.b;
|
|
+ return VoxelShapes.optimisedFullCube; // Tuinity - optimise voxelshape
|
|
}
|
|
|
|
public static VoxelShape create(double d0, double d1, double d2, double d3, double d4, double d5) {
|
|
@@ -67,7 +130,7 @@ public final class VoxelShapes {
|
|
return new VoxelShapeCube(voxelshapebitset);
|
|
}
|
|
} else {
|
|
- return new VoxelShapeArray(VoxelShapes.b.a, new double[]{axisalignedbb.minX, axisalignedbb.maxX}, new double[]{axisalignedbb.minY, axisalignedbb.maxY}, new double[]{axisalignedbb.minZ, axisalignedbb.maxZ});
|
|
+ return new com.tuinity.tuinity.voxel.AABBVoxelShape(axisalignedbb); // Tuinity - optimise VoxelShapes for single AABB shapes
|
|
}
|
|
}
|
|
|
|
@@ -132,6 +195,20 @@ public final class VoxelShapes {
|
|
|
|
public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { return VoxelShapes.c(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER
|
|
public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) {
|
|
+ // Tuinity start - optimise voxelshape
|
|
+ if (operatorboolean == OperatorBoolean.AND) {
|
|
+ if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) {
|
|
+ return ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb.intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb);
|
|
+ } else if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof VoxelShapeArray) {
|
|
+ return ((VoxelShapeArray)voxelshape1).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb);
|
|
+ } else if (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape instanceof VoxelShapeArray) {
|
|
+ return ((VoxelShapeArray)voxelshape).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb);
|
|
+ }
|
|
+ }
|
|
+ return abstract_c(voxelshape, voxelshape1, operatorboolean);
|
|
+ }
|
|
+ public static boolean abstract_c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) {
|
|
+ // Tuinity end - optimise voxelshape
|
|
if (operatorboolean.apply(false, false)) {
|
|
throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException());
|
|
} else if (voxelshape == voxelshape1) {
|
|
@@ -314,6 +391,7 @@ public final class VoxelShapes {
|
|
}
|
|
}
|
|
|
|
+ public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER
|
|
public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) {
|
|
return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
|
|
index a173d2562e..21f34b82f0 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -94,6 +94,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;
|
|
@@ -121,6 +123,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot
|
|
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig((((WorldDataServer)worlddatamutable).getName()), this.spigotConfig); // Paper
|
|
this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
|
|
+ this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config
|
|
this.generator = gen;
|
|
this.world = new CraftWorld((WorldServer) this, gen, env);
|
|
this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
|
|
@@ -397,13 +400,38 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
}
|
|
}
|
|
|
|
+ // Tuinity start
|
|
+ // Does not affect TE. This simply just a raw set type - runs no logic.
|
|
+ final void setTypeAndDataRaw(final BlockPosition pos, final IBlockData blockData, final TileEntity tileEntity) {
|
|
+ this.getChunkAt(pos.getX() >> 4, pos.getZ() >> 4).setTypeAndDataRaw(pos, blockData);
|
|
+ if (tileEntity == null) {
|
|
+ this.removeTileEntity(pos);
|
|
+ } else {
|
|
+ this.setTileEntity(pos, tileEntity);
|
|
+ }
|
|
+ ((WorldServer)this).getChunkProvider().flagDirty(pos);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
@Override
|
|
public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) {
|
|
- return this.a(blockposition, iblockdata, i, 512);
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ return this.setTypeAndData(blockposition, iblockdata, i, null);
|
|
+ }
|
|
+ // Up to the caller to handle previous tile state.
|
|
+ public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i, TileEntity tileEntity) {
|
|
+ return this.setTypeAndData(blockposition, iblockdata, i, 512, tileEntity);
|
|
+ // Tuinity end - add tileEntity parameter
|
|
}
|
|
|
|
@Override
|
|
public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) {
|
|
+ // Tuinity start - add tileEntity parameter
|
|
+ return this.setTypeAndData(blockposition, iblockdata, i, j, null);
|
|
+ }
|
|
+ public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i, int j, TileEntity tileEntity) {
|
|
+ // Tuinity end - add tileEntity parameter
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async set type call"); // Tuinity
|
|
// CraftBukkit start - tree generation
|
|
if (this.captureTreeGeneration) {
|
|
// Paper start
|
|
@@ -436,7 +464,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
}
|
|
// CraftBukkit end
|
|
|
|
- IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0); // CraftBukkit custom NO_PLACE flag
|
|
+ IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0, tileEntity); // CraftBukkit custom NO_PLACE flag // Tuinity - add tileEntity parameter
|
|
this.chunkPacketBlockController.onBlockChange(this, blockposition, iblockdata, iblockdata1, i); // Paper - Anti-Xray
|
|
|
|
if (iblockdata1 == null) {
|
|
@@ -504,6 +532,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, int j) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity
|
|
IBlockData iblockdata = newBlock;
|
|
IBlockData iblockdata1 = oldBlock;
|
|
IBlockData iblockdata2 = actualBlock;
|
|
@@ -936,6 +965,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
return;
|
|
// Paper end
|
|
}
|
|
+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
|
|
}
|
|
// Paper start - Prevent armor stands from doing entity lookups
|
|
@Override
|
|
@@ -1031,7 +1061,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
while (iterator.hasNext()) {
|
|
TileEntity tileentity1 = (TileEntity) iterator.next();
|
|
|
|
- if (tileentity1.getPosition().equals(blockposition)) {
|
|
+ if (tileentity != tileentity1 && tileentity1.getPosition().equals(blockposition)) { // Tuinity - don't remove us if we double set...
|
|
tileentity1.an_();
|
|
iterator.remove();
|
|
}
|
|
@@ -1120,6 +1150,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
|
|
// copied from below
|
|
List<Entity> list = Lists.newArrayList();
|
|
+ // Tuinity start - add list parameter
|
|
+ return this.getHardCollidingEntities(entity, axisalignedbb, predicate, list);
|
|
+ }
|
|
+ public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate, List<Entity> list) {
|
|
+ // Tuinity end - add list parameter
|
|
int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D);
|
|
int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D);
|
|
int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D);
|
|
@@ -1143,8 +1178,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
|
|
@Override
|
|
public List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<? super Entity> predicate) {
|
|
- this.getMethodProfiler().c("getEntities");
|
|
+ // Tuinity start - add list parameter
|
|
List<Entity> list = Lists.newArrayList();
|
|
+ return this.getEntities(entity, axisalignedbb, predicate, list);
|
|
+ }
|
|
+ public List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<? super Entity> predicate, List<Entity> list) {
|
|
+ // Tuinity end - add list parameter
|
|
+ this.getMethodProfiler().c("getEntities");
|
|
int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D);
|
|
int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D);
|
|
int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D);
|
|
diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
index b651eb87bb..5cba3b0e61 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldBorder.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
@@ -47,11 +47,43 @@ public class WorldBorder {
|
|
return axisalignedbb.maxX > this.e() && axisalignedbb.minX < this.g() && axisalignedbb.maxZ > this.f() && axisalignedbb.minZ < this.h();
|
|
}
|
|
|
|
+ // Tuinity start - optimise collisions
|
|
+ // determines whether we are colliding with one of the wordborder faces.
|
|
+ public final boolean isCollidingOnBorderEdge(AxisAlignedBB boundingBox) {
|
|
+ return this.isCollidingOnBorderEdge(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
|
|
+ }
|
|
+
|
|
+ public final boolean isCollidingOnBorderEdge(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) {
|
|
+ double minX = this.getMinX() - 1.0E-7;
|
|
+ double maxX = this.getMaxX() + 1.0E-7;
|
|
+
|
|
+ double minZ = this.getMinZ() - 1.0E-7;
|
|
+ double maxZ = this.getMaxZ() + 1.0E-7;
|
|
+
|
|
+ return
|
|
+ // First, check if the worldborder is enclosing the specified box.
|
|
+ // We check this first as it's most likely to fail.
|
|
+ !(minX < boxMinX && maxX > boxMaxX && minZ < boxMinZ && maxZ > boxMaxZ)
|
|
+ &&
|
|
+
|
|
+ // Now we verify if we're even intersecting.
|
|
+ (minX < boxMaxX && maxX > boxMinX && minZ < boxMaxZ && maxZ > boxMinZ)
|
|
+ &&
|
|
+
|
|
+ // Now verify that the worldborder isn't being enclosed.
|
|
+ // This is never expected to happen, but is left here to ensure our logic
|
|
+ // is right 100% of the time.
|
|
+ !(boxMinX < minX && boxMaxX > maxX && boxMinZ < minZ && boxMaxZ > maxZ)
|
|
+ ;
|
|
+ }
|
|
+ // Tuinity end - optimise collisions
|
|
+
|
|
public double a(Entity entity) {
|
|
return this.b(entity.locX(), entity.locZ());
|
|
}
|
|
-
|
|
+
|
|
public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER
|
|
+ public final VoxelShape getCollisionShape() { return this.c(); } // Tuinity - OBFHELPER
|
|
public VoxelShape c() {
|
|
return this.j.m();
|
|
}
|
|
@@ -67,18 +99,22 @@ public class WorldBorder {
|
|
return Math.min(d6, d3);
|
|
}
|
|
|
|
+ public final double getMinX() { return this.e(); } // Tuinity - OBFHELPER
|
|
public double e() {
|
|
return this.j.a();
|
|
}
|
|
|
|
+ public final double getMinZ() { return this.f(); } // Tuinity - OBFHELPER
|
|
public double f() {
|
|
return this.j.c();
|
|
}
|
|
|
|
+ public final double getMaxX() { return this.g(); } // Tuinity - OBFHELPER
|
|
public double g() {
|
|
return this.j.b();
|
|
}
|
|
|
|
+ public final double getMaxZ() { return this.h(); } // Tuinity - OBFHELPER
|
|
public double h() {
|
|
return this.j.d();
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index 63c1190729..2bee2dab9f 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 implements GeneratorAccessSeed {
|
|
|
|
public static final BlockPosition a = new BlockPosition(100, 50, 0);
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- public final Int2ObjectMap<Entity> entitiesById = new Int2ObjectLinkedOpenHashMap();
|
|
+ public final Int2ObjectMap<Entity> entitiesById = new Int2ObjectLinkedOpenHashMap(); final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entitiesForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2); // Tuinity - make removing entities while ticking safe
|
|
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
|
|
@@ -79,7 +79,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
private final PortalTravelAgent portalTravelAgent;
|
|
private final TickListServer<Block> nextTickListBlock;
|
|
private final TickListServer<FluidType> nextTickListFluid;
|
|
- private final Set<NavigationAbstract> navigators;
|
|
+ private final Set<NavigationAbstract> navigators; final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigatorsForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2); // Tuinity - make removing entities while ticking safe
|
|
protected final PersistentRaid persistentRaid;
|
|
private final ObjectLinkedOpenHashSet<BlockActionData> L;
|
|
private boolean ticking;
|
|
@@ -200,6 +200,100 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
// Paper end - rewrite ticklistserver
|
|
|
|
+ // Tuinity start
|
|
+ public final boolean areChunksLoadedForMove(AxisAlignedBB axisalignedbb) {
|
|
+ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
|
|
+ // ICollisionAccess methods for VoxelShapes)
|
|
+ // be more strict too, add a block (dumb plugins in move events?)
|
|
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3;
|
|
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
|
|
+
|
|
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
|
|
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ChunkProviderServer chunkProvider = this.getChunkProvider();
|
|
+
|
|
+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
|
+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
|
+ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final void loadChunksForMoveAsync(AxisAlignedBB axisalignedbb, double toX, double toZ,
|
|
+ java.util.function.Consumer<List<IChunkAccess>> onLoad) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ this.getChunkProvider().serverThreadQueue.execute(() -> {
|
|
+ this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ List<IChunkAccess> ret = new java.util.ArrayList<>();
|
|
+ it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList();
|
|
+
|
|
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3;
|
|
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
|
|
+
|
|
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
|
|
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ChunkProviderServer chunkProvider = this.getChunkProvider();
|
|
+
|
|
+ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
|
|
+ int[] loadedChunks = new int[1];
|
|
+
|
|
+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
|
|
+
|
|
+ java.util.function.Consumer<IChunkAccess> consumer = (IChunkAccess chunk) -> {
|
|
+ if (chunk != null) {
|
|
+ int ticketLevel = Math.max(33, chunkProvider.playerChunkMap.getUpdatingChunk(chunk.getPos().pair()).getTicketLevel());
|
|
+ ret.add(chunk);
|
|
+ ticketLevels.add(ticketLevel);
|
|
+ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
|
|
+ }
|
|
+ if (++loadedChunks[0] == requiredChunks) {
|
|
+ try {
|
|
+ onLoad.accept(java.util.Collections.unmodifiableList(ret));
|
|
+ } finally {
|
|
+ for (int i = 0, len = ret.size(); i < len; ++i) {
|
|
+ ChunkCoordIntPair chunkPos = ret.get(i).getPos();
|
|
+ int ticketLevel = ticketLevels.getInt(i);
|
|
+
|
|
+ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
|
|
+ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
|
+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
|
+ chunkProvider.getChunkAtAsynchronously(cx, cz, ChunkStatus.FULL, true, false, consumer);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
+ // Tuinity start - execute chunk tasks mid tick
|
|
+ long lastMidTickExecuteFailure;
|
|
+ // Tuinity end - execute chunk tasks mid tick
|
|
+
|
|
// Add env and gen to constructor, WorldData -> WorldDataServer
|
|
public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey<World> resourcekey, ResourceKey<DimensionManager> resourcekey1, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<MobSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
|
|
super(iworlddataserver, resourcekey, resourcekey1, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor
|
|
@@ -260,6 +354,349 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper
|
|
}
|
|
|
|
+ // Tuinity start - optimise collision
|
|
+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) {
|
|
+ if (entity != null) {
|
|
+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1;
|
|
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1;
|
|
+
|
|
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1;
|
|
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + 1.0E-7D) + 1;
|
|
+
|
|
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 1;
|
|
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 1;
|
|
+
|
|
+
|
|
+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition();
|
|
+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy
|
|
+
|
|
+ // special cases:
|
|
+ if (minBlockY > 255 || maxBlockY < 0) {
|
|
+ // no point in checking
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ int minYIterate = Math.max(0, minBlockY);
|
|
+ int maxYIterate = Math.min(255, maxBlockY);
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider;
|
|
+ // TODO special case single chunk?
|
|
+
|
|
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
|
|
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
|
|
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
|
|
+
|
|
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
|
|
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
|
|
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
|
|
+
|
|
+ int chunkXGlobalPos = currChunkX << 4;
|
|
+ int chunkZGlobalPos = currChunkZ << 4;
|
|
+ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ ChunkSection[] sections = chunk.getSections();
|
|
+
|
|
+ // bound y
|
|
+
|
|
+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
|
|
+ ChunkSection section = sections[currY >>> 4];
|
|
+ if (section == null || section.isFullOfAir()) {
|
|
+ // empty
|
|
+ // skip to next section
|
|
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ DataPaletteBlock<IBlockData> blocks = section.blockIds;
|
|
+ int blockKeyY = (currY & 15) << 8;
|
|
+
|
|
+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0;
|
|
+
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ int blockKeyXY = blockKeyY | currX;
|
|
+ int blockX = currX | chunkXGlobalPos; // world position
|
|
+
|
|
+ int edgeCountXY;
|
|
+ if (blockX == minBlockX || blockX == maxBlockX) {
|
|
+ edgeCountXY = edgeCountY + 1;
|
|
+ } else {
|
|
+ edgeCountXY = edgeCountY;
|
|
+ }
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ int blockZ = currZ | chunkZGlobalPos; // world position
|
|
+
|
|
+ int edgeCountFull;
|
|
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
|
|
+ edgeCountFull = edgeCountXY + 1;
|
|
+ } else {
|
|
+ edgeCountFull = edgeCountXY;
|
|
+ }
|
|
+
|
|
+ if (edgeCountFull == 3) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ int blockKeyFull = blockKeyXY | (currZ << 4);
|
|
+ IBlockData blockData = blocks.rawGet(blockKeyFull);
|
|
+
|
|
+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
|
|
+ mutablePos.setValues(blockX, currY, blockZ);
|
|
+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape);
|
|
+ if (voxelshape2 != VoxelShapes.getEmptyShape()) {
|
|
+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ);
|
|
+
|
|
+ if (voxelshape3.intersects(axisalignedbb)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public final boolean hardCollidesWithAnyEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<Entity> predicate) {
|
|
+ if (axisalignedbb.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+ AxisAlignedBB axisalignedbb1 = axisalignedbb.grow(1.0E-7D, 1.0E-7D, 1.0E-7D);
|
|
+ List<Entity> entities = (entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb, predicate == null ? IEntitySelector.notSpectator() : predicate.and(IEntitySelector.notSpectator())) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate);
|
|
+
|
|
+ for (int i = 0, len = entities.size(); i < len; ++i) {
|
|
+ Entity otherEntity = entities.get(i);
|
|
+
|
|
+ if (predicate != null && !predicate.test(otherEntity)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (entity != null) {
|
|
+ if (entity.isSameVehicle(otherEntity)) {
|
|
+ continue;
|
|
+ }
|
|
+ AxisAlignedBB hardCollisionBox = entity.getHardCollisionBoxWith(otherEntity);
|
|
+ if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ AxisAlignedBB hardCollisionBox = otherEntity.getHardCollisionBox();
|
|
+
|
|
+ if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
|
|
+ return this.hasAnyCollisions(entity, axisalignedbb, true);
|
|
+ }
|
|
+
|
|
+ public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) {
|
|
+ return this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, loadChunks) || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null);
|
|
+ }
|
|
+
|
|
+ // Tuinity start - optimise collision
|
|
+ public void getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> list, boolean loadChunks) {
|
|
+ if (entity != null) {
|
|
+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) {
|
|
+ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1;
|
|
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1;
|
|
+
|
|
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1;
|
|
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + 1.0E-7D) + 1;
|
|
+
|
|
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 1;
|
|
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 1;
|
|
+
|
|
+
|
|
+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition();
|
|
+ VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy
|
|
+
|
|
+ // special cases:
|
|
+ if (minBlockY > 255 || maxBlockY < 0) {
|
|
+ // no point in checking
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ int minYIterate = Math.max(0, minBlockY);
|
|
+ int maxYIterate = Math.min(255, maxBlockY);
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider;
|
|
+ // TODO special case single chunk?
|
|
+
|
|
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
|
|
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
|
|
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
|
|
+
|
|
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
|
|
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
|
|
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
|
|
+
|
|
+ int chunkXGlobalPos = currChunkX << 4;
|
|
+ int chunkZGlobalPos = currChunkZ << 4;
|
|
+ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ));
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ChunkSection[] sections = chunk.getSections();
|
|
+
|
|
+ // bound y
|
|
+
|
|
+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
|
|
+ ChunkSection section = sections[currY >>> 4];
|
|
+ if (section == null || section.isFullOfAir()) {
|
|
+ // empty
|
|
+ // skip to next section
|
|
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ DataPaletteBlock<IBlockData> blocks = section.blockIds;
|
|
+ int blockKeyY = (currY & 15) << 8;
|
|
+
|
|
+ int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0;
|
|
+
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ int blockKeyXY = blockKeyY | currX;
|
|
+ int blockX = currX | chunkXGlobalPos; // world position
|
|
+
|
|
+ int edgeCountXY;
|
|
+ if (blockX == minBlockX || blockX == maxBlockX) {
|
|
+ edgeCountXY = edgeCountY + 1;
|
|
+ } else {
|
|
+ edgeCountXY = edgeCountY;
|
|
+ }
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ int blockZ = currZ | chunkZGlobalPos; // world position
|
|
+
|
|
+ int edgeCountFull;
|
|
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
|
|
+ edgeCountFull = edgeCountXY + 1;
|
|
+ } else {
|
|
+ edgeCountFull = edgeCountXY;
|
|
+ }
|
|
+
|
|
+ if (edgeCountFull == 3) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ int blockKeyFull = blockKeyXY | (currZ << 4);
|
|
+ IBlockData blockData = blocks.rawGet(blockKeyFull);
|
|
+
|
|
+ if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
|
|
+ mutablePos.setValues(blockX, currY, blockZ);
|
|
+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape);
|
|
+ if (voxelshape2 != VoxelShapes.getEmptyShape()) {
|
|
+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ);
|
|
+
|
|
+ VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final void getEntityHardCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<Entity> predicate, List<AxisAlignedBB> list) {
|
|
+ if (axisalignedbb.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ AxisAlignedBB axisalignedbb1 = axisalignedbb.grow(1.0E-7D, 1.0E-7D, 1.0E-7D);
|
|
+ List<Entity> entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
|
|
+ try {
|
|
+ if (entity != null && entity.hardCollides()) {
|
|
+ this.getEntities(entity, axisalignedbb, predicate == null ? IEntitySelector.notSpectator() : predicate.and(IEntitySelector.notSpectator()), entities);
|
|
+ } else {
|
|
+ this.getHardCollidingEntities(entity, axisalignedbb1, predicate, entities);
|
|
+ }
|
|
+
|
|
+ for (int i = 0, len = entities.size(); i < len; ++i) {
|
|
+ Entity otherEntity = entities.get(i);
|
|
+
|
|
+ if (predicate != null && !predicate.test(otherEntity)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (entity != null) {
|
|
+ if (entity.isSameVehicle(otherEntity)) {
|
|
+ continue;
|
|
+ }
|
|
+ AxisAlignedBB hardCollisionBox = entity.getHardCollisionBoxWith(otherEntity);
|
|
+ if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) {
|
|
+ list.add(hardCollisionBox);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ AxisAlignedBB hardCollisionBox = otherEntity.getHardCollisionBox();
|
|
+
|
|
+ if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) {
|
|
+ list.add(hardCollisionBox);
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final void getCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> list, boolean loadChunks) {
|
|
+ this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks);
|
|
+ this.getEntityHardCollisions(entity, axisalignedbb, null, list);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(Entity entity) {
|
|
+ return !this.hasAnyCollisions(entity, entity.getBoundingBox());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
|
|
+ return !this.hasAnyCollisions(entity, axisalignedbb);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
|
|
+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate);
|
|
+ }
|
|
+ // Tuinity end - optimise collision
|
|
+
|
|
// CraftBukkit start
|
|
@Override
|
|
protected TileEntity getTileEntity(BlockPosition pos, boolean validate) {
|
|
@@ -462,7 +899,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
timings.scheduledBlocks.stopTiming(); // Paper
|
|
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
gameprofilerfiller.exitEnter("raid");
|
|
this.timings.raids.startTiming(); // Paper - timings
|
|
this.persistentRaid.a();
|
|
@@ -471,7 +908,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
timings.doSounds.startTiming(); // Spigot
|
|
this.ah();
|
|
timings.doSounds.stopTiming(); // Spigot
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.ticking = false;
|
|
gameprofilerfiller.exitEnter("entities");
|
|
boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
|
|
@@ -487,13 +924,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
|
|
this.tickingEntities = true;
|
|
- ObjectIterator objectiterator = this.entitiesById.int2ObjectEntrySet().iterator();
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> objectiterator = this.entitiesForIteration.iterator(); // Tuinity
|
|
|
|
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
|
|
timings.entityTick.startTiming(); // Spigot
|
|
while (objectiterator.hasNext()) {
|
|
- Entry<Entity> entry = (Entry) objectiterator.next();
|
|
- Entity entity = (Entity) entry.getValue();
|
|
+ Entity entity = (Entity) objectiterator.next(); // Tuinity
|
|
Entity entity1 = entity.getVehicle();
|
|
|
|
/* CraftBukkit start - We prevent spawning in general, so this butchering is not needed
|
|
@@ -509,6 +945,13 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
gameprofilerfiller.enter("checkDespawn");
|
|
if (!entity.dead) {
|
|
entity.checkDespawn();
|
|
+ // Tuinity start - optimise notify()
|
|
+ if (entity.inChunk && entity.valid) {
|
|
+ this.updateNavigatorsInRegion(entity);
|
|
+ } else {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
}
|
|
|
|
gameprofilerfiller.exit();
|
|
@@ -529,14 +972,20 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
gameprofilerfiller.enter("remove");
|
|
if (entity.dead) {
|
|
this.removeEntityFromChunk(entity);
|
|
- objectiterator.remove();
|
|
+ this.entitiesById.remove(entity.getId()); // Tuinity
|
|
this.unregisterEntity(entity);
|
|
+ } else if (entity.inChunk && entity.valid) { // Tuinity start - optimise notify()
|
|
+ this.updateNavigatorsInRegion(entity);
|
|
+ } else {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
}
|
|
+ // Tuinity end - optimise notify()
|
|
|
|
gameprofilerfiller.exit();
|
|
}
|
|
timings.entityTick.stopTiming(); // Spigot
|
|
|
|
+ objectiterator.finishedIterating(); // Tuinity
|
|
this.tickingEntities = false;
|
|
// Paper start
|
|
for (java.lang.Runnable run : this.afterEntityTickingTasks) {
|
|
@@ -548,7 +997,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
this.afterEntityTickingTasks.clear();
|
|
// Paper end
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
|
|
Entity entity2;
|
|
|
|
@@ -558,7 +1007,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
|
|
timings.tickEntities.stopTiming(); // Spigot
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.tickBlockEntities();
|
|
}
|
|
|
|
@@ -804,7 +1253,26 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
|
|
}
|
|
|
|
+ // Tuinity start - log detailed entity tick information
|
|
+ static final java.util.concurrent.ConcurrentLinkedDeque<Entity> currentlyTickingEntities = new java.util.concurrent.ConcurrentLinkedDeque<>();
|
|
+
|
|
+ public static List<Entity> getCurrentlyTickingEntities() {
|
|
+ List<Entity> ret = Lists.newArrayListWithCapacity(4);
|
|
+
|
|
+ for (Entity entity : currentlyTickingEntities) {
|
|
+ ret.add(entity);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // Tuinity end - log detailed entity tick information
|
|
+
|
|
public void entityJoinedWorld(Entity entity) {
|
|
+ // Tuinity start - log detailed entity tick information
|
|
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
|
|
+ try {
|
|
+ currentlyTickingEntities.push(entity);
|
|
+ // Tuinity end - log detailed entity tick information
|
|
if (!(entity instanceof EntityHuman) && !this.getChunkProvider().a(entity)) {
|
|
this.chunkCheck(entity);
|
|
} else {
|
|
@@ -850,6 +1318,11 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
} // Paper - timings
|
|
|
|
}
|
|
+ // Tuinity start - log detailed entity tick information
|
|
+ } finally {
|
|
+ currentlyTickingEntities.pop();
|
|
+ }
|
|
+ // Tuinity end - log detailed entity tick information
|
|
}
|
|
|
|
public void a(Entity entity, Entity entity1) {
|
|
@@ -893,6 +1366,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
int i = MathHelper.floor(entity.locX() / 16.0D);
|
|
int j = Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior
|
|
int k = MathHelper.floor(entity.locZ() / 16.0D);
|
|
+ // Tuinity start
|
|
+ int oldRegionX = entity.chunkX >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int oldRegionZ = entity.chunkZ >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int newRegionX = i >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int newRegionZ = k >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ // Tuinity end
|
|
|
|
if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) {
|
|
// Paper start - remove entity if its in a chunk more correctly.
|
|
@@ -902,6 +1381,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start
|
|
+ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) {
|
|
this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY);
|
|
}
|
|
@@ -915,6 +1400,11 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
} else {
|
|
this.getChunkAt(i, k).a(entity);
|
|
}
|
|
+ // Tuinity start
|
|
+ if (entity.inChunk && (oldRegionX != newRegionX || oldRegionZ != newRegionZ)) {
|
|
+ this.addNavigatorsIfPathingToRegion(entity);
|
|
+ }
|
|
+ // Tuinity end
|
|
}
|
|
|
|
this.getMethodProfiler().exit();
|
|
@@ -1226,7 +1716,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
Entity entity = (Entity) iterator.next();
|
|
|
|
if (!(entity instanceof EntityPlayer)) {
|
|
- if (this.tickingEntities) {
|
|
+ if (false && this.tickingEntities) { // Tuinity
|
|
throw (IllegalStateException) SystemUtils.c(new IllegalStateException("Removing entity while ticking!"));
|
|
}
|
|
|
|
@@ -1254,6 +1744,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
|
|
public void unregisterEntity(Entity entity) {
|
|
org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
|
|
+ this.entitiesForIteration.remove(entity); // Tuinity
|
|
// Paper start - fix entity registration issues
|
|
if (entity instanceof EntityComplexPart) {
|
|
// Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways
|
|
@@ -1320,17 +1811,108 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
this.getScoreboard().a(entity);
|
|
// CraftBukkit start - SPIGOT-5278
|
|
if (entity instanceof EntityDrowned) {
|
|
- this.navigators.remove(((EntityDrowned) entity).navigationWater);
|
|
- this.navigators.remove(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity start
|
|
+ this.navigators.remove(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationWater);
|
|
+ this.navigators.remove(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity end
|
|
} else
|
|
// CraftBukkit end
|
|
if (entity instanceof EntityInsentient) {
|
|
- this.navigators.remove(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity start
|
|
+ this.navigators.remove(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.remove(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity end
|
|
}
|
|
+ this.removeNavigatorsFromData(entity); // Tuinity - optimise notify()
|
|
new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
|
|
entity.valid = false; // CraftBukkit
|
|
}
|
|
|
|
+ // Tuinity start - optimise notify()
|
|
+ void removeNavigatorsIfNotPathingFromRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (!((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (!((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (!((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void removeNavigatorsFromData(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void addNavigatorsIfPathingToRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void updateNavigatorsInRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationWater);
|
|
+ } else {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationLand);
|
|
+ } else {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityInsentient)entity).getNavigation());
|
|
+ } else {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+
|
|
private void registerEntity(Entity entity) {
|
|
org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
|
|
// Paper start - don't double enqueue entity registration
|
|
@@ -1341,7 +1923,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
return;
|
|
}
|
|
// Paper end
|
|
- if (this.tickingEntities) {
|
|
+ if (false && this.tickingEntities) { // Tuinity
|
|
if (!entity.isQueuedForRegister) { // Paper
|
|
this.entitiesToAdd.add(entity);
|
|
entity.isQueuedForRegister = true; // Paper
|
|
@@ -1349,6 +1931,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
} else {
|
|
entity.isQueuedForRegister = false; // Paper
|
|
this.entitiesById.put(entity.getId(), entity);
|
|
+ this.entitiesForIteration.add(entity); // Tuinity
|
|
if (entity instanceof EntityEnderDragon) {
|
|
EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eK();
|
|
int i = aentitycomplexpart.length;
|
|
@@ -1357,6 +1940,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
EntityComplexPart entitycomplexpart = aentitycomplexpart[j];
|
|
|
|
this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart);
|
|
+ this.entitiesForIteration.add(entitycomplexpart); // Tuinity
|
|
}
|
|
}
|
|
|
|
@@ -1381,12 +1965,16 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
// this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true
|
|
// CraftBukkit start - SPIGOT-5278
|
|
if (entity instanceof EntityDrowned) {
|
|
- this.navigators.add(((EntityDrowned) entity).navigationWater);
|
|
- this.navigators.add(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity start
|
|
+ this.navigators.add(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationWater);
|
|
+ this.navigators.add(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity end
|
|
} else
|
|
// CraftBukkit end
|
|
if (entity instanceof EntityInsentient) {
|
|
- this.navigators.add(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity start
|
|
+ this.navigators.add(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.add(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity end
|
|
}
|
|
entity.valid = true; // CraftBukkit
|
|
this.getChunkProvider().addEntity(entity); // Paper - from above to be below valid=true
|
|
@@ -1402,7 +1990,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
|
|
public void removeEntity(Entity entity) {
|
|
- if (this.tickingEntities) {
|
|
+ if (false && this.tickingEntities) { // Tuinity
|
|
throw (IllegalStateException) SystemUtils.c(new IllegalStateException("Removing entity while ticking!"));
|
|
} else {
|
|
this.removeEntityFromChunk(entity);
|
|
@@ -1503,8 +2091,26 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition);
|
|
|
|
if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) {
|
|
+ // Tuinity start - optimise notify()
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region<PlayerChunkMap.RegionData> region = this.getChunkProvider().playerChunkMap.dataRegionManager.getRegion(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ if (region == null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
|
|
- Iterator iterator = this.navigators.iterator();
|
|
+ // Tuinity start
|
|
+ // Tuinity start - optimise notify()
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData>> sectionIterator = null;
|
|
+ try {
|
|
+ for (sectionIterator = region.getSections(); sectionIterator.hasNext();) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section = sectionIterator.next();
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ if (navigators == null) {
|
|
+ continue;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigators.iterator();
|
|
+ // Tuinity end - optimise notify()
|
|
+ try { // Tuinity end
|
|
|
|
while (iterator.hasNext()) {
|
|
NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next();
|
|
@@ -1512,7 +2118,21 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
if (!navigationabstract.i()) {
|
|
navigationabstract.b(blockposition);
|
|
}
|
|
- }
|
|
+ // Tuinity start - optimise notify()
|
|
+ if (!navigationabstract.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(navigationabstract);
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+ }
|
|
+ } finally { // Tuinity start
|
|
+ iterator.finishedIterating();
|
|
+ } // Tuinity end
|
|
+ } // Tuinity start - optimise notify()
|
|
+ } finally {
|
|
+ if (sectionIterator != null) {
|
|
+ sectionIterator.finishedIterating();
|
|
+ }
|
|
+ } // Tuinity end - optimise notify()
|
|
|
|
this.tickingEntities = wasTicking; // Paper
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
index 5ccdc0b87b..888dae2d5e 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
@@ -218,7 +218,7 @@ public class WorldUpgrader {
|
|
int l = Integer.parseInt(matcher.group(2)) << 5;
|
|
|
|
try {
|
|
- RegionFile regionfile = new RegionFile(file2, file1, true);
|
|
+ RegionFile regionfile = new RegionFile(file2, file1, true, true); // Tuinity - allow for chunk regionfiles to regen header
|
|
Throwable throwable = null;
|
|
|
|
try {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java
|
|
index 4ec53a54e3..31c81b4b52 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java
|
|
@@ -77,7 +77,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot {
|
|
public Material getBlockType(int x, int y, int z) {
|
|
CraftChunk.validateChunkCoordinates(x, y, z);
|
|
|
|
- return CraftMagicNumbers.getMaterial(blockids[y >> 4].a(x, y & 0xF, z).getBlock());
|
|
+ return blockids[y >> 4].a(x, y & 0xF, z).getBukkitMaterial(); // Tuinity - optimise getType calls
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index 6282a05ae8..d434bd93bc 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -231,7 +231,7 @@ import javax.annotation.Nullable; // Paper
|
|
import javax.annotation.Nonnull; // Paper
|
|
|
|
public final class CraftServer implements Server {
|
|
- private final String serverName = "Paper"; // Paper
|
|
+ private final String serverName = "Tuinity"; // Paper // Tuinity
|
|
private final String serverVersion;
|
|
private final String bukkitVersion = Versioning.getBukkitVersion();
|
|
private final Logger logger = Logger.getLogger("Minecraft");
|
|
@@ -848,6 +848,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.worldDataServer.setDifficulty(config.difficulty);
|
|
world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals);
|
|
@@ -882,6 +883,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
|
|
@@ -1828,7 +1830,10 @@ public final class CraftServer implements Server {
|
|
|
|
@Override
|
|
public boolean isPrimaryThread() {
|
|
- return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario
|
|
+ // Tuinity start
|
|
+ final Thread currThread = Thread.currentThread();
|
|
+ return currThread == console.serverThread || currThread instanceof com.tuinity.tuinity.util.TickThread || currThread.equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario
|
|
+ // Tuinity End
|
|
}
|
|
|
|
@Override
|
|
@@ -2238,6 +2243,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 ca0ca5e407..87e634482d 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -336,6 +336,14 @@ public class CraftWorld implements World {
|
|
this.generator = gen;
|
|
|
|
environment = env;
|
|
+
|
|
+ //Tuinity start - per world spawn limits
|
|
+ monsterSpawn = world.tuinityConfig.spawnLimitMonsters;
|
|
+ animalSpawn = world.tuinityConfig.spawnLimitAnimals;
|
|
+ waterAmbientSpawn = world.tuinityConfig.spawnLimitWaterAmbient;
|
|
+ waterAnimalSpawn = world.tuinityConfig.spawnLimitWaterAnimals;
|
|
+ ambientSpawn = world.tuinityConfig.spawnLimitAmbient;
|
|
+ //Tuinity end
|
|
}
|
|
|
|
@Override
|
|
@@ -402,14 +410,7 @@ public class CraftWorld implements World {
|
|
|
|
@Override
|
|
public Chunk getChunkAt(int x, int z) {
|
|
- // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it
|
|
- net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z);
|
|
- if (chunk == null) {
|
|
- addTicket(x, z);
|
|
- chunk = this.world.getChunkProvider().getChunkAt(x, z, true);
|
|
- }
|
|
- return chunk.bukkitChunk;
|
|
- // Paper end
|
|
+ return this.world.getChunkProvider().getChunkAt(x, z, true).bukkitChunk; // Tuinity - revert paper diff
|
|
}
|
|
|
|
// Paper start
|
|
@@ -492,6 +493,7 @@ public class CraftWorld implements World {
|
|
org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
|
|
if (isChunkLoaded(x, z)) {
|
|
world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE); // Paper
|
|
+ ((ChunkMapDistance)world.getChunkProvider().playerChunkMap.chunkDistanceManager).removeTickets(ChunkCoordIntPair.pair(x, z), TicketType.DELAYED_UNLOAD); // Tuinity - delay chunk unloads - let plugins override
|
|
}
|
|
|
|
return true;
|
|
@@ -2541,7 +2543,7 @@ public class CraftWorld implements World {
|
|
}
|
|
return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
|
|
net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null);
|
|
- if (chunk != null) addTicket(x, z); // Paper
|
|
+ if (false && chunk != null) addTicket(x, z); // Paper // Tuinity - revert
|
|
return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk());
|
|
}, MinecraftServer.getServer());
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
index 9dd994f8b1..2cebdb36f1 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
@@ -138,6 +138,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")
|
|
@@ -252,7 +259,7 @@ public class Main {
|
|
if (buildDate.before(deadline.getTime())) {
|
|
// Paper start - This is some stupid bullshit
|
|
System.err.println("*** Warning, you've not updated in a while! ***");
|
|
- System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper
|
|
+ System.err.println("*** Please download a new build ***"); // Paper // Tuinity
|
|
//System.err.println("*** Server will start in 20 seconds ***");
|
|
//Thread.sleep(TimeUnit.SECONDS.toMillis(20));
|
|
// Paper End
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
index 9b0e868f00..ee53060b11 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
@@ -207,7 +207,7 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public Material getType() {
|
|
- return CraftMagicNumbers.getMaterial(world.getType(position).getBlock());
|
|
+ return world.getType(position).getBukkitMaterial(); // Tuinity - optimise getType calls
|
|
}
|
|
|
|
@Override
|
|
@@ -505,15 +505,30 @@ public class CraftBlock implements Block {
|
|
return null;
|
|
}
|
|
|
|
- return Biome.valueOf(IRegistry.BIOME.getKey(base).getKey().toUpperCase(java.util.Locale.ENGLISH));
|
|
+ return base.getBukkitBiome(); // Tuinity - optimise biome conversion
|
|
}
|
|
|
|
+ private static final java.util.EnumMap<Biome, BiomeBase> BUKKIT_BIOME_TO_NMS_CACHE = new java.util.EnumMap<>(Biome.class); // Tuinity - optimise biome conversion
|
|
+
|
|
public static BiomeBase biomeToBiomeBase(Biome bio) {
|
|
if (bio == null) {
|
|
return null;
|
|
}
|
|
|
|
- return IRegistry.BIOME.get(new MinecraftKey(bio.name().toLowerCase(java.util.Locale.ENGLISH)));
|
|
+ // Tuinity start - optimise biome conversion
|
|
+ BiomeBase cached = BUKKIT_BIOME_TO_NMS_CACHE.get(bio);
|
|
+
|
|
+ if (cached != null) {
|
|
+ return cached;
|
|
+ }
|
|
+
|
|
+ BiomeBase ret = IRegistry.BIOME.get(new MinecraftKey(bio.name().toLowerCase(java.util.Locale.ENGLISH)));
|
|
+ synchronized (BUKKIT_BIOME_TO_NMS_CACHE) {
|
|
+ BUKKIT_BIOME_TO_NMS_CACHE.put(bio, ret);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ // Tuinity end - optimise biome conversion
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
|
index 11aa2dc18c..c51c43573f 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
|
@@ -136,7 +136,7 @@ public class CraftBlockState implements BlockState {
|
|
|
|
@Override
|
|
public Material getType() {
|
|
- return CraftMagicNumbers.getMaterial(data.getBlock());
|
|
+ return data.getBukkitMaterial(); // Tuinity - optimise getType calls
|
|
}
|
|
|
|
public void setFlag(int flag) {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
index bbded5671e..9808901532 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
@@ -44,7 +44,7 @@ public class CraftBlockData implements BlockData {
|
|
|
|
@Override
|
|
public Material getMaterial() {
|
|
- return CraftMagicNumbers.getMaterial(state.getBlock());
|
|
+ return state.getBukkitMaterial(); // Tuinity - optimise getType calls
|
|
}
|
|
|
|
public IBlockData getState() {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
index d1df4e5799..6f18a78980 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
@@ -495,27 +495,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
entity.setHeadRotation(yaw);
|
|
}
|
|
|
|
- @Override// Paper start
|
|
- public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
|
|
- net.minecraft.server.PlayerChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().playerChunkMap;
|
|
- java.util.concurrent.CompletableFuture<Boolean> future = new java.util.concurrent.CompletableFuture<>();
|
|
-
|
|
- loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> {
|
|
- net.minecraft.server.ChunkCoordIntPair pair = new net.minecraft.server.ChunkCoordIntPair(chunk.getX(), chunk.getZ());
|
|
- ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, pair, 31, 0);
|
|
- net.minecraft.server.PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pair.pair());
|
|
- if (updatingChunk != null) {
|
|
- return updatingChunk.getEntityTickingFuture();
|
|
- } else {
|
|
- return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle()));
|
|
- }
|
|
- }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> {
|
|
- future.completeExceptionally(ex);
|
|
- return null;
|
|
- });
|
|
- return future;
|
|
- }
|
|
- // Paper end
|
|
+ // Tuinity
|
|
|
|
@Override
|
|
public boolean teleport(Location location) {
|
|
@@ -549,6 +529,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
return true;
|
|
}
|
|
|
|
+ // Tuinity start - implement teleportAsync better
|
|
+ @Override
|
|
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location location, TeleportCause cause) {
|
|
+ Preconditions.checkArgument(location != null, "location");
|
|
+ location.checkFinite();
|
|
+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call.
|
|
+
|
|
+ net.minecraft.server.WorldServer world = ((CraftWorld)locationClone.getWorld()).getHandle();
|
|
+ java.util.concurrent.CompletableFuture<Boolean> ret = new java.util.concurrent.CompletableFuture<>();
|
|
+
|
|
+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> {
|
|
+ net.minecraft.server.ChunkProviderServer chunkProviderServer = world.getChunkProvider();
|
|
+ for (net.minecraft.server.IChunkAccess chunk : list) {
|
|
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
|
|
+ }
|
|
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ try {
|
|
+ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE);
|
|
+ } catch (Throwable throwable) {
|
|
+ if (throwable instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)throwable;
|
|
+ }
|
|
+ ret.completeExceptionally(throwable);
|
|
+ }
|
|
+ });
|
|
+ });
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // Tuinity end - implement teleportAsync better
|
|
+
|
|
@Override
|
|
public boolean teleport(org.bukkit.entity.Entity destination) {
|
|
return teleport(destination.getLocation());
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java
|
|
index 948a59217c..ab43c97e8f 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java
|
|
@@ -73,7 +73,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData {
|
|
|
|
@Override
|
|
public Material getType(int x, int y, int z) {
|
|
- return CraftMagicNumbers.getMaterial(getTypeId(x, y, z).getBlock());
|
|
+ return getTypeId(x, y, z).getBukkitMaterial(); // Tuinity - optimise getType calls
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
|
|
index fd32d1450a..c38e514b00 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
|
|
@@ -25,6 +25,10 @@ class CraftAsyncTask extends CraftTask {
|
|
@Override
|
|
public void run() {
|
|
final Thread thread = Thread.currentThread();
|
|
+ // Tuinity start - name threads according to running plugin
|
|
+ final String nameBefore = thread.getName();
|
|
+ thread.setName(nameBefore + " - " + this.getOwner().getName()); try {
|
|
+ // Tuinity end - name threads according to running plugin
|
|
synchronized (workers) {
|
|
if (getPeriod() == CraftTask.CANCEL) {
|
|
// Never continue running after cancelled.
|
|
@@ -92,6 +96,7 @@ class CraftAsyncTask extends CraftTask {
|
|
}
|
|
}
|
|
}
|
|
+ } finally { thread.setName(nameBefore); } // Tuinity - name worker thread according
|
|
}
|
|
|
|
LinkedList<BukkitWorker> getWorkers() {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
|
|
index ca2be30609..2c57013765 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
|
|
@@ -100,9 +100,18 @@ public final class CraftScoreboardManager implements ScoreboardManager {
|
|
|
|
// CraftBukkit method
|
|
public void getScoreboardScores(IScoreboardCriteria criteria, String name, Consumer<ScoreboardScore> consumer) {
|
|
+ // Tuinity start - add timings for scoreboard search
|
|
+ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily
|
|
+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync();
|
|
+ try {
|
|
+ // Tuinity end - add timings for scoreboard search
|
|
for (CraftScoreboard scoreboard : scoreboards) {
|
|
Scoreboard board = scoreboard.board;
|
|
board.getObjectivesForCriteria(criteria, name, (score) -> consumer.accept(score));
|
|
}
|
|
+ } finally { // Tuinity start - add timings for scoreboard search
|
|
+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync();
|
|
+ }
|
|
+ // Tuinity end - add timings for scoreboard search
|
|
}
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
index f72c13beda..50f855b931 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
@@ -119,6 +119,32 @@ public class UnsafeList<E> extends AbstractList<E> implements List<E>, RandomAcc
|
|
return indexOf(o) >= 0;
|
|
}
|
|
|
|
+ // Tuinity start
|
|
+ protected transient int maxSize;
|
|
+ public void setSize(int size) {
|
|
+ if (this.maxSize < this.size) {
|
|
+ this.maxSize = this.size;
|
|
+ }
|
|
+ this.size = size;
|
|
+ }
|
|
+
|
|
+ public void completeReset() {
|
|
+ if (this.data != null) {
|
|
+ Arrays.fill(this.data, 0, Math.max(this.size, this.maxSize), null);
|
|
+ }
|
|
+ this.size = 0;
|
|
+ this.maxSize = 0;
|
|
+ if (this.iterPool != null) {
|
|
+ for (Iterator temp : this.iterPool) {
|
|
+ if (temp == null) {
|
|
+ continue;
|
|
+ }
|
|
+ ((Itr)temp).valid = false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
@Override
|
|
public void clear() {
|
|
// Create new array to reset memory usage to initial capacity
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
|
|
index 674096cab1..001b1e5197 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
|
|
@@ -11,7 +11,7 @@ public final class Versioning {
|
|
public static String getBukkitVersion() {
|
|
String result = "Unknown-Version";
|
|
|
|
- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.destroystokyo.paper/paper-api/pom.properties");
|
|
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity
|
|
Properties properties = new Properties();
|
|
|
|
if (stream != null) {
|
|
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/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
|
|
index 513c1041c3..4d31090848 100644
|
|
--- a/src/main/java/org/spigotmc/WatchdogThread.java
|
|
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
|
|
@@ -61,6 +61,84 @@ public class WatchdogThread extends Thread
|
|
}
|
|
}
|
|
|
|
+ // Tuinity start - log detailed tick information
|
|
+ private void dumpTickingInfo() {
|
|
+ Logger log = Bukkit.getServer().getLogger();
|
|
+
|
|
+ // ticking entities
|
|
+ for (net.minecraft.server.Entity entity : net.minecraft.server.WorldServer.getCurrentlyTickingEntities()) {
|
|
+ double posX, posY, posZ;
|
|
+ net.minecraft.server.Vec3D mot;
|
|
+ double moveStartX, moveStartY, moveStartZ;
|
|
+ net.minecraft.server.Vec3D moveVec;
|
|
+ synchronized (entity.posLock) {
|
|
+ posX = entity.locX();
|
|
+ posY = entity.locY();
|
|
+ posZ = entity.locZ();
|
|
+ mot = entity.getMot();
|
|
+ moveStartX = entity.getMoveStartX();
|
|
+ moveStartY = entity.getMoveStartY();
|
|
+ moveStartZ = entity.getMoveStartZ();
|
|
+ moveVec = entity.getMoveVector();
|
|
+ }
|
|
+
|
|
+ String entityType = entity.getMinecraftKey().toString();
|
|
+ java.util.UUID entityUUID = entity.getUniqueID();
|
|
+ net.minecraft.server.World world = entity.getWorld();
|
|
+
|
|
+ log.log(Level.SEVERE, "Ticking entity: " + entityType);
|
|
+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
|
|
+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
|
|
+ if (moveVec != null) {
|
|
+ log.log(Level.SEVERE, "Move call information: ");
|
|
+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
|
|
+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString());
|
|
+ }
|
|
+ log.log(Level.SEVERE, "UUID: " + entityUUID);
|
|
+ }
|
|
+
|
|
+ // packet processors
|
|
+ for (net.minecraft.server.PacketListener packetListener : net.minecraft.server.PlayerConnectionUtils.getCurrentPacketProcessors()) {
|
|
+ if (packetListener instanceof net.minecraft.server.PlayerConnection) {
|
|
+ net.minecraft.server.EntityPlayer player = ((net.minecraft.server.PlayerConnection)packetListener).player;
|
|
+ long totalPackets = net.minecraft.server.PlayerConnectionUtils.getTotalProcessedPackets();
|
|
+ if (player == null) {
|
|
+ log.log(Level.SEVERE, "Handling packet for player connection (null player): " + packetListener);
|
|
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
|
|
+ } else {
|
|
+ // exclude velocity, this is set client side... Paper will also warn on high velocity set too
|
|
+ double posX, posY, posZ;
|
|
+ double moveStartX, moveStartY, moveStartZ;
|
|
+ net.minecraft.server.Vec3D moveVec;
|
|
+ synchronized (player.posLock) {
|
|
+ posX = player.locX();
|
|
+ posY = player.locY();
|
|
+ posZ = player.locZ();
|
|
+ moveStartX = player.getMoveStartX();
|
|
+ moveStartY = player.getMoveStartY();
|
|
+ moveStartZ = player.getMoveStartZ();
|
|
+ moveVec = player.getMoveVector();
|
|
+ }
|
|
+
|
|
+ java.util.UUID entityUUID = player.getUniqueID();
|
|
+ net.minecraft.server.World world = player.getWorld();
|
|
+
|
|
+ log.log(Level.SEVERE, "Handling packet for player '" + player.getName() + "', UUID: " + entityUUID);
|
|
+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
|
|
+ if (moveVec != null) {
|
|
+ log.log(Level.SEVERE, "Move call information: ");
|
|
+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
|
|
+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString());
|
|
+ }
|
|
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
|
|
+ }
|
|
+ } else {
|
|
+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - log detailed tick information
|
|
+
|
|
@Override
|
|
public void run()
|
|
{
|
|
@@ -117,6 +195,7 @@ public class WatchdogThread extends Thread
|
|
log.log( Level.SEVERE, "------------------------------" );
|
|
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
|
|
ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
|
|
+ this.dumpTickingInfo(); // Tuinity - log detailed tick information
|
|
dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log );
|
|
log.log( Level.SEVERE, "------------------------------" );
|
|
//
|