Files
Purpur/patches/server/0001-Tuinity-Server-Changes.patch
William Blake Galbreath 1eaac716d2 Updated Upstream (Paper & Tuinity)
Upstream has released updates that appears to apply and compile correctly

Paper Changes:
2609a118 Updated Upstream (Bukkit/CraftBukkit)

Tuinity Changes:
0850468 Properly ignite entities that stand in fire
2020-11-07 15:02:14 -06:00

9998 lines
496 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
Brand changes
MC-Dev fixes
Util patch
Tuinity Server Config
Multi-Threaded Server Ticking Vanilla
This patch is the vanilla server changes
Currently a placeholder patch.
Multi-Threaded ticking CraftBukkit
These are the changes to CB
Currently a placeholder patch.
Add soft async catcher
Must be enabled via -Dtuinity.strict-thread-checks=true
Delay chunk unloads
Chunk unloads are now delayed by 1s. Specifically, ticket level
reduction is delayed by 1s. This is done to allow players to
teleport and have their pets follow them, as the chunks will no longer
unload or have entity ticking status removed.
It's also targetted to reduce performance regressions when
plugins or edge cases in code do not spam sync loads since chunks
without tickets get unloaded immediately.
Configurable under `delay-chunkunloads-by` in config.
This patch replaces the paper patch as the paper patch only
affects player loaded chunks, when we want to target all
loads.
Attempt to recalculate regionfile header if it is corrupt
Instead of trying to relocate the chunk, which is seems to never
be the correct choice, so we end up duplicating or swapping chunks,
we instead drop the current regionfile header and recalculate -
hoping that at least then we don't swap chunks, and maybe recover
them all.
Lag compensate block breaking
Use time instead of ticks if ticks fall behind
Update version fetcher repo
Sets the target github repo to Tuinity in the version checker. Also disables the jenkins build lookups.
Per World Spawn Limits
Detail more information in watchdog dumps
- Dump position, world, velocity, and uuid for currently ticking entities
- Dump player name, player uuid, position, and world for packet handling
Execute chunk tasks mid-tick
This will help the server load chunks if tick times are high.
Change writes to use NORMAL priority rather than LOW
Should limit build up of I/O tasks, or at least properly
indicate to server owners that I/O is falling behind
Allow controlled flushing for network manager
Only make one flush call when emptying the packet queue too
This patch will be used to optimise out flush calls in later
patches.
Consolidate flush calls for entity tracker packets
Most server packets seem to be sent from here, so try to avoid
expensive flush calls from them.
This change was motivated due to local testing:
- My server spawn has 130 cows in it (for testing a prev. patch)
- Try to let 200 players join spawn
Without this change, I could only get 20 players on before they
all started timing out due to the load put on the Netty I/O threads.
With this change I could get all 200 on at 0ms ping.
(one of the primary issues is that my CPU is kinda trash, and having
4 extra threads at 100% is just too much for it).
So in general this patch should reduce Netty I/O thread load.
Time scoreboard search
Plugins leaking scoreboards will make this very expensive,
let server owners debug it easily
Make CallbackExecutor strict again
The correct fix for double scheduling is to avoid it. The reason
this class is used is because double scheduling causes issues
elsewhere, and it acts as an explicit detector of what double
schedules. Effectively, use the callback executor as a tool of
finding issues rather than hiding these issues.
This patch also reverts incorrect use(s) of the class by paper.
- getChunkFutureAsynchronously
There is no risk at all of recursion. The future is executed on
the chunk provider's thread queue, the same place general plugin
load callbacks are executed on. Forcing the task execution into
the callback executor also prevents the future from catching
any exception thrown from it.
Optimise entity hard collision checking
Very few entities actually hard collide, so store them in their own
entity slices and provide a special getEntites type call just for them.
This reduces entity collision checking impact (in my testing) by 25%
for crammed entities (shove 130 cows into an 8x6 area in one chunk).
Less crammed entities are likely to show significantly less benefit.
Effectively, this patch optimises crammed entity situations.
Improved oversized chunk data packet handling
Now target all TE data, except for TE's that do not have
update packets.
This patch relies upon the improve extra packet handling
patch, as we now use PacketPlayOutMapChunk as an extra packet.
See its patch notes for further details.
Reduce iterator allocation from chunk gen
Replace via iterating over an array
Prevent long map entry creation in light engine
Use fastiterator to prevent it
Highly optimise single and multi-AABB VoxelShapes and collisions
Reduce allocation rate from crammed entities
Optimise chunk tick iteration
Use a dedicated list of entity ticking chunks to reduce the cost
Use entity ticking chunk map for entity tracker
Should bring us back in-line with tracker performance
before the loaded entity list reversion.
Improve paper prevent moving into unloaded chunk check
Check the AABB of the move
Improve async tp to not load chunks when crossing worlds
Fixes an issue where a getCubes call would load neighbouring chunks.
Loads less chunks than paper's implementation
Revert getChunkAt(Async) retaining chunks for long periods of time
Rework PlayerChunk main thread checks
These need to fail instead of continuing, as hiding these errors
the way paper has is just going to allow unexpected reordering
of callbacks.
For example, thanks to this patch incorrect future
completion (completion of the world gen future,
PlayerChunkMap#b(PlayerChunk, ChunkStatus)) was detected and fixed.
Allow Entities to be removed from a world while ticking
Fixes issues like disconnecting players while ticking them, or
issues where teleporting players across worlds while ticking.
Also allows us to run mid tick while ticking entities.
Prevent unload() calls removing tickets for sync loads
Prevent log spam for "Block is water but TE is chest"
Happens when breaking a waterlogged chest.
Fix is to just not validate the TE while the chest is being removed.
Optimise collision checking in player move packet handling
Don't need to do another getCubes call if the move() call
doesn't find any collisions
Optimise heightmap access
HeightMap uses DataBits for storage to reduce the memory footprint
of the underlying heightmap. However, this reduction in memory
footprint comes at the cost of encoding/decoding each access.
So we can make the tradeoff of raw array access by using a char
array internally. For every 100,000 chunks, this will add approximately
80MB overhead (4 heightmaps per full chunk) - which is acceptable.
Manually inline methods in BlockPosition
Separate lookup locking from state access in UserCache
Prevent lookups from stalling simple state access/write calls
Distance manager tick timings
Recently this has been taking up more time, so add a timings to
really figure out how much.
Name craft scheduler threads according to the plugin using them
Provides quick access to culprits running far more threads than
they should be
Fix swamp hut cat generation deadlock
The worldgen thread will attempt to get structure references
via the world's getChunkAt method, which is fine if the gen is
not cancelled - but if the chunk was unloaded, the call will block
indefinitely. Instead of using the world state, we use the already
supplied generatoraccess which will always have the chunk available.
Range check flag dirty calls in PlayerChunk
Simply return.
Optimise tab complete
Some of the toLowerCase calls can be expensive.
Do not allow ticket level changes while unloading playerchunks
Sync loading the chunk at this stage would cause it to load
older data, as well as screwing our region state.
Make sure inlined getChunkAt has inlined logic for loaded chunks
Tux did some profiling some time ago and showed that the
previous getChunkAt method which had inlined logic for loaded
chunks did get inlined, but the standard CPS.getChunkAt
method was not inlined.
Paper recently reverted this optimisation, so it's been reintroduced
here.
Temporarily Revert usage of Region Manager
Has some stability issues.
Add packet limiter config
Example config:
packet-limiter:
kick-message: '&cSent too many packets'
limits:
all:
interval: 7.0
max-packet-rate: 500.0
PacketPlayInAutoRecipe:
interval: 4.0
max-packet-rate: 5.0
action: DROP
all section refers to all incoming packets, the action for all is
hard coded to KICK.
For specific limits, the section name is the class's name,
and an action can be defined: DROP or KICK
If interval or rate are less-than 0, the limit is ignored
Optimise closest entity lookup used by AI goals
Use a special entity slice for tracking entities by class as well
as counts per chunk. This should reduce the number of entities searched.
Optimise EntityInsentient#checkDespawn
Use a distance map to map out close players.
Note that it's important that we cache the distance map value per chunk
since the penalty of a map lookup could outweigh the benefits of
searching less players (as it basically did in the outside range patch).
Remove streams for villager AI
POI searching:
Turns out chaining a lot of streams together has inane amounts of
overheard. Use the good ol iterator method to remove that overhead.
The rest is just standard stream removal.
Also remove streams for poi searching in some zombie pathfinding.
Don't lookup fluid state when raytracing
Just use the iblockdata already retrieved, removes a getType call.
Reduce pathfinder branches
Reduce static path type detection to simple lazy-init fields
Add Velocity natives for encryption and compression
Optimise non-flush packet sending
Places like entity tracking make heavy use of packet sending,
and internally netty will use some very expensive thread wakeup
calls when scheduling.
Thanks to various hacks in ProtocolLib as well as other
plugins, we cannot simply use a queue of packets to group
send on execute. We have to call execute for each packet.
Tux's suggestion here is exactly what was needed - tag
the Runnable indicating it should not make a wakeup call.
Big thanks to Tux for making this possible as I had given
up on this optimisation before he came along.
Locally this patch drops the entity tracker tick by a full 1.5x.
diff --git a/pom.xml b/pom.xml
index 80f1652913..78c2a8bbcc 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"
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.4-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>
@@ -37,6 +37,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
+ <!-- Tuinity start - fix compile issue (cannot see new api) by moving netty include BEFORE server jar -->
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-all</artifactId>
+ <version>4.1.50.Final</version>
+ </dependency>
+ <!-- Tuinity end - fix compile issue (cannot see new api) by moving netty include BEFORE server jar -->
<dependency>
<groupId>io.papermc</groupId>
<artifactId>minecraft-server</artifactId>
@@ -97,11 +104,7 @@
<artifactId>cleaner</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-all</artifactId>
- <version>4.1.50.Final</version>
- </dependency>
+ <!-- Tuinity - fix compile issue (cannot see new api) by moving netty include BEFORE server jar -->
<!-- deprecated API depend -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
@@ -141,6 +144,13 @@
<version>4.8.47</version>
<scope>test</scope>
</dependency>
+ <!-- for optimized protocol handling -->
+ <dependency>
+ <groupId>com.velocitypowered</groupId>
+ <artifactId>velocity-native</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
<repositories>
@@ -165,15 +175,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 884b59d478..68ab5ccb2f 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..255bbd6e48 100644
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
@@ -24,8 +24,8 @@ public class PaperVersionFetcher implements VersionFetcher {
@Nonnull
@Override
public String getVersionMessage(@Nonnull String serverVersion) {
- String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]");
- String updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]);
+ String[] parts = serverVersion.substring("git-Tuinity-".length()).split("[-\\s]"); // Tuinity
+ String updateMessage = getUpdateStatusMessage("Spottedleaf/Tuinity", GITHUB_BRANCH_NAME, parts[0]); // Tuinity
String history = getHistory();
return history != null ? history + "\n" + updateMessage : updateMessage;
@@ -49,13 +49,10 @@ public class PaperVersionFetcher implements VersionFetcher {
private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) {
int distance;
- try {
- int jenkinsBuild = Integer.parseInt(versionInfo);
- distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion());
- } catch (NumberFormatException ignored) {
+ // Tuinity - we don't have jenkins setup
versionInfo = versionInfo.replace("\"", "");
distance = fetchDistanceFromGitHub(repo, branch, versionInfo);
- }
+ // Tuinity - we don't have jenkins setup
switch (distance) {
case -1:
diff --git a/src/main/java/com/destroystokyo/paper/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/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
index 103576715e..e8fdbe7b8d 100644
--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
@@ -590,10 +590,11 @@ public class CommandDispatcher<S> {
final String truncatedInput = fullInput.substring(0, cursor);
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
int i = 0;
+ final String remainingLower = truncatedInput.substring(start).toLowerCase(); // Tuinity
for (final CommandNode<S> node : parent.getChildren()) {
CompletableFuture<Suggestions> future = Suggestions.empty();
try {
- future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start));
+ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start, remainingLower)); // Tuinity
} catch (final CommandSyntaxException ignored) {
}
futures[i++] = future;
diff --git a/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java
index cb993ca102..849686f7b2 100644
--- a/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java
+++ b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java
@@ -34,10 +34,10 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
- if ("true".startsWith(builder.getRemaining().toLowerCase())) {
+ if ("true".startsWith(builder.getRemainingLowercase())) { // Tuinity
builder.suggest("true");
}
- if ("false".startsWith(builder.getRemaining().toLowerCase())) {
+ if ("false".startsWith(builder.getRemainingLowercase())) { // Tuinity
builder.suggest("false");
}
return builder.buildFuture();
diff --git a/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java
index bc0024adb8..0343f6663c 100644
--- a/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java
+++ b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java
@@ -14,9 +14,16 @@ public class SuggestionsBuilder {
private final String input;
private final int start;
private final String remaining;
+ private String remainingLowercase; public final String getRemainingLowercase() { return this.remainingLowercase == null ? this.remainingLowercase = this.remaining.toLowerCase() : this.remainingLowercase; } // Tuinity
private final List<Suggestion> result = new ArrayList<>();
public SuggestionsBuilder(final String input, final int start) {
+ // Tuinity start
+ this(input, start, null);
+ }
+ public SuggestionsBuilder(final String input, final int start, final String remainingLowercase) {
+ this.remainingLowercase = remainingLowercase;
+ // Tuinity end
this.input = input;
this.start = start;
this.remaining = input.substring(start);
diff --git a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java
index 7720578796..e5db29d4ca 100644
--- a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java
+++ b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java
@@ -20,11 +20,11 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
public class LiteralCommandNode<S> extends CommandNode<S> {
- private final String literal;
+ private final String literal; private final String literalLower; // Tuinity
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
super(command, requirement, redirect, modifier, forks);
- this.literal = literal;
+ this.literal = literal; this.literalLower = this.literal.toLowerCase(); // Tuinity
}
public String getLiteral() {
@@ -66,7 +66,7 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
@Override
public CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
- if (literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) {
+ if (literalLower.startsWith(builder.getRemainingLowercase())) { // Tuinity
return builder.suggest(literal).buildFuture();
} else {
return Suggestions.empty();
diff --git a/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java b/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java
new file mode 100644
index 0000000000..37428f4b9a
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java
@@ -0,0 +1,186 @@
+package com.tuinity.tuinity.chunk;
+
+import com.destroystokyo.paper.util.maplist.EntityList;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.Entity;
+import net.minecraft.server.MathHelper;
+import org.spigotmc.AsyncCatcher;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public final class ChunkEntitiesByClass {
+
+ // this class attempts to restore the original intent of nms.EntitySlice and improve upon it:
+ // fast lookups for specific entity types in a chunk. However vanilla does not track things on a
+ // chunk-wide basis, which is very important to our optimisations here: we want to eliminate chunks
+ // before searching multiple slices. We also want to maintain only lists that we need to maintain for memory purposes:
+ // so we have no choice but to lazily initialise mappings of class -> entity.
+ // Typically these are used for entity AI lookups, which means we take a heavy initial cost but ultimately win
+ // since AI lookups happen a lot.
+
+ // This optimisation is only half of the battle with entity AI, we need to be smarter about picking the closest entity.
+ // See World#getClosestEntity
+
+ // aggressively high load factors for each map here + fastutil collections: we want the smallest memory footprint
+ private final ExposedReference2IntOpenHashMap<Class<?>> chunkWideCount = new ExposedReference2IntOpenHashMap<>(4, 0.9f);
+ {
+ this.chunkWideCount.defaultReturnValue(Integer.MIN_VALUE);
+ }
+ private final Reference2ObjectOpenHashMap<Class<?>, ArrayList<Entity>>[] slices = new Reference2ObjectOpenHashMap[16];
+ private final Chunk chunk;
+
+ public ChunkEntitiesByClass(final Chunk chunk) {
+ this.chunk = chunk;
+ }
+
+ public boolean hasEntitiesMaybe(final Class<?> clazz) {
+ final int count = this.chunkWideCount.getInt(clazz);
+ return count == Integer.MIN_VALUE || count > 0;
+ }
+
+ public void addEntity(final Entity entity, final int sectionY) {
+ AsyncCatcher.catchOp("Add entity call");
+ if (this.chunkWideCount.isEmpty()) {
+ return;
+ }
+
+ final Object[] keys = this.chunkWideCount.getKey();
+ final int[] values = this.chunkWideCount.getValue();
+
+ Reference2ObjectOpenHashMap<Class<?>, ArrayList<Entity>> slice = this.slices[sectionY];
+ if (slice == null) {
+ slice = this.slices[sectionY] = new Reference2ObjectOpenHashMap<>(4, 0.9f);
+ }
+
+ for (int i = 0, len = keys.length; i < len; ++i) {
+ final Object _key = keys[i];
+ if (!(_key instanceof Class)) {
+ continue;
+ }
+ final Class<?> key = (Class<?>)_key;
+ if (key.isInstance(entity)) {
+ ++values[i];
+ slice.computeIfAbsent(key, (keyInMap) -> {
+ return new ArrayList<>();
+ }).add(entity);
+ }
+ }
+ }
+
+ public void removeEntity(final Entity entity, final int sectionY) {
+ AsyncCatcher.catchOp("Remove entity call");
+ if (this.chunkWideCount.isEmpty()) {
+ return;
+ }
+
+ final Object[] keys = this.chunkWideCount.getKey();
+ final int[] values = this.chunkWideCount.getValue();
+
+ Reference2ObjectOpenHashMap<Class<?>, ArrayList<Entity>> slice = this.slices[sectionY];
+ if (slice == null) {
+ return; // seriously brain damaged plugins
+ }
+
+ for (int i = 0, len = keys.length; i < len; ++i) {
+ final Object _key = keys[i];
+ if (!(_key instanceof Class)) {
+ continue;
+ }
+ final Class<?> key = (Class<?>)_key;
+ if (key.isInstance(entity)) {
+ --values[i];
+ final ArrayList<Entity> list = slice.get(key);
+ if (list == null) {
+ return; // seriously brain damaged plugins
+ }
+ list.remove(entity);
+ }
+ }
+ }
+
+
+ private void computeClass(final Class<?> clazz) {
+ AsyncCatcher.catchOp("Entity class compute call");
+ int totalCount = 0;
+
+ EntityList entityList = this.chunk.entities;
+ Entity[] entities = entityList.getRawData();
+ for (int i = 0, len = entityList.size(); i < len; ++i) {
+ final Entity entity = entities[i];
+
+ if (clazz.isInstance(entity)) {
+ ++totalCount;
+ Reference2ObjectOpenHashMap<Class<?>, ArrayList<Entity>> slice = this.slices[entity.chunkY];
+ if (slice == null) {
+ slice = this.slices[entity.chunkY] = new Reference2ObjectOpenHashMap<>(4, 0.9f);
+ }
+ slice.computeIfAbsent(clazz, (keyInMap) -> {
+ return new ArrayList<>();
+ }).add(entity);
+ }
+ }
+
+ this.chunkWideCount.put(clazz, totalCount);
+ }
+
+ public void lookupClass(final Class<?> clazz, final Entity entity, final AxisAlignedBB boundingBox, final Predicate<Entity> predicate, final List<Entity> into) {
+ final int count = this.chunkWideCount.getInt(clazz);
+ if (count == Integer.MIN_VALUE) {
+ this.computeClass(clazz);
+ if (this.chunkWideCount.getInt(clazz) <= 0) {
+ return;
+ }
+ } else if (count <= 0) {
+ return;
+ }
+
+ // copied from getEntities
+ int min = MathHelper.floor((boundingBox.minY - 2.0D) / 16.0D);
+ int max = MathHelper.floor((boundingBox.maxY + 2.0D) / 16.0D);
+
+ min = MathHelper.clamp(min, 0, this.slices.length - 1);
+ max = MathHelper.clamp(max, 0, this.slices.length - 1);
+
+ for (int y = min; y <= max; ++y) {
+ final Reference2ObjectOpenHashMap<Class<?>, ArrayList<Entity>> slice = this.slices[y];
+ if (slice == null) {
+ continue;
+ }
+
+ final ArrayList<Entity> entities = slice.get(clazz);
+ if (entities == null) {
+ continue;
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ Entity entity1 = entities.get(i);
+ if (entity1.shouldBeRemoved) continue; // Paper
+
+ if (entity1 != entity && entity1.getBoundingBox().intersects(boundingBox)) {
+ if (predicate == null || predicate.test(entity1)) {
+ into.add(entity1);
+ }
+ }
+ }
+ }
+ }
+
+ static final class ExposedReference2IntOpenHashMap<K> extends Reference2IntOpenHashMap<K> {
+
+ public ExposedReference2IntOpenHashMap(final int expected, final float loadFactor) {
+ super(expected, loadFactor);
+ }
+
+ public Object[] getKey() {
+ return this.key;
+ }
+
+ public int[] getValue() {
+ return this.value;
+ }
+ }
+}
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..4cb10fe69c
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
@@ -0,0 +1,408 @@
+package com.tuinity.tuinity.chunk;
+
+import co.aikar.timings.MinecraftTimings;
+import co.aikar.timings.Timing;
+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.ChunkCoordIntPair;
+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(2, 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> force) {
+ // 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;
+ if (region.dead) {
+ throw new IllegalStateException("Dead region should not be in live region manager state: " + 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 (force == null) {
+ section = this.regionsBySection.computeIfAbsent(sectionKey, this.createRegionIfAbsent);
+ } else {
+ final RegionSection<T> existing = this.regionsBySection.putIfAbsent(sectionKey, force);
+ if (existing != null) {
+ throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() +
+ ", with " + force.toStringWithRegion());
+ }
+
+ section = force;
+ }
+
+ section.region = mergeIntoCandidate;
+ mergeIntoCandidate.sections.add(section);
+
+ return section;
+ }
+
+ public void addChunk(final int chunkX, final int chunkZ) {
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("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.ensureTickThread("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);
+ } else {
+ throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist");
+ }
+ } finally {
+ this.removeChunkTimings.stopTiming();
+ }
+ }
+
+ public void recalculateRegions() {
+ com.tuinity.tuinity.util.TickThread.ensureTickThread("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 {
+ region.markedForRecalc = false;
+ // clear unused regions
+ for (final Iterator<RegionSection<T>> iterator = region.deadSections.iterator(); iterator.hasNext(); ) {
+ final RegionSection<T> deadSection = iterator.next();
+ if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) {
+ throw new IllegalStateException("Cannot remove dead section '" +
+ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " +
+ this.regionsBySection.get(deadSection.regionCoordinate));
+ }
+ if (!region.sections.remove(deadSection)) {
+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection);
+ }
+
+ 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
+ region.dead = true;
+
+ // destroy region state
+ for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ final RegionSection<T> aliveSection = iterator.next();
+ if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) {
+ throw new IllegalStateException("Cannot remove alive section '" +
+ aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " +
+ this.regionsBySection.get(aliveSection.regionCoordinate));
+ }
+ }
+
+ // rebuild regions
+ for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ final RegionSection<T> aliveSection = iterator.next();
+ this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection);
+ }
+ } finally {
+ this.regionRecalculateTimings.stopTiming();
+ }
+ }
+
+ public static final class Region<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
+ protected final IteratorSafeOrderedReferenceSet<RegionSection<T>> sections = new IteratorSafeOrderedReferenceSet<>(true);
+ 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(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS);
+ }
+
+ 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("Source region is dead! Source " + this + ", target " + mergeTarget);
+ } else if (mergeTarget.dead) {
+ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget);
+ }
+ this.dead = true;
+
+ for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ final RegionSection<T> section = iterator.next();
+
+ if (!mergeTarget.sections.add(section)) {
+ throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget);
+ }
+
+ section.region = mergeTarget;
+ }
+
+ for (final RegionSection<T> deadSection : this.deadSections) {
+ if (!this.sections.contains(deadSection)) {
+ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this);
+ }
+ mergeTarget.deadSections.add(deadSection);
+ }
+ }
+
+ protected void markSectionAlive(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 markSectionDead(final RegionSection<T> section) {
+ this.deadSections.add(section);
+ if (!this.markedForRecalc && (this.sections.size() >= this.regionManager.minSectionRecalcCount || this.sections.size() == this.deadSections.size()) && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) {
+ this.regionManager.addToRecalcQueue(this);
+ this.markedForRecalc = true;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder ret = new StringBuilder(128);
+
+ ret.append("Region{");
+ ret.append("dead=").append(this.dead).append(',');
+ ret.append("markedForRecalc=").append(this.markedForRecalc).append(',');
+
+ ret.append("sectionCount=").append(this.sections.size()).append(',');
+ ret.append("sections=[");
+ for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ final RegionSection<T> section = iterator.next();
+ ret.append(section);
+ if (iterator.hasNext()) {
+ ret.append(',');
+ }
+ }
+ ret.append(']');
+
+ ret.append('}');
+ return ret.toString();
+ }
+ }
+
+ 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);
+ }
+
+ private static int getChunkIndex(final int chunkX, final int chunkZ) {
+ return (chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT);
+ }
+
+ protected void addChunk(final int chunkX, final int chunkZ) {
+ final long bitset = this.chunksBitset;
+ final long after = this.chunksBitset = bitset | (1L << getChunkIndex(chunkX, chunkZ));
+ if (after == bitset) {
+ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString());
+ }
+ if (bitset != 0L) {
+ return;
+ }
+ this.region.markSectionAlive(this);
+ }
+
+ protected void removeChunk(final int chunkX, final int chunkZ) {
+ final long before = this.chunksBitset;
+ final long bitset = this.chunksBitset = before & ~(1L << getChunkIndex(chunkX, chunkZ));
+ if (before == bitset) {
+ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString());
+ }
+ if (bitset != 0L) {
+ return;
+ }
+ this.region.markSectionDead(this);
+ }
+
+ @Override
+ public String toString() {
+ return "RegionSection{" +
+ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
+ "chunksBitset=" + Long.toHexString(this.chunksBitset) + "," +
+ "hash=" + this.hashCode() + "," +
+ "}";
+ }
+
+ public String toStringWithRegion() {
+ return "RegionSection{" +
+ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
+ "chunksBitset=" + Long.toHexString(this.chunksBitset) + "," +
+ "hash=" + this.hashCode() + "," +
+ "region=" + this.region + "," +
+ "}";
+ }
+ }
+
+ 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..048139d134
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
@@ -0,0 +1,375 @@
+package com.tuinity.tuinity.config;
+
+import com.destroystokyo.paper.util.SneakyThrow;
+import net.minecraft.server.MinecraftServer;
+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 boolean createWorldSections = true;
+
+ 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);
+ }
+
+ static String getString(final String path, final String dfl) {
+ TuinityConfig.config.addDefault(path, dfl);
+ return TuinityConfig.config.getString(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", 5) * 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 final class PacketLimit {
+ public final double packetLimitInterval;
+ public final double maxPacketRate;
+ public final ViolateAction violateAction;
+
+ public PacketLimit(final double packetLimitInterval, final double maxPacketRate, final ViolateAction violateAction) {
+ this.packetLimitInterval = packetLimitInterval;
+ this.maxPacketRate = maxPacketRate;
+ this.violateAction = violateAction;
+ }
+
+ public static enum ViolateAction {
+ KICK, DROP;
+ }
+ }
+
+ public static String kickMessage;
+ public static PacketLimit allPacketsLimit;
+ public static java.util.Map<Class<? extends net.minecraft.server.Packet<?>>, PacketLimit> packetSpecificLimits = new java.util.HashMap<>();
+
+ private static void packetLimiter() {
+ packetSpecificLimits.clear();
+ kickMessage = org.bukkit.ChatColor.translateAlternateColorCodes('&', TuinityConfig.getString("packet-limiter.kick-message", "&cSent too many packets"));
+ allPacketsLimit = new PacketLimit(
+ TuinityConfig.getDouble("packet-limiter.limits.all.interval", 7.0),
+ TuinityConfig.getDouble("packet-limiter.limits.all.max-packet-rate", 500.0),
+ PacketLimit.ViolateAction.KICK
+ );
+ if (allPacketsLimit.maxPacketRate <= 0.0 || allPacketsLimit.packetLimitInterval <= 0.0) {
+ allPacketsLimit = null;
+ }
+ final ConfigurationSection section = TuinityConfig.config.getConfigurationSection("packet-limiter.limits");
+
+ // add default packets
+
+ // auto recipe limiting
+ TuinityConfig.getDouble("packet-limiter.limits." +
+ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".interval", 4.0);
+ TuinityConfig.getDouble("packet-limiter.limits." +
+ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".max-packet-rate", 5.0);
+ TuinityConfig.getString("packet-limiter.limits." +
+ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".action", PacketLimit.ViolateAction.DROP.name());
+
+ for (final String packetClassName : section.getKeys(false)) {
+ if (packetClassName.equals("all")) {
+ continue;
+ }
+ final Class<?> packetClazz;
+
+ try {
+ packetClazz = Class.forName("net.minecraft.server." + packetClassName);
+ } catch (final ClassNotFoundException ex) {
+ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml");
+ continue;
+ }
+
+ if (!net.minecraft.server.Packet.class.isAssignableFrom(packetClazz)) {
+ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml");
+ continue;
+ }
+
+ if (!(section.get(packetClassName.concat(".interval")) instanceof Number) || !(section.get(packetClassName.concat(".max-packet-rate")) instanceof Number)) {
+ throw new RuntimeException("Packet limit setting " + packetClassName + " is missing interval or max-packet-rate!");
+ }
+
+ final String actionString = section.getString(packetClassName.concat(".action"), "KICK");
+ PacketLimit.ViolateAction action = PacketLimit.ViolateAction.KICK;
+ for (PacketLimit.ViolateAction test : PacketLimit.ViolateAction.values()) {
+ if (actionString.equalsIgnoreCase(test.name())) {
+ action = test;
+ break;
+ }
+ }
+
+ final double interval = section.getDouble(packetClassName.concat(".interval"));
+ final double rate = section.getDouble(packetClassName.concat(".max-packet-rate"));
+
+ if (interval > 0.0 && rate > 0.0) {
+ packetSpecificLimits.put((Class)packetClazz, new PacketLimit(interval, rate, action));
+ }
+ }
+ }
+
+ public static final class WorldConfig {
+
+ public final String worldName;
+ public String configPath;
+ 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);
+ this.configPath = worldSectionPath;
+ if (TuinityConfig.createWorldSections) {
+ if (section == null) {
+ section = TuinityConfig.config.createSection(worldSectionPath);
+ }
+ TuinityConfig.config.set(worldSectionPath, section);
+ }
+
+ this.load();
+ }
+
+ public void load() {
+ 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) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.set(path, val);
+ if (config != null && config.get(path) != null) {
+ config.set(path, val);
+ }
+ }
+
+ boolean getBoolean(final String path, final boolean dfl) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.addDefault(path, Boolean.valueOf(dfl));
+ if (TuinityConfig.configVersion < 1) {
+ if (config != null && config.getBoolean(path) == dfl) {
+ config.set(path, null);
+ }
+ }
+ return config == null ? this.worldDefaults.getBoolean(path) : config.getBoolean(path, this.worldDefaults.getBoolean(path));
+ }
+
+ int getInt(final String path, final int dfl) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.addDefault(path, Integer.valueOf(dfl));
+ if (TuinityConfig.configVersion < 1) {
+ if (config != null && config.getInt(path) == dfl) {
+ config.set(path, null);
+ }
+ }
+ return config == null ? this.worldDefaults.getInt(path) : config.getInt(path, this.worldDefaults.getInt(path));
+ }
+
+ long getLong(final String path, final long dfl) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.addDefault(path, Long.valueOf(dfl));
+ if (TuinityConfig.configVersion < 1) {
+ if (config != null && config.getLong(path) == dfl) {
+ config.set(path, null);
+ }
+ }
+ return config == null ? this.worldDefaults.getLong(path) : config.getLong(path, this.worldDefaults.getLong(path));
+ }
+
+ double getDouble(final String path, final double dfl) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.addDefault(path, Double.valueOf(dfl));
+ if (TuinityConfig.configVersion < 1) {
+ if (config != null && config.getDouble(path) == dfl) {
+ config.set(path, null);
+ }
+ }
+ return config == null ? this.worldDefaults.getDouble(path) : config.getDouble(path, this.worldDefaults.getDouble(path));
+ }
+
+ String getString(final String path, final String dfl) {
+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath);
+ this.worldDefaults.addDefault(path, dfl);
+ return config == null ? this.worldDefaults.getString(path) : config.getString(path, this.worldDefaults.getString(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..104a5c7bdc
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java
@@ -0,0 +1,73 @@
+package com.tuinity.tuinity.util;
+
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Chunk;
+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 UnsafeList<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 UnsafeList<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;
+ }
+
+ static final UnsafeList<Chunk> TEMP_GET_CHUNKS_LIST = new UnsafeList<>(1024);
+ static boolean tempGetChunksListInUse;
+
+ public static UnsafeList<Chunk> getTempGetChunksList() {
+ if (!Bukkit.isPrimaryThread() || tempGetChunksListInUse) {
+ return new UnsafeList<>();
+ }
+ tempGetChunksListInUse = true;
+ return TEMP_GET_CHUNKS_LIST;
+ }
+
+ public static void returnTempGetChunksList(List<Chunk> list) {
+ if (list != TEMP_GET_CHUNKS_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempGetChunksListInUse = false;
+ }
+
+ public static void reset() {
+ TEMP_COLLISION_LIST.completeReset();
+ TEMP_GET_ENTITIES_LIST.completeReset();
+ }
+}
diff --git a/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java
new file mode 100644
index 0000000000..d2c7d2c792
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java
@@ -0,0 +1,100 @@
+package com.tuinity.tuinity.util;
+
+public final class IntervalledCounter {
+
+ protected long[] times;
+ protected final long interval;
+ protected long minTime;
+ protected int sum;
+ protected int head; // inclusive
+ protected int tail; // exclusive
+
+ public IntervalledCounter(final long interval) {
+ this.times = new long[8];
+ this.interval = interval;
+ }
+
+ public void updateCurrentTime() {
+ this.updateCurrentTime(System.nanoTime());
+ }
+
+ public void updateCurrentTime(final long currentTime) {
+ int sum = this.sum;
+ int head = this.head;
+ final int tail = this.tail;
+ final long minTime = currentTime - this.interval;
+
+ final int arrayLen = this.times.length;
+
+ // guard against overflow by using subtraction
+ while (head != tail && this.times[head] - minTime < 0) {
+ head = (head + 1) % arrayLen;
+ --sum;
+ }
+
+ this.sum = sum;
+ this.head = head;
+ this.minTime = minTime;
+ }
+
+ public void addTime(final long currTime) {
+ // guard against overflow by using subtraction
+ if (currTime - this.minTime < 0) {
+ return;
+ }
+ int nextTail = (this.tail + 1) % this.times.length;
+ if (nextTail == this.head) {
+ this.resize();
+ nextTail = (this.tail + 1) % this.times.length;
+ }
+
+ this.times[this.tail] = currTime;
+ this.tail = nextTail;
+ }
+
+ public void updateAndAdd(final int count) {
+ final long currTime = System.nanoTime();
+ this.updateCurrentTime(currTime);
+ for (int i = 0; i < count; ++i) {
+ this.addTime(currTime);
+ }
+ }
+
+ public void updateAndAdd(final int count, final long currTime) {
+ this.updateCurrentTime(currTime);
+ for (int i = 0; i < count; ++i) {
+ this.addTime(currTime);
+ }
+ }
+
+ private void resize() {
+ final long[] oldElements = this.times;
+ final long[] newElements = new long[this.times.length * 2];
+ this.times = newElements;
+
+ final int head = this.head;
+ final int tail = this.tail;
+ final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head));
+ this.head = 0;
+ this.tail = size;
+
+ if (tail >= head) {
+ System.arraycopy(oldElements, head, newElements, 0, size);
+ } else {
+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
+ }
+ }
+
+ // returns in units per second
+ public double getRate() {
+ return this.size() / (this.interval * 1.0e-9);
+ }
+
+ public int size() {
+ final int head = this.head;
+ final int tail = this.tail;
+
+ return tail >= head ? (tail - head) : (tail + (this.times.length - head));
+ }
+}
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..6d2851ffa3
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
@@ -0,0 +1,288 @@
+package com.tuinity.tuinity.util.maplist;
+
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import org.bukkit.Bukkit;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+public final class IteratorSafeOrderedReferenceSet<E> {
+
+ public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0;
+
+ protected final Reference2IntLinkedOpenHashMap<E> indexMap;
+ protected int firstInvalidIndex = -1;
+
+ /* list impl */
+ protected E[] listElements;
+ protected int listSize;
+
+ protected final double maxFragFactor;
+
+ protected int iteratorCount;
+
+ private final boolean threadRestricted;
+
+ public IteratorSafeOrderedReferenceSet() {
+ this(16, 0.75f, 16, 0.2);
+ }
+
+ public IteratorSafeOrderedReferenceSet(final boolean threadRestricted) {
+ this(16, 0.75f, 16, 0.2, threadRestricted);
+ }
+
+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+ final double maxFragFactor) {
+ this(setCapacity, setLoadFactor, arrayCapacity, maxFragFactor, false);
+ }
+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+ final double maxFragFactor, final boolean threadRestricted) {
+ this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor);
+ this.indexMap.defaultReturnValue(-1);
+ this.maxFragFactor = maxFragFactor;
+ this.listElements = (E[])new Object[arrayCapacity];
+ this.threadRestricted = threadRestricted;
+ }
+
+ protected final boolean allowSafeIteration() {
+ return !this.threadRestricted || Bukkit.isPrimaryThread();
+ }
+
+ protected final double getFragFactor() {
+ return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
+ }
+
+ public int createRawIterator() {
+ if (this.allowSafeIteration()) {
+ ++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.allowSafeIteration() && --this.iteratorCount == 0) {
+ if (this.getFragFactor() >= this.maxFragFactor) {
+ this.defrag();
+ }
+ }
+ }
+
+ 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;
+ }
+ if (this.listElements[index] != element) {
+ throw new IllegalStateException();
+ }
+ this.listElements[index] = null;
+ if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) {
+ this.defrag();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(final E element) {
+ return this.indexMap.containsKey(element);
+ }
+
+ public boolean add(final E element) {
+ 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() {
+ return this.iterator(0);
+ }
+
+ public IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
+ if (this.allowSafeIteration()) {
+ ++this.iteratorCount;
+ }
+ return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+ }
+
+ public java.util.Iterator<E> unsafeIterator() {
+ return this.unsafeIterator(0);
+ }
+ public java.util.Iterator<E> unsafeIterator(final int flags) {
+ return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+ }
+
+ 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 final int maxIndex;
+ protected int nextIndex;
+ protected E pendingValue;
+ protected boolean finished;
+ protected E lastReturned;
+
+ protected BaseIterator(final IteratorSafeOrderedReferenceSet<E> set, final boolean canFinish, final int maxIndex) {
+ this.set = set;
+ this.canFinish = canFinish;
+ this.maxIndex = maxIndex;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.finished) {
+ return false;
+ }
+ if (this.pendingValue != null) {
+ return true;
+ }
+
+ final E[] elements = this.set.listElements;
+ int index, len;
+ for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) {
+ final E element = elements[index];
+ if (element != null) {
+ this.pendingValue = 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.pendingValue;
+
+ this.pendingValue = null;
+ this.lastReturned = ret;
+
+ return ret;
+ }
+
+ @Override
+ public void remove() {
+ final E lastReturned = this.lastReturned;
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ this.lastReturned = null;
+ this.set.remove(lastReturned);
+ }
+
+ @Override
+ public void finishedIterating() {
+ if (this.finished || !this.canFinish) {
+ throw new IllegalStateException();
+ }
+ this.lastReturned = null;
+ this.finished = true;
+ if (this.set.allowSafeIteration()) {
+ 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..b321ad5163
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java
@@ -0,0 +1,162 @@
+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;
+
+ public AABBVoxelShape(AxisAlignedBB aabb) {
+ super(VoxelShapes.getFullUnoptimisedCube().getShape());
+ this.aabb = aabb;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.aabb.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
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return AxisAlignedBB.collideX(this.aabb, axisalignedbb, d0);
+ case 1:
+ return AxisAlignedBB.collideY(this.aabb, axisalignedbb, d0);
+ case 2:
+ return AxisAlignedBB.collideZ(this.aabb, axisalignedbb, d0);
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public boolean intersects(AxisAlignedBB axisalingedbb) {
+ return this.aabb.voxelShapeIntersect(axisalingedbb);
+ }
+}
diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java
index ed9b2f9adf..6aa9f07336 100644
--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java
+++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java
@@ -13,6 +13,149 @@ public class AxisAlignedBB {
public final double maxY;
public final double maxZ;
+ // Tuinity start
+ public final boolean isEmpty() {
+ return (this.maxX - this.minX) < MCUtil.COLLISION_EPSILON && (this.maxY - this.minY) < MCUtil.COLLISION_EPSILON && (this.maxZ - this.minZ) < MCUtil.COLLISION_EPSILON;
+ }
+
+ 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 - 3*MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*MCUtil.COLLISION_EPSILON, x + (16.0 + 3*MCUtil.COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*MCUtil.COLLISION_EPSILON), false);
+ }
+
+ /*
+ A couple of rules for VoxelShape collisions:
+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement
+ checks.
+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite
+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code
+ will automatically round it to 0.
+ */
+
+ public final boolean voxelShapeIntersect(AxisAlignedBB other) {
+ return this.voxelShapeIntersect(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ);
+ }
+
+ public final boolean voxelShapeIntersect(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+ return (this.minX - maxX) < -MCUtil.COLLISION_EPSILON && (this.maxX - minX) > MCUtil.COLLISION_EPSILON &&
+ (this.minY - maxY) < -MCUtil.COLLISION_EPSILON && (this.maxY - minY) > MCUtil.COLLISION_EPSILON &&
+ (this.minZ - maxZ) < -MCUtil.COLLISION_EPSILON && (this.maxZ - minZ) > MCUtil.COLLISION_EPSILON;
+ }
+
+ public static double collideX(AxisAlignedBB target, AxisAlignedBB source, double source_move) {
+ if (target.isEmpty() || source.isEmpty()) {
+ return source_move;
+ }
+ if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+
+ if ((source.minY - target.maxY) < -MCUtil.COLLISION_EPSILON && (source.maxY - target.minY) > MCUtil.COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -MCUtil.COLLISION_EPSILON && (source.maxZ - target.minZ) > MCUtil.COLLISION_EPSILON) {
+
+ if (source_move >= 0.0) {
+ double max_move = target.minX - source.maxX; // < 0.0 if no strict collision
+ if (max_move < -MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ double max_move = target.maxX - source.minX; // > 0.0 if no strict collision
+ if (max_move > MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideY(AxisAlignedBB target, AxisAlignedBB source, double source_move) {
+ if (target.isEmpty() || source.isEmpty()) {
+ return source_move;
+ }
+ if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -MCUtil.COLLISION_EPSILON && (source.maxX - target.minX) > MCUtil.COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -MCUtil.COLLISION_EPSILON && (source.maxZ - target.minZ) > MCUtil.COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ double max_move = target.minY - source.maxY; // < 0.0 if no strict collision
+ if (max_move < -MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ double max_move = target.maxY - source.minY; // > 0.0 if no strict collision
+ if (max_move > MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideZ(AxisAlignedBB target, AxisAlignedBB source, double source_move) {
+ if (target.isEmpty() || source.isEmpty()) {
+ return source_move;
+ }
+ if (Math.abs(source_move) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -MCUtil.COLLISION_EPSILON && (source.maxX - target.minX) > MCUtil.COLLISION_EPSILON &&
+ (source.minY - target.maxY) < -MCUtil.COLLISION_EPSILON && (source.maxY - target.minY) > MCUtil.COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision
+ if (max_move < -MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision
+ if (max_move > MCUtil.COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ 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 +328,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 +337,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 +357,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 6b655b744d..e811295b4d 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;
}
- public void o(int i) { // Paper - protected -> public
+ protected void o_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type
this.a = i;
}
- public void p(int i) { // Paper - protected -> public
+ protected void p_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type
this.b = i;
}
- public void q(int i) { // Paper - protected -> public
+ protected void q_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type
this.e = i;
}
diff --git a/src/main/java/net/minecraft/server/Behavior.java b/src/main/java/net/minecraft/server/Behavior.java
index 65af976527..0b9d469a92 100644
--- a/src/main/java/net/minecraft/server/Behavior.java
+++ b/src/main/java/net/minecraft/server/Behavior.java
@@ -7,7 +7,7 @@ import java.util.Map.Entry;
public abstract class Behavior<E extends EntityLiving> {
protected final Map<MemoryModuleType<?>, MemoryStatus> a;
- private Behavior.Status b;
+ private Behavior.Status b; public final Behavior.Status getStatus() { return this.b; } // Tuinity - OBFHELPER
private long c;
private final int d;
private final int e;
diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
index 63a761ebef..8d445e9c08 100644
--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
@@ -55,6 +55,227 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
}
}
+ // Tuinity - remove streams entirely for poi search
+ // the only intentional vanilla diff is that this function will NOT load in poi data, anything else is a bug!
+ protected static Set<BlockPosition> findNearestPoi(VillagePlace poiStorage,
+ Predicate<VillagePlaceType> villagePlaceType,
+ Predicate<BlockPosition> positionPredicate,
+ BlockPosition sourcePosition,
+ int range, // distance on x y z axis
+ VillagePlace.Occupancy occupancy,
+ int max) {
+ java.util.TreeSet<BlockPosition> ret = new java.util.TreeSet<>((blockpos1, blockpos2) -> {
+ // important to keep distanceSquared order: the param is the source
+ return Double.compare(blockpos1.distanceSquared(sourcePosition), blockpos2.distanceSquared(sourcePosition));
+ });
+ findNearestPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, max, ret);
+ return new java.util.HashSet<>(ret);
+ }
+ protected static void findNearestPoi(VillagePlace poiStorage,
+ Predicate<VillagePlaceType> villagePlaceType,
+ Predicate<BlockPosition> positionPredicate,
+ BlockPosition sourcePosition,
+ int range, // distance on x y z axis
+ VillagePlace.Occupancy occupancy,
+ int max,
+ java.util.SortedSet<BlockPosition> ret) {
+ // the biggest issue with the original mojang implementation is that they chain so many streams together
+ // the amount of streams chained just rolls performance, even if nothing is iterated over
+ Predicate<? super VillagePlaceRecord> occupancyFilter = occupancy.getPredicate();
+ double rangeSquared = range * range;
+
+ // First up, we need to iterate the chunks
+ // all the values here are in chunk sections
+ int lowerX = MathHelper.floor(sourcePosition.getX() - range) >> 4;
+ int lowerY = Math.max(0, MathHelper.floor(sourcePosition.getY() - range) >> 4);
+ int lowerZ = MathHelper.floor(sourcePosition.getZ() - range) >> 4;
+ int upperX = MathHelper.floor(sourcePosition.getX() + range) >> 4;
+ int upperY = Math.min(15, MathHelper.floor(sourcePosition.getY() + range) >> 4);
+ int upperZ = MathHelper.floor(sourcePosition.getZ() + range) >> 4;
+
+ // Vanilla iterates by x until max is reached then increases z
+ // vanilla also searches by increasing Y section value
+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) {
+ for (int currX = lowerX; currX <= upperX; ++currX) {
+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need
+ Optional<VillagePlaceSection> poiSectionOptional = poiStorage.getIfLoaded(SectionPosition.asLong(currX, currY, currZ));
+ VillagePlaceSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null);
+ if (poiSection == null) {
+ continue;
+ }
+
+ java.util.Map<VillagePlaceType, Set<VillagePlaceRecord>> sectionData = poiSection.getData();
+ if (sectionData.isEmpty()) {
+ continue;
+ }
+
+ // now we search the section data
+ for (java.util.Iterator<java.util.Map.Entry<VillagePlaceType, Set<VillagePlaceRecord>>> iterator = sectionData.entrySet().iterator();
+ iterator.hasNext();) {
+ java.util.Map.Entry<VillagePlaceType, Set<VillagePlaceRecord>> entry = iterator.next();
+ if (!villagePlaceType.test(entry.getKey())) {
+ // filter out by poi type
+ continue;
+ }
+
+ // now we can look at the poi data
+ for (VillagePlaceRecord poiData : entry.getValue()) {
+ if (!occupancyFilter.test(poiData)) {
+ // filter by occupancy
+ continue;
+ }
+
+ // vanilla code is pretty dumb about filtering by distance: first they filter out
+ // so that only values in the square radius of range are returned but then they
+ // filter out so that the distance is in range
+ // but there's a catch! distanceSquared, by default, will ADD 0.5 to ONLY ONE OF the
+ // block position parameters (itself, in this case the poi position)! So if we want to
+ // maintain exact vanilla behaviour, well shit we need to play dumb as well.
+
+ BlockPosition poiPosition = poiData.getPosition();
+
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
+ // out of range for square radius
+ continue;
+ }
+
+ if (poiPosition.distanceSquared(sourcePosition) > rangeSquared) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (!positionPredicate.test(poiPosition)) {
+ // filter by position
+ continue;
+ }
+
+ // found one!
+ ret.add(poiPosition);
+ if (ret.size() > max) {
+ ret.remove(ret.last());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected static BlockPosition findAnyFirstPoi(VillagePlace poiStorage,
+ Predicate<VillagePlaceType> villagePlaceType,
+ Predicate<BlockPosition> positionPredicate,
+ BlockPosition sourcePosition,
+ int range, // distance on x y z axis
+ VillagePlace.Occupancy occupancy) {
+ Set<BlockPosition> ret = new java.util.HashSet<>();
+ findPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, 1, ret);
+ return ret.isEmpty() ? null : ret.iterator().next();
+ }
+
+ protected static Set<BlockPosition> findPoi(VillagePlace poiStorage,
+ Predicate<VillagePlaceType> villagePlaceType,
+ Predicate<BlockPosition> positionPredicate,
+ BlockPosition sourcePosition,
+ int range, // distance on x y z axis
+ VillagePlace.Occupancy occupancy,
+ int max) {
+ Set<BlockPosition> ret = new java.util.HashSet<>();
+ findPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, max, ret);
+ return ret;
+ }
+ protected static void findPoi(VillagePlace poiStorage,
+ Predicate<VillagePlaceType> villagePlaceType,
+ Predicate<BlockPosition> positionPredicate,
+ BlockPosition sourcePosition,
+ int range, // distance on x y z axis
+ VillagePlace.Occupancy occupancy,
+ int max,
+ Set<BlockPosition> ret) {
+ // the biggest issue with the original mojang implementation is that they chain so many streams together
+ // the amount of streams chained just rolls performance, even if nothing is iterated over
+ Predicate<? super VillagePlaceRecord> occupancyFilter = occupancy.getPredicate();
+ double rangeSquared = range * range;
+
+ // First up, we need to iterate the chunks
+ // all the values here are in chunk sections
+ int lowerX = MathHelper.floor(sourcePosition.getX() - range) >> 4;
+ int lowerY = Math.max(0, MathHelper.floor(sourcePosition.getY() - range) >> 4);
+ int lowerZ = MathHelper.floor(sourcePosition.getZ() - range) >> 4;
+ int upperX = MathHelper.floor(sourcePosition.getX() + range) >> 4;
+ int upperY = Math.min(15, MathHelper.floor(sourcePosition.getY() + range) >> 4);
+ int upperZ = MathHelper.floor(sourcePosition.getZ() + range) >> 4;
+
+ // Vanilla iterates by x until max is reached then increases z
+ // vanilla also searches by increasing Y section value
+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) {
+ for (int currX = lowerX; currX <= upperX; ++currX) {
+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need
+ Optional<VillagePlaceSection> poiSectionOptional = poiStorage.getIfLoaded(SectionPosition.asLong(currX, currY, currZ));
+ VillagePlaceSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null);
+ if (poiSection == null) {
+ continue;
+ }
+
+ java.util.Map<VillagePlaceType, Set<VillagePlaceRecord>> sectionData = poiSection.getData();
+ if (sectionData.isEmpty()) {
+ continue;
+ }
+
+ // now we search the section data
+ for (java.util.Iterator<java.util.Map.Entry<VillagePlaceType, Set<VillagePlaceRecord>>> iterator = sectionData.entrySet().iterator();
+ iterator.hasNext();) {
+ java.util.Map.Entry<VillagePlaceType, Set<VillagePlaceRecord>> entry = iterator.next();
+ if (!villagePlaceType.test(entry.getKey())) {
+ // filter out by poi type
+ continue;
+ }
+
+ // now we can look at the poi data
+ for (VillagePlaceRecord poiData : entry.getValue()) {
+ if (!occupancyFilter.test(poiData)) {
+ // filter by occupancy
+ continue;
+ }
+
+ // vanilla code is pretty dumb about filtering by distance: first they filter out
+ // so that only values in the square radius of range are returned but then they
+ // filter out so that the distance is in range
+ // but there's a catch! distanceSquared, by default, will ADD 0.5 to ONLY ONE OF the
+ // block position parameters (itself, in this case the poi position)! So if we want to
+ // maintain exact vanilla behaviour, well shit we need to play dumb as well.
+
+ BlockPosition poiPosition = poiData.getPosition();
+
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
+ // out of range for square radius
+ continue;
+ }
+
+ if (poiPosition.distanceSquared(sourcePosition) > rangeSquared) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (!positionPredicate.test(poiPosition)) {
+ // filter by position
+ continue;
+ }
+
+ // found one!
+ ret.add(poiPosition);
+ if (ret.size() >= max) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Tuinity - remove streams entirely for poi search
+
protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) {
this.f = i + 20L + (long) worldserver.getRandom().nextInt(20);
VillagePlace villageplace = worldserver.y();
@@ -74,7 +295,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
return true;
}
};
- Set<BlockPosition> set = (Set) villageplace.b(this.b.c(), predicate, entitycreature.getChunkCoordinates(), 48, VillagePlace.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet());
+ Set<BlockPosition> set = findNearestPoi(villageplace, this.b.c(), predicate, entitycreature.getChunkCoordinates(), 48, VillagePlace.Occupancy.HAS_SPACE, 5); // Tuinity - remove streams entirely for poi search
PathEntity pathentity = entitycreature.getNavigation().a(set, this.b.d());
if (pathentity != null && pathentity.j()) {
@@ -84,7 +305,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
villageplace.a(this.b.c(), (blockposition1) -> {
return blockposition1.equals(blockposition);
}, blockposition, 1);
- entitycreature.getBehaviorController().setMemory(this.c, (Object) GlobalPos.create(worldserver.getDimensionKey(), blockposition));
+ entitycreature.getBehaviorController().setMemory(this.c, GlobalPos.create(worldserver.getDimensionKey(), blockposition)); // Tuinity - decompile fix
this.e.ifPresent((obyte) -> {
worldserver.broadcastEntityEffect(entitycreature, obyte);
});
diff --git a/src/main/java/net/minecraft/server/BehaviorGate.java b/src/main/java/net/minecraft/server/BehaviorGate.java
index 46e9105812..fb967bc03f 100644
--- a/src/main/java/net/minecraft/server/BehaviorGate.java
+++ b/src/main/java/net/minecraft/server/BehaviorGate.java
@@ -12,7 +12,7 @@ public class BehaviorGate<E extends EntityLiving> extends Behavior<E> {
private final Set<MemoryModuleType<?>> b;
private final BehaviorGate.Order c;
private final BehaviorGate.Execution d;
- private final WeightedList<Behavior<? super E>> e = new WeightedList<>(false); // Paper - don't use a clone
+ private final WeightedList<Behavior<? super E>> e = new WeightedList<>(false); protected final WeightedList<Behavior<? super E>> getList() { return this.e; } // Paper - don't use a clone // Tuinity - OBFHELPER
public BehaviorGate(Map<MemoryModuleType<?>, MemoryStatus> map, Set<MemoryModuleType<?>> set, BehaviorGate.Order behaviorgate_order, BehaviorGate.Execution behaviorgate_execution, List<Pair<Behavior<? super E>, Integer>> list) {
super(map);
@@ -26,11 +26,17 @@ public class BehaviorGate<E extends EntityLiving> extends Behavior<E> {
@Override
protected boolean b(WorldServer worldserver, E e0, long i) {
- return this.e.c().filter((behavior) -> {
- return behavior.a() == Behavior.Status.RUNNING;
- }).anyMatch((behavior) -> {
- return behavior.b(worldserver, e0, i);
- });
+ // Tuinity start - remove streams
+ List<WeightedList.a<Behavior<? super E>>> list = this.getList().getList();
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ Behavior<? super E> behavior = list.get(index).getValue();
+ if (behavior.getStatus() == Status.RUNNING && behavior.b(worldserver, e0, i)) { // copied from removed code, make sure to update
+ return true;
+ }
+ }
+
+ return false;
+ // Tuinity end - remove streams
}
@Override
@@ -46,20 +52,28 @@ public class BehaviorGate<E extends EntityLiving> extends Behavior<E> {
@Override
protected void d(WorldServer worldserver, E e0, long i) {
- this.e.c().filter((behavior) -> {
- return behavior.a() == Behavior.Status.RUNNING;
- }).forEach((behavior) -> {
- behavior.f(worldserver, e0, i);
- });
+ // Tuinity start - remove streams
+ List<WeightedList.a<Behavior<? super E>>> list = this.getList().getList();
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ Behavior<? super E> behavior = list.get(index).getValue();
+ if (behavior.getStatus() == Behavior.Status.RUNNING) {
+ behavior.f(worldserver, e0, i); // copied from removed code, make sure to update
+ }
+ }
+ // Tuinity end - remove streams
}
@Override
protected void c(WorldServer worldserver, E e0, long i) {
- this.e.c().filter((behavior) -> {
- return behavior.a() == Behavior.Status.RUNNING;
- }).forEach((behavior) -> {
- behavior.g(worldserver, e0, i);
- });
+ // Tuinity start - remove streams
+ List<WeightedList.a<Behavior<? super E>>> list = this.getList().getList();
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ Behavior<? super E> behavior = list.get(index).getValue();
+ if (behavior.getStatus() == Behavior.Status.RUNNING) {
+ behavior.g(worldserver, e0, i); // copied from removed code, make sure to update
+ }
+ }
+ // Tuinity end - remove streams
BehaviorController behaviorcontroller = e0.getBehaviorController();
this.b.forEach(behaviorcontroller::removeMemory); // Paper - decomp fix
@@ -79,21 +93,29 @@ public class BehaviorGate<E extends EntityLiving> extends Behavior<E> {
RUN_ONE {
@Override
public <E extends EntityLiving> void a(WeightedList<Behavior<? super E>> weightedlist, WorldServer worldserver, E e0, long i) {
- weightedlist.c().filter((behavior) -> {
- return behavior.a() == Behavior.Status.STOPPED;
- }).filter((behavior) -> {
- return behavior.e(worldserver, e0, i);
- }).findFirst();
+ // Tuinity start - remove streams
+ List<WeightedList.a<Behavior<? super E>>> list = weightedlist.getList();
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ Behavior<? super E> behavior = list.get(index).getValue();
+ if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.e(worldserver, e0, i)) { // copied from removed code, make sure to update
+ break;
+ }
+ }
+ // Tuinity end - remove streams
}
},
TRY_ALL {
@Override
public <E extends EntityLiving> void a(WeightedList<Behavior<? super E>> weightedlist, WorldServer worldserver, E e0, long i) {
- weightedlist.c().filter((behavior) -> {
- return behavior.a() == Behavior.Status.STOPPED;
- }).forEach((behavior) -> {
- behavior.e(worldserver, e0, i);
- });
+ // Tuinity start - remove streams
+ List<WeightedList.a<Behavior<? super E>>> list = weightedlist.getList();
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ Behavior<? super E> behavior = list.get(index).getValue();
+ if (behavior.getStatus() == Behavior.Status.STOPPED) {
+ behavior.e(worldserver, e0, i); // copied from removed code, make sure to update
+ }
+ }
+ // Tuinity end - remove streams
}
};
diff --git a/src/main/java/net/minecraft/server/BehaviorLookInteract.java b/src/main/java/net/minecraft/server/BehaviorLookInteract.java
index a33303c318..ce57e6a4ac 100644
--- a/src/main/java/net/minecraft/server/BehaviorLookInteract.java
+++ b/src/main/java/net/minecraft/server/BehaviorLookInteract.java
@@ -7,7 +7,7 @@ import java.util.function.Predicate;
public class BehaviorLookInteract extends Behavior<EntityLiving> {
private final EntityTypes<?> b;
- private final int c;
+ private final int c; private final int getMaxRange() { return this.c; } // Tuinity - OBFHELPER
private final Predicate<EntityLiving> d;
private final Predicate<EntityLiving> e;
@@ -29,7 +29,20 @@ public class BehaviorLookInteract extends Behavior<EntityLiving> {
@Override
public boolean a(WorldServer worldserver, EntityLiving entityliving) {
- return this.e.test(entityliving) && this.b(entityliving).stream().anyMatch(this::a);
+ // Tuinity start - remove streams
+ if (!this.e.test(entityliving)) {
+ return false;
+ }
+
+ List<EntityLiving> list = this.b(entityliving);
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ if (this.a(list.get(index))) {
+ return true;
+ }
+ }
+
+ return false;
+ // Tuinity end - remove streams
}
@Override
@@ -37,16 +50,28 @@ public class BehaviorLookInteract extends Behavior<EntityLiving> {
super.a(worldserver, entityliving, i);
BehaviorController<?> behaviorcontroller = entityliving.getBehaviorController();
- behaviorcontroller.getMemory(MemoryModuleType.VISIBLE_MOBS).ifPresent((list) -> {
- list.stream().filter((entityliving1) -> {
- return entityliving1.h((Entity) entityliving) <= (double) this.c;
- }).filter(this::a).findFirst().ifPresent((entityliving1) -> {
- behaviorcontroller.setMemory(MemoryModuleType.INTERACTION_TARGET, (Object) entityliving1);
- behaviorcontroller.setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BehaviorPositionEntity(entityliving1, true)));
- });
- });
+ // Tuinity start - remove streams
+ List<EntityLiving> inLOS = behaviorcontroller.getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(null);
+ if (inLOS != null) {
+ double maxRangeSquared = this.getMaxRange();
+ for (int index = 0, len = inLOS.size(); index < len; ++index) {
+ EntityLiving entity = inLOS.get(index);
+ if (!this.canTarget(entity)) {
+ continue;
+ }
+ double distance = entity.getDistanceSquared(entityliving.locX(), entityliving.locY(), entityliving.locZ());
+ if (distance > maxRangeSquared) {
+ continue;
+ }
+ behaviorcontroller.setMemory(MemoryModuleType.INTERACTION_TARGET, entity); // Tuinity - decompile fix
+ behaviorcontroller.setMemory(MemoryModuleType.LOOK_TARGET, (new BehaviorPositionEntity(entity, true))); // Tuinity - decompile fix
+ break;
+ }
+ }
+ // Tuinity end - remove streams
}
+ private final boolean canTarget(EntityLiving entityliving) { return this.a(entityliving); } // Tuinity - OBFHELPER
private boolean a(EntityLiving entityliving) {
return this.b.equals(entityliving.getEntityType()) && this.d.test(entityliving);
}
diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java
index 1f334d6328..2760b377d1 100644
--- a/src/main/java/net/minecraft/server/BlockBase.java
+++ b/src/main/java/net/minecraft/server/BlockBase.java
@@ -295,21 +295,23 @@ 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;
private final BlockBase.e o;
private final BlockBase.e p;
@Nullable
- protected BlockBase.BlockData.Cache a;
+ protected BlockBase.BlockData.Cache a; protected final BlockBase.BlockData.Cache getShapeCache() { return this.a; } // Tuinity - OBFHELPER
+ public PathType staticPathType; // Tuinity - cache static path types
+ public PathType neighbourOverridePathType; // Tuinity - cache static path types
protected BlockData(Block block, ImmutableMap<IBlockState<?>, Comparable<?>> immutablemap, MapCodec<IBlockData> mapcodec) {
super(block, immutablemap, mapcodec);
@@ -343,12 +345,22 @@ public abstract class BlockBase {
protected Fluid fluid;
// Paper end
+ // Tuinity start - micro the hell out of this call
+ protected boolean shapeExceedsCube = true;
+ public final boolean shapeExceedsCube() {
+ return this.shapeExceedsCube;
+ }
+ // Tuinity end
+
public void a() {
this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid()
this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking()
if (!this.getBlock().o()) {
this.a = new BlockBase.BlockData.Cache(this.p());
}
+ this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here
+ this.staticPathType = null; // Tuinity - cache static path type
+ this.neighbourOverridePathType = null; // Tuinity - cache static path types
}
@@ -372,10 +384,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);
}
@@ -385,7 +399,7 @@ public abstract class BlockBase {
}
public final boolean d() { // Paper
- return this.a == null || this.a.c;
+ return this.shapeExceedsCube; // Tuinity - moved into shape cache init
}
public final boolean e() { // Paper
diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java
index 12a0230448..9e5e6de52e 100644
--- a/src/main/java/net/minecraft/server/BlockChest.java
+++ b/src/main/java/net/minecraft/server/BlockChest.java
@@ -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/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
index 2d887af902..2291135eae 100644
--- a/src/main/java/net/minecraft/server/BlockPosition.java
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
@@ -449,10 +449,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;
}
@@ -462,12 +462,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) {
@@ -482,8 +488,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) {
@@ -511,21 +520,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 dcbae1c451..f3702ed75e 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -91,6 +91,151 @@ public class Chunk implements IChunkAccess {
private final int[] inventoryEntityCounts = new int[16];
// Paper end
+ // Tuinity start - optimise hard collision handling
+ final com.destroystokyo.paper.util.maplist.EntityList[] hardCollidingEntities = new com.destroystokyo.paper.util.maplist.EntityList[16];
+
+ {
+ for (int i = 0, len = this.hardCollidingEntities.length; i < len; ++i) {
+ this.hardCollidingEntities[i] = new com.destroystokyo.paper.util.maplist.EntityList();
+ }
+ }
+
+ public final void getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> into, Predicate<Entity> predicate) {
+ // copied from getEntities
+ int min = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
+ int max = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D);
+
+ min = MathHelper.clamp(min, 0, this.hardCollidingEntities.length - 1);
+ max = MathHelper.clamp(max, 0, this.hardCollidingEntities.length - 1);
+
+ for (int k = min; k <= max; ++k) {
+ com.destroystokyo.paper.util.maplist.EntityList entityList = this.hardCollidingEntities[k];
+ Entity[] entities = entityList.getRawData();
+
+ for (int i = 0, len = entityList.size(); i < len; ++i) {
+ Entity entity1 = entities[i];
+ if (entity1.shouldBeRemoved) continue; // Paper
+
+ if (entity1 != entity && entity1.getBoundingBox().intersects(axisalignedbb)) {
+ if (predicate == null || predicate.test(entity1)) {
+ into.add(entity1);
+ }
+
+ if (!(entity1 instanceof EntityEnderDragon)) {
+ continue;
+ }
+
+ EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon)entity1).children;
+ int l = aentitycomplexpart.length;
+
+ for (int i1 = 0; i1 < l; ++i1) {
+ EntityComplexPart entitycomplexpart = aentitycomplexpart[i1];
+
+ if (entitycomplexpart != entity && entitycomplexpart.getBoundingBox().intersects(axisalignedbb) && (predicate == null || predicate.test(entitycomplexpart))) {
+ into.add(entitycomplexpart);
+ }
+ }
+ }
+ }
+ }
+ }
+ // Tuinity end - optimise hard collision handling
+
+ // Tuinity start - entity slices by class
+ private final com.tuinity.tuinity.chunk.ChunkEntitiesByClass entitiesByClass = new com.tuinity.tuinity.chunk.ChunkEntitiesByClass(this);
+
+ public boolean hasEntitiesMaybe(Class<?> clazz) {
+ return this.entitiesByClass.hasEntitiesMaybe(clazz);
+ }
+
+ public final void getEntitiesClass(Class<?> clazz, Entity entity, AxisAlignedBB boundingBox, Predicate<Entity> predicate, List<Entity> into) {
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
+ this.getEntities((Class)clazz, boundingBox, (List)into, (Predicate)predicate);
+ return;
+ }
+ this.entitiesByClass.lookupClass(clazz, entity, boundingBox, predicate, into);
+ }
+ // Tuinity end - entity slices by class
+
+ // Tuinity start - optimise checkDespawn
+ private boolean playerGeneralAreaCacheSet;
+ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playerGeneralAreaCache;
+
+ void updateGeneralAreaCache() {
+ this.updateGeneralAreaCache(((WorldServer)this.world).getChunkProvider().playerChunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey));
+ }
+
+ void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> value) {
+ this.playerGeneralAreaCacheSet = true;
+ this.playerGeneralAreaCache = value;
+ }
+
+ public EntityPlayer findNearestPlayer(Entity to, Predicate<Entity> predicate) {
+ if (!this.playerGeneralAreaCacheSet) {
+ this.updateGeneralAreaCache();
+ }
+
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> nearby = this.playerGeneralAreaCache;
+
+ if (nearby == null) {
+ return null;
+ }
+
+ Object[] backingSet = nearby.getBackingSet();
+ double closestDistance = Double.MAX_VALUE;
+ EntityPlayer closest = null;
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)_player;
+
+ double distance = to.getDistanceSquared(player.locX(), player.locY(), player.locZ());
+ if (distance < closestDistance && predicate.test(player)) {
+ closest = player;
+ closestDistance = distance;
+ }
+ }
+
+ return closest;
+ }
+
+ public void getNearestPlayers(Entity source, Predicate<Entity> predicate, double range, List<EntityPlayer> ret) {
+ if (!this.playerGeneralAreaCacheSet) {
+ this.updateGeneralAreaCache();
+ }
+
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> nearby = this.playerGeneralAreaCache;
+
+ if (nearby == null) {
+ return;
+ }
+
+ double rangeSquared = range * range;
+
+ Object[] backingSet = nearby.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)_player;
+
+ if (range >= 0.0) {
+ double distanceSquared = player.getDistanceSquared(source.locX(), source.locY(), source.locZ());
+ if (distanceSquared > rangeSquared) {
+ continue;
+ }
+ }
+
+ if (predicate == null || predicate.test(player)) {
+ ret.add(player);
+ }
+ }
+ }
+ // Tuinity end - optimise checkDespawn
+
public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList<Block> ticklist, TickList<FluidType> ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer<Chunk> consumer) {
this.sections = new ChunkSection[16];
this.e = Maps.newHashMap();
@@ -330,7 +475,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
}
}
@@ -547,6 +692,7 @@ public class Chunk implements IChunkAccess {
@Override
public void a(Entity entity) {
+ org.spigotmc.AsyncCatcher.catchOp("Chunk add entity"); // Tuinity
this.q = true;
int i = MathHelper.floor(entity.locX() / 16.0D);
int j = MathHelper.floor(entity.locZ() / 16.0D);
@@ -592,8 +738,8 @@ public class Chunk implements IChunkAccess {
entity.chunkX = this.loc.x;
entity.chunkY = k;
entity.chunkZ = this.loc.z;
- this.entities.add(entity); // Paper - per chunk entity list
- this.entitySlices[k].add(entity);
+ this.entities.add(entity); this.entitiesByClass.addEntity(entity, entity.chunkY); // Paper - per chunk entity list // Tuinity - entities by class
+ this.entitySlices[k].add(entity); if (entity.hardCollides()) this.hardCollidingEntities[k].add(entity); // Tuinity - optimise hard colliding entities
// Paper start
if (entity instanceof EntityItem) {
itemCounts[k]++;
@@ -616,6 +762,7 @@ public class Chunk implements IChunkAccess {
}
public void a(Entity entity, int i) {
+ org.spigotmc.AsyncCatcher.catchOp("Chunk remove entity"); // Tuinity // Tuinity
if (i < 0) {
i = 0;
}
@@ -630,7 +777,7 @@ public class Chunk implements IChunkAccess {
entity.entitySlice = null;
entity.inChunk = false;
}
- if (!this.entitySlices[i].remove(entity)) {
+ if (entity.hardCollides()) this.hardCollidingEntities[i].remove(entity); this.entitiesByClass.removeEntity(entity, i); if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities // Tuinity - entities by class
return;
}
if (entity instanceof EntityItem) {
@@ -873,6 +1020,7 @@ public class Chunk implements IChunkAccess {
}
public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> list, @Nullable Predicate<? super Entity> predicate) {
+ org.spigotmc.AsyncCatcher.catchOp("Chunk get entities call"); // Tuinity
int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D);
@@ -912,6 +1060,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) {
+ org.spigotmc.AsyncCatcher.catchOp("Chunk get entities (by type) call"); // Tuinity
int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D);
@@ -941,7 +1090,9 @@ public class Chunk implements IChunkAccess {
}
+ public final <T extends Entity> void getEntities(Class<? extends T> oclass, AxisAlignedBB axisalignedbb, List<T> list, @Nullable Predicate<? super T> predicate) { this.a(oclass, axisalignedbb, list, predicate); } // Tuinity - OBFHELPER
public <T extends Entity> void a(Class<? extends T> oclass, AxisAlignedBB axisalignedbb, List<T> list, @Nullable Predicate<? super T> predicate) {
+ org.spigotmc.AsyncCatcher.catchOp("Chunk get entities (by class) call"); // Tuinity
int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D);
diff --git a/src/main/java/net/minecraft/server/ChunkCache.java b/src/main/java/net/minecraft/server/ChunkCache.java
index 8eecdcde51..53c977513d 100644
--- a/src/main/java/net/minecraft/server/ChunkCache.java
+++ b/src/main/java/net/minecraft/server/ChunkCache.java
@@ -12,6 +12,142 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
protected boolean d;
protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER
+ // Tuinity start - optimise pathfinder collision detection
+ @Override
+ public boolean getCubes(Entity entity) {
+ return !this.collidesWithAnyBlockOrWorldBorder(entity, entity.getBoundingBox());
+ }
+
+ @Override
+ public boolean getCubes(Entity entity, AxisAlignedBB axisalignedbb) {
+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb);
+ }
+
+ @Override
+ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb);
+ }
+
+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
+ if (entity != null) {
+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) {
+ return true;
+ }
+ }
+
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 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;
+
+ // TODO special case single 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
+
+ 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
+
+ int chunkXGlobalPos = currChunkX << 4;
+ int chunkZGlobalPos = currChunkZ << 4;
+ Chunk chunk = (Chunk)this.getChunkIfLoaded(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 currZ = minZ; currZ <= maxZ; ++currZ) {
+ int blockKeyZY = blockKeyY | (currZ << 4);
+ int blockZ = currZ | chunkZGlobalPos; // world position
+
+ int edgeCountZY;
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
+ edgeCountZY = edgeCountY + 1;
+ } else {
+ edgeCountZY = edgeCountY;
+ }
+
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int blockX = currX | chunkXGlobalPos; // world position
+
+ int edgeCountFull;
+ if (blockX == minBlockX || blockX == maxBlockX) {
+ edgeCountFull = edgeCountZY + 1;
+ } else {
+ edgeCountFull = edgeCountZY;
+ }
+
+ if (edgeCountFull == 3) {
+ continue;
+ }
+
+ int blockKeyFull = blockKeyZY | currX;
+ 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;
+ }
+ // Tuinity end - optimise pathfinder collision detection
+
public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) {
this.e = world;
this.a = blockposition.getX() >> 4;
diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
index 3c7b225edb..f1c686810f 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,21 +106,45 @@ 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();
+ // Tuinity start - delay chunk unloads
+ int[] tempLevel = new int[] { PlayerChunkMap.GOLDEN_TICKET + 1 };
+ Entry<ArraySetSorted<Ticket<?>>>[] entryPass = new Entry[1];
+ java.util.function.Predicate<Ticket<?>> isExpired = (ticket) -> { // CraftBukkit - decompile error
+ // 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().delayUnloadViable && ticket.getTicketLevel() < tempLevel[0]) {
+ tempLevel[0] = ticket.getTicketLevel();
+ }
+ if (ret && ticket.getTicketType() == TicketType.DELAYED_UNLOAD && ticket.isCached) {
+ this.delayedChunks.remove(entryPass[0].getLongKey(), ticket); // clean up ticket...
+ }
+ return ret;
+ };
+ // Tuinity end - delay chunk unloads
while (objectiterator.hasNext()) {
- Entry<ArraySetSorted<Ticket<?>>> entry = (Entry) objectiterator.next();
+ Entry<ArraySetSorted<Ticket<?>>> entry = (Entry) objectiterator.next(); entryPass[0] = entry; // Tuinity - only allocate lambda once
- if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error
- return ticket.b(this.currentTick);
- })) {
+ if ((entry.getValue()).removeIf(isExpired)) { // Tuinity - move above - only allocate once
+ // 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);
}
if (((ArraySetSorted) entry.getValue()).isEmpty()) {
objectiterator.remove();
}
+
+ tempLevel[0] = PlayerChunkMap.GOLDEN_TICKET + 1; // Tuinity - reset
}
}
@@ -98,6 +163,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 +242,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().delayUnloadViable) {
+ this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted);
}
- // Paper end
+ // Tuinity end - delay chunk unloads
}
if (arraysetsorted.isEmpty()) {
@@ -370,6 +420,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 +438,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 +449,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 +499,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 6acb5f05a0..84429f12d0 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -22,6 +22,12 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+// Tuinity start
+import it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
+// Tuinity end
+
public class ChunkProviderServer extends IChunkProvider {
private static final List<ChunkStatus> b = ChunkStatus.a(); static final List<ChunkStatus> getPossibleChunkStatuses() { return ChunkProviderServer.b; } // Paper - OBFHELPER
@@ -121,7 +127,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) {
@@ -183,9 +189,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) {
@@ -210,6 +216,165 @@ 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);
+ PlayerChunk playerChunk = this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ));
+ if (playerChunk != null) {
+ ChunkStatus holderStatus = playerChunk.getChunkHolderStatus();
+ IChunkAccess immediate = playerChunk.getAvailableChunkNow();
+ if (immediate != null) {
+ if (allowSubTicketLevel ? immediate.getChunkStatus().isAtLeastStatus(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isAtLeastStatus(status))) {
+ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad);
+ return;
+ } else {
+ if (gen || (!allowSubTicketLevel && immediate.getChunkStatus().isAtLeastStatus(status))) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ } 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, MCUtil.getTicketLevelFor(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;
+ }
+ }
+ });
+ }
+
+ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> tickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ // 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);
@@ -545,6 +710,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);
@@ -563,9 +730,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();
@@ -576,12 +746,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((Throwable) (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));
}
@@ -600,8 +778,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);
@@ -638,6 +816,8 @@ public class ChunkProviderServer extends IChunkProvider {
public boolean tickDistanceManager() { // Paper - private -> public
if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper
+ if (this.playerChunkMap.unloadingPlayerChunk) { MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity
+ 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();
@@ -647,6 +827,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
@@ -735,7 +916,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");
@@ -745,7 +926,7 @@ 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();
@@ -822,19 +1003,23 @@ 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
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Chunk> iterator = this.entityTickingChunks.iterator();
+ try {
+ while (iterator.hasNext()) {
+ Chunk chunk = iterator.next();
+ 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
@@ -846,11 +1031,15 @@ 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
+ } finally {
+ iterator.finishedIterating();
+ }
+ // 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
@@ -862,7 +1051,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) {
@@ -1002,44 +1209,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 8e7da2c5f3..5eb14c4cd8 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) {
@@ -401,10 +409,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 e52df8096e..cebd808e27 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 f6c9bdbf52..51ea295d66 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 26b48b5ffa..353b61aa57 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 5504facd2e..fcba187bbd 100644
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
@@ -169,6 +169,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 0c952fea30..420557c270 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;
@@ -207,6 +207,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 - optimise entity tracking
final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this);
@@ -222,6 +230,41 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
}
// Paper end - optimise entity tracking
+ // Tuinity start
+ /**
+ * Overriding this field will cause memory leaks.
+ */
+ private final boolean hardCollides;
+
+ private static final java.util.Map<Class<? extends Entity>, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>());
+ {
+ Boolean hardCollides = cachedOverrides.get(this.getClass());
+ if (hardCollides == null) {
+ try {
+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("j", Entity.class);
+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("aZ");
+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod)
+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) {
+ hardCollides = Boolean.TRUE;
+ } else {
+ hardCollides = Boolean.FALSE;
+ }
+ cachedOverrides.put(this.getClass(), hardCollides);
+ }
+ catch (ThreadDeath thr) { throw thr; }
+ catch (Throwable thr) {
+ // shouldn't happen, just explode
+ throw new RuntimeException(thr);
+ }
+ }
+ this.hardCollides = hardCollides.booleanValue();
+ }
+
+ public final boolean hardCollides() {
+ return this.hardCollides;
+ }
+ // Tuinity end
+
public Entity(EntityTypes<?> entitytypes, World world) {
this.id = Entity.entityCount.incrementAndGet();
this.passengers = Lists.newArrayList();
@@ -591,7 +634,39 @@ 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();
@@ -619,7 +694,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
// Paper end
vec3d = this.a(vec3d, enummovetype);
- Vec3D vec3d1 = this.g(vec3d);
+ Vec3D vec3d1 = this.performCollision(vec3d); // Tuinity - optimise collisions
if (vec3d1.g() > 1.0E-7D) {
this.a(this.getBoundingBox().c(vec3d1));
@@ -722,11 +797,39 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
float f2 = this.getBlockSpeedFactor();
this.setMot(this.getMot().d((double) f2, 1.0D, (double) f2));
- if (this.world.c(this.getBoundingBox().shrink(0.001D)).noneMatch((iblockdata1) -> {
- return iblockdata1.a((Tag) TagsBlock.FIRE) || iblockdata1.a(Blocks.LAVA);
- }) && this.fireTicks <= 0) {
- this.setFireTicks(-this.getMaxFireTicks());
+ // Tuinity start - remove streams here
+ if (this.fireTicks <= 0) {
+ AxisAlignedBB boundingBox = this.getBoundingBox().shrink(0.001D);
+ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition();
+ int minX = MathHelper.floor(boundingBox.minX);
+ int minY = MathHelper.floor(boundingBox.minY);
+ int minZ = MathHelper.floor(boundingBox.minZ);
+ int maxX = MathHelper.floor(boundingBox.maxX);
+ int maxY = MathHelper.floor(boundingBox.maxY);
+ int maxZ = MathHelper.floor(boundingBox.maxZ);
+ boolean inFireLoaded = true;
+ boolean inFire = false;
+ fire_search:
+ for (int currY = minY; currY <= maxY; ++currY) {
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ mutablePos.setValues(currX, currY, currZ);
+ IBlockData type = this.getWorld().getTypeIfLoaded(mutablePos);
+ if (type == null) {
+ inFireLoaded = false;
+ break fire_search;
+ }
+ if (!inFire && (type.a(TagsBlock.FIRE) || type.a(Blocks.LAVA))) {
+ inFire = true;
+ }
+ }
+ }
+ }
+ if (!inFire & inFireLoaded) {
+ this.setFireTicks(-this.getMaxFireTicks());
+ }
}
+ // Tuinity end - remove streams here
if (this.aG() && this.isBurning()) {
this.playSound(SoundEffects.ENTITY_GENERIC_EXTINGUISH_FIRE, 0.7F, 1.6F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
@@ -735,6 +838,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 ap() {
@@ -815,6 +925,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) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+ AxisAlignedBB target = potentialCollisions.get(i);
+ value = AxisAlignedBB.collideX(target, 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) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+ AxisAlignedBB target = potentialCollisions.get(i);
+ value = AxisAlignedBB.collideY(target, 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) < MCUtil.COLLISION_EPSILON) {
+ return 0.0;
+ }
+ AxisAlignedBB target = potentialCollisions.get(i);
+ value = AxisAlignedBB.collideZ(target, 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, this instanceof EntityPlayer && !this.world.paperConfig.preventMovingIntoUnloadedChunks);
+
+ 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 g(Vec3D vec3d) {
AxisAlignedBB axisalignedbb = this.getBoundingBox();
VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this);
@@ -850,6 +1086,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
return vec3d1;
}
+ public static double getXZSquared(Vec3D vec3d) { return Entity.c(vec3d); } // Tuinity - OBFHELPER
public static double c(Vec3D vec3d) {
return vec3d.x * vec3d.x + vec3d.z * vec3d.z;
}
@@ -1358,6 +1595,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
return d3 * d3 + d4 * d4 + d5 * d5;
}
+ public final double getDistanceSquared(Entity other) { return this.h(other); } // Tuinity - OBFHELPER
public double h(Entity entity) {
return this.e(entity.getPositionVector());
}
@@ -1938,9 +2176,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
float f1 = this.size.width * 0.8F;
AxisAlignedBB axisalignedbb = AxisAlignedBB.g((double) f1, 0.10000000149011612D, (double) f1).d(this.locX(), this.getHeadY(), this.locZ());
- return this.world.b(this, axisalignedbb, (iblockdata, blockposition) -> {
+ return ((WorldServer)this.world).collidesWithAnyBlockOrWorldBorder(this, axisalignedbb, false, false, (iblockdata, blockposition) -> { // Tuinity - use optimised method
return iblockdata.o(this.world, blockposition);
- }).findAny().isPresent();
+ }); // Tuinity - use optimised method
}
}
@@ -1948,11 +2186,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
return EnumInteractionResult.PASS;
}
- public boolean j(Entity entity) {
+ public final boolean hardCollidesWith(Entity other) { return this.j(other); } // Tuinity - OBFHELPER
+ public boolean j(Entity entity) { // Tuinity - diff on change, hard colliding entities override this
return entity.aZ() && !this.isSameVehicle(entity);
}
- public boolean aZ() {
+ public final boolean collisionBoxIsHard() { return this.aZ(); } // Tuinity - OBFHELPER
+ public boolean aZ() {// Tuinity - diff on change, hard colliding entities override this
return false;
}
@@ -3294,12 +3534,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) {
@@ -3354,7 +3598,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/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java
index 957a351c3f..57166a543a 100644
--- a/src/main/java/net/minecraft/server/EntityCat.java
+++ b/src/main/java/net/minecraft/server/EntityCat.java
@@ -292,7 +292,7 @@ public class EntityCat extends EntityTameableAnimal {
WorldServer worldserver = worldaccess.getMinecraftWorld();
- if (worldserver instanceof WorldServer && ((WorldServer) worldserver).getStructureManager().a(this.getChunkCoordinates(), true, StructureGenerator.SWAMP_HUT).e()) {
+ if (worldserver instanceof WorldServer && ((WorldServer) worldserver).getStructureManager().getStructureStarts(this.getChunkCoordinates(), true, StructureGenerator.SWAMP_HUT, worldaccess).e()) { // Tuinity - fix deadlock on chunk gen
this.setCatType(10);
this.setPersistent();
}
diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
index 7ddf276732..23007c9ccb 100644
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
@@ -707,7 +707,13 @@ public abstract class EntityInsentient extends EntityLiving {
if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.L()) {
this.die();
} else if (!this.isPersistent() && !this.isSpecialPersistence()) {
- EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D, IEntitySelector.affectsSpawning); // Paper
+ // Tuinity start - optimise checkDespawn
+ Chunk chunk = this.getCurrentChunk();
+ EntityHuman entityhuman = chunk == null || this.world.paperConfig.hardDespawnDistance >= (31 * 16 * 31 * 16) ? this.world.findNearbyPlayer(this, -1.0D, IEntitySelector.affectsSpawning) : chunk.findNearestPlayer(this, IEntitySelector.affectsSpawning); // Paper
+ if (entityhuman == null) {
+ entityhuman = ((WorldServer)this.world).playersAffectingSpawning.isEmpty() ? null : ((WorldServer)this.world).playersAffectingSpawning.get(0);
+ }
+ // Tuinity end - optimise checkDespawn
if (entityhuman != null) {
double d0 = entityhuman.h((Entity) this); // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
index fe0334b505..87cd6fb0c0 100644
--- a/src/main/java/net/minecraft/server/EntityLiving.java
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
@@ -2847,7 +2847,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
@@ -2876,6 +2880,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 4efc40c01e..f322dccd83 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/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/IBlockAccess.java b/src/main/java/net/minecraft/server/IBlockAccess.java
index c4a83448ed..5c3eb4fc7e 100644
--- a/src/main/java/net/minecraft/server/IBlockAccess.java
+++ b/src/main/java/net/minecraft/server/IBlockAccess.java
@@ -55,7 +55,7 @@ public interface IBlockAccess {
return MovingObjectPositionBlock.a(raytrace1.a(), EnumDirection.a(vec3d.x, vec3d.y, vec3d.z), new BlockPosition(raytrace1.a()));
}
// Paper end
- Fluid fluid = this.getFluid(blockposition);
+ Fluid fluid = iblockdata.getFluid(); // Tuinity - don't need to go to world state again
Vec3D vec3d = raytrace1.b();
Vec3D vec3d1 = raytrace1.a();
VoxelShape voxelshape = raytrace1.a(iblockdata, this, blockposition);
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 25e54a1fad..b66c802d5e 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 07dbdd5609..40ca3364d4 100644
--- a/src/main/java/net/minecraft/server/IEntityAccess.java
+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
@@ -52,16 +52,26 @@ public interface IEntityAccess {
return this.b(oclass, axisalignedbb, IEntitySelector.g);
}
+ // Tuinity start - optimise hard collision
+ /**
+ * Not guaranteed to only return hard colliding entities
+ */
+ default List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+ return this.getEntities(entity, axisalignedbb, predicate);
+ }
+ // Tuinity end - optimise hard collision
+
default Stream<VoxelShape> c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
if (axisalignedbb.a() < 1.0E-7D) {
return Stream.empty();
} else {
AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
- return this.getEntities(entity, axisalignedbb1, predicate.and((entity1) -> {
+ if (predicate == null) predicate = (e) -> true; // Tuinity - allow nullable
+ predicate = predicate.and((entity1) -> { // Tuinity - optimise entity hard collisions // Tuinity - allow nullable
boolean flag;
- if (entity1.getBoundingBox().c(axisalignedbb1)) {
+ if (true || entity1.getBoundingBox().c(axisalignedbb1)) { // Tuinity - always true, wtf did they think this.getEntities(entity, axisalignedbb1) does?
label25:
{
if (entity == null) {
@@ -79,7 +89,7 @@ public interface IEntityAccess {
flag = false;
return flag;
- })).stream().map(Entity::getBoundingBox).map(VoxelShapes::a);
+ }); return ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb1, predicate) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate)).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); // Tuinity - optimise entity hard collisions
}
}
@@ -179,12 +189,12 @@ public interface IEntityAccess {
}
@Nullable
- default <T extends EntityLiving> T a(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) {
+ default <T extends EntityLiving> T a(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { // Tuinity - diff on change, override in World - this should be "get closest entity by class that matches path finder target condition"
return this.a(this.a(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix
}
@Nullable
- default <T extends EntityLiving> T b(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) {
+ default <T extends EntityLiving> T b(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { // Tuinity - diff on change, override in World - this should be "get closest entity by class that matches path finder target condition"
return this.a(this.b(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix
}
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/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java
index c61cd50df0..d987483255 100644
--- a/src/main/java/net/minecraft/server/LoginListener.java
+++ b/src/main/java/net/minecraft/server/LoginListener.java
@@ -234,7 +234,7 @@ public class LoginListener implements PacketLoginInListener {
s = (new BigInteger(MinecraftEncryption.a("", this.server.getKeyPair().getPublic(), this.loginKey))).toString(16);
this.g = LoginListener.EnumProtocolState.AUTHENTICATING;
- this.networkManager.a(cipher, cipher1);
+ this.networkManager.a(this.loginKey); // Tuinity
} catch (CryptographyException cryptographyexception) {
throw new IllegalStateException("Protocol error", cryptographyexception);
}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index ff74be1451..653ba0f1d8 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -38,6 +38,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
public final class MCUtil {
+ public static final double COLLISION_EPSILON = 1.0E-7; // Tuinity - Just in case mojang changes this...
public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
0, 2, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index d0ffe5790e..08518ff57b 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -155,6 +155,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
//public ConsoleReader reader; // Paper
public static int currentTick = 0; // Paper - Further improve tick loop
+ public static long currentTickLong = 0L; // Tuinity
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
public boolean serverAutoSave = false; // Paper
@@ -955,6 +956,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
LOGGER.info("Done ({})! For help, type \"help\"", doneTime);
// Paper end
+ com.tuinity.tuinity.config.TuinityConfig.createWorldSections = false; // Tuinity - don't let plugin created worlds fill our config
org.spigotmc.WatchdogThread.tick(); // Paper
org.spigotmc.WatchdogThread.hasStarted = true; // Paper
Arrays.fill( recentTps, 20 );
@@ -972,6 +974,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
this.lastOverloadTime = this.nextTick;
}
+ ++MinecraftServer.currentTickLong; // Tuinity
if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 )
{
final long diff = curTime - tickSection;
@@ -986,7 +989,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
@@ -1079,6 +1082,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);
@@ -1092,22 +1165,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) {
@@ -1134,6 +1192,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
private boolean bb() {
if (super.executeNext()) {
+ this.executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
return true;
} else {
if (this.canSleepForTick()) {
@@ -1201,7 +1260,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();
@@ -1266,6 +1325,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;
@@ -1292,16 +1353,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();
@@ -1312,7 +1373,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
@@ -1354,11 +1415,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;
@@ -1452,7 +1514,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 1558c5f825..55fa391170 100644
--- a/src/main/java/net/minecraft/server/NavigationAbstract.java
+++ b/src/main/java/net/minecraft/server/NavigationAbstract.java
@@ -85,7 +85,7 @@ public abstract class NavigationAbstract {
@Nullable
public PathEntity a(Stream<BlockPosition> stream, int i) {
- return this.a((Set) stream.collect(Collectors.toSet()), 8, false, i);
+ return this.a((Set) stream.collect(Collectors.toSet()), 8, false, i); // Tuinity - diff on change, inlined into SensorNearestBed
}
@Nullable
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index 7a84ea4116..eb6b81d88f 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -27,6 +27,8 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
+import io.netty.util.concurrent.AbstractEventExecutor; // Tuinity
+
public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
private static final Logger LOGGER = LogManager.getLogger();
@@ -71,6 +73,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;
}
@@ -142,8 +177,63 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
if (MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot
}
+ // Tuinity start - packet limiter
+ protected final Object PACKET_LIMIT_LOCK = new Object();
+ protected final com.tuinity.tuinity.util.IntervalledCounter allPacketCounts = com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit != null ? new com.tuinity.tuinity.util.IntervalledCounter(
+ (long)(com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.packetLimitInterval * 1.0e9)
+ ) : null;
+ protected final java.util.Map<Class<? extends net.minecraft.server.Packet<?>>, com.tuinity.tuinity.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>();
+
+ private boolean stopReadingPackets;
+ private void killForPacketSpam() {
+ this.sendPacket(new PacketPlayOutKickDisconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]), (future) -> {
+ this.close(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]);
+ });
+ this.stopReading();
+ this.stopReadingPackets = true;
+ }
+ // Tuinity end - packet limiter
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) throws Exception {
if (this.channel.isOpen()) {
+ // Tuinity start - packet limiter
+ if (this.stopReadingPackets) {
+ return;
+ }
+ if (this.allPacketCounts != null ||
+ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.containsKey(packet.getClass())) {
+ long time = System.nanoTime();
+ synchronized (PACKET_LIMIT_LOCK) {
+ if (this.allPacketCounts != null) {
+ this.allPacketCounts.updateAndAdd(1, time);
+ if (this.allPacketCounts.getRate() >= com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.maxPacketRate) {
+ this.killForPacketSpam();
+ return;
+ }
+ }
+
+ for (Class<?> check = packet.getClass(); check != Object.class; check = check.getSuperclass()) {
+ com.tuinity.tuinity.config.TuinityConfig.PacketLimit packetSpecificLimit =
+ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.get(check);
+ if (packetSpecificLimit == null) {
+ continue;
+ }
+ com.tuinity.tuinity.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> {
+ return new com.tuinity.tuinity.util.IntervalledCounter((long)(packetSpecificLimit.packetLimitInterval * 1.0e9));
+ });
+ counter.updateAndAdd(1, time);
+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate) {
+ switch (packetSpecificLimit.violateAction) {
+ case DROP:
+ return;
+ case KICK:
+ this.killForPacketSpam();
+ return;
+ }
+ }
+ }
+ }
+ }
+ // Tuinity end - packet limiter
try {
a(packet, this.packetListener);
} catch (CancelledPacketHandleException cancelledpackethandleexception) {
@@ -217,7 +307,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 +333,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 +363,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);
@@ -285,39 +383,83 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
}
// Paper end
} else {
- this.channel.eventLoop().execute(() -> {
- if (enumprotocol != enumprotocol1) {
- this.setProtocol(enumprotocol);
- }
+ // Tuinity start - optimise packets that are not flushed
+ Runnable choice1 = null;
+ AbstractEventExecutor.LazyRunnable choice2 = null;
+ // note: since the type is not dynamic here, we need to actually copy the old executor code
+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code.
+ if (flush) {
+ choice1 = () -> {
+ if (enumprotocol != enumprotocol1) {
+ this.setProtocol(enumprotocol);
+ }
- // Paper start
- if (!isConnected()) {
- packet.onPacketDispatchFinish(player, null);
- return;
- }
- try {
+ // Paper start
+ if (!isConnected()) {
+ packet.onPacketDispatchFinish(player, null);
+ return;
+ }
+ try {
+ // Paper end
+ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter
+
+
+ if (genericfuturelistener != null) {
+ channelfuture1.addListener(genericfuturelistener);
+ }
+ // Paper start
+ if (packet.hasFinishListener()) {
+ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
+ }
+ // Paper end
+
+ channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+ // Paper start
+ } catch (Exception e) {
+ LOGGER.error("NetworkException: " + player, e);
+ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
+ packet.onPacketDispatchFinish(player, null);
+ }
// Paper end
- ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet);
-
-
- if (genericfuturelistener != null) {
- channelfuture1.addListener(genericfuturelistener);
- }
- // Paper start
- if (packet.hasFinishListener()) {
- channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
- }
- // Paper end
+ };
+ } else {
+ // explicitly declare a variable to make the lambda use the type
+ choice2 = () -> {
+ if (enumprotocol != enumprotocol1) {
+ this.setProtocol(enumprotocol);
+ }
- channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
- // Paper start
- } catch (Exception e) {
- LOGGER.error("NetworkException: " + player, e);
- close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
- packet.onPacketDispatchFinish(player, null);
- }
- // Paper end
- });
+ // Paper start
+ if (!isConnected()) {
+ packet.onPacketDispatchFinish(player, null);
+ return;
+ }
+ try {
+ // Paper end
+ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter
+
+
+ if (genericfuturelistener != null) {
+ channelfuture1.addListener(genericfuturelistener);
+ }
+ // Paper start
+ if (packet.hasFinishListener()) {
+ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
+ }
+ // Paper end
+
+ channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+ // Paper start
+ } catch (Exception e) {
+ LOGGER.error("NetworkException: " + player, e);
+ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
+ packet.onPacketDispatchFinish(player, null);
+ }
+ // Paper end
+ };
+ }
+ this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2);
+ // Tuinity end - optimise packets that are not flushed
}
}
@@ -340,6 +482,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 +491,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;
@@ -433,10 +583,16 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel;
}
- public void a(Cipher cipher, Cipher cipher1) {
+ public void a(javax.crypto.SecretKey secretkey) { // Tuinity
this.n = true;
- this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(cipher));
- this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(cipher1));
+ // Tuinity start
+ try {
+ this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(/*MinecraftEncryption.a(2, secretkey)*/ secretkey));
+ this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(/*MinecraftEncryption.a(1, secretkey)*/ secretkey));
+ } catch (java.security.GeneralSecurityException e) {
+ throw new RuntimeException("Couldn't enable encryption", e);
+ }
+ // Tuinity end
}
public boolean isConnected() {
diff --git a/src/main/java/net/minecraft/server/PacketCompressor.java b/src/main/java/net/minecraft/server/PacketCompressor.java
index 3cdd07cad8..50b2a8dfbd 100644
--- a/src/main/java/net/minecraft/server/PacketCompressor.java
+++ b/src/main/java/net/minecraft/server/PacketCompressor.java
@@ -7,14 +7,18 @@ import java.util.zip.Deflater;
public class PacketCompressor extends MessageToByteEncoder<ByteBuf> {
- private final byte[] a = new byte[8192];
- private final Deflater b;
+ // Tuinity start - use Velocity natives
+// private final byte[] a = new byte[8192];
+// private final Deflater b;
private int c;
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor;
public PacketCompressor(int i) {
this.c = i;
- this.b = new Deflater();
+// this.b = new Deflater();
+ this.compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1);
}
+ // Tuinity end
protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception {
int i = bytebuf.readableBytes();
@@ -24,24 +28,46 @@ public class PacketCompressor extends MessageToByteEncoder<ByteBuf> {
packetdataserializer.d(0);
packetdataserializer.writeBytes(bytebuf);
} else {
- byte[] abyte = new byte[i];
-
- bytebuf.readBytes(abyte);
- packetdataserializer.d(abyte.length);
- this.b.setInput(abyte, 0, i);
- this.b.finish();
-
- while (!this.b.finished()) {
- int j = this.b.deflate(this.a);
-
- packetdataserializer.writeBytes(this.a, 0, j);
+ // Tuinity start - delegate to Velocity natives
+// byte[] abyte = new byte[i];
+//
+// bytebuf.readBytes(abyte);
+// packetdataserializer.d(abyte.length);
+// this.b.setInput(abyte, 0, i);
+// this.b.finish();
+//
+// while (!this.b.finished()) {
+// int j = this.b.deflate(this.a);
+//
+// packetdataserializer.writeBytes(this.a, 0, j);
+// }
+//
+// this.b.reset();
+ packetdataserializer.d(i);
+ ByteBuf source = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(),
+ this.compressor, bytebuf);
+ try {
+ this.compressor.deflate(source, bytebuf1);
+ } finally {
+ source.release();
}
-
- this.b.reset();
+ // Tuinity end
}
}
+ // Tuinity start
+ @Override
+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, msg.readableBytes() + 1);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ this.compressor.close();
+ }
+ // Tuinity end
+
public void a(int i) {
this.c = i;
}
diff --git a/src/main/java/net/minecraft/server/PacketDecompressor.java b/src/main/java/net/minecraft/server/PacketDecompressor.java
index 23c850be01..4bab19a52b 100644
--- a/src/main/java/net/minecraft/server/PacketDecompressor.java
+++ b/src/main/java/net/minecraft/server/PacketDecompressor.java
@@ -10,13 +10,17 @@ import java.util.zip.Inflater;
public class PacketDecompressor extends ByteToMessageDecoder {
- private final Inflater a;
+ // Tuinity start - use Velocity natives
+ //private final Inflater a;
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor;
private int b;
public PacketDecompressor(int i) {
this.b = i;
- this.a = new Inflater();
+ //this.a = new Inflater();
+ this.compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1);
}
+ // Tuinity end
protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List<Object> list) throws Exception {
if (bytebuf.readableBytes() != 0) {
@@ -34,20 +38,41 @@ public class PacketDecompressor extends ByteToMessageDecoder {
throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of " + 2097152);
}
- byte[] abyte = new byte[packetdataserializer.readableBytes()];
-
- packetdataserializer.readBytes(abyte);
- this.a.setInput(abyte);
- byte[] abyte1 = new byte[i];
-
- this.a.inflate(abyte1);
- list.add(Unpooled.wrappedBuffer(abyte1));
- this.a.reset();
+ // Tuinity start
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(), compressor, bytebuf);
+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelhandlercontext.alloc(), compressor, i);
+ try {
+ compressor.inflate(compatibleIn, uncompressed, i);
+ list.add(uncompressed);
+ bytebuf.clear();
+ } catch (Exception e) {
+ uncompressed.release();
+ throw e;
+ } finally {
+ compatibleIn.release();
+ }
+// byte[] abyte = new byte[packetdataserializer.readableBytes()];
+//
+// packetdataserializer.readBytes(abyte);
+// this.a.setInput(abyte);
+// byte[] abyte1 = new byte[i];
+//
+// this.a.inflate(abyte1);
+// list.add(Unpooled.wrappedBuffer(abyte1));
+// this.a.reset();
+ // Tuinity end
}
}
}
+ // Tuinity start
+ @Override
+ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+ this.compressor.close();
+ }
+ // Tuinity end
+
public void a(int i) {
this.b = i;
}
diff --git a/src/main/java/net/minecraft/server/PacketDecrypter.java b/src/main/java/net/minecraft/server/PacketDecrypter.java
index c85f291c5b..771cc0f4fa 100644
--- a/src/main/java/net/minecraft/server/PacketDecrypter.java
+++ b/src/main/java/net/minecraft/server/PacketDecrypter.java
@@ -8,13 +8,24 @@ import javax.crypto.Cipher;
public class PacketDecrypter extends MessageToMessageDecoder<ByteBuf> {
- private final PacketEncryptionHandler a;
+ // Tuinity start
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher;
+ //private final PacketEncryptionHandler a;
- public PacketDecrypter(Cipher cipher) {
- this.a = new PacketEncryptionHandler(cipher);
+ public PacketDecrypter(javax.crypto.SecretKey key /* Cipher cipher */) throws java.security.GeneralSecurityException {
+ //this.a = new PacketEncryptionHandler(cipher);
+ this.cipher = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
}
protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List<Object> list) throws Exception {
- list.add(this.a.a(channelhandlercontext, bytebuf));
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(), cipher, bytebuf).slice();
+ try {
+ cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
}
+ // Tuinity end
}
diff --git a/src/main/java/net/minecraft/server/PacketEncrypter.java b/src/main/java/net/minecraft/server/PacketEncrypter.java
index e353694768..aba14794cc 100644
--- a/src/main/java/net/minecraft/server/PacketEncrypter.java
+++ b/src/main/java/net/minecraft/server/PacketEncrypter.java
@@ -5,15 +5,38 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Cipher;
-public class PacketEncrypter extends MessageToByteEncoder<ByteBuf> {
+// Tuinity start
+// We rewrite this class as the Velocity natives support in-place encryption
+import io.netty.handler.codec.MessageToMessageEncoder; // An unfortunate import, but this is required to fix a compiler error
+public class PacketEncrypter extends MessageToMessageEncoder<ByteBuf> {
- private final PacketEncryptionHandler a;
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher;
+ //private final PacketEncryptionHandler a;
- public PacketEncrypter(Cipher cipher) {
- this.a = new PacketEncryptionHandler(cipher);
+ public PacketEncrypter(javax.crypto.SecretKey key /* Cipher cipher */) throws java.security.GeneralSecurityException {
+ // this.a = new PacketEncryptionHandler(cipher);
+ this.cipher = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
}
- protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception {
- this.a.a(bytebuf, bytebuf1);
+// protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception {
+// this.a.a(bytebuf, bytebuf1);
+// }
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, java.util.List<Object> out) throws Exception {
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(ctx.alloc(), this.cipher, msg);
+ try {
+ this.cipher.process(compatible);
+ out.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
}
}
+// Tuinity end
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
index 5094a5d6fb..72fdbf1534 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 int[] 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;
// Paper start - Async-Anti-Xray - Set the ready flag to true
@@ -31,14 +31,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) {
+ public PacketPlayOutMapChunk(Chunk chunk, int i) { 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();
@@ -46,27 +48,12 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
this.b = chunkcoordintpair.z;
this.h = i == 65535;
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().a();
- }
-
- 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.j()), chunk, i, chunkPacketInfo);
+ // Tuinity - move this after the tile entity logic, we need to determine whether we're going to split
+ // Tuinity - before writing chunk block data
+ // Tuinity - note: for future maintenance, git will prefer the smallest diff, so if moving the TE code is
+ // Tuinity - a smaller diff, do that, else move the chunk writing - this makes sure the start/end is correct
+ Iterator iterator; // Tuinity - move declaration up
+ Entry entry; // Tuinity - move delcaration up
// Paper end
this.g = Lists.newArrayList();
iterator = chunk.getTileEntities().entrySet().iterator();
@@ -79,8 +66,16 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
int j = blockposition.getY() >> 4;
if (this.f() || (i & 1 << j) != 0) {
+ // Tuinity start - improve oversized chunk data packet handling
+ ++totalTileEntities;
+ if (totalTileEntities > TE_SPLIT_LIMIT) {
+ this.mustSplit = true;
+ this.getTileEntityData().clear();
+ this.extraPackets.clear();
+ break;
+ }
// Paper start - improve oversized chunk data packet handling
- if (++totalTileEntities > TE_LIMIT) {
+ if (totalTileEntities > TE_LIMIT) { // Tuinity end - improve oversized chunk data packet handling
PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket();
if (updatePacket != null) {
this.extraPackets.add(updatePacket);
@@ -94,7 +89,42 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
this.g.add(nbttagcompound);
}
}
+ // Tuinity start - moved after tile entity gathering
+ iterator = chunk.f().iterator(); // Declared earlier
+
+ 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().a();
+ }
+
+ 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.j()), chunk, i, chunkPacketInfo);
+ // Tuinity end - moved after tile entity gathering
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));
+ }
+ }
+ }
+ // Tuinity end - improve oversized chunk data packet handling
}
// Paper start - Async-Anti-Xray - Getter and Setter for the ready flag
@@ -185,7 +215,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
}
@@ -202,7 +232,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/PathType.java b/src/main/java/net/minecraft/server/PathType.java
index fb37f5b500..52a2d3db7d 100644
--- a/src/main/java/net/minecraft/server/PathType.java
+++ b/src/main/java/net/minecraft/server/PathType.java
@@ -4,6 +4,8 @@ public enum PathType {
BLOCKED(-1.0F), OPEN(0.0F), WALKABLE(0.0F), WALKABLE_DOOR(0.0F), TRAPDOOR(0.0F), FENCE(-1.0F), LAVA(-1.0F), WATER(8.0F), WATER_BORDER(8.0F), RAIL(0.0F), UNPASSABLE_RAIL(-1.0F), DANGER_FIRE(8.0F), DAMAGE_FIRE(16.0F), DANGER_CACTUS(8.0F), DAMAGE_CACTUS(-1.0F), DANGER_OTHER(8.0F), DAMAGE_OTHER(-1.0F), DOOR_OPEN(0.0F), DOOR_WOOD_CLOSED(-1.0F), DOOR_IRON_CLOSED(-1.0F), BREACH(4.0F), LEAVES(-1.0F), STICKY_HONEY(8.0F), COCOA(0.0F);
+ PathType belowOverride; // Tuinity
+
private final float y;
private PathType(float f) {
diff --git a/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java b/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java
index 475c0764b9..9f48d476c0 100644
--- a/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java
+++ b/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java
@@ -50,7 +50,7 @@ public class PathfinderGoalMoveThroughVillage extends PathfinderGoal {
if (!worldserver.a_(blockposition1)) {
return Double.NEGATIVE_INFINITY;
} else {
- Optional<BlockPosition> optional = worldserver.y().c(VillagePlaceType.b, this::a, blockposition1, 10, VillagePlace.Occupancy.IS_OCCUPIED);
+ Optional<BlockPosition> optional = Optional.ofNullable(BehaviorFindPosition.findAnyFirstPoi(worldserver.y(), VillagePlaceType.b, this::a, blockposition1, 10, VillagePlace.Occupancy.IS_OCCUPIED)); // Tuinity - remove streams here
return !optional.isPresent() ? Double.NEGATIVE_INFINITY : -((BlockPosition) optional.get()).j(blockposition);
}
@@ -59,7 +59,7 @@ public class PathfinderGoalMoveThroughVillage extends PathfinderGoal {
if (vec3d == null) {
return false;
} else {
- Optional<BlockPosition> optional = worldserver.y().c(VillagePlaceType.b, this::a, new BlockPosition(vec3d), 10, VillagePlace.Occupancy.IS_OCCUPIED);
+ Optional<BlockPosition> optional = Optional.ofNullable(BehaviorFindPosition.findAnyFirstPoi(worldserver.y(), VillagePlaceType.b, this::a, new BlockPosition(vec3d), 10, VillagePlace.Occupancy.IS_OCCUPIED)); // Tuinity - remove streams here
if (!optional.isPresent()) {
return false;
diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java
index 74e81e1e4a..33804e6893 100644
--- a/src/main/java/net/minecraft/server/PathfinderNormal.java
+++ b/src/main/java/net/minecraft/server/PathfinderNormal.java
@@ -421,6 +421,12 @@ public class PathfinderNormal extends PathfinderAbstract {
if (pathtype == PathType.OPEN && j >= 1) {
PathType pathtype1 = b(iblockaccess, blockposition_mutableblockposition.d(i, j - 1, k));
+ // Tuinity start - reduce pathfinder branches
+ if (pathtype1.belowOverride != null) {
+ pathtype = pathtype1.belowOverride;
+ } else {
+ PathType original1 = pathtype1;
+ // Tuinity end - reduce pathfinder branches
pathtype = pathtype1 != PathType.WALKABLE && pathtype1 != PathType.OPEN && pathtype1 != PathType.WATER && pathtype1 != PathType.LAVA ? PathType.WALKABLE : PathType.OPEN;
if (pathtype1 == PathType.DAMAGE_FIRE) {
pathtype = PathType.DAMAGE_FIRE;
@@ -437,6 +443,7 @@ public class PathfinderNormal extends PathfinderAbstract {
if (pathtype1 == PathType.STICKY_HONEY) {
pathtype = PathType.STICKY_HONEY;
}
+ original1.belowOverride = pathtype; } // Tuinity - reduce pathfinder branches
}
if (pathtype == PathType.WALKABLE) {
@@ -462,22 +469,29 @@ public class PathfinderNormal extends PathfinderAbstract {
pathtype = PathType.BLOCKED;
} else {
// Paper end
-
+ // Tuinity start - reduce pathfinder branching
+ if (iblockdata.neighbourOverridePathType == PathType.OPEN) {
+ continue;
+ } else if (iblockdata.neighbourOverridePathType != null) {
+ return iblockdata.neighbourOverridePathType;
+ }
+ // Tuinity end - reduce pathfinder branching
if (iblockdata.a(Blocks.CACTUS)) {
- return PathType.DANGER_CACTUS;
+ return iblockdata.neighbourOverridePathType = PathType.DANGER_CACTUS; // Tuinity - reduce pathfinder branching
}
if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) {
- return PathType.DANGER_OTHER;
+ return iblockdata.neighbourOverridePathType = PathType.DANGER_OTHER; // Tuinity - reduce pathfinder branching
}
if (a(iblockdata)) {
- return PathType.DANGER_FIRE;
+ return iblockdata.neighbourOverridePathType = PathType.DANGER_FIRE; // Tuinity - reduce pathfinder branching
}
if (iblockdata.getFluid().a((Tag) TagsFluid.WATER)) { // Paper - remove another getType call
- return PathType.WATER_BORDER;
+ return iblockdata.neighbourOverridePathType = PathType.WATER_BORDER; // Tuinity - reduce pathfinder branching
}
+ iblockdata.neighbourOverridePathType = PathType.OPEN; // Tuinity - reduce pathfinder branching
} // Paper
}
}
@@ -490,6 +504,20 @@ public class PathfinderNormal extends PathfinderAbstract {
protected static PathType b(IBlockAccess iblockaccess, BlockPosition blockposition) {
IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper
if (iblockdata == null) return PathType.BLOCKED; // Paper
+ // Tuinity start - reduce pathfinder branches
+ if (iblockdata.staticPathType != null) {
+ return iblockdata.staticPathType;
+ }
+ if (iblockdata.getShapeCache() == null) {
+ // while it might be called static, it might vary on shape! However, only a few blocks have variable shape.
+ // So we rarely enter here.
+ return getStaticTypeSlow(iblockaccess, blockposition, iblockdata);
+ } else {
+ return iblockdata.staticPathType = getStaticTypeSlow(iblockaccess, blockposition, iblockdata);
+ }
+ }
+ protected static PathType getStaticTypeSlow(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata) {
+ // Tuinity end - reduce pathfinder branches
Block block = iblockdata.getBlock();
Material material = iblockdata.getMaterial();
diff --git a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java
index 253377c623..3ebe3d0dc4 100644
--- a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java
+++ b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java
@@ -51,6 +51,7 @@ public class PathfinderTargetCondition {
return this;
}
+ public final boolean test(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { return this.a(entityliving, entityliving1); } // Tuinity - OBFHELPER
public boolean a(@Nullable EntityLiving entityliving, EntityLiving entityliving1) {
if (entityliving == entityliving1) {
return false;
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 11a67ca18f..80cc96a4de 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -56,6 +56,12 @@ public class PlayerChunk {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Tuinity start - optimise checkDespawn
+ Chunk chunk = this.getFullChunkIfCached();
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache();
+ }
+ // Tuinity end - optimise checkDespawn
}
// Paper end - optimise isOutsideOfRange
// Paper start - optimize chunk status progression without jumping through thread pool
@@ -362,7 +368,7 @@ public class PlayerChunk {
if (!blockposition.isValidLocation()) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks
Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
- if (chunk != null) {
+ if (chunk != null && (blockposition.getY() >= 0 && blockposition.getY() <= 255)) { // Tuinity - updates cannot happen in sections that don't exist
byte b0 = (byte) SectionPosition.a(blockposition.getY());
if (b0 >= this.dirtyBlocks.length) return; // CraftBukkit - SPIGOT-6086
@@ -421,7 +427,7 @@ public class PlayerChunk {
this.a(world, blockposition, iblockdata);
} else {
ChunkSection chunksection = chunk.getSections()[sectionposition.getY()];
- if (chunksection == null) chunksection = new ChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found
+ //if (chunksection == null) chunksection = new ChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found // Tuinity - handled better by spigot
PacketPlayOutMultiBlockChange packetplayoutmultiblockchange = new PacketPlayOutMultiBlockChange(sectionposition, shortset, chunksection, this.x);
this.a(packetplayoutmultiblockchange, false);
@@ -504,6 +510,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);
@@ -559,6 +566,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;
@@ -568,7 +576,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(() -> {
@@ -633,7 +642,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();
@@ -664,7 +674,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();
@@ -674,6 +685,9 @@ public class PlayerChunk {
// Paper start - rewrite ticklistserver
PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z);
// Paper end - rewrite ticklistserver
+ // Tuinity start - ticking chunk set
+ PlayerChunk.this.chunkMap.world.getChunkProvider().tickingChunks.add(tickingChunk);
+ // Tuinity end - ticking chunk set
}
});
@@ -684,6 +698,12 @@ public class PlayerChunk {
if (flag4 && !flag5) {
this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
+ // Tuinity start - ticking chunk set
+ Chunk chunkIfCached = this.getFullChunkIfCached();
+ if (chunkIfCached != null) {
+ this.chunkMap.world.getChunkProvider().tickingChunks.remove(chunkIfCached);
+ }
+ // Tuinity end - ticking chunk set
}
boolean flag6 = playerchunk_state.isAtLeast(PlayerChunk.State.ENTITY_TICKING);
@@ -695,13 +715,16 @@ 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 - entity ticking chunk set
+ PlayerChunk.this.chunkMap.world.getChunkProvider().entityTickingChunks.add(entityTickingChunk);
+ // Tuinity end - entity ticking chunk set
}
@@ -713,6 +736,12 @@ public class PlayerChunk {
if (flag6 && !flag7) {
this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
+ // Tuinity start - entity ticking chunk set
+ Chunk chunkIfCached = this.getFullChunkIfCached();
+ if (chunkIfCached != null) {
+ this.chunkMap.world.getChunkProvider().entityTickingChunks.remove(chunkIfCached);
+ }
+ // Tuinity end - entity ticking chunk set
}
// Paper start - raise IO/load priority if priority changes, use our preferred priority
@@ -738,7 +767,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 6c399bcea0..2a8455b813 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -121,31 +121,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,8 +195,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
// Paper end - no-tick view distance
+ // Tuinity start - optimise checkDespawn
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap;
+ // Tuinity end - optimise checkDespawn
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
@@ -227,9 +228,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
player.needsChunkCenterUpdate = false;
// Paper end - no-tick view distance
+ // Tuinity start - optimise checkDespawn
+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, 33);
+ // Tuinity end - optimise checkDespawn
}
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);
@@ -244,9 +249,13 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.playerViewDistanceTickMap.remove(player);
this.playerViewDistanceNoTickMap.remove(player);
// Paper end - no-tick view distance
+ // Tuinity start - optimise checkDespawn
+ this.playerGeneralAreaMap.remove(player);
+ // Tuinity end - optimise checkDespawn
}
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
@@ -274,9 +283,27 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
player.needsChunkCenterUpdate = false;
// Paper end - no-tick view distance
+ // Tuinity start - optimise checkDespawn
+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, 33);
+ // Tuinity end - optimise checkDespawn
}
// Paper end
+ // Tuinity start
+ public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator<RegionData> {
+
+ ;
+
+ @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
+
private final java.util.concurrent.ExecutorService lightThread;
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);
@@ -444,6 +471,26 @@ 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 = null;//new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data");
+ // Tuinity end
+ // Tuinity start - optimise checkDespawn
+ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ);
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache(newState);
+ }
+ },
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ);
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache(newState);
+ }
+ });
+ // Tuinity end - optimise checkDespawn
}
// Paper start - Chunk Prioritization
public void queueHolderUpdate(PlayerChunk playerchunk) {
@@ -756,6 +803,8 @@ 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 (this.unloadingPlayerChunk) { MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity
if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) {
return playerchunk;
} else {
@@ -778,6 +827,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);
@@ -970,7 +1020,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;
@@ -1004,7 +1054,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());
@@ -1012,6 +1062,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
// Paper end
+ boolean unloadingPlayerChunk = false; // Tuinity - do not allow ticket level changes while unloading chunks
+
private void a(long i, PlayerChunk playerchunk) {
CompletableFuture<IChunkAccess> completablefuture = playerchunk.getChunkSave();
Consumer<IChunkAccess> consumer = (ichunkaccess) -> { // CraftBukkit - decompile error
@@ -1020,7 +1072,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
if (completablefuture1 != completablefuture) {
this.a(i, playerchunk);
} else {
- if (this.pendingUnload.remove(i, playerchunk) && ichunkaccess != null) {
+ // Tuinity start - do not allow ticket level changes while unloading chunks
+ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload");
+ boolean unloadingBefore = this.unloadingPlayerChunk;
+ this.unloadingPlayerChunk = true;
+ try {
+ // Tuinity end - do not allow ticket level changes while unloading chunks
+ // Tuinity start
+ boolean removed;
+ if ((removed = this.pendingUnload.remove(i, playerchunk)) && ichunkaccess != null) { // Tuinity end
if (ichunkaccess instanceof Chunk) {
((Chunk) ichunkaccess).setLoaded(false);
}
@@ -1044,6 +1104,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.lightEngine.queueUpdate();
this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null);
}
+ //if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks
}
};
@@ -1059,6 +1121,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 {
@@ -1246,7 +1309,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) {
@@ -1498,6 +1564,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) {
@@ -1511,6 +1578,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;
@@ -1626,7 +1694,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
this.world, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound,
- com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
+ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - writes are async, no need for priority
return;
}
super.write(chunkcoordintpair, nbttagcompound);
@@ -1710,6 +1778,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
}
// Paper end
+ // Tuinity start
+ public PlayerChunk getUnloadingPlayerChunk(int chunkX, int chunkZ) {
+ return this.pendingUnload.get(ChunkCoordIntPair.pair(chunkX, chunkZ));
+ }
+ // Tuinity end
// Paper start - async io
@@ -2037,22 +2110,25 @@ 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());
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Chunk> iterator = this.world.getChunkProvider().entityTickingChunks.iterator();
+ try {
+ while (iterator.hasNext()) {
+ Chunk chunk = iterator.next();
+ 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 {
+ iterator.finishedIterating();
}
} finally {
- this.world.timings.tracker2.stopTiming();
+ this.world.timings.tracker1.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 563ae7355f..b89caa8ad1 100644
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
@@ -415,7 +415,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;
}
@@ -1054,7 +1056,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);
}
@@ -1124,7 +1126,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;
}
@@ -1180,6 +1182,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.setOnGround(packetplayinflying.b()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move
// Paper start - prevent position desync
if (this.teleportPos != null) {
@@ -1204,7 +1207,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 485b609bb5..614cfacb1e 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 5b0cd414ca..a3ac883500 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 1751fb6934..1ffa213a81 100644
--- a/src/main/java/net/minecraft/server/RegionFile.java
+++ b/src/main/java/net/minecraft/server/RegionFile.java
@@ -5,6 +5,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -29,15 +30,350 @@ public class RegionFile implements AutoCloseable {
private static final Logger LOGGER = LogManager.getLogger();
private static final ByteBuffer c = ByteBuffer.allocateDirect(1);
private final FileChannel dataFile;
- private final java.nio.file.Path e;
- private final RegionFileCompression f;
+ private final java.nio.file.Path e; private final java.nio.file.Path getContainingDataFolder() { return this.e; } // Tuinity - OBFHELPER
+ private final RegionFileCompression f; private final RegionFileCompression getRegionFileCompression() { return this.f; } // Tuinity - OBFHELPER
private final ByteBuffer g;
- private final IntBuffer h;
- private final IntBuffer i;
+ private final IntBuffer h; private final IntBuffer getOffsets() { return this.h; } // Tuinity - OBFHELPER
+ private final IntBuffer i; private final IntBuffer getTimestamps() { return this.i; } // Tuinity - OBFHELPER
@VisibleForTesting
protected final RegionFileBitSet freeSectors;
public final File file; // Paper
+ // 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((java.io.DataInput)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() + ", data will be lost", 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((DataInput)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
@@ -65,11 +401,22 @@ 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.g = ByteBuffer.allocateDirect(8192);
+ this.canRecalcHeader = canRecalcHeader;
+ // Tuinity end - add can recalc flag
this.file = java_nio_file_path.toFile(); // Paper
initOversizedState(); // Paper
this.freeSectors = new RegionFileBitSet();
@@ -97,14 +444,16 @@ public class RegionFile implements AutoCloseable {
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", java_nio_file_path, i);
}
- long j = Files.size(java_nio_file_path);
+ final long j = Files.size(java_nio_file_path); final long regionFileSize = j;
+ boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption
+ boolean hasBackedUp = false; // Tuinity - recalculate header on header corruption
for (int k = 0; k < 1024; ++k) {
- int l = this.h.get(k);
+ int l = this.h.get(k); final int headerLocation = k; // Tuinity - we expect this to be the header location
if (l != 0) {
- int i1 = b(l);
- int j1 = a(l);
+ final int i1 = b(l); final int offset = i1; // Tuinity - we expect this to be offset in file in sectors
+ int j1 = a(l); final int sectorLength; // Tuinity - diff on change, we expect this to be sector length of region - watch out for reassignments
// Spigot start
if (j1 == 255) {
// We're maxed out, so we need to read the proper length from the section
@@ -112,33 +461,105 @@ public class RegionFile implements AutoCloseable {
this.dataFile.read(realLen, i1 * 4096);
j1 = (realLen.getInt(0) + 4) / 4096 + 1;
}
+ sectorLength = j1; // Tuinity - diff on change, we expect this to be sector length of region
// Spigot end
if (i1 < 2) {
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", java_nio_file_path, k, i1);
- this.h.put(k, 0);
- } else if (j1 == 0) {
+ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change
+ } else if (j1 <= 0) { // Tuinity - <= 0, not ==
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", java_nio_file_path, k);
- this.h.put(k, 0);
+ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change
} else if ((long) i1 * 4096L > j) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", java_nio_file_path, k, i1);
- this.h.put(k, 0);
+ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change
} else {
- this.freeSectors.a(i1, j1);
+ //this.freeSectors.a(i1, j1); // Tuinity - move this down so we can check if it fails to allocate
+ }
+ // Tuinity start - recalculate header on header corruption
+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
+ 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 (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.getTimestamps().put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.getOffsets().put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ }
+ boolean failedToAllocate = !this.freeSectors.tryAllocate(offset, sectorLength);
+ if (failedToAllocate) {
+ MinecraftServer.LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.file.getAbsolutePath());
}
+ if (failedToAllocate & !canRecalcHeader) {
+ // location = chunkX | (chunkZ << 5);
+ MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() +
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.getTimestamps().put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.getOffsets().put(headerLocation, 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 or had other issues
+ MinecraftServer.LOGGER.error("Recalculating regionfile " + this.file.getAbsolutePath() + ", header gave erroneous 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.e.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 {
@@ -163,6 +584,12 @@ public class RegionFile implements AutoCloseable {
((java.nio.Buffer) bytebuffer).flip();
if (bytebuffer.remaining() < 5) {
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkcoordintpair, l, bytebuffer.remaining());
+ // Tuinity start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getReadStream(chunkcoordintpair);
+ }
+ // Tuinity end
return null;
} else {
int i1 = bytebuffer.getInt();
@@ -170,6 +597,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 - recalculate header on regionfile corruption
return null;
} else {
int j1 = i1 - 1;
@@ -177,17 +610,49 @@ public class RegionFile implements AutoCloseable {
if (a(b0)) {
if (j1 != 0) {
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
+ // Tuinity start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getReadStream(chunkcoordintpair);
+ }
+ // Tuinity end - recalculate header on regionfile corruption
}
- return this.a(chunkcoordintpair, b(b0));
+ // Tuinity start - recalculate header on regionfile corruption
+ DataInputStream ret = this.a(chunkcoordintpair, b(b0));
+ if (ret == null && this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getReadStream(chunkcoordintpair);
+ }
+ return ret;
+ // Tuinity end - recalculate header on regionfile corruption
} 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 - recalculate header on regionfile corruption
return null;
} else {
- return this.a(chunkcoordintpair, b0, a(bytebuffer, j1));
+ // Tuinity start - recalculate header on regionfile corruption
+ DataInputStream ret = this.a(chunkcoordintpair, b0, a(bytebuffer, j1));
+ if (ret == null && this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getReadStream(chunkcoordintpair);
+ }
+ return ret;
+ // Tuinity end - recalculate header on regionfile corruption
}
}
}
@@ -347,10 +812,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.f.a() | 128));
+ bytebuffer.put((byte) (compressionType.compressionTypeId() | 128)); // Tuinity - replace with compressionType
((java.nio.Buffer) bytebuffer).flip();
return bytebuffer;
}
@@ -387,6 +857,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.g).position(0);
this.dataFile.write(this.g, 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 d64f7ad925..8b341c14e7 100644
--- a/src/main/java/net/minecraft/server/RegionFileCache.java
+++ b/src/main/java/net/minecraft/server/RegionFileCache.java
@@ -15,12 +15,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
@@ -54,9 +85,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
@@ -145,6 +176,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
@@ -160,6 +198,17 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final
try {
if (datainputstream != null) {
nbttagcompound = NBTCompressedStreamTools.a((DataInput) datainputstream);
+ // Tuinity start - recover from corrupt regionfile header
+ if (this.isChunkData) {
+ ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(nbttagcompound);
+ if (!chunkPos.equals(chunkcoordintpair)) {
+ MinecraftServer.LOGGER.error("Attempting to read chunk data at " + chunkcoordintpair.toString() + " but got chunk data for " + chunkPos.toString() + " instead! Attempting regionfile recalculation for regionfile " + regionfile.file.getAbsolutePath());
+ regionfile.recalculateHeader();
+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
+ return this.readFromRegionFile(regionfile, chunkcoordintpair);
+ }
+ }
+ // Tuinity end - recover from corrupt regionfile header
return nbttagcompound;
}
diff --git a/src/main/java/net/minecraft/server/RegionFileCompression.java b/src/main/java/net/minecraft/server/RegionFileCompression.java
index 3382d678e6..3b7894256d 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);
}
@@ -45,6 +45,7 @@ public class RegionFileCompression {
return RegionFileCompression.d.containsKey(i);
}
+ public final int compressionTypeId() { return this.a(); } // Tuinity - OBFHELPER
public int a() {
return this.e;
}
@@ -53,6 +54,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/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java
index 04256a9510..d9362b74fd 100644
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
@@ -50,8 +50,8 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
}
- @Nullable
- protected Optional<R> c(long i) {
+ @Nullable protected Optional<R> getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER
+ @Nullable protected Optional<R> c(long i) { // Tuinity - OBFHELPER
return (Optional) this.c.get(i);
}
diff --git a/src/main/java/net/minecraft/server/SensorNearestBed.java b/src/main/java/net/minecraft/server/SensorNearestBed.java
index ad3609f2b8..d3d28f97f9 100644
--- a/src/main/java/net/minecraft/server/SensorNearestBed.java
+++ b/src/main/java/net/minecraft/server/SensorNearestBed.java
@@ -40,15 +40,15 @@ public class SensorNearestBed extends Sensor<EntityInsentient> {
return true;
}
};
- Stream<BlockPosition> stream = villageplace.a(VillagePlaceType.r.c(), predicate, entityinsentient.getChunkCoordinates(), 48, VillagePlace.Occupancy.ANY);
- PathEntity pathentity = entityinsentient.getNavigation().a(stream, VillagePlaceType.r.d());
+ Set<BlockPosition> set = BehaviorFindPosition.findPoi(villageplace, VillagePlaceType.r.c(), predicate, entityinsentient.getChunkCoordinates(), 48, VillagePlace.Occupancy.ANY, Integer.MAX_VALUE); // Tuinity - remove streams
+ PathEntity pathentity = entityinsentient.getNavigation().a(set, 8, false, VillagePlaceType.r.d()); // this.a((Set) stream.collect(Collectors.toSet()), 8, false, i) // Tuinity - remove streams
if (pathentity != null && pathentity.j()) {
BlockPosition blockposition = pathentity.m();
Optional<VillagePlaceType> optional = villageplace.c(blockposition);
if (optional.isPresent()) {
- entityinsentient.getBehaviorController().setMemory(MemoryModuleType.NEAREST_BED, (Object) blockposition);
+ entityinsentient.getBehaviorController().setMemory(MemoryModuleType.NEAREST_BED, blockposition); // Tuinity - decompile fix
}
} else if (this.b < 5) {
this.a.long2LongEntrySet().removeIf((entry) -> {
diff --git a/src/main/java/net/minecraft/server/SensorNearestItems.java b/src/main/java/net/minecraft/server/SensorNearestItems.java
index 2e747158d4..1de170b9fe 100644
--- a/src/main/java/net/minecraft/server/SensorNearestItems.java
+++ b/src/main/java/net/minecraft/server/SensorNearestItems.java
@@ -18,20 +18,23 @@ public class SensorNearestItems extends Sensor<EntityInsentient> {
protected void a(WorldServer worldserver, EntityInsentient entityinsentient) {
BehaviorController<?> behaviorcontroller = entityinsentient.getBehaviorController();
- List<EntityItem> list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (entityitem) -> {
- return true;
+ // Tuinity start - remove streams
+ List<EntityItem> list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (EntityItem item) -> {
+ return entityinsentient.i(item.getItemStack()) && item.a((Entity)entityinsentient, 9.0D); // copied from removed code, make sure to update - move here so we sort less
});
- entityinsentient.getClass();
- list.sort(Comparator.comparingDouble(entityinsentient::h));
- Stream stream = list.stream().filter((entityitem) -> {
- return entityinsentient.i(entityitem.getItemStack());
- }).filter((entityitem) -> {
- return entityitem.a((Entity) entityinsentient, 9.0D);
- });
-
- entityinsentient.getClass();
- Optional<EntityItem> optional = stream.filter(entityinsentient::hasLineOfSight).findFirst();
+ list.sort(Comparator.comparingDouble(entityinsentient::h)); // better to take the sort perf hit than using line of sight more than we need to.
+ EntityItem nearest = null;
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ EntityItem item = list.get(index);
+ if (entityinsentient.hasLineOfSight(item)) {
+ nearest = item;
+ break;
+ }
+ }
+
+ Optional<EntityItem> optional = Optional.ofNullable(nearest);
+ // Tuinity end - remove streams
behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional);
}
diff --git a/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java b/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java
index f6568a54ab..4005df5ef3 100644
--- a/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java
+++ b/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java
@@ -21,10 +21,17 @@ public class SensorNearestLivingEntities extends Sensor<EntityLiving> {
list.sort(Comparator.comparingDouble(entityliving::h));
BehaviorController<?> behaviorcontroller = entityliving.getBehaviorController();
- behaviorcontroller.setMemory(MemoryModuleType.MOBS, (Object) list);
- behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, list.stream().filter((entityliving1) -> {
- return a(entityliving, entityliving1);
- }).collect(Collectors.toList()));
+ behaviorcontroller.setMemory(MemoryModuleType.MOBS, list); // Tuinity - decompile fix
+ // Tuinity start - remove streams
+ List<EntityLiving> visible = new java.util.ArrayList<>(list.size());
+ for (int index = 0, len = list.size(); index < len; ++index) {
+ EntityLiving nearby = list.get(index);
+ if (Sensor.a(entityliving, nearby)) { // copied from removed code, make sure to update
+ visible.add(nearby);
+ }
+ }
+ behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, visible);
+ // Tuinity end - remove streams
}
@Override
diff --git a/src/main/java/net/minecraft/server/SensorNearestPlayers.java b/src/main/java/net/minecraft/server/SensorNearestPlayers.java
index 904a6d5ac6..c8e43a9f2a 100644
--- a/src/main/java/net/minecraft/server/SensorNearestPlayers.java
+++ b/src/main/java/net/minecraft/server/SensorNearestPlayers.java
@@ -19,22 +19,30 @@ public class SensorNearestPlayers extends Sensor<EntityLiving> {
@Override
protected void a(WorldServer worldserver, EntityLiving entityliving) {
- Stream stream = worldserver.getPlayers().stream().filter(IEntitySelector.g).filter((entityplayer) -> {
- return entityliving.a((Entity) entityplayer, 16.0D);
- });
-
- entityliving.getClass();
- List<EntityHuman> list = (List) stream.sorted(Comparator.comparingDouble(entityliving::h)).collect(Collectors.toList());
+ // Tuinity start - remove streams
+ List<EntityHuman> nearby = (List)worldserver.getNearbyPlayers(entityliving, 16.0, IEntitySelector.g);
+ nearby.sort((e1, e2) -> Double.compare(entityliving.getDistanceSquared(e1), entityliving.getDistanceSquared(e2)));
BehaviorController<?> behaviorcontroller = entityliving.getBehaviorController();
- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, (Object) list);
- List<EntityHuman> list1 = (List) list.stream().filter((entityhuman) -> {
- return a(entityliving, (EntityLiving) entityhuman);
- }).collect(Collectors.toList());
-
- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, (Object) (list1.isEmpty() ? null : (EntityHuman) list1.get(0)));
- Optional<EntityHuman> optional = list1.stream().filter(IEntitySelector.f).findFirst();
-
- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, optional);
+ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, nearby);
+ EntityHuman first = null;
+ EntityHuman firstNonSpectator = null;
+ for (int index = 0, len = nearby.size(); index < len; ++index) {
+ EntityHuman entity = nearby.get(index);
+ if (!Sensor.a(entityliving, (EntityLiving)entity)) { // copied from removed code, make sure to update
+ continue;
+ }
+ if (first == null) {
+ first = entity;
+ }
+ if (IEntitySelector.f.test(entity)) { // copied from removed code, make sure to update
+ firstNonSpectator = entity;
+ break;
+ }
+ }
+
+ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, first);
+ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, Optional.ofNullable(firstNonSpectator));
+ // Tuinity end - remove streams
}
}
diff --git a/src/main/java/net/minecraft/server/SensorVillagerBabies.java b/src/main/java/net/minecraft/server/SensorVillagerBabies.java
index a367bbfde4..794b33a13b 100644
--- a/src/main/java/net/minecraft/server/SensorVillagerBabies.java
+++ b/src/main/java/net/minecraft/server/SensorVillagerBabies.java
@@ -17,11 +17,23 @@ public class SensorVillagerBabies extends Sensor<EntityLiving> {
@Override
protected void a(WorldServer worldserver, EntityLiving entityliving) {
- entityliving.getBehaviorController().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, (Object) this.a(entityliving));
+ entityliving.getBehaviorController().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, this.a(entityliving)); // Tuinity - decompile fix
}
private List<EntityLiving> a(EntityLiving entityliving) {
- return (List) this.c(entityliving).stream().filter(this::b).collect(Collectors.toList());
+ // Tuinity start - remove streams
+ List<EntityLiving> nearby = this.c(entityliving); // copied from removed code, make sure to update
+ List<EntityLiving> ret = new java.util.ArrayList<>();
+
+ for (int index = 0, len = nearby.size(); index < len; ++index) {
+ EntityLiving entity = nearby.get(index);
+ if (this.b(entity)) { // copied from removed code, make sure to update
+ ret.add(entity);
+ }
+ }
+
+ return ret;
+ // Tuinity end - remove streams
}
private boolean b(EntityLiving entityliving) {
diff --git a/src/main/java/net/minecraft/server/ServerConnection.java b/src/main/java/net/minecraft/server/ServerConnection.java
index 5f4dacf9c9..0668d383db 100644
--- a/src/main/java/net/minecraft/server/ServerConnection.java
+++ b/src/main/java/net/minecraft/server/ServerConnection.java
@@ -74,6 +74,11 @@ public class ServerConnection {
ServerConnection.LOGGER.info("Using default channel type");
}
+ // Tuinity start - indicate Velocity natives in use
+ ServerConnection.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
+ ServerConnection.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
+ // Tuinity end
+
this.listeningChannels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) throws Exception {
try {
diff --git a/src/main/java/net/minecraft/server/StructureManager.java b/src/main/java/net/minecraft/server/StructureManager.java
index f199368a6d..2598ae3710 100644
--- a/src/main/java/net/minecraft/server/StructureManager.java
+++ b/src/main/java/net/minecraft/server/StructureManager.java
@@ -35,8 +35,13 @@ public class StructureManager {
// Paper start - remove structure streams
public java.util.List<StructureStart<?>> getFeatureStarts(SectionPosition sectionPosition, StructureGenerator<?> structureGenerator) {
+ // Tuinity start - add world parameter
+ return this.getFeatureStarts(sectionPosition, structureGenerator, null);
+ }
+ public java.util.List<StructureStart<?>> getFeatureStarts(SectionPosition sectionPosition, StructureGenerator<?> structureGenerator, IWorldReader world) {
+ // Tuinity end - add world parameter
java.util.List<StructureStart<?>> list = new ObjectArrayList<>();
- for (Long curLong: getLevel().getChunkAt(sectionPosition.a(), sectionPosition.c(), ChunkStatus.STRUCTURE_REFERENCES).b(structureGenerator)) {
+ for (Long curLong: (world == null ? getLevel() : world).getChunkAt(sectionPosition.a(), sectionPosition.c(), ChunkStatus.STRUCTURE_REFERENCES).b(structureGenerator)) { // Tuinity - fix deadlock on world gen - chunk can be unloaded while generating, so we should be using the generator's regionlimitedaccess so we always get the chunk
SectionPosition sectionPosition1 = SectionPosition.a(new ChunkCoordIntPair(curLong), 0);
StructureStart<?> structurestart = a(sectionPosition1, structureGenerator, getLevel().getChunkAt(sectionPosition1.a(), sectionPosition1.c(), ChunkStatus.STRUCTURE_STARTS));
if (structurestart != null && structurestart.e()) {
@@ -65,8 +70,12 @@ public class StructureManager {
}
public StructureStart<?> a(BlockPosition blockposition, boolean flag, StructureGenerator<?> structuregenerator) {
+ // Tuinity start - add world parameter
+ return this.getStructureStarts(blockposition,flag, structuregenerator, null);
+ }
+ public StructureStart<?> getStructureStarts(BlockPosition blockposition, boolean flag, StructureGenerator<?> structuregenerator, IWorldReader world) {
// Paper start - remove structure streams
- for (StructureStart<?> structurestart : getFeatureStarts(SectionPosition.a(blockposition), structuregenerator)) {
+ for (StructureStart<?> structurestart : getFeatureStarts(SectionPosition.a(blockposition), structuregenerator, world)) { // Tuinity end - add world parameter
if (structurestart.c().b(blockposition)) {
if (!flag) {
return structurestart;
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..25cff70b45 100644
--- a/src/main/java/net/minecraft/server/TicketType.java
+++ b/src/main/java/net/minecraft/server/TicketType.java
@@ -26,8 +26,19 @@ 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
+ // Tuinity start - delay chunk unloads
+ boolean delayUnloadViable = true;
+ static {
+ TicketType.LIGHT.delayUnloadViable = false;
+ TicketType.PLUGIN.delayUnloadViable = false;
+ TicketType.PRIORITY.delayUnloadViable = false;
+ TicketType.URGENT.delayUnloadViable = false;
+ TicketType.DELAYED_UNLOAD.delayUnloadViable = false;
+ }
+ // Tuinity end - delay chunk unloads
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/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java
index 2484293b12..1496c43fc9 100644
--- a/src/main/java/net/minecraft/server/UserCache.java
+++ b/src/main/java/net/minecraft/server/UserCache.java
@@ -49,6 +49,11 @@ public class UserCache {
private final File g;
private final AtomicLong h = new AtomicLong();
+ // 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.e = gameprofilerepository;
this.g = file;
@@ -56,6 +61,7 @@ public class UserCache {
}
private void a(UserCache.UserCacheEntry usercache_usercacheentry) {
+ try { this.stateLock.lock(); // Tuinity - allow better concurrency
GameProfile gameprofile = usercache_usercacheentry.a();
usercache_usercacheentry.a(this.d());
@@ -70,6 +76,7 @@ public class UserCache {
if (uuid != null) {
this.d.put(uuid, usercache_usercacheentry);
}
+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency
}
@@ -107,7 +114,7 @@ public class UserCache {
}
public void saveProfile(GameProfile gameprofile) { a(gameprofile); } // Paper - OBFHELPER
- public synchronized void a(GameProfile gameprofile) { // Paper - synchronize
+ public void a(GameProfile gameprofile) { // Paper - synchronize // Tuinity - allow better concurrency
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
@@ -124,8 +131,9 @@ public class UserCache {
}
@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.c.get(s1);
boolean flag = false;
@@ -139,10 +147,14 @@ public class UserCache {
GameProfile gameprofile;
if (usercache_usercacheentry != null) {
+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency
usercache_usercacheentry.a(this.d());
gameprofile = usercache_usercacheentry.a();
} else {
+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency
+ try { this.lookupLock.lock(); // Tuinity - allow better concurrency
gameprofile = a(this.e, s); // Spigot - use correct case for offline players
+ } finally { this.lookupLock.unlock(); } // Tuinity - allow better concurrency
if (gameprofile != null) {
this.a(gameprofile);
flag = false;
@@ -154,6 +166,7 @@ public class UserCache {
}
return gameprofile;
+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Tuinity - allow better concurrency
}
// Paper start
@@ -287,7 +300,9 @@ public class UserCache {
}
private Stream<UserCache.UserCacheEntry> a(int i) {
+ try { this.stateLock.lock(); // Tuinity - allow better concurrency
return ImmutableList.copyOf(this.d.values()).stream().sorted(Comparator.comparing(UserCache.UserCacheEntry::c).reversed()).limit((long) i);
+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency
}
private static JsonElement a(UserCache.UserCacheEntry usercache_usercacheentry, DateFormat dateformat) {
diff --git a/src/main/java/net/minecraft/server/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java
index 7f05587d42..5af554870b 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 ORIGIN = new Vec3D(0.0D, 0.0D, 0.0D);
+ public static final Vec3D ORIGIN = new Vec3D(0.0D, 0.0D, 0.0D); public static Vec3D getZeroVector() { return Vec3D.ORIGIN; } // 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 b926cebd05..3c9668c9c3 100644
--- a/src/main/java/net/minecraft/server/VillagePlace.java
+++ b/src/main/java/net/minecraft/server/VillagePlace.java
@@ -165,7 +165,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
@@ -290,7 +290,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null,
- com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
+ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - writes are async, no need for priority
return;
}
super.write(chunkcoordintpair, nbttagcompound);
@@ -309,6 +309,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
this.d = predicate;
}
+ public final Predicate<? super VillagePlaceRecord> getPredicate() { return this.a(); } // Tuinity - OBFHELPER
public Predicate<? super VillagePlaceRecord> a() {
return this.d;
}
diff --git a/src/main/java/net/minecraft/server/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
index 0b40c2f4da..6eaf9fc9cc 100644
--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
+++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
@@ -6,7 +6,7 @@ import java.util.Objects;
public class VillagePlaceRecord {
- private final BlockPosition a;
+ private final BlockPosition a; public final BlockPosition getPosition() { return this.a; } // Tuinity - OBFHELPER
private final VillagePlaceType b;
private int c;
private final Runnable d;
diff --git a/src/main/java/net/minecraft/server/VillagePlaceSection.java b/src/main/java/net/minecraft/server/VillagePlaceSection.java
index 77c66bc995..f43bc1f7d6 100644
--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
+++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
@@ -23,12 +23,12 @@ public class VillagePlaceSection {
private static final Logger LOGGER = LogManager.getLogger();
private final Short2ObjectMap<VillagePlaceRecord> b;
- private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c;
+ private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c; public final Map<VillagePlaceType, Set<VillagePlaceRecord>> getData() { return this.c; } // Tuinity - OBFHELPER
private final Runnable d;
private boolean e;
public static Codec<VillagePlaceSection> a(Runnable runnable) {
- Codec codec = RecordCodecBuilder.create((instance) -> {
+ Codec<VillagePlaceSection> codec = RecordCodecBuilder.create((instance) -> { // Tuinity - decompile fix
return instance.group(RecordCodecBuilder.point(runnable), Codec.BOOL.optionalFieldOf("Valid", false).forGetter((villageplacesection) -> {
return villageplacesection.e;
}), VillagePlaceRecord.a(runnable).listOf().fieldOf("Records").forGetter((villageplacesection) -> {
diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java
index eb926b74e1..700660dd93 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;
}
@@ -48,9 +48,15 @@ public abstract class VoxelShape {
public final VoxelShape offset(double x, double y, double z) { return this.a(x, y, z); } // Paper - OBFHELPER
public VoxelShape a(double d0, double d1, double d2) {
- 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)));
+ 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 - diff on change, copied into VoxelShapeArray override
}
+ // 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 3c29cb1452..c14b7bd63e 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((Throwable) (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,63 @@ 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);
+ }
+
+ @Override
+ public List<AxisAlignedBB> d() { // getBoundingBoxesRepresentation
+ if (this.boundingBoxesRepresentation == null) {
+ return super.d();
+ }
+ List<AxisAlignedBB> ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length);
+
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+ for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) {
+ ret.add(boundingBox.offset(offX, offY, offZ));
+ }
+
+ return ret;
+ }
+
+ 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) {
+ // this can be optimised by checking an "overall shape" first, but not needed
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) {
+ if (axisalingedbb.voxelShapeIntersect(boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ,
+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ // Tuinity end - optimise multi-aabb shapes
}
diff --git a/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java b/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java
index e841611bb7..259605daab 100644
--- a/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java
+++ b/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java
@@ -91,7 +91,7 @@ public class VoxelShapeSpliterator extends AbstractSpliterator<VoxelShape> {
VoxelShape voxelshape = iblockdata.b((IBlockAccess) this.g, this.e, this.c);
if (voxelshape == VoxelShapes.b()) {
- if (!this.b.a((double) i, (double) j, (double) k, (double) i + 1.0D, (double) j + 1.0D, (double) k + 1.0D)) {
+ if (!this.b.voxelShapeIntersect((double) i, (double) j, (double) k, (double) i + 1.0D, (double) j + 1.0D, (double) k + 1.0D)) { // Tuinity - keep vanilla behavior for voxelshape intersection - See comment in AxisAlignedBB
continue;
}
diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
index e21c747b6c..db735e29d4 100644
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
@@ -17,18 +17,80 @@ 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})); public 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.voxelShapeIntersect(aabb)) {
+ list.add(shapeCasted.aabb);
+ }
+ } else if (shape instanceof VoxelShapeArray) {
+ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape;
+ // this can be optimised by checking an "overall shape" first, but not needed
+
+ double offX = shapeCasted.offsetX;
+ double offY = shapeCasted.offsetY;
+ double offZ = shapeCasted.offsetZ;
+
+ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) {
+ double minX, minY, minZ, maxX, maxY, maxZ;
+ if (aabb.voxelShapeIntersect(minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ,
+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)) {
+ list.add(new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false));
+ }
+ }
+ } 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.voxelShapeIntersect(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 +129,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 +194,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.voxelShapeIntersect(((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((Throwable) (new IllegalArgumentException()));
} else if (voxelshape == voxelshape1) {
@@ -314,8 +390,9 @@ 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;
+ return (voxelshape != b() || voxelshape != getFullUnoptimisedCube()) && (voxelshape1 != b() || voxelshape1 != getFullUnoptimisedCube()) ? ((voxelshape == VoxelShapes.getEmptyShape() || voxelshape.isEmpty()) && (voxelshape1 == VoxelShapes.getEmptyShape() || voxelshape1.isEmpty()) ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; // Tuinity - optimise call by checking against more constant shapes
}
@VisibleForTesting
diff --git a/src/main/java/net/minecraft/server/WeightedList.java b/src/main/java/net/minecraft/server/WeightedList.java
index 5d9d58411f..f0fdfd6891 100644
--- a/src/main/java/net/minecraft/server/WeightedList.java
+++ b/src/main/java/net/minecraft/server/WeightedList.java
@@ -14,7 +14,7 @@ import java.util.stream.Stream;
public class WeightedList<U> {
- protected final List<WeightedList.a<U>> list; // Paper - decompile conflict
+ protected final List<WeightedList.a<U>> list; public final List<WeightedList.a<U>> getList() { return this.list; } // Paper - decompile conflict // Tuinity - OBFHELPER
private final Random b;
private final boolean isUnsafe; // Paper
@@ -74,7 +74,7 @@ public class WeightedList<U> {
public static class a<T> {
- private final T a;
+ private final T a; public final T getValue() { return this.a; } // Tuinity - OBFHELPER
private final int b;
private double c;
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index d64d94b8c3..f7103ec2a9 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,10 +123,39 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
return typeKey;
}
+ // Tuinity start - optimise checkDespawn
+ public final List<EntityPlayer> getNearbyPlayers(Entity source, double maxRange, Predicate<Entity> predicate) {
+ Chunk chunk = source.getCurrentChunk();
+ if (chunk == null || maxRange < 0.0 || maxRange > 31.0*16.0) {
+ return this.getNearbyPlayersSlow(source, maxRange, predicate);
+ }
+
+ List<EntityPlayer> ret = new java.util.ArrayList<>();
+ chunk.getNearestPlayers(source, predicate, maxRange, ret);
+ return ret;
+ }
+
+ private List<EntityPlayer> getNearbyPlayersSlow(Entity source, double maxRange, Predicate<Entity> predicate) {
+ List<EntityPlayer> ret = new java.util.ArrayList<>();
+ double maxRangeSquared = maxRange * maxRange;
+
+ for (EntityHuman player : this.getPlayers()) {
+ if ((maxRange < 0.0 || player.getDistanceSquared(source.locX(), source.locY(), source.locZ()) < maxRangeSquared)) {
+ if (predicate == null || predicate.test(player)) {
+ ret.add((EntityPlayer)player);
+ }
+ }
+ }
+
+ return ret;
+ }
+ // Tuinity end - optimise checkDespawn
+
protected World(WorldDataMutable worlddatamutable, ResourceKey<World> resourcekey, final DimensionManager dimensionmanager, Supplier<GameProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper
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, 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
@@ -286,6 +317,15 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
@Override
public final Chunk getChunkAt(int i, int j) { // Paper - final to help inline
+ // Tuinity start - make sure loaded chunks get the inlined variant of this function
+ ChunkProviderServer cps = ((WorldServer)this).chunkProvider;
+ if (cps.serverThread == Thread.currentThread()) {
+ Chunk ifLoaded = cps.getChunkAtIfLoadedMainThread(i, j);
+ if (ifLoaded != null) {
+ return ifLoaded;
+ }
+ }
+ // Tuinity end - make sure loaded chunks get the inlined variant of this function
return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL, true); // Paper - avoid a method jump
}
@@ -360,6 +400,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
@Override
public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) {
+ org.spigotmc.AsyncCatcher.catchOp("set type call"); // Tuinity
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
// Paper start
@@ -460,6 +501,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;
@@ -893,6 +935,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
@@ -1076,10 +1119,44 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
return this.getChunkAt(i, j, ChunkStatus.FULL, false);
}
+ // Tuinity start - optimise hard collision handling
+ @Override
+ public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+ return this.getHardCollidingEntities(entity, axisalignedbb, predicate, Lists.newArrayList());
+ }
+
+ public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate, List<Entity> list) {
+ // copied from below
+ 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);
+ int l = MathHelper.floor((axisalignedbb.maxZ + 2.0D) / 16.0D);
+
+ ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider();
+
+ for (int i1 = i; i1 <= j; ++i1) {
+ for (int j1 = k; j1 <= l; ++j1) {
+ Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1);
+
+ if (chunk != null) {
+ chunk.getHardCollidingEntities(entity, axisalignedbb, list, predicate);
+ }
+ }
+ }
+
+ return list;
+ }
+ // Tuinity end - optimise hard collision handling
+
@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);
@@ -1135,7 +1212,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
- chunk.a(oclass, axisalignedbb, list, predicate);
+ chunk.getEntitiesClass(oclass, null, axisalignedbb, (Predicate)predicate, (List)list); // Tuinity - optimise lookup by entity class
}
}
}
@@ -1158,7 +1235,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
if (chunk != null) {
- chunk.a(oclass, axisalignedbb, list, predicate);
+ chunk.getEntitiesClass(oclass, null, axisalignedbb, (Predicate)predicate, (List)list); // Tuinity - optimise lookup by entity class
}
}
}
@@ -1166,6 +1243,106 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
return list;
}
+ // Tuinity start
+ @Override
+ public <T extends EntityLiving> T b(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) {
+ return this.getClosestEntity(oclass, pathfindertargetcondition, entityliving, d0, d1, d2, axisalignedbb);
+ }
+
+ @Override
+ public <T extends EntityLiving> T a(Class<? extends T> oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) {
+ return this.getClosestEntity(oclass, pathfindertargetcondition, entityliving, d0, d1, d2, axisalignedbb);
+ }
+
+ public final <T extends EntityLiving> T getClosestEntity(Class<? extends T> clazz,
+ PathfinderTargetCondition condition,
+ @Nullable EntityLiving source,
+ double x, double y, double z,
+ AxisAlignedBB boundingBox) {
+ org.bukkit.craftbukkit.util.UnsafeList<Entity> entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
+ try {
+ int lowerX = MCUtil.fastFloor((boundingBox.minX - 2.0D)) >> 4;
+ int upperX = MCUtil.fastFloor((boundingBox.maxX + 2.0D)) >> 4;
+ int lowerZ = MCUtil.fastFloor((boundingBox.minZ - 2.0D)) >> 4;
+ int upperZ = MCUtil.fastFloor((boundingBox.maxZ + 2.0D)) >> 4;
+
+ org.bukkit.craftbukkit.util.UnsafeList<Chunk> chunks = com.tuinity.tuinity.util.CachedLists.getTempGetChunksList();
+ try {
+ T closest = null;
+ double closestDistance = Double.MAX_VALUE;
+ ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider();
+
+ int centerX = (lowerX + upperX) >> 1;
+ int centerZ = (lowerZ + upperZ) >> 1;
+ // Copied from MCUtil.getSpiralOutChunks
+ Chunk temp;
+ if ((temp = chunkProvider.getChunkAtIfLoadedImmediately(centerX, centerZ)) != null && temp.hasEntitiesMaybe(clazz)) {
+ chunks.add(temp);
+ }
+ int radius = Math.max((upperX - lowerX + 1) >> 1, (upperZ - lowerZ + 1) >> 1);
+ for (int r = 1; r <= radius; r++) {
+ int ox = -r;
+ int oz = r;
+
+ // Iterates the edge of half of the box; then negates for other half.
+ while (ox <= r && oz > -r) {
+ {
+ int cx = centerX + ox;
+ int cz = centerZ + oz;
+ if (cx >= lowerX && cx <= upperX && cz >= lowerZ && cz <= upperZ &&
+ (temp = chunkProvider.getChunkAtIfLoadedImmediately(cx, cz)) != null &&
+ temp.hasEntitiesMaybe(clazz)) {
+ chunks.add(temp);
+ }
+ }
+ {
+ int cx = centerX - ox;
+ int cz = centerZ - oz;
+ if (cx >= lowerX && cx <= upperX && cz >= lowerZ && cz <= upperZ &&
+ (temp = chunkProvider.getChunkAtIfLoadedImmediately(cx, cz)) != null &&
+ temp.hasEntitiesMaybe(clazz)) {
+ chunks.add(temp);
+ }
+ }
+
+ if (ox < r) {
+ ox++;
+ } else {
+ oz--;
+ }
+ }
+ }
+
+ Object[] chunkData = chunks.getRawDataArray();
+ for (int cindex = 0, clen = chunks.size(); cindex < clen; ++cindex) {
+ final Chunk chunk = (Chunk)chunkData[cindex];
+
+ chunk.getEntitiesClass(clazz, source, boundingBox, null, entities);
+
+ Object[] entityData = entities.getRawDataArray();
+ for (int eindex = 0, entities_len = entities.size(); eindex < entities_len; ++eindex) {
+ T entity = (T)entityData[eindex];
+ double distance = entity.getDistanceSquared(x, y, z);
+ // check distance first, as it's the least expensive
+ if (distance < closestDistance && condition.test(source, entity)) {
+ closest = entity;
+ closestDistance = distance;
+ }
+ }
+
+ entities.setSize(0);
+ }
+
+ return closest;
+ } finally {
+ com.tuinity.tuinity.util.CachedLists.returnTempGetChunksList(chunks);
+ }
+ } finally {
+ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities);
+ }
+ }
+ // Tuinity end
+
@Nullable
public abstract Entity getEntity(int i);
diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java
index f011869880..26a8c4ffe2 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() - MCUtil.COLLISION_EPSILON;
+ double maxX = this.getMaxX() + MCUtil.COLLISION_EPSILON;
+
+ double minZ = this.getMinZ() - MCUtil.COLLISION_EPSILON;
+ double maxZ = this.getMaxZ() + MCUtil.COLLISION_EPSILON;
+
+ 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 b196810316..274a383be1 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -55,12 +55,13 @@ import org.bukkit.event.server.MapInitializeEvent;
import org.bukkit.event.weather.LightningStrikeEvent;
import org.bukkit.event.world.TimeSkipEvent;
// CraftBukkit end
+import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity
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, true); // 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
@@ -84,7 +85,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, true); // Tuinity - make removing entities while ticking safe
protected final PersistentRaid persistentRaid;
private final ObjectLinkedOpenHashSet<BlockActionData> L;
private boolean ticking;
@@ -205,6 +206,104 @@ 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<>();
+ IntArrayList ticketLevels = new 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
+
+ // Tuinity start - optimise checkDespawn
+ final List<EntityPlayer> playersAffectingSpawning = new java.util.ArrayList<>();
+ // Tuinity end - optimise checkDespawn
+
// Add env and gen to constructor, WorldData -> WorldDataServer
public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey<World> resourcekey, 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, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor
@@ -265,6 +364,451 @@ 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 - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 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 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
+
+ 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
+
+ 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 currZ = minZ; currZ <= maxZ; ++currZ) {
+ int blockKeyZY = blockKeyY | (currZ << 4);
+ int blockZ = currZ | chunkZGlobalPos; // world position
+
+ int edgeCountZY;
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
+ edgeCountZY = edgeCountY + 1;
+ } else {
+ edgeCountZY = edgeCountY;
+ }
+
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int blockX = currX | chunkXGlobalPos; // world position
+
+ int edgeCountFull;
+ if (blockX == minBlockX || blockX == maxBlockX) {
+ edgeCountFull = edgeCountZY + 1;
+ } else {
+ edgeCountFull = edgeCountZY;
+ }
+
+ if (edgeCountFull == 3) {
+ continue;
+ }
+
+ int blockKeyFull = blockKeyZY | currX;
+ 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 boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks, boolean collideWithUnloaded, java.util.function.BiPredicate<IBlockData, BlockPosition> predicate) {
+ if (entity != null) {
+ if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) {
+ return true;
+ }
+ }
+
+ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 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 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
+
+ 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
+
+ int chunkXGlobalPos = currChunkX << 4;
+ int chunkZGlobalPos = currChunkZ << 4;
+ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+
+ if (chunk == null) {
+ if (collideWithUnloaded) {
+ return true;
+ } else {
+ 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 currZ = minZ; currZ <= maxZ; ++currZ) {
+ int blockKeyZY = blockKeyY | (currZ << 4);
+ int blockZ = currZ | chunkZGlobalPos; // world position
+
+ int edgeCountZY;
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
+ edgeCountZY = edgeCountY + 1;
+ } else {
+ edgeCountZY = edgeCountY;
+ }
+
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int blockX = currX | chunkXGlobalPos; // world position
+
+ int edgeCountFull;
+ if (blockX == minBlockX || blockX == maxBlockX) {
+ edgeCountFull = edgeCountZY + 1;
+ } else {
+ edgeCountFull = edgeCountZY;
+ }
+
+ if (edgeCountFull == 3) {
+ continue;
+ }
+
+ int blockKeyFull = blockKeyZY | currX;
+ 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) && (predicate == null || predicate.test(blockData, mutablePos))) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public final boolean hardCollidesWithAnyEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<Entity> predicate) {
+ if (axisalignedbb.isEmpty()) {
+ return false;
+ }
+ axisalignedbb = axisalignedbb.grow(MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON);
+ List<Entity> entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
+ try {
+ if (entity != null && entity.hardCollides()) {
+ this.getEntities(entity, axisalignedbb, predicate, entities);
+ } else {
+ this.getHardCollidingEntities(entity, axisalignedbb, predicate, entities);
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ Entity otherEntity = entities.get(i);
+
+ if ((entity == null || otherEntity.collisionBoxIsHard()) || entity.hardCollidesWith(otherEntity)) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities);
+ }
+ }
+
+ 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);
+ }
+
+ 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 - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1;
+ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 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 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
+
+ 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
+
+ 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 currZ = minZ; currZ <= maxZ; ++currZ) {
+ int blockKeyZY = blockKeyY | (currZ << 4);
+ int blockZ = currZ | chunkZGlobalPos; // world position
+
+ int edgeCountZY;
+ if (blockZ == minBlockZ || blockZ == maxBlockZ) {
+ edgeCountZY = edgeCountY + 1;
+ } else {
+ edgeCountZY = edgeCountY;
+ }
+
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int blockX = currX | chunkXGlobalPos; // world position
+
+ int edgeCountFull;
+ if (blockX == minBlockX || blockX == maxBlockX) {
+ edgeCountFull = edgeCountZY + 1;
+ } else {
+ edgeCountFull = edgeCountZY;
+ }
+
+ if (edgeCountFull == 3) {
+ continue;
+ }
+
+ int blockKeyFull = blockKeyZY | currX;
+ 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 = axisalignedbb.grow(MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON);
+ List<Entity> entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
+ try {
+ if (entity != null && entity.hardCollides()) {
+ this.getEntities(entity, axisalignedbb, predicate, entities);
+ } else {
+ this.getHardCollidingEntities(entity, axisalignedbb, predicate, entities);
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ Entity otherEntity = entities.get(i);
+
+ if ((entity == null || otherEntity.collisionBoxIsHard()) || entity.hardCollidesWith(otherEntity)) {
+ list.add(otherEntity.getBoundingBox());
+ }
+ }
+ } 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) {
+ if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false;
+ return !this.hasAnyCollisions(entity, axisalignedbb);
+ }
+
+ @Override
+ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+ if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false;
+ 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) {
@@ -318,6 +862,14 @@ public class WorldServer extends World implements GeneratorAccessSeed {
public void doTick(BooleanSupplier booleansupplier) {
GameProfilerFiller gameprofilerfiller = this.getMethodProfiler();
+ // Tuinity start - optimise checkDespawn
+ this.playersAffectingSpawning.clear();
+ for (EntityPlayer player : this.getPlayers()) {
+ if (IEntitySelector.affectsSpawning.test(player)) {
+ this.playersAffectingSpawning.add(player);
+ }
+ }
+ // Tuinity end - optimise checkDespawn
this.ticking = true;
gameprofilerfiller.enter("world border");
@@ -467,7 +1019,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();
@@ -476,7 +1028,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
timings.doSounds.startTiming(); // Spigot
this.ak();
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
@@ -492,13 +1044,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
@@ -534,7 +1085,7 @@ 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);
}
@@ -542,6 +1093,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
}
timings.entityTick.stopTiming(); // Spigot
+ objectiterator.finishedIterating(); // Tuinity
this.tickingEntities = false;
// Paper start
for (java.lang.Runnable run : this.afterEntityTickingTasks) {
@@ -553,7 +1105,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
}
this.afterEntityTickingTasks.clear();
// Paper end
- this.getMinecraftServer().midTickLoadChunks(); // Paper
+ // Tuinity - replace logic
Entity entity2;
@@ -563,7 +1115,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
}
timings.tickEntities.stopTiming(); // Spigot
- this.getMinecraftServer().midTickLoadChunks(); // Paper
+ // Tuinity - replace logic
this.tickBlockEntities();
}
@@ -809,7 +1361,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 {
@@ -862,6 +1433,11 @@ public class WorldServer extends World implements GeneratorAccessSeed {
//} finally { timer.stopTiming(); } // Paper - timings - move up
}
+ // Tuinity start - log detailed entity tick information
+ } finally {
+ currentlyTickingEntities.pop();
+ }
+ // Tuinity end - log detailed entity tick information
}
public void a(Entity entity, Entity entity1) {
@@ -1299,7 +1875,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((Throwable) (new IllegalStateException("Removing entity while ticking!")));
}
@@ -1327,6 +1903,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
@@ -1393,12 +1970,16 @@ 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
}
new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
entity.valid = false; // CraftBukkit
@@ -1414,7 +1995,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
@@ -1422,6 +2003,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).eJ();
int i = aentitycomplexpart.length;
@@ -1430,6 +2012,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
EntityComplexPart entitycomplexpart = aentitycomplexpart[j];
this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart);
+ this.entitiesForIteration.add(entitycomplexpart); // Tuinity
}
}
@@ -1454,12 +2037,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
@@ -1475,7 +2062,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((Throwable) (new IllegalStateException("Removing entity while ticking!")));
} else {
this.removeEntityFromChunk(entity);
@@ -1571,13 +2158,16 @@ public class WorldServer extends World implements GeneratorAccessSeed {
@Override
public void notify(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1, int i) {
+ org.spigotmc.AsyncCatcher.catchOp("notify call"); // Tuinity
this.getChunkProvider().flagDirty(blockposition);
VoxelShape voxelshape = iblockdata.getCollisionShape(this, blockposition);
VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition);
if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) {
boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
- Iterator iterator = this.navigators.iterator();
+ // Tuinity start
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator();
+ try { // Tuinity end
while (iterator.hasNext()) {
NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next();
@@ -1586,6 +2176,9 @@ public class WorldServer extends World implements GeneratorAccessSeed {
navigationabstract.b(blockposition);
}
}
+ } finally { // Tuinity start
+ iterator.finishedIterating();
+ } // Tuinity end
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/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 4e1b4d7cde..9bf854b5a2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -232,7 +232,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");
@@ -856,6 +856,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);
@@ -890,6 +891,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
@@ -1853,7 +1855,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
@@ -2272,6 +2277,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 299f57ca2a..4de6252f07 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -341,6 +341,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
@@ -414,14 +422,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
@@ -504,6 +505,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;
@@ -2536,7 +2538,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 9118f05424..a9c96d45c6 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/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 4ec0e9220d..1fe253bc72 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -503,27 +503,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) {
@@ -557,6 +537,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/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 6fa2e271f7..3a9491e949 100644
--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
@@ -113,9 +113,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..7c0d90552e 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
@@ -35,6 +35,13 @@ public class UnsafeList<E> extends AbstractList<E> implements List<E>, RandomAcc
iterPool[0] = new Itr();
}
+ // Tuinity start
+ @Override
+ public void sort(java.util.Comparator<? super E> c) {
+ Arrays.sort((E[])this.data, 0, size, c);
+ }
+ // Tuinity end
+
public UnsafeList(int capacity) {
this(capacity, 5);
}
@@ -119,6 +126,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..51e9c54cdd 100644
--- a/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -10,8 +10,9 @@ 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
{
+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed thread check for reason: Asynchronous " + reason, new Throwable()); // Tuinity - not all exceptions are printed
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, "------------------------------" );
//