This commit is contained in:
peaches94
2022-07-17 15:03:03 -05:00
parent 31386cdae1
commit 3e539e3144
14 changed files with 2103 additions and 0 deletions

21
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: CI
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '18'
cache: 'gradle'
- name: Patch and build
run: |
git config --global user.email "no-reply@github.com"
git config --global user.name "Github Actions"
./gradlew applyPatches --stacktrace
./gradlew build --stacktrace

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.gradle/
.idea/
petal-api/
petal-server/
build-data/
build/
run/

71
build.gradle.kts Normal file
View File

@@ -0,0 +1,71 @@
import io.papermc.paperweight.util.constants.PAPERCLIP_CONFIG
import java.nio.charset.StandardCharsets
plugins {
java
id("com.github.johnrengelman.shadow") version "7.1.2" apply false
id("io.papermc.paperweight.patcher") version "1.3.8"
}
repositories {
mavenCentral()
maven("https://papermc.io/repo/repository/maven-public/") {
content { onlyForConfigurations(PAPERCLIP_CONFIG) }
}
}
dependencies {
remapper("net.fabricmc:tiny-remapper:0.8.2:fat")
decompiler("net.minecraftforge:forgeflower:1.5.605.7")
paperclip("io.papermc:paperclip:3.0.2")
}
subprojects {
apply(plugin = "java")
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
tasks.withType<JavaCompile>().configureEach {
options.encoding = StandardCharsets.UTF_8.name()
options.release.set(17)
}
repositories {
mavenLocal()
mavenCentral()
maven("https://oss.sonatype.org/content/groups/public/")
maven("https://papermc.io/repo/repository/maven-public/")
maven("https://ci.emc.gs/nexus/content/groups/aikar/")
maven("https://repo.aikar.co/content/groups/aikar")
maven("https://repo.md-5.net/content/repositories/releases/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://jitpack.io")
}
}
paperweight {
serverProject.set(project(":petal-server"))
remapRepo.set("https://maven.fabricmc.net/")
decompileRepo.set("https://files.minecraftforge.net/maven/")
useStandardUpstream("purpur") {
url.set(github("PurpurMC", "Purpur"))
ref.set(providers.gradleProperty("purpurRef"))
withStandardPatcher {
apiSourceDirPath.set("Purpur-API")
serverSourceDirPath.set("Purpur-Server")
apiPatchDir.set(layout.projectDirectory.dir("patches/api"))
serverPatchDir.set(layout.projectDirectory.dir("patches/server"))
apiOutputDir.set(layout.projectDirectory.dir("petal-api"))
serverOutputDir.set(layout.projectDirectory.dir("petal-server"))
}
}
}

9
gradle.properties Normal file
View File

@@ -0,0 +1,9 @@
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.vfs.watch=false
group=host.bloom.petal
version=1.19-R0.1-SNAPSHOT
mcVersion=1.19
packageVersion=1_19_R1
purpurRef=8aa5c259c5cb5ebdc0b8e081834aef5aeee0dac6

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

240
gradlew vendored Executable file
View File

@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

91
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,91 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,186 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
Date: Tue, 14 Jun 2022 21:20:31 -0500
Subject: [PATCH] conf: brand server to petal
diff --git a/build.gradle.kts b/build.gradle.kts
index fd3805da1f276c76a2e814f755f6755245ec2ff4..92842974ebdaeb4f44fd3966088920d4539edd35 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,7 +9,7 @@ plugins {
}
dependencies {
- implementation(project(":purpur-api")) // Purpur
+ implementation(project(":petal-api")) // Purpur // petal
// Pufferfish start
implementation("io.papermc.paper:paper-mojangapi:1.19-R0.1-SNAPSHOT") {
exclude("io.papermc.paper", "paper-api")
@@ -87,7 +87,7 @@ tasks.jar {
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
- "Implementation-Version" to "git-Purpur-$implementationVersion",// Purpur
+ "Implementation-Version" to "git-petal-$implementationVersion",// Purpur // petal
"Implementation-Vendor" to date, // Paper
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
index acd95cf1dc7f009b63e44e4404e1736283fd458e..f10c8cde1bf11db618fcd128aa60c3b524746360 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("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Purpur
+ Metrics metrics = new Metrics("petal", serverUUID, logFailedRequests, Bukkit.getLogger()); // Purpur // petal
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() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur
- metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Purpur
+ metrics.addCustomChart(new Metrics.SimplePie("petal_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Purpur // petal
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
index fba5dbdb7bcbb55400ef18342c9b54612972a718..3f9aa4292ff45c6b6af0ddfeecb645f813ec5eca 100644
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
@@ -33,8 +33,8 @@ public class PaperVersionFetcher implements VersionFetcher {
@Nonnull
@Override
public Component getVersionMessage(@Nonnull String serverVersion) {
- String[] parts = serverVersion.substring("git-Purpur-".length()).split("[-\\s]"); // Purpur
- final Component updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", "ver/" + getMinecraftVersion(), parts[0]); // Purpur
+ String[] parts = serverVersion.substring("git-petal-".length()).split("[-\\s]"); // Purpur // petal
+ final Component updateMessage = getUpdateStatusMessage("Bloom-host/petal", "ver/" + getMinecraftVersion(), parts[0]); // Purpur // petal
final Component history = getHistory();
return history != null ? Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), history, updateMessage) : updateMessage; // Purpur
@@ -58,6 +58,8 @@ public class PaperVersionFetcher implements VersionFetcher {
private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) {
//int distance; // Purpur - use field
+ // petal start
+ /*
try {
int jenkinsBuild = Integer.parseInt(versionInfo);
distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion());
@@ -65,6 +67,10 @@ public class PaperVersionFetcher implements VersionFetcher {
versionInfo = versionInfo.replace("\"", "");
distance = fetchDistanceFromGitHub(repo, branch, versionInfo);
}
+ */
+ versionInfo = versionInfo.replace("\"", "");
+ distance = fetchDistanceFromGitHub(repo, branch, versionInfo);
+ // petal end
switch (distance) {
case -1:
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 2ece154b5e7daaa3e0b128145fc5e1d452125f72..36854f0c3be5b17b771d73c3863e45adf3267933 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -913,7 +913,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
shutdownThread = Thread.currentThread();
org.spigotmc.WatchdogThread.doStop(); // Paper
if (!isSameThread()) {
- MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PURPUR)"); // Purpur
+ MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PETAL)"); // Purpur // petal
while (this.getRunningThread().isAlive()) {
this.getRunningThread().stop();
try {
@@ -1682,7 +1682,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@DontObfuscate
public String getServerModName() {
- return org.purpurmc.purpur.PurpurConfig.serverModName; // Purpur - Purpur > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+ // petal start
+ final String purpurModName = org.purpurmc.purpur.PurpurConfig.serverModName; // Purpur - Purpur > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+
+ return switch (purpurModName) {
+ case "Purpur" -> "petal";
+ default -> purpurModName;
+ };
+ // petal end
}
public SystemReport fillSystemReport(SystemReport details) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 7208236ee3c604620fd0d0afe49215320e45e24b..62f3ee7b1b12239319f00882b854fcbe3a8d5ed8 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -246,7 +246,7 @@ import javax.annotation.Nullable; // Paper
import javax.annotation.Nonnull; // Paper
public final class CraftServer implements Server {
- private final String serverName = "Purpur"; // Paper // Purpur
+ private final String serverName = "petal"; // Paper // Purpur // petal
private final String serverVersion;
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index fb87620c742ff7912f5e8ccd2a7930dd605576d9..8c0215954360adf6c51bd71994273e8b6b8e07a3 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/org.purpurmc.purpur/purpur-api/pom.properties"); // Purpur
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/host.bloom.petal/petal-api/pom.properties"); // Purpur // petal
Properties properties = new Properties();
if (stream != null) {
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index 4a88fbee3566ba54b615745a2e4801691f494557..d89aecd421b83d539d29fd5e87c1bf9a992ab2dc 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -155,14 +155,14 @@ public class WatchdogThread extends Thread
if (isLongTimeout) {
// Paper end
log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug." ); // Paper // Purpur
+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a petal bug." ); // Paper // Purpur // petal
log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" );
log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" );
log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" );
log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" );
- log.log( Level.SEVERE, "If you are unsure or still think this is a Purpur bug, please report this to https://github.com/PurpurMC/Purpur/issues" ); // Purpur
+ log.log( Level.SEVERE, "If you are unsure or still think this is a petal bug, please report this to https://github.com/Bloom-host/petal/issues" ); // Purpur // petal
log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" );
- log.log( Level.SEVERE, "Purpur version: " + Bukkit.getServer().getVersion() ); // Purpur
+ log.log( Level.SEVERE, "petal version: " + Bukkit.getServer().getVersion() ); // Purpur // petal
//
if ( net.minecraft.world.level.Level.lastPhysicsProblem != null )
{
@@ -185,12 +185,12 @@ public class WatchdogThread extends Thread
// Paper end
} else
{
- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur
+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PETAL - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur // petal
log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
}
// Paper end - Different message for short timeout
log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Purpur!):" ); // Paper // Purpur
+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to petal!):" ); // Paper // Purpur // petal
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
this.dumpTickingInfo(); // Paper - log detailed tick information
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log );
@@ -206,7 +206,7 @@ public class WatchdogThread extends Thread
WatchdogThread.dumpThread( thread, log );
}
} else {
- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH ---"); // Purpur
+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PETAL - THIS IS NOT A BUG OR A CRASH ---"); // Purpur // petal
}
log.log( Level.SEVERE, "------------------------------" );

View File

@@ -0,0 +1,975 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
Date: Sun, 26 Jun 2022 16:51:37 -0500
Subject: [PATCH] feat: async path processing
diff --git a/src/main/java/host/bloom/pathfinding/AsyncPath.java b/src/main/java/host/bloom/pathfinding/AsyncPath.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eeced76de761
--- /dev/null
+++ b/src/main/java/host/bloom/pathfinding/AsyncPath.java
@@ -0,0 +1,280 @@
+package host.bloom.pathfinding;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.pathfinder.Node;
+import net.minecraft.world.level.pathfinder.Path;
+import net.minecraft.world.phys.Vec3;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+
+/**
+ * i'll be using this to represent a path that not be processed yet!
+ */
+public class AsyncPath extends Path {
+
+ /**
+ * marks whether this async path has been processed
+ */
+ private volatile boolean processed = false;
+
+ /**
+ * a future representing the processing state of this path
+ */
+ private final @NotNull CompletableFuture<Path> processingFuture;
+
+ /**
+ * a list of positions that this path could path towards
+ */
+ private final Set<BlockPos> positions;
+
+ /**
+ * the supplier of the real processed path
+ */
+ private final Supplier<Path> pathSupplier;
+
+ /*
+ * Processed values
+ */
+
+ /**
+ * this is a reference to the nodes list in the parent `Path` object
+ */
+ private final List<Node> nodes;
+ /**
+ * the block we're trying to path to
+ *
+ * while processing, we have no idea where this is so consumers of `Path` should check that the path is processed before checking the target block
+ */
+ private @Nullable BlockPos target;
+ /**
+ * how far we are to the target
+ *
+ * while processing, the target could be anywhere but theoretically we're always "close" to a theoretical target so default is 0
+ */
+ private float distToTarget = 0;
+ /**
+ * whether we can reach the target
+ *
+ * while processing we can always theoretically reach the target so default is true
+ */
+ private boolean canReach = true;
+
+ public AsyncPath(@NotNull List<Node> emptyNodeList, @NotNull Set<BlockPos> positions, @NotNull Supplier<Path> pathSupplier) {
+ //noinspection ConstantConditions
+ super(emptyNodeList, null, false);
+
+ this.nodes = emptyNodeList;
+ this.positions = positions;
+ this.pathSupplier = pathSupplier;
+
+ this.processingFuture = AsyncPathProcessor.queue(this).thenApply((unused) -> this);
+ }
+
+ @Override
+ public boolean isProcessed() {
+ return this.processed;
+ }
+
+ /**
+ * returns the future representing the processing state of this path
+ * @return a future
+ */
+ public @NotNull CompletableFuture<Path> getProcessingFuture() {
+ return this.processingFuture;
+ }
+
+ /**
+ * an easy way to check if this processing path is the same as an attempted new path
+ *
+ * @param positions - the positions to compare against
+ * @return true if we are processing the same positions
+ */
+ public boolean hasSameProcessingPositions(final Set<BlockPos> positions) {
+ if (this.positions.size() != positions.size()) {
+ return false;
+ }
+
+ return this.positions.containsAll(positions);
+ }
+
+ /**
+ * starts processing this path
+ */
+ public synchronized void process() {
+ if (this.processed) {
+ return;
+ }
+
+ final Path bestPath = this.pathSupplier.get();
+
+ this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path
+ this.target = bestPath.getTarget();
+ this.distToTarget = bestPath.getDistToTarget();
+ this.canReach = bestPath.canReach();
+
+ this.processed = true;
+ }
+
+ /**
+ * if this path is accessed while it hasn't processed, just process it in-place
+ */
+ private void checkProcessed() {
+ if (!this.processed) {
+ this.process();
+ }
+ }
+
+ /*
+ * overrides we need for final fields that we cannot modify after processing
+ */
+
+ @Override
+ public @NotNull BlockPos getTarget() {
+ this.checkProcessed();
+
+ return this.target;
+ }
+
+ @Override
+ public float getDistToTarget() {
+ this.checkProcessed();
+
+ return this.distToTarget;
+ }
+
+ @Override
+ public boolean canReach() {
+ this.checkProcessed();
+
+ return this.canReach;
+ }
+
+ /*
+ * overrides to ensure we're processed first
+ */
+
+ @Override
+ public boolean isDone() {
+ return this.isProcessed() && super.isDone();
+ }
+
+ @Override
+ public void advance() {
+ this.checkProcessed();
+
+ super.advance();
+ }
+
+ @Override
+ public boolean notStarted() {
+ this.checkProcessed();
+
+ return super.notStarted();
+ }
+
+ @Nullable
+ @Override
+ public Node getEndNode() {
+ this.checkProcessed();
+
+ return super.getEndNode();
+ }
+
+ @Override
+ public Node getNode(int index) {
+ this.checkProcessed();
+
+ return super.getNode(index);
+ }
+
+ @Override
+ public void truncateNodes(int length) {
+ this.checkProcessed();
+
+ super.truncateNodes(length);
+ }
+
+ @Override
+ public void replaceNode(int index, Node node) {
+ this.checkProcessed();
+
+ super.replaceNode(index, node);
+ }
+
+ @Override
+ public int getNodeCount() {
+ this.checkProcessed();
+
+ return super.getNodeCount();
+ }
+
+ @Override
+ public int getNextNodeIndex() {
+ this.checkProcessed();
+
+ return super.getNextNodeIndex();
+ }
+
+ @Override
+ public void setNextNodeIndex(int nodeIndex) {
+ this.checkProcessed();
+
+ super.setNextNodeIndex(nodeIndex);
+ }
+
+ @Override
+ public Vec3 getEntityPosAtNode(Entity entity, int index) {
+ this.checkProcessed();
+
+ return super.getEntityPosAtNode(entity, index);
+ }
+
+ @Override
+ public BlockPos getNodePos(int index) {
+ this.checkProcessed();
+
+ return super.getNodePos(index);
+ }
+
+ @Override
+ public Vec3 getNextEntityPos(Entity entity) {
+ this.checkProcessed();
+
+ return super.getNextEntityPos(entity);
+ }
+
+ @Override
+ public BlockPos getNextNodePos() {
+ this.checkProcessed();
+
+ return super.getNextNodePos();
+ }
+
+ @Override
+ public Node getNextNode() {
+ this.checkProcessed();
+
+ return super.getNextNode();
+ }
+
+ @Nullable
+ @Override
+ public Node getPreviousNode() {
+ this.checkProcessed();
+
+ return super.getPreviousNode();
+ }
+
+ @Override
+ public boolean hasNext() {
+ this.checkProcessed();
+
+ return super.hasNext();
+ }
+}
diff --git a/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java b/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..e900f8fb637c0d69f94c5a24fb17f794513a482b
--- /dev/null
+++ b/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java
@@ -0,0 +1,44 @@
+package host.bloom.pathfinding;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.pathfinder.Path;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * used to handle the scheduling of async path processing
+ */
+public class AsyncPathProcessor {
+
+ private static final Executor mainThreadExecutor = MinecraftServer.getServer();
+ private static final Executor pathProcessingExecutor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("petal-path-processor-%d")
+ .setPriority(Thread.NORM_PRIORITY - 2)
+ .build());
+
+ protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
+ return CompletableFuture.runAsync(path::process, pathProcessingExecutor);
+ }
+
+ /**
+ * takes a possibly unprocessed path, and waits until it is completed
+ * the consumer will be immediately invoked if the path is already processed
+ * the consumer will always be called on the main thread
+ *
+ * @param path a path to wait on
+ * @param afterProcessing a consumer to be called
+ */
+ public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) {
+ if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) {
+ asyncPath.getProcessingFuture().thenAcceptAsync(afterProcessing, mainThreadExecutor);
+ } else {
+ afterProcessing.accept(path);
+ }
+ }
+}
diff --git a/src/main/java/host/bloom/pathfinding/NodeEvaluatorCache.java b/src/main/java/host/bloom/pathfinding/NodeEvaluatorCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..d70c82e4b117ee3bf9df6ba322e4ae9fc99d1124
--- /dev/null
+++ b/src/main/java/host/bloom/pathfinding/NodeEvaluatorCache.java
@@ -0,0 +1,39 @@
+package host.bloom.pathfinding;
+
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import org.apache.commons.lang.Validate;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class NodeEvaluatorCache {
+ private static final Map<NodeEvaluatorGenerator, ConcurrentLinkedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>();
+ private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>();
+
+ private static @NotNull Queue<NodeEvaluator> getDequeForGenerator(@NotNull NodeEvaluatorGenerator generator) {
+ return threadLocalNodeEvaluators.computeIfAbsent(generator, (key) -> new ConcurrentLinkedQueue<>());
+ }
+
+ public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator) {
+ var nodeEvaluator = getDequeForGenerator(generator).poll();
+
+ if (nodeEvaluator == null) {
+ nodeEvaluator = generator.generate();
+ }
+
+ nodeEvaluatorToGenerator.put(nodeEvaluator, generator);
+
+ return nodeEvaluator;
+ }
+
+ public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
+ final var generator = nodeEvaluatorToGenerator.remove(nodeEvaluator);
+ Validate.notNull(generator, "NodeEvaluator already returned");
+
+ getDequeForGenerator(generator).offer(nodeEvaluator);
+ }
+
+}
diff --git a/src/main/java/host/bloom/pathfinding/NodeEvaluatorGenerator.java b/src/main/java/host/bloom/pathfinding/NodeEvaluatorGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5327cb257d63291adc8b5c60cffb4e47e1e5b0e
--- /dev/null
+++ b/src/main/java/host/bloom/pathfinding/NodeEvaluatorGenerator.java
@@ -0,0 +1,10 @@
+package host.bloom.pathfinding;
+
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import org.jetbrains.annotations.NotNull;
+
+public interface NodeEvaluatorGenerator {
+
+ @NotNull NodeEvaluator generate();
+
+}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index bf3b8ccb3e031e0ad24cd51e28ea8cbd4f8a8030..e0453df8c0fcdb40ef0ed5ae8865d45df3325e46 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -93,8 +93,21 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType, predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes);
// Paper end - optimise POI access
- Path path = findPathToPois(entity, set);
- if (path != null && path.canReach()) {
+ // petal start - await on path async
+ Path possiblePath = findPathToPois(entity, set);
+
+ // petal - wait on the path to be processed
+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ // petal - readd canReach check
+ if (path == null || !path.canReach()) {
+ for(Pair<Holder<PoiType>, BlockPos> pair : set) {
+ this.batchCache.computeIfAbsent(pair.getSecond().asLong(), (m) -> {
+ return new AcquirePoi.JitteredLinearRetry(entity.level.random, time);
+ });
+ }
+ return;
+ }
+
BlockPos blockPos = path.getTarget();
poiManager.getType(blockPos).ifPresent((holder) -> {
poiManager.take(this.poiType, (holderx, blockPos2) -> {
@@ -107,13 +120,8 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
this.batchCache.clear();
DebugPackets.sendPoiTicketCountPacket(world, blockPos);
});
- } else {
- for(Pair<Holder<PoiType>, BlockPos> pair : set) {
- this.batchCache.computeIfAbsent(pair.getSecond().asLong(), (m) -> {
- return new AcquirePoi.JitteredLinearRetry(entity.level.random, time);
- });
- }
- }
+ });
+ // petal end
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
index 18364ce4c60172529b10bc9e3a813dcedc4b766f..e6cd04bc4e19a54b1bd621ce1c880e932207bce3 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
@@ -58,6 +58,7 @@ public class MoveToTargetSink extends Behavior<Mob> {
@Override
protected boolean canStillUse(ServerLevel serverLevel, Mob mob, long l) {
+ if (this.path != null && !this.path.isProcessed()) return true; // petal - wait for path to process
if (this.path != null && this.lastTargetPos != null) {
Optional<WalkTarget> optional = mob.getBrain().getMemory(MemoryModuleType.WALK_TARGET);
PathNavigation pathNavigation = mob.getNavigation();
@@ -87,6 +88,8 @@ public class MoveToTargetSink extends Behavior<Mob> {
@Override
protected void tick(ServerLevel world, Mob entity, long time) {
+ if (this.path != null && !this.path.isProcessed()) return; // petal - wait for processing
+
Path path = entity.getNavigation().getPath();
Brain<?> brain = entity.getBrain();
if (this.path != path) {
@@ -94,6 +97,12 @@ public class MoveToTargetSink extends Behavior<Mob> {
brain.setMemory(MemoryModuleType.PATH, path);
}
+ // petal start - periodically recall moveTo to ensure we're moving towards the correct path
+ if (time % 8 == 0) {
+ entity.getNavigation().moveTo(this.path, (double)this.speedModifier);
+ }
+ // petal end
+
if (path != null && this.lastTargetPos != null) {
WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get();
if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D && this.tryComputePath(entity, walkTarget, world.getGameTime())) {
@@ -112,6 +121,20 @@ public class MoveToTargetSink extends Behavior<Mob> {
if (this.reachedTarget(entity, walkTarget)) {
brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
} else {
+ // petal start - move this out to postProcessPath
+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(this.path, (unusedPath) -> {
+ this.postProcessPath(entity, walkTarget, time);
+ });
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean postProcessPath(Mob entity, WalkTarget walkTarget, long time) {
+ Brain<?> brain = entity.getBrain();
+ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition();
+
boolean bl = this.path != null && this.path.canReach();
if (bl) {
brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
@@ -128,10 +151,17 @@ public class MoveToTargetSink extends Behavior<Mob> {
this.path = entity.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0);
return this.path != null;
}
+
+ // We failed, so erase and move on
+ brain.eraseMemory(MemoryModuleType.WALK_TARGET);
+ if (bl) {
+ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
}
+ this.path = null;
return false;
}
+ // petal end
private boolean reachedTarget(Mob entity, WalkTarget walkTarget) {
return walkTarget.getTarget().currentBlockPosition().distManhattan(entity.blockPosition()) <= walkTarget.getCloseEnoughDist();
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
index 9bd6d4f7b86daaaa9cfbad454dde06b797e3f667..dc9dca72a22df9acadb8cdae8383522c996cbe10 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
@@ -71,19 +71,25 @@ public class SetClosestHomeAsWalkTarget extends Behavior<LivingEntity> {
Set<Pair<Holder<PoiType>, BlockPos>> set = poiManager.findAllWithType((poiType) -> {
return poiType.is(PoiTypes.HOME);
}, predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY).collect(Collectors.toSet());
- Path path = AcquirePoi.findPathToPois(pathfinderMob, set);
- if (path != null && path.canReach()) {
+ // petal start - await on path async
+ Path possiblePath = AcquirePoi.findPathToPois(pathfinderMob, set);
+
+ // petal - wait on the path to be processed
+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ if (path == null || !path.canReach() || this.triedCount < 5) { // petal - readd canReach check
+ this.batchCache.long2LongEntrySet().removeIf((entry) -> {
+ return entry.getLongValue() < this.lastUpdate;
+ });
+ return;
+ }
+
BlockPos blockPos = path.getTarget();
Optional<Holder<PoiType>> optional = poiManager.getType(blockPos);
if (optional.isPresent()) {
entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(blockPos, this.speedModifier, 1));
DebugPackets.sendPoiTicketCountPacket(world, blockPos);
}
- } else if (this.triedCount < 5) {
- this.batchCache.long2LongEntrySet().removeIf((entry) -> {
- return entry.getLongValue() < this.lastUpdate;
- });
- }
-
+ });
+ // petal end
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
index 29a872393f2f995b13b4ed26b42c6464ab27ca73..664d94c39948888bbd452ffd4b68c3790cfb0291 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
@@ -8,6 +8,14 @@ import net.minecraft.world.level.pathfinder.PathFinder;
import net.minecraft.world.phys.Vec3;
public class AmphibiousPathNavigation extends PathNavigation {
+ // petal start
+ private static final host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = () -> {
+ var nodeEvaluator = new AmphibiousNodeEvaluator(false);
+ nodeEvaluator.setCanPassDoors(true);
+ return nodeEvaluator;
+ };
+ // petal end
+
public AmphibiousPathNavigation(Mob mob, Level world) {
super(mob, world);
}
@@ -16,7 +24,7 @@ public class AmphibiousPathNavigation extends PathNavigation {
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new AmphibiousNodeEvaluator(false);
this.nodeEvaluator.setCanPassDoors(true);
- return new PathFinder(this.nodeEvaluator, range);
+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); // petal
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
index 27cd393e81f6ef9b5690c051624d8d2af50acd34..da870c729a8a4673d734e8704355ad1c92855f3c 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
@@ -12,6 +12,15 @@ import net.minecraft.world.level.pathfinder.PathFinder;
import net.minecraft.world.phys.Vec3;
public class FlyingPathNavigation extends PathNavigation {
+
+ // petal start
+ private static final host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = () -> {
+ var nodeEvaluator = new FlyNodeEvaluator();
+ nodeEvaluator.setCanPassDoors(true);
+ return nodeEvaluator;
+ };
+ // petal end
+
public FlyingPathNavigation(Mob entity, Level world) {
super(entity, world);
}
@@ -20,7 +29,7 @@ public class FlyingPathNavigation extends PathNavigation {
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new FlyNodeEvaluator();
this.nodeEvaluator.setCanPassDoors(true);
- return new PathFinder(this.nodeEvaluator, range);
+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); // petal
}
@Override
@@ -45,6 +54,8 @@ public class FlyingPathNavigation extends PathNavigation {
this.recomputePath();
}
+ if (this.path != null && !this.path.isProcessed()) return; // petal
+
if (!this.isDone()) {
if (this.canUpdatePath()) {
this.followThePath();
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
index f610c06d7bb51ec2c63863dd46711712986a106a..ff5f0788e1df9ea7a96a8fea475cc010d12e9772 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
@@ -15,6 +15,15 @@ import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
import net.minecraft.world.phys.Vec3;
public class GroundPathNavigation extends PathNavigation {
+
+ // petal start
+ private static final host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = () -> {
+ var nodeEvaluator = new WalkNodeEvaluator();
+ nodeEvaluator.setCanPassDoors(true);
+ return nodeEvaluator;
+ };
+ // petal end
+
private boolean avoidSun;
public GroundPathNavigation(Mob entity, Level world) {
@@ -25,7 +34,7 @@ public class GroundPathNavigation extends PathNavigation {
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new WalkNodeEvaluator();
this.nodeEvaluator.setCanPassDoors(true);
- return new PathFinder(this.nodeEvaluator, range);
+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); // petal
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index 3f672d7c2377fca16a6d8d31cf7aaae4f009fdce..bcc368d3d3adf73ff9ff2395d2f2b321c6134efd 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -151,6 +151,9 @@ public abstract class PathNavigation {
return null;
} else if (!this.canUpdatePath()) {
return null;
+ } else if (this.path instanceof host.bloom.pathfinding.AsyncPath asyncPath && !asyncPath.isProcessed() && asyncPath.hasSameProcessingPositions(positions)) { // petal start - catch early if it's still processing these positions let it keep processing
+ return this.path;
+ // petal end
} else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) {
return this.path;
} else {
@@ -177,11 +180,20 @@ public abstract class PathNavigation {
PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i));
Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, positions, followRange, distance, this.maxVisitedNodesMultiplier);
this.level.getProfiler().pop();
- if (path != null && path.getTarget() != null) {
- this.targetPos = path.getTarget();
- this.reachRange = distance;
- this.resetStuckTimeout();
- }
+
+ if (!positions.isEmpty()) this.targetPos = positions.iterator().next(); // petal - assign early a target position. most calls will only have 1 position
+
+ // petal start - async
+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(path, processedPath -> {
+ if (processedPath != this.path) return; // petal - check that processing didn't take so long that we calculated a new path
+
+ if (processedPath != null && processedPath.getTarget() != null) {
+ this.targetPos = processedPath.getTarget();
+ this.reachRange = distance;
+ this.resetStuckTimeout();
+ }
+ });
+ // petal end
return path;
}
@@ -228,8 +240,8 @@ public abstract class PathNavigation {
if (this.isDone()) {
return false;
} else {
- this.trimPath();
- if (this.path.getNodeCount() <= 0) {
+ if (path.isProcessed()) this.trimPath(); // petal - only trim if processed
+ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // petal - only check node count if processed
return false;
} else {
this.speedModifier = speed;
@@ -253,6 +265,8 @@ public abstract class PathNavigation {
this.recomputePath();
}
+ if (this.path != null && !this.path.isProcessed()) return; // petal - skip pathfinding if we're still processing
+
if (!this.isDone()) {
if (this.canUpdatePath()) {
this.followThePath();
@@ -278,6 +292,7 @@ public abstract class PathNavigation {
}
protected void followThePath() {
+ if (!this.path.isProcessed()) return; // petal
Vec3 vec3 = this.getTempMobPos();
this.maxDistanceToWaypoint = this.mob.getBbWidth() > 0.75F ? this.mob.getBbWidth() / 2.0F : 0.75F - this.mob.getBbWidth() / 2.0F;
Vec3i vec3i = this.path.getNextNodePos();
@@ -440,7 +455,7 @@ public abstract class PathNavigation {
// Paper start
public boolean isViableForPathRecalculationChecking() {
return !this.needsPathRecalculation() &&
- (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0);
+ (this.path != null && this.path.isProcessed() && !this.path.isDone() && this.path.getNodeCount() != 0);
}
// Paper end
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
index 8db20db72cd51046213625fac46c35854c59ec5d..11b386697279333ffd5f3abc9e1dbc9c19711764 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -57,20 +57,28 @@ public class NearestBedSensor extends Sensor<Mob> {
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
// don't ask me why it's unbounded. ask mojang.
io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
- Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
+
+ // petal start - await on path async
+ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
// Paper end - optimise POI access
- if (path != null && path.canReach()) {
+ // petal - wait on the path to be processed
+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ // petal - readd canReach check
+ if (path == null || !path.canReach()) {
+ this.batchCache.long2LongEntrySet().removeIf((entry) -> {
+ return entry.getLongValue() < this.lastUpdate;
+ });
+ return;
+ }
+
BlockPos blockPos = path.getTarget();
Optional<Holder<PoiType>> optional = poiManager.getType(blockPos);
if (optional.isPresent()) {
entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, blockPos);
}
- } else if (this.triedCount < 5) {
- this.batchCache.long2LongEntrySet().removeIf((entry) -> {
- return entry.getLongValue() < this.lastUpdate;
- });
- }
+ });
+ // petal end
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
index a7c0c2a8b9b5c1336ce33418e24e0bfd77cec5b0..227a8d1381277ad2b8d545e9e2b951ff5f7a2e36 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
@@ -1143,7 +1143,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
} else {
Bee.this.pathfindRandomlyTowards(Bee.this.hivePos);
}
- } else {
+ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // petal - check processing
boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos);
if (!flag) {
@@ -1205,7 +1205,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
} else {
Path pathentity = Bee.this.navigation.getPath();
- return pathentity != null && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone();
+ return pathentity != null && pathentity.isProcessed() && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone(); // petal - ensure path is processed
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
index 8210aa958b8bc7d36f2959d261a750c444017fec..1ddb90fbd3ace1345f77b37fc517f924819f6d7a 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
@@ -464,6 +464,14 @@ public class Frog extends Animal {
}
static class FrogPathNavigation extends AmphibiousPathNavigation {
+ // petal start
+ private static final host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = () -> {
+ var nodeEvaluator = new Frog.FrogNodeEvaluator(true);
+ nodeEvaluator.setCanPassDoors(true);
+ return nodeEvaluator;
+ };
+ // petal end
+
FrogPathNavigation(Frog frog, Level world) {
super(frog, world);
}
@@ -472,7 +480,7 @@ public class Frog extends Animal {
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new Frog.FrogNodeEvaluator(true);
this.nodeEvaluator.setCanPassDoors(true);
- return new PathFinder(this.nodeEvaluator, range);
+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); // petal
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
index 68e31cf561f3d76bce6fa4324a75594c776f8964..6ce824a1f74163f60fc3b222f3aaf59cbbe5c857 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
@@ -288,7 +288,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
protected boolean closeToNextPos() {
Path pathentity = this.getNavigation().getPath();
- if (pathentity != null) {
+ if (pathentity != null && pathentity.isProcessed()) { // petal - ensure path is processed
BlockPos blockposition = pathentity.getTarget();
if (blockposition != null) {
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java
index 2a335f277bd0e4b8ad0f60d8226eb8aaa80a871f..527f5fb55b596b44c7418a6f70e7243432c160dd 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java
@@ -30,6 +30,17 @@ public class Path {
this.reached = reachesTarget;
}
+ // petal start
+ /**
+ * checks if the path is completely processed in the case of it being computed async
+ *
+ * @return true if the path is processed
+ */
+ public boolean isProcessed() {
+ return true;
+ }
+ // petal end
+
public void advance() {
++this.nextNodeIndex;
}
@@ -104,6 +115,8 @@ public class Path {
}
public boolean sameAs(@Nullable Path o) {
+ if (o == this) return true; // petal - short circuit
+
if (o == null) {
return false;
} else if (o.nodes.size() != this.nodes.size()) {
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
index d23481453717f715124156b5d83f6448f720d049..57bac2a4a8b2e4249daf3905dfa035592b3ef189 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -25,36 +25,73 @@ public class PathFinder {
private static final boolean DEBUG = false;
private final BinaryHeap openSet = new BinaryHeap();
- public PathFinder(NodeEvaluator pathNodeMaker, int range) {
+ private final @Nullable host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator; // petal - we use this later to generate an evaluator
+
+ // petal start - add nodeEvaluatorGenerator as optional param
+ public PathFinder(NodeEvaluator pathNodeMaker, int range, @Nullable host.bloom.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator) {
this.nodeEvaluator = pathNodeMaker;
this.maxVisitedNodes = range;
+ this.nodeEvaluatorGenerator = nodeEvaluatorGenerator;
+ }
+
+ public PathFinder(NodeEvaluator pathNodeMaker, int range) {
+ this(pathNodeMaker, range, null);
}
+ // petal end
@Nullable
public Path findPath(PathNavigationRegion world, Mob mob, Set<BlockPos> positions, float followRange, int distance, float rangeMultiplier) {
- this.openSet.clear();
- this.nodeEvaluator.prepare(world, mob);
- Node node = this.nodeEvaluator.getStart();
+ //this.openSet.clear(); // petal - it's always cleared in processPath
+ // petal start - use a generated evaluator if we have one otherwise run sync
+ var nodeEvaluator = this.nodeEvaluatorGenerator == null ? this.nodeEvaluator : host.bloom.pathfinding.NodeEvaluatorCache.takeNodeEvaluator(this.nodeEvaluatorGenerator);
+ nodeEvaluator.prepare(world, mob);
+ Node node = nodeEvaluator.getStart();
if (node == null) {
return null;
} else {
// Paper start - remove streams - and optimize collection
List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
for (BlockPos pos : positions) {
- map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), pos));
+ map.add(new java.util.AbstractMap.SimpleEntry<>(nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), pos));
}
// Paper end
- Path path = this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier);
- this.nodeEvaluator.done();
- return path;
+
+ // petal start
+ if (this.nodeEvaluatorGenerator == null) {
+ // run sync :(
+ return this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier);
+ }
+
+ return new host.bloom.pathfinding.AsyncPath(Lists.newArrayList(), positions, () -> {
+ try {
+ return this.processPath(nodeEvaluator, node, map, followRange, distance, rangeMultiplier);
+ } finally {
+ nodeEvaluator.done();
+ host.bloom.pathfinding.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator);
+ }
+ });
+ // petal end
}
}
- @Nullable
+ // petal start - split pathfinding into the original sync method for compat and processing for delaying
// Paper start - optimize collection
private Path findPath(ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
+ // readd the profiler code for sync
profiler.push("find_path");
profiler.markForCharting(MetricCategory.PATH_FINDING);
+
+ try {
+ return this.processPath(this.nodeEvaluator, startNode, positions, followRange, distance, rangeMultiplier);
+ } finally {
+ this.nodeEvaluator.done();
+ }
+ }
+ // petal end
+
+ private synchronized @org.jetbrains.annotations.NotNull Path processPath(NodeEvaluator nodeEvaluator, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) { // petal - sync to only use the caching functions in this class on a single thread
+ org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty()); // ensure that we have at least one position, which means we'll always return a path
+
// Set<Target> set = positions.keySet();
startNode.g = 0.0F;
startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
@@ -91,7 +128,7 @@ public class PathFinder {
}
if (!(node.distanceTo(startNode) >= followRange)) {
- int k = this.nodeEvaluator.getNeighbors(this.neighbors, node);
+ int k = nodeEvaluator.getNeighbors(this.neighbors, node);
for(int l = 0; l < k; ++l) {
Node node2 = this.neighbors[l];
@@ -123,9 +160,14 @@ public class PathFinder {
if (best == null || comparator.compare(path, best) < 0)
best = path;
}
+
+ // petal - ignore this warning, we know that the above loop always runs at least once since positions is not empty
+ //noinspection ConstantConditions
return best;
// Paper end
+ // petal end
}
+ // petal end
protected float distance(Node a, Node b) {
return a.distanceTo(b);

View File

@@ -0,0 +1,279 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
Date: Sat, 2 Jul 2022 00:35:56 -0500
Subject: [PATCH] feat: multithreaded tracker
Co-authored-by: Paul Sauve <paul@technove.co>
based off the airplane multithreaded tracker this patch properly handles
concurrent accesses everywhere, as well as being much simpler to maintain
some things are too unsafe to run off the main thread so we don't attempt to do
that. this multithreaded tracker remains accurate, non-breaking and fast
diff --git a/src/main/java/host/bloom/tracker/MultithreadedTracker.java b/src/main/java/host/bloom/tracker/MultithreadedTracker.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5f3cf027534fe87e8b7d5e9fd14604b2327c701
--- /dev/null
+++ b/src/main/java/host/bloom/tracker/MultithreadedTracker.java
@@ -0,0 +1,125 @@
+package host.bloom.tracker;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet;
+import io.papermc.paper.world.ChunkEntitySlices;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MultithreadedTracker {
+
+ private static final int parallelism = Math.max(4, Runtime.getRuntime().availableProcessors());
+ private static final Executor trackerExecutor = Executors.newFixedThreadPool(parallelism, new ThreadFactoryBuilder()
+ .setNameFormat("petal-tracker-%d")
+ .setPriority(Thread.NORM_PRIORITY - 2)
+ .build());
+
+ private final IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks;
+ private final AtomicInteger taskIndex = new AtomicInteger();
+
+ private final ConcurrentLinkedQueue<Runnable> mainThreadTasks;
+ private final AtomicInteger finishedTasks = new AtomicInteger();
+
+ public MultithreadedTracker(IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks, ConcurrentLinkedQueue<Runnable> mainThreadTasks) {
+ this.entityTickingChunks = entityTickingChunks;
+ this.mainThreadTasks = mainThreadTasks;
+ }
+
+ public void tick() {
+ int iterator = this.entityTickingChunks.createRawIterator();
+
+ if (iterator == -1) {
+ return;
+ }
+
+ try {
+ this.taskIndex.set(iterator);
+ this.finishedTasks.set(0);
+
+ for (int i = 0; i < parallelism; i++) {
+ trackerExecutor.execute(this::run);
+ }
+
+ while (this.taskIndex.get() < this.entityTickingChunks.getListSize()) {
+ this.runMainThreadTasks();
+ this.handleTask(); // assist
+ }
+
+ while (this.finishedTasks.get() != parallelism) {
+ this.runMainThreadTasks();
+ }
+
+ this.runMainThreadTasks(); // finish any remaining tasks
+ } finally {
+ this.entityTickingChunks.finishRawIterator();
+ }
+ }
+
+ private void runMainThreadTasks() {
+ try {
+ Runnable task;
+ while ((task = this.mainThreadTasks.poll()) != null) {
+ task.run();
+ }
+ } catch (Throwable throwable) {
+ MinecraftServer.LOGGER.warn("Tasks failed while ticking track queue", throwable);
+ }
+ }
+
+ private void run() {
+ try {
+ while (handleTask()) ;
+ } finally {
+ this.finishedTasks.incrementAndGet();
+ }
+ }
+
+ private boolean handleTask() {
+ int index;
+ while ((index = this.taskIndex.getAndIncrement()) < this.entityTickingChunks.getListSize()) {
+ LevelChunk chunk = this.entityTickingChunks.rawGet(index);
+ if (chunk != null) {
+ try {
+ this.processChunk(chunk);
+ } catch (Throwable throwable) {
+ MinecraftServer.LOGGER.warn("Ticking tracker failed", throwable);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void processChunk(LevelChunk chunk) {
+ final ChunkEntitySlices entitySlices = chunk.level.entityManager.entitySliceManager.getChunk(chunk.locX, chunk.locZ);
+ if (entitySlices == null) {
+ return;
+ }
+
+ final Entity[] rawEntities = entitySlices.entities.getRawData();
+ final ChunkMap chunkMap = chunk.level.chunkSource.chunkMap;
+
+ for (int i = 0; i < rawEntities.length; i++) {
+ Entity entity = rawEntities[i];
+ if (entity != null) {
+ ChunkMap.TrackedEntity entityTracker = chunkMap.entityMap.get(entity.getId());
+ if (entityTracker != null) {
+ entityTracker.updatePlayers(entityTracker.entity.getPlayersInTrackRange());
+
+ // run this on the main thread but queue it up here so we can run it while processing tracking at the same time
+ this.mainThreadTasks.offer(entityTracker.serverEntity::sendChanges);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
index 0fd814f1d65c111266a2b20f86561839a4cef755..169ac3ad1b1e8e3e1874ada2471e478233c6ada7 100644
--- a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
+++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
@@ -15,7 +15,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
/* list impl */
protected E[] listElements;
- protected int listSize;
+ protected int listSize; public int getListSize() { return this.listSize; } // petal - expose listSize
protected final double maxFragFactor;
diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
index 47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749..85882eeb86d7b74db0219aa65783946d8083885d 100644
--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
@@ -27,7 +27,7 @@ public final class ChunkEntitySlices {
protected final EntityCollectionBySection allEntities;
protected final EntityCollectionBySection hardCollidingEntities;
protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
- protected final EntityList entities = new EntityList();
+ public final EntityList entities = new EntityList();
public ChunkHolder.FullChunkStatus status;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 2daef87d5c1de8e6f9a8c7c6a7753d980b05178f..345f4ddee48da851cbabd9de878642fc57a2d7b0 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -2082,8 +2082,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
entity.tracker = null; // Paper - We're no longer tracked
}
+ // petal start - multithreaded tracker
+ private @Nullable host.bloom.tracker.MultithreadedTracker multithreadedTracker;
+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
+
+ public void runOnTrackerMainThread(final Runnable runnable) {
+ this.trackerMainThreadTasks.add(runnable);
+ }
+
// Paper start - optimised tracker
private final void processTrackQueue() {
+ if (true) {
+ if (this.multithreadedTracker == null) {
+ this.multithreadedTracker = new host.bloom.tracker.MultithreadedTracker(this.level.chunkSource.entityTickingChunks, this.trackerMainThreadTasks);
+ }
+
+ this.multithreadedTracker.tick();
+ return;
+ }
+ // petal end
+
this.level.timings.tracker1.startTiming();
try {
for (TrackedEntity tracker : this.entityMap.values()) {
@@ -2276,10 +2294,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public class TrackedEntity {
public final ServerEntity serverEntity; // Purpur -> package -> public
- final Entity entity;
+ public final Entity entity; // petal -> public
private final int range;
SectionPos lastSectionPos;
- public final Set<ServerPlayerConnection> seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl
+ public final Set<ServerPlayerConnection> seenBy = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Paper - optimise map impl // petal - sync
public TrackedEntity(Entity entity, int i, int j, boolean flag) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
@@ -2291,7 +2309,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper start - use distance map to optimise tracker
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates;
- final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
+ public final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) { // petal -> public
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
this.lastTrackerCandidates = newTrackerCandidates;
@@ -2363,7 +2381,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void removePlayer(ServerPlayer player) {
- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // petal - we can remove async too
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
@@ -2371,7 +2389,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void updatePlayer(ServerPlayer player) {
- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // petal - we can update async
if (player != this.entity) {
// Paper start - remove allocation of Vec3D here
// Vec3 vec3d = player.position().subtract(this.entity.position());
diff --git a/src/main/java/net/minecraft/server/level/ServerBossEvent.java b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
index ca42c2642a729b90d22b968af7258f3aee72e14b..40261b80d947a6be43465013fae5532197cfe721 100644
--- a/src/main/java/net/minecraft/server/level/ServerBossEvent.java
+++ b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
@@ -13,7 +13,7 @@ import net.minecraft.util.Mth;
import net.minecraft.world.BossEvent;
public class ServerBossEvent extends BossEvent {
- private final Set<ServerPlayer> players = Sets.newHashSet();
+ private final Set<ServerPlayer> players = Sets.newConcurrentHashSet(); // petal - players can be removed in async tracking
private final Set<ServerPlayer> unmodifiablePlayers = Collections.unmodifiableSet(this.players);
public boolean visible = true;
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 3441339e1ba5efb0e25c16fa13cb65d2fbdafc42..9d0cb0468800cbd88f43424466e9a29a05d3945d 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -249,14 +249,18 @@ public class ServerEntity {
public void removePairing(ServerPlayer player) {
this.entity.stopSeenByPlayer(player);
- player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}));
+ // petal start - ensure main thread
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() ->
+ player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}))
+ );
+ // petal end
}
public void addPairing(ServerPlayer player) {
ServerGamePacketListenerImpl playerconnection = player.connection;
Objects.requireNonNull(player.connection);
- this.sendPairingData(playerconnection::send, player); // CraftBukkit - add player
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> this.sendPairingData(playerconnection::send, player)); // CraftBukkit - add player // petal - main thread
this.entity.startSeenByPlayer(player);
}

View File

@@ -0,0 +1,132 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
Date: Sun, 10 Jul 2022 13:29:20 -0500
Subject: [PATCH] feat: reduce work done by game event system
1. going into game event dispatching can be expensive so run the checks before dispatching
2. euclideangameeventdispatcher is not used concurrently so we ban that usage for improved performance with allays
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 0d82ca8ba9f2b11213cfe1ab01dc6ef3f2f510c9..bad88fddd25a99fdbe35285f7694fed26f943296 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1672,6 +1672,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (chunk != null) {
for (int j2 = k; j2 <= j1; ++j2) {
flag |= chunk.getEventDispatcher(j2).walkListeners(event, emitterPos, emitter, (gameeventlistener, vec3d1) -> {
+ if (!gameeventlistener.listensToEvent(event, emitter)) return; // petal - if they don't listen, ignore
(gameeventlistener.handleEventsImmediately() ? list : this.gameEventMessages).add(new GameEvent.Message(event, emitterPos, emitter, gameeventlistener, vec3d1));
});
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
index 22c309343299e60ed8028229b7f134109001ff35..d5947d29295ddc93ba8ac1c0fc61f7badad582c4 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
@@ -85,6 +85,13 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi
}
}
+ // petal start
+ @Override
+ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) {
+ return !this.isRemoved() && gameEvent == GameEvent.ENTITY_DIE && context.sourceEntity() instanceof LivingEntity;
+ }
+ // petal end
+
public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) {
org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
blockEntity.sculkSpreader.updateCursors(world, pos, world.getRandom(), true);
diff --git a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventDispatcher.java b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventDispatcher.java
index 0dd708ebe81f73710de51215529c05ec61837dd3..f5b402efa86f824c460db8cac20c1c2b090f82d0 100644
--- a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventDispatcher.java
+++ b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventDispatcher.java
@@ -13,8 +13,8 @@ import net.minecraft.world.phys.Vec3;
public class EuclideanGameEventDispatcher implements GameEventDispatcher {
private final List<GameEventListener> listeners = Lists.newArrayList();
- private final Set<GameEventListener> listenersToRemove = Sets.newHashSet();
- private final List<GameEventListener> listenersToAdd = Lists.newArrayList();
+ //private final Set<GameEventListener> listenersToRemove = Sets.newHashSet(); // petal - not necessary
+ //private final List<GameEventListener> listenersToAdd = Lists.newArrayList(); // petal
private boolean processing;
private final ServerLevel level;
@@ -30,7 +30,7 @@ public class EuclideanGameEventDispatcher implements GameEventDispatcher {
@Override
public void register(GameEventListener listener) {
if (this.processing) {
- this.listenersToAdd.add(listener);
+ throw new java.util.ConcurrentModificationException(); // petal - disallow concurrent modification
} else {
this.listeners.add(listener);
}
@@ -41,7 +41,7 @@ public class EuclideanGameEventDispatcher implements GameEventDispatcher {
@Override
public void unregister(GameEventListener listener) {
if (this.processing) {
- this.listenersToRemove.add(listener);
+ throw new java.util.ConcurrentModificationException(); // petal - disallow concurrent modification
} else {
this.listeners.remove(listener);
}
@@ -58,7 +58,7 @@ public class EuclideanGameEventDispatcher implements GameEventDispatcher {
while(iterator.hasNext()) {
GameEventListener gameEventListener = iterator.next();
- if (this.listenersToRemove.remove(gameEventListener)) {
+ if (false) { // petal - disallow concurrent modification
iterator.remove();
} else {
Optional<Vec3> optional = getPostableListenerPosition(this.level, pos, gameEventListener);
@@ -72,6 +72,8 @@ public class EuclideanGameEventDispatcher implements GameEventDispatcher {
this.processing = false;
}
+ // petal start
+ /*
if (!this.listenersToAdd.isEmpty()) {
this.listeners.addAll(this.listenersToAdd);
this.listenersToAdd.clear();
@@ -81,6 +83,8 @@ public class EuclideanGameEventDispatcher implements GameEventDispatcher {
this.listeners.removeAll(this.listenersToRemove);
this.listenersToRemove.clear();
}
+ */
+ // petal end
return bl;
}
diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java
index e5601afe8b739da518f36ae306f5e0cb252238f0..bc8f04424c5e8c416d6988f0e06d8cadbb400ca7 100644
--- a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java
+++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java
@@ -12,4 +12,10 @@ public interface GameEventListener {
int getListenerRadius();
boolean handleGameEvent(ServerLevel world, GameEvent.Message event);
+
+ // petal start - add check for seeing if this listener cares about an event
+ default boolean listensToEvent(net.minecraft.world.level.gameevent.GameEvent gameEvent, net.minecraft.world.level.gameevent.GameEvent.Context context) {
+ return true;
+ }
+ // petal end
}
diff --git a/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java b/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java
index e45f54534bbf054eaf0008546ff459d4c11ddd50..e49d0d1c2a539fcd7e75262c4010475193964287 100644
--- a/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java
+++ b/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java
@@ -162,6 +162,13 @@ public class VibrationListener implements GameEventListener {
return true;
}
+ // petal start
+ @Override
+ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) {
+ return this.receivingEvent == null && gameEvent.is(this.config.getListenableEvents());
+ }
+ // petal end
+
public interface VibrationListenerConfig {
default TagKey<GameEvent> getListenableEvents() {

View File

@@ -0,0 +1,76 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
Date: Sun, 10 Jul 2022 15:44:38 -0500
Subject: [PATCH] feat: reduce sensor work
this patch is focused around the sensors used for ai
delete the line of sight cache less often and use a faster nearby comparison
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 411593b1b105d62440d76b7bd1b8c74b701e3e75..aec8b3e33dd41978d3542634567a0ad6996f9647 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1012,20 +1012,22 @@ public abstract class LivingEntity extends Entity {
}
if (entity != null) {
- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+ // petal start - only do itemstack lookup if we need to
+ //ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
EntityType<?> entitytypes = entity.getType();
// Purpur start
- if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) {
+ if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)) {
d0 *= entity.level.purpurConfig.skeletonHeadVisibilityPercent;
}
- else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) {
+ else if (entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)) {
d0 *= entity.level.purpurConfig.zombieHeadVisibilityPercent;
}
- else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
+ else if (entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) {
d0 *= entity.level.purpurConfig.creeperHeadVisibilityPercent;
}
// Purpur end
+ // petal end
// Purpur start
if (entity instanceof LivingEntity entityliving) {
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 34db1bd524bb97fbbe0f86b088a2ac343e730f5e..1df1e912194c4d2b934859d999e6d09e802a5978 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -878,10 +878,10 @@ public abstract class Mob extends LivingEntity {
return;
}
// Paper end
+ int i = this.level.getServer().getTickCount() + this.getId(); // petal - move up
this.level.getProfiler().push("sensing");
- this.sensing.tick();
+ if (i % 10 == 0) this.sensing.tick(); // petal - only refresh line of sight cache every half second
this.level.getProfiler().pop();
- int i = this.level.getServer().getTickCount() + this.getId();
if (i % 2 != 0 && this.tickCount > 1) {
this.level.getProfiler().push("targetSelector");
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
index d8cf99a3014a4b8152ae819fa663c2fdf34dce57..6cce3def9c52cb365c7e831be42eceb1e0710cb4 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
@@ -18,7 +18,14 @@ public class NearestLivingEntitySensor<T extends LivingEntity> extends Sensor<T>
List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, aABB, (e) -> {
return e != entity && e.isAlive();
});
- list.sort(Comparator.comparingDouble(entity::distanceToSqr));
+ // petal start - use manual comparison because we don't need the accuracy of Double.compare
+ list.sort((e1, e2) -> {
+ double dist1 = entity.distanceToSqr(e1), dist2 = entity.distanceToSqr(e2);
+ if (dist1 < dist2) return -1;
+ if (dist1 > dist2) return 1;
+ return 0;
+ });
+ // petal end
Brain<?> brain = entity.getBrain();
brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list);
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(entity, list));

9
settings.gradle.kts Normal file
View File

@@ -0,0 +1,9 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://papermc.io/repo/repository/maven-public/")
}
}
rootProject.name = "petal"
include("petal-api", "petal-server")