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 Tuinity Changes: 4513d90 Fix ghost blocks in ticking view distance
7900 lines
387 KiB
Diff
7900 lines
387 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..e69b7dbc7a 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; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the x coordinate - Also revert the decision to expose set on an immutable type
|
|
+ protected int b; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the y coordinate - Also revert the decision to expose set on an immutable type
|
|
+ protected int e; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the z coordinate - Also revert the decision to expose set on an immutable type
|
|
|
|
// 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..c77f71b6de 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,30 @@ public class BlockPosition extends BaseBlockPosition {
|
|
}
|
|
}
|
|
|
|
- /* // Paper start - comment out useless overrides @Override
|
|
- @Override
|
|
- public void o(int i) {
|
|
- super.o(i);
|
|
+ // Tuinity start
|
|
+ // only expose set on the mutable blockpos
|
|
+ public final void setX(int value) {
|
|
+ ((BaseBlockPosition)this).a = value;
|
|
+ }
|
|
+ public final void setY(int value) {
|
|
+ ((BaseBlockPosition)this).b = value;
|
|
+ }
|
|
+ public final void setZ(int value) {
|
|
+ ((BaseBlockPosition)this).e = value;
|
|
}
|
|
|
|
- @Override
|
|
- public void p(int i) {
|
|
- super.p(i);
|
|
+ public final void o(int i) {
|
|
+ ((BaseBlockPosition)this).a = i; // need cast thanks to name conflict
|
|
+ }
|
|
+
|
|
+ public final void p(int i) {
|
|
+ ((BaseBlockPosition)this).b = i;
|
|
}
|
|
|
|
- public void q(int i) {
|
|
- super.q(i);
|
|
+ public final void q(int i) {
|
|
+ ((BaseBlockPosition)this).e = 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..b87bfb1dee 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);
|
|
|
|
@@ -1174,7 +1211,7 @@ public class Chunk implements IChunkAccess {
|
|
IBlockData iblockdata = this.getType(blockposition);
|
|
IBlockData iblockdata1 = Block.b(iblockdata, (GeneratorAccess) this.world, blockposition);
|
|
|
|
- this.world.setTypeAndData(blockposition, iblockdata1, 20);
|
|
+ this.world.setTypeAndData(blockposition, iblockdata1, 20 | 2); // Tuinity - paper sends chunks before they're ticking ready, so we need to notify here
|
|
}
|
|
|
|
this.n[i].clear();
|
|
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 03a79ff992..d59ef07e1c 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
|
|
@@ -276,6 +276,30 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper end
|
|
|
|
private final java.util.concurrent.ExecutorService lightThread;
|
|
+
|
|
+ // 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
|
|
@@ -442,6 +466,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) {
|
|
@@ -754,6 +781,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 {
|
|
@@ -776,6 +804,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);
|
|
@@ -968,7 +997,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;
|
|
@@ -1002,7 +1031,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());
|
|
@@ -1041,6 +1070,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
|
|
}
|
|
|
|
}
|
|
@@ -1057,6 +1087,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 {
|
|
@@ -1244,7 +1275,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) {
|
|
@@ -1496,6 +1530,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) {
|
|
@@ -1509,6 +1544,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;
|
|
@@ -2035,23 +2071,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 6259ff4a57..ddb56ffd2b 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;
|
|
}
|
|
@@ -975,7 +982,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);
|
|
}
|
|
@@ -999,7 +1006,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);
|
|
@@ -1007,7 +1014,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) {
|
|
@@ -1040,7 +1052,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;
|
|
}
|
|
@@ -1096,6 +1108,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) {
|
|
@@ -1120,7 +1133,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..8c47db51a0 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,41 @@ 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) {
|
|
+ Chunk chunk = this.getChunkAt(pos.getX() >> 4, pos.getZ() >> 4);
|
|
+ IBlockData old = chunk.getType(pos);
|
|
+ chunk.setTypeAndDataRaw(pos, blockData);
|
|
+ if (tileEntity == null) {
|
|
+ this.removeTileEntity(pos);
|
|
+ } else {
|
|
+ this.setTileEntity(pos, tileEntity);
|
|
+ }
|
|
+ ((WorldServer)this).getChunkProvider().flagDirty(pos);
|
|
+ this.notifyPoiChange(pos, old, blockData);
|
|
+ }
|
|
+ // 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 +467,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 +535,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;
|
|
@@ -552,6 +584,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
}
|
|
// CraftBukkit end
|
|
|
|
+ public final void notifyPoiChange(BlockPosition blockposition, IBlockData old, IBlockData neww) { this.a(blockposition, old, neww); } // Tuinity - OBFHELPER
|
|
public void a(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1) {}
|
|
|
|
public boolean setAir(BlockPosition blockposition) { return this.a(blockposition, false); } // Paper - OBFHELPER
|
|
@@ -936,6 +969,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 +1065,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 +1154,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 +1182,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, "------------------------------" );
|
|
//
|