mirror of
https://github.com/PurpurMC/Purpur.git
synced 2026-02-17 16:37:43 +01:00
Upstream has released updates that appear to apply and compile correctly Paper Changes: a70924789 [Auto] Updated Upstream (CraftBukkit) Tuinity Changes: d1db107 Updated Upstream (Paper)
15258 lines
753 KiB
Diff
15258 lines
753 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.
|
|
|
|
Do not retain playerchunkmap instance in light thread factory
|
|
|
|
The executor returned is finalizable and of course
|
|
that causes issues.
|
|
|
|
Do not load chunks during a crash report
|
|
|
|
This causes deadlocks in some cases when generating
|
|
crash reports.
|
|
|
|
Fixes https://github.com/Spottedleaf/Tuinity/issues/215
|
|
|
|
Improve abnormal server shutdown process
|
|
|
|
- When we're trying to kill the main thread from watchdog,
|
|
step up the stop() spamming after 15s to really kill the main thread.
|
|
|
|
- Do not wait for window disposing when disposing of the server
|
|
gui. It looks like during sigint shutdown there can be some
|
|
deadlock between the server thread and awt shutdown thread here.
|
|
|
|
Copy passenger list in enderTeleportTo
|
|
|
|
Fixes https://github.com/Spottedleaf/Tuinity/issues/208
|
|
|
|
Apply more strict limitations to books
|
|
|
|
For reference is the vanilla limits, this must be checked
|
|
on update
|
|
- Max book pages: 50
|
|
- Max Length: 256
|
|
|
|
Currently, patch limits to 50 pages and 512 characters.
|
|
|
|
Rewrite the light engine
|
|
|
|
The standard vanilla light engine is plagued by
|
|
awful performance. Paper's changes to the light engine
|
|
help a bit, however they appear to cause some lighting
|
|
errors - most easily noticed in coral generation.
|
|
|
|
The vanilla light engine's is too abstract to be modified -
|
|
so an entirely new implementation is required to fix the
|
|
performance and lighting errors.
|
|
|
|
The new implementation is designed primarily to optimise
|
|
light level propagations (increase and decrease). Unlike
|
|
the vanilla light engine, this implementation tracks more
|
|
information per queued value when performing a
|
|
breadth first search. Vanilla just tracks coordinate, which
|
|
means every time they handle a queued value, they must
|
|
also determine the coordinate's target light level
|
|
from its neighbours - very wasteful, especially considering
|
|
these checks read neighbour block data.
|
|
The new light engine tracks both position and target level,
|
|
as well as whether the target block needs to be read at all
|
|
(for checking sided propagation). So, the work done per coordinate
|
|
is significantly reduced because no work is done for calculating
|
|
the target level.
|
|
In my testing, the block get calls were reduced by approximately
|
|
an order of magnitude. However, the light read checks were only
|
|
reduced by approximately 2x - but this is fine, light read checks
|
|
are extremely cheap compared to block gets.
|
|
|
|
Generation testing showed that the new light engine improved
|
|
total generation (not lighting itself, but the whole generation process)
|
|
by 2x. According to cpu time, the light engine itself spent 10x less time
|
|
lighting chunks for generation.
|
|
|
|
fixup! Highly optimise single and multi-AABB VoxelShapes and collisions
|
|
|
|
Revert Temporarily-Revert-usage-of-Region-Manager
|
|
|
|
Optimise WorldServer#notify
|
|
|
|
Iterating over all of the navigators in the world is pretty expensive.
|
|
Instead, only iterate over navigators in the current region that are
|
|
eligible for repathing.
|
|
|
|
fixup! Util patch
|
|
|
|
fixup! Util patch
|
|
|
|
Actually unload POI data
|
|
|
|
While it's not likely for a poi data leak to be meaningful,
|
|
sometimes it is.
|
|
|
|
This patch also prevents the saving/unloading of POI data when
|
|
world saving is disabled.
|
|
|
|
diff --git a/pom.xml b/pom.xml
|
|
index 80f165291385f5f54def1a8e8487b209fb06a4a3..78c2a8bbcc0132f891c8aa545529d20aa0d9eb57 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 884b59d478aa7de49906520e77866a7949bed19d..68ab5ccb2fcfe1de0503c9336572f28e11832b2d 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 e33e889c291d37a821a4fbd40d9aac7bb079de0d..5dfa0658838c4801cdf260eae8b98163f729e5af 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/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
|
|
index dee00aac05f1acf050f05d4db557a08dd0f301c8..52c0ab1ce46e1f3233ef746d9bc699356fa9fae4 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
|
|
@@ -593,7 +593,7 @@ public class Metrics {
|
|
boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
|
|
// Only start Metrics, if it's enabled in the config
|
|
if (config.getBoolean("enabled", true)) {
|
|
- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger());
|
|
+ Metrics metrics = new Metrics("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page
|
|
|
|
metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
|
|
String minecraftVersion = Bukkit.getVersion();
|
|
@@ -603,7 +603,7 @@ public class Metrics {
|
|
|
|
metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
|
|
metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() || PaperConfig.isProxyOnlineMode() ? "online" : "offline"));
|
|
- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
|
|
+ metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page
|
|
|
|
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
|
|
Map<String, Map<String, Integer>> map = new HashMap<>();
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
index ad6b15f401ce7493a2a247e2a2af23f73ade02ca..c9e3a0c8742caeef85c429861cf4b7f2b470ed51 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
|
@@ -225,8 +225,20 @@ public class PaperCommand extends Command {
|
|
updateLight(sender, world, lightengine, queue);
|
|
return;
|
|
}
|
|
- lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue // Tuinity - no longer needed
|
|
sender.sendMessage("Updating Light " + coord);
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) { // Tuinity start - replace impl: faster, and it actually works
|
|
+ lightengine.relight(coord.x, coord.z, () -> {
|
|
+ MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ PlayerChunk visibleChunk = world.getChunkProvider().playerChunkMap.getVisibleChunk(chunk.coordinateKey);
|
|
+ if (visibleChunk != null) {
|
|
+ visibleChunk.sendPacketToTrackedPlayers(new PacketPlayOutLightUpdate(chunk.getPos(), lightengine, true), false);
|
|
+ }
|
|
+ updateLight(sender, world, lightengine, queue);
|
|
+ });
|
|
+ });
|
|
+ lightengine.queueUpdate();
|
|
+ } else { // Tuinity end - replace impl: faster, and it actually works
|
|
int cx = chunk.getPos().x << 4;
|
|
int cz = chunk.getPos().z << 4;
|
|
for (int y = 0; y < world.getHeight(); y++) {
|
|
@@ -250,6 +262,7 @@ public class PaperCommand extends Command {
|
|
updateLight(sender, world, lightengine, queue);
|
|
}
|
|
lightengine.a(world.paperConfig.lightQueueSize);
|
|
+ } // Tuinity - replace impl: faster, and it actually works
|
|
}, MinecraftServer.getServer());
|
|
}
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
|
|
index 49a38c6608b652ff48ef4eaca0dd3ccb1ba570e3..255bbd6e48b95c70fad02ba692c64c7579496827 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 e7624948ea4aa1a07d84ed3d295cfe2dd354fd14..77df6888803093ad9527d276033f2ed767b39764 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 103576715ef6ae4df4b216ae9ae31b6fb1086bd5..e8fdbe7b8d8192a3247d98534e78ede7a7314a91 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 cb993ca102402d9c19ea9fa04e5db09c21205896..849686f7b2a8b0044f7cd14c8c2e401e80966462 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 bc0024adb804ac055a4f8afb7f85d00ec13931e9..0343f6663c450c3f0d9c57d817eef9c979055939 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 7720578796e28d28e8c0c9aa40155cd205c17d54..e5db29d4cadb5702c7d06b0b6e2d05586a652ec8 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 0000000000000000000000000000000000000000..37428f4b9ae45175fda545e9d8b55cf8a3b8c87b
|
|
--- /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 0000000000000000000000000000000000000000..cae06962d80cdd00962236891472ba815b0ab8cd
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
|
|
@@ -0,0 +1,491 @@
|
|
+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.Arrays;
|
|
+import java.util.EnumMap;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.function.Function;
|
|
+
|
|
+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 = 32;
|
|
+ public static final int REGION_CHUNK_SIZE_SHIFT = 5; // 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"));
|
|
+ }
|
|
+
|
|
+ // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f
|
|
+
|
|
+ /*
|
|
+ protected void check() {
|
|
+ ReferenceOpenHashSet<Region<T>> checked = new ReferenceOpenHashSet<>();
|
|
+
|
|
+ for (RegionSection<T> section : this.regionsBySection.values()) {
|
|
+ if (!checked.add(section.region)) {
|
|
+ section.region.check();
|
|
+ }
|
|
+ }
|
|
+ for (Region<T> region : this.needsRecalculation) {
|
|
+ region.check();
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ 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 RegionSection<T> getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection<T> force) {
|
|
+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ);
|
|
+
|
|
+ if (force == null) {
|
|
+ RegionSection<T> region = this.regionsBySection.get(sectionKey);
|
|
+ if (region != null) {
|
|
+ return region;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ int mergeCandidateSectionSize = -1;
|
|
+ Region<T> mergeIntoCandidate = null;
|
|
+
|
|
+ // 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;
|
|
+ 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 i = 0; i < this.toMerge.size(); ++i) {
|
|
+ final Region<T> region = this.toMerge.get(i);
|
|
+ if (region.dead || mergeIntoCandidate == region) {
|
|
+ continue;
|
|
+ }
|
|
+ region.mergeInto(mergeIntoCandidate);
|
|
+ }
|
|
+ this.toMerge.clear();
|
|
+ } else {
|
|
+ mergeIntoCandidate = new Region<>(this);
|
|
+ }
|
|
+
|
|
+ final RegionSection<T> section;
|
|
+ if (force == null) {
|
|
+ this.regionsBySection.put(sectionKey, section = new RegionSection<>(sectionKey, this));
|
|
+ } 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);
|
|
+ //mergeIntoCandidate.check();
|
|
+ //this.check();
|
|
+
|
|
+ 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);
|
|
+ //this.check();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void recalculateRegion(final Region<T> region) {
|
|
+ this.regionRecalculateTimings.startTiming();
|
|
+ try {
|
|
+ region.markedForRecalc = false;
|
|
+ //region.check();
|
|
+ // clear unused regions
|
|
+ for (final Iterator<RegionSection<T>> iterator = region.deadSections.iterator(); iterator.hasNext();) {
|
|
+ final RegionSection<T> deadSection = iterator.next();
|
|
+
|
|
+ if (deadSection.hasChunks()) {
|
|
+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!");
|
|
+ }
|
|
+ if (!region.sections.remove(deadSection)) {
|
|
+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection);
|
|
+ }
|
|
+ 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));
|
|
+ }
|
|
+ }
|
|
+ region.deadSections.clear();
|
|
+
|
|
+ // implicitly cover cases where size == 0
|
|
+ if (region.sections.size() < this.minSectionRecalcCount) {
|
|
+ //region.check();
|
|
+ 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 (!aliveSection.hasChunks()) {
|
|
+ throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!");
|
|
+ }
|
|
+ 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 check() {
|
|
+ if (this.dead) {
|
|
+ throw new IllegalStateException("Dead region!");
|
|
+ }
|
|
+ for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final RegionSection<T> section = iterator.next();
|
|
+ if (section.region != this) {
|
|
+ throw new IllegalStateException("Region section must point to us!");
|
|
+ }
|
|
+ if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) {
|
|
+ throw new IllegalStateException("Region section must match the regionmanager state!");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ protected void mergeInto(final Region<T> mergeTarget) {
|
|
+ if (this == mergeTarget) {
|
|
+ throw new IllegalStateException("Cannot merge a region onto itself");
|
|
+ }
|
|
+ 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;
|
|
+ if (this.markedForRecalc) {
|
|
+ this.regionManager.removeFromRecalcQueue(this);
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+ }
|
|
+ //mergeTarget.check();
|
|
+ }
|
|
+
|
|
+ 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 final long[] chunksBitset = new long[Math.max(1, REGION_CHUNK_SIZE * REGION_CHUNK_SIZE / Long.SIZE)];
|
|
+ protected int chunkCount;
|
|
+ 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 boolean hasChunks() {
|
|
+ return this.chunkCount != 0;
|
|
+ }
|
|
+
|
|
+ protected void addChunk(final int chunkX, final int chunkZ) {
|
|
+ final int index = getChunkIndex(chunkX, chunkZ);
|
|
+ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE
|
|
+ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1)));
|
|
+ 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 (++this.chunkCount != 1) {
|
|
+ return;
|
|
+ }
|
|
+ this.region.markSectionAlive(this);
|
|
+ }
|
|
+
|
|
+ protected void removeChunk(final int chunkX, final int chunkZ) {
|
|
+ final int index = getChunkIndex(chunkX, chunkZ);
|
|
+ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE
|
|
+ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1)));
|
|
+ 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 (--this.chunkCount != 0) {
|
|
+ return;
|
|
+ }
|
|
+ this.region.markSectionDead(this);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "RegionSection{" +
|
|
+ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
|
|
+ "chunkCount=" + this.chunkCount + "," +
|
|
+ "chunksBitset=" + toString(this.chunksBitset) + "," +
|
|
+ "hash=" + this.hashCode() +
|
|
+ "}";
|
|
+ }
|
|
+
|
|
+ public String toStringWithRegion() {
|
|
+ return "RegionSection{" +
|
|
+ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
|
|
+ "chunkCount=" + this.chunkCount + "," +
|
|
+ "chunksBitset=" + toString(this.chunksBitset) + "," +
|
|
+ "hash=" + this.hashCode() + "," +
|
|
+ "region=" + this.region +
|
|
+ "}";
|
|
+ }
|
|
+
|
|
+ private static String toString(final long[] array) {
|
|
+ StringBuilder ret = new StringBuilder();
|
|
+ for (long value : array) {
|
|
+ // zero pad the hex string
|
|
+ char[] zeros = new char[Long.SIZE / 4];
|
|
+ Arrays.fill(zeros, '0');
|
|
+ String string = Long.toHexString(value);
|
|
+ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length());
|
|
+
|
|
+ ret.append(zeros);
|
|
+ }
|
|
+
|
|
+ return ret.toString();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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/chunk/light/BlockStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e7df9dc4b82c08aafc0a0bb076d9027c1f7758e2
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java
|
|
@@ -0,0 +1,155 @@
|
|
+package com.tuinity.tuinity.chunk.light;
|
|
+
|
|
+import net.minecraft.server.BlockPosition;
|
|
+import net.minecraft.server.Chunk;
|
|
+import net.minecraft.server.ChunkSection;
|
|
+import net.minecraft.server.ChunkStatus;
|
|
+import net.minecraft.server.IBlockData;
|
|
+import net.minecraft.server.IChunkAccess;
|
|
+import net.minecraft.server.ILightAccess;
|
|
+import net.minecraft.server.ProtoChunkExtension;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.Set;
|
|
+
|
|
+public final class BlockStarLightEngine extends StarLightEngine {
|
|
+
|
|
+ public BlockStarLightEngine() {
|
|
+ super(false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) {
|
|
+ return chunk.getBlockNibbles();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) {
|
|
+ chunk.setBlockNibbles(to);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected boolean canUseChunk(final IChunkAccess chunk) {
|
|
+ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && chunk.isLit();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected final void checkBlock(final int worldX, final int worldY, final int worldZ) {
|
|
+ // blocks can change opacity
|
|
+ // blocks can change emitted light
|
|
+ // blocks can change direction of propagation
|
|
+
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ final int emittedMask = this.emittedLightMask;
|
|
+
|
|
+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ);
|
|
+ final IBlockData blockData = this.getBlockData(worldX, worldY, worldZ);
|
|
+ final int emittedLevel = blockData.getEmittedLight() & emittedMask;
|
|
+
|
|
+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel);
|
|
+ // this accounts for change in emitted light that would cause an increase
|
|
+ if (emittedLevel != 0) {
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
|
|
+ emittedLevel << (6 + 6 + 9) |
|
|
+ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) |
|
|
+ (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
|
|
+ }
|
|
+ // this also accounts for a change in emitted light that would cause a decrease
|
|
+ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa)
|
|
+ // as it checks all neighbours (even if current level is 0)
|
|
+ this.decreaseQueue[this.decreaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
|
|
+ currentLevel << (6 + 6 + 9) |
|
|
+ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4));
|
|
+ // always keep sided transparent false here, new block might be conditionally transparent which would
|
|
+ // prevent us from decreasing sources in the directions where the new block is opaque
|
|
+ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always
|
|
+ // catch that and fix it.
|
|
+ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk,
|
|
+ final Set<BlockPosition> positions) {
|
|
+ for (final BlockPosition pos : positions) {
|
|
+ this.checkBlock(pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ this.performLightDecrease(lightAccess);
|
|
+ }
|
|
+
|
|
+ protected Iterator<BlockPosition> getSources(final IChunkAccess chunk) {
|
|
+ if (chunk instanceof ProtoChunkExtension || chunk instanceof Chunk) {
|
|
+ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is
|
|
+ // skipping empty sections, and the far more optimised reading of types.
|
|
+ List<BlockPosition> sources = new ArrayList<>();
|
|
+
|
|
+ int offX = chunk.getPos().x << 4;
|
|
+ int offZ = chunk.getPos().z << 4;
|
|
+
|
|
+ final ChunkSection[] sections = chunk.getSections();
|
|
+ for (int sectionY = 0; sectionY <= 15; ++sectionY) {
|
|
+ if (sections[sectionY] == null || sections[sectionY].isFullOfAir()) {
|
|
+ // no sources in empty sections
|
|
+ continue;
|
|
+ }
|
|
+ final ChunkSection section = sections[sectionY];
|
|
+
|
|
+ for (int localY = 0; localY <= 15; ++localY) {
|
|
+ final int realY = localY | (sectionY << 4);
|
|
+ for (int localZ = 0; localZ <= 15; ++localZ) {
|
|
+ for (int localX = 0; localX <= 15; ++localX) {
|
|
+ final IBlockData blockData = section.getType(localX, localY, localZ);
|
|
+ if (blockData.getEmittedLight() <= 0) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ sources.add(new BlockPosition(offX + localX, realY, offZ + localZ));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return sources.iterator();
|
|
+ } else {
|
|
+ return chunk.getLightSources().iterator();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) {
|
|
+ // setup sources
|
|
+ final int emittedMask = this.emittedLightMask;
|
|
+ for (final Iterator<BlockPosition> positions = this.getSources(chunk); positions.hasNext();) {
|
|
+ final BlockPosition pos = positions.next();
|
|
+ final IBlockData blockData = this.getBlockData(pos.getX(), pos.getY(), pos.getZ());
|
|
+ final int emittedLight = blockData.getEmittedLight() & emittedMask;
|
|
+
|
|
+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) {
|
|
+ // some other source is brighter
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) |
|
|
+ (emittedLight) << (6 + 6 + 9) |
|
|
+ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) |
|
|
+ (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
|
|
+
|
|
+
|
|
+ // propagation wont set this for us
|
|
+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight);
|
|
+ }
|
|
+
|
|
+ if (needsEdgeChecks) {
|
|
+ // not required to propagate here, but this will reduce the hit of the edge checks
|
|
+ this.performLightIncrease(lightAccess);
|
|
+
|
|
+ // verify neighbour edges
|
|
+ this.checkChunkEdges(lightAccess, chunk);
|
|
+ } else {
|
|
+ this.propagateNeighbourLevels(lightAccess, chunk, -1, 16);
|
|
+
|
|
+ this.performLightIncrease(lightAccess);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..6cae16cc32c49d1787b18f3f51788fe4743113bf
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java
|
|
@@ -0,0 +1,196 @@
|
|
+package com.tuinity.tuinity.chunk.light;
|
|
+
|
|
+import net.minecraft.server.NibbleArray;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.Arrays;
|
|
+
|
|
+// SWMR -> Single Writer Multi Reader Nibble Array
|
|
+public final class SWMRNibbleArray {
|
|
+
|
|
+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block
|
|
+ protected static final byte[] FULL_LIT = new byte[ARRAY_SIZE];
|
|
+ static {
|
|
+ Arrays.fill(FULL_LIT, (byte)-1);
|
|
+ }
|
|
+ // this allows us to maintain only 1 byte array when we're not updating
|
|
+ static final ThreadLocal<ArrayDeque<byte[]>> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new);
|
|
+
|
|
+ private static byte[] allocateBytes() {
|
|
+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst();
|
|
+ if (inPool != null) {
|
|
+ return inPool;
|
|
+ }
|
|
+
|
|
+ return new byte[ARRAY_SIZE];
|
|
+ }
|
|
+
|
|
+ private static void freeBytes(final byte[] bytes) {
|
|
+ WORKING_BYTES_POOL.get().addFirst(bytes);
|
|
+ }
|
|
+
|
|
+ protected byte[] workingBytes;
|
|
+ protected byte[] visibleBytes;
|
|
+ protected final int defaultNullValue;
|
|
+ private boolean isNullNibble;
|
|
+
|
|
+ public SWMRNibbleArray(final boolean isNullNibble, final int defaultNullValue) {
|
|
+ this(null, defaultNullValue);
|
|
+ this.isNullNibble = isNullNibble;
|
|
+ }
|
|
+
|
|
+ public SWMRNibbleArray() {
|
|
+ this(null, 0); // lazy init
|
|
+ }
|
|
+
|
|
+ public SWMRNibbleArray(final byte[] bytes) {
|
|
+ this(bytes, 0);
|
|
+ }
|
|
+
|
|
+ protected SWMRNibbleArray(final byte[] bytes, final int defaultNullValue) {
|
|
+ if (bytes != null && bytes.length != ARRAY_SIZE) {
|
|
+ throw new IllegalArgumentException();
|
|
+ }
|
|
+ this.defaultNullValue = defaultNullValue;
|
|
+ this.visibleBytes = bytes != null ? bytes.clone() : null;
|
|
+ }
|
|
+
|
|
+ public boolean isDirty() {
|
|
+ return this.workingBytes != null;
|
|
+ }
|
|
+
|
|
+ public boolean isNullNibbleUpdating() {
|
|
+ return this.workingBytes == null && this.isNullNibble;
|
|
+ }
|
|
+
|
|
+ public boolean isNullNibbleVisible() {
|
|
+ synchronized (this) {
|
|
+ return this.isNullNibble;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void markNonNull() {
|
|
+ synchronized (this) {
|
|
+ this.isNullNibble = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean isInitialisedUpdating() {
|
|
+ return this.workingBytes != null || this.visibleBytes != null;
|
|
+ }
|
|
+
|
|
+ public boolean isInitialisedVisible() {
|
|
+ synchronized (this) {
|
|
+ return this.visibleBytes != null;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void initialiseWorking() {
|
|
+ if (this.workingBytes != null) {
|
|
+ return;
|
|
+ }
|
|
+ final byte[] working = allocateBytes();
|
|
+ this.copyIntoImpl(working, 0);
|
|
+ this.workingBytes = working;
|
|
+ }
|
|
+
|
|
+ public void copyFrom(final byte[] src, final int off) {
|
|
+ if (this.workingBytes == null) {
|
|
+ this.workingBytes = allocateBytes();
|
|
+ }
|
|
+ System.arraycopy(src, off, this.workingBytes, 0, ARRAY_SIZE);
|
|
+ }
|
|
+
|
|
+ public boolean updateVisible() {
|
|
+ if (this.workingBytes == null) {
|
|
+ return false;
|
|
+
|
|
+ }
|
|
+ final byte[] oldVisible = this.visibleBytes;
|
|
+
|
|
+ synchronized (this) {
|
|
+ this.isNullNibble = false;
|
|
+
|
|
+ this.visibleBytes = this.workingBytes;
|
|
+ this.workingBytes = null;
|
|
+ }
|
|
+
|
|
+ if (oldVisible != null) {
|
|
+ freeBytes(oldVisible);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public void copyInto(final byte[] bytes, final int off) {
|
|
+ synchronized (this) {
|
|
+ this.copyIntoImpl(bytes, off);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void copyIntoImpl(final byte[] bytes, final int off) {
|
|
+ if (this.visibleBytes != null) {
|
|
+ System.arraycopy(this.visibleBytes, 0, bytes, off, ARRAY_SIZE);
|
|
+ } else {
|
|
+ if (this.isNullNibble && this.defaultNullValue != 0) {
|
|
+ Arrays.fill(bytes, off, off + ARRAY_SIZE, (byte)(this.defaultNullValue | (this.defaultNullValue << 4)));
|
|
+ } else {
|
|
+ Arrays.fill(bytes, off, off + ARRAY_SIZE, (byte)0);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public NibbleArray asNibble() {
|
|
+ synchronized (this) {
|
|
+ return this.visibleBytes == null ? (this.isNullNibble ? null : new NibbleArray()) : new NibbleArray(this.visibleBytes.clone());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int getUpdating(final int x, final int y, final int z) {
|
|
+ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8));
|
|
+ }
|
|
+
|
|
+ public int getUpdating(final int index) {
|
|
+ // indices range from 0 -> 4096
|
|
+ byte[] bytes = this.workingBytes == null ? this.visibleBytes : this.workingBytes;
|
|
+ if (bytes == null) {
|
|
+ return this.isNullNibble ? this.defaultNullValue : 0;
|
|
+ }
|
|
+ final byte value = bytes[index >>> 1];
|
|
+
|
|
+ // if we are an even index, we want lower 4 bits
|
|
+ // if we are an odd index, we want upper 4 bits
|
|
+ return ((value >>> ((index & 1) << 2)) & 0xF);
|
|
+ }
|
|
+
|
|
+ public int getVisible(final int x, final int y, final int z) {
|
|
+ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8));
|
|
+ }
|
|
+
|
|
+ public int getVisible(final int index) {
|
|
+ synchronized (this) {
|
|
+ // indices range from 0 -> 4096
|
|
+ if (this.visibleBytes == null) {
|
|
+ return this.isNullNibble ? this.defaultNullValue : 0;
|
|
+ }
|
|
+ final byte value = this.visibleBytes[index >>> 1];
|
|
+
|
|
+ // if we are an even index, we want lower 4 bits
|
|
+ // if we are an odd index, we want upper 4 bits
|
|
+ return ((value >>> ((index & 1) << 2)) & 0xF);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void set(final int x, final int y, final int z, final int value) {
|
|
+ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value);
|
|
+ }
|
|
+
|
|
+ public void set(final int index, final int value) {
|
|
+ if (this.workingBytes == null) {
|
|
+ this.initialiseWorking();
|
|
+ }
|
|
+ final int shift = (index & 1) << 2;
|
|
+ final int i = index >>> 1;
|
|
+
|
|
+ this.workingBytes[i] = (byte)((this.workingBytes[i] & (0xF0 >>> shift)) | (value << shift));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..055f2e1a469830294767af8eb733e998d54596a5
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java
|
|
@@ -0,0 +1,365 @@
|
|
+package com.tuinity.tuinity.chunk.light;
|
|
+
|
|
+import net.minecraft.server.BlockPosition;
|
|
+import net.minecraft.server.ChunkCoordIntPair;
|
|
+import net.minecraft.server.ChunkSection;
|
|
+import net.minecraft.server.ChunkStatus;
|
|
+import net.minecraft.server.IBlockAccess;
|
|
+import net.minecraft.server.IBlockData;
|
|
+import net.minecraft.server.IChunkAccess;
|
|
+import net.minecraft.server.ILightAccess;
|
|
+import net.minecraft.server.VoxelShape;
|
|
+import net.minecraft.server.VoxelShapes;
|
|
+import java.util.Arrays;
|
|
+import java.util.Set;
|
|
+
|
|
+public final class SkyStarLightEngine extends StarLightEngine {
|
|
+
|
|
+ public SkyStarLightEngine() {
|
|
+ super(true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) {
|
|
+ return chunk.getSkyNibbles();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) {
|
|
+ chunk.setSkyNibbles(to);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected boolean canUseChunk(final IChunkAccess chunk) {
|
|
+ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && chunk.isLit();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected final void checkBlock(final int worldX, final int worldY, final int worldZ) {
|
|
+ // blocks can change opacity
|
|
+ // blocks can change direction of propagation
|
|
+
|
|
+ // same logic applies from BlockStarLightEngine#checkBlock
|
|
+
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+
|
|
+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ);
|
|
+
|
|
+ if (currentLevel == 15) {
|
|
+ // must re-propagate clobbered source
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
|
|
+ currentLevel << (6 + 6 + 9) |
|
|
+ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) |
|
|
+ (FLAG_HAS_SIDED_TRANSPARENT_BLOCKS); // don't know if the block is conditionally transparent
|
|
+ } else {
|
|
+ this.setLightLevel(worldX, worldY, worldZ, 0);
|
|
+ }
|
|
+
|
|
+ this.decreaseQueue[this.decreaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
|
|
+ (currentLevel) << (6 + 6 + 9) |
|
|
+ ((Direction.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4));
|
|
+ }
|
|
+
|
|
+ protected final int[] heightMap = new int[16 * 16];
|
|
+ {
|
|
+ Arrays.fill(this.heightMap, -1024); // clear heightmap
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk,
|
|
+ final Set<BlockPosition> positions) {
|
|
+ final IBlockAccess world = lightAccess.getWorld();
|
|
+ final int chunkX = atChunk.getPos().x;
|
|
+ final int chunkZ = atChunk.getPos().z;
|
|
+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16));
|
|
+
|
|
+ // setup heightmap for changes
|
|
+ int highestBlockY = -1024;
|
|
+ for (final BlockPosition pos : positions) {
|
|
+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset;
|
|
+ final int curr = this.heightMap[index];
|
|
+ if (pos.getY() > curr) {
|
|
+ this.heightMap[index] = pos.getY();
|
|
+ }
|
|
+ if (pos.getY() > highestBlockY) {
|
|
+ highestBlockY = pos.getY();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // now we can recalculate the sources for the changed columns
|
|
+ for (int index = 0; index < (16 * 16); ++index) {
|
|
+ final int maxY = this.heightMap[index];
|
|
+ if (maxY == -1024) {
|
|
+ // not changed
|
|
+ continue;
|
|
+ }
|
|
+ this.heightMap[index] = -1024; // restore default for next caller
|
|
+
|
|
+ final int columnX = (index & 15) | (chunkX << 4);
|
|
+ final int columnZ = (index >>> 4) | (chunkZ << 4);
|
|
+
|
|
+ // try and propagate from the above y
|
|
+ int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ);
|
|
+
|
|
+ // maxPropagationY is now the highest block that could not be propagated to
|
|
+
|
|
+ // remove all sources below that are not 15
|
|
+ final int propagateDirection = Direction.NEGATIVE_Y.ordinal();
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ for (int currY = maxPropagationY; currY >= -15; --currY) {
|
|
+ if (this.getLightLevel(columnX, currY, columnZ) != 15) {
|
|
+ break;
|
|
+ }
|
|
+ this.setLightLevel(columnX, currY, columnZ, 0);
|
|
+ this.decreaseQueue[this.decreaseQueueInitialLength++] = (columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) |
|
|
+ (15 << (6 + 6 + 9)) |
|
|
+ ((propagateDirection) << (6 + 6 + 9 + 4));
|
|
+ // do not set transparent blocks for the same reason we don't in the checkBlock method
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // we need to initialise nibbles up to the highest section (we don't save null nibbles)
|
|
+ for (int y = -1; y <= Math.min(16, (highestBlockY >> 4)); ++y) {
|
|
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, y, chunkZ);
|
|
+ nibble.markNonNull();
|
|
+ }
|
|
+
|
|
+ for (final BlockPosition pos : positions) {
|
|
+ this.checkBlock(pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ this.performLightDecrease(lightAccess);
|
|
+ }
|
|
+
|
|
+ protected void initLightNeighbours(final int chunkX, final int chunkZ) {
|
|
+ // vanilla requires that written nibble data has initialised nibble data in 1 radius
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ IChunkAccess chunk = this.getChunkInCache(dx + chunkX, dz + chunkZ);
|
|
+ if (chunk == null) {
|
|
+ continue;
|
|
+ }
|
|
+ // find lowest section
|
|
+ int lowest = 15;
|
|
+ ChunkSection[] sections = chunk.getSections();
|
|
+ for (;lowest > 0 && (sections[lowest] == null || sections[lowest].isFullOfAir()); --lowest) {}
|
|
+
|
|
+ if (lowest == -1) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ for (int y = lowest; y >= -1; --y) {
|
|
+ SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, y, dz + chunkZ);
|
|
+ if (nibble != null && !nibble.isDirty() && nibble.isInitialisedUpdating()) {
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ SWMRNibbleArray ours = this.getNibbleFromCache(chunkX, dy + y, chunkZ);
|
|
+ if (ours != null && !ours.isDirty() && ours.isNullNibbleUpdating()) {
|
|
+ ours.initialiseWorking();
|
|
+ ours.updateVisible();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) {
|
|
+ final IBlockAccess world = lightAccess.getWorld();
|
|
+ final ChunkCoordIntPair chunkPos = chunk.getPos();
|
|
+ final int chunkX = chunkPos.x;
|
|
+ final int chunkZ = chunkPos.z;
|
|
+
|
|
+ final ChunkSection[] sections = chunk.getSections();
|
|
+ final SWMRNibbleArray[] originalNibbles = this.getNibblesForChunkFromCache(chunkX, chunkZ);
|
|
+
|
|
+ int highestNonEmptySection = 16;
|
|
+ while (highestNonEmptySection == -1 || highestNonEmptySection == 16 || sections[highestNonEmptySection] == null || sections[highestNonEmptySection].isFullOfAir()) {
|
|
+ // try propagate FULL to neighbours
|
|
+
|
|
+ // check neighbours to see if we need to propagate into them
|
|
+ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) {
|
|
+ final int neighbourX = chunkX + direction.x;
|
|
+ final int neighbourZ = chunkZ + direction.z;
|
|
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ);
|
|
+ if (neighbourNibble == null) {
|
|
+ // unloaded neighbour
|
|
+ continue;
|
|
+ }
|
|
+ if (neighbourNibble.isNullNibbleUpdating()) {
|
|
+ // most of the time we fall here
|
|
+ // no point of propagating full light into full light
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // it looks like we need to propagate into the neighbour
|
|
+
|
|
+ final int incX;
|
|
+ final int incZ;
|
|
+ final int startX;
|
|
+ final int startZ;
|
|
+
|
|
+ if (direction.x != 0) {
|
|
+ // x direction
|
|
+ incX = 0;
|
|
+ incZ = 1;
|
|
+
|
|
+ if (direction.x < 0) {
|
|
+ // negative
|
|
+ startX = chunkX << 4;
|
|
+ } else {
|
|
+ startX = chunkX << 4 | 15;
|
|
+ }
|
|
+ startZ = chunkZ << 4;
|
|
+ } else {
|
|
+ // z direction
|
|
+ incX = 1;
|
|
+ incZ = 0;
|
|
+
|
|
+ if (direction.z < 0) {
|
|
+ // negative
|
|
+ startZ = chunkZ << 4;
|
|
+ } else {
|
|
+ startZ = chunkZ << 4 | 15;
|
|
+ }
|
|
+ startX = chunkX << 4;
|
|
+ }
|
|
+
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ final int propagateDirection = direction.ordinal() | 16; // we only want to check in this direction
|
|
+
|
|
+ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) {
|
|
+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) {
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) |
|
|
+ (15 << (6 + 6 + 9)) | // we know we're at full lit here
|
|
+ ((propagateDirection) << (6 + 6 + 9 + 4));
|
|
+ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY)
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (highestNonEmptySection-- == -1) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (highestNonEmptySection >= 0) {
|
|
+ // mark the rest of our nibbles as 0
|
|
+ for (int currY = highestNonEmptySection; currY >= -1; --currY) {
|
|
+ this.getNibbleFromCache(chunkX, currY, chunkZ).markNonNull();
|
|
+ }
|
|
+
|
|
+ // fill out our other sources
|
|
+ final int minX = chunkPos.x << 4;
|
|
+ final int maxX = chunkPos.x << 4 | 15;
|
|
+ final int minZ = chunkPos.z << 4;
|
|
+ final int maxZ = chunkPos.z << 4 | 15;
|
|
+ final int startY = highestNonEmptySection << 4 | 15;
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final int end = this.tryPropagateSkylight(world, currX, startY, currZ);
|
|
+ if (end == startY) {
|
|
+ // we need to propagate this one ourselves.
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (startY << (6 + 6)) + this.coordinateOffset) |
|
|
+ (15 << (6 + 6 + 9)) | // we know we're at full lit here
|
|
+ ((Direction.NEGATIVE_Y.ordinal()) << (6 + 6 + 9 + 4)); // no need to check upwards, we know it's 15.
|
|
+ // we know this block is air because the section is empty, so it's obviously not sidedly
|
|
+ // transparent.
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } // else: apparently the chunk is empty
|
|
+
|
|
+ if (needsEdgeChecks) {
|
|
+ // not required to propagate here, but this will reduce the hit of the edge checks
|
|
+ this.performLightIncrease(lightAccess);
|
|
+
|
|
+ this.checkChunkEdges(lightAccess, chunk);
|
|
+ } else {
|
|
+ this.propagateNeighbourLevels(lightAccess, chunk, -1, highestNonEmptySection);
|
|
+
|
|
+ this.performLightIncrease(lightAccess);
|
|
+ }
|
|
+
|
|
+ this.initLightNeighbours(chunkPos.x, chunkPos.z);
|
|
+ }
|
|
+
|
|
+ protected final int tryPropagateSkylight(final IBlockAccess world, final int worldX, final int startY, final int worldZ) {
|
|
+ final BlockPosition.MutableBlockPosition mutablePos = this.mutablePos3;
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ final int propagateDirection = Direction.NEGATIVE_Y.ordinal(); // just don't check upwards.
|
|
+
|
|
+ if (this.getLightLevel(worldX, startY + 1, worldZ) != 15) {
|
|
+ return startY;
|
|
+ }
|
|
+
|
|
+ IBlockData above = this.getBlockData(worldX, startY + 1, worldZ);
|
|
+ if (above == null) {
|
|
+ above = AIR_BLOCK_DATA;
|
|
+ }
|
|
+
|
|
+ int maxPropagationY;
|
|
+ for (maxPropagationY = startY; maxPropagationY >= -15; --maxPropagationY) {
|
|
+ IBlockData current = this.getBlockData(worldX, maxPropagationY, worldZ);
|
|
+ if (current == null) {
|
|
+ current = AIR_BLOCK_DATA;
|
|
+ }
|
|
+
|
|
+ final VoxelShape fromShape;
|
|
+ if (above.isConditionallyFullOpaque()) {
|
|
+ this.mutablePos2.setValues(worldX, maxPropagationY + 1, worldZ);
|
|
+ fromShape = above.getCullingFace(world, this.mutablePos2, Direction.NEGATIVE_Y.nms);
|
|
+ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) {
|
|
+ // above wont let us propagate
|
|
+ break;
|
|
+ }
|
|
+ } else {
|
|
+ fromShape = VoxelShapes.getEmptyShape();
|
|
+ }
|
|
+
|
|
+ final int opacityIfCached = current.getOpacityIfCached();
|
|
+ // does light propagate from the top down?
|
|
+ if (opacityIfCached != -1) {
|
|
+ if (opacityIfCached != 0) {
|
|
+ // we cannot propagate 15 through this
|
|
+ break;
|
|
+ }
|
|
+ // most of the time it falls here.
|
|
+ this.setLightLevel(worldX, maxPropagationY, worldZ, 15);
|
|
+ // add to propagate
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) |
|
|
+ (15 << (6 + 6 + 9)) | // we know we're at full lit here
|
|
+ ((propagateDirection) << (6 + 6 + 9 + 4));
|
|
+ } else {
|
|
+ mutablePos.setValues(worldX, maxPropagationY, worldZ);
|
|
+ int flags = 0;
|
|
+ if (current.isConditionallyFullOpaque()) {
|
|
+ final VoxelShape cullingFace = current.getCullingFace(world, mutablePos, Direction.POSITIVE_Y.nms);
|
|
+
|
|
+ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) {
|
|
+ // can't propagate here, we're done on this column.
|
|
+ break;
|
|
+ }
|
|
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
|
|
+ }
|
|
+
|
|
+ final int opacity = current.getOpacity(world, mutablePos);
|
|
+ if (opacity > 0) {
|
|
+ // let the queued value (if any) handle it from here.
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ this.setLightLevel(worldX, maxPropagationY, worldZ, 15);
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) |
|
|
+ (15 << (6 + 6 + 9)) | // we know we're at full lit here
|
|
+ ((propagateDirection) << (6 + 6 + 9 + 4)) |
|
|
+ flags;
|
|
+ }
|
|
+
|
|
+ above = current;
|
|
+ }
|
|
+
|
|
+ return maxPropagationY;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..7fcbfa5c224426540693424c34a517bda23e6dd1
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java
|
|
@@ -0,0 +1,1014 @@
|
|
+package com.tuinity.tuinity.chunk.light;
|
|
+
|
|
+import com.destroystokyo.paper.util.math.IntegerUtil;
|
|
+import net.minecraft.server.BlockPosition;
|
|
+import net.minecraft.server.Blocks;
|
|
+import net.minecraft.server.ChunkCoordIntPair;
|
|
+import net.minecraft.server.ChunkSection;
|
|
+import net.minecraft.server.EnumDirection;
|
|
+import net.minecraft.server.EnumSkyBlock;
|
|
+import net.minecraft.server.IBlockAccess;
|
|
+import net.minecraft.server.IBlockData;
|
|
+import net.minecraft.server.IChunkAccess;
|
|
+import net.minecraft.server.ILightAccess;
|
|
+import net.minecraft.server.SectionPosition;
|
|
+import net.minecraft.server.VoxelShape;
|
|
+import net.minecraft.server.VoxelShapes;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Arrays;
|
|
+import java.util.List;
|
|
+import java.util.Set;
|
|
+
|
|
+public abstract class StarLightEngine {
|
|
+
|
|
+ protected static final IBlockData AIR_BLOCK_DATA = Blocks.AIR.getBlockData();
|
|
+
|
|
+ protected static final ChunkSection EMPTY_CHUNK_SECTION = new ChunkSection(0);
|
|
+
|
|
+ protected static final Direction[] DIRECTIONS = Direction.values();
|
|
+ protected static final Direction[] AXIS_DIRECTIONS = DIRECTIONS;
|
|
+ protected static final Direction[] ONLY_HORIZONTAL_DIRECTIONS = new Direction[] {
|
|
+ Direction.POSITIVE_X, Direction.NEGATIVE_X,
|
|
+ Direction.POSITIVE_Z, Direction.NEGATIVE_Z
|
|
+ };
|
|
+
|
|
+ protected static enum Direction {
|
|
+
|
|
+ // Declaration order is important and relied upon. Do not change without modifying propagation code.
|
|
+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0),
|
|
+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1),
|
|
+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0);
|
|
+
|
|
+ static {
|
|
+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X;
|
|
+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z;
|
|
+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y;
|
|
+ }
|
|
+
|
|
+ protected Direction opposite;
|
|
+
|
|
+ public final int x;
|
|
+ public final int y;
|
|
+ public final int z;
|
|
+ public final EnumDirection nms;
|
|
+
|
|
+ Direction(final int x, final int y, final int z) {
|
|
+ this.x = x;
|
|
+ this.y = y;
|
|
+ this.z = z;
|
|
+ this.nms = EnumDirection.from(x, y, z);
|
|
+ }
|
|
+
|
|
+ public Direction getOpposite() {
|
|
+ return this.opposite;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1
|
|
+ // for explaining how light propagates via breadth-first search
|
|
+
|
|
+ // While the above is a good start to understanding the general idea of what the general principles are, it's not
|
|
+ // exactly how the vanilla light engine should behave for minecraft.
|
|
+
|
|
+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2]
|
|
+ // for the y chunk section it's from [-1, 16] or [0, 17]
|
|
+ // index = x + (z * 5) + (y * 25)
|
|
+ // null index indicates the chunk section doesn't exist (empty or out of bounds)
|
|
+ protected final ChunkSection[] sectionCache = new ChunkSection[5 * 5 * (16 + 2 + 2)]; // add two extra sections for buffer
|
|
+
|
|
+ // the exact same as above, except for storing fast access to SWMRNibbleArray
|
|
+ // for the y chunk section it's from [-1, 16] or [0, 17]
|
|
+ // index = x + (z * 5) + (y * 25)
|
|
+ protected final SWMRNibbleArray[] nibbleCache = new SWMRNibbleArray[5 * 5 * (16 + 2 + 2)]; // add two extra sections for buffer
|
|
+
|
|
+ // always initialsed during start of lighting. no index is null.
|
|
+ // index = x + (z * 5)
|
|
+ protected final IChunkAccess[] chunkCache = new IChunkAccess[5 * 5];
|
|
+
|
|
+ protected final BlockPosition.MutableBlockPosition mutablePos1 = new BlockPosition.MutableBlockPosition();
|
|
+ protected final BlockPosition.MutableBlockPosition mutablePos2 = new BlockPosition.MutableBlockPosition();
|
|
+ protected final BlockPosition.MutableBlockPosition mutablePos3 = new BlockPosition.MutableBlockPosition();
|
|
+
|
|
+ protected int encodeOffsetX;
|
|
+ protected int encodeOffsetY;
|
|
+ protected int encodeOffsetZ;
|
|
+
|
|
+ protected int coordinateOffset;
|
|
+
|
|
+ protected int chunkOffsetX;
|
|
+ protected int chunkOffsetY;
|
|
+ protected int chunkOffsetZ;
|
|
+
|
|
+ protected int chunkIndexOffset;
|
|
+ protected int chunkSectionIndexOffset;
|
|
+
|
|
+ protected final boolean skylightPropagator;
|
|
+ protected final int emittedLightMask;
|
|
+
|
|
+ protected StarLightEngine(final boolean skylightPropagator) {
|
|
+ this.skylightPropagator = skylightPropagator;
|
|
+ this.emittedLightMask = skylightPropagator ? 0 : 0xF;
|
|
+ }
|
|
+
|
|
+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) {
|
|
+ // 31 = center + encodeOffset
|
|
+ this.encodeOffsetX = 31 - centerX;
|
|
+ this.encodeOffsetY = 31; // we want 0 to be the smallest encoded value
|
|
+ this.encodeOffsetZ = 31 - centerZ;
|
|
+
|
|
+ // coordinateIndex = x | (z << 6) | (y << 12)
|
|
+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12);
|
|
+
|
|
+ // 2 = (centerX >> 4) + chunkOffset
|
|
+ this.chunkOffsetX = 2 - (centerX >> 4);
|
|
+ this.chunkOffsetY = 2; // lowest should be 0, not -2
|
|
+ this.chunkOffsetZ = 2 - (centerZ >> 4);
|
|
+
|
|
+ // chunk index = x + (5 * z)
|
|
+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ);
|
|
+
|
|
+ // chunk section index = x + (5 * z) + ((5*5) * y)
|
|
+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY);
|
|
+ }
|
|
+
|
|
+ protected final void setupCaches(final ILightAccess world, final int centerX, final int centerY, final int centerZ, final boolean relaxed) {
|
|
+ final int centerChunkX = centerX >> 4;
|
|
+ final int centerChunkY = centerY >> 4;
|
|
+ final int centerChunkZ = centerZ >> 4;
|
|
+
|
|
+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7);
|
|
+
|
|
+ final int minX = centerChunkX - 1;
|
|
+ final int minZ = centerChunkZ - 1;
|
|
+ final int maxX = centerChunkX + 1;
|
|
+ final int maxZ = centerChunkZ + 1;
|
|
+
|
|
+ for (int cx = minX; cx <= maxX; ++cx) {
|
|
+ for (int cz = minZ; cz <= maxZ; ++cz) {
|
|
+ final IChunkAccess chunk = (IChunkAccess)world.getFeaturesReadyChunk(cx, cz);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ if (relaxed) {
|
|
+ continue;
|
|
+ }
|
|
+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready");
|
|
+ }
|
|
+
|
|
+ if (!this.canUseChunk(chunk)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.setChunkInCache(cx, cz, chunk);
|
|
+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections());
|
|
+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final IChunkAccess getChunkInCache(final int chunkX, final int chunkZ) {
|
|
+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset];
|
|
+ }
|
|
+
|
|
+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final IChunkAccess chunk) {
|
|
+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk;
|
|
+ }
|
|
+
|
|
+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final ChunkSection[] sections) {
|
|
+ final int chunkIndex = chunkX + 5*chunkZ;
|
|
+ for (int cy = -1; cy <= 16; ++cy) {
|
|
+ this.sectionCache[chunkIndex + (cy * (5 * 5)) + this.chunkSectionIndexOffset] = sections == null ? null : (cy >= 0 && cy <= 15 ? (sections[cy] == null || sections[cy].isFullOfAir() ? EMPTY_CHUNK_SECTION : sections[cy]) : EMPTY_CHUNK_SECTION);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) {
|
|
+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset];
|
|
+ }
|
|
+
|
|
+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) {
|
|
+ final SWMRNibbleArray[] ret = getEmptyLightArray();
|
|
+
|
|
+ for (int cy = -1; cy <= 16; ++cy) {
|
|
+ ret[cy + 1] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset];
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) {
|
|
+ for (int cy = -1; cy <= 16; ++cy) {
|
|
+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy + 1]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) {
|
|
+ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble;
|
|
+ }
|
|
+
|
|
+ protected final void updateVisible(final ILightAccess lightAccess) {
|
|
+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) {
|
|
+ final SWMRNibbleArray nibble = this.nibbleCache[index];
|
|
+ if (nibble != null && nibble.updateVisible()) {
|
|
+ final int chunkX = (index % 5) - this.chunkOffsetX;
|
|
+ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ;
|
|
+ final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY;
|
|
+ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK, new SectionPosition(chunkX, chunkY, chunkZ));
|
|
+ // initialise 1 radius neighbours
|
|
+ if (this.skylightPropagator) {
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ SWMRNibbleArray neighbour = this.getNibbleFromCache(chunkX + dx, chunkY + dy, chunkZ + dz);
|
|
+ if (neighbour != null && !neighbour.isDirty() && neighbour.isNullNibbleUpdating()) {
|
|
+ neighbour.initialiseWorking();
|
|
+ neighbour.updateVisible();
|
|
+ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK,
|
|
+ new SectionPosition(chunkX + dx, chunkY + dy, chunkZ + dz));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void destroyCaches() {
|
|
+ Arrays.fill(this.sectionCache, null);
|
|
+ Arrays.fill(this.nibbleCache, null);
|
|
+ Arrays.fill(this.chunkCache, null);
|
|
+ }
|
|
+
|
|
+ protected final IBlockData getBlockData(final int worldX, final int worldY, final int worldZ) {
|
|
+ final ChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset];
|
|
+
|
|
+ if (section != null) {
|
|
+ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_DATA : section.blockIds.rawGet((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8));
|
|
+ }
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ protected final IBlockData getBlockData(final int sectionIndex, final int localIndex) {
|
|
+ final ChunkSection section = this.sectionCache[sectionIndex];
|
|
+
|
|
+ if (section != null) {
|
|
+ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_DATA : section.blockIds.rawGet(localIndex);
|
|
+ }
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) {
|
|
+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset];
|
|
+
|
|
+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8));
|
|
+ }
|
|
+
|
|
+ protected final int getLightLevel(final int sectionIndex, final int localIndex) {
|
|
+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex];
|
|
+
|
|
+ return nibble == null ? 0 : nibble.getUpdating(localIndex);
|
|
+ }
|
|
+
|
|
+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) {
|
|
+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset];
|
|
+
|
|
+ if (nibble != null) {
|
|
+ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int level) {
|
|
+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex];
|
|
+
|
|
+ if (nibble != null) {
|
|
+ nibble.set(localIndex, level);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static SWMRNibbleArray[] getFilledEmptyLight(final boolean skylight) {
|
|
+ final SWMRNibbleArray[] ret = getEmptyLightArray();
|
|
+
|
|
+ for (int i = 0, len = ret.length; i < len; ++i) {
|
|
+ ret[i] = new SWMRNibbleArray(true, skylight ? 15 : 0);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public static SWMRNibbleArray[] getEmptyLightArray() {
|
|
+ return new SWMRNibbleArray[16 + 2];
|
|
+ }
|
|
+
|
|
+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk);
|
|
+
|
|
+ protected abstract void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to);
|
|
+
|
|
+ protected abstract boolean canUseChunk(final IChunkAccess chunk);
|
|
+
|
|
+ public final void blocksChangedInChunk(final ILightAccess lightAccess, final int chunkX, final int chunkZ,
|
|
+ final Set<BlockPosition> positions) {
|
|
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false);
|
|
+ try {
|
|
+ this.propagateBlockChanges(lightAccess, this.getChunkInCache(chunkX, chunkZ), positions);
|
|
+ this.updateVisible(lightAccess);
|
|
+ } finally {
|
|
+ this.destroyCaches();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // subclasses should not initialise caches, as this will always be done by the super call
|
|
+ // subclasses should not invoke updateVisible, as this will always be done by the super call
|
|
+ protected abstract void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk,
|
|
+ final Set<BlockPosition> positions);
|
|
+
|
|
+ protected abstract void checkBlock(final int worldX, final int worldY, final int worldZ);
|
|
+
|
|
+ // subclasses should not initialise caches, as this will always be done by the super call
|
|
+ // subclasses should not invoke updateVisible, as this will always be done by the super call
|
|
+ protected final void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk) {
|
|
+ final ChunkCoordIntPair chunkPos = chunk.getPos();
|
|
+ final int chunkX = chunkPos.x;
|
|
+ final int chunkZ = chunkPos.z;
|
|
+
|
|
+ for (int currSectionY = 16; currSectionY >= -1; --currSectionY) {
|
|
+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ);
|
|
+ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) {
|
|
+ final int neighbourOffX = direction.x;
|
|
+ final int neighbourOffZ = direction.z;
|
|
+
|
|
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX,
|
|
+ currSectionY, chunkZ + neighbourOffZ);
|
|
+
|
|
+ if (neighbourNibble == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) {
|
|
+ if (this.skylightPropagator) {
|
|
+ if (currNibble.isNullNibbleUpdating() == neighbourNibble.isNullNibbleUpdating()) {
|
|
+ continue;
|
|
+ } // else fall through to edge checks
|
|
+ } else {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final int incX;
|
|
+ final int incZ;
|
|
+ final int startX;
|
|
+ final int startZ;
|
|
+
|
|
+ if (neighbourOffX != 0) {
|
|
+ // x direction
|
|
+ incX = 0;
|
|
+ incZ = 1;
|
|
+
|
|
+ if (direction.x < 0) {
|
|
+ // negative
|
|
+ startX = chunkX << 4;
|
|
+ } else {
|
|
+ startX = chunkX << 4 | 15;
|
|
+ }
|
|
+ startZ = chunkZ << 4;
|
|
+ } else {
|
|
+ // z direction
|
|
+ incX = 1;
|
|
+ incZ = 0;
|
|
+
|
|
+ if (neighbourOffZ < 0) {
|
|
+ // negative
|
|
+ startZ = chunkZ << 4;
|
|
+ } else {
|
|
+ startZ = chunkZ << 4 | 15;
|
|
+ }
|
|
+ startX = chunkX << 4;
|
|
+ }
|
|
+
|
|
+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) {
|
|
+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) {
|
|
+ final int neighbourX = currX + neighbourOffX;
|
|
+ final int neighbourZ = currZ + neighbourOffZ;
|
|
+
|
|
+ final int currentLevel = currNibble.getUpdating((currX & 15) |
|
|
+ ((currZ & 15)) << 4 |
|
|
+ ((currY & 15) << 8)
|
|
+ );
|
|
+ final int neighbourLevel = neighbourNibble.getUpdating((neighbourX & 15) |
|
|
+ ((neighbourZ & 15)) << 4 |
|
|
+ ((currY & 15) << 8)
|
|
+ );
|
|
+
|
|
+ if (currentLevel == neighbourLevel && (currentLevel == 0 || currentLevel == 15)) {
|
|
+ // nothing to check here
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (IntegerUtil.branchlessAbs(currentLevel - neighbourLevel) == 1) {
|
|
+ final IBlockData currentBlock = this.getBlockData(currX, currY, currZ);
|
|
+ final IBlockData neighbourBlock = this.getBlockData(neighbourX, currY, neighbourZ);
|
|
+
|
|
+ final int currentOpacity = currentBlock.getOpacityIfCached();
|
|
+ final int neighbourOpacity = neighbourBlock.getOpacityIfCached();
|
|
+ if (currentOpacity == 0 || currentOpacity == 1 ||
|
|
+ neighbourOpacity == 0 || neighbourOpacity == 1) {
|
|
+ // looks good
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // setup queue, it looks like something could be inconsistent
|
|
+ this.checkBlock(currX, currY, currZ);
|
|
+ this.checkBlock(neighbourX, currY, neighbourZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.performLightDecrease(lightAccess);
|
|
+ }
|
|
+
|
|
+ protected final void propagateNeighbourLevels(final ILightAccess lightAccess, final IChunkAccess chunk, final int fromSection, final int toSection) {
|
|
+ final ChunkCoordIntPair chunkPos = chunk.getPos();
|
|
+ final int chunkX = chunkPos.x;
|
|
+ final int chunkZ = chunkPos.z;
|
|
+
|
|
+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) {
|
|
+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ);
|
|
+ for (final Direction direction : ONLY_HORIZONTAL_DIRECTIONS) {
|
|
+ final int neighbourOffX = direction.x;
|
|
+ final int neighbourOffZ = direction.z;
|
|
+
|
|
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX,
|
|
+ currSectionY, chunkZ + neighbourOffZ);
|
|
+
|
|
+ if (neighbourNibble == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!neighbourNibble.isInitialisedUpdating()) {
|
|
+ if (this.skylightPropagator) {
|
|
+ if (currNibble.isNullNibbleUpdating() == neighbourNibble.isNullNibbleUpdating() || !neighbourNibble.isNullNibbleUpdating()) {
|
|
+ continue;
|
|
+ } // else fall through to edge checks
|
|
+ } else {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final int incX;
|
|
+ final int incZ;
|
|
+ final int startX;
|
|
+ final int startZ;
|
|
+
|
|
+ if (neighbourOffX != 0) {
|
|
+ // x direction
|
|
+ incX = 0;
|
|
+ incZ = 1;
|
|
+
|
|
+ if (direction.x < 0) {
|
|
+ // negative
|
|
+ startX = (chunkX << 4) - 1;
|
|
+ } else {
|
|
+ startX = (chunkX << 4) + 16;
|
|
+ }
|
|
+ startZ = chunkZ << 4;
|
|
+ } else {
|
|
+ // z direction
|
|
+ incX = 1;
|
|
+ incZ = 0;
|
|
+
|
|
+ if (neighbourOffZ < 0) {
|
|
+ // negative
|
|
+ startZ = (chunkZ << 4) - 1;
|
|
+ } else {
|
|
+ startZ = (chunkZ << 4) + 16;
|
|
+ }
|
|
+ startX = chunkX << 4;
|
|
+ }
|
|
+
|
|
+ final int propagateDirection = direction.getOpposite().ordinal() | 16; // we only want to check in this direction towards this chunk
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+
|
|
+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) {
|
|
+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) {
|
|
+ final int level = neighbourNibble.getUpdating(
|
|
+ (currX & 15) |
|
|
+ (currZ & 15) << 4 |
|
|
+ (currY & 15) << 8
|
|
+ );
|
|
+
|
|
+ if (level <= 1) {
|
|
+ // nothing to propagate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) |
|
|
+ (level << (6 + 6 + 9)) |
|
|
+ ((propagateDirection) << (6 + 6 + 9 + 4)) |
|
|
+ FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; // don't know if the current block is transparent, must check.
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final void checkChunkEdges(final ILightAccess lightAccess, final int chunkX, final int chunkZ) {
|
|
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false);
|
|
+ try {
|
|
+ this.checkChunkEdges(lightAccess, this.getChunkInCache(chunkX, chunkZ));
|
|
+ this.updateVisible(lightAccess);
|
|
+ } finally {
|
|
+ this.destroyCaches();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // subclasses should not initialise caches, as this will always be done by the super call
|
|
+ // subclasses should not invoke updateVisible, as this will always be done by the super call
|
|
+ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current
|
|
+ // chunks light values with respect to neighbours
|
|
+ protected abstract void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks);
|
|
+
|
|
+ public final void light(final ILightAccess lightAccess, final int chunkX, final int chunkZ) {
|
|
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false);
|
|
+ // force current chunk into cache
|
|
+ final IChunkAccess chunk = (IChunkAccess)lightAccess.getFeaturesReadyChunk(chunkX, chunkZ);
|
|
+ this.setChunkInCache(chunkX, chunkZ, chunk);
|
|
+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections());
|
|
+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk));
|
|
+
|
|
+ try {
|
|
+ this.lightChunk(lightAccess, chunk, false);
|
|
+ this.updateVisible(lightAccess);
|
|
+ } finally {
|
|
+ this.destroyCaches();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final void relight(final ILightAccess world, final int chunkX, final int chunkZ) {
|
|
+ final IChunkAccess chunk = (IChunkAccess)world.getFeaturesReadyChunk(chunkX, chunkZ);
|
|
+ this.relightChunk(world, chunk);
|
|
+ }
|
|
+
|
|
+ protected final void relightChunk(final ILightAccess lightAccess, final IChunkAccess chunk) {
|
|
+ final ChunkCoordIntPair chunkPos = chunk.getPos();
|
|
+ this.setupEncodeOffset(chunkPos.x * 16 + 7, 128, chunkPos.z * 16 + 7);
|
|
+
|
|
+ try {
|
|
+ this.setChunkInCache(chunkPos.x, chunkPos.z, chunk);
|
|
+ this.setBlocksForChunkInCache(chunkPos.x, chunkPos.z, chunk.getSections());
|
|
+ final SWMRNibbleArray[] chunkNibbles = getFilledEmptyLight(this.skylightPropagator);
|
|
+ this.setNibblesForChunkInCache(chunkPos.x, chunkPos.z, chunkNibbles);
|
|
+ this.lightChunk(lightAccess, chunk, false);
|
|
+
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int cx = dx + chunkPos.x;
|
|
+ final int cz = dz + chunkPos.z;
|
|
+ final IChunkAccess neighbourChunk = (IChunkAccess)lightAccess.getFeaturesReadyChunk(cx, cz);
|
|
+
|
|
+ if (neighbourChunk == null || !this.canUseChunk(neighbourChunk)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.setChunkInCache(cx, cz, neighbourChunk);
|
|
+ this.setBlocksForChunkInCache(cx, cz, neighbourChunk.getSections());
|
|
+ this.setNibblesForChunkInCache(cx, cz, getFilledEmptyLight(this.skylightPropagator));
|
|
+ this.lightChunk(lightAccess, neighbourChunk, false);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.setNibbles(chunk, chunkNibbles);
|
|
+ this.updateVisible(lightAccess);
|
|
+ } finally {
|
|
+ this.destroyCaches();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // old algorithm for propagating
|
|
+ // this is also the basic algorithm, the optimised algorithm is always going to be tested against this one
|
|
+ // and this one is always tested against vanilla
|
|
+ // contains:
|
|
+ // lower 21 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6))))
|
|
+ // next 4 bits: propagated light level (0, 15]
|
|
+ // next 5 bits: direction propagated from
|
|
+ // next 0 bits: unused
|
|
+ // last 2 bits: state flags
|
|
+ // state flags:
|
|
+ // whether the propagation needs to check if its current level is equal to the expected level
|
|
+ // used only in increase propagation
|
|
+ protected static final int FLAG_RECHECK_LEVEL = Integer.MIN_VALUE >>> 1;
|
|
+ // whether the propagation needs to consider if its block is conditionally transparent
|
|
+ protected static final int FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Integer.MIN_VALUE;
|
|
+
|
|
+ protected final int[] increaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1];
|
|
+ protected int increaseQueueInitialLength;
|
|
+ protected final int[] decreaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1];
|
|
+ protected int decreaseQueueInitialLength;
|
|
+
|
|
+ protected static final Direction[][] OLD_CHECK_DIRECTIONS = new Direction[4 * 8][];
|
|
+ static {
|
|
+ for (int i = 0; i < AXIS_DIRECTIONS.length; ++i) {
|
|
+ final Direction direction = AXIS_DIRECTIONS[i];
|
|
+ final List<Direction> directions = new ArrayList<>(Arrays.asList(AXIS_DIRECTIONS));
|
|
+ directions.remove(direction.getOpposite());
|
|
+ OLD_CHECK_DIRECTIONS[direction.ordinal()] = directions.toArray(new Direction[0]);
|
|
+ OLD_CHECK_DIRECTIONS[direction.ordinal() | 8] = AXIS_DIRECTIONS; // flag ALL_DIRECTIONS
|
|
+ OLD_CHECK_DIRECTIONS[direction.ordinal() | 16] = new Direction[] { direction }; // flag ONLY_THIS_DIRECTION
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void performLightIncrease(final ILightAccess lightAccess) {
|
|
+ final IBlockAccess world = lightAccess.getWorld();
|
|
+ final int[] queue = this.increaseQueue;
|
|
+ int queueReadIndex = 0;
|
|
+ int queueLength = this.increaseQueueInitialLength;
|
|
+ this.increaseQueueInitialLength = 0;
|
|
+ final int decodeOffsetX = -this.encodeOffsetX;
|
|
+ final int decodeOffsetY = -this.encodeOffsetY;
|
|
+ final int decodeOffsetZ = -this.encodeOffsetZ;
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ final int sectionOffset = this.chunkSectionIndexOffset;
|
|
+
|
|
+ while (queueReadIndex < queueLength) {
|
|
+ final int queueValue = queue[queueReadIndex++];
|
|
+
|
|
+ final int posX = ((queueValue & 63) + decodeOffsetX);
|
|
+ final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ);
|
|
+ final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY);
|
|
+ final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF);
|
|
+ final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F);
|
|
+ final Direction[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection];
|
|
+
|
|
+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0) {
|
|
+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) {
|
|
+ // not at the level we expect, so something changed.
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) {
|
|
+ // we don't need to worry about our state here.
|
|
+ for (final Direction propagate : checkDirections) {
|
|
+ final int offX = posX + propagate.x;
|
|
+ final int offY = posY + propagate.y;
|
|
+ final int offZ = posZ + propagate.z;
|
|
+
|
|
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
|
|
+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8);
|
|
+
|
|
+ final int currentLevel = this.getLightLevel(sectionIndex, localIndex);
|
|
+
|
|
+ if (currentLevel >= (propagatedLightLevel - 1)) {
|
|
+ continue; // already at the level we want
|
|
+ }
|
|
+
|
|
+ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex);
|
|
+ if (blockData == null) {
|
|
+ continue;
|
|
+ }
|
|
+ final int opacityCached = blockData.getOpacityIfCached();
|
|
+ if (opacityCached != -1) {
|
|
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached);
|
|
+ if (targetLevel > currentLevel) {
|
|
+ this.setLightLevel(sectionIndex, localIndex, targetLevel);
|
|
+ if (targetLevel > 1) {
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4));
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ continue;
|
|
+ } else {
|
|
+ this.mutablePos1.setValues(offX, offY, offZ);
|
|
+ int flags = 0;
|
|
+ if (blockData.isConditionallyFullOpaque()) {
|
|
+ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms);
|
|
+
|
|
+ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) {
|
|
+ continue;
|
|
+ }
|
|
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
|
|
+ }
|
|
+
|
|
+ final int opacity = blockData.getOpacity(world, this.mutablePos1);
|
|
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity);
|
|
+ if (targetLevel <= currentLevel) {
|
|
+ continue;
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, targetLevel);
|
|
+ if (targetLevel > 1) {
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4))
|
|
+ | (flags);
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ // we actually need to worry about our state here
|
|
+ final IBlockData fromBlock = this.getBlockData(posX, posY, posZ);
|
|
+ this.mutablePos2.setValues(posX, posY, posZ);
|
|
+ for (final Direction propagate : checkDirections) {
|
|
+ final int offX = posX + propagate.x;
|
|
+ final int offY = posY + propagate.y;
|
|
+ final int offZ = posZ + propagate.z;
|
|
+
|
|
+ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape();
|
|
+
|
|
+ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
|
|
+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8);
|
|
+
|
|
+ final int currentLevel = this.getLightLevel(sectionIndex, localIndex);
|
|
+
|
|
+ if (currentLevel >= (propagatedLightLevel - 1)) {
|
|
+ continue; // already at the level we want
|
|
+ }
|
|
+
|
|
+ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex);
|
|
+ if (blockData == null) {
|
|
+ continue;
|
|
+ }
|
|
+ final int opacityCached = blockData.getOpacityIfCached();
|
|
+ if (opacityCached != -1) {
|
|
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached);
|
|
+ if (targetLevel > currentLevel) {
|
|
+ this.setLightLevel(sectionIndex, localIndex, targetLevel);
|
|
+ if (targetLevel > 1) {
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4));
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ continue;
|
|
+ } else {
|
|
+ this.mutablePos1.setValues(offX, offY, offZ);
|
|
+ int flags = 0;
|
|
+ if (blockData.isConditionallyFullOpaque()) {
|
|
+ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms);
|
|
+
|
|
+ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) {
|
|
+ continue;
|
|
+ }
|
|
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
|
|
+ }
|
|
+
|
|
+ final int opacity = blockData.getOpacity(world, this.mutablePos1);
|
|
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity);
|
|
+ if (targetLevel <= currentLevel) {
|
|
+ continue;
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, targetLevel);
|
|
+ if (targetLevel > 1) {
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4))
|
|
+ | (flags);
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void performLightDecrease(final ILightAccess lightAccess) {
|
|
+ final IBlockAccess world = lightAccess.getWorld();
|
|
+ final int[] queue = this.decreaseQueue;
|
|
+ final int[] increaseQueue = this.increaseQueue;
|
|
+ int queueReadIndex = 0;
|
|
+ int queueLength = this.decreaseQueueInitialLength;
|
|
+ this.decreaseQueueInitialLength = 0;
|
|
+ int increaseQueueLength = this.increaseQueueInitialLength;
|
|
+ final int decodeOffsetX = -this.encodeOffsetX;
|
|
+ final int decodeOffsetY = -this.encodeOffsetY;
|
|
+ final int decodeOffsetZ = -this.encodeOffsetZ;
|
|
+ final int encodeOffset = this.coordinateOffset;
|
|
+ final int sectionOffset = this.chunkSectionIndexOffset;
|
|
+ final int emittedMask = this.emittedLightMask;
|
|
+
|
|
+ while (queueReadIndex < queueLength) {
|
|
+ final int queueValue = queue[queueReadIndex++];
|
|
+
|
|
+ final int posX = ((queueValue & 63) + decodeOffsetX);
|
|
+ final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ);
|
|
+ final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY);
|
|
+ final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF);
|
|
+ final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F);
|
|
+ final Direction[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection];
|
|
+
|
|
+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) {
|
|
+ // we don't need to worry about our state here.
|
|
+ for (final Direction propagate : checkDirections) {
|
|
+ final int offX = posX + propagate.x;
|
|
+ final int offY = posY + propagate.y;
|
|
+ final int offZ = posZ + propagate.z;
|
|
+
|
|
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
|
|
+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8);
|
|
+
|
|
+ final int lightLevel = this.getLightLevel(sectionIndex, localIndex);
|
|
+
|
|
+ if (lightLevel == 0) {
|
|
+ // already at lowest, nothing we can do
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex);
|
|
+ final int opacityCached = blockData.getOpacityIfCached();
|
|
+ // no null check, blockData cannot be null if level != 0
|
|
+ if (opacityCached != -1) {
|
|
+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached));
|
|
+ if (lightLevel > targetLevel) {
|
|
+ // it looks like another source propagated here, so re-propagate it
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (lightLevel << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | FLAG_RECHECK_LEVEL;
|
|
+ continue;
|
|
+ }
|
|
+ final int emittedLight = blockData.getEmittedLight() & emittedMask;
|
|
+ if (emittedLight != 0) {
|
|
+ // re-propagate source
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (emittedLight << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, emittedLight);
|
|
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4));
|
|
+ continue;
|
|
+ }
|
|
+ continue;
|
|
+ } else {
|
|
+ this.mutablePos1.setValues(offX, offY, offZ);
|
|
+ int flags = 0;
|
|
+ if (blockData.isConditionallyFullOpaque()) {
|
|
+ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms);
|
|
+
|
|
+ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) {
|
|
+ continue;
|
|
+ }
|
|
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
|
|
+ }
|
|
+
|
|
+ final int opacity = blockData.getOpacity(world, this.mutablePos1);
|
|
+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity));
|
|
+ if (lightLevel > targetLevel) {
|
|
+ // it looks like another source propagated here, so re-propagate it
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (lightLevel << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | (FLAG_RECHECK_LEVEL | flags);
|
|
+ continue;
|
|
+ }
|
|
+ final int emittedLight = blockData.getEmittedLight() & emittedMask;
|
|
+ if (emittedLight != 0) {
|
|
+ // re-propagate source
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (emittedLight << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | flags;
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, emittedLight);
|
|
+ if (targetLevel > 0) {
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4))
|
|
+ | flags;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ // we actually need to worry about our state here
|
|
+ final IBlockData fromBlock = this.getBlockData(posX, posY, posZ);
|
|
+ this.mutablePos2.setValues(posX, posY, posZ);
|
|
+ for (final Direction propagate : checkDirections) {
|
|
+ final int offX = posX + propagate.x;
|
|
+ final int offY = posY + propagate.y;
|
|
+ final int offZ = posZ + propagate.z;
|
|
+
|
|
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
|
|
+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8);
|
|
+
|
|
+ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape();
|
|
+
|
|
+ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int lightLevel = this.getLightLevel(sectionIndex, localIndex);
|
|
+
|
|
+ if (lightLevel == 0) {
|
|
+ // already at lowest, nothing we can do
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final IBlockData blockData = this.getBlockData(sectionIndex, localIndex);
|
|
+ final int opacityCached = blockData.getOpacityIfCached();
|
|
+ // no null check, blockData cannot be null if level != 0
|
|
+ if (opacityCached != -1) {
|
|
+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached));
|
|
+ if (lightLevel > targetLevel) {
|
|
+ // it looks like another source propagated here, so re-propagate it
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (lightLevel << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | FLAG_RECHECK_LEVEL;
|
|
+ continue;
|
|
+ }
|
|
+ final int emittedLight = blockData.getEmittedLight() & emittedMask;
|
|
+ if (emittedLight != 0) {
|
|
+ // re-propagate source
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (emittedLight << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | (blockData.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, emittedLight);
|
|
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4));
|
|
+ continue;
|
|
+ }
|
|
+ continue;
|
|
+ } else {
|
|
+ this.mutablePos1.setValues(offX, offY, offZ);
|
|
+ int flags = 0;
|
|
+ if (blockData.isConditionallyFullOpaque()) {
|
|
+ final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms);
|
|
+
|
|
+ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) {
|
|
+ continue;
|
|
+ }
|
|
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
|
|
+ }
|
|
+
|
|
+ final int opacity = blockData.getOpacity(world, this.mutablePos1);
|
|
+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity));
|
|
+ if (lightLevel > targetLevel) {
|
|
+ // it looks like another source propagated here, so re-propagate it
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (lightLevel << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | (FLAG_RECHECK_LEVEL | flags);
|
|
+ continue;
|
|
+ }
|
|
+ final int emittedLight = blockData.getEmittedLight() & emittedMask;
|
|
+ if (emittedLight != 0) {
|
|
+ // re-propagate source
|
|
+ increaseQueue[increaseQueueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (emittedLight << (6 + 6 + 9))
|
|
+ | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4))
|
|
+ | flags;
|
|
+ }
|
|
+ this.setLightLevel(sectionIndex, localIndex, emittedLight);
|
|
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
|
|
+ queue[queueLength++] =
|
|
+ (offX + (offZ << 6) + (offY << 12) + encodeOffset)
|
|
+ | (targetLevel << (6 + 6 + 9))
|
|
+ | (propagate.ordinal() << (6 + 6 + 9 + 4))
|
|
+ | flags;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // propagate sources we clobbered
|
|
+ this.increaseQueueInitialLength = increaseQueueLength;
|
|
+ this.performLightIncrease(lightAccess);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..767831d169710388613396fcf8845453ef09f09d
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/light/ThreadedStarLightEngine.java
|
|
@@ -0,0 +1,181 @@
|
|
+package com.tuinity.tuinity.chunk.light;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+import net.minecraft.server.BlockPosition;
|
|
+import net.minecraft.server.ILightAccess;
|
|
+import net.minecraft.server.MCUtil;
|
|
+import net.minecraft.server.WorldServer;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.HashSet;
|
|
+import java.util.Iterator;
|
|
+import java.util.Set;
|
|
+
|
|
+public final class ThreadedStarLightEngine {
|
|
+
|
|
+ protected final WorldServer world;
|
|
+ protected final ILightAccess lightAccess;
|
|
+
|
|
+ protected final ArrayDeque<SkyStarLightEngine> cachedSkyPropagators;
|
|
+ protected final ArrayDeque<BlockStarLightEngine> cachedBlockPropagators;
|
|
+
|
|
+ protected final Long2ObjectOpenHashMap<Set<BlockPosition>> changedBlocks = new Long2ObjectOpenHashMap<>();
|
|
+
|
|
+ public ThreadedStarLightEngine(final ILightAccess lightAccess, final boolean hasSkyLight, final boolean hasBlockLight) {
|
|
+ this.lightAccess = lightAccess;
|
|
+ this.world = (WorldServer)lightAccess.getWorld();
|
|
+ this.cachedSkyPropagators = hasSkyLight ? new ArrayDeque<>() : null;
|
|
+ this.cachedBlockPropagators = hasBlockLight ? new ArrayDeque<>() : null;
|
|
+ }
|
|
+
|
|
+ public WorldServer getWorld() {
|
|
+ return this.world;
|
|
+ }
|
|
+
|
|
+ protected final SkyStarLightEngine getSkyLightEngine() {
|
|
+ if (this.cachedSkyPropagators == null) {
|
|
+ return null;
|
|
+ }
|
|
+ final SkyStarLightEngine ret;
|
|
+ synchronized (this.cachedSkyPropagators) {
|
|
+ ret = this.cachedSkyPropagators.pollFirst();
|
|
+ }
|
|
+
|
|
+ if (ret == null) {
|
|
+ return new SkyStarLightEngine();
|
|
+ }
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) {
|
|
+ if (this.cachedSkyPropagators == null) {
|
|
+ return;
|
|
+ }
|
|
+ synchronized (this.cachedSkyPropagators) {
|
|
+ this.cachedSkyPropagators.addFirst(engine);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final BlockStarLightEngine getBlockLightEngine() {
|
|
+ if (this.cachedBlockPropagators == null) {
|
|
+ return null;
|
|
+ }
|
|
+ final BlockStarLightEngine ret;
|
|
+ synchronized (this.cachedBlockPropagators) {
|
|
+ ret = this.cachedBlockPropagators.pollFirst();
|
|
+ }
|
|
+
|
|
+ if (ret == null) {
|
|
+ return new BlockStarLightEngine();
|
|
+ }
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) {
|
|
+ if (this.cachedBlockPropagators == null) {
|
|
+ return;
|
|
+ }
|
|
+ synchronized (this.cachedBlockPropagators) {
|
|
+ this.cachedBlockPropagators.addFirst(engine);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void blockChange(BlockPosition pos) {
|
|
+ if (pos.getY() < 0 || pos.getY() > 255) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ pos = pos.immutableCopy();
|
|
+ synchronized (this.changedBlocks) {
|
|
+ this.changedBlocks.computeIfAbsent(MCUtil.getCoordinateKey(pos), (final long keyInMap) -> {
|
|
+ return new HashSet<>();
|
|
+ }).add(pos);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void lightChunk(final int chunkX, final int chunkZ) {
|
|
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
|
|
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
|
|
+
|
|
+ try {
|
|
+ if (skyEngine != null) {
|
|
+ skyEngine.light(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ if (blockEngine != null) {
|
|
+ blockEngine.light(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ } finally {
|
|
+ this.releaseSkyLightEngine(skyEngine);
|
|
+ this.releaseBlockLightEngine(blockEngine);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void relightChunk(final int chunkX, final int chunkZ) {
|
|
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
|
|
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
|
|
+
|
|
+ try {
|
|
+ if (skyEngine != null) {
|
|
+ skyEngine.relight(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ if (blockEngine != null) {
|
|
+ blockEngine.relight(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ } finally {
|
|
+ this.releaseSkyLightEngine(skyEngine);
|
|
+ this.releaseBlockLightEngine(blockEngine);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void checkChunkEdges(final int chunkX, final int chunkZ) {
|
|
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
|
|
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
|
|
+
|
|
+ try {
|
|
+ if (skyEngine != null) {
|
|
+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ if (blockEngine != null) {
|
|
+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
|
|
+ }
|
|
+ } finally {
|
|
+ this.releaseSkyLightEngine(skyEngine);
|
|
+ this.releaseBlockLightEngine(blockEngine);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void propagateChanges() {
|
|
+ synchronized (this.changedBlocks) {
|
|
+ if (this.changedBlocks.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
|
|
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
|
|
+
|
|
+ try {
|
|
+ // TODO be smarter about this in the future
|
|
+ final Long2ObjectOpenHashMap<Set<BlockPosition>> changedBlocks;
|
|
+ synchronized (this.changedBlocks) {
|
|
+ changedBlocks = this.changedBlocks.clone();
|
|
+ this.changedBlocks.clear();
|
|
+ }
|
|
+
|
|
+ for (final Iterator<Long2ObjectMap.Entry<Set<BlockPosition>>> iterator = changedBlocks.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
+ final Long2ObjectMap.Entry<Set<BlockPosition>> entry = iterator.next();
|
|
+ final long coordinate = entry.getLongKey();
|
|
+ final Set<BlockPosition> positions = entry.getValue();
|
|
+
|
|
+ if (skyEngine != null) {
|
|
+ skyEngine.blocksChangedInChunk(this.lightAccess, MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate), positions);
|
|
+ }
|
|
+ if (blockEngine != null) {
|
|
+ blockEngine.blocksChangedInChunk(this.lightAccess, MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate), positions);
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ this.releaseSkyLightEngine(skyEngine);
|
|
+ this.releaseBlockLightEngine(blockEngine);
|
|
+ }
|
|
+ }
|
|
+}
|
|
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 0000000000000000000000000000000000000000..42ce3b80217b574a1852e12f500b366a912e23e2
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
@@ -0,0 +1,381 @@
|
|
+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 boolean useNewLightEngine;
|
|
+
|
|
+ private static void useNewLightEngine() {
|
|
+ useNewLightEngine = TuinityConfig.getBoolean("use-new-light-engine", true);
|
|
+ }
|
|
+
|
|
+ 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 0000000000000000000000000000000000000000..21e50c75e0bffaa5cc5faf6aa81ae7428caca731
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java
|
|
@@ -0,0 +1,74 @@
|
|
+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();
|
|
+ TEMP_GET_CHUNKS_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 0000000000000000000000000000000000000000..d2c7d2c7920324d7207225ed19484e804368489d
|
|
--- /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 0000000000000000000000000000000000000000..08ed243259f052165c6f75aed1d1d65a14219715
|
|
--- /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 0000000000000000000000000000000000000000..be408aebbccbda46e8aa82ef337574137cfa0096
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
@@ -0,0 +1,335 @@
|
|
+package com.tuinity.tuinity.util.maplist;
|
|
+
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
|
+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;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ public void check() {
|
|
+ int iterated = 0;
|
|
+ ReferenceOpenHashSet<E> check = new ReferenceOpenHashSet<>();
|
|
+ if (this.listElements != null) {
|
|
+ for (int i = 0; i < this.listSize; ++i) {
|
|
+ Object obj = this.listElements[i];
|
|
+ if (obj != null) {
|
|
+ iterated++;
|
|
+ if (!check.add((E)obj)) {
|
|
+ throw new IllegalStateException("contains duplicate");
|
|
+ }
|
|
+ if (!this.contains((E)obj)) {
|
|
+ throw new IllegalStateException("desync");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (iterated != this.size()) {
|
|
+ throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size());
|
|
+ }
|
|
+
|
|
+ check.clear();
|
|
+ iterated = 0;
|
|
+ for (final java.util.Iterator<E> iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final E element = iterator.next();
|
|
+ iterated++;
|
|
+ if (!check.add(element)) {
|
|
+ throw new IllegalStateException("contains duplicate (iterator is wrong)");
|
|
+ }
|
|
+ if (!this.contains(element)) {
|
|
+ throw new IllegalStateException("desync (iterator is wrong)");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (iterated != this.size()) {
|
|
+ throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size());
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ 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();
|
|
+ }
|
|
+ //this.check();
|
|
+ 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;
|
|
+
|
|
+ //this.check();
|
|
+ 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;
|
|
+ //this.check();
|
|
+ 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;
|
|
+ //this.check();
|
|
+ }
|
|
+
|
|
+ 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/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..155d10994f2d7df9ac927d955d99016fe304360f
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java
|
|
@@ -0,0 +1,295 @@
|
|
+package com.tuinity.tuinity.util.misc;
|
|
+
|
|
+import it.unimi.dsi.fastutil.HashCommon;
|
|
+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
|
+import net.minecraft.server.MCUtil;
|
|
+
|
|
+public final class Delayed26WayDistancePropagator3D {
|
|
+
|
|
+ // this map is considered "stale" unless updates are propagated.
|
|
+ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f);
|
|
+
|
|
+ // this map is never stale
|
|
+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
|
|
+
|
|
+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
|
|
+ // propagating updates
|
|
+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public static interface LevelChangeCallback {
|
|
+
|
|
+ /**
|
|
+ * This can be called for intermediate updates. So do not rely on newLevel being close to or
|
|
+ * the exact level that is expected after a full propagation has occured.
|
|
+ */
|
|
+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
|
|
+
|
|
+ }
|
|
+
|
|
+ protected final LevelChangeCallback changeCallback;
|
|
+
|
|
+ public Delayed26WayDistancePropagator3D() {
|
|
+ this(null);
|
|
+ }
|
|
+
|
|
+ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) {
|
|
+ this.changeCallback = changeCallback;
|
|
+ }
|
|
+
|
|
+ public int getLevel(final long pos) {
|
|
+ return this.levels.get(pos);
|
|
+ }
|
|
+
|
|
+ public int getLevel(final int x, final int y, final int z) {
|
|
+ return this.levels.get(MCUtil.getSectionKey(x, y, z));
|
|
+ }
|
|
+
|
|
+ public void setSource(final int x, final int y, final int z, final int level) {
|
|
+ this.setSource(MCUtil.getSectionKey(x, y, z), level);
|
|
+ }
|
|
+
|
|
+ public void setSource(final long coordinate, final int level) {
|
|
+ if ((level & 63) != level || level == 0) {
|
|
+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
|
|
+ }
|
|
+
|
|
+ final byte byteLevel = (byte)level;
|
|
+ final byte oldLevel = this.sources.put(coordinate, byteLevel);
|
|
+
|
|
+ if (oldLevel == byteLevel) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ // queue to update later
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+
|
|
+ public void removeSource(final int x, final int y, final int z) {
|
|
+ this.removeSource(MCUtil.getSectionKey(x, y, z));
|
|
+ }
|
|
+
|
|
+ public void removeSource(final long coordinate) {
|
|
+ if (this.sources.remove(coordinate) != 0) {
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // queues used for BFS propagating levels
|
|
+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
|
|
+ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
|
|
+ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected long levelIncreaseWorkQueueBitset;
|
|
+ protected long levelRemoveWorkQueueBitset;
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << index);
|
|
+ }
|
|
+
|
|
+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelRemoveWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ public void propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
|
|
+ final long coordinate = iterator.nextLong();
|
|
+
|
|
+ final byte currentLevel = this.levels.get(coordinate);
|
|
+ final byte updatedSource = this.sources.get(coordinate);
|
|
+
|
|
+ if (currentLevel == updatedSource) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (updatedSource > currentLevel) {
|
|
+ // level increase
|
|
+ this.addToIncreaseWorkQueue(coordinate, updatedSource);
|
|
+ } else {
|
|
+ // level decrease
|
|
+ this.addToRemoveWorkQueue(coordinate, currentLevel);
|
|
+ // if the current coordinate is a source, then the decrease propagation will detect that and queue
|
|
+ // the source propagation
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.updatedSources.clear();
|
|
+
|
|
+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
|
|
+ // make the removes remove less)
|
|
+ this.propagateIncreases();
|
|
+
|
|
+ // now we propagate the decreases (which will then re-propagate clobbered sources)
|
|
+ this.propagateDecreases();
|
|
+ }
|
|
+
|
|
+ protected void propagateIncreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
|
|
+ this.levelIncreaseWorkQueueBitset != 0L;
|
|
+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
|
|
+
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final boolean neighbourCheck = level < 0;
|
|
+
|
|
+ final byte currentLevel;
|
|
+ if (neighbourCheck) {
|
|
+ level = (byte)-level;
|
|
+ currentLevel = this.levels.get(coordinate);
|
|
+ } else {
|
|
+ currentLevel = this.levels.putIfGreater(coordinate, level);
|
|
+ }
|
|
+
|
|
+ if (neighbourCheck) {
|
|
+ // used when propagating from decrease to indicate that this level needs to check its neighbours
|
|
+ // this means the level at coordinate could be equal, but would still need neighbours checked
|
|
+
|
|
+ if (currentLevel != level) {
|
|
+ // something caused the level to change, which means something propagated to it (which means
|
|
+ // us propagating here is redundant), or something removed the level (which means we
|
|
+ // cannot propagate further)
|
|
+ continue;
|
|
+ }
|
|
+ } else if (currentLevel >= level) {
|
|
+ // something higher/equal propagated
|
|
+ continue;
|
|
+ }
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
|
|
+ }
|
|
+
|
|
+ if (level == 1) {
|
|
+ // can't propagate 0 to neighbours
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = MCUtil.getSectionX(coordinate);
|
|
+ final int y = MCUtil.getSectionY(coordinate);
|
|
+ final int z = MCUtil.getSectionZ(coordinate);
|
|
+
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if ((dy | dz | dx) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z);
|
|
+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void propagateDecreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
|
|
+ this.levelRemoveWorkQueueBitset != 0L;
|
|
+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
|
|
+
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ final byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
|
|
+ if (currentLevel == 0) {
|
|
+ // something else removed
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (currentLevel > level) {
|
|
+ // something higher propagated here or we hit the propagation of another source
|
|
+ // in the second case we need to re-propagate because we could have just clobbered another source's
|
|
+ // propagation
|
|
+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
|
|
+ }
|
|
+
|
|
+ final byte source = this.sources.get(coordinate);
|
|
+ if (source != 0) {
|
|
+ // must re-propagate source later
|
|
+ this.addToIncreaseWorkQueue(coordinate, source);
|
|
+ }
|
|
+
|
|
+ if (level == 0) {
|
|
+ // can't propagate -1 to neighbours
|
|
+ // we have to check neighbours for removing 1 just in case the neighbour is 2
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = MCUtil.getSectionX(coordinate);
|
|
+ final int y = MCUtil.getSectionY(coordinate);
|
|
+ final int z = MCUtil.getSectionZ(coordinate);
|
|
+
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if ((dy | dz | dx) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z);
|
|
+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // propagate sources we clobbered in the process
|
|
+ this.propagateIncreases();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..606417a8aeaca2682595f417bba8e9d411999da9
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java
|
|
@@ -0,0 +1,713 @@
|
|
+package com.tuinity.tuinity.util.misc;
|
|
+
|
|
+import it.unimi.dsi.fastutil.HashCommon;
|
|
+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
|
+import net.minecraft.server.MCUtil;
|
|
+
|
|
+public final class Delayed8WayDistancePropagator2D {
|
|
+
|
|
+ // Test
|
|
+ /*
|
|
+ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference, Delayed8WayDistancePropagator2D test) {
|
|
+ int got = test.getLevel(x, z);
|
|
+
|
|
+ int expect = 0;
|
|
+ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet();
|
|
+ if (nearest != null) {
|
|
+ for (Object _obj : nearest) {
|
|
+ if (_obj instanceof Ticket) {
|
|
+ Ticket ticket = (Ticket)_obj;
|
|
+ long ticketCoord = reference.getLastCoordinate(ticket);
|
|
+ int viewDistance = reference.getLastViewDistance(ticket);
|
|
+ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x),
|
|
+ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z));
|
|
+ int level = viewDistance - distance;
|
|
+ if (level > expect) {
|
|
+ expect = level;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (expect != got) {
|
|
+ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static class Ticket {
|
|
+
|
|
+ int x;
|
|
+ int z;
|
|
+
|
|
+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> empty
|
|
+ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this);
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void main(final String[] args) {
|
|
+ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket>() {
|
|
+ @Override
|
|
+ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> getEmptySetFor(Ticket object) {
|
|
+ return object.empty;
|
|
+ }
|
|
+ };
|
|
+ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D();
|
|
+
|
|
+ final int maxDistance = 64;
|
|
+ // test origin
|
|
+ {
|
|
+ Ticket originTicket = new Ticket();
|
|
+ int originDistance = 31;
|
|
+ // test single source
|
|
+ reference.add(originTicket, 0, 0, originDistance);
|
|
+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test single source decrease
|
|
+ reference.update(originTicket, 0, 0, originDistance/2);
|
|
+ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test source increase
|
|
+ originDistance = 2*originDistance;
|
|
+ reference.update(originTicket, 0, 0, originDistance);
|
|
+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
|
|
+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ reference.remove(originTicket);
|
|
+ test.removeSource(0, 0); test.propagateUpdates();
|
|
+ }
|
|
+
|
|
+ // test multiple sources at origin
|
|
+ {
|
|
+ int originDistance = 31;
|
|
+ java.util.List<Ticket> list = new java.util.ArrayList<>();
|
|
+ for (int i = 0; i < 10; ++i) {
|
|
+ Ticket a = new Ticket();
|
|
+ list.add(a);
|
|
+ a.x = (i & 1) == 1 ? -i : i;
|
|
+ a.z = (i & 1) == 1 ? -i : i;
|
|
+ }
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.add(ticket, ticket.x, ticket.z, originDistance);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level decrease
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance/2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance/2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level increase
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance*2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance*2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket remove
|
|
+ for (int i = 0, len = list.size(); i < len; ++i) {
|
|
+ if ((i & 3) != 0) {
|
|
+ continue;
|
|
+ }
|
|
+ Ticket ticket = list.get(i);
|
|
+ reference.remove(ticket);
|
|
+ test.removeSource(ticket.x, ticket.z);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // now test at coordinate offsets
|
|
+ // test offset
|
|
+ {
|
|
+ Ticket originTicket = new Ticket();
|
|
+ int originDistance = 31;
|
|
+ int offX = 54432;
|
|
+ int offZ = -134567;
|
|
+ // test single source
|
|
+ reference.add(originTicket, offX, offZ, originDistance);
|
|
+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test single source decrease
|
|
+ reference.update(originTicket, offX, offZ, originDistance/2);
|
|
+ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test source increase
|
|
+ originDistance = 2*originDistance;
|
|
+ reference.update(originTicket, offX, offZ, originDistance);
|
|
+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
|
|
+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ reference.remove(originTicket);
|
|
+ test.removeSource(offX, offZ); test.propagateUpdates();
|
|
+ }
|
|
+
|
|
+ // test multiple sources at origin
|
|
+ {
|
|
+ int originDistance = 31;
|
|
+ int offX = 54432;
|
|
+ int offZ = -134567;
|
|
+ java.util.List<Ticket> list = new java.util.ArrayList<>();
|
|
+ for (int i = 0; i < 10; ++i) {
|
|
+ Ticket a = new Ticket();
|
|
+ list.add(a);
|
|
+ a.x = offX + ((i & 1) == 1 ? -i : i);
|
|
+ a.z = offZ + ((i & 1) == 1 ? -i : i);
|
|
+ }
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.add(ticket, ticket.x, ticket.z, originDistance);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level decrease
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance/2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance/2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level increase
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance*2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance*2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket remove
|
|
+ for (int i = 0, len = list.size(); i < len; ++i) {
|
|
+ if ((i & 3) != 0) {
|
|
+ continue;
|
|
+ }
|
|
+ Ticket ticket = list.get(i);
|
|
+ reference.remove(ticket);
|
|
+ test.removeSource(ticket.x, ticket.z);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // this map is considered "stale" unless updates are propagated.
|
|
+ protected final LevelMap levels = new LevelMap(8192*2, 0.6f);
|
|
+
|
|
+ // this map is never stale
|
|
+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
|
|
+
|
|
+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
|
|
+ // propagating updates
|
|
+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public static interface LevelChangeCallback {
|
|
+
|
|
+ /**
|
|
+ * This can be called for intermediate updates. So do not rely on newLevel being close to or
|
|
+ * the exact level that is expected after a full propagation has occured.
|
|
+ */
|
|
+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
|
|
+
|
|
+ }
|
|
+
|
|
+ protected final LevelChangeCallback changeCallback;
|
|
+
|
|
+ public Delayed8WayDistancePropagator2D() {
|
|
+ this(null);
|
|
+ }
|
|
+
|
|
+ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) {
|
|
+ this.changeCallback = changeCallback;
|
|
+ }
|
|
+
|
|
+ public int getLevel(final long pos) {
|
|
+ return this.levels.get(pos);
|
|
+ }
|
|
+
|
|
+ public int getLevel(final int x, final int z) {
|
|
+ return this.levels.get(MCUtil.getCoordinateKey(x, z));
|
|
+ }
|
|
+
|
|
+ public void setSource(final int x, final int z, final int level) {
|
|
+ this.setSource(MCUtil.getCoordinateKey(x, z), level);
|
|
+ }
|
|
+
|
|
+ public void setSource(final long coordinate, final int level) {
|
|
+ if ((level & 63) != level || level == 0) {
|
|
+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
|
|
+ }
|
|
+
|
|
+ final byte byteLevel = (byte)level;
|
|
+ final byte oldLevel = this.sources.put(coordinate, byteLevel);
|
|
+
|
|
+ if (oldLevel == byteLevel) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ // queue to update later
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+
|
|
+ public void removeSource(final int x, final int z) {
|
|
+ this.removeSource(MCUtil.getCoordinateKey(x, z));
|
|
+ }
|
|
+
|
|
+ public void removeSource(final long coordinate) {
|
|
+ if (this.sources.remove(coordinate) != 0) {
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // queues used for BFS propagating levels
|
|
+ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
|
|
+ this.levelIncreaseWorkQueues[i] = new WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
|
|
+ this.levelRemoveWorkQueues[i] = new WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected long levelIncreaseWorkQueueBitset;
|
|
+ protected long levelRemoveWorkQueueBitset;
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[index];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << index);
|
|
+ }
|
|
+
|
|
+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
|
|
+ final WorkQueue queue = this.levelRemoveWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelRemoveWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ public void propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
|
|
+ final long coordinate = iterator.nextLong();
|
|
+
|
|
+ final byte currentLevel = this.levels.get(coordinate);
|
|
+ final byte updatedSource = this.sources.get(coordinate);
|
|
+
|
|
+ if (currentLevel == updatedSource) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (updatedSource > currentLevel) {
|
|
+ // level increase
|
|
+ this.addToIncreaseWorkQueue(coordinate, updatedSource);
|
|
+ } else {
|
|
+ // level decrease
|
|
+ this.addToRemoveWorkQueue(coordinate, currentLevel);
|
|
+ // if the current coordinate is a source, then the decrease propagation will detect that and queue
|
|
+ // the source propagation
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.updatedSources.clear();
|
|
+
|
|
+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
|
|
+ // make the removes remove less)
|
|
+ this.propagateIncreases();
|
|
+
|
|
+ // now we propagate the decreases (which will then re-propagate clobbered sources)
|
|
+ this.propagateDecreases();
|
|
+ }
|
|
+
|
|
+ protected void propagateIncreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
|
|
+ this.levelIncreaseWorkQueueBitset != 0L;
|
|
+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
|
|
+
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final boolean neighbourCheck = level < 0;
|
|
+
|
|
+ final byte currentLevel;
|
|
+ if (neighbourCheck) {
|
|
+ level = (byte)-level;
|
|
+ currentLevel = this.levels.get(coordinate);
|
|
+ } else {
|
|
+ currentLevel = this.levels.putIfGreater(coordinate, level);
|
|
+ }
|
|
+
|
|
+ if (neighbourCheck) {
|
|
+ // used when propagating from decrease to indicate that this level needs to check its neighbours
|
|
+ // this means the level at coordinate could be equal, but would still need neighbours checked
|
|
+
|
|
+ if (currentLevel != level) {
|
|
+ // something caused the level to change, which means something propagated to it (which means
|
|
+ // us propagating here is redundant), or something removed the level (which means we
|
|
+ // cannot propagate further)
|
|
+ continue;
|
|
+ }
|
|
+ } else if (currentLevel >= level) {
|
|
+ // something higher/equal propagated
|
|
+ continue;
|
|
+ }
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
|
|
+ }
|
|
+
|
|
+ if (level == 1) {
|
|
+ // can't propagate 0 to neighbours
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = (int)coordinate;
|
|
+ final int z = (int)(coordinate >>> 32);
|
|
+
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz);
|
|
+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void propagateDecreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
|
|
+ this.levelRemoveWorkQueueBitset != 0L;
|
|
+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
|
|
+
|
|
+ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ final byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
|
|
+ if (currentLevel == 0) {
|
|
+ // something else removed
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (currentLevel > level) {
|
|
+ // something higher propagated here or we hit the propagation of another source
|
|
+ // in the second case we need to re-propagate because we could have just clobbered another source's
|
|
+ // propagation
|
|
+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
|
|
+ }
|
|
+
|
|
+ final byte source = this.sources.get(coordinate);
|
|
+ if (source != 0) {
|
|
+ // must re-propagate source later
|
|
+ this.addToIncreaseWorkQueue(coordinate, source);
|
|
+ }
|
|
+
|
|
+ if (level == 0) {
|
|
+ // can't propagate -1 to neighbours
|
|
+ // we have to check neighbours for removing 1 just in case the neighbour is 2
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = (int)coordinate;
|
|
+ final int z = (int)(coordinate >>> 32);
|
|
+
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz);
|
|
+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // propagate sources we clobbered in the process
|
|
+ this.propagateIncreases();
|
|
+ }
|
|
+
|
|
+ protected static final class LevelMap extends Long2ByteOpenHashMap {
|
|
+ public LevelMap() {
|
|
+ super();
|
|
+ }
|
|
+
|
|
+ public LevelMap(final int expected, final float loadFactor) {
|
|
+ super(expected, loadFactor);
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private int find(final long k) {
|
|
+ if (k == 0L) {
|
|
+ return this.containsNullKey ? this.n : -(this.n + 1);
|
|
+ } else {
|
|
+ final long[] key = this.key;
|
|
+ long curr;
|
|
+ int pos;
|
|
+ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) {
|
|
+ return -(pos + 1);
|
|
+ } else if (k == curr) {
|
|
+ return pos;
|
|
+ } else {
|
|
+ while((curr = key[pos = pos + 1 & this.mask]) != 0L) {
|
|
+ if (k == curr) {
|
|
+ return pos;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -(pos + 1);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void insert(final int pos, final long k, final byte v) {
|
|
+ if (pos == this.n) {
|
|
+ this.containsNullKey = true;
|
|
+ }
|
|
+
|
|
+ this.key[pos] = k;
|
|
+ this.value[pos] = v;
|
|
+ if (this.size++ >= this.maxFill) {
|
|
+ this.rehash(HashCommon.arraySize(this.size + 1, this.f));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ public byte putIfGreater(final long key, final byte value) {
|
|
+ final int pos = this.find(key);
|
|
+ if (pos < 0) {
|
|
+ if (this.defRetValue < value) {
|
|
+ this.insert(-pos - 1, key, value);
|
|
+ }
|
|
+ return this.defRetValue;
|
|
+ } else {
|
|
+ final byte curr = this.value[pos];
|
|
+ if (value > curr) {
|
|
+ this.value[pos] = value;
|
|
+ return curr;
|
|
+ }
|
|
+ return curr;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void removeEntry(final int pos) {
|
|
+ --this.size;
|
|
+ this.shiftKeys(pos);
|
|
+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
|
|
+ this.rehash(this.n / 2);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void removeNullEntry() {
|
|
+ this.containsNullKey = false;
|
|
+ --this.size;
|
|
+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
|
|
+ this.rehash(this.n / 2);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ public byte removeIfGreaterOrEqual(final long key, final byte value) {
|
|
+ if (key == 0L) {
|
|
+ if (!this.containsNullKey) {
|
|
+ return this.defRetValue;
|
|
+ }
|
|
+ final byte current = this.value[this.n];
|
|
+ if (value >= current) {
|
|
+ this.removeNullEntry();
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ } else {
|
|
+ long[] keys = this.key;
|
|
+ byte[] values = this.value;
|
|
+ long curr;
|
|
+ int pos;
|
|
+ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) {
|
|
+ return this.defRetValue;
|
|
+ } else if (key == curr) {
|
|
+ final byte current = values[pos];
|
|
+ if (value >= current) {
|
|
+ this.removeEntry(pos);
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ } else {
|
|
+ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) {
|
|
+ if (key == curr) {
|
|
+ final byte current = values[pos];
|
|
+ if (value >= current) {
|
|
+ this.removeEntry(pos);
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return this.defRetValue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static final class WorkQueue {
|
|
+
|
|
+ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque();
|
|
+ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque();
|
|
+
|
|
+ }
|
|
+
|
|
+ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue {
|
|
+
|
|
+ /**
|
|
+ * Assumes non-empty. If empty, undefined behaviour.
|
|
+ */
|
|
+ public long removeFirstLong() {
|
|
+ // copied from superclass
|
|
+ long t = this.array[this.start];
|
|
+ if (++this.start == this.length) {
|
|
+ this.start = 0;
|
|
+ }
|
|
+
|
|
+ return t;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue {
|
|
+
|
|
+ /**
|
|
+ * Assumes non-empty. If empty, undefined behaviour.
|
|
+ */
|
|
+ public byte removeFirstByte() {
|
|
+ // copied from superclass
|
|
+ byte t = this.array[this.start];
|
|
+ if (++this.start == this.length) {
|
|
+ this.start = 0;
|
|
+ }
|
|
+
|
|
+ return t;
|
|
+ }
|
|
+ }
|
|
+}
|
|
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 0000000000000000000000000000000000000000..002abb3cbf0f742e685f2f043d2600de03e37a19
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java
|
|
@@ -0,0 +1,165 @@
|
|
+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
|
|
+ if (this.aabb.isEmpty() || axisalignedbb.isEmpty()) {
|
|
+ return d0;
|
|
+ }
|
|
+ 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 ed9b2f9adfecdc6d1b9925579ec510657adde11f..9fbb77e70d1abaf393fd6e4fd5cb124fcaedaef8 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.minX - other.maxX) < -MCUtil.COLLISION_EPSILON && (this.maxX - other.minX) > MCUtil.COLLISION_EPSILON &&
|
|
+ (this.minY - other.maxY) < -MCUtil.COLLISION_EPSILON && (this.maxY - other.minY) > MCUtil.COLLISION_EPSILON &&
|
|
+ (this.minZ - other.maxZ) < -MCUtil.COLLISION_EPSILON && (this.maxZ - other.minZ) > MCUtil.COLLISION_EPSILON;
|
|
+ }
|
|
+
|
|
+ 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 boolean voxelShapeIntersect(double minX1, double minY1, double minZ1, double maxX1, double maxY1, double maxZ1,
|
|
+ double minX2, double minY2, double minZ2, double maxX2, double maxY2, double maxZ2) {
|
|
+ return (minX1 - maxX2) < -MCUtil.COLLISION_EPSILON && (maxX1 - minX2) > MCUtil.COLLISION_EPSILON &&
|
|
+ (minY1 - maxY2) < -MCUtil.COLLISION_EPSILON && (maxY1 - minY2) > MCUtil.COLLISION_EPSILON &&
|
|
+ (minZ1 - maxZ2) < -MCUtil.COLLISION_EPSILON && (maxZ1 - minZ2) > MCUtil.COLLISION_EPSILON;
|
|
+ }
|
|
+
|
|
+ public static double collideX(AxisAlignedBB target, AxisAlignedBB source, double 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 (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 (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 6b655b744d31d9660c7521ab596b27bcd92f4d58..e811295b4d6afcd920f60e0ce5440e43300d9085 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 65af976527133ee5c2f52e411e19c4f7f06df3ef..0b9d469a92decfb0632805791868ef7faa88c535 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 63a761ebef80d4af09cdc2682e496d78492c4a3a..8d445e9c0875db6cf45e4d8bcfce7cd3d5094d94 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 46e910581210421c8699637431804dc2f43eb4a6..fb967bc03f58fab8cec2732b1890108f2fc66af8 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 a33303c31881b6391723e16a06d7841d48679958..ce57e6a4acac97d6da82202094306e7e91f1c87e 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 1f334d63282bd5c23dc3b275a220f09e60c34537..4d1ac4e6b61897fc03b091475ef7be3ed0b228a9 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);
|
|
@@ -328,6 +330,7 @@ public abstract class BlockBase {
|
|
this.n = blockbase_info.s;
|
|
this.o = blockbase_info.t;
|
|
this.p = blockbase_info.u;
|
|
+ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Tuinity
|
|
}
|
|
// Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time
|
|
private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData;
|
|
@@ -343,12 +346,100 @@ 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
|
|
+
|
|
+ // Tuinity start
|
|
+ protected int opacityIfCached = -1;
|
|
+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15]
|
|
+ public final int getOpacityIfCached() {
|
|
+ return this.opacityIfCached;
|
|
+ }
|
|
+
|
|
+ protected final boolean conditionallyFullOpaque;
|
|
+ public final boolean isConditionallyFullOpaque() {
|
|
+ return this.conditionallyFullOpaque;
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
+ // Tuinity start
|
|
+ public static boolean isSpecialCollidingBlock(BlockBase.BlockData blockData) {
|
|
+ return blockData.shapeExceedsCube() || blockData.getBlock() == Blocks.MOVING_PISTON;
|
|
+ }
|
|
+
|
|
+ protected long blockCollisionBehavior = ChunkSection.KNOWN_SPECIAL_BLOCK;
|
|
+ public final long getBlockCollisionBehavior() {
|
|
+ return this.blockCollisionBehavior;
|
|
+ }
|
|
+ // 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
|
|
+ this.opacityIfCached = this.a == null || this.isConditionallyFullOpaque() ? -1 : this.a.getOpacity(); // Tuinity - cache opacity for light
|
|
+ // Tuinity start - optimise culling shape cache for light
|
|
+ if (this.a != null && this.a.getCullingShapeCache() != null) {
|
|
+ for (int i = 0, len = this.a.getCullingShapeCache().length; i < len; ++i) {
|
|
+ VoxelShape face = this.a.getCullingShapeCache()[i].simplify();
|
|
+ if (face.isEmpty()) {
|
|
+ this.a.getCullingShapeCache()[i] = VoxelShapes.getEmptyShape();
|
|
+ continue;
|
|
+ }
|
|
+ List<AxisAlignedBB> boxes = face.getBoundingBoxesRepresentation();
|
|
+ if (boxes.size() == 1) {
|
|
+ AxisAlignedBB boundingBox = boxes.get(0);
|
|
+ if (boundingBox.equals(VoxelShapes.optimisedFullCube.aabb)) {
|
|
+ this.a.getCullingShapeCache()[i] = VoxelShapes.fullCube();
|
|
+ } else {
|
|
+ this.a.getCullingShapeCache()[i] = VoxelShapes.of(boundingBox);
|
|
+ if (!(this.a.getCullingShapeCache()[i] instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) &&
|
|
+ this.a.getCullingShapeCache()[i].getBoundingBoxesRepresentation().size() == 1) {
|
|
+ this.a.getCullingShapeCache()[i] = new com.tuinity.tuinity.voxel.AABBVoxelShape(boundingBox);
|
|
+ }
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ this.a.getCullingShapeCache()[i] = face;
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise culling shape cache for light
|
|
+ // Tuinity start - init block collision behavior field
|
|
+ if (isSpecialCollidingBlock(this)) {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_SPECIAL_BLOCK;
|
|
+ } else {
|
|
+ try {
|
|
+ VoxelShape constantShape = this.getCollisionShape(null, null, null);
|
|
+ if (constantShape == null) {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK;
|
|
+ } else {
|
|
+ if (constantShape.isEmpty()) {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_EMPTY_BLOCK;
|
|
+ } else {
|
|
+ List<AxisAlignedBB> boxes = constantShape.simplify().getBoundingBoxesRepresentation();
|
|
+ if (boxes.size() == 1 && boxes.get(0).equals(VoxelShapes.optimisedFullCube.aabb)) {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_FULL_BLOCK;
|
|
+ } else {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } catch (ThreadDeath thr) {
|
|
+ throw thr;
|
|
+ } catch (Throwable thr) {
|
|
+ this.blockCollisionBehavior = ChunkSection.KNOWN_UNKNOWN_BLOCK;
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - init block collision behavior field
|
|
|
|
}
|
|
|
|
@@ -372,10 +463,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 +478,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
|
|
@@ -675,9 +768,9 @@ public abstract class BlockBase {
|
|
private static final int f = EnumBlockSupport.values().length;
|
|
protected final boolean a;
|
|
private final boolean g;
|
|
- private final int h;
|
|
+ private final int h; private final int getOpacity() { return this.h; } // Tuinity - OBFHELPER
|
|
@Nullable
|
|
- private final VoxelShape[] i;
|
|
+ private final VoxelShape[] i; private final VoxelShape[] getCullingShapeCache () { return this.i; } // Tuinity - OBFHELPER
|
|
protected final VoxelShape b;
|
|
protected final boolean c;
|
|
private final boolean[] j;
|
|
diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java
|
|
index 12a0230448dd8d56f6dc20e23cacaf0b8a9433d1..9e5e6de52efabe9126f6c47acb35fa1dc461ff4f 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 2d887af902a33b0e28d8f0a6ac2e59c815a7856e..2291135eaef64c403183724cb6e413cd7e472672 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 9c078d30afef20bd1ea5975299c5513334829b19..e5169c5c7a71566c22c5b3c4686e56ee65816e53 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -91,6 +91,175 @@ 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 - rewrite light engine
|
|
+ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false);
|
|
+ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true);
|
|
+
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() {
|
|
+ return this.blockNibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.blockNibbles = nibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() {
|
|
+ return this.skyNibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.skyNibbles = nibbles;
|
|
+ }
|
|
+ // Tuinity end - rewrite light engine
|
|
+
|
|
+ // 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();
|
|
@@ -297,6 +466,10 @@ public class Chunk implements IChunkAccess {
|
|
|
|
public Chunk(World world, ProtoChunk protochunk) {
|
|
this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null);
|
|
+ // Tuinity start - copy over protochunk light
|
|
+ this.blockNibbles = protochunk.getBlockNibbles();
|
|
+ this.skyNibbles = protochunk.getSkyNibbles();
|
|
+ // Tuinity end - copy over protochunk light
|
|
Iterator iterator = protochunk.y().iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
@@ -330,7 +503,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 +720,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 +766,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 +790,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 +805,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) {
|
|
@@ -943,6 +1118,7 @@ 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 getEntities call"); // Spigot
|
|
int i = MathHelper.floor((axisalignedbb.minY - 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 8eecdcde510661ec3a13a25a04ba394f6b6dc012..187a639347413a3aef1b6025926e2bf5936c11fd 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCache.java
|
|
@@ -1,5 +1,6 @@
|
|
package net.minecraft.server;
|
|
|
|
+import java.util.List;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Stream;
|
|
import javax.annotation.Nullable;
|
|
@@ -12,6 +13,216 @@ 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.getCollisionsForBlocksOrWorldBorder(entity, entity.getBoundingBox(), null, true, null);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(Entity entity, AxisAlignedBB axisalignedbb) {
|
|
+ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
|
|
+ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null);
|
|
+ }
|
|
+
|
|
+ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> list,
|
|
+ boolean collidesWithUnloaded,
|
|
+ java.util.function.BiPredicate<IBlockData, BlockPosition> predicate) {
|
|
+ boolean ret = false;
|
|
+ final boolean checkOnly = true;
|
|
+
|
|
+ if (entity != null) {
|
|
+ if (this.getWorldBorder().isAlmostCollidingOnBorder(axisalignedbb)) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list);
|
|
+ ret = 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 = null;
|
|
+
|
|
+ // special cases:
|
|
+ if (minBlockY > 255 || maxBlockY < 0) {
|
|
+ // no point in checking
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
+ if (collidesWithUnloaded) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ));
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ int minXIterate;
|
|
+ int maxXIterate;
|
|
+ int minZIterate;
|
|
+ int maxZIterate;
|
|
+
|
|
+ boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
|
|
+ if (!sectionHasSpecial && currChunkX == minChunkX) {
|
|
+ minXIterate = minX + 1;
|
|
+ } else {
|
|
+ minXIterate = minX;
|
|
+ }
|
|
+ if (!sectionHasSpecial && currChunkX == maxChunkX) {
|
|
+ maxXIterate = maxX - 1;
|
|
+ } else {
|
|
+ maxXIterate = maxX;
|
|
+ }
|
|
+
|
|
+ if (!sectionHasSpecial && currChunkZ == minChunkZ) {
|
|
+ minZIterate = minZ + 1;
|
|
+ } else {
|
|
+ minZIterate = minZ;
|
|
+ }
|
|
+ if (!sectionHasSpecial && currChunkZ == maxChunkZ) {
|
|
+ maxZIterate = maxZ - 1;
|
|
+ } else {
|
|
+ maxZIterate = maxZ;
|
|
+ }
|
|
+
|
|
+ DataPaletteBlock<IBlockData> blocks = section.blockIds;
|
|
+
|
|
+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
|
|
+ block_search_loop:
|
|
+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
|
|
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
|
|
+ long blockInfo = section.getKnownBlockInfo(localBlockIndex);
|
|
+ switch ((int)blockInfo) {
|
|
+ case (int)ChunkSection.KNOWN_EMPTY_BLOCK: {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ case (int)ChunkSection.KNOWN_FULL_BLOCK: {
|
|
+ AxisAlignedBB box = new AxisAlignedBB(
|
|
+ currX | chunkXGlobalPos, currY, currZ | chunkZGlobalPos,
|
|
+ (currX | chunkXGlobalPos) + 1, currY + 1, (currZ | chunkZGlobalPos) + 1,
|
|
+ false
|
|
+ );
|
|
+ if (predicate != null) {
|
|
+ if (!box.voxelShapeIntersect(axisalignedbb)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ // fall through to get the block for the predicate
|
|
+ } else {
|
|
+ if (box.voxelShapeIntersect(axisalignedbb)) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ list.add(box);
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ }
|
|
+ default: {
|
|
+ int blockX = currX | chunkXGlobalPos;
|
|
+ int blockY = currY;
|
|
+ int blockZ = currZ | chunkZGlobalPos;
|
|
+
|
|
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
|
|
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
|
|
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
|
|
+ if (edgeCount == 3 || (edgeCount != 0 && blockInfo != ChunkSection.KNOWN_SPECIAL_BLOCK)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+
|
|
+ IBlockData blockData = blocks.rawGet(localBlockIndex);
|
|
+
|
|
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
|
|
+ if (collisionShape == null) {
|
|
+ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity);
|
|
+ }
|
|
+ mutablePos.setValues(blockX, blockY, blockZ);
|
|
+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape);
|
|
+ if (voxelshape2 != VoxelShapes.getEmptyShape()) {
|
|
+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ);
|
|
+
|
|
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+
|
|
+ if (checkOnly) {
|
|
+ if (voxelshape3.intersects(axisalignedbb)) {
|
|
+ return true;
|
|
+ }
|
|
+ } else {
|
|
+ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // 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 3c7b225edbe23dc1959002293a6f8b816287b5a8..f1c686810fb4e9c05df45d664c93af73d17f0624 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 6acb5f05a05c542f8257e205ef70987be2d29194..84429f12d0c6e0990b7cbb1b503b7868b3a02b14 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 8e7da2c5f3852920ec5fbcdd2bff4d299e6aa499..64dd95292fb4d058f6200bfcadaedfbd62b2461d 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) {
|
|
@@ -85,13 +93,15 @@ public class ChunkRegionLoader {
|
|
ProtoChunkTickList<FluidType> protochunkticklist1 = new ProtoChunkTickList<>((fluidtype) -> {
|
|
return fluidtype == null || fluidtype == FluidTypes.EMPTY;
|
|
}, chunkcoordintpair, nbttagcompound1.getList("LiquidsToBeTicked", 9));
|
|
- boolean flag = nbttagcompound1.getBoolean("isLightOn");
|
|
+ boolean flag = (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine || nbttagcompound1.getBoolean("starlight.lit")) && nbttagcompound1.getBoolean("isLightOn"); boolean canUseSkyLight = flag && getStatus(nbttagcompound).isAtLeastStatus(ChunkStatus.LIGHT); boolean canUseBlockLight = canUseSkyLight; // Tuinity
|
|
NBTTagList nbttaglist = nbttagcompound1.getList("Sections", 10);
|
|
boolean flag1 = true;
|
|
ChunkSection[] achunksection = new ChunkSection[16];
|
|
boolean flag2 = worldserver.getDimensionManager().hasSkyLight();
|
|
ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider();
|
|
LightEngine lightengine = chunkproviderserver.getLightEngine();
|
|
+ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false); // Tuinity - replace light impl
|
|
+ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true); // Tuinity - replace light impl
|
|
|
|
if (flag) {
|
|
tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main
|
|
@@ -119,6 +129,7 @@ public class ChunkRegionLoader {
|
|
|
|
if (flag) {
|
|
if (nbttagcompound2.hasKeyOfType("BlockLight", 7)) {
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseBlockLight) blockNibbles[b0 + 1] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("BlockLight")); // Tuinity - replace light impl
|
|
// Paper start - delay this task since we're executing off-main
|
|
NibbleArray blockLight = new NibbleArray(nbttagcompound2.getByteArray("BlockLight"));
|
|
tasksToExecuteOnMain.add(() -> {
|
|
@@ -128,6 +139,7 @@ public class ChunkRegionLoader {
|
|
}
|
|
|
|
if (flag2 && nbttagcompound2.hasKeyOfType("SkyLight", 7)) {
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseSkyLight) skyNibbles[b0 + 1] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("SkyLight")); // Tuinity - replace light impl
|
|
// Paper start - delay this task since we're executing off-main
|
|
NibbleArray skyLight = new NibbleArray(nbttagcompound2.getByteArray("SkyLight"));
|
|
tasksToExecuteOnMain.add(() -> {
|
|
@@ -138,6 +150,20 @@ public class ChunkRegionLoader {
|
|
}
|
|
}
|
|
|
|
+ // Tuinity start - correctly set nullable status for light data
|
|
+ boolean nullableSky = true;
|
|
+ for (int y = 16; y >= -1; --y) {
|
|
+ com.tuinity.tuinity.chunk.light.SWMRNibbleArray nibble = skyNibbles[y + 1];
|
|
+ if (nibble.isInitialisedUpdating()) {
|
|
+ nullableSky = false;
|
|
+ continue;
|
|
+ }
|
|
+ if (!nullableSky) {
|
|
+ nibble.markNonNull();
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - correctly set nullable status for light data
|
|
+
|
|
long j = nbttagcompound1.getLong("InhabitedTime");
|
|
ChunkStatus.Type chunkstatus_type = a(nbttagcompound);
|
|
Object object;
|
|
@@ -173,8 +199,12 @@ public class ChunkRegionLoader {
|
|
object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys.
|
|
createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here
|
|
);// Paper end
|
|
+ ((Chunk)object).setBlockNibbles(blockNibbles); // Tuinity - replace light impl
|
|
+ ((Chunk)object).setSkyNibbles(skyNibbles); // Tuinity - replace light impl
|
|
} else {
|
|
ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter
|
|
+ protochunk.setBlockNibbles(blockNibbles); // Tuinity - replace light impl
|
|
+ protochunk.setSkyNibbles(skyNibbles); // Tuinity - replace light impl
|
|
|
|
protochunk.a(biomestorage);
|
|
object = protochunk;
|
|
@@ -354,14 +384,14 @@ public class ChunkRegionLoader {
|
|
NibbleArray[] skyLight = new NibbleArray[17 - (-1)];
|
|
|
|
for (int i = -1; i < 17; ++i) {
|
|
- NibbleArray blockArray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i));
|
|
- NibbleArray skyArray = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i));
|
|
+ NibbleArray blockArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : chunk.getBlockNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded
|
|
+ NibbleArray skyArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : chunk.getSkyNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded
|
|
|
|
// copy data for safety
|
|
- if (blockArray != null) {
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && blockArray != null) { // Tuinity - data already copied
|
|
blockArray = blockArray.copy();
|
|
}
|
|
- if (skyArray != null) {
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && skyArray != null) { // Tuinity - data already copied
|
|
skyArray = skyArray.copy();
|
|
}
|
|
|
|
@@ -401,10 +431,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();
|
|
@@ -429,8 +459,8 @@ public class ChunkRegionLoader {
|
|
NibbleArray nibblearray; // block light
|
|
NibbleArray nibblearray1; // sky light
|
|
if (asyncsavedata == null) {
|
|
- nibblearray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData)
|
|
- nibblearray1 = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData)
|
|
+ nibblearray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : ichunkaccess.getBlockNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded
|
|
+ nibblearray1 = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : ichunkaccess.getSkyNibbles()[i + 1].asNibble()) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded
|
|
} else {
|
|
nibblearray = asyncsavedata.blockLight[i + 1]; // +1 to offset the -1 starting index
|
|
nibblearray1 = asyncsavedata.skyLight[i + 1]; // +1 to offset the -1 starting index
|
|
@@ -444,11 +474,11 @@ public class ChunkRegionLoader {
|
|
}
|
|
|
|
if (nibblearray != null && !nibblearray.c()) {
|
|
- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper
|
|
+ nbttagcompound2.setByteArray("BlockLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned
|
|
}
|
|
|
|
if (nibblearray1 != null && !nibblearray1.c()) {
|
|
- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper
|
|
+ nbttagcompound2.setByteArray("SkyLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned
|
|
}
|
|
|
|
nbttaglist.add(nbttagcompound2);
|
|
@@ -457,7 +487,7 @@ public class ChunkRegionLoader {
|
|
|
|
nbttagcompound1.set("Sections", nbttaglist);
|
|
if (flag) {
|
|
- nbttagcompound1.setBoolean("isLightOn", true);
|
|
+ nbttagcompound1.setBoolean("isLightOn", true); if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) nbttagcompound1.setBoolean("starlight.lit", true); // Tuinity
|
|
}
|
|
|
|
BiomeStorage biomestorage = ichunkaccess.getBiomeIndex();
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index e52df8096e399c84ff8a2637fdd65ea57d9001d0..ea943e44abb0ea9f7d471070fee14f6e5f205d53 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -11,10 +11,45 @@ public class ChunkSection {
|
|
short nonEmptyBlockCount; // Paper - package-private
|
|
short tickingBlockCount; // Paper - private -> package-private
|
|
private short e;
|
|
- final DataPaletteBlock<IBlockData> blockIds; // Paper - package-private
|
|
+ public final DataPaletteBlock<IBlockData> blockIds; // Paper - package-private // Tuinity - public
|
|
|
|
final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
|
|
|
|
+ // Tuinity start
|
|
+ protected int specialCollidingBlocks;
|
|
+
|
|
+ public final boolean hasSpecialCollidingBlocks() {
|
|
+ return this.specialCollidingBlocks != 0;
|
|
+ }
|
|
+
|
|
+ private final long[] knownBlockCollisionData = new long[16 * 16 * 16 * 2 / 64]; // blocks * bits per block / bits per long
|
|
+ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to have voxelshape of empty
|
|
+ public static final long KNOWN_FULL_BLOCK = 0b01; // known to have voxelshape of full cube
|
|
+ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info
|
|
+ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions
|
|
+
|
|
+ private final void updateKnownBlockInfo(int blockIndex, IBlockData blockData) {
|
|
+ int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
|
|
+ int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
|
|
+
|
|
+ long value = this.knownBlockCollisionData[arrayIndex];
|
|
+
|
|
+ value &= ~(0b11L << (valueShift << 1));
|
|
+ value |= (blockData.getBlockCollisionBehavior() << (valueShift << 1));
|
|
+
|
|
+ this.knownBlockCollisionData[arrayIndex] = value;
|
|
+ }
|
|
+
|
|
+ public final long getKnownBlockInfo(int blockIndex) {
|
|
+ int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
|
|
+ int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
|
|
+
|
|
+ long value = this.knownBlockCollisionData[arrayIndex];
|
|
+
|
|
+ return (value >>> (valueShift << 1)) & 0b11L;
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
// Paper start - Anti-Xray - Add parameters
|
|
@Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) {
|
|
@@ -62,6 +97,16 @@ public class ChunkSection {
|
|
iblockdata1 = (IBlockData) this.blockIds.b(i, j, k, iblockdata);
|
|
}
|
|
|
|
+ // Tuinity start
|
|
+ this.updateKnownBlockInfo(i | (k << 4) | (j << 8), iblockdata);
|
|
+ if (iblockdata1.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) {
|
|
+ --this.specialCollidingBlocks;
|
|
+ }
|
|
+ if (iblockdata.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) {
|
|
+ ++this.specialCollidingBlocks;
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
Fluid fluid = iblockdata1.getFluid();
|
|
Fluid fluid1 = iblockdata.getFluid();
|
|
|
|
@@ -96,6 +141,7 @@ public class ChunkSection {
|
|
return iblockdata1;
|
|
}
|
|
|
|
+ public final boolean isFullOfAir() { return this.c(); } // Tuinity - OBFHELPER
|
|
public boolean c() {
|
|
return this.nonEmptyBlockCount == 0;
|
|
}
|
|
@@ -129,6 +175,12 @@ public class ChunkSection {
|
|
this.tickingBlockCount = 0;
|
|
this.e = 0;
|
|
this.blockIds.forEachLocation((iblockdata, location) -> { // Paper
|
|
+ // Tuinity start
|
|
+ if (iblockdata.getBlockCollisionBehavior() == KNOWN_SPECIAL_BLOCK) {
|
|
+ ++this.specialCollidingBlocks;
|
|
+ }
|
|
+ this.updateKnownBlockInfo(location, iblockdata);
|
|
+ // Tuinity end
|
|
Fluid fluid = iblockdata.getFluid();
|
|
|
|
if (!iblockdata.isAir()) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java
|
|
index f6c9bdbf52d773d7aa601125b887b347163f9328..51ea295d66312c95685b9fe4ee502a029d2fff20 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 26b48b5ffa4ce3fbe50810dc1a8070d333a2684f..353b61aa57501fa76ce42dff5ba61bdb6a1e302e 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 95ef96286855624590b72d69514b0fc0e08fddba..73163b417af7e522a4509bf9c1ab56d6499be622 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 5504facd2e453238caa71d98743be5416d4dd4fe..ecff0657e5666ddc2e6a5c3111bfb2b8dd2b78d3 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);
|
|
@@ -357,7 +358,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
|
|
}
|
|
|
|
if (this.q != null) {
|
|
- this.q.b();
|
|
+ //this.q.b(); // Tuinity - do not wait for AWT, causes deadlock with sigint handler (AWT shutdown will properly clear our resources anyways)
|
|
}
|
|
|
|
if (this.remoteControlListener != null) {
|
|
diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java
|
|
index 550232cb3819138b3bae0fa1c51429485e8bc593..229c3b0f0c650b501f31147adaa17194af57fedd 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 e44e5652c12fbee51acedc1f911181b8443fae93..d93db1049ef9421f6b3edd0dc52a421c4d1b51c2 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));
|
|
@@ -710,7 +785,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
|
|
try {
|
|
- this.checkBlockCollisions();
|
|
+ this.checkBlockCollisions(this.fireTicks <= 0); // Tuinity - move fire checking into method here
|
|
} catch (Throwable throwable) {
|
|
CrashReport crashreport = CrashReport.a(throwable, "Checking entity block collision");
|
|
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being checked for collision");
|
|
@@ -722,11 +797,7 @@ 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 - move into checkBlockCollisions
|
|
|
|
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 +806,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 +893,135 @@ 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);
|
|
+ if (world.getWorldBorder().isCollidingWithBorderEdge(collisionBox)) {
|
|
+ VoxelShapes.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions);
|
|
+ }
|
|
+
|
|
+ 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).add(vec3d3);
|
|
+
|
|
+ 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 +1057,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;
|
|
}
|
|
@@ -962,18 +1170,34 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
|
|
protected void checkBlockCollisions() {
|
|
+ // Tuinity start
|
|
+ this.checkBlockCollisions(false);
|
|
+ }
|
|
+ protected void checkBlockCollisions(boolean checkFire) {
|
|
+ boolean inFire = false;
|
|
+ // Tuinity end
|
|
AxisAlignedBB axisalignedbb = this.getBoundingBox();
|
|
BlockPosition blockposition = new BlockPosition(axisalignedbb.minX + 0.001D, axisalignedbb.minY + 0.001D, axisalignedbb.minZ + 0.001D);
|
|
BlockPosition blockposition1 = new BlockPosition(axisalignedbb.maxX - 0.001D, axisalignedbb.maxY - 0.001D, axisalignedbb.maxZ - 0.001D);
|
|
BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
|
|
|
|
if (this.world.areChunksLoadedBetween(blockposition, blockposition1)) {
|
|
- for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) {
|
|
- for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) {
|
|
- for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) {
|
|
+ // Tuinity start - reorder iteration to more cache aware
|
|
+ for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) {
|
|
+ for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) {
|
|
+ for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) {
|
|
+ // Tuinity end - reorder iteration to more cache aware
|
|
blockposition_mutableblockposition.d(i, j, k);
|
|
IBlockData iblockdata = this.world.getType(blockposition_mutableblockposition);
|
|
|
|
+ // Tuinity start - move fire checking in here - reuse getType from this method
|
|
+ if (checkFire) {
|
|
+ if (!inFire && (iblockdata.a(TagsBlock.FIRE) || iblockdata.a(Blocks.LAVA))) {
|
|
+ inFire = true;
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - move fire checking in here - reuse getType from this method
|
|
+
|
|
try {
|
|
iblockdata.a(this.world, blockposition_mutableblockposition, this);
|
|
this.a(iblockdata);
|
|
@@ -987,6 +1211,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
}
|
|
}
|
|
}
|
|
+ // Tuinity start - move fire checking in here - reuse getType from this method
|
|
+ if (checkFire & !inFire) {
|
|
+ this.setFireTicks(-this.getMaxFireTicks());
|
|
+ }
|
|
+ // Tuinity end - move fire checking in here - reuse getType from this method
|
|
}
|
|
|
|
}
|
|
@@ -1358,6 +1587,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());
|
|
}
|
|
@@ -1944,9 +2174,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
|
|
}
|
|
}
|
|
|
|
@@ -1954,11 +2184,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;
|
|
}
|
|
|
|
@@ -2842,7 +3074,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
this.cp().forEach((entity) -> {
|
|
worldserver.chunkCheck(entity);
|
|
entity.az = true;
|
|
- Iterator iterator = entity.passengers.iterator();
|
|
+ Iterator iterator = new java.util.ArrayList<>(entity.passengers).iterator(); // Tuinity - copy list to guard against CME
|
|
|
|
while (iterator.hasNext()) {
|
|
Entity entity1 = (Entity) iterator.next();
|
|
@@ -3300,12 +3532,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) {
|
|
@@ -3360,7 +3596,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 957a351c3f6c4f66d7af6657ab0c3cbeed94662f..57166a543a9af9e10e38c983487fac7ea9d42d52 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 dcc5b098bfe36ef7ee8536b3da65c4ce1748c9d8..7b32a1fb79dcae355a8d95f3a8aa4284ec5d10db 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
@@ -709,7 +709,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 dd3287f953a1a24d2406816b3c0ae176476b6452..43a4b474273a58ac3995407e72e5b830696b9ba0 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityLiving.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
@@ -2858,7 +2858,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.pushable(this, world.paperConfig.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule
|
|
+ // Tuinity start - reduce memory allocation from collideNearby
|
|
+ List<Entity> list = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList();
|
|
+ this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.pushable(this, world.paperConfig.fixClimbingBypassingCrammingRule), list); // Paper - fix climbing bypassing cramming rule
|
|
+ try {
|
|
+ // Tuinity end - reduce memory allocation from collideNearby
|
|
|
|
if (!list.isEmpty()) {
|
|
// Paper - move up
|
|
@@ -2887,6 +2891,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/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
index 7240b885d96eb2df187b6229449af1a893a4524e..2c276971d47e48b39afa176994eba5747a3a3951 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -527,6 +527,174 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
}
|
|
}
|
|
|
|
+ /* // TODO remove debug
|
|
+ this.networkManager.disableAutomaticFlush();
|
|
+
|
|
+ if (MinecraftServer.currentTick % 20 == 0) {
|
|
+ int centerX = MathHelper.floor(this.locX()) >> 4;
|
|
+ int centerZ = MathHelper.floor(this.locZ()) >> 4;
|
|
+ byte[] full = new byte[2048];
|
|
+ byte[] empty = new byte[2048];
|
|
+ java.util.Arrays.fill(full, (byte)-1);
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ int cx = centerX + dx;
|
|
+ int cz = centerZ + dz;
|
|
+
|
|
+ if (this.getWorldServer().getChunkProvider().getChunkAtIfLoadedImmediately(cx, cz) == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ for (int y = -1; y <= 16; ++y) {
|
|
+ NibbleArray nibble = this.getWorldServer().getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY)
|
|
+ .a(new SectionPosition(cx, y, cz));
|
|
+ org.bukkit.Color color;
|
|
+ org.bukkit.block.data.BlockData blockColor;
|
|
+ if (nibble == null) {
|
|
+ color = org.bukkit.Color.RED;
|
|
+ blockColor = org.bukkit.Material.RED_WOOL.createBlockData();
|
|
+ continue;
|
|
+ } else {
|
|
+ if (nibble.c()) { // is null
|
|
+ color = org.bukkit.Color.BLUE;
|
|
+ blockColor = org.bukkit.Material.BLUE_WOOL.createBlockData();
|
|
+ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), full)) {
|
|
+ color = org.bukkit.Color.RED;
|
|
+ blockColor = org.bukkit.Material.RED_WOOL.createBlockData();
|
|
+ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), empty)) {
|
|
+ color = org.bukkit.Color.BLACK;
|
|
+ blockColor = org.bukkit.Material.BLACK_WOOL.createBlockData();
|
|
+ } else {
|
|
+ color = org.bukkit.Color.ORANGE;
|
|
+ blockColor = org.bukkit.Material.ORANGE_WOOL.createBlockData();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ org.bukkit.Particle.DustOptions dustOptions = new org.bukkit.Particle.DustOptions(color, 1.7f);
|
|
+
|
|
+ for (int i = 0; i <= 16; ++i) {
|
|
+ // y axis
|
|
+
|
|
+ double xVal = i == 0 ? 0.5 : (i == 16 ? 15.5 : i);
|
|
+
|
|
+ // left side
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 0.5,
|
|
+ y*16 + xVal,
|
|
+ cz * 16 + 0.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 0.5,
|
|
+ y*16 + xVal,
|
|
+ cz * 16 + 15.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ // right side
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 15.5,
|
|
+ y*16 + xVal,
|
|
+ cz * 16 + 0.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 15.5,
|
|
+ y*16 + xVal,
|
|
+ cz * 16 + 15.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+
|
|
+ // x axis
|
|
+
|
|
+ // bottom
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + xVal,
|
|
+ y*16 + 0.5,
|
|
+ cz * 16 + 0.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + xVal,
|
|
+ y*16 + 0.5,
|
|
+ cz * 16 + 15.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ // top
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + xVal,
|
|
+ y*16 + 15.5,
|
|
+ cz * 16 + 0.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + xVal,
|
|
+ y*16 + 15.5,
|
|
+ cz * 16 + 15.5,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ // z axis
|
|
+ // bottom
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 0.5,
|
|
+ y*16 + 0.5,
|
|
+ cz * 16 + xVal,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 15.5,
|
|
+ y*16 + 0.5,
|
|
+ cz * 16 + xVal,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ //top
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 0.5,
|
|
+ y*16 + 15.5,
|
|
+ cz * 16 + xVal,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+
|
|
+ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE,
|
|
+ cx * 16 + 15.5,
|
|
+ y*16 + 15.5,
|
|
+ cz * 16 + xVal,
|
|
+ 1,
|
|
+ dustOptions
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.networkManager.enableAutomaticFlush();
|
|
+
|
|
+ System.out.println("Block: " + this.getBukkitEntity().getLocation().getBlock().getLightFromBlocks());
|
|
+ System.out.println("Sky: " + this.getBukkitEntity().getLocation().getBlock().getLightFromSky());
|
|
+ */ // TODO remove debug
|
|
+
|
|
if (this.getHealth() != this.lastHealthSent || this.lastFoodSent != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastSentSaturationZero) {
|
|
this.playerConnection.sendPacket(new PacketPlayOutUpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit
|
|
this.lastHealthSent = this.getHealth();
|
|
diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
|
|
index 4efc40c01ec12b80bd7cf9d35cf0ea0df973baf7..f322dccd834ff56b99f8796309709b5b6ac01456 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/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java
|
|
index 36aafc3b71013bcec0f4d956761fc2710848b3fd..c9963c19892c3e408964b72983bc6e26f8b63b67 100644
|
|
--- a/src/main/java/net/minecraft/server/EnumDirection.java
|
|
+++ b/src/main/java/net/minecraft/server/EnumDirection.java
|
|
@@ -160,8 +160,8 @@ public enum EnumDirection implements INamable {
|
|
return EnumDirection.q[MathHelper.a(i % EnumDirection.q.length)];
|
|
}
|
|
|
|
- @Nullable
|
|
- public static EnumDirection a(int i, int j, int k) {
|
|
+ @Nullable public static EnumDirection from(int i, int j, int k) { return a(i, j, k); } // Tuinity - OBFHELPER
|
|
+ @Nullable public static EnumDirection a(int i, int j, int k) {
|
|
return (EnumDirection) EnumDirection.r.get(BlockPosition.a(i, j, k));
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java
|
|
index 068b92c5c4ae112771757626ea75694e59f3d255..a43c4ca3ea2e0dbf34a177592daed18c64bb14d3 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 c4a83448ed4513f6e4ab179d1d43e5bb0cb13641..5c3eb4fc7e5aec2ad8d0050673fc8f4d2bff6a71 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/IChunkAccess.java b/src/main/java/net/minecraft/server/IChunkAccess.java
|
|
index 180b6b58dc5663158db84b6f1257591439b48c31..46f9ca664782c4f68a34461dcf1cdc878b3517a8 100644
|
|
--- a/src/main/java/net/minecraft/server/IChunkAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/IChunkAccess.java
|
|
@@ -24,6 +24,22 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start
|
|
+ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() {
|
|
+ throw new UnsupportedOperationException(this.getClass().getName());
|
|
+ }
|
|
+ default void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ throw new UnsupportedOperationException(this.getClass().getName());
|
|
+ }
|
|
+
|
|
+ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() {
|
|
+ throw new UnsupportedOperationException(this.getClass().getName());
|
|
+ }
|
|
+ default void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ throw new UnsupportedOperationException(this.getClass().getName());
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
IBlockData getType(final int x, final int y, final int z); // Paper
|
|
@Nullable
|
|
IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag);
|
|
@@ -122,6 +138,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess {
|
|
@Nullable
|
|
NBTTagCompound j(BlockPosition blockposition);
|
|
|
|
+ default Stream<BlockPosition> getLightSources() { return this.m(); } // Tuinity - OBFHELPER
|
|
Stream<BlockPosition> m();
|
|
|
|
TickList<Block> n();
|
|
@@ -142,6 +159,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess {
|
|
return ashortlist[i];
|
|
}
|
|
|
|
+ default boolean isLit() { return this.r(); } // Tuinity - OBFHELPER
|
|
boolean r();
|
|
|
|
void b(boolean flag);
|
|
diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
|
|
index 582a5695bac7d078e3022b8ee70c512c0680d992..5601088cd5024a40e8296bab979f43de924c2b62 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 25e54a1fadc5d31fb250a3f47524b4f345fc8cc6..cce0ac8a36bef3b9e5a2b95e0c3dd137e8525226 100644
|
|
--- a/src/main/java/net/minecraft/server/ICollisionAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/ICollisionAccess.java
|
|
@@ -28,6 +28,11 @@ public interface ICollisionAccess extends IBlockAccess {
|
|
}
|
|
|
|
default boolean b(AxisAlignedBB axisalignedbb) {
|
|
+ // Tuinity start - allow overriding in WorldServer
|
|
+ return this.getCubes(axisalignedbb);
|
|
+ }
|
|
+ default boolean getCubes(AxisAlignedBB axisalignedbb) {
|
|
+ // Tuinity end - allow overriding in WorldServer
|
|
return this.b((Entity) null, axisalignedbb, (entity) -> {
|
|
return true;
|
|
});
|
|
@@ -46,6 +51,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 2639c17b7f6100533f33124f9e49990cd303d161..cbaf18af1066e8bde10293bba5eb3060bae1e66f 100644
|
|
--- a/src/main/java/net/minecraft/server/IEntityAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
|
|
@@ -55,16 +55,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) {
|
|
@@ -82,7 +92,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
|
|
}
|
|
}
|
|
|
|
@@ -204,12 +214,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/ILightAccess.java b/src/main/java/net/minecraft/server/ILightAccess.java
|
|
index be5384ee41290b24b0c419c3e8f4553db34b2399..df28f7a6bf4c650a22ddf046eae4d5e8ca5879a9 100644
|
|
--- a/src/main/java/net/minecraft/server/ILightAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/ILightAccess.java
|
|
@@ -4,9 +4,10 @@ import javax.annotation.Nullable;
|
|
|
|
public interface ILightAccess {
|
|
|
|
- @Nullable
|
|
- IBlockAccess c(int i, int j);
|
|
+ default @Nullable IBlockAccess getFeaturesReadyChunk(int i, int j) { return this.c(i, j); } // Tuinity - OBFHELPER
|
|
+ @Nullable IBlockAccess c(int i, int j);
|
|
|
|
+ default void markLightSectionDirty(EnumSkyBlock enumskyblock, SectionPosition sectionposition) { this.a(enumskyblock, sectionposition); } // Tuinity - OBFHELPER
|
|
default void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition) {}
|
|
|
|
IBlockAccess getWorld();
|
|
diff --git a/src/main/java/net/minecraft/server/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/LightEngineGraphSection.java
|
|
index 13d067f48647dea63ef1bf3a2a3e0868074ba75f..04afd7f285db2f281a038e0be6f557b8a692936b 100644
|
|
--- a/src/main/java/net/minecraft/server/LightEngineGraphSection.java
|
|
+++ b/src/main/java/net/minecraft/server/LightEngineGraphSection.java
|
|
@@ -74,8 +74,10 @@ public abstract class LightEngineGraphSection extends LightEngineGraph {
|
|
return i == Long.MAX_VALUE ? this.b(j) : k + 1;
|
|
}
|
|
|
|
+ public final int getSource(long coordinate) { return this.b(coordinate); } // Tuinity - OBFHELPER
|
|
protected abstract int b(long i);
|
|
|
|
+ public final void update(long coordinate, int level, boolean flag) { this.b(coordinate, level, flag); } // Tuinity - OBFHELPER
|
|
public void b(long i, int j, boolean flag) {
|
|
this.a(Long.MAX_VALUE, i, j, flag);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java
|
|
index b98e60772bad7e06845b50fdc11e98c0ea775d3d..e0bbfe1422cbad811ecb43d7436380d86b0f8abc 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/LightEngineThreaded.java b/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
index 2f9c97dd4e1d705a87772d18c7ab4883a876af08..d4902ed0d12d9697402ca60bb8a298f753ccf527 100644
|
|
--- a/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
+++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java
|
|
@@ -2,6 +2,11 @@ package net.minecraft.server;
|
|
|
|
import com.mojang.datafixers.util.Pair;
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper
|
|
+// Tuinity start
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+// Tuinity end
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import it.unimi.dsi.fastutil.objects.ObjectList;
|
|
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
|
|
@@ -156,12 +161,244 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
private volatile int f = 5;
|
|
private final AtomicBoolean g = new AtomicBoolean();
|
|
|
|
+ // Tuinity start - replace light engine impl
|
|
+ protected final com.tuinity.tuinity.chunk.light.ThreadedStarLightEngine theLightEngine;
|
|
+ public final boolean hasBlockLight;
|
|
+ public final boolean hasSkyLight;
|
|
+
|
|
+ protected final LightEngineLayerEventListener skyReader;
|
|
+ protected final LightEngineLayerEventListener blockReader;
|
|
+ // Tuinity end - replace light engine impl
|
|
+
|
|
public LightEngineThreaded(ILightAccess ilightaccess, PlayerChunkMap playerchunkmap, boolean flag, ThreadedMailbox<Runnable> threadedmailbox, Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailbox) {
|
|
super(ilightaccess, true, flag);
|
|
this.d = playerchunkmap; this.playerChunkMap = d; // Paper
|
|
this.e = mailbox;
|
|
this.b = threadedmailbox;
|
|
+ // Tuinity start - replace light engine impl
|
|
+ this.hasBlockLight = true;
|
|
+ this.hasSkyLight = flag;
|
|
+ this.theLightEngine = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? new com.tuinity.tuinity.chunk.light.ThreadedStarLightEngine(ilightaccess, flag, true) : null;
|
|
+
|
|
+ this.blockReader = new LightEngineLayerEventListener() {
|
|
+ @Override
|
|
+ public NibbleArray a(SectionPosition sectionPosition) {
|
|
+ IChunkAccess chunk = LightEngineThreaded.this.getChunk(sectionPosition.getX(), sectionPosition.getZ());
|
|
+ return chunk == null ? null : chunk.getBlockNibbles()[sectionPosition.getY() + 1].asNibble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int b(BlockPosition blockPosition) {
|
|
+ int cx = blockPosition.getX() >> 4;
|
|
+ int cy = blockPosition.getY() >> 4;
|
|
+ int cz = blockPosition.getZ() >> 4;
|
|
+ IChunkAccess chunk = LightEngineThreaded.this.getChunk(cx, cz);
|
|
+ if (chunk == null) {
|
|
+ return 0;
|
|
+ }
|
|
+ if (cy < -1 || cy > 16) {
|
|
+ return 0;
|
|
+ }
|
|
+ return chunk.getBlockNibbles()[cy + 1].getVisible(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void a(SectionPosition sectionPosition, boolean b) {
|
|
+ return; // don't care.
|
|
+ }
|
|
+ };
|
|
+ if (!flag) {
|
|
+ this.skyReader = LightEngineLayerEventListener.Void.INSTANCE;
|
|
+ } else {
|
|
+ this.skyReader = new LightEngineLayerEventListener() {
|
|
+ @Override
|
|
+ public NibbleArray a(SectionPosition sectionPosition) {
|
|
+ IChunkAccess chunk = LightEngineThreaded.this.getChunk(sectionPosition.getX(), sectionPosition.getZ());
|
|
+ return chunk == null ? null : chunk.getSkyNibbles()[sectionPosition.getY() + 1].asNibble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int b(BlockPosition blockPosition) {
|
|
+ int cx = blockPosition.getX() >> 4;
|
|
+ int cy = blockPosition.getY() >> 4;
|
|
+ int cz = blockPosition.getZ() >> 4;
|
|
+ IChunkAccess chunk = LightEngineThreaded.this.getChunk(cx, cz);
|
|
+ if (chunk == null) {
|
|
+ return 15;
|
|
+ }
|
|
+ if (cy < -1) {
|
|
+ cy = -1;
|
|
+ } else if (cy > 16) {
|
|
+ cy = 16;
|
|
+ }
|
|
+ com.tuinity.tuinity.chunk.light.SWMRNibbleArray nibble = chunk.getSkyNibbles()[cy + 1];
|
|
+ return nibble.getVisible(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void a(SectionPosition sectionPosition, boolean b) {
|
|
+ return; // don't care.
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+ // Tuinity end - replace light engine impl
|
|
+ }
|
|
+
|
|
+ // Tuinity start - replace light engine impl
|
|
+ protected final IChunkAccess getChunk(final int chunkX, final int chunkZ) {
|
|
+ final WorldServer world = this.theLightEngine.getWorld();
|
|
+ return world.getChunkProvider().getChunkAtImmediately(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public void relight(int chunkX, int chunkZ, Runnable whenComplete) {
|
|
+ this.scheduleLightWorkTask(chunkX, chunkZ, LightEngineThreaded.Update.POST_UPDATE, () -> {
|
|
+ this.theLightEngine.relightChunk(chunkX, chunkZ);
|
|
+ whenComplete.run();
|
|
+ });
|
|
+ }
|
|
+
|
|
+ protected final Long2IntOpenHashMap holdingChunks = new Long2IntOpenHashMap();
|
|
+ protected final LongArrayList postWorkTicketRelease = new LongArrayList();
|
|
+
|
|
+ private void addLightWorkTicket(int chunkX, int chunkZ) {
|
|
+ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ final int current = this.holdingChunks.putIfAbsent(coordinate, 1);
|
|
+ if (current == 0) {
|
|
+ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate);
|
|
+ this.theLightEngine.getWorld().getChunkProvider().addTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos,
|
|
+ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos);
|
|
+ this.theLightEngine.getWorld().getChunkProvider().tickDistanceManager();
|
|
+ } else {
|
|
+ this.holdingChunks.put(coordinate, current + 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final void releaseLightWorkChunk(int chunkX, int chunkZ) {
|
|
+ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ final int current = this.holdingChunks.get(coordinate);
|
|
+ if (current == 1) {
|
|
+ this.holdingChunks.remove(coordinate);
|
|
+ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate);
|
|
+ this.theLightEngine.getWorld().getChunkProvider().removeTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos,
|
|
+ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos);
|
|
+ } else {
|
|
+ this.holdingChunks.put(coordinate, current - 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected final CompletableFuture<IChunkAccess> acquireLightWorkChunk(int chunkX, int chunkZ) {
|
|
+ ChunkProviderServer chunkProvider = this.theLightEngine.getWorld().getChunkProvider();
|
|
+ PlayerChunkMap chunkMap = chunkProvider.playerChunkMap;
|
|
+ int targetLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
|
+
|
|
+ this.addLightWorkTicket(chunkX, chunkZ);
|
|
+
|
|
+ // light doesn't always load one radius neighbours...
|
|
+ // i.e if they get unloaded
|
|
+ boolean neighboursAtFeatures = true;
|
|
+ int targetNeighbourLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT.getPreviousStatus());
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ PlayerChunk neighbour = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(dx + chunkX, dz + chunkZ));
|
|
+ ChunkStatus status;
|
|
+ if (neighbour == null || neighbour.getTicketLevel() > targetNeighbourLevel ||
|
|
+ (status = neighbour.getChunkHolderStatus()) == null ||
|
|
+ !status.isAtLeastStatus(ChunkStatus.LIGHT.getPreviousStatus())) {
|
|
+ neighboursAtFeatures = false;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ PlayerChunk playerChunk = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ));
|
|
+ ChunkStatus holderStatus;
|
|
+ if (!neighboursAtFeatures || playerChunk == null || playerChunk.getTicketLevel() > targetLevel ||
|
|
+ (holderStatus = playerChunk.getChunkHolderStatus()) == null ||
|
|
+ !holderStatus.isAtLeastStatus(ChunkStatus.LIGHT)) {
|
|
+ CompletableFuture<IChunkAccess> ret = new CompletableFuture<>();
|
|
+
|
|
+ int[] loads = new int[1];
|
|
+ int requiredLoads = 3 * 3;
|
|
+ java.util.function.Consumer<IChunkAccess> onLoad = (chunk) -> {
|
|
+ if (++loads[0] == requiredLoads) {
|
|
+ ret.complete(this.getChunk(chunkX, chunkZ));
|
|
+ }
|
|
+ };
|
|
+
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ chunkProvider.getChunkAtAsynchronously(chunkX + dx, chunkZ + dz,
|
|
+ (dx | dz) == 0 ? ChunkStatus.LIGHT : ChunkStatus.LIGHT.getPreviousStatus(),
|
|
+ true, false, onLoad);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return CompletableFuture.completedFuture(playerChunk.getAvailableChunkNow());
|
|
+ }
|
|
+
|
|
+ // note: task is discarded if the chunk is not at light status or if the chunk is not lit
|
|
+ protected final void scheduleLightWorkTask(int chunkX, int chunkZ, LightEngineThreaded.Update type, Runnable task) {
|
|
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
|
|
+ this.playerChunkMap.mainInvokingExecutor.execute(() -> {
|
|
+ this.scheduleLightWorkTask(chunkX, chunkZ, type, task);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ IChunkAccess current = this.getChunk(chunkX, chunkZ);
|
|
+
|
|
+ if (current == null || !current.isLit() || !current.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ this.acquireLightWorkChunk(chunkX, chunkZ).whenCompleteAsync((chunk, throwable) -> {
|
|
+ if (throwable != null) {
|
|
+ MinecraftServer.LOGGER.fatal("Failed to load light chunk for light work", throwable);
|
|
+ this.releaseLightWorkChunk(chunkX, chunkZ);
|
|
+ } else {
|
|
+ this.scheduleTask(chunkX, chunkZ, type, () -> {
|
|
+ try {
|
|
+ task.run();
|
|
+ } finally {
|
|
+ this.postWorkTicketRelease.add(MCUtil.getCoordinateKey(chunkX, chunkZ));
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+ }, this.playerChunkMap.mainInvokingExecutor);
|
|
+ }
|
|
+
|
|
+ // override things from superclass
|
|
+
|
|
+ @Override
|
|
+ public boolean a() {
|
|
+ return this.theLightEngine != null ? false : super.a();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public LightEngineLayerEventListener a(EnumSkyBlock var0) {
|
|
+ if (this.theLightEngine == null) {
|
|
+ return super.a(var0);
|
|
+ }
|
|
+ if (var0 == EnumSkyBlock.BLOCK) {
|
|
+ return this.blockReader;
|
|
+ } else {
|
|
+ return this.skyReader;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int b(BlockPosition var0, int var1) {
|
|
+ if (this.theLightEngine == null) {
|
|
+ return super.b(var0, var1);
|
|
+ }
|
|
+ int var2 = this.skyReader.b(var0) - var1;
|
|
+ int var3 = this.blockReader.b(var0);
|
|
+ return Math.max(var3, var2);
|
|
}
|
|
+ // Tuinity end - replace light engine impl
|
|
|
|
public void close() {}
|
|
|
|
@@ -179,6 +416,15 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
public void a(BlockPosition blockposition) {
|
|
BlockPosition blockposition1 = blockposition.immutableCopy();
|
|
|
|
+ // Tuinity start - replace light engine impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ this.scheduleLightWorkTask(blockposition1.getX() >> 4, blockposition1.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, () -> {
|
|
+ this.theLightEngine.blockChange(blockposition1);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity start - replace light engine impl
|
|
+
|
|
this.a(blockposition.getX() >> 4, blockposition.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, SystemUtils.a(() -> {
|
|
super.a(blockposition1);
|
|
}, () -> {
|
|
@@ -187,6 +433,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
}
|
|
|
|
protected void a(ChunkCoordIntPair chunkcoordintpair) {
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - replace light impl
|
|
this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> {
|
|
return 0;
|
|
}, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
@@ -211,6 +462,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
|
|
@Override
|
|
public void a(SectionPosition sectionposition, boolean flag) {
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - replace light impl
|
|
this.a(sectionposition.a(), sectionposition.c(), () -> {
|
|
return 0;
|
|
}, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
@@ -222,6 +478,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
|
|
@Override
|
|
public void a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - replace light impl
|
|
this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
super.a(chunkcoordintpair, flag);
|
|
}, () -> {
|
|
@@ -231,6 +492,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
|
|
@Override
|
|
public void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition, @Nullable NibbleArray nibblearray, boolean flag) {
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - replace light impl
|
|
this.a(sectionposition.a(), sectionposition.c(), () -> {
|
|
return 0;
|
|
}, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
@@ -240,6 +506,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
}));
|
|
}
|
|
|
|
+ private void scheduleTask(int x, int z, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { this.a(x, z, lightenginethreaded_update, runnable); } // Tuinity - OBFHELPER
|
|
private void a(int i, int j, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) {
|
|
this.a(i, j, this.d.c(ChunkCoordIntPair.pair(i, j)), lightenginethreaded_update, runnable);
|
|
}
|
|
@@ -252,6 +519,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
|
|
@Override
|
|
public void b(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - replace light impl
|
|
this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> {
|
|
return 0;
|
|
}, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> {
|
|
@@ -264,6 +536,13 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
public CompletableFuture<IChunkAccess> a(IChunkAccess ichunkaccess, boolean flag) {
|
|
ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos();
|
|
|
|
+ // Tuinity start - rewrite light engine
|
|
+ if (flag && this.theLightEngine != null) {
|
|
+ this.d.c(chunkcoordintpair);
|
|
+ return CompletableFuture.completedFuture(ichunkaccess);
|
|
+ }
|
|
+ // Tuinity end - rewrite light engine
|
|
+
|
|
// Paper start
|
|
//ichunkaccess.b(false); // Don't need to disable this
|
|
long pair = chunkcoordintpair.pair();
|
|
@@ -277,6 +556,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
return;
|
|
}
|
|
// Paper end
|
|
+ if (this.theLightEngine == null) { // Tuinity - replace light engine impl
|
|
ChunkSection[] achunksection = ichunkaccess.getSections();
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
@@ -293,16 +573,19 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
super.a(blockposition, ichunkaccess.g(blockposition));
|
|
});
|
|
}
|
|
+ } else { // Tuinity start - replace light engine impl
|
|
+ this.theLightEngine.lightChunk(chunkcoordintpair.x, chunkcoordintpair.z);
|
|
+ } // Tuinity end - replace light engine impl
|
|
|
|
// this.d.c(chunkcoordintpair); // Paper - move into post task below
|
|
}, () -> {
|
|
return "lightChunk " + chunkcoordintpair + " " + flag;
|
|
// Paper start - merge the 2 together
|
|
}), () -> {
|
|
- this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done
|
|
+ this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done // Tuinity - diff on change, copied to top of method for early return if the chunk is already lit
|
|
if (skippedPre[0]) return; // Paper - future's already complete
|
|
ichunkaccess.b(true);
|
|
- super.b(chunkcoordintpair, false);
|
|
+ if (this.theLightEngine == null) super.b(chunkcoordintpair, false); // Tuinity - replace light engine impl
|
|
// Paper start
|
|
future.complete(ichunkaccess);
|
|
});
|
|
@@ -311,7 +594,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
}
|
|
|
|
public void queueUpdate() {
|
|
- if ((!this.queue.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { // Paper
|
|
+ if ((!this.queue.isEmpty() || (this.theLightEngine == null && super.a())) && this.g.compareAndSet(false, true)) { // Paper // Tuinity - replace light impl
|
|
this.b.a((() -> { // Paper - decompile error
|
|
this.b();
|
|
this.g.set(false);
|
|
@@ -325,17 +608,36 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable {
|
|
private final java.util.List<Runnable> pre = new java.util.ArrayList<>();
|
|
private final java.util.List<Runnable> post = new java.util.ArrayList<>();
|
|
private void b() {
|
|
+ //final long start = System.nanoTime(); // TODO remove debug
|
|
if (queue.poll(pre, post)) {
|
|
pre.forEach(Runnable::run);
|
|
pre.clear();
|
|
- super.a(Integer.MAX_VALUE, true, true);
|
|
+ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl
|
|
post.forEach(Runnable::run);
|
|
post.clear();
|
|
} else {
|
|
// might have level updates to go still
|
|
- super.a(Integer.MAX_VALUE, true, true);
|
|
+ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl
|
|
+ }
|
|
+ // Tuinity start - replace light impl
|
|
+ if (this.theLightEngine != null) {
|
|
+ this.theLightEngine.propagateChanges();
|
|
+ if (!this.postWorkTicketRelease.isEmpty()) {
|
|
+ LongArrayList copy = this.postWorkTicketRelease.clone();
|
|
+ this.postWorkTicketRelease.clear();
|
|
+ this.playerChunkMap.mainInvokingExecutor.execute(() -> {
|
|
+ LongIterator iterator = copy.iterator();
|
|
+ while (iterator.hasNext()) {
|
|
+ long coordinate = iterator.nextLong();
|
|
+ this.releaseLightWorkChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate));
|
|
+ }
|
|
+ });
|
|
+ }
|
|
}
|
|
+ // Tuinity end - replace light impl
|
|
// Paper end
|
|
+ //final long end = System.nanoTime(); // TODO remove debug
|
|
+ //System.out.println("Block updates took " + (end - start) * 1.0e-6 + "ms"); // TODO remove debug
|
|
}
|
|
|
|
public void a(int i) {
|
|
diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java
|
|
index c61cd50df0c81f7ab12bd0c955fd6f07f2b02e64..d987483255195c0bde713a92676baced1eaff2b3 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 ff74be14512a947e81b62d53e616131ca7d7f609..e79e773f2219f9a9ae076fcbc8108b792201b11a 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>(),
|
|
@@ -221,6 +222,63 @@ public final class MCUtil {
|
|
return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ()));
|
|
}
|
|
|
|
+ // Tuinity start
|
|
+
|
|
+ static final int SECTION_X_BITS = 22;
|
|
+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
|
|
+ static final int SECTION_Y_BITS = 20;
|
|
+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
|
|
+ static final int SECTION_Z_BITS = 22;
|
|
+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
|
|
+ // format is y,z,x (in order of LSB to MSB)
|
|
+ static final int SECTION_Y_SHIFT = 0;
|
|
+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
|
|
+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
|
|
+ static final int SECTION_TO_BLOCK_SHIFT = 4;
|
|
+
|
|
+ public static long getSectionKey(final int x, final int y, final int z) {
|
|
+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getSectionKey(final SectionPosition pos) {
|
|
+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getSectionKey(final ChunkCoordIntPair pos, final int y) {
|
|
+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getSectionKey(final BlockPosition pos) {
|
|
+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
|
|
+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
|
|
+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
|
|
+ }
|
|
+
|
|
+ public static long getSectionKey(final Entity entity) {
|
|
+ return ((MCUtil.fastFloor(entity.locX()) & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((MCUtil.fastFloor(entity.locY()) & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((MCUtil.fastFloor(entity.locZ()) & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static int getSectionX(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
|
|
+ }
|
|
+
|
|
+ public static int getSectionY(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
|
|
+ }
|
|
+
|
|
+ public static int getSectionZ(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
// assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable
|
|
public static <T> void mergeSortedSets(final java.util.function.Consumer<T> consumer, final java.util.Comparator<? super T> comparator, final java.util.SortedSet<T>...sets) {
|
|
final ObjectRBTreeSet<T> all = new ObjectRBTreeSet<>(comparator);
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 172fc9ef9c0d3444eb99f750a17d42f130d94f73..2bb4710868b4756c3446bb785c7dc1f00c9e4258 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -156,6 +156,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
|
|
@@ -754,10 +755,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
org.spigotmc.WatchdogThread.doStop(); // Paper
|
|
if (!isMainThread()) {
|
|
MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER)");
|
|
+ long start = System.nanoTime(); // Tuinity - really try hard to kill the main thread
|
|
while (this.getThread().isAlive()) {
|
|
this.getThread().stop();
|
|
try {
|
|
- Thread.sleep(1);
|
|
+ if ((System.nanoTime() - start) <= (15L * 1000L * 1000L * 1000L)) Thread.sleep(1); // Tuinity - really try hard to kill the main thread - if we're past 15s we're probably in a terrible loop, spam it to really kill it
|
|
} catch (InterruptedException e) {}
|
|
}
|
|
}
|
|
@@ -952,6 +954,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper end
|
|
|
|
PaperJvmChecker.checkJvm(); // Paper jvm version nag
|
|
+ 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 );
|
|
@@ -969,6 +972,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;
|
|
@@ -983,7 +987,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper end
|
|
tickSection = curTime;
|
|
}
|
|
- midTickChunksTasksRan = 0; // Paper
|
|
+ // Tuinity - replace logic
|
|
// Spigot end
|
|
|
|
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
|
|
@@ -1076,6 +1080,76 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start - execute chunk tasks mid tick
|
|
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
|
|
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
|
|
+
|
|
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
|
|
+
|
|
+ private static long lastMidTickExecute;
|
|
+ private static long lastMidTickExecuteFailure;
|
|
+
|
|
+ private boolean tickMidTickTasks() {
|
|
+ // give all worlds a fair chance at by targetting them all.
|
|
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
|
|
+ boolean executed = false;
|
|
+ for (WorldServer world : this.getWorlds()) {
|
|
+ long currTime = System.nanoTime();
|
|
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
|
+ continue;
|
|
+ }
|
|
+ if (!world.getChunkProvider().runTasks()) {
|
|
+ // we need to back off if this fails
|
|
+ world.lastMidTickExecuteFailure = currTime;
|
|
+ } else {
|
|
+ executed = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return executed;
|
|
+ }
|
|
+
|
|
+ public final void executeMidTickTasks() {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
|
|
+ long startTime = System.nanoTime();
|
|
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
|
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
|
|
+ // so, backoff to prevent this
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
|
|
+ try {
|
|
+ for (;;) {
|
|
+ boolean moreTasks = this.tickMidTickTasks();
|
|
+ long currTime = System.nanoTime();
|
|
+ long diff = currTime - startTime;
|
|
+
|
|
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
|
|
+ if (!moreTasks) {
|
|
+ lastMidTickExecuteFailure = currTime;
|
|
+ }
|
|
+
|
|
+ // note: negative values reduce the time
|
|
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
|
|
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
|
|
+ // make sure something like a GC or dumb plugin doesn't screw us over...
|
|
+ overuse = 10L * 1000L * 1000L; // 10ms
|
|
+ }
|
|
+
|
|
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
|
|
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
|
|
+
|
|
+ lastMidTickExecute = currTime + extraSleep;
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ } finally {
|
|
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - execute chunk tasks mid tick
|
|
+
|
|
private void executeModerately() {
|
|
this.executeAll();
|
|
java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
|
@@ -1089,22 +1163,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
});
|
|
}
|
|
|
|
- // Paper start
|
|
- public int midTickChunksTasksRan = 0;
|
|
- private long midTickLastRan = 0;
|
|
- public void midTickLoadChunks() {
|
|
- if (!isMainThread() || System.nanoTime() - midTickLastRan < 1000000) {
|
|
- // only check once per 0.25ms incase this code is called in a hot method
|
|
- return;
|
|
- }
|
|
- try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
|
|
- for (WorldServer value : this.getWorlds()) {
|
|
- value.getChunkProvider().serverThreadQueue.midTickLoadChunks();
|
|
- }
|
|
- midTickLastRan = System.nanoTime();
|
|
- }
|
|
- }
|
|
- // Paper end
|
|
+ // Tuinity - replace logic
|
|
|
|
@Override
|
|
protected TickTask postToMainThread(Runnable runnable) {
|
|
@@ -1131,6 +1190,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()) {
|
|
@@ -1198,7 +1258,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper start - move oversleep into full server tick
|
|
isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
|
|
this.awaitTasks(() -> {
|
|
- midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick
|
|
+ // Tuinity - replace logic
|
|
return !this.canOversleep();
|
|
});
|
|
isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
|
|
@@ -1263,6 +1323,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;
|
|
@@ -1289,16 +1351,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();
|
|
|
|
@@ -1309,7 +1371,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
|
|
@@ -1351,11 +1413,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;
|
|
@@ -1449,7 +1512,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 1558c5f8256f50be6850f1d7f70eee3e8ec76496..b92ca4a6de01f3f86367fb8dfe3591b08a3e9218 100644
|
|
--- a/src/main/java/net/minecraft/server/NavigationAbstract.java
|
|
+++ b/src/main/java/net/minecraft/server/NavigationAbstract.java
|
|
@@ -21,7 +21,7 @@ public abstract class NavigationAbstract {
|
|
protected long j;
|
|
protected double k;
|
|
protected float l;
|
|
- protected boolean m;
|
|
+ protected boolean m; protected final boolean needsPathRecalculation() { return this.m; } // Tuinity - OBFHELPER
|
|
protected long n;
|
|
protected PathfinderAbstract o;
|
|
private BlockPosition p;
|
|
@@ -30,6 +30,13 @@ public abstract class NavigationAbstract {
|
|
private final Pathfinder s; public Pathfinder getPathfinder() { return this.s; } // Paper - OBFHELPER
|
|
private boolean t;
|
|
|
|
+ // Tuinity start
|
|
+ public boolean isViableForPathRecalculationChecking() {
|
|
+ return !this.needsPathRecalculation() &&
|
|
+ (this.c != null && !this.c.c() && this.c.e() != 0);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public NavigationAbstract(EntityInsentient entityinsentient, World world) {
|
|
this.g = Vec3D.ORIGIN;
|
|
this.h = BaseBlockPosition.ZERO;
|
|
@@ -85,7 +92,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
|
|
@@ -393,7 +400,7 @@ public abstract class NavigationAbstract {
|
|
}
|
|
|
|
public void b(BlockPosition blockposition) {
|
|
- if (this.c != null && !this.c.c() && this.c.e() != 0) {
|
|
+ if (this.c != null && !this.c.c() && this.c.e() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking()
|
|
PathPoint pathpoint = this.c.d();
|
|
Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
index fb1e3c705b8abee13695762cdfd0e9f1bfdb5ad8..6a0ec0105399066dede622b45c9471b32c162cf6 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;
|
|
}
|
|
@@ -145,8 +180,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) {
|
|
@@ -222,7 +312,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
|
|
@@ -248,6 +338,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();
|
|
|
|
@@ -270,7 +368,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);
|
|
@@ -290,39 +388,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
|
|
}
|
|
|
|
}
|
|
@@ -345,6 +487,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();
|
|
@@ -352,16 +496,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;
|
|
@@ -438,10 +588,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/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java
|
|
index 4085426af03f032cf405bdfd1e40a8e5dc27c1d1..348d16ddec3b4da0b6b4e4f49916b966005b5259 100644
|
|
--- a/src/main/java/net/minecraft/server/NibbleArray.java
|
|
+++ b/src/main/java/net/minecraft/server/NibbleArray.java
|
|
@@ -56,6 +56,7 @@ public class NibbleArray {
|
|
boolean poolSafe = false;
|
|
public java.lang.Runnable cleaner;
|
|
private void registerCleaner() {
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) return; // Tuinity - purge cleaner usage
|
|
if (!poolSafe) {
|
|
cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes);
|
|
} else {
|
|
@@ -63,7 +64,7 @@ public class NibbleArray {
|
|
}
|
|
}
|
|
// Paper end
|
|
- @Nullable protected byte[] a;
|
|
+ @Nullable protected byte[] a; public final byte[] justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist() { return this.a; }
|
|
|
|
|
|
public NibbleArray() {}
|
|
@@ -74,7 +75,7 @@ public class NibbleArray {
|
|
}
|
|
public NibbleArray(byte[] abyte, boolean isSafe) {
|
|
this.a = abyte;
|
|
- if (!isSafe) this.a = getCloneIfSet(); // Paper - clone for safety
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && !isSafe) this.a = getCloneIfSet(); // Paper - clone for safety // Tuinity - no need to clone
|
|
registerCleaner();
|
|
// Paper end
|
|
if (abyte.length != 2048) {
|
|
@@ -162,7 +163,7 @@ public class NibbleArray {
|
|
|
|
public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER
|
|
public NibbleArray b() {
|
|
- return this.a == null ? new NibbleArray() : new NibbleArray(this.a); // Paper - clone in ctor
|
|
+ return this.a == null ? new NibbleArray() : new NibbleArray(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? this.a.clone() : this.a); // Paper - clone in ctor // Tuinity - no longer clone in constructor
|
|
}
|
|
|
|
public String toString() {
|
|
diff --git a/src/main/java/net/minecraft/server/PacketCompressor.java b/src/main/java/net/minecraft/server/PacketCompressor.java
|
|
index 3cdd07cad85ef2d2c4b6c27a55a878695b4a7b12..50b2a8dfbdd0fe60e295d7c7214d7c99bcbeb19a 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 23c850be0155c1ece807d244117a196488f0a13b..4bab19a52b400071e69b06b940ab6432dfe59a1b 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 c85f291c5b22a8e85c7556b220cba698701748f2..771cc0f4fa98be294abba65c83442205b6b0ef0b 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 e35369476839e9622520af1027d7478aa6d1b037..aba14794cc4cb114fea17bb92816ac29a64b44f8 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/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
|
|
index a22f0cccecc85b4e4fe4603bcfa213f15c23db69..6cc4a035c8b1312b59685b20039d5e82bb1e1a3e 100644
|
|
--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
|
|
+++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java
|
|
@@ -26,12 +26,12 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
|
|
@Override
|
|
public void onPacketDispatch(EntityPlayer player) {
|
|
- remainingSends.incrementAndGet();
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) remainingSends.incrementAndGet();
|
|
}
|
|
|
|
@Override
|
|
public void onPacketDispatchFinish(EntityPlayer player, ChannelFuture future) {
|
|
- if (remainingSends.decrementAndGet() <= 0) {
|
|
+ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && remainingSends.decrementAndGet() <= 0) {
|
|
// incase of any race conditions, schedule this delayed
|
|
MCUtil.scheduleTask(5, () -> {
|
|
if (remainingSends.get() == 0) {
|
|
@@ -44,7 +44,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
|
|
@Override
|
|
public boolean hasFinishListener() {
|
|
- return true;
|
|
+ return !com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine; // Tuinity - replace light impl
|
|
}
|
|
|
|
// Paper end
|
|
@@ -54,8 +54,8 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
this.a = chunkcoordintpair.x;
|
|
this.b = chunkcoordintpair.z;
|
|
this.i = flag;
|
|
- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper
|
|
- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper
|
|
+ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage
|
|
+ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage
|
|
|
|
for (int i = 0; i < 18; ++i) {
|
|
NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i));
|
|
@@ -66,7 +66,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
this.e |= 1 << i;
|
|
} else {
|
|
this.c |= 1 << i;
|
|
- this.g.add(nibblearray.getCloneIfSet()); // Paper
|
|
+ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again
|
|
}
|
|
}
|
|
|
|
@@ -75,7 +75,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
this.f |= 1 << i;
|
|
} else {
|
|
this.d |= 1 << i;
|
|
- this.h.add(nibblearray1.getCloneIfSet()); // Paper
|
|
+ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.getCloneIfSet()); // Paper // Tuinity - don't clone again
|
|
}
|
|
}
|
|
}
|
|
@@ -88,8 +88,8 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
this.i = flag;
|
|
this.c = i;
|
|
this.d = j;
|
|
- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper
|
|
- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper
|
|
+ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage
|
|
+ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage
|
|
|
|
for (int k = 0; k < 18; ++k) {
|
|
NibbleArray nibblearray;
|
|
@@ -97,7 +97,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
if ((this.c & 1 << k) != 0) {
|
|
nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k));
|
|
if (nibblearray != null && !nibblearray.c()) {
|
|
- this.g.add(nibblearray.getCloneIfSet()); // Paper
|
|
+ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again
|
|
} else {
|
|
this.c &= ~(1 << k);
|
|
if (nibblearray != null) {
|
|
@@ -109,7 +109,7 @@ public class PacketPlayOutLightUpdate implements Packet<PacketListenerPlayOut> {
|
|
if ((this.d & 1 << k) != 0) {
|
|
nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k));
|
|
if (nibblearray != null && !nibblearray.c()) {
|
|
- this.h.add(nibblearray.getCloneIfSet()); // Paper
|
|
+ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again
|
|
} else {
|
|
this.d &= ~(1 << k);
|
|
if (nibblearray != null) {
|
|
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
index 5094a5d6fb3c1a84d6e8f6abe79e894c047d9cfa..72fdbf1534b65284ac8020dcc15fe1512766d087 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 fb37f5b500c52f915b4536e5ec35552b75056046..52a2d3db7da3596bfdd6fd51147cc93bbe6c7ed0 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 475c0764b97b056f17720f37b1ca3eb1a2375334..9f48d476c05dbeabbfe3c650ce4ad33ec691a56a 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 74e81e1e4aea6f74b14a84231ddeb7f2fb845ae7..33804e68931e8b4145b896eedeab79bde78779f2 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 253377c6238594de1f76cafcbf8223592e4d3f6b..3ebe3d0dc4c2c6aee6ea349006a74cbe5aa8e78f 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 11a67ca18f566bfc214659e7fb454ea2b4d8a7ad..35a569f61ba2d48f3f9692927a23777bafcf26d6 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
|
|
@@ -377,13 +383,14 @@ public class PlayerChunk {
|
|
|
|
public void a(EnumSkyBlock enumskyblock, int i) {
|
|
Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
|
|
+ if (this.getAvailableChunkNow() != null) this.getAvailableChunkNow().setNeedsSaving(true); // Tuinity - always mark as needing saving
|
|
|
|
if (chunk != null) {
|
|
chunk.setNeedsSaving(true);
|
|
if (enumskyblock == EnumSkyBlock.SKY) {
|
|
- this.s |= 1 << i - -1;
|
|
+ this.s |= 1 << (i - -1); // Tuinity - fix mojang oopsie
|
|
} else {
|
|
- this.r |= 1 << i - -1;
|
|
+ this.r |= 1 << (i - -1); // Tuinity - fix mojang oopsie
|
|
}
|
|
|
|
}
|
|
@@ -421,7 +428,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 +511,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 +567,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 +577,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 +643,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 +675,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 +686,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 +699,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 +716,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 +737,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 +768,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 6c399bcea03e839bf2f21e92b5d76d46b7088667..d3bf356ea768a32a5684eb851a2a0addcc67727b 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,35 @@ 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> {
|
|
+ // Tuinity start - optimise notify()
|
|
+ PATHING_NAVIGATORS() {
|
|
+ @Override
|
|
+ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<RegionData> section,
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> regionManager) {
|
|
+ return new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(true);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+ ;
|
|
+
|
|
+ @Override
|
|
+ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<RegionData> section,
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> regionManager) {
|
|
+ throw new AbstractMethodError();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> dataRegionManager;
|
|
+ // Tuiniy end
|
|
+
|
|
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);
|
|
@@ -310,9 +345,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
this.worldLoadListener = worldloadlistener;
|
|
// Paper start - use light thread
|
|
+ String threadName = ((WorldDataServer)this.world.getWorldData()).getName() + " - Light"; // Tuinity - make sure playerchunkmap instance is not retained by the thread factory
|
|
ThreadedMailbox<Runnable> lightthreaded; ThreadedMailbox<Runnable> threadedmailbox1 = lightthreaded = ThreadedMailbox.a(lightThread = java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
|
|
Thread thread = new Thread(r);
|
|
- thread.setName(((WorldDataServer)world.getWorldData()).getName() + " - Light");
|
|
+ thread.setName(threadName); // Tuinity - make sure playerchunkmap instance is not retained by the thread factory
|
|
thread.setDaemon(true);
|
|
thread.setPriority(Thread.NORM_PRIORITY+1);
|
|
return thread;
|
|
@@ -444,6 +480,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 = 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 +812,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,7 +836,9 @@ 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.getVillagePlace().dequeueUnload(playerchunk.location.pair()); // Tuinity - unload POI data
|
|
|
|
this.updatingChunks.put(i, playerchunk);
|
|
this.updatingChunksModified = true;
|
|
@@ -904,7 +964,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
}
|
|
|
|
- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more
|
|
+ static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more // Tuinity - private -> package private
|
|
|
|
protected void unloadChunks(BooleanSupplier booleansupplier) {
|
|
GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler();
|
|
@@ -970,7 +1030,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 +1064,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 +1072,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 +1082,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 +1114,9 @@ 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
|
|
+ if (removed) this.getVillagePlace().queueUnload(playerchunk.location.pair(), MinecraftServer.currentTickLong + 1); // Tuinity - unload POI data
|
|
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks
|
|
|
|
}
|
|
};
|
|
@@ -1059,6 +1132,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 {
|
|
@@ -1134,6 +1208,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
this.getVillagePlace().loadInData(chunkcoordintpair, chunkHolder.poiData);
|
|
chunkHolder.tasks.forEach(Runnable::run);
|
|
+ this.getVillagePlace().dequeueUnload(chunkcoordintpair.pair()); // Tuinity
|
|
// Paper end
|
|
|
|
if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
|
|
@@ -1246,7 +1321,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 +1576,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 +1590,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 +1706,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 +1790,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 +2122,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 3e1f88bce8e0ba95f73bca204549db0c65b1465e..df01083f826463cce714eccd5317791c781f04ed 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
@@ -419,7 +419,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;
|
|
}
|
|
@@ -898,6 +900,36 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
|
|
@Override
|
|
public void a(PacketPlayInBEdit packetplayinbedit) {
|
|
+ // Tuinity start - apply strict vanilla limits to books
|
|
+ ItemStack bookStack = packetplayinbedit.b();
|
|
+ if (!bookStack.isEmpty() && bookStack.getTag() != null) {
|
|
+ NBTTagList pages = bookStack.getTag().getList("pages", 8);
|
|
+ if (pages != null && !pages.isEmpty()) {
|
|
+ if (pages.size() > 50) {
|
|
+ this.minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!"));
|
|
+ return;
|
|
+ }
|
|
+ for (int i = 0, len = pages.size(); i < len; ++i) {
|
|
+ NBTBase base = pages.get(i);
|
|
+ if (!(base instanceof NBTTagString)) {
|
|
+ this.minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!"));
|
|
+ return;
|
|
+ }
|
|
+ String string = ((NBTTagString)base).asString();
|
|
+ if (string.length() <= 256) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (string.length() > (256*2)) {
|
|
+ this.minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!"));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Don't check the full length of the string, it's too expensive, could be exploited.
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - apply strict vanilla limits to books
|
|
// Paper start
|
|
ItemStack testStack = packetplayinbedit.b(); // TODO(Proximyst): Add obfhelper here
|
|
if (!server.isPrimaryThread() && !testStack.isEmpty() && testStack.getTag() != null) {
|
|
@@ -1058,7 +1090,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);
|
|
}
|
|
@@ -1128,7 +1160,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;
|
|
}
|
|
@@ -1184,6 +1216,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) {
|
|
@@ -1208,7 +1241,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 7ea293f38dedd6066601d94adbe175a31c502e1f..e698dd22607b2b2c4068c5bfb03ac53eb5bac080 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 485b609bb5387b0f8a46c1201177cdc6d183ad91..614cfacb1e8ac5d68f0ce931933fac5d71ca39c9 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 5b0cd414ca1949ab53b289f7159f18da07d21f14..e2500821dfa7060363e851dccc12e56dfc3cb051 100644
|
|
--- a/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
@@ -48,6 +48,31 @@ public class ProtoChunk implements IChunkAccess {
|
|
private volatile boolean u;
|
|
final World world; // Paper - Anti-Xray - Add world // Paper - private -> default
|
|
|
|
+ // Tuinity start - rewrite light engine
|
|
+ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(false);
|
|
+ private volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(true);
|
|
+
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() {
|
|
+ return this.blockNibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.blockNibbles = nibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() {
|
|
+ return this.skyNibbles;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.skyNibbles = nibbles;
|
|
+ }
|
|
+ // Tuinity end - rewrite light engine
|
|
+
|
|
// Paper start - Anti-Xray - Add world
|
|
@Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, World world) {
|
|
@@ -173,20 +198,17 @@ public class ProtoChunk implements IChunkAccess {
|
|
ChunkSection chunksection = this.a(j >> 4);
|
|
IBlockData iblockdata1 = chunksection.setType(i & 15, j & 15, k & 15, iblockdata);
|
|
|
|
- if (this.g.b(ChunkStatus.FEATURES) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) {
|
|
+ if ((com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (this.g.b(ChunkStatus.LIGHT) && this.isLit()) : (this.g.b(ChunkStatus.FEATURES))) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { // Tuinity - move block updates to only happen after lighting occurs
|
|
LightEngine lightengine = this.e();
|
|
|
|
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();
|
|
-
|
|
- HeightMap.Type heightmap_type;
|
|
+ // Tuinity - reduce iterator creation
|
|
|
|
- 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 +224,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/ProtoChunkExtension.java b/src/main/java/net/minecraft/server/ProtoChunkExtension.java
|
|
index 300cbb8b01d94e7eb0cded0c8e118103c416d4b6..60c57a2b5008b1bf4af65df09fdc0f301b8143ff 100644
|
|
--- a/src/main/java/net/minecraft/server/ProtoChunkExtension.java
|
|
+++ b/src/main/java/net/minecraft/server/ProtoChunkExtension.java
|
|
@@ -8,7 +8,29 @@ import javax.annotation.Nullable;
|
|
|
|
public class ProtoChunkExtension extends ProtoChunk {
|
|
|
|
- private final Chunk a;
|
|
+ private final Chunk a; public final Chunk getWrappedChunk() { return this.a; } // Tuinity - OBFHELPER
|
|
+
|
|
+ // Tuinity start - rewrite light engine
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() {
|
|
+ return this.getWrappedChunk().getBlockNibbles();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.getWrappedChunk().setBlockNibbles(nibbles);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() {
|
|
+ return this.getWrappedChunk().getSkyNibbles();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) {
|
|
+ this.getWrappedChunk().setSkyNibbles(nibbles);
|
|
+ }
|
|
+ // Tuinity end - rewrite light engine
|
|
|
|
public ProtoChunkExtension(Chunk chunk) {
|
|
super(chunk.getPos(), ChunkConverter.a, chunk.world); // Paper - Anti-Xray - Add parameter
|
|
diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
|
|
index 1751fb6934d9242e475c1a44b2a4a1ade6987766..1ffa213a819f9d39488ca3599f77e771de8081a5 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 1ebdf73cc927405bc536dc74a5118d2a086db0e5..cfa3ecb031b59ec677f016ecdea92d16436fb511 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 d64f7ad925e5f40740a58ceee0845ac2db5419f2..8b341c14e7082fc96a464f2386a3dedea31ec59c 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 3382d678e68e559b8d3cb9dced4fce24206cd38f..3b7894256dc8daa81be35f845cb5f8de02d7cb00 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 04256a95108b8182e8f808e856e0d2b62165e242..79a11d17a2822b192dec5981d0344ae689c3d385 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
@@ -25,8 +25,8 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
// Paper - nuke IOWorker
|
|
- private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap();
|
|
- protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> protected
|
|
+ private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap<Optional<R>> getDataBySection() { return this.c; } // Tuinity - OBFHELPER
|
|
+ protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); protected final LongLinkedOpenHashSet getDirtySections() { return this.d; } // Paper - private -> protected // Tuinity - OBFHELPER
|
|
private final Function<Runnable, Codec<R>> e;
|
|
private final Function<Runnable, R> f;
|
|
private final DataFixer g;
|
|
@@ -50,8 +50,42 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
|
|
|
|
}
|
|
|
|
- @Nullable
|
|
- protected Optional<R> c(long i) {
|
|
+ // Tuinity start - actually unload POI data
|
|
+ public void unloadData(long coordinate) {
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate);
|
|
+ this.writeDirtyData(chunkPos);
|
|
+
|
|
+ Long2ObjectMap<Optional<R>> data = this.getDataBySection();
|
|
+ int before = data.size();
|
|
+
|
|
+ for (int section = 0; section < 16; ++section) {
|
|
+ data.remove(SectionPosition.asLong(chunkPos.x, section, chunkPos.z));
|
|
+ }
|
|
+
|
|
+ if (before != data.size()) {
|
|
+ this.onUnload(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void onUnload(long coordinate) {}
|
|
+
|
|
+ public boolean isEmpty(long coordinate) {
|
|
+ Long2ObjectMap<Optional<R>> data = this.getDataBySection();
|
|
+ int x = MCUtil.getCoordinateX(coordinate);
|
|
+ int z = MCUtil.getCoordinateZ(coordinate);
|
|
+ for (int section = 0; section < 16; ++section) {
|
|
+ Optional<R> optional = data.get(SectionPosition.asLong(x, section, z));
|
|
+ if (optional != null && optional.orElse(null) != null) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+ // Tuinity end - actually unload POI data
|
|
+
|
|
+ @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);
|
|
}
|
|
|
|
@@ -150,6 +184,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
|
|
});
|
|
}
|
|
}
|
|
+ if (this instanceof VillagePlace) { ((VillagePlace)this).queueUnload(chunkcoordintpair.pair(), MinecraftServer.currentTickLong + 1); } // Tuinity - unload POI data
|
|
|
|
}
|
|
|
|
@@ -221,6 +256,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
|
|
return dynamic.get("DataVersion").asInt(1945);
|
|
}
|
|
|
|
+ public final void writeDirtyData(ChunkCoordIntPair chunkcoordintpair) { this.a(chunkcoordintpair); } // Tuinity - OBFHELPER
|
|
public void a(ChunkCoordIntPair chunkcoordintpair) {
|
|
if (!this.d.isEmpty()) {
|
|
for (int i = 0; i < 16; ++i) {
|
|
diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java
|
|
index f95925f1c5d091f1a129d0437bb6e175c6ac080f..0bb3ad0bffc04eba38cd827eaf5c63e8bf2aee93 100644
|
|
--- a/src/main/java/net/minecraft/server/SectionPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/SectionPosition.java
|
|
@@ -7,7 +7,7 @@ import java.util.stream.StreamSupport;
|
|
|
|
public class SectionPosition extends BaseBlockPosition {
|
|
|
|
- private SectionPosition(int i, int j, int k) {
|
|
+ public SectionPosition(int i, int j, int k) { // Tuinity - private -> public
|
|
super(i, j, k);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/SensorNearestBed.java b/src/main/java/net/minecraft/server/SensorNearestBed.java
|
|
index ad3609f2b884f64f1a1a449036cece49a46e933e..d3d28f97f9d2f969a182aec5e0947b6969d2939c 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 2e747158d48ab28ac1611990cc97aa4a9e30b30e..1de170b9fe6f2888da6dcf0151aaf1f865691c6a 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 f6568a54ab85bc3a682f6fbb19dda7a783625bbe..4005df5ef3dec956a54feff539db2e63c226059a 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 904a6d5ac61d2ac81f1057068383e9ab432852db..c8e43a9f2a23178fdef52375b7204b90b28ac20b 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 a367bbfde4fbfeca6d01dec49c05f5e185aab43a..794b33a13b7f11b973caf085b0bded9b2135a4d7 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 5f4dacf9c93c2495a07df2647fe0411f796da6af..0668d383db1f3a81d1053954d72678c7ac5aecec 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 f199368a6d78b0cd52f11ca2c8509d729b918852..2598ae3710d46c2cfd2be5d6be2a56e59ceef6ea 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 e41cb8613efc86499dfe3be36c9130ab6dc9b89e..c19ffb925a02d123da8a5c77186e6105422dccf7 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 5c789b25f1df2eae8ea8ceb4ba977ba336fe6d5e..3f942c632621e7ac7d3ac596aa408d687c3fa90d 100644
|
|
--- a/src/main/java/net/minecraft/server/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/TicketType.java
|
|
@@ -26,8 +26,21 @@ public class TicketType<T> {
|
|
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
|
|
public static final TicketType<ChunkCoordIntPair> PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
public static final TicketType<ChunkCoordIntPair> URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
|
|
- public static final TicketType<Long> DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper
|
|
+ public static final TicketType<Long> DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads
|
|
+ public static final TicketType<Long> REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail
|
|
+ public static final TicketType<ChunkCoordIntPair> LIGHT_UPDATE = a("light_update", Comparator.comparingLong(ChunkCoordIntPair::pair)); // Tuinity - ensure chunks stay loaded for lighting
|
|
|
|
+ // 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;
|
|
+ TicketType.LIGHT_UPDATE.delayUnloadViable = false; // Tuinity - ensure chunks stay loaded for lighting
|
|
+ }
|
|
+ // 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 2484293b12d9ec88b8a2570aa853a12f0d858193..1496c43fc9487caf6ddb3782a9d1c79ef6ca1e94 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 7f05587d42b7cdb09552277ec2e467f0edf06f10..5af554870bcf36e47aef43b966b141b9eda6c4d5 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 b926cebd053bef829517c9d9bbf1c609c23ca04a..99778f80c0f6c2e15cc3b8298dc0bbc5493f3e83 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlace.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlace.java
|
|
@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixer;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import it.unimi.dsi.fastutil.longs.Long2ByteMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Tuinity
|
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
import java.io.File;
|
|
@@ -22,8 +23,24 @@ import java.util.stream.Stream;
|
|
|
|
public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
|
|
- private final VillagePlace.a a = new VillagePlace.a();
|
|
- private final LongSet b = new LongOpenHashSet();
|
|
+ // Tuinity start - unload poi data
|
|
+ // the vanilla tracker needs to be replaced because it does not support level removes
|
|
+ private final com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D();
|
|
+ static final int POI_DATA_SOURCE = 7;
|
|
+ public static int convertBetweenLevels(final int level) {
|
|
+ return POI_DATA_SOURCE - level;
|
|
+ }
|
|
+
|
|
+ protected void updateDistanceTracking(long section) {
|
|
+ if (this.isSectionDistanceTrackerSource(section)) {
|
|
+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
|
|
+ } else {
|
|
+ this.villageDistanceTracker.removeSource(section);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - unload poi data
|
|
+
|
|
+ private final LongSet b = new LongOpenHashSet(); private final LongSet getLoadedChunks() { return this.b; } // Tuinity - OBFHELPER
|
|
|
|
private final WorldServer world; // Paper
|
|
|
|
@@ -34,9 +51,124 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
public VillagePlace(File file, DataFixer datafixer, boolean flag, WorldServer world) {
|
|
super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag);
|
|
this.world = world;
|
|
+ if (world == null) { throw new IllegalStateException("world must be non-null"); }// Tuinity - require non-null
|
|
// Paper end - add world parameter
|
|
}
|
|
|
|
+ // Tuinity start - actually unload POI data
|
|
+ private final java.util.TreeSet<QueuedUnload> queuedUnloads = new java.util.TreeSet<>();
|
|
+ private final Long2ObjectOpenHashMap<QueuedUnload> queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>();
|
|
+
|
|
+ static final class QueuedUnload implements Comparable<QueuedUnload> {
|
|
+
|
|
+ private final long unloadTick;
|
|
+ private final long coordinate;
|
|
+
|
|
+ public QueuedUnload(long unloadTick, long coordinate) {
|
|
+ this.unloadTick = unloadTick;
|
|
+ this.coordinate = coordinate;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int compareTo(QueuedUnload other) {
|
|
+ if (other.unloadTick == this.unloadTick) {
|
|
+ return Long.compare(this.coordinate, other.coordinate);
|
|
+ } else {
|
|
+ return Long.compare(this.unloadTick, other.unloadTick);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ int hash = 1;
|
|
+ hash = hash * 31 + Long.hashCode(this.unloadTick);
|
|
+ hash = hash * 31 + Long.hashCode(this.coordinate);
|
|
+ return hash;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean equals(Object obj) {
|
|
+ if (obj == null || obj.getClass() != QueuedUnload.class) {
|
|
+ return false;
|
|
+ }
|
|
+ QueuedUnload other = (QueuedUnload)obj;
|
|
+ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ long determineDelay(long coordinate) {
|
|
+ if (this.isEmpty(coordinate)) {
|
|
+ return 60 * 20;
|
|
+ } else {
|
|
+ return 5 * 20;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void queueUnload(long coordinate, long minTarget) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload queue");
|
|
+ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate);
|
|
+ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload);
|
|
+ if (existing != null) {
|
|
+ this.queuedUnloads.remove(existing);
|
|
+ }
|
|
+ this.queuedUnloads.add(unload);
|
|
+ }
|
|
+
|
|
+ public void dequeueUnload(long coordinate) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload dequeue");
|
|
+ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate);
|
|
+ if (unload != null) {
|
|
+ this.queuedUnloads.remove(unload);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void pollUnloads(BooleanSupplier canSleepForTick) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload");
|
|
+ long currentTick = MinecraftServer.currentTickLong;
|
|
+ ChunkProviderServer chunkProvider = this.world.getChunkProvider();
|
|
+ PlayerChunkMap playerChunkMap = chunkProvider.playerChunkMap;
|
|
+ // copied target determination from PlayerChunkMap
|
|
+ int target = Math.min(this.queuedUnloads.size() - 100, (int) (this.queuedUnloads.size() * PlayerChunkMap.UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
|
+ for (java.util.Iterator<QueuedUnload> iterator = this.queuedUnloads.iterator();
|
|
+ iterator.hasNext() && (this.queuedUnloads.size() > target || canSleepForTick.getAsBoolean());) {
|
|
+ QueuedUnload unload = iterator.next();
|
|
+ if (unload.unloadTick > currentTick) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ long coordinate = unload.coordinate;
|
|
+
|
|
+ iterator.remove();
|
|
+ this.queuedUnloadsByCoordinate.remove(coordinate);
|
|
+
|
|
+ if (playerChunkMap.getUnloadingPlayerChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate)) != null
|
|
+ || playerChunkMap.getUpdatingChunk(coordinate) != null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.unloadData(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void unloadData(long coordinate) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async unloading poi data");
|
|
+ super.unloadData(coordinate);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void onUnload(long coordinate) {
|
|
+ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload callback");
|
|
+ this.getLoadedChunks().remove(coordinate);
|
|
+ int chunkX = MCUtil.getCoordinateX(coordinate);
|
|
+ int chunkZ = MCUtil.getCoordinateZ(coordinate);
|
|
+ for (int section = 0; section < 16; ++section) {
|
|
+ long sectionPos = SectionPosition.asLong(chunkX, section, chunkZ);
|
|
+ this.updateDistanceTracking(sectionPos);
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - actually unload POI data
|
|
+
|
|
public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) {
|
|
((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype);
|
|
}
|
|
@@ -138,10 +270,11 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
}
|
|
|
|
public int a(SectionPosition sectionposition) {
|
|
- this.a.a();
|
|
- return this.a.c(sectionposition.s());
|
|
+ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking util
|
|
+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(MCUtil.getSectionKey(sectionposition))); // Tuinity - replace distance tracking util
|
|
}
|
|
|
|
+ private boolean isSectionDistanceTrackerSource(long section) { return this.f(section); } // Tuinity - OBFHELPER
|
|
private boolean f(long i) {
|
|
Optional<VillagePlaceSection> optional = this.c(i);
|
|
|
|
@@ -157,7 +290,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
super.a(booleansupplier);
|
|
} else {
|
|
//super.a(booleansupplier); // re-implement below
|
|
- while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) {
|
|
+ while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean() && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled
|
|
ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r();
|
|
|
|
NBTTagCompound data;
|
|
@@ -165,22 +298,27 @@ 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
|
|
}
|
|
}
|
|
+ // Tuinity start - unload POI data
|
|
+ if (!this.world.isSavingDisabled()) { // don't write to disk if saving is disabled
|
|
+ this.pollUnloads(booleansupplier);
|
|
+ }
|
|
+ // Tuinity end - unload POI data
|
|
// Paper end
|
|
- this.a.a();
|
|
+ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking until
|
|
}
|
|
|
|
@Override
|
|
protected void a(long i) {
|
|
super.a(i);
|
|
- this.a.b(i, this.a.b(i), false);
|
|
+ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util
|
|
}
|
|
|
|
@Override
|
|
protected void b(long i) {
|
|
- this.a.b(i, this.a.b(i), false);
|
|
+ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util
|
|
}
|
|
|
|
public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) {
|
|
@@ -245,7 +383,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
|
|
|
|
@Override
|
|
protected int b(long i) {
|
|
- return VillagePlace.this.f(i) ? 0 : 7;
|
|
+ return VillagePlace.this.f(i) ? 0 : 7; // Tuinity - unload poi data - diff on change, this specifies the source level to use for distance tracking
|
|
}
|
|
|
|
@Override
|
|
@@ -290,7 +428,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 +447,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 0b40c2f4dada7d8432e3f91e9cf206c2bda3b24b..6eaf9fc9cc93f79a497b07a3549d459ba66be849 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 77c66bc9952542d2444b402896a3d9f622ca2ff9..f43bc1f7d693d63c6bbdba976c048d2d0c8767b1 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 eb926b74e17fb2f88c1d6ce2fb546541f8e6e274..e3b72922e2dfad07f3452ec5ee2af379d968c52d 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,16 @@ 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 final VoxelShape simplify() { return this.c(); } // Tuinity - OBFHELPER
|
|
public VoxelShape c() {
|
|
VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()};
|
|
|
|
@@ -70,6 +77,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 3c29cb1452cde1308f630bfcb82876ef19057e8f..c14b7bd63e3917bc5f495655c40d8825a8d2062f 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 e841611bb7c36dffec44bb9e74a0a9657a113263..259605daabb18aedb15d56c78e6553ae2d22e13f 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 e21c747b6c39155c44bf30860681d67b0b29fb12..636bbbc42466cb54c300352f400464fe64cc2e79 100644
|
|
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
@@ -17,18 +17,101 @@ 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 boolean 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.isEmpty() && shapeCasted.aabb.voxelShapeIntersect(aabb)) {
|
|
+ list.add(shapeCasted.aabb);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ } 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;
|
|
+
|
|
+ boolean ret = false;
|
|
+
|
|
+ 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)) {
|
|
+ AxisAlignedBB box = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false);
|
|
+ if (!box.isEmpty()) {
|
|
+ list.add(box);
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ } else {
|
|
+ boolean ret = false;
|
|
+
|
|
+ java.util.List<AxisAlignedBB> boxes = shape.getBoundingBoxesRepresentation();
|
|
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
|
|
+ AxisAlignedBB box = boxes.get(i);
|
|
+ if (!box.isEmpty() && box.voxelShapeIntersect(aabb)) {
|
|
+ list.add(box);
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ if (!shapeCasted.isEmpty()) {
|
|
+ list.add(shapeCasted.aabb);
|
|
+ }
|
|
+ } else if (shape instanceof VoxelShapeArray) {
|
|
+ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape;
|
|
+
|
|
+ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) {
|
|
+ if (!boundingBox.isEmpty()) {
|
|
+ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ));
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ java.util.List<AxisAlignedBB> boxes = shape.getBoundingBoxesRepresentation();
|
|
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
|
|
+ AxisAlignedBB box = boxes.get(i);
|
|
+ if (!box.isEmpty()) {
|
|
+ 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 +150,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 +215,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 +411,52 @@ 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;
|
|
+ if (voxelshape == getFullUnoptimisedCube() || voxelshape == optimisedFullCube
|
|
+ || voxelshape1 == getFullUnoptimisedCube() || voxelshape1 == optimisedFullCube) {
|
|
+ return true;
|
|
+ }
|
|
+ boolean v1Empty = voxelshape == getEmptyShape();
|
|
+ boolean v2Empty = voxelshape1 == getEmptyShape();
|
|
+ if (v1Empty && v2Empty) {
|
|
+ return false;
|
|
+ }
|
|
+ if ((voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v1Empty) && (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v2Empty)) {
|
|
+ if (!v1Empty && !v2Empty && (voxelshape != voxelshape1)) {
|
|
+ AxisAlignedBB boundingBox1 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb;
|
|
+ AxisAlignedBB boundingBox2 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb;
|
|
+ // can call it here in some cases
|
|
+
|
|
+ // check overall bounding box
|
|
+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY);
|
|
+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY);
|
|
+ if (minY > MCUtil.COLLISION_EPSILON || maxY < (1 - MCUtil.COLLISION_EPSILON)) {
|
|
+ return false;
|
|
+ }
|
|
+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX);
|
|
+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX);
|
|
+ if (minX > MCUtil.COLLISION_EPSILON || maxX < (1 - MCUtil.COLLISION_EPSILON)) {
|
|
+ return false;
|
|
+ }
|
|
+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ);
|
|
+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ);
|
|
+ if (minZ > MCUtil.COLLISION_EPSILON || maxZ < (1 - MCUtil.COLLISION_EPSILON)) {
|
|
+ return false;
|
|
+ }
|
|
+ // fall through to full merge check
|
|
+ } else {
|
|
+ AxisAlignedBB boundingBox = v1Empty ? ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb : ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb;
|
|
+ // check if the bounding box encloses the full cube
|
|
+ return (boundingBox.minY <= MCUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - MCUtil.COLLISION_EPSILON)) &&
|
|
+ (boundingBox.minX <= MCUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - MCUtil.COLLISION_EPSILON)) &&
|
|
+ (boundingBox.minZ <= MCUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - MCUtil.COLLISION_EPSILON));
|
|
+ }
|
|
+ }
|
|
+ return b_rare(voxelshape, voxelshape1);
|
|
+ }
|
|
+ public static boolean b_rare(VoxelShape voxelshape, VoxelShape voxelshape1) {
|
|
+ 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 5d9d58411f2fad9d5da703f964d269b4a7c2b205..f0fdfd6891e59891e7370a2d682b65c647b28e9e 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 9ed21f434c5fb019b74dfe9ee0b802ccc5c07fd8..59abca0fd2330e1cdeda603d4146e4bb1afc3090 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 f011869880fedae4b69e505491e8bdbc5f51dfba..0d10d317cd0b60fc0866ae505c7fd71fa886c48b 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldBorder.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
@@ -47,11 +47,59 @@ 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 almost colliding with the world border
|
|
+ // for clear collisions, this rets false
|
|
+ public final boolean isAlmostCollidingOnBorder(AxisAlignedBB boundingBox) {
|
|
+ return this.isAlmostCollidingOnBorder(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
|
|
+ }
|
|
+
|
|
+ public final boolean isAlmostCollidingOnBorder(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) {
|
|
+ double borderMinX = this.getMinX();
|
|
+ double borderMaxX = this.getMaxX();
|
|
+
|
|
+ double borderMinZ = this.getMinZ();
|
|
+ double borderMaxZ = this.getMaxZ();
|
|
+
|
|
+ return
|
|
+ // Not intersecting if we're smaller
|
|
+ !AxisAlignedBB.voxelShapeIntersect(
|
|
+ boxMinX + MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + MCUtil.COLLISION_EPSILON,
|
|
+ boxMaxX - MCUtil.COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - MCUtil.COLLISION_EPSILON,
|
|
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
|
|
+ )
|
|
+ &&
|
|
+
|
|
+ // Are intersecting if we're larger
|
|
+ AxisAlignedBB.voxelShapeIntersect(
|
|
+ boxMinX - MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - MCUtil.COLLISION_EPSILON,
|
|
+ boxMaxX + MCUtil.COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + MCUtil.COLLISION_EPSILON,
|
|
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
|
|
+ )
|
|
+ ;
|
|
+ }
|
|
+
|
|
+ public final boolean isCollidingWithBorderEdge(AxisAlignedBB boundingBox) {
|
|
+ return this.isCollidingWithBorderEdge(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
|
|
+ }
|
|
+
|
|
+ public final boolean isCollidingWithBorderEdge(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) {
|
|
+ double borderMinX = this.getMinX() + MCUtil.COLLISION_EPSILON;
|
|
+ double borderMaxX = this.getMaxX() - MCUtil.COLLISION_EPSILON;
|
|
+
|
|
+ double borderMinZ = this.getMinZ() + MCUtil.COLLISION_EPSILON;
|
|
+ double borderMaxZ = this.getMaxZ() - MCUtil.COLLISION_EPSILON;
|
|
+
|
|
+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ;
|
|
+ }
|
|
+ // 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 +115,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 5b0b6edfa790918e56399ff6c83f3feb6e5aca49..800471d450ddcb8d291dc72e93c3d8251cc63248 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,303 @@ 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,
|
|
+ boolean collidesWithUnloaded) {
|
|
+ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, null);
|
|
+ }
|
|
+
|
|
+ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb,
|
|
+ boolean loadChunks, boolean collidesWithUnloaded,
|
|
+ java.util.function.BiPredicate<IBlockData, BlockPosition> predicate) {
|
|
+ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, predicate);
|
|
+ }
|
|
+
|
|
+ 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, true)
|
|
+ || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null);
|
|
+ }
|
|
+
|
|
+ // returns whether any collisions were detected
|
|
+ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<AxisAlignedBB> list,
|
|
+ boolean loadChunks, boolean collidesWithUnloaded, boolean checkOnly,
|
|
+ java.util.function.BiPredicate<IBlockData, BlockPosition> predicate) {
|
|
+ boolean ret = false;
|
|
+
|
|
+ if (entity != null) {
|
|
+ if (this.getWorldBorder().isAlmostCollidingOnBorder(axisalignedbb)) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list);
|
|
+ ret = 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 = null;
|
|
+
|
|
+ // special cases:
|
|
+ if (minBlockY > 255 || maxBlockY < 0) {
|
|
+ // no point in checking
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ 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 (collidesWithUnloaded) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ));
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ int minXIterate;
|
|
+ int maxXIterate;
|
|
+ int minZIterate;
|
|
+ int maxZIterate;
|
|
+
|
|
+ boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
|
|
+ if (!sectionHasSpecial && currChunkX == minChunkX) {
|
|
+ minXIterate = minX + 1;
|
|
+ } else {
|
|
+ minXIterate = minX;
|
|
+ }
|
|
+ if (!sectionHasSpecial && currChunkX == maxChunkX) {
|
|
+ maxXIterate = maxX - 1;
|
|
+ } else {
|
|
+ maxXIterate = maxX;
|
|
+ }
|
|
+
|
|
+ if (!sectionHasSpecial && currChunkZ == minChunkZ) {
|
|
+ minZIterate = minZ + 1;
|
|
+ } else {
|
|
+ minZIterate = minZ;
|
|
+ }
|
|
+ if (!sectionHasSpecial && currChunkZ == maxChunkZ) {
|
|
+ maxZIterate = maxZ - 1;
|
|
+ } else {
|
|
+ maxZIterate = maxZ;
|
|
+ }
|
|
+
|
|
+ DataPaletteBlock<IBlockData> blocks = section.blockIds;
|
|
+
|
|
+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
|
|
+ block_search_loop:
|
|
+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
|
|
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
|
|
+ long blockInfo = section.getKnownBlockInfo(localBlockIndex);
|
|
+ switch ((int)blockInfo) {
|
|
+ case (int)ChunkSection.KNOWN_EMPTY_BLOCK: {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ case (int)ChunkSection.KNOWN_FULL_BLOCK: {
|
|
+ AxisAlignedBB box = new AxisAlignedBB(
|
|
+ currX | chunkXGlobalPos, currY, currZ | chunkZGlobalPos,
|
|
+ (currX | chunkXGlobalPos) + 1, currY + 1, (currZ | chunkZGlobalPos) + 1,
|
|
+ false
|
|
+ );
|
|
+ if (predicate != null) {
|
|
+ if (!box.voxelShapeIntersect(axisalignedbb)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ // fall through to get the block for the predicate
|
|
+ } else {
|
|
+ if (box.voxelShapeIntersect(axisalignedbb)) {
|
|
+ if (checkOnly) {
|
|
+ return true;
|
|
+ } else {
|
|
+ list.add(box);
|
|
+ ret = true;
|
|
+ }
|
|
+ }
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+ }
|
|
+ default: {
|
|
+ int blockX = currX | chunkXGlobalPos;
|
|
+ int blockY = currY;
|
|
+ int blockZ = currZ | chunkZGlobalPos;
|
|
+
|
|
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
|
|
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
|
|
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
|
|
+ if (edgeCount == 3 || (edgeCount != 0 && blockInfo != ChunkSection.KNOWN_SPECIAL_BLOCK)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+
|
|
+ IBlockData blockData = blocks.rawGet(localBlockIndex);
|
|
+
|
|
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
|
|
+ mutablePos.setValues(blockX, blockY, blockZ);
|
|
+ if (collisionShape == null) {
|
|
+ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity);
|
|
+ }
|
|
+ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape);
|
|
+ if (voxelshape2 != VoxelShapes.getEmptyShape()) {
|
|
+ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ);
|
|
+
|
|
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
|
|
+ continue block_search_loop;
|
|
+ }
|
|
+
|
|
+ if (checkOnly) {
|
|
+ if (voxelshape3.intersects(axisalignedbb)) {
|
|
+ return true;
|
|
+ }
|
|
+ } else {
|
|
+ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ 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)) {
|
|
+ if (!otherEntity.getBoundingBox().isEmpty()) {
|
|
+ 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, true, false, null);
|
|
+ this.getEntityHardCollisions(entity, axisalignedbb, null, list);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean getCubes(AxisAlignedBB axisalignedbb) {
|
|
+ return !this.hasAnyCollisions(null, axisalignedbb);
|
|
+ }
|
|
+
|
|
+ @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, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate);
|
|
+ }
|
|
+ // Tuinity end - optimise collision
|
|
+
|
|
// CraftBukkit start
|
|
@Override
|
|
protected TileEntity getTileEntity(BlockPosition pos, boolean validate) {
|
|
@@ -318,6 +714,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 +871,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 +880,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 +896,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
|
|
@@ -514,6 +917,15 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
gameprofilerfiller.enter("checkDespawn");
|
|
if (!entity.dead) {
|
|
entity.checkDespawn();
|
|
+ // Tuinity start - optimise notify()
|
|
+ if (entity.inChunk && entity.valid) {
|
|
+ if (this.getChunkProvider().isInEntityTickingChunk(entity)) {
|
|
+ this.updateNavigatorsInRegion(entity);
|
|
+ }
|
|
+ } else {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
}
|
|
|
|
gameprofilerfiller.exit();
|
|
@@ -534,14 +946,22 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
gameprofilerfiller.enter("remove");
|
|
if (entity.dead) {
|
|
this.removeEntityFromChunk(entity);
|
|
- objectiterator.remove();
|
|
+ this.entitiesById.remove(entity.getId()); // Tuinity
|
|
this.unregisterEntity(entity);
|
|
+ } else if (entity.inChunk && entity.valid) { // Tuinity start - optimise notify()
|
|
+ if (this.getChunkProvider().isInEntityTickingChunk(entity)) {
|
|
+ this.updateNavigatorsInRegion(entity);
|
|
+ }
|
|
+ } else {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
}
|
|
+ // Tuinity end - optimise notify()
|
|
|
|
gameprofilerfiller.exit();
|
|
}
|
|
timings.entityTick.stopTiming(); // Spigot
|
|
|
|
+ objectiterator.finishedIterating(); // Tuinity
|
|
this.tickingEntities = false;
|
|
// Paper start
|
|
for (java.lang.Runnable run : this.afterEntityTickingTasks) {
|
|
@@ -553,7 +973,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
this.afterEntityTickingTasks.clear();
|
|
// Paper end
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
|
|
Entity entity2;
|
|
|
|
@@ -563,7 +983,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
|
|
timings.tickEntities.stopTiming(); // Spigot
|
|
- this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
+ // Tuinity - replace logic
|
|
this.tickBlockEntities();
|
|
}
|
|
|
|
@@ -809,7 +1229,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 +1301,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) {
|
|
@@ -920,6 +1364,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
int i = MathHelper.floor(entity.locX() / 16.0D);
|
|
int j = Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior
|
|
int k = MathHelper.floor(entity.locZ() / 16.0D);
|
|
+ // Tuinity start
|
|
+ int oldRegionX = entity.chunkX >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int oldRegionZ = entity.chunkZ >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int newRegionX = i >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ int newRegionZ = k >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT;
|
|
+ // Tuinity end
|
|
|
|
if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) {
|
|
// Paper start - remove entity if its in a chunk more correctly.
|
|
@@ -929,6 +1379,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start
|
|
+ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) {
|
|
+ this.removeNavigatorsFromData(entity);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) {
|
|
this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY);
|
|
}
|
|
@@ -942,6 +1398,11 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
} else {
|
|
this.getChunkAt(i, k).a(entity);
|
|
}
|
|
+ // Tuinity start
|
|
+ if (entity.inChunk && (oldRegionX != newRegionX || oldRegionZ != newRegionZ)) {
|
|
+ this.addNavigatorsIfPathingToRegion(entity);
|
|
+ }
|
|
+ // Tuinity end
|
|
}
|
|
|
|
this.getMethodProfiler().exit();
|
|
@@ -1297,7 +1758,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!")));
|
|
}
|
|
|
|
@@ -1325,6 +1786,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
|
|
@@ -1391,17 +1853,108 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
this.getScoreboard().a(entity);
|
|
// CraftBukkit start - SPIGOT-5278
|
|
if (entity instanceof EntityDrowned) {
|
|
- this.navigators.remove(((EntityDrowned) entity).navigationWater);
|
|
- this.navigators.remove(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity start
|
|
+ this.navigators.remove(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationWater);
|
|
+ this.navigators.remove(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationLand);
|
|
+ // Tuinity end
|
|
} else
|
|
// CraftBukkit end
|
|
if (entity instanceof EntityInsentient) {
|
|
- this.navigators.remove(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity start
|
|
+ this.navigators.remove(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.remove(((EntityInsentient) entity).getNavigation());
|
|
+ // Tuinity end
|
|
}
|
|
new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
|
|
+ this.removeNavigatorsFromData(entity); // Tuinity - optimise notify()
|
|
entity.valid = false; // CraftBukkit
|
|
}
|
|
|
|
+ // Tuinity start - optimise notify()
|
|
+ void removeNavigatorsIfNotPathingFromRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (!((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (!((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (!((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void removeNavigatorsFromData(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void addNavigatorsIfPathingToRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void updateNavigatorsInRegion(Entity entity) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section =
|
|
+ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
|
|
+ if (section != null) {
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ // Copied from above
|
|
+ if (entity instanceof EntityDrowned) {
|
|
+ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationWater);
|
|
+ } else {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationWater);
|
|
+ }
|
|
+ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityDrowned)entity).navigationLand);
|
|
+ } else {
|
|
+ navigators.remove(((EntityDrowned)entity).navigationLand);
|
|
+ }
|
|
+ } else if (entity instanceof EntityInsentient) {
|
|
+ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
|
|
+ navigators.add(((EntityInsentient)entity).getNavigation());
|
|
+ } else {
|
|
+ navigators.remove(((EntityInsentient)entity).getNavigation());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+
|
|
private void registerEntity(Entity entity) {
|
|
org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
|
|
// Paper start - don't double enqueue entity registration
|
|
@@ -1412,7 +1965,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
|
|
@@ -1420,6 +1973,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;
|
|
@@ -1428,6 +1982,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
EntityComplexPart entitycomplexpart = aentitycomplexpart[j];
|
|
|
|
this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart);
|
|
+ this.entitiesForIteration.add(entitycomplexpart); // Tuinity
|
|
}
|
|
}
|
|
|
|
@@ -1452,12 +2007,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
|
|
@@ -1473,7 +2032,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);
|
|
@@ -1569,13 +2128,32 @@ 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)) {
|
|
+ // Tuinity start - optimise notify()
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region<PlayerChunkMap.RegionData> region = this.getChunkProvider().playerChunkMap.dataRegionManager.getRegion(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ if (region == null) {
|
|
+ return;
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
|
|
- Iterator iterator = this.navigators.iterator();
|
|
+ // Tuinity start
|
|
+ // Tuinity start - optimise notify()
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData>> sectionIterator = null;
|
|
+ try {
|
|
+ for (sectionIterator = region.getSections(); sectionIterator.hasNext();) {
|
|
+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<PlayerChunkMap.RegionData> section = sectionIterator.next();
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<NavigationAbstract> navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS);
|
|
+ if (navigators == null) {
|
|
+ continue;
|
|
+ }
|
|
+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigators.iterator();
|
|
+ // Tuinity end - optimise notify()
|
|
+ try { // Tuinity end
|
|
|
|
while (iterator.hasNext()) {
|
|
NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next();
|
|
@@ -1583,7 +2161,21 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
if (!navigationabstract.i()) {
|
|
navigationabstract.b(blockposition);
|
|
}
|
|
- }
|
|
+ // Tuinity start - optimise notify()
|
|
+ if (!navigationabstract.isViableForPathRecalculationChecking()) {
|
|
+ navigators.remove(navigationabstract);
|
|
+ }
|
|
+ // Tuinity end - optimise notify()
|
|
+ }
|
|
+ } finally { // Tuinity start
|
|
+ iterator.finishedIterating();
|
|
+ } // Tuinity end
|
|
+ } // Tuinity start - optimise notify()
|
|
+ } finally {
|
|
+ if (sectionIterator != null) {
|
|
+ sectionIterator.finishedIterating();
|
|
+ }
|
|
+ } // Tuinity end - optimise notify()
|
|
|
|
this.tickingEntities = wasTicking; // Paper
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java
|
|
index 5ccdc0b87b922724c3dd3085860c55d4959ca0b4..888dae2d5ee8a71e83dd24e5f3c6bc8513016f9d 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/CraftCrashReport.java b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java
|
|
index 7511e38130f38703164395a670f12d1af648ff04..e602efcb3fad390bb6bff1055e782bba909d7694 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java
|
|
@@ -37,7 +37,7 @@ public class CraftCrashReport implements CrashReportCallable<Object> {
|
|
value.append("\n Force Loaded Chunks: {");
|
|
for (World world : Bukkit.getWorlds()) {
|
|
value.append(' ').append(world.getName()).append(": {");
|
|
- for (Map.Entry<Plugin, Collection<Chunk>> entry : world.getPluginChunkTickets().entrySet()) {
|
|
+ for (Map.Entry<Plugin, Collection<net.minecraft.server.ChunkCoordIntPair>> entry : ((CraftWorld)world).getPluginChunkTicketsCoordinates().entrySet()) { // Tuinity - do not load chunks in crash reports
|
|
value.append(' ').append(entry.getKey().getDescription().getFullName()).append(": ").append(Integer.toString(entry.getValue().size())).append(',');
|
|
}
|
|
value.append("},");
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index dc7de2b59ec5ca3e5fba34dbb2aa2e6aed8f95cb..a383ba5e897101a3da1544c877148b43be7a6319 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");
|
|
@@ -861,6 +861,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);
|
|
@@ -895,6 +896,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
|
|
@@ -1858,7 +1860,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
|
|
@@ -2280,6 +2285,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 b71316cce3bdbf3485be456f0260c6b3463cff8e..a69b38f293723a58691b2777d170f9cc146d1148 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;
|
|
@@ -717,6 +719,30 @@ public class CraftWorld implements World {
|
|
return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build()));
|
|
}
|
|
|
|
+ // Tuinity start - don't load chunks for crash reports
|
|
+ public Map<Plugin, Collection<ChunkCoordIntPair>> getPluginChunkTicketsCoordinates() {
|
|
+ // Copied from above
|
|
+ Map<Plugin, ImmutableList.Builder<ChunkCoordIntPair>> ret = new HashMap<>();
|
|
+ ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager;
|
|
+
|
|
+ for (Long2ObjectMap.Entry<ArraySetSorted<Ticket<?>>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) {
|
|
+ long chunkKey = chunkTickets.getLongKey();
|
|
+ ArraySetSorted<Ticket<?>> tickets = chunkTickets.getValue();
|
|
+
|
|
+ ChunkCoordIntPair chunk = new ChunkCoordIntPair(chunkKey);
|
|
+ for (Ticket<?> ticket : tickets) {
|
|
+ if (ticket.getTicketType() != TicketType.PLUGIN_TICKET) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret.computeIfAbsent((Plugin) ticket.identifier, (key) -> ImmutableList.builder()).add(chunk);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build()));
|
|
+ }
|
|
+ // Tuinity end - don't load chunks for crash reports
|
|
+
|
|
@Override
|
|
public boolean isChunkForceLoaded(int x, int z) {
|
|
return getHandle().getForceLoadedChunks().contains(ChunkCoordIntPair.pair(x, z));
|
|
@@ -2553,7 +2579,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 010f702ea44d2146b0745b2b4d21f948d16cc424..d2ec9f7f105a36a1077ac0df56b1abb4786a565b 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 475dc1aa2cba77c13033938e719a66707f358914..a6d849facba1526ae2a2b7f3fb9a140d0b50289c 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
@@ -508,27 +508,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) {
|
|
@@ -562,6 +542,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 fd32d1450a6a2ede3405be7d31697cd16957f553..c38e514b004a4684026d5a89c606399a4fd7efe1 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 6fa2e271f7f01cd0bf247e2071fa33bd8c5c6cbe..3a9491e9495bec93d5556bd8c09196ea117161d5 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 f72c13bedaa6fa45e26f5dcad564835bdd4af61f..7c0d90552eeb6de7dab174e2ba4acfc89a7b3db0 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 674096cab190d62622f9947853b056f57d43a2a5..001b1e5197eaa51bfff9031aa6c69876c9a47960 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 9f7d2ef932ab41cef5d3d0736d20a7c7e4a2c888..51e9c54cddf4b28ba3d3d892322c487774bdab70 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 513c1041c34ebb3ac1775674a3f4526693759c08..4d310908489953d6d061b2358f614142edacb62e 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, "------------------------------" );
|
|
//
|