diff --git a/build-data/divinemc.at b/build-data/divinemc.at index 262146c..a8ffb2d 100644 --- a/build-data/divinemc.at +++ b/build-data/divinemc.at @@ -1,10 +1,62 @@ # This file is auto generated, any changes may be overridden! # See CONTRIBUTING.md on how to add access transformers. -public net.minecraft.world.entity.projectile.AbstractArrow startFalling()V -public net.minecraft.world.level.block.state.pattern.BlockPattern matches(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Lnet/minecraft/core/Direction;Lcom/google/common/cache/LoadingCache;)Lnet/minecraft/world/level/block/state/pattern/BlockPattern$BlockPatternMatch; -public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; -public net.minecraft.world.level.chunk.storage.RegionFile isOversized(II)Z -public net.minecraft.world.level.chunk.storage.RegionFile recalculateHeader()Z -public net.minecraft.world.level.chunk.storage.RegionFile setOversized(IIZ)V -public net.minecraft.world.level.chunk.storage.RegionFile write(Lnet/minecraft/world/level/ChunkPos;Ljava/nio/ByteBuffer;)V +private-f net.minecraft.world.level.levelgen.NoiseChunk$Cache2D function +private-f net.minecraft.world.level.levelgen.NoiseChunk$CacheOnce function +private-f net.minecraft.world.level.levelgen.NoiseChunk$FlatCache noiseFiller +private-f net.minecraft.world.level.levelgen.NoiseChunk$NoiseInterpolator noiseFiller +private-f net.minecraft.world.level.levelgen.RandomState router +private-f net.minecraft.world.level.levelgen.RandomState sampler +public net.minecraft.util.Mth SIN +public net.minecraft.world.entity.ai.Brain sensors +public net.minecraft.world.entity.ai.sensing.Sensor scanRate +public net.minecraft.world.entity.ai.sensing.Sensor timeToTick +public net.minecraft.world.level.ServerExplosion damageSource +public net.minecraft.world.level.ServerExplosion source +public net.minecraft.world.level.chunk.LevelChunkSection nonEmptyBlockCount +public net.minecraft.world.level.chunk.LevelChunkSection tickingBlockCount +public net.minecraft.world.level.chunk.LevelChunkSection tickingFluidCount +public net.minecraft.world.level.chunk.PalettedContainer palette +public net.minecraft.world.level.chunk.PalettedContainer strategy +public net.minecraft.world.level.levelgen.DensityFunctions$BlendAlpha +public net.minecraft.world.level.levelgen.DensityFunctions$BlendDensity +public net.minecraft.world.level.levelgen.DensityFunctions$BlendOffset +public net.minecraft.world.level.levelgen.DensityFunctions$Clamp +public net.minecraft.world.level.levelgen.DensityFunctions$Constant +public net.minecraft.world.level.levelgen.DensityFunctions$Mapped +public net.minecraft.world.level.levelgen.DensityFunctions$Mapped$Type +public net.minecraft.world.level.levelgen.DensityFunctions$Noise +public net.minecraft.world.level.levelgen.DensityFunctions$RangeChoice +public net.minecraft.world.level.levelgen.DensityFunctions$Shift +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftA +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftB +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftedNoise +public net.minecraft.world.level.levelgen.DensityFunctions$TwoArgumentSimpleFunction +public net.minecraft.world.level.levelgen.DensityFunctions$WeirdScaledSampler +public net.minecraft.world.level.levelgen.DensityFunctions$WeirdScaledSampler$RarityValueMapper mapper +public net.minecraft.world.level.levelgen.DensityFunctions$YClampedGradient +public net.minecraft.world.level.levelgen.NoiseChunk$BlendAlpha +public net.minecraft.world.level.levelgen.NoiseChunk$BlendOffset +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity getSpaghettiRarity3D(D)D +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity getSphaghettiRarity2D(D)D +public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedHi +public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedLo +public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator +public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates +public net.minecraft.world.level.levelgen.synth.BlendedNoise mainNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise maxLimitNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise minLimitNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise smearScaleMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzFactor +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzScale +public net.minecraft.world.level.levelgen.synth.BlendedNoise yFactor +public net.minecraft.world.level.levelgen.synth.BlendedNoise yMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise yScale +public net.minecraft.world.level.levelgen.synth.ImprovedNoise p +public net.minecraft.world.level.levelgen.synth.PerlinNoise amplitudes +public net.minecraft.world.level.levelgen.synth.PerlinNoise lowestFreqInputFactor +public net.minecraft.world.level.levelgen.synth.PerlinNoise lowestFreqValueFactor +public net.minecraft.world.level.levelgen.synth.PerlinNoise noiseLevels +public net.minecraft.world.level.levelgen.synth.SimplexNoise p public net.minecraft.world.level.pathfinder.SwimNodeEvaluator allowBreaching diff --git a/build.gradle.kts b/build.gradle.kts index 3c42348..fb5fe28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,13 +43,13 @@ subprojects { extensions.configure { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(22) } } tasks.withType { options.encoding = Charsets.UTF_8.name() - options.release = 21 + options.release = 22 options.isFork = true } tasks.withType { diff --git a/divinemc-api/paper-patches/features/0001-Rebrand.patch b/divinemc-api/paper-patches/features/0001-Rebrand.patch new file mode 100644 index 0000000..4ea15a9 --- /dev/null +++ b/divinemc-api/paper-patches/features/0001-Rebrand.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 21 Feb 2025 22:58:46 +0300 +Subject: [PATCH] Rebrand + + +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java +index fb1fe2651e53a9bf46b3632c638e13eea9dcda93..81e92d1053efd15c079e318a4ae087948bc07866 100644 +--- a/src/main/java/io/papermc/paper/ServerBuildInfo.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java +@@ -25,6 +25,14 @@ public interface ServerBuildInfo { + */ + Key BRAND_PURPUR_ID = Key.key("purpurmc", "purpur"); + // Purpur end ++ ++ // DivineMC start - Rebrand ++ /** ++ * The brand id for DivineMC. ++ */ ++ Key BRAND_DIVINEMC_ID = Key.key("bxteam", "divinemc"); ++ // DivineMC end ++ + /** + * Gets the {@code ServerBuildInfo}. + * +diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +index c880d0010849ab733ad13bbd18fab3c864d0cf61..a76439e59eefa4b6dbd0e100d72c21055d0ca008 100644 +--- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +@@ -259,7 +259,7 @@ public class VersionCommand extends BukkitCommand { + // Purpur start + int distance = getVersionFetcher().distance(); + final Component message = Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), +- ChatColor.parseMM("Current Purpur Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), ++ ChatColor.parseMM("Current DivineMC Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), // DivineMC - Rebrand + // Purpur end + msg + ); diff --git a/divinemc-api/paper-patches/features/0002-Configuration.patch b/divinemc-api/paper-patches/features/0002-Configuration.patch new file mode 100644 index 0000000..c0c586c --- /dev/null +++ b/divinemc-api/paper-patches/features/0002-Configuration.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 21 Feb 2025 23:00:22 +0300 +Subject: [PATCH] Configuration + + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 78637a4f9650c1dd7ccc94bbfeb1fac048aa7f69..225c13225837c2748843cece816e2ad70da4b056 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -2346,6 +2346,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + } + // Purpur end + ++ // DivineMC start - Configuration ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getDivineConfig() { ++ throw new UnsupportedOperationException("Not supported yet"); ++ } ++ // DivineMC end - Configuration ++ + /** + * Sends the component to the player + * diff --git a/divinemc-api/paper-patches/features/0002-Delete-Timings.patch b/divinemc-api/paper-patches/features/0003-Delete-Timings.patch similarity index 91% rename from divinemc-api/paper-patches/features/0002-Delete-Timings.patch rename to divinemc-api/paper-patches/features/0003-Delete-Timings.patch index 7ff3eeb..f907ba1 100644 --- a/divinemc-api/paper-patches/features/0002-Delete-Timings.patch +++ b/divinemc-api/paper-patches/features/0003-Delete-Timings.patch @@ -2104,6 +2104,150 @@ index 632c4961515f5052551f841cfa840e60bba7a257..00000000000000000000000000000000 - super.stopTiming(); - } -} +diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java +index 71eb845a4d3b8b6ec3b816a0f20ec807e0f9a86d..a43419c23aa0f6fd809caf5a841cb138f350b7ba 100644 +--- a/src/main/java/org/bukkit/command/Command.java ++++ b/src/main/java/org/bukkit/command/Command.java +@@ -33,16 +33,6 @@ public abstract class Command { + protected String usageMessage; + private String permission; + private net.kyori.adventure.text.Component permissionMessage; // Paper +- /** +- * @deprecated Timings will be removed in the future +- */ +- @Deprecated(forRemoval = true) +- public co.aikar.timings.Timing timings; // Paper +- /** +- * @deprecated Timings will be removed in the future +- */ +- @Deprecated(forRemoval = true) +- @NotNull public String getTimingName() {return getName();} // Paper + + protected Command(@NotNull String name) { + this(name, "", "/" + name, new ArrayList()); +diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +index abe256e1e45ce28036da4aa1586715bc8a1a3414..9eab8024e0675865f17669847759a26d28f74f3a 100644 +--- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java ++++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +@@ -12,7 +12,6 @@ public class FormattedCommandAlias extends Command { + + public FormattedCommandAlias(@NotNull String alias, @NotNull String[] formatStrings) { + super(alias); +- timings = co.aikar.timings.TimingsManager.getCommandTiming("minecraft", this); // Spigot + this.formatStrings = formatStrings; + } + +@@ -120,10 +119,6 @@ public class FormattedCommandAlias extends Command { + return formatString.trim(); // Paper - Causes an extra space at the end, breaks with brig commands + } + +- @NotNull +- @Override // Paper +- public String getTimingName() {return "Command Forwarder - " + super.getTimingName();} // Paper +- + private static boolean inRange(int i, int j, int k) { + return i >= j && i <= k; + } +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index 685aa9d73f14a5e3a85b251b83fbe8134b0a244c..6878e8bff4900a6d9b41cb981a69821da7f51242 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -39,7 +39,6 @@ public class SimpleCommandMap implements CommandMap { + register("bukkit", new VersionCommand("version")); + register("bukkit", new ReloadCommand("reload")); + //register("bukkit", new PluginsCommand("plugins")); // Paper +- register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper + } + + public void setFallbackCommands() { +@@ -71,7 +70,6 @@ public class SimpleCommandMap implements CommandMap { + */ + @Override + public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) { +- command.timings = co.aikar.timings.TimingsManager.getCommandTiming(fallbackPrefix, command); // Paper + label = label.toLowerCase(Locale.ROOT).trim(); + fallbackPrefix = fallbackPrefix.toLowerCase(Locale.ROOT).trim(); + boolean registered = register(label, command, false, fallbackPrefix); +@@ -166,12 +164,6 @@ public class SimpleCommandMap implements CommandMap { + parsedArgs = event.getArgs(); + // Purpur end - ExecuteCommandEvent + +- // Paper start - Plugins do weird things to workaround normal registration +- if (target.timings == null) { +- target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target); +- } +- // Paper end +- + try { + //try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Purpur - Remove Timings + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 001465eedafa51ac027a4db51cba6223edfe1171..9cb0f09b821a4020d17771a5b64ddd53e7d78478 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -720,12 +720,7 @@ public final class SimplePluginManager implements PluginManager { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + +- executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Paper +- if (false) { // Spigot - RL handles useTimings check now // Paper +- getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); +- } else { +- getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); +- } ++ getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); // DivineMC - Delete Timings + } + + @NotNull +@@ -955,8 +950,7 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public boolean useTimings() { +- if (true) {return this.paperPluginManager.useTimings();} // Paper +- return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot ++ return false; // DivineMC - Delete Timings + } + + /** +@@ -966,7 +960,7 @@ public final class SimplePluginManager implements PluginManager { + */ + @Deprecated(forRemoval = true) + public void useTimings(boolean use) { +- co.aikar.timings.Timings.setTimingsEnabled(use); // Paper ++ // DivineMC - Delete Timings + } + + // Paper start +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 065f182fbfe4541d602f57d548f286ee3c2fab19..40350504b5f7a92d834e95bb2e4e4268195ec9e7 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -43,7 +43,6 @@ import org.bukkit.plugin.TimedRegisteredListener; + import org.bukkit.plugin.UnknownDependencyException; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.yaml.snakeyaml.error.YAMLException; + + /** +@@ -294,7 +293,7 @@ public final class JavaPluginLoader implements PluginLoader { + } + } + +- EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Paper ++ EventExecutor executor = new EventExecutor() { // Paper // DivineMC - Delete Timings + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { // Paper + try { +@@ -308,7 +307,7 @@ public final class JavaPluginLoader implements PluginLoader { + throw new EventException(t); + } + } +- }, plugin, method, eventClass); // Paper ++ }; // Paper // DivineMC - Delete Timings + if (false) { // Spigot - RL handles useTimings check now + eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } else { diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java deleted file mode 100644 index b4249da3eb26eae26ec000cc4d56cd21ac2fc6d5..0000000000000000000000000000000000000000 diff --git a/divinemc-api/paper-patches/features/0001-Disable-reload-command-by-default.patch b/divinemc-api/paper-patches/features/0004-Disable-reload-command-by-default.patch similarity index 100% rename from divinemc-api/paper-patches/features/0001-Disable-reload-command-by-default.patch rename to divinemc-api/paper-patches/features/0004-Disable-reload-command-by-default.patch diff --git a/divinemc-server/build.gradle.kts.patch b/divinemc-server/build.gradle.kts.patch index 10256b7..d27dd65 100644 --- a/divinemc-server/build.gradle.kts.patch +++ b/divinemc-server/build.gradle.kts.patch @@ -57,17 +57,16 @@ implementation("ca.spottedleaf:concurrentutil:0.0.3") implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 -@@ -176,6 +_,9 @@ +@@ -176,6 +_,8 @@ implementation("org.mozilla:rhino-engine:1.7.14") // Purpur implementation("dev.omega24:upnp4j:1.0") // Purpur -+ implementation("com.github.luben:zstd-jni:1.5.6-9") // DivineMC -+ implementation("org.lz4:lz4-java:1.8.0") // DivineMC ++ implementation("net.objecthunter:exp4j:0.4.8") // DivineMC + runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") -@@ -203,26 +_,35 @@ +@@ -203,30 +_,41 @@ implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") } @@ -91,6 +90,7 @@ val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash" val date = git.exec(providers, "show", "-s", "--format=%ci", gitHash).get().trim() val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim() ++ val experimental = rootProject.providers.gradleProperty("experimental").get() attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", - "Implementation-Title" to "Purpur", // Purpur @@ -109,3 +109,8 @@ "Build-Number" to (build ?: ""), "Build-Time" to buildTime.toString(), "Git-Branch" to gitBranch, + "Git-Commit" to gitHash, ++ "Experimental" to experimental, // DivineMC - Experimental flag + ) + for (tld in setOf("net", "com", "org")) { + attributes("$tld/bukkit", "Sealed" to true) diff --git a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch index f4e6ae1..ceca597 100644 --- a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch +++ b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch @@ -17,6 +17,75 @@ index 394443d00e661715439be1e56dddc129947699a4..480ad57a6b7b74e6b83e9c6ceb69ea1f public CrashReport(String title, Throwable exception) { io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 1485186d4989874ef89c4e83830f26358a43759c..680369af59fd2aa36bf1cf4e28b598854383abe3 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -62,6 +62,14 @@ import org.slf4j.Logger; + + public class Main { + private static final Logger LOGGER = LogUtils.getLogger(); ++ // DivineMC start - Log experimental warning ++ static { ++ io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo(); ++ if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) { ++ LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName()); ++ } ++ } ++ // DivineMC end - Log experimental warning + + @SuppressForbidden( + reason = "System.out needed before bootstrap" +@@ -114,6 +122,18 @@ public class Main { + org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); // Purpur - register minecraft debug commands + // Purpur end - Add toggle for enchant level clamping - load config files early + ++ // DivineMC start - Server startup settings ++ org.bukkit.configuration.file.YamlConfiguration divinemcConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("divinemc-settings")); ++ boolean divinemcNativeMathEnabled = divinemcConfiguration.getBoolean("settings.chunk-generation.native-acceleration-enabled", true); ++ if (divinemcNativeMathEnabled) { ++ try { ++ Class.forName("org.bxteam.divinemc.math.NativeLoader").getField("lookup").get(null); ++ } catch (Throwable e) { ++ e.printStackTrace(); ++ } ++ } ++ // DivineMC end - Server startup settings ++ + io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper + Bootstrap.bootStrap(); + Bootstrap.validate(); +diff --git a/net/minecraft/server/gui/MinecraftServerGui.java b/net/minecraft/server/gui/MinecraftServerGui.java +index 614c7d9f673c926562acc8fa3b3788623900db41..33456c7c106abbddf743e1203a6e8122cf10b797 100644 +--- a/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/net/minecraft/server/gui/MinecraftServerGui.java +@@ -51,7 +51,7 @@ public class MinecraftServerGui extends JComponent { + } catch (Exception var3) { + } + +- final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI ++ final JFrame jFrame = new JFrame("DivineMC Minecraft server"); // Purpur - Improve GUI // DivineMC - Rebrand + final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server); + jFrame.setDefaultCloseOperation(2); + jFrame.add(minecraftServerGui); +@@ -59,7 +59,7 @@ public class MinecraftServerGui extends JComponent { + jFrame.setLocationRelativeTo(null); + jFrame.setVisible(true); + // Paper start - Improve ServerGUI +- jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI ++ jFrame.setName("DivineMC Minecraft server"); // Purpur - Improve GUI // DivineMC - Rebrand + try { + jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); + } catch (java.io.IOException ignore) { +@@ -69,7 +69,7 @@ public class MinecraftServerGui extends JComponent { + @Override + public void windowClosing(WindowEvent event) { + if (!minecraftServerGui.isClosing.getAndSet(true)) { +- jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI ++ jFrame.setTitle("DivineMC Minecraft server - shutting down!"); // Purpur - Improve GUI // DivineMC - Rebrand + server.halt(true); + minecraftServerGui.runFinalizers(); + } diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 80ed0e4b8c867d031413b4140e52af1342fdcb54..6ebd1300c2561116b83cb2472ac7939ead36d576 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java diff --git a/divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch b/divinemc-server/minecraft-patches/features/0002-Configuration.patch similarity index 68% rename from divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch rename to divinemc-server/minecraft-patches/features/0002-Configuration.patch index 54bb57d..296c93f 100644 --- a/divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch +++ b/divinemc-server/minecraft-patches/features/0002-Configuration.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 16:19:14 +0300 -Subject: [PATCH] DivineMC Configuration +Date: Mon, 27 Jan 2025 20:22:52 +0300 +Subject: [PATCH] Configuration diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index db82329935145ec12fa47eef730613ee9c7666ee..0eecc41b02f205022a717691a18114d5c091bc3d 100644 +index 4f90ebcd86fba38dec313143e36614e992c7dbc7..959d87f4cd1efe8cf591e98c7d32728067f7117c 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -243,6 +243,17 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -13,36 +13,36 @@ index db82329935145ec12fa47eef730613ee9c7666ee..0eecc41b02f205022a717691a18114d5 org.purpurmc.purpur.PurpurConfig.registerCommands(); */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage + -+ // DivineMC start - DivineMC configuration ++ // DivineMC start - Configuration + try { -+ org.bxteam.divinemc.configuration.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings")); ++ org.bxteam.divinemc.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings")); + } catch (Exception e) { + DedicatedServer.LOGGER.error("Unable to load server configuration", e); + return false; + } -+ org.bxteam.divinemc.command.DivineCommands.registerCommands(this); // DivineMC - register commands -+ // DivineMC end - DivineMC configuration ++ org.bxteam.divinemc.command.DivineCommands.registerCommands(this); ++ // DivineMC end - Configuration + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now this.setPvpAllowed(properties.pvp); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 66d1525eff7e23c49d0a2d4b217a58f88c2a3d6c..3ed76a5eabbe35f23d1809b669fc94dd63399764 100644 +index 0fe8f4601eedfa68c38ebadc7847ba7a07ff6fb6..3856bbe579ef6df2f220c46bc69461cab026a131 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -171,6 +171,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files -+ public final org.bxteam.divinemc.configuration.DivineWorldConfig divinemcConfig; // DivineMC - DivineMC config files ++ public final org.bxteam.divinemc.DivineWorldConfig divineConfig; // DivineMC - Configuration public static BlockPos lastPhysicsProblem; // Spigot - private int tileTickPosition; - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions -@@ -896,6 +897,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -898,6 +899,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // Purpur - Purpur config files -+ this.divinemcConfig = new org.bxteam.divinemc.configuration.DivineWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // DivineMC - DivineMC configuration ++ this.divineConfig = new org.bxteam.divinemc.DivineWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // DivineMC - Configuration this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); diff --git a/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch b/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch deleted file mode 100644 index 01012c1..0000000 --- a/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 00:14:45 +0300 -Subject: [PATCH] Add missing purpur config options - - -diff --git a/net/minecraft/world/entity/animal/allay/Allay.java b/net/minecraft/world/entity/animal/allay/Allay.java -index 22e0fad86da2e7b932863ef30182355aa41424a1..d4eee24b5ab89f82e4e90f551d6651330d4508cb 100644 ---- a/net/minecraft/world/entity/animal/allay/Allay.java -+++ b/net/minecraft/world/entity/animal/allay/Allay.java -@@ -181,6 +181,18 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - } - // Purpur end - Configurable entity base attributes - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.allayTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.allayAlwaysDropExp; -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -diff --git a/net/minecraft/world/entity/animal/camel/Camel.java b/net/minecraft/world/entity/animal/camel/Camel.java -index 1d7ae2a08968860636918e7c66b60139a9d761b4..126af60a694600d40e3ae6bd39e94e55dd9d0b6e 100644 ---- a/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/net/minecraft/world/entity/animal/camel/Camel.java -@@ -97,6 +97,18 @@ public class Camel extends AbstractHorse { - } - // Purpur end - Make entity breeding times configurable - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.camelTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.camelAlwaysDropExp; -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - public void addAdditionalSaveData(CompoundTag compound) { - super.addAdditionalSaveData(compound); -diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java -index c4ea9485294b7dec2582c638802f003ad70659b6..d286d4a45b6c8d5c684ad11500d2ad1a10a70c18 100644 ---- a/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/net/minecraft/world/entity/animal/frog/Frog.java -@@ -165,6 +165,23 @@ public class Frog extends Animal implements VariantHolder> { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.frogTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.frogAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.frogMaxHealth); -+ } -+ // DivineMC end -+ - // Purpur start - Make entity breeding times configurable - @Override - public int getPurpurBreedTime() { -diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java -index e888e606b4b14fa6485de7426bc146b6005962af..a4383a7d41c91f9e478c7e221828ba92437af06c 100644 ---- a/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -107,6 +107,23 @@ public class Tadpole extends AbstractFish { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.tadpoleTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.tadpoleAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.tadpoleMaxHealth); -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - protected PathNavigation createNavigation(Level level) { - return new WaterBoundPathNavigation(this, level); -diff --git a/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index 11a5da22149a61ca48bbb0a8ed10b71e91a5cc98..ed1ffc1578e50fa6fedc6124fe016a1535c0e968 100644 ---- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -120,6 +120,18 @@ public class Sniffer extends Animal { - } - // Purpur end - Make entity breeding times configurable - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.snifferTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.snifferAlwaysDropExp; -+ } -+ // DivineMC end -+ - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java -index f968e5c99bdb23b268bc34ea1ba5d54ae9ad0ff9..f74c784906208034f51b31bd9aba45733c3ebebe 100644 ---- a/net/minecraft/world/entity/monster/warden/Warden.java -+++ b/net/minecraft/world/entity/monster/warden/Warden.java -@@ -155,6 +155,23 @@ public class Warden extends Monster implements VibrationSystem { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.wardenTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.wardenAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.wardenMaxHealth); -+ } -+ // DivineMC end -+ - @Override - public Packet getAddEntityPacket(ServerEntity entity) { - return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0); -diff --git a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -index b230955ae880d84fde40b4feffa5caf3c4449eb7..5b88c69427d5915ff547e4caf7b5656e96912e93 100644 ---- a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -+++ b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -@@ -26,8 +26,8 @@ import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.storage.loot.LootTable; - - public abstract class AbstractChestBoat extends AbstractBoat implements HasCustomInventoryScreen, ContainerEntity { -- private static final int CONTAINER_SIZE = 27; -- private NonNullList itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); -+ private static final int CONTAINER_SIZE = org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9; // DivineMC - Add missing purpur config options -+ private NonNullList itemStacks = NonNullList.withSize(org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9, ItemStack.EMPTY); // DivineMC - Add missing purpur config options - @Nullable - private ResourceKey lootTable; - private long lootTableSeed; -@@ -118,7 +118,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto - - @Override - public int getContainerSize() { -- return 27; -+ return org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9; // DivineMC - Add missing purpur config options - } - - @Override diff --git a/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch b/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch new file mode 100644 index 0000000..0ba337c --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch @@ -0,0 +1,433 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 00:54:57 +0300 +Subject: [PATCH] Implement Secure Seed + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +diff --git a/net/minecraft/server/commands/SeedCommand.java b/net/minecraft/server/commands/SeedCommand.java +index a65affc41a4fc299bc2281f0f53f2e075633899d..7284c984f9309d4a862fe6f89df993c9a6a9efa6 100644 +--- a/net/minecraft/server/commands/SeedCommand.java ++++ b/net/minecraft/server/commands/SeedCommand.java +@@ -12,6 +12,17 @@ public class SeedCommand { + long seed = context.getSource().getLevel().getSeed(); + Component component = ComponentUtils.copyOnClickText(String.valueOf(seed)); + context.getSource().sendSuccess(() -> Component.translatable("commands.seed.success", component), false); ++ ++ // DivineMC start - Implement Secure Seed ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ su.plo.matter.Globals.setupGlobals(context.getSource().getLevel()); ++ String seedStr = su.plo.matter.Globals.seedToString(su.plo.matter.Globals.worldSeed); ++ Component featureSeedComponent = ComponentUtils.copyOnClickText(seedStr); ++ ++ context.getSource().sendSuccess(() -> Component.translatable(("Feature seed: %s"), featureSeedComponent), false); ++ } ++ // DivineMC end - Implement Secure Seed ++ + return (int)seed; + })); + } +diff --git a/net/minecraft/server/dedicated/DedicatedServerProperties.java b/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 5748658abf0b90812005ae9d426df92daf5532f0..8b4b91be368b4195326eeb1c714d713a95f28d76 100644 +--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -114,7 +114,17 @@ public class DedicatedServerProperties extends Settings GsonHelper.parse(!property.isEmpty() ? property : "{}"), new JsonObject()), + this.get("level-type", property -> property.toLowerCase(Locale.ROOT), WorldPresets.NORMAL.location().toString()) +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 6540b2d6a1062d883811ce240c49d30d1925b291..2678bf59d557f085c7265e2f3eb038647723d35e 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -652,6 +652,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + public ChunkGenerator getGenerator() { ++ su.plo.matter.Globals.setupGlobals(level); // DivineMC - Implement Secure Seed + return this.chunkMap.generator(); + } + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 59d03ddc42d53e2b825abe0cf2ab24e85d586a19..dd113d67356177a8d98ea10a8b6d4a4d5159674c 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -634,6 +634,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + chunkGenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkGenerator, gen); + } + // CraftBukkit end ++ su.plo.matter.Globals.setupGlobals(this); // DivineMC - Implement Secure Seed + boolean flag = server.forceSynchronousWrites(); + DataFixer fixerUpper = server.getFixerUpper(); + // Paper - rewrite chunk system +diff --git a/net/minecraft/world/entity/monster/Slime.java b/net/minecraft/world/entity/monster/Slime.java +index 240a54b210e23d5b79e6bcaf3806aa454668135d..66dffb8a2c6725ea2502b498044b854f043f3c73 100644 +--- a/net/minecraft/world/entity/monster/Slime.java ++++ b/net/minecraft/world/entity/monster/Slime.java +@@ -423,8 +423,13 @@ public class Slime extends Mob implements Enemy { + return false; + } + +- ChunkPos chunkPos = new ChunkPos(pos); +- boolean flag = level.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel) level).getSeed(), level.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper ++ ChunkPos chunkPos = new ChunkPos(pos); ++ // DivineMC start - Implement Secure Seed ++ boolean isSlimeChunk = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? level.getChunk(chunkPos.x, chunkPos.z).isSlimeChunk() ++ : WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel) level).getSeed(), level.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper ++ boolean flag = level.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; ++ // DivineMC end - Implement Secure Seed + // Paper start - Replace rules for Height in Slime Chunks + final double maxHeightSlimeChunk = level.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; + if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { +diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java +index 6d565b52552534ce9cacfc35ad1bf4adcb69eac3..80a0f5524e91e55d716e93c29e199d9816b0072a 100644 +--- a/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -82,6 +82,10 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + public final Map blockEntities = new Object2ObjectOpenHashMap<>(); + protected final LevelHeightAccessor levelHeightAccessor; + protected final LevelChunkSection[] sections; ++ // DivineMC start - Implement Secure Seed ++ private boolean slimeChunk; ++ private boolean hasComputedSlimeChunk; ++ // DivineMC end - Implement Secure Seed + // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading. + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); +@@ -191,6 +195,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + return GameEventListenerRegistry.NOOP; + } + ++ // DivineMC start - Implement Secure Seed ++ public boolean isSlimeChunk() { ++ if (!hasComputedSlimeChunk) { ++ hasComputedSlimeChunk = true; ++ slimeChunk = su.plo.matter.WorldgenCryptoRandom.seedSlimeChunk(chunkPos.x, chunkPos.z).nextInt(10) == 0; ++ } ++ ++ return slimeChunk; ++ } ++ // DivineMC end - Implement Secure Seed ++ + public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper + @Nullable + public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving); +diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java +index 6ed51cf42b5864194d671b5b56f5b9bdf0291dc0..7e0b602e9fd9e3b3f60014ab179b3a82e3bf5c2a 100644 +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -343,7 +343,11 @@ public abstract class ChunkGenerator { + Registry registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Map> map = registry.stream().collect(Collectors.groupingBy(structure1 -> structure1.step().ordinal())); + List list = this.featuresPerStep.get(); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(blockPos.getX(), blockPos.getZ(), su.plo.matter.Globals.Salt.UNDEFINED, 0) ++ : new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); ++ // DivineMC end - Implement Secure Seed + long l = worldgenRandom.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + Set> set = new ObjectArraySet<>(); + ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach(chunkPos -> { +@@ -556,8 +560,18 @@ public abstract class ChunkGenerator { + } else { + ArrayList list1 = new ArrayList<>(list.size()); + list1.addAll(list); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), pos.x, pos.z); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ pos.x, pos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, 0 ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ ++ worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), pos.x, pos.z); ++ } ++ // DivineMC end - Implement Secure Seed + int i = 0; + + for (StructureSet.StructureSelectionEntry structureSelectionEntry1 : list1) { +diff --git a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +index 619b98e42e254c0c260c171a26a2472ddf59b885..a3093ef1b47544f2eb02d366040526697dafc8db 100644 +--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -205,14 +205,21 @@ public class ChunkGeneratorStructureState { + List> list = new ArrayList<>(count); + int spread = placement.spread(); + HolderSet holderSet = placement.preferredBiomes(); +- RandomSource randomSource = RandomSource.create(); +- // Paper start - Add missing structure set seed configs +- if (this.conf.strongholdSeed != null && structureSet.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { +- randomSource.setSeed(this.conf.strongholdSeed); +- } else { +- // Paper end - Add missing structure set seed configs +- randomSource.setSeed(this.concentricRingsSeed); +- } // Paper - Add missing structure set seed configs ++ // DivineMC start - Implement Secure Seed ++ RandomSource randomSource = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.STRONGHOLDS, 0) ++ : RandomSource.create(); ++ ++ if (!org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ // Paper start - Add missing structure set seed configs ++ if (this.conf.strongholdSeed != null && structureSet.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { ++ randomSource.setSeed(this.conf.strongholdSeed); ++ } else { ++ // Paper end - Add missing structure set seed configs ++ randomSource.setSeed(this.concentricRingsSeed); ++ } // Paper - Add missing structure set seed configs ++ } ++ // DivineMC end - Implement Secure Seed + double d = randomSource.nextDouble() * Math.PI * 2.0; + int i = 0; + int i1 = 0; +diff --git a/net/minecraft/world/level/chunk/status/ChunkStep.java b/net/minecraft/world/level/chunk/status/ChunkStep.java +index b8348976e80578d9eff64eea68c04c603fed49ad..9494e559113798fe451a6d0226be3ae0449021dc 100644 +--- a/net/minecraft/world/level/chunk/status/ChunkStep.java ++++ b/net/minecraft/world/level/chunk/status/ChunkStep.java +@@ -60,6 +60,7 @@ public final class ChunkStep implements ca.spottedleaf.moonrise.patches.chunk_sy + } + + public CompletableFuture apply(WorldGenContext worldGenContext, StaticCache2D cache, ChunkAccess chunk) { ++ su.plo.matter.Globals.setupGlobals(worldGenContext.level()); // DivineMC - Implement Secure Seed + if (chunk.getPersistedStatus().isBefore(this.targetStatus)) { + ProfiledDuration profiledDuration = JvmProfiler.INSTANCE + .onChunkGenerate(chunk.getPos(), worldGenContext.level().dimension(), this.targetStatus.getName()); +diff --git a/net/minecraft/world/level/levelgen/WorldOptions.java b/net/minecraft/world/level/levelgen/WorldOptions.java +index c92508741439a8d0d833ea02d0104416adb83c92..630f6c409db349819fc5fd19a3d78fadb4cfb6d6 100644 +--- a/net/minecraft/world/level/levelgen/WorldOptions.java ++++ b/net/minecraft/world/level/levelgen/WorldOptions.java +@@ -9,17 +9,28 @@ import net.minecraft.util.RandomSource; + import org.apache.commons.lang3.StringUtils; + + public class WorldOptions { ++ // DivineMC start - Implement Secure Seed ++ private static final boolean isSecureSeedEnabled = org.bxteam.divinemc.DivineConfig.enableSecureSeed; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( +- instance -> instance.group( ++ instance -> isSecureSeedEnabled ++ ? instance.group( + Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), ++ Codec.LONG_STREAM.fieldOf("feature_seed").stable().forGetter(WorldOptions::featureSeedStream), + Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), + Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), +- Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(worldOptions -> worldOptions.legacyCustomOptions) +- ) +- .apply(instance, instance.stable(WorldOptions::new)) ++ Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(generatorOptions -> generatorOptions.legacyCustomOptions)).apply(instance, instance.stable(WorldOptions::new)) ++ : instance.group( ++ Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), ++ Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), ++ Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), ++ Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(worldOptions -> worldOptions.legacyCustomOptions)).apply(instance, instance.stable(WorldOptions::new)) + ); +- public static final WorldOptions DEMO_OPTIONS = new WorldOptions("North Carolina".hashCode(), true, true); ++ public static final WorldOptions DEMO_OPTIONS = isSecureSeedEnabled ++ ? new WorldOptions("North Carolina".hashCode(), su.plo.matter.Globals.createRandomWorldSeed(), true, true) ++ : new WorldOptions("North Carolina".hashCode(), true, true); ++ // DivineMC end - Implement Secure Seed + private final long seed; ++ private long[] featureSeed = su.plo.matter.Globals.createRandomWorldSeed(); // DivineMC - Implement Secure Seed + private final boolean generateStructures; + private final boolean generateBonusChest; + private final Optional legacyCustomOptions; +@@ -28,9 +39,21 @@ public class WorldOptions { + this(seed, generateStructures, generateBonusChest, Optional.empty()); + } + ++ // DivineMC start - Implement Secure Seed ++ public WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest) { ++ this(seed, featureSeed, generateStructures, bonusChest, Optional.empty()); ++ } ++ + public static WorldOptions defaultWithRandomSeed() { +- return new WorldOptions(randomSeed(), true, false); ++ return isSecureSeedEnabled ++ ? new WorldOptions(randomSeed(), su.plo.matter.Globals.createRandomWorldSeed(), true, false) ++ : new WorldOptions(randomSeed(), true, false); ++ } ++ ++ private WorldOptions(long seed, java.util.stream.LongStream featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, featureSeed.toArray(), generateStructures, bonusChest, legacyCustomOptions); + } ++ // DivineMC end - Implement Secure Seed + + public static WorldOptions testWorldWithRandomSeed() { + return new WorldOptions(randomSeed(), false, false); +@@ -43,10 +66,27 @@ public class WorldOptions { + this.legacyCustomOptions = legacyCustomOptions; + } + ++ // DivineMC start - Implement Secure Seed ++ private WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, generateStructures, bonusChest, legacyCustomOptions); ++ this.featureSeed = featureSeed; ++ } ++ // DivineMC end - Implement Secure Seed ++ + public long seed() { + return this.seed; + } + ++ // DivineMC start - Implement Secure Seed ++ public long[] featureSeed() { ++ return this.featureSeed; ++ } ++ ++ public java.util.stream.LongStream featureSeedStream() { ++ return java.util.stream.LongStream.of(this.featureSeed); ++ } ++ // DivineMC end - Implement Secure Seed ++ + public boolean generateStructures() { + return this.generateStructures; + } +@@ -59,17 +99,25 @@ public class WorldOptions { + return this.legacyCustomOptions.isPresent(); + } + ++ // DivineMC start - Implement Secure Seed + public WorldOptions withBonusChest(boolean generateBonusChest) { +- return new WorldOptions(this.seed, this.generateStructures, generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(this.seed, this.featureSeed, this.generateStructures, generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(this.seed, this.generateStructures, generateBonusChest, this.legacyCustomOptions); + } + + public WorldOptions withStructures(boolean generateStructures) { +- return new WorldOptions(this.seed, generateStructures, this.generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(this.seed, this.featureSeed, generateStructures, this.generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(this.seed, generateStructures, this.generateBonusChest, this.legacyCustomOptions); + } + + public WorldOptions withSeed(OptionalLong seed) { +- return new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(seed.orElse(randomSeed()), su.plo.matter.Globals.createRandomWorldSeed(), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); + } ++ // DivineMC end - Implement Secure Seed + + public static OptionalLong parseSeed(String seed) { + seed = seed.trim(); +diff --git a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +index 38475f6975533909924c8d54f438cf43cdfe31a3..6759df026a29810021ddb37f3ddb62382b83a94e 100644 +--- a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +@@ -41,7 +41,11 @@ public class GeodeFeature extends Feature { + int i1 = geodeConfiguration.maxGenOffset; + List> list = Lists.newLinkedList(); + int i2 = geodeConfiguration.distributionPoints.sample(randomSource); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) ++ : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ // DivineMC end - Implement Secure Seed + NormalNoise normalNoise = NormalNoise.create(worldgenRandom, -4, 1.0); + List list1 = Lists.newLinkedList(); + double d = (double)i2 / geodeConfiguration.outerWallDistance.getMaxValue(); +diff --git a/net/minecraft/world/level/levelgen/structure/Structure.java b/net/minecraft/world/level/levelgen/structure/Structure.java +index 8328e864c72b7a358d6bb1f33459b8c4df2ecb1a..7c35949c9a2f102e9da246fd5bf81d9e14eb36ca 100644 +--- a/net/minecraft/world/level/levelgen/structure/Structure.java ++++ b/net/minecraft/world/level/levelgen/structure/Structure.java +@@ -249,6 +249,14 @@ public abstract class Structure { + } + + private static WorldgenRandom makeRandom(long seed, ChunkPos chunkPos) { ++ // DivineMC start - Implement Secure Seed ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ return new su.plo.matter.WorldgenCryptoRandom( ++ chunkPos.x, chunkPos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, seed ++ ); ++ } ++ // DivineMC end - Implement Secure Seed ++ + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); + worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); + return worldgenRandom; +diff --git a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +index ee0d9dddb36b6879fa113299e24f1aa3b2b151cc..f01d96895ab348971fb31b614026fb3d106e9a3e 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +@@ -67,8 +67,17 @@ public class RandomSpreadStructurePlacement extends StructurePlacement { + public ChunkPos getPotentialStructureChunk(long seed, int regionX, int regionZ) { + int i = Math.floorDiv(regionX, this.spacing); + int i1 = Math.floorDiv(regionZ, this.spacing); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(seed, i, i1, this.salt()); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ i, i1, su.plo.matter.Globals.Salt.POTENTIONAL_FEATURE, this.salt ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom.setLargeFeatureWithSalt(seed, i, i1, this.salt()); ++ } ++ // DivineMC end - Implement Secure Seed + int i2 = this.spacing - this.separation; + int i3 = this.spreadType.evaluate(worldgenRandom, i2); + int i4 = this.spreadType.evaluate(worldgenRandom, i2); +diff --git a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +index 670335a7bbfbc9da64c389977498c22dfcd03251..7174a9767cbc94544be81c74d6468f3f73386edc 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -118,8 +118,17 @@ public abstract class StructurePlacement { + public abstract StructurePlacementType type(); + + private static boolean probabilityReducer(long levelSeed, int regionX, int regionZ, int salt, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, salt); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ regionX, regionZ, su.plo.matter.Globals.Salt.UNDEFINED, salt ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom.setLargeFeatureWithSalt(levelSeed, salt, regionX, regionZ); ++ } ++ // DivineMC end - Implement Secure Seed + return worldgenRandom.nextFloat() < probability; + } + +diff --git a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +index eb85edaa3b7fab4f11545b0fa8bfea882dedb67d..63143ceec98f7a84ec4064d05e8f88c11200172f 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +@@ -64,7 +64,11 @@ public class JigsawPlacement { + ChunkGenerator chunkGenerator = context.chunkGenerator(); + StructureTemplateManager structureTemplateManager = context.structureTemplateManager(); + LevelHeightAccessor levelHeightAccessor = context.heightAccessor(); +- WorldgenRandom worldgenRandom = context.random(); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(context.chunkPos().x, context.chunkPos().z, su.plo.matter.Globals.Salt.JIGSAW_PLACEMENT, 0) ++ : context.random(); ++ // DivineMC end - Implement Secure Seed + Registry registry = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL); + Rotation random = Rotation.getRandom(worldgenRandom); + StructureTemplatePool structureTemplatePool = startPool.unwrapKey() diff --git a/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch b/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch new file mode 100644 index 0000000..dca0f27 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch @@ -0,0 +1,810 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:04:55 +0300 +Subject: [PATCH] Async Pathfinding + +Original code by Bloom-host, licensed under GPL v3 +You can find the original code on https://github.com/Bloom-host/Petal + +Makes most pathfinding-related work happen asynchronously + +diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2a361e097 100644 +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -94,21 +94,54 @@ public class AcquirePoi { + } + } + // Paper end - optimise POI access +- Path path = findPathToPois(mob, set); +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- poiManager.getType(target).ifPresent(holder -> { +- poiManager.take(acquirablePois, (holder1, blockPos) -> blockPos.equals(target), target, 1); +- memoryAccessor.set(GlobalPos.of(level.dimension(), target)); +- entityEventId.ifPresent(id -> level.broadcastEntityEvent(mob, id)); +- map.clear(); +- DebugPackets.sendPoiTicketCountPacket(level, target); ++ // DivineMC start - Async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // await on path async ++ Path possiblePath = findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ // read canReach check ++ if (path == null || !path.canReach()) { ++ for (Pair, BlockPos> pair : set) { ++ map.computeIfAbsent( ++ pair.getSecond().asLong(), ++ m -> new JitteredLinearRetry(mob.level().random, time) ++ ); ++ } ++ return; ++ } ++ BlockPos blockPos = path.getTarget(); ++ poiManager.getType(blockPos).ifPresent(poiType -> { ++ poiManager.take(acquirablePois, ++ (holder, blockPos2) -> blockPos2.equals(blockPos), ++ blockPos, ++ 1 ++ ); ++ memoryAccessor.set(GlobalPos.of(level.dimension(), blockPos)); ++ entityEventId.ifPresent(status -> level.broadcastEntityEvent(mob, status)); ++ map.clear(); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ }); + }); + } else { +- for (Pair, BlockPos> pair : set) { +- map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); ++ Path path = findPathToPois(mob, set); ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ poiManager.getType(target).ifPresent(holder -> { ++ poiManager.take(acquirablePois, (holder1, blockPos) -> blockPos.equals(target), target, 1); ++ memoryAccessor.set(GlobalPos.of(level.dimension(), target)); ++ entityEventId.ifPresent(id -> level.broadcastEntityEvent(mob, id)); ++ map.clear(); ++ DebugPackets.sendPoiTicketCountPacket(level, target); ++ }); ++ } else { ++ for (Pair, BlockPos> pair : set) { ++ map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); ++ } + } + } ++ // DivineMC end - Async path processing + + return true; + } +diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac99260b9ad 100644 +--- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java ++++ b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +@@ -21,6 +21,7 @@ public class MoveToTargetSink extends Behavior { + private int remainingCooldown; + @Nullable + private Path path; ++ private boolean finishedProcessing; // DivineMC - async path processing + @Nullable + private BlockPos lastTargetPos; + private float speedModifier; +@@ -53,9 +54,11 @@ public class MoveToTargetSink extends Behavior { + Brain brain = owner.getBrain(); + WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); + boolean flag = this.reachedTarget(owner, walkTarget); +- if (!flag && this.tryComputePath(owner, walkTarget, level.getGameTime())) { ++ if (!org.bxteam.divinemc.DivineConfig.asyncPathfinding && !flag && this.tryComputePath(owner, walkTarget, level.getGameTime())) { // DivineMC - async path processing + this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); + return true; ++ } else if (org.bxteam.divinemc.DivineConfig.asyncPathfinding && !flag) { // DivineMC - async pathfinding ++ return true; + } else { + brain.eraseMemory(MemoryModuleType.WALK_TARGET); + if (flag) { +@@ -69,6 +72,7 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected boolean canStillUse(ServerLevel level, Mob entity, long gameTime) { ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding && !this.finishedProcessing) return true; // DivineMC - wait for processing + if (this.path != null && this.lastTargetPos != null) { + Optional memory = entity.getBrain().getMemory(MemoryModuleType.WALK_TARGET); + boolean flag = memory.map(MoveToTargetSink::isWalkTargetSpectator).orElse(false); +@@ -95,27 +99,98 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected void start(ServerLevel level, Mob entity, long gameTime) { ++ // DivineMC start - start processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ Brain brain = entity.getBrain(); ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); ++ ++ this.finishedProcessing = false; ++ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ this.path = this.computePath(entity, walkTarget); ++ return; ++ } ++ // DivineMC end - start processing + entity.getBrain().setMemory(MemoryModuleType.PATH, this.path); + entity.getNavigation().moveTo(this.path, (double)this.speedModifier); + } + + @Override + protected void tick(ServerLevel level, Mob owner, long gameTime) { +- Path path = owner.getNavigation().getPath(); +- Brain brain = owner.getBrain(); +- if (this.path != path) { +- this.path = path; +- brain.setMemory(MemoryModuleType.PATH, path); +- } ++ // DivineMC start - Async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ if (this.path != null && !this.path.isProcessed()) return; // wait for processing + +- if (path != null && this.lastTargetPos != null) { +- WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); +- if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0 && this.tryComputePath(owner, walkTarget, level.getGameTime())) { +- this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); +- this.start(level, owner, gameTime); ++ if (!this.finishedProcessing) { ++ this.finishedProcessing = true; ++ ++ Brain brain = owner.getBrain(); ++ boolean canReach = this.path != null && this.path.canReach(); ++ if (canReach) { ++ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); ++ } else if (!brain.hasMemoryValue(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)) { ++ brain.setMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, gameTime); ++ } ++ ++ if (!canReach) { ++ Optional walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET); ++ ++ if (!walkTarget.isPresent()) return; ++ ++ BlockPos blockPos = walkTarget.get().getTarget().currentBlockPosition(); ++ Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob) owner, 10, 7, Vec3.atBottomCenterOf(blockPos), (float) Math.PI / 2F); ++ if (vec3 != null) { ++ // try recalculating the path using a random position ++ this.path = owner.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); ++ this.finishedProcessing = false; ++ return; ++ } ++ } ++ ++ owner.getBrain().setMemory(MemoryModuleType.PATH, this.path); ++ owner.getNavigation().moveTo(this.path, this.speedModifier); + } ++ ++ Path path = owner.getNavigation().getPath(); ++ Brain brain = owner.getBrain(); ++ ++ if (path != null && this.lastTargetPos != null && brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) { ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); // we know isPresent = true ++ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D) { ++ this.start(level, owner, gameTime); ++ } ++ } ++ } else { ++ Path path = owner.getNavigation().getPath(); ++ Brain brain = owner.getBrain(); ++ if (this.path != path) { ++ this.path = path; ++ brain.setMemory(MemoryModuleType.PATH, path); ++ } ++ ++ if (path != null && this.lastTargetPos != null) { ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); ++ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0 ++ && this.tryComputePath(owner, walkTarget, level.getGameTime())) { ++ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ this.start(level, owner, gameTime); ++ } ++ } ++ } ++ // DivineMC end - Async path processing ++ } ++ ++ // DivineMC start - Async path processing ++ @Nullable ++ private Path computePath(Mob entity, WalkTarget walkTarget) { ++ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); ++ this.speedModifier = walkTarget.getSpeedModifier(); ++ Brain brain = entity.getBrain(); ++ if (this.reachedTarget(entity, walkTarget)) { ++ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); + } ++ return entity.getNavigation().createPath(blockPos, 0); + } ++ // DivineMC end - Async path processing + + private boolean tryComputePath(Mob mob, WalkTarget target, long time) { + BlockPos blockPos = target.getTarget().currentBlockPosition(); +diff --git a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c0cb53645 100644 +--- a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java ++++ b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +@@ -60,17 +60,38 @@ public class SetClosestHomeAsWalkTarget { + poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY + ) + .collect(Collectors.toSet()); +- Path path = AcquirePoi.findPathToPois(mob, set); +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- Optional> type = poiManager.getType(target); +- if (type.isPresent()) { +- walkTarget.set(new WalkTarget(target, speedModifier, 1)); +- DebugPackets.sendPoiTicketCountPacket(level, target); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // await on path async ++ Path possiblePath = AcquirePoi.findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ if (path == null || !path.canReach() || mutableInt.getValue() < 5) { // read canReach check ++ map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); ++ return; ++ } ++ BlockPos blockPos = path.getTarget(); ++ Optional> optional2 = poiManager.getType(blockPos); ++ if (optional2.isPresent()) { ++ walkTarget.set(new WalkTarget(blockPos, speedModifier, 1)); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ } ++ }); ++ } else { ++ Path path = AcquirePoi.findPathToPois(mob, set); ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ Optional> type = poiManager.getType(target); ++ if (type.isPresent()) { ++ walkTarget.set(new WalkTarget(target, speedModifier, 1)); ++ DebugPackets.sendPoiTicketCountPacket(level, target); ++ } ++ } else if (mutableInt.getValue() < 5) { ++ map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); + } +- } else if (mutableInt.getValue() < 5) { +- map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); + } ++ // DivineMC end - async path processing + + return true; + } else { +diff --git a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java +index d8f532c5e68ff4dff933556c4f981e9474c044e6..37f3d3888ea2a862d006cf2b201f9715bcb8ce1e 100644 +--- a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java ++++ b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java +@@ -56,7 +56,7 @@ public abstract class DoorInteractGoal extends Goal { + } else { + GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.mob.getNavigation(); + Path path = groundPathNavigation.getPath(); +- if (path != null && !path.isDone()) { ++ if (path != null && path.isProcessed() && !path.isDone()) { // DivineMC - async path processing + for (int i = 0; i < Math.min(path.getNextNodeIndex() + 2, path.getNodeCount()); i++) { + Node node = path.getNode(i); + this.doorPos = new BlockPos(node.x, node.y + 1, node.z); +diff --git a/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java b/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java +index 66a02fe7594522ef391d67e09856bf3f70fe597d..0cd0d1059ac3612ba34f1a47a5023a8d07ee612f 100644 +--- a/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java +@@ -12,9 +12,25 @@ public class AmphibiousPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ AmphibiousNodeEvaluator nodeEvaluator = new AmphibiousNodeEvaluator(false); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new AmphibiousNodeEvaluator(false); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +index 71ea68b56b3069bdf8e47931156b6ef49ea8ce5d..0e680d3954d5847125484c2a93d9cd8e133ad8c3 100644 +--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +@@ -16,9 +16,25 @@ public class FlyingPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ FlyNodeEvaluator nodeEvaluator = new FlyNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new FlyNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +@@ -48,6 +64,7 @@ public class FlyingPathNavigation extends PathNavigation { + if (this.hasDelayedRecomputation) { + this.recomputePath(); + } ++ if (this.path != null && !this.path.isProcessed()) return; // DivineMC - async path processing + + if (!this.isDone()) { + if (this.canUpdatePath()) { +diff --git a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +index 045cfafb3afe8271d60852ae3c7cdcb039b44d4f..f544bf28ac6531061da08c726684687416b9426c 100644 +--- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +@@ -24,9 +24,25 @@ public class GroundPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ protected static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index b44f2c49509d847817a78e9c4fb1499fb378054b..c0b61c38a82fd3de046b550a49d8cde6afa9d9af 100644 +--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -169,6 +169,10 @@ public abstract class PathNavigation { + return null; + } else if (!this.canUpdatePath()) { + return null; ++ // DivineMC start - catch early if it's still processing these positions let it keep processing ++ } else if (this.path instanceof org.bxteam.divinemc.entity.pathfinding.AsyncPath asyncPath && !asyncPath.isProcessed() && asyncPath.hasSameProcessingPositions(targets)) { ++ return this.path; ++ // DivineMC end - catch early if it's still processing these positions let it keep processing + } else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) { + return this.path; + } else { +@@ -195,12 +199,30 @@ public abstract class PathNavigation { + int i = (int)(followRange + regionOffset); + PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i)); + Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, targets, followRange, accuracy, this.maxVisitedNodesMultiplier); +- profilerFiller.pop(); +- if (path != null && path.getTarget() != null) { +- this.targetPos = path.getTarget(); +- this.reachRange = accuracy; +- this.resetStuckTimeout(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // assign early a target position. most calls will only have 1 position ++ if (!targets.isEmpty()) this.targetPos = targets.iterator().next(); ++ ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(path, processedPath -> { ++ // check that processing didn't take so long that we calculated a new path ++ if (processedPath != this.path) return; ++ ++ if (processedPath != null && processedPath.getTarget() != null) { ++ this.targetPos = processedPath.getTarget(); ++ this.reachRange = accuracy; ++ this.resetStuckTimeout(); ++ } ++ }); ++ } else { ++ profilerFiller.pop(); ++ if (path != null && path.getTarget() != null) { ++ this.targetPos = path.getTarget(); ++ this.reachRange = accuracy; ++ this.resetStuckTimeout(); ++ } + } ++ // DivineMC end - async path processing + + return path; + } +@@ -251,8 +273,8 @@ public abstract class PathNavigation { + if (this.isDone()) { + return false; + } else { +- this.trimPath(); +- if (this.path.getNodeCount() <= 0) { ++ if (path.isProcessed()) this.trimPath(); // DivineMC - only trim if processed ++ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // DivineMC - only check node count if processed + return false; + } else { + this.speedModifier = speed; +@@ -275,6 +297,7 @@ public abstract class PathNavigation { + if (this.hasDelayedRecomputation) { + this.recomputePath(); + } ++ if (this.path != null && !this.path.isProcessed()) return; // DivineMC - skip pathfinding if we're still processing + + if (!this.isDone()) { + if (this.canUpdatePath()) { +@@ -304,6 +327,7 @@ public abstract class PathNavigation { + } + + protected void followThePath() { ++ if (!this.path.isProcessed()) return; // DivineMC - skip if not processed + Vec3 tempMobPos = this.getTempMobPos(); + this.maxDistanceToWaypoint = this.mob.getBbWidth() > 0.75F ? this.mob.getBbWidth() / 2.0F : 0.75F - this.mob.getBbWidth() / 2.0F; + Vec3i nextNodePos = this.path.getNextNodePos(); +@@ -460,7 +484,7 @@ public abstract class PathNavigation { + public boolean shouldRecomputePath(BlockPos pos) { + if (this.hasDelayedRecomputation) { + return false; +- } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { ++ } else if (this.path != null && this.path.isProcessed() && !this.path.isDone() && this.path.getNodeCount() != 0) { // DivineMC - Skip if not processed + Node endNode = this.path.getEndNode(); + Vec3 vec3 = new Vec3((endNode.x + this.mob.getX()) / 2.0, (endNode.y + this.mob.getY()) / 2.0, (endNode.z + this.mob.getZ()) / 2.0); + return pos.closerToCenterThan(vec3, this.path.getNodeCount() - this.path.getNextNodeIndex()); +diff --git a/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java b/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java +index 2979846853898d78a2df19df2287da16dbe4ae71..504911c14aad5c53aeea2c71bda3978f022601dd 100644 +--- a/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java +@@ -15,11 +15,27 @@ public class WaterBoundPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ SwimNodeEvaluator nodeEvaluator = new SwimNodeEvaluator(nodeEvaluatorFeatures.allowBreaching()); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN; + this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching); + this.nodeEvaluator.setCanPassDoors(false); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..3a8b3c71f6fd0c0f57a3190dc8a0f808c6d1002b 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +@@ -57,17 +57,37 @@ public class NearestBedSensor extends Sensor { + java.util.List, 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(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Configurable villager search radius +- Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); + // Paper end - optimise POI access +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- Optional> type = poiManager.getType(target); +- if (type.isPresent()) { +- entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); ++ // DivineMC start - async pathfinding ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ // read canReach check ++ if ((path == null || !path.canReach()) && this.triedCount < 5) { ++ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); ++ return; ++ } ++ if (path == null) return; ++ ++ BlockPos blockPos = path.getTarget(); ++ Optional> optional = poiManager.getType(blockPos); ++ if (optional.isPresent()) { ++ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, blockPos); ++ } ++ }); ++ } else { ++ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ Optional> type = poiManager.getType(target); ++ if (type.isPresent()) { ++ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); ++ } ++ } else if (this.triedCount < 5) { ++ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); + } +- } else if (this.triedCount < 5) { +- this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); + } ++ // DivineMC end - async pathfinding + } + } + } +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index d5727999eb67ff30dbf47865d59452483338e170..ddbee0f0f42fae0a26321bb324d22f5e7520ae72 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -936,7 +936,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()) { // DivineMC - check processing + boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); + if (!flag) { + this.dropAndBlacklistHive(); +@@ -990,7 +990,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + return true; + } else { + Path path = Bee.this.navigation.getPath(); +- return path != null && path.getTarget().equals(pos) && path.canReach() && path.isDone(); ++ return path != null && path.isProcessed() && path.getTarget().equals(pos) && path.canReach() && path.isDone(); // DivineMC - ensure path is processed + } + } + } +diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java +index 143a740ce2e7f9d384b71b4d64e8b8218f60cba7..3c657179644750846180ab1c45250a9e17dda396 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -480,6 +480,17 @@ public class Frog extends Animal implements VariantHolder> { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ Frog.FrogNodeEvaluator nodeEvaluator = new Frog.FrogNodeEvaluator(true); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + public boolean canCutCorner(PathType pathType) { + return pathType != PathType.WATER_BORDER && super.canCutCorner(pathType); +@@ -488,6 +499,11 @@ public class Frog extends Animal implements VariantHolder> { + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new Frog.FrogNodeEvaluator(true); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + } +diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java +index 6c73245b8d04f194e72165aa0000ca79a95db59d..2686df57d9d48db1438278d0d053bdbd3c65c0a7 100644 +--- a/net/minecraft/world/entity/monster/Drowned.java ++++ b/net/minecraft/world/entity/monster/Drowned.java +@@ -313,7 +313,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + + protected boolean closeToNextPos() { + Path path = this.getNavigation().getPath(); +- if (path != null) { ++ if (path != null && path.isProcessed()) { // DivineMC - ensure path is processed + BlockPos target = path.getTarget(); + if (target != null) { + double d = this.distanceToSqr(target.getX(), target.getY(), target.getZ()); +diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java +index 241526239bdbd5d9276f85e7fca46a7051f46a25..2329973f735e1ae7fbb76798dc113f4261c906b7 100644 +--- a/net/minecraft/world/entity/monster/Strider.java ++++ b/net/minecraft/world/entity/monster/Strider.java +@@ -579,9 +579,25 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + super(strider, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java +index f968e5c99bdb23b268bc34ea1ba5d54ae9ad0ff9..7b185f07f099c2704ecd5c983a11f4085a4f13c2 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -602,6 +602,16 @@ public class Warden extends Monster implements VibrationSystem { + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, GroundPathNavigation.nodeEvaluatorGenerator) { ++ @Override ++ protected float distance(Node first, Node second) { ++ return first.distanceToXZ(second); ++ } ++ }; ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes) { + @Override + protected float distance(Node first, Node second) { +diff --git a/net/minecraft/world/level/pathfinder/Path.java b/net/minecraft/world/level/pathfinder/Path.java +index d6d3c8f5e5dd4a8cab0d3fcc131c3a59f06130c6..839653a997f1e10970fa2956fadaf493808cb206 100644 +--- a/net/minecraft/world/level/pathfinder/Path.java ++++ b/net/minecraft/world/level/pathfinder/Path.java +@@ -26,6 +26,17 @@ public class Path { + this.reached = reached; + } + ++ // DivineMC start - async path processing ++ /** ++ * 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; ++ } ++ // DivineMC end - async path processing ++ + public void advance() { + this.nextNodeIndex++; + } +@@ -99,6 +110,7 @@ public class Path { + } + + public boolean sameAs(@Nullable Path pathentity) { ++ if (pathentity == this) return true; // DivineMC - async path processing + if (pathentity == null) { + return false; + } else if (pathentity.nodes.size() != this.nodes.size()) { +diff --git a/net/minecraft/world/level/pathfinder/PathFinder.java b/net/minecraft/world/level/pathfinder/PathFinder.java +index 81de6c1bbef1cafd3036e736dd305fbedc8368c6..0796343f89c8424d0f7553c60dde952ab06b9f3a 100644 +--- a/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -25,11 +25,19 @@ public class PathFinder { + public final NodeEvaluator nodeEvaluator; + private static final boolean DEBUG = false; + private final BinaryHeap openSet = new BinaryHeap(); ++ private final @Nullable org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator; // DivineMC - we use this later to generate an evaluator + +- public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ // DivineMC start - support nodeEvaluatorgenerators ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes, @Nullable org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator) { // DivineMC - add nodeEvaluatorGenerator + this.nodeEvaluator = nodeEvaluator; + this.maxVisitedNodes = maxVisitedNodes; ++ this.nodeEvaluatorGenerator = nodeEvaluatorGenerator; ++ } ++ ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ this(nodeEvaluator, maxVisitedNodes, null); + } ++ // DivineMC end - support nodeEvaluatorgenerators + + public void setMaxVisitedNodes(int maxVisitedNodes) { + this.maxVisitedNodes = maxVisitedNodes; +@@ -37,26 +45,63 @@ public class PathFinder { + + @Nullable + public Path findPath(PathNavigationRegion region, Mob mob, Set targetPositions, float maxRange, int accuracy, float searchDepthMultiplier) { +- this.openSet.clear(); +- this.nodeEvaluator.prepare(region, mob); +- Node start = this.nodeEvaluator.getStart(); ++ // DivineMC start - use a generated evaluator if we have one otherwise run sync ++ if (!org.bxteam.divinemc.DivineConfig.asyncPathfinding) ++ this.openSet.clear(); // it's always cleared in processPath ++ NodeEvaluator nodeEvaluator = this.nodeEvaluatorGenerator == null ++ ? this.nodeEvaluator ++ : org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.takeNodeEvaluator(this.nodeEvaluatorGenerator, this.nodeEvaluator); ++ nodeEvaluator.prepare(region, mob); ++ Node start = nodeEvaluator.getStart(); ++ // DivineMC end - use a generated evaluator if we have one otherwise run sync + if (start == null) { ++ org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); // DivineMC - handle nodeEvaluatorGenerator + return null; + } else { + // Paper start - Perf: remove streams and optimize collection + List> map = Lists.newArrayList(); + for (BlockPos pos : targetPositions) { +- map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); ++ map.add(new java.util.AbstractMap.SimpleEntry<>(nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); // DivineMC - handle nodeEvaluatorGenerator + } + // Paper end - Perf: remove streams and optimize collection +- Path path = this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); +- this.nodeEvaluator.done(); +- return path; ++ // DivineMC start - async path processing ++ if (this.nodeEvaluatorGenerator == null) { ++ // run sync :( ++ org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); ++ return this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); ++ } ++ ++ return new org.bxteam.divinemc.entity.pathfinding.AsyncPath(Lists.newArrayList(), targetPositions, () -> { ++ try { ++ return this.processPath(nodeEvaluator, start, map, maxRange, accuracy, searchDepthMultiplier); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ return null; ++ } finally { ++ nodeEvaluator.done(); ++ org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator); ++ } ++ }); ++ // DivineMC end - async path processing + } + } + + @Nullable + private Path findPath(Node node, List> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // Paper - optimize collection ++ // DivineMC start - split pathfinding into the original sync method for compat and processing for delaying ++ try { ++ return this.processPath(this.nodeEvaluator, node, positions, maxRange, accuracy, searchDepthMultiplier); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ return null; ++ } finally { ++ this.nodeEvaluator.done(); ++ } ++ } ++ ++ private synchronized @org.jetbrains.annotations.NotNull Path processPath(NodeEvaluator nodeEvaluator, Node node, List> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // 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 ++ // DivineMC end - split pathfinding into the original sync method for compat and processing for delaying + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("find_path"); + profilerFiller.markForCharting(MetricCategory.PATH_FINDING); +@@ -95,7 +140,7 @@ public class PathFinder { + } + + if (!(node1.distanceTo(node) >= maxRange)) { +- int neighbors = this.nodeEvaluator.getNeighbors(this.neighbors, node1); ++ int neighbors = nodeEvaluator.getNeighbors(this.neighbors, node1); // DivineMC - use provided nodeEvaluator + + for (int i2 = 0; i2 < neighbors; i2++) { + Node node2 = this.neighbors[i2]; diff --git a/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch b/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch new file mode 100644 index 0000000..114bd44 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:14:58 +0300 +Subject: [PATCH] Threaded light engine + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index b3f498558614243cf633dcd71e3c49c2c55e6e0f..da2921268a9aa4545a12c155a33bed9fccb4f19c 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -212,7 +212,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); + this.progressListener = progressListener; + this.chunkStatusListener = chunkStatusListener; +- ConsecutiveExecutor consecutiveExecutor1 = new ConsecutiveExecutor(dispatcher, "light"); ++ ConsecutiveExecutor consecutiveExecutor1 = onLightExecutorInit(ConsecutiveExecutor::new); // DivineMC - Threaded light engine + // Paper - rewrite chunk system + this.lightEngine = new ThreadedLevelLightEngine( + lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor1, null // Paper - rewrite chunk system +@@ -232,6 +232,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system + } + ++ // DivineMC start - Threaded light engine ++ private java.util.concurrent.ExecutorService lightThread = null; ++ ++ private ConsecutiveExecutor onLightExecutorInit(java.util.function.BiFunction original) { ++ lightThread = new java.util.concurrent.ThreadPoolExecutor( ++ 1, 1, ++ 0, java.util.concurrent.TimeUnit.SECONDS, ++ new java.util.concurrent.LinkedBlockingQueue<>(), ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setPriority(Thread.NORM_PRIORITY - 1).setDaemon(true).setNameFormat(String.format("%s - Light", level.dimension().location().toDebugFileName())).build() ++ ); ++ return original.apply(lightThread, "light"); ++ } ++ // DivineMC end - Threaded light engine ++ + private void setChunkUnsaved(ChunkPos chunkPos) { + // Paper - rewrite chunk system + } diff --git a/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch new file mode 100644 index 0000000..d1078a5 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch @@ -0,0 +1,333 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:18:49 +0300 +Subject: [PATCH] Multithreaded Tracker + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee50c9901a 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -264,9 +264,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); +- for (int i = 0, len = inRange.size(); i < len; i++) { +- ++(backingSet[i].mobCounts[index]); ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ final ServerPlayer player = backingSet[i]; ++ if (player == null) continue; ++ ++(player.mobCounts[index]); ++ } ++ } else { ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ ++(backingSet[i].mobCounts[index]); ++ } + } ++ // DivineMC end - Multithreaded tracker + } + + // Paper start - per player mob count backoff +@@ -950,6 +960,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker + } + ++ // DivineMC start - Multithreaded tracker ++ private final java.util.concurrent.ConcurrentLinkedQueue trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private boolean tracking = false; ++ ++ public void runOnTrackerMainThread(final Runnable runnable) { ++ if (false && this.tracking) { // TODO: check here ++ this.trackerMainThreadTasks.add(runnable); ++ } else { ++ runnable.run(); ++ } ++ } ++ // DivineMC end - Multithreaded tracker ++ + // Paper start - optimise entity tracker + private void newTrackerTick() { + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();; +@@ -972,6 +995,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - optimise entity tracker + + protected void tick() { ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; ++ org.bxteam.divinemc.entity.tracking.MultithreadedTracker.tick(level); ++ return; ++ } ++ // DivineMC end - Multithreaded tracker + // Paper start - optimise entity tracker + if (true) { + this.newTrackerTick(); +@@ -1094,7 +1124,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ // DivineMC start - Multithreaded tracker ++ public final Set seenBy = org.bxteam.divinemc.DivineConfig.multithreadedEnabled ++ ? com.google.common.collect.Sets.newConcurrentHashSet() ++ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ // DivineMC end - Multithreaded tracker + + // Paper start - optimise entity tracker + private long lastChunkUpdate = -1L; +@@ -1121,21 +1155,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastTrackedChunk = chunk; + + final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); ++ final int playersLen = players.size(); // Ensure length won't change in the future tasks ++ ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) { ++ final boolean isServerPlayer = this.entity instanceof ServerPlayer; ++ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); ++ Runnable updatePlayerTasks = () -> { ++ for (int i = 0; i < playersLen; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } + +- for (int i = 0, len = players.size(); i < len; ++i) { +- final ServerPlayer player = playersRaw[i]; +- this.updatePlayer(player); +- } ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ this.removePlayer(player); ++ } ++ } ++ } ++ }; ++ ++ // Only update asynchronously for real player, and sync update for fake players ++ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens ++ // To prevent visible issue with player type NPCs ++ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. ++ if (isRealPlayer || !isServerPlayer) { ++ org.bxteam.divinemc.entity.tracking.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); ++ } else { ++ updatePlayerTasks.run(); ++ } ++ } else { ++ for (int i = 0, len = players.size(); i < len; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } + +- if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { +- // need to purge any players possible not in the chunk list +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { +- final ServerPlayer player = conn.getPlayer(); +- if (!players.contains(player)) { +- this.removePlayer(player); ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ this.removePlayer(player); ++ } + } + } + } ++ // DivineMC end - Multithreaded tracker + } + + @Override +@@ -1197,9 +1265,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcast(Packet packet) { +- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { ++ // DivineMC start - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + serverPlayerConnection.send(packet); + } ++ // DivineMC end - Multithreaded tracker + } + + public void broadcastAndSend(Packet packet) { +@@ -1210,21 +1280,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcastRemoved() { +- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { ++ // DivineMC start - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); + } ++ // DivineMC end - Multithreaded tracker + } + + public void removePlayer(ServerPlayer player) { +- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + } + } + + public void updatePlayer(ServerPlayer player) { +- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && player == null) return; // DivineMC - Multithreaded tracker + // Paper start - remove allocation of Vec3D here + // Vec3 vec3 = player.position().subtract(this.entity.position()); + double vec3_dx = player.getX() - this.entity.getX(); +diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java +index f106373ef3ac4a8685c2939c9e8361688a285913..3b4dff8867e91884b5720ca8a9cb64af655f8475 100644 +--- a/net/minecraft/server/level/ServerBossEvent.java ++++ b/net/minecraft/server/level/ServerBossEvent.java +@@ -13,7 +13,11 @@ import net.minecraft.util.Mth; + import net.minecraft.world.BossEvent; + + public class ServerBossEvent extends BossEvent { +- private final Set players = Sets.newHashSet(); ++ // DivineMC start - Multithreaded tracker - players can be removed in async tracking ++ private final Set players = org.bxteam.divinemc.DivineConfig.multithreadedEnabled ++ ? Sets.newConcurrentHashSet() ++ : Sets.newHashSet(); ++ // DivineMC end - Multithreaded tracker + private final Set unmodifiablePlayers = Collections.unmodifiableSet(this.players); + public boolean visible = true; + +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java +index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e 100644 +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -110,8 +110,13 @@ public class ServerEntity { + .forEach( + removedPassenger -> { + if (removedPassenger instanceof ServerPlayer serverPlayer1) { +- serverPlayer1.connection +- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && Thread.currentThread() instanceof org.bxteam.divinemc.entity.tracking.MultithreadedTracker.MultithreadedTrackerThread) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot())); ++ } else { ++ serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); ++ } ++ // DivineMC end - Multithreaded tracker + } + } + ); +@@ -304,7 +309,11 @@ public class ServerEntity { + + public void removePairing(ServerPlayer player) { + this.entity.stopSeenByPlayer(player); +- player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())); ++ // DivineMC start - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())) ++ ); ++ // DivineMC end - Multithreaded tracker + } + + public void addPairing(ServerPlayer player) { +@@ -404,18 +413,27 @@ public class ServerEntity { + List> list = entityData.packDirty(); + if (list != null) { + this.trackedDataValues = entityData.getNonDefaultValues(); +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); ++ // DivineMC start - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)) ++ ); ++ // DivineMC end - Multithreaded tracker + } + + if (this.entity instanceof LivingEntity) { + Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); + if (!attributesToSync.isEmpty()) { +- // CraftBukkit start - Send scaled max health +- if (this.entity instanceof ServerPlayer serverPlayer) { +- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); +- } +- // CraftBukkit end +- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); ++ // DivineMC start - Multithreaded tracker ++ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> { ++ // CraftBukkit start - Send scaled max health ++ if (this.entity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); ++ } ++ // CraftBukkit end ++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); ++ }); ++ // DivineMC end - Multithreaded tracker + } + + attributesToSync.clear(); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index dd113d67356177a8d98ea10a8b6d4a4d5159674c..5d2483b2d59ae246b822d73b9996c2aa86623ab1 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -2514,7 +2514,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public LevelEntityGetter getEntities() { +- org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot ++ //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // DivineMC - Multithreaded tracker + return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system + } + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b45b37fcdfe0d3877b368444f8f6a376d6373f59..cfd3698dc575669456136da9cbbb100fd557e5ac 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1813,7 +1813,7 @@ public class ServerGamePacketListenerImpl + } + + public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { +- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper ++ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // DivineMC - Multithreaded tracker + // Paper start - Prevent teleporting dead entities + if (this.player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +index 8013594bb4844e7a8abf28123958e7f632d39341..72593629324ccd4d70b8ed86a90fb69785d57f5f 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +@@ -24,8 +24,11 @@ public class AttributeInstance { + private final Map> modifiersByOperation = Maps.newEnumMap( + AttributeModifier.Operation.class + ); +- private final Map modifierById = new Object2ObjectArrayMap<>(); +- private final Map permanentModifiers = new Object2ObjectArrayMap<>(); ++ // DivineMC start - Multithreaded tracker ++ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.DivineConfig.multithreadedEnabled; ++ private final Map modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ private final Map permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ // DivineMC end - Multithreaded tracker + private double baseValue; + private boolean dirty = true; + private double cachedValue; +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index a25d74592e89e3d6339479c6dc2b6f45d1932cfc..621b183211b8148bb8db256d2119c82f8a2c626b 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -19,9 +19,12 @@ import org.slf4j.Logger; + + public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Map, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>(); +- private final Set attributesToSync = new ObjectOpenHashSet<>(); +- private final Set attributesToUpdate = new ObjectOpenHashSet<>(); ++ // DivineMC start - Multithreaded tracker ++ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.DivineConfig.multithreadedEnabled; ++ private final Map, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); ++ private final Set attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ private final Set attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ // DivineMC end - Multithreaded tracker + private final AttributeSupplier supplier; + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + diff --git a/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch b/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch new file mode 100644 index 0000000..d8757d2 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 00:54:19 +0300 +Subject: [PATCH] Async locate command + + +diff --git a/net/minecraft/server/commands/LocateCommand.java b/net/minecraft/server/commands/LocateCommand.java +index 13bcd8653d766cd0b754a22e9aab261fbc62b0a5..0bc2d17e9bad62f62ab171feb2a92b87e1c3ba6a 100644 +--- a/net/minecraft/server/commands/LocateCommand.java ++++ b/net/minecraft/server/commands/LocateCommand.java +@@ -103,44 +103,77 @@ public class LocateCommand { + } + + private static int locateStructure(CommandSourceStack source, ResourceOrTagKeyArgument.Result structure) throws CommandSyntaxException { +- Registry registry = source.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE); +- HolderSet holderSet = (HolderSet)getHolders(structure, registry) +- .orElseThrow(() -> ERROR_STRUCTURE_INVALID.create(structure.asPrintable())); +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- ServerLevel level = source.getLevel(); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Pair> pair = level.getChunkSource().getGenerator().findNearestMapStructure(level, holderSet, blockPos, 100, false); +- stopwatch.stop(); +- if (pair == null) { +- throw ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()); +- } else { +- return showLocateResult(source, structure, blockPos, pair, "commands.locate.structure.success", false, stopwatch.elapsed()); +- } ++ // DivineMC start - Async structure locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ Registry registry = source.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE); ++ HolderSet holderSet; ++ try { ++ holderSet = getHolders(structure, registry) ++ .orElseThrow(() -> ERROR_STRUCTURE_INVALID.create(structure.asPrintable())); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ return; ++ } ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ ServerLevel level = source.getLevel(); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Pair> pair = level.getChunkSource().getGenerator().findNearestMapStructure(level, holderSet, blockPos, 100, false); ++ stopwatch.stop(); ++ if (pair == null) { ++ try { ++ throw ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, structure, blockPos, pair, "commands.locate.structure.success", false, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async structure locate + } + + private static int locateBiome(CommandSourceStack source, ResourceOrTagArgument.Result biome) throws CommandSyntaxException { +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Pair> pair = source.getLevel().findClosestBiome3d(biome, blockPos, 6400, 32, 64); +- stopwatch.stop(); +- if (pair == null) { +- throw ERROR_BIOME_NOT_FOUND.create(biome.asPrintable()); +- } else { +- return showLocateResult(source, biome, blockPos, pair, "commands.locate.biome.success", true, stopwatch.elapsed()); +- } ++ // DivineMC start - Async biome locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Pair> pair = source.getLevel().findClosestBiome3d(biome, blockPos, 6400, 32, 64); ++ stopwatch.stop(); ++ if (pair == null) { ++ try { ++ throw ERROR_BIOME_NOT_FOUND.create(biome.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, biome, blockPos, pair, "commands.locate.biome.success", true, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async biome locate + } + + private static int locatePoi(CommandSourceStack source, ResourceOrTagArgument.Result poiType) throws CommandSyntaxException { +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- ServerLevel level = source.getLevel(); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Optional, BlockPos>> optional = level.getPoiManager().findClosestWithType(poiType, blockPos, 256, PoiManager.Occupancy.ANY); +- stopwatch.stop(); +- if (optional.isEmpty()) { +- throw ERROR_POI_NOT_FOUND.create(poiType.asPrintable()); +- } else { +- return showLocateResult(source, poiType, blockPos, optional.get().swap(), "commands.locate.poi.success", false, stopwatch.elapsed()); +- } ++ // DivineMC start - Async poi locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ ServerLevel level = source.getLevel(); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Optional, BlockPos>> optional = level.getPoiManager().findClosestWithType(poiType, blockPos, 256, PoiManager.Occupancy.ANY); ++ stopwatch.stop(); ++ if (optional.isEmpty()) { ++ try { ++ throw ERROR_POI_NOT_FOUND.create(poiType.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, poiType, blockPos, optional.get().swap(), "commands.locate.poi.success", false, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async poi locate + } + + public static int showLocateResult( +@@ -195,7 +228,7 @@ public class LocateCommand { + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.coordinates.tooltip"))) + ); + source.sendSuccess(() -> Component.translatable(translationKey, elementName, component, i), false); +- LOGGER.info("Locating element " + elementName + " took " + duration.toMillis() + " ms"); ++ LOGGER.info("Locating element {} on Thread:{} took {} ms", elementName, Thread.currentThread().getName(), duration.toMillis()); // DivineMC - Log thread name + return i; + } + diff --git a/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch new file mode 100644 index 0000000..027468b --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch @@ -0,0 +1,988 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 00:59:03 +0300 +Subject: [PATCH] Parallel world ticking + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..0c99bffa769d53562a10d23c4a9b37dc59c7f478 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -1031,7 +1031,7 @@ public final class ChunkHolderManager { + if (changedFullStatus.isEmpty()) { + return; + } +- if (!TickThread.isTickThread()) { ++ if (!TickThread.isTickThreadFor(world)) { // DivineMC - parallel world ticking + this.taskScheduler.scheduleChunkTask(() -> { + final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { +@@ -1057,7 +1057,7 @@ public final class ChunkHolderManager { + + // note: never call while inside the chunk system, this will absolutely break everything + public void processUnloads() { +- TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // DivineMC - parallel world ticking + + if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { + throw new IllegalStateException("Cannot unload chunks recursively"); +@@ -1339,7 +1339,7 @@ public final class ChunkHolderManager { + + List changedFullStatus = null; + +- final boolean isTickThread = TickThread.isTickThread(); ++ final boolean isTickThread = TickThread.isTickThreadFor(world); // DivineMC - parallel world ticking + + boolean ret = false; + final boolean canProcessFullUpdates = processFullUpdates & isTickThread; +diff --git a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 4a881636ba21fae9e50950bbba2b4321b71d35ab..d019e6cd603c918b576b950a3c678862b2248c93 100644 +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -46,7 +46,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..10ab4be4b8d7e488148bab395e344fca0d09fbb0 100644 +--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -78,7 +78,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c072546ad329c6 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -89,7 +89,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -147,7 +147,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -201,7 +201,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -251,7 +251,7 @@ public interface DispenseItemBehavior { + org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy); + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -329,7 +329,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -389,7 +389,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event); + } + +@@ -425,7 +425,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -482,7 +482,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -510,8 +510,10 @@ public interface DispenseItemBehavior { + // CraftBukkit start + level.captureTreeGeneration = false; + if (level.capturedBlockStates.size() > 0) { +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); + List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); +@@ -548,7 +550,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -591,7 +593,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -644,7 +646,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -702,7 +704,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - only single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -783,7 +785,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..e2b7ee10569812c94a5ff6d6e731941f24527c55 100644 +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -39,7 +39,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +index 116395b6c00a0814922516707544a9ff26d68835..37f710bfa8a9388351d1d32f6f2eeac7c8bfd4bd 100644 +--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -62,7 +62,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index 449d9b72ff4650961daa9d1bd25940f3914a6b12..097528f85e5c0393c8b20a68aede99b4b949cb24 100644 +--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -32,7 +32,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..6fad185f34c4614f16012ec008add241f188d462 100644 +--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -25,7 +25,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index 5ab2c8333178335515e619b87ae420f948c83bd1..9d2bc41befd0f73b6a0f097d45fbe771e13be86f 100644 +--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -27,7 +27,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + blockSource.level().getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 781030cb2e0316151c20351f04347c8db63f43e1..527547b98b70429830a3cf82fddba202e0ba8131 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -306,6 +306,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1757,35 +1758,49 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent +- serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent +- serverLevel.updateLagCompensationTick(); // Paper - lag compensation +- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers +- serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables +- profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); +- /* Drop global time updates +- if (this.tickCount % 20 == 0) { +- profilerFiller.push("timeSync"); +- this.synchronizeTime(serverLevel); ++ // DivineMC start - parallel world ticking ++ java.util.ArrayDeque> tasks = new java.util.ArrayDeque<>(); ++ try { ++ for (ServerLevel serverLevel : this.getAllLevels()) { ++ serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent ++ serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent ++ serverLevel.updateLagCompensationTick(); // Paper - lag compensation ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers ++ serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables ++ profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); ++ profilerFiller.push("tick"); ++ ++ serverLevelTickingSemaphore.acquire(); ++ tasks.add( ++ serverLevel.tickExecutor.submit(() -> { ++ try { ++ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); ++ currentThread.currentlyTickingServerLevel = serverLevel; ++ ++ serverLevel.tick(hasTimeLeft); ++ } catch (Throwable throwable) { ++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); ++ ++ serverLevel.fillReportDetails(crashreport); ++ throw new ReportedException(crashreport); ++ } finally { ++ serverLevelTickingSemaphore.release(); ++ } ++ }, serverLevel) ++ ); ++ ++ profilerFiller.pop(); + profilerFiller.pop(); ++ serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions + } +- // CraftBukkit end */ + +- profilerFiller.push("tick"); +- +- try { +- serverLevel.tick(hasTimeLeft); +- } catch (Throwable var7) { +- CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world"); +- serverLevel.fillReportDetails(crashReport); +- throw new ReportedException(crashReport); ++ while (!tasks.isEmpty()) { ++ tasks.pop().get(); + } +- +- profilerFiller.pop(); +- profilerFiller.pop(); +- serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions ++ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException(e); // Propagate exception + } ++ // DivineMC end - parallel world ticking + this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked + + profilerFiller.popPush("connection"); +@@ -1876,6 +1891,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; + Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); + newLevels.remove(level.dimension()); ++ level.tickExecutor.shutdown(); // DivineMC - parallel world ticking + this.levels = Collections.unmodifiableMap(newLevels); + } + // CraftBukkit end +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 959d87f4cd1efe8cf591e98c7d32728067f7117c..697f690305db56ae5a05483aae37994d4e8f9f83 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -234,6 +234,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ // DivineMC start - parallel world ticking ++ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.bxteam.divinemc.DivineConfig.parallelThreadCount); ++ DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); ++ // DivineMC end - parallel world ticking + /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage + try { + org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 5d2483b2d59ae246b822d73b9996c2aa86623ab1..92f3e5d929997a974c367ec3ce02cda4acdb5183 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -184,7 +184,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; +- final EntityTickList entityTickList = new EntityTickList(); ++ final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - parallel world ticking + // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; +@@ -210,6 +210,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private double preciseTime; // Purpur - Configurable daylight cycle + private boolean forceTime; // Purpur - Configurable daylight cycle + private final RandomSequences randomSequences; ++ public java.util.concurrent.ExecutorService tickExecutor; // DivineMC - parallel world ticking + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess levelStorageAccess; +@@ -702,6 +703,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); + // Paper end - rewrite chunk system + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit ++ this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.bxteam.divinemc.server.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // DivineMC - parallel world ticking + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle + } + +@@ -1292,7 +1294,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper start - rewrite chunk system + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // DivineMC - remove + } + // Paper end - rewrite chunk system + +@@ -1305,7 +1307,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper start - rewrite chunk system + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // DivineMC - remove + } + // Paper end - rewrite chunk system + +@@ -1565,6 +1567,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + private void addPlayer(ServerPlayer player) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) + Entity entity = this.getEntities().get(player.getUUID()); + if (entity != null) { + LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); +@@ -1577,7 +1580,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + // CraftBukkit start + private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { +- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) + entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 515b2178e71a3723eace26c89032e45678145224..a33071d4b9b5ab75bcef93411e67d4f7c47d5c62 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -428,6 +428,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + // Paper end - rewrite chunk system + ++ public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking ++ + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); + this.textFilter = server.createTextFilterForPlayer(this); +@@ -803,6 +805,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + + @Override + public void tick() { ++ hasTickedAtLeastOnceInNewWorld = true; // DivineMC - parallel world ticking + // CraftBukkit start + if (this.joining) { + this.joining = false; +@@ -1448,6 +1451,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return this; + } else { + // CraftBukkit start ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking (additional concurrency issues logs) + /* + this.isChangingDimension = true; + LevelData levelData = level.getLevelData(); +@@ -1815,6 +1819,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return OptionalInt.empty(); + } else { + // CraftBukkit start ++ // DivineMC start - parallel world ticking ++ if (!hasTickedAtLeastOnceInNewWorld) { ++ MinecraftServer.LOGGER.warn("Ignoring request to open container " + abstractContainerMenu + " because we haven't ticked in the current world yet!", new Throwable()); ++ return OptionalInt.empty(); ++ } ++ // DivineMC end - parallel world ticking + this.containerMenu = abstractContainerMenu; // Moved up + if (!this.isImmobile()) + this.connection +@@ -1879,6 +1889,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + @Override + public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // DivineMC start - parallel world ticking (debugging) ++ if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { ++ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace); ++ } ++ // DivineMC end - parallel world ticking (debugging) + org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit + // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cfd3698dc575669456136da9cbbb100fd557e5ac..aabce23007006fe2dca1e4ac7c0657d2a1cae30e 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -572,7 +572,7 @@ public class ServerGamePacketListenerImpl + return; + } + // Paper end - Prevent moving into unloaded chunks +- if (d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ if (!org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && (d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner())) { // DivineMC - stop weird movement + // CraftBukkit end + LOGGER.warn( + "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5 +@@ -602,7 +602,7 @@ public class ServerGamePacketListenerImpl + d5 = d2 - rootVehicle.getZ(); + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean flag2 = false; +- if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot ++ if (!org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold)) { // Spigot // DivineMC - stop weird movement + flag2 = true; // Paper - diff on change, this should be moved wrongly + LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7)); + } +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index e8ff6e79ce7ba0ec8b2a90bcb81283f52106c535..6b23cf5122fe65b2ad253ed8536658441297e953 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -150,6 +150,7 @@ public abstract class PlayerList { + abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // DivineMC - parallel world ticking + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameProfile = player.getGameProfile(); +@@ -716,6 +717,13 @@ public abstract class PlayerList { + return this.respawn(player, keepInventory, reason, eventReason, null); + } + public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) { ++ // DivineMC start - parallel world ticking (additional concurrency issues logs) ++ System.out.println("respawning player - current player container is " + player.containerMenu + " but their inventory is " + player.inventoryMenu); ++ if (location != null) // TODO: Is this really never null, or is IntelliJ IDEA tripping? Because I'm pretty sure that this can be null and there isn't any @NotNull annotations ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); ++ else ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.serverLevel().getWorld().getName()); ++ // DivineMC end - parallel world ticking (additional concurrency issues logs) + player.stopRiding(); // CraftBukkit + this.players.remove(player); + this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -726,6 +734,7 @@ public abstract class PlayerList { + ServerPlayer serverPlayer = player; + Level fromWorld = player.level(); + player.wonGame = false; ++ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking + // CraftBukkit end + serverPlayer.connection = player.connection; + serverPlayer.restoreFrom(player, keepInventory); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 80f2d38449f1db1d9b6926e4552d3061cb88b4af..356a1bfc610214912f58c4126cdd5694ffecfcb8 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -850,7 +850,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities ++ if (false && !(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities // DivineMC - parallel world ticking + this.handlePortal(); + } + } +@@ -3870,6 +3870,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking + List passengers = this.getPassengers(); + List list = new ArrayList<>(passengers.size()); + this.ejectPassengers(); +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index 3dcd8df0b395a8fed8bc0cbe0ff78f4ae0056fd3..228c63bf506548666ce87fae694e2270c97c6770 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -93,7 +93,14 @@ public abstract class AbstractContainerMenu { + public void startOpen() {} + // CraftBukkit end + ++ public Throwable containerCreationStacktrace; // DivineMC - parallel world ticking ++ + protected AbstractContainerMenu(@Nullable MenuType menuType, int containerId) { ++ // DivineMC start - parallel world ticking (debugging) ++ if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { ++ this.containerCreationStacktrace = new Throwable(); ++ } ++ // DivineMC start - parallel world ticking (debugging) + this.menuType = menuType; + this.containerId = containerId; + } +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index 264b713e8b7c3d5f7d8e1facc90a60349f2cf414..f461b060e03edf4102290a424ab008b88d80bdc2 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -407,8 +407,8 @@ public final class ItemStack implements DataComponentHolder { + if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { + serverLevel.captureTreeGeneration = false; + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // DivineMC - parallel world ticking ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // DivineMC - parallel world ticking + List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); + serverLevel.capturedBlockStates.clear(); + org.bukkit.event.world.StructureGrowEvent structureEvent = null; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 3856bbe579ef6df2f220c46bc69461cab026a131..8f37c27bba829733fb8db5f35470092a76c83e98 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -172,6 +172,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files + public final org.bxteam.divinemc.DivineWorldConfig divineConfig; // DivineMC - Configuration ++ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // DivineMC - parallel world ticking (moved to world) + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -1146,6 +1147,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { + // Paper start - Protect Bedrock and End Portal/Frames from being destroyed +@@ -1530,7 +1532,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system + if ((++tickedEntities & 7) == 0) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + } + // Paper end - rewrite chunk system + } +@@ -1553,7 +1555,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + // Paper end - Prevent block entity and entity crashes + } +- this.moonrise$midTickTasks(); // Paper - rewrite chunk system ++ // this.moonrise$midTickTasks(); // Paper - rewrite chunk system + } + + // Paper start - Option to prevent armor stands from doing entity lookups +@@ -1696,6 +1698,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // DivineMC - parallel world ticking + // Paper start - Perf: Optimize capturedTileEntities lookup + net.minecraft.world.level.block.entity.BlockEntity blockEntity; + if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { +@@ -1713,6 +1716,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + + public void setBlockEntity(BlockEntity blockEntity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking + BlockPos blockPos = blockEntity.getBlockPos(); + if (!this.isOutsideBuildHeight(blockPos)) { + // CraftBukkit start +@@ -1797,6 +1801,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + Profiler.get().incrementCounter("getEntities"); + List list = Lists.newArrayList(); + +@@ -2109,8 +2114,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public abstract RecipeAccess recipeAccess(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { +- this.randValue = this.randValue * 3 + 1013904223; +- int i = this.randValue >> 2; ++ int i = this.random.nextInt() >> 2; // DivineMC - parallel world ticking + return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); + } + +diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java +index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..274f36581e7040c67bf8649258660228a3e8cce0 100644 +--- a/net/minecraft/world/level/block/DispenserBlock.java ++++ b/net/minecraft/world/level/block/DispenserBlock.java +@@ -50,7 +50,7 @@ public class DispenserBlock extends BaseEntityBlock { + private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); + public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); + private static final int TRIGGER_DURATION = 4; +- public static boolean eventFired = false; // CraftBukkit ++ public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + + @Override + public MapCodec codec() { +@@ -96,7 +96,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); + if (dispenseMethod != DispenseItemBehavior.NOOP) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent +- DispenserBlock.eventFired = false; // CraftBukkit - reset event status ++ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // DivineMC - parallel world ticking + dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); + } + } +diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java +index 85f0eac75784565c658c5178c544f969db3d6f54..22c527a7b44a434b66a2217ed88eca79703b2036 100644 +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { + // CraftBukkit start + .map((value) -> { + if (this == Blocks.WARPED_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // DivineMC - parallel world ticking + } else if (this == Blocks.CRIMSON_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // DivineMC - parallel world ticking + } + return value; + }) +diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java +index 904369f4d7db41026183f2de7c96c2f0f4dc204d..1a3f07834fd1483aa77f7733512908b62583edda 100644 +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -94,7 +94,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + return false; + } else { + level.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit ++ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // DivineMC - parallel world ticking + if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 12c9d60314c99fb65e640d255a2d0c6b7790ad4d..0f56ea9dd444148b134a3d97f0b7c4563df73e39 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -292,7 +292,7 @@ public class RedStoneWireBlock extends Block { + + // Paper start - Optimize redstone (Eigencraft) + // The bulk of the new functionality is found in RedstoneWireTurbo.java +- io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); ++ // io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); + + /* + * Modified version of pre-existing updateSurroundingRedstone, which is called from +@@ -308,7 +308,7 @@ public class RedStoneWireBlock extends Block { + if (orientation != null) { + source = pos.relative(orientation.getFront().getOpposite()); + } +- turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // DivineMC - parallel world ticking + return; + } + updatePowerStrength(worldIn, pos, state, orientation, blockAdded); +@@ -336,7 +336,7 @@ public class RedStoneWireBlock extends Block { + // [Space Walker] suppress shape updates and emit those manually to + // bypass the new neighbor update stack. + if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { +- turbo.updateNeighborShapes(level, pos, state); ++ level.turbo.updateNeighborShapes(level, pos, state); // DivineMC - parallel world ticking + } + } + } +diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java +index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..d10f8909fcfa930c726f2620e3ffc912baa40be7 100644 +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -26,7 +26,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + protected static final float AABB_OFFSET = 6.0F; + protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); + protected final TreeGrower treeGrower; +- public static org.bukkit.TreeType treeType; // CraftBukkit ++ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // DivineMC - parallel world ticking (from Folia) + + @Override + public MapCodec codec() { +@@ -63,8 +63,10 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); + level.captureTreeGeneration = false; + if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); ++ SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); + java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); +diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index 26db603ed681a6c302596627d4dd5bf8a9bafc4e..3b56375f705a0d65901f702e53e1fc609eeaf1a6 100644 +--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -77,6 +77,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + return canUnlock(player, code, displayName, null); + } + public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) { ++ // DivineMC start - parallel world ticking ++ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) { ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Player " + serverPlayer.getScoreboardName() + " (" + serverPlayer.getStringUUID() + ") attempted to open a BlockEntity @ " + blockEntity.getLevel().getWorld().getName() + " " + blockEntity.getBlockPos().getX() + ", " + blockEntity.getBlockPos().getY() + ", " + blockEntity.getBlockPos().getZ() + " while they were in a different world " + serverPlayer.level().getWorld().getName() + " than the block themselves!"); ++ return false; ++ } ++ // DivineMC end - parallel world ticking + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { + final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); + net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName)); +diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index 1638eccef431fb68775af624110f1968f0c6dabd..8f3eb265adcacf6d4f71db9a298ba8a7ce99c941 100644 +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + // Paper end - Fix NPE in SculkBloomEvent world access + + public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.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. ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(pos); // DivineMC - parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. + sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // DivineMC - parallel world ticking // CraftBukkit + } + + @Override +diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java +index cf7311c507de09a8f89934e430b2201e8bdffe51..30bd72ad2f66616a89b78fb0677109b8341eb132 100644 +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -204,55 +204,59 @@ public final class TreeGrower { + // CraftBukkit start + private void setTreeType(Holder> holder) { + ResourceKey> treeFeature = holder.unwrapKey().get(); ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType; + if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; ++ treeType = org.bukkit.TreeType.TREE; + } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; ++ treeType = org.bukkit.TreeType.RED_MUSHROOM; + } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; ++ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; ++ treeType = org.bukkit.TreeType.COCOA_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; ++ treeType = org.bukkit.TreeType.SMALL_JUNGLE; + } else if (treeFeature == TreeFeatures.PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; ++ treeType = org.bukkit.TreeType.TALL_REDWOOD; + } else if (treeFeature == TreeFeatures.SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; ++ treeType = org.bukkit.TreeType.REDWOOD; + } else if (treeFeature == TreeFeatures.ACACIA) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; ++ treeType = org.bukkit.TreeType.ACACIA; + } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; ++ treeType = org.bukkit.TreeType.BIRCH; + } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; ++ treeType = org.bukkit.TreeType.TALL_BIRCH; + } else if (treeFeature == TreeFeatures.SWAMP_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; ++ treeType = org.bukkit.TreeType.SWAMP; + } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; ++ treeType = org.bukkit.TreeType.BIG_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; ++ treeType = org.bukkit.TreeType.JUNGLE_BUSH; + } else if (treeFeature == TreeFeatures.DARK_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; ++ treeType = org.bukkit.TreeType.DARK_OAK; + } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; ++ treeType = org.bukkit.TreeType.MEGA_REDWOOD; + } else if (treeFeature == TreeFeatures.MEGA_PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; ++ treeType = org.bukkit.TreeType.MEGA_PINE; + } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; ++ treeType = org.bukkit.TreeType.JUNGLE; + } else if (treeFeature == TreeFeatures.AZALEA_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; ++ treeType = org.bukkit.TreeType.AZALEA; + } else if (treeFeature == TreeFeatures.MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; ++ treeType = org.bukkit.TreeType.MANGROVE; + } else if (treeFeature == TreeFeatures.TALL_MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; ++ treeType = org.bukkit.TreeType.TALL_MANGROVE; + } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; ++ treeType = org.bukkit.TreeType.CHERRY; + } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; ++ treeType = org.bukkit.TreeType.PALE_OAK; + } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; ++ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; + } else { + throw new IllegalArgumentException("Unknown tree generator " + treeFeature); + } ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); ++ // DivineMC end - parallel world ticking + } + // CraftBukkit end + } +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 5652ed5fb03a4a1a94c348109cb499196f909712..6167f72d1e374a7093f9880ab50e27eda603a680 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -367,6 +367,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + @Nullable + public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking + // CraftBukkit end + int y = pos.getY(); + LevelChunkSection section = this.getSection(this.getSectionIndex(y)); +diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java +index 423779a2b690f387a4f0bd07b97b50e0baefda76..f43f318577efff0c920029d467a54608614f410c 100644 +--- a/net/minecraft/world/level/entity/EntityTickList.java ++++ b/net/minecraft/world/level/entity/EntityTickList.java +@@ -11,16 +11,27 @@ import net.minecraft.world.entity.Entity; + public class EntityTickList { + private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system + ++ // DivineMC start - parallel world ticking ++ // Used to track async entity additions/removals/loops ++ private final net.minecraft.server.level.ServerLevel serverLevel; ++ ++ public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) { ++ this.serverLevel = serverLevel; ++ } ++ // DivineMC end - parallel world ticking ++ + private void ensureActiveIsNotIterated() { + // Paper - rewrite chunk system + } + + public void add(Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // DivineMC - parallel world ticking + this.ensureActiveIsNotIterated(); + this.entities.add(entity); // Paper - rewrite chunk system + } + + public void remove(Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // DivineMC - parallel world ticking + this.ensureActiveIsNotIterated(); + this.entities.remove(entity); // Paper - rewrite chunk system + } +@@ -30,6 +41,7 @@ public class EntityTickList { + } + + public void forEach(Consumer entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // DivineMC - parallel world ticking + // Paper start - rewrite chunk system + // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... + // (by dfl iterator() is configured to not iterate over new entries) +diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java +index ffe604f8397a002800e6ecc2f878d0f6f1c98703..8f6436d2e87e7e11d98dcf20f4a62a4b3c7f6d92 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapIndex.java ++++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java +@@ -34,17 +34,24 @@ public class MapIndex extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { +- for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { +- tag.putInt(entry.getKey(), entry.getIntValue()); ++ // DivineMC start - make map data thread-safe ++ synchronized (this.usedAuxIds) { ++ for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { ++ tag.putInt(entry.getKey(), entry.getIntValue()); ++ } + } +- ++ // DivineMC end - make map data thread-safe + return tag; + } + + public MapId getFreeAuxValueForMap() { +- int i = this.usedAuxIds.getInt("map") + 1; +- this.usedAuxIds.put("map", i); +- this.setDirty(); +- return new MapId(i); ++ // DivineMC start - make map data thread-safe ++ synchronized (this.usedAuxIds) { ++ int i = this.usedAuxIds.getInt("map") + 1; ++ this.usedAuxIds.put("map", i); ++ this.setDirty(); ++ return new MapId(i); ++ } ++ // DivineMC end - make map data thread-safe + } + } diff --git a/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch b/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch new file mode 100644 index 0000000..075f081 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch @@ -0,0 +1,401 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 31 Jan 2025 21:50:46 +0300 +Subject: [PATCH] C2ME: opts_native_math + + +diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java +index 73962e79a0f3d892e3155443a1b84508b0f4042e..1d7c8c2196afb8515802734ad756abec1d5ceaf2 100644 +--- a/net/minecraft/world/level/biome/BiomeManager.java ++++ b/net/minecraft/world/level/biome/BiomeManager.java +@@ -29,39 +29,64 @@ public class BiomeManager { + } + + public Holder getBiome(BlockPos pos) { +- int i = pos.getX() - 2; +- int i1 = pos.getY() - 2; +- int i2 = pos.getZ() - 2; +- int i3 = i >> 2; +- int i4 = i1 >> 2; +- int i5 = i2 >> 2; +- double d = (i & 3) / 4.0; +- double d1 = (i1 & 3) / 4.0; +- double d2 = (i2 & 3) / 4.0; +- int i6 = 0; +- double d3 = Double.POSITIVE_INFINITY; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ int mask = org.bxteam.divinemc.math.Bindings.c2me_natives_biome_access_sample(this.biomeZoomSeed, pos.getX(), pos.getY(), pos.getZ()); + +- for (int i7 = 0; i7 < 8; i7++) { +- boolean flag = (i7 & 4) == 0; +- boolean flag1 = (i7 & 2) == 0; +- boolean flag2 = (i7 & 1) == 0; +- int i8 = flag ? i3 : i3 + 1; +- int i9 = flag1 ? i4 : i4 + 1; +- int i10 = flag2 ? i5 : i5 + 1; +- double d4 = flag ? d : d - 1.0; +- double d5 = flag1 ? d1 : d1 - 1.0; +- double d6 = flag2 ? d2 : d2 - 1.0; +- double fiddledDistance = getFiddledDistance(this.biomeZoomSeed, i8, i9, i10, d4, d5, d6); +- if (d3 > fiddledDistance) { +- i6 = i7; +- d3 = fiddledDistance; ++ return this.noiseBiomeSource.getNoiseBiome( ++ ((pos.getX() - 2) >> 2) + ((mask & 4) != 0 ? 1 : 0), ++ ((pos.getY() - 2) >> 2) + ((mask & 2) != 0 ? 1 : 0), ++ ((pos.getZ() - 2) >> 2) + ((mask & 1) != 0 ? 1 : 0) ++ ); ++ } else { ++ final int var0 = pos.getX() - 2; ++ final int var1 = pos.getY() - 2; ++ final int var2 = pos.getZ() - 2; ++ final int var3 = var0 >> 2; ++ final int var4 = var1 >> 2; ++ final int var5 = var2 >> 2; ++ final double var6 = (double) (var0 & 3) / 4.0; ++ final double var7 = (double) (var1 & 3) / 4.0; ++ final double var8 = (double) (var2 & 3) / 4.0; ++ int var9 = 0; ++ double var10 = Double.POSITIVE_INFINITY; ++ for (int var11 = 0; var11 < 8; ++var11) { ++ boolean var12 = (var11 & 4) == 0; ++ boolean var13 = (var11 & 2) == 0; ++ boolean var14 = (var11 & 1) == 0; ++ long var15 = var12 ? var3 : var3 + 1; ++ long var16 = var13 ? var4 : var4 + 1; ++ long var17 = var14 ? var5 : var5 + 1; ++ double var18 = var12 ? var6 : var6 - 1.0; ++ double var19 = var13 ? var7 : var7 - 1.0; ++ double var20 = var14 ? var8 : var8 - 1.0; ++ long var21 = this.biomeZoomSeed * (this.biomeZoomSeed * 6364136223846793005L + 1442695040888963407L) + var15; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var15; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; ++ double var22 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var23 = (var22 - 0.5) * 0.9; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.biomeZoomSeed; ++ double var24 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var25 = (var24 - 0.5) * 0.9; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.biomeZoomSeed; ++ double var26 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var27 = (var26 - 0.5) * 0.9; ++ double var28 = Mth.square(var20 + var27) + Mth.square(var19 + var25) + Mth.square(var18 + var23); ++ if (var10 > var28) { ++ var9 = var11; ++ var10 = var28; ++ } + } +- } + +- int i7x = (i6 & 4) == 0 ? i3 : i3 + 1; +- int i11 = (i6 & 2) == 0 ? i4 : i4 + 1; +- int i12 = (i6 & 1) == 0 ? i5 : i5 + 1; +- return this.noiseBiomeSource.getNoiseBiome(i7x, i11, i12); ++ int resX = (var9 & 4) == 0 ? var3 : var3 + 1; ++ int resY = (var9 & 2) == 0 ? var4 : var4 + 1; ++ int resZ = (var9 & 1) == 0 ? var5 : var5 + 1; ++ return this.noiseBiomeSource.getNoiseBiome(resX, resY, resZ); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public Holder getNoiseBiomeAtPosition(double x, double y, double z) { +diff --git a/net/minecraft/world/level/levelgen/DensityFunctions.java b/net/minecraft/world/level/levelgen/DensityFunctions.java +index fa08f06be03b2e6120ddc105563f68d551da741c..7178013421233d7dab36eb07a768907ce40e8745 100644 +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -501,6 +501,11 @@ public final class DensityFunctions { + } + + protected static final class EndIslandDensityFunction implements DensityFunction.SimpleFunction { ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ // DivineMC end - C2ME: opts_native_math + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of( + MapCodec.unit(new DensityFunctions.EndIslandDensityFunction(0L)) + ); +@@ -521,6 +526,16 @@ public final class DensityFunctions { + RandomSource randomSource = new LegacyRandomSource(seed); + randomSource.consumeCount(17292); + this.islandNoise = new SimplexNoise(randomSource); ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ int[] permutation = (this.islandNoise).p; ++ java.lang.foreign.MemorySegment segment = this.c2me$arena.allocate(permutation.length * 4L, 64); ++ java.lang.foreign.MemorySegment.copy(java.lang.foreign.MemorySegment.ofArray(permutation), 0L, segment, 0L, permutation.length * 4L); ++ java.lang.invoke.VarHandle.fullFence(); ++ this.c2me$samplerData = segment; ++ this.c2me$samplerDataPtr = segment.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + private static float getHeightValue(SimplexNoise noise, int x, int z) { +@@ -567,7 +582,13 @@ public final class DensityFunctions { + + @Override + public double compute(DensityFunction.FunctionContext context) { +- return (getHeightValue(this.islandNoise, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled && this.c2me$samplerDataPtr != 0L) { ++ return ((double) org.bxteam.divinemc.math.Bindings.c2me_natives_end_islands_sample(this.c2me$samplerDataPtr, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ } else { ++ return (getHeightValue(this.islandNoise, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + @Override +@@ -814,10 +835,42 @@ public final class DensityFunctions { + return this.noise.getValue(context.blockX() * this.xzScale, context.blockY() * this.yScale, context.blockZ() * this.xzScale); + } + ++ // DivineMC start - C2ME: opts_native_math + @Override +- public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- contextProvider.fillAllDirectly(array, this); ++ public void fillArray(double[] densities, DensityFunction.ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.noise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * this.xzScale(); ++ y[i] = pos.blockY() * this.yScale(); ++ z[i] = pos.blockZ() * this.xzScale(); ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); + } ++ // DivineMC end - C2ME: opts_native_math + + @Override + public DensityFunction mapAll(DensityFunction.Visitor visitor) { +@@ -938,6 +991,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * 0.25; ++ y[i] = pos.blockY() * 0.25; ++ z[i] = pos.blockZ() * 0.25; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public record ShiftA(@Override DensityFunction.NoiseHolder offsetNoise) implements DensityFunctions.ShiftNoise { +@@ -959,6 +1052,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * 0.25; ++ y[i] = 0; ++ z[i] = pos.blockZ() * 0.25; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public record ShiftB(@Override DensityFunction.NoiseHolder offsetNoise) implements DensityFunctions.ShiftNoise { +@@ -980,6 +1113,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockZ() * 0.25; ++ y[i] = pos.blockX() * 0.25; ++ z[i] = 0.0; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + interface ShiftNoise extends DensityFunction { +diff --git a/net/minecraft/world/level/levelgen/synth/BlendedNoise.java b/net/minecraft/world/level/levelgen/synth/BlendedNoise.java +index af5f714c285aad5ef844b17a266e06b5092d33aa..173c9f4024d085e0591f9eb5502a6f15168673e7 100644 +--- a/net/minecraft/world/level/levelgen/synth/BlendedNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/BlendedNoise.java +@@ -36,6 +36,11 @@ public class BlendedNoise implements DensityFunction.SimpleFunction { + private final double maxValue; + public final double xzScale; + public final double yScale; ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ // DivineMC end - C2ME: opts_native_math + + public static BlendedNoise createUnseeded(double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier) { + return new BlendedNoise(new XoroshiroRandomSource(0L), xzScale, yScale, xzFactor, yFactor, smearScaleMultiplier); +@@ -62,6 +67,12 @@ public class BlendedNoise implements DensityFunction.SimpleFunction { + this.xzMultiplier = 684.412 * this.xzScale; + this.yMultiplier = 684.412 * this.yScale; + this.maxValue = minLimitNoise.maxBrokenValue(this.yMultiplier); ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ this.c2me$samplerData = org.bxteam.divinemc.math.BindingsTemplate.interpolated_noise_sampler$create(this.c2me$arena, this); ++ this.c2me$samplerDataPtr = this.c2me$samplerData.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + @VisibleForTesting +diff --git a/net/minecraft/world/level/levelgen/synth/NormalNoise.java b/net/minecraft/world/level/levelgen/synth/NormalNoise.java +index 45060882654217eeb9a07357c5149b12fbff02c1..75e3641b40841622a7545bc371197ff1a28968d2 100644 +--- a/net/minecraft/world/level/levelgen/synth/NormalNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/NormalNoise.java +@@ -21,6 +21,15 @@ public class NormalNoise { + private final PerlinNoise second; + private final double maxValue; + private final NormalNoise.NoiseParameters parameters; ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ ++ public long c2me$getPointer() { ++ return this.c2me$samplerDataPtr; ++ } ++ // DivineMC end - C2ME: opts_native_math + + @Deprecated + public static NormalNoise createLegacyNetherBiome(RandomSource random, NormalNoise.NoiseParameters parameters) { +@@ -62,6 +71,12 @@ public class NormalNoise { + + this.valueFactor = 0.16666666666666666 / expectedDeviation(i2 - i1); + this.maxValue = (this.first.maxValue() + this.second.maxValue()) * this.valueFactor; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ this.c2me$samplerData = org.bxteam.divinemc.math.BindingsTemplate.double_octave_sampler_data$create(this.c2me$arena, this.first, this.second, this.valueFactor); ++ this.c2me$samplerDataPtr = this.c2me$samplerData.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public double maxValue() { diff --git a/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch b/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch new file mode 100644 index 0000000..9cb6893 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch @@ -0,0 +1,145 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 31 Jan 2025 22:40:54 +0300 +Subject: [PATCH] Optimize Fluids + + +diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java +index 47a7ce88bf4d26408545dcc061aa763311af0dc9..13877d2bd4289652a9627780839b8d879a66d753 100644 +--- a/net/minecraft/world/level/block/LiquidBlock.java ++++ b/net/minecraft/world/level/block/LiquidBlock.java +@@ -193,6 +193,7 @@ public class LiquidBlock extends Block implements BucketPickup { + Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) { ++ level.setBlock(pos, block.defaultBlockState(), 3); // DivineMC - Optimize Fluids + this.fizz(level, pos); + } + // CraftBukkit end +diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java +index 44bc0823e163bb7edee27889201ec76e93e095cf..974e1e5dcb2613c5aaedd3f2f66483c9dcd6cd23 100644 +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java +@@ -199,6 +199,7 @@ public abstract class FlowingFluid extends Fluid { + BlockPos blockPos = pos.relative(direction); + final BlockState blockStateIfLoaded = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing + if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing ++ if (!shouldSpreadLiquid(level, blockPos, blockStateIfLoaded)) continue; // DivineMC - Optimize Fluids + // CraftBukkit start + org.bukkit.block.Block source = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos); + org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction)); +@@ -213,6 +214,39 @@ public abstract class FlowingFluid extends Fluid { + } + } + ++ // DivineMC start - Optimize Fluids ++ private boolean shouldSpreadLiquid(Level level, BlockPos pos, BlockState state) { ++ if (state.is(Blocks.LAVA)) { ++ boolean isSoulSoil = level.getBlockState(pos.below()).is(Blocks.SOUL_SOIL); ++ ++ for (Direction direction : net.minecraft.world.level.block.LiquidBlock.POSSIBLE_FLOW_DIRECTIONS) { ++ BlockPos blockPos = pos.relative(direction.getOpposite()); ++ if (level.getFluidState(blockPos).is(net.minecraft.tags.FluidTags.WATER)) { ++ Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) { ++ this.fizz(level, pos); ++ level.setBlock(pos, block.defaultBlockState(), 3); ++ } ++ return false; ++ } ++ ++ if (isSoulSoil && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, Blocks.BASALT.defaultBlockState())) { ++ this.fizz(level, pos); ++ } ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ private void fizz(LevelAccessor level, BlockPos pos) { ++ level.levelEvent(1501, pos, 0); ++ } ++ // DivineMC end - Optimize Fluids ++ + protected FluidState getNewLiquid(ServerLevel level, BlockPos pos, BlockState state) { + int i = 0; + int i1 = 0; +@@ -341,33 +375,46 @@ public abstract class FlowingFluid extends Fluid { + protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(level, pos, state); } // Paper - Add BlockBreakBlockEvent + protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); + ++ // DivineMC start - Optimize Fluids + protected int getSlopeDistance(LevelReader level, BlockPos pos, int depth, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadContext) { +- int i = 1000; ++ int slopeFindDistance = this.getSlopeFindDistance(level); ++ int minDistance = slopeFindDistance; + +- for (Direction direction1 : Direction.Plane.HORIZONTAL) { +- if (direction1 != direction) { +- BlockPos blockPos = pos.relative(direction1); +- BlockState blockState = spreadContext.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing +- if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing +- FluidState fluidState = blockState.getFluidState(); +- if (this.canPassThrough(level, this.getFlowing(), pos, state, direction1, blockPos, blockState, fluidState)) { +- if (spreadContext.isHole(blockPos)) { +- return depth; ++ java.util.Deque stack = new java.util.ArrayDeque<>(); ++ stack.push(new Node(pos, depth, direction)); ++ ++ while (!stack.isEmpty()) { ++ Node current = stack.pop(); ++ BlockPos currentPos = current.pos; ++ int currentDepth = current.depth; ++ Direction fromDirection = current.direction; ++ ++ for (Direction dir : Direction.Plane.HORIZONTAL) { ++ if (dir == fromDirection) continue; ++ ++ BlockPos neighborPos = currentPos.relative(dir); ++ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); ++ if (neighborState == null) continue; // Prevent chunk loading ++ ++ FluidState fluidState = neighborState.getFluidState(); ++ if (this.canPassThrough(level, this.getFlowing(), currentPos, state, dir, neighborPos, neighborState, fluidState)) { ++ if (spreadContext.isHole(neighborPos)) { ++ return currentDepth; + } + +- if (depth < this.getSlopeFindDistance(level)) { +- int slopeDistance = this.getSlopeDistance(level, blockPos, depth + 1, direction1.getOpposite(), blockState, spreadContext); +- if (slopeDistance < i) { +- i = slopeDistance; +- } ++ if (currentDepth + 1 < slopeFindDistance && currentDepth + 1 < minDistance) { ++ stack.push(new Node(neighborPos, currentDepth + 1, dir.getOpposite())); + } + } + } + } + +- return i; ++ return minDistance; + } + ++ private record Node(BlockPos pos, int depth, Direction direction) { } ++ // DivineMC end - Optimize Fluids ++ + boolean isWaterHole(BlockGetter level, BlockPos pos, BlockState state, BlockPos belowPos, BlockState belowState) { + return canPassThroughWall(Direction.DOWN, level, pos, state, belowPos, belowState) + && (belowState.getFluidState().getType().isSame(this) || canHoldFluid(level, belowPos, belowState, this.getFlowing())); +diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java +index 85629a43f5469a89dd6078d879f475e8212438ec..35b5a33c79c883f28c99c992695b188524593b55 100644 +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -224,6 +224,7 @@ public abstract class LavaFluid extends FlowingFluid { + // CraftBukkit end + } + ++ level.setBlock(pos, Blocks.STONE.defaultBlockState(), 3); // DivineMC - Optimize Fluids + this.fizz(level, pos); + return; + } diff --git a/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch b/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch new file mode 100644 index 0000000..3bc3c6a --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch @@ -0,0 +1,918 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:09:39 +0300 +Subject: [PATCH] World and Noise gen optimizations + + +diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java +index 55ce935a2fab7e32904d9ff599867269035d703f..4fa84743ba7f570f11a4979b7e5381478c844aef 100644 +--- a/net/minecraft/world/level/ChunkPos.java ++++ b/net/minecraft/world/level/ChunkPos.java +@@ -110,7 +110,12 @@ public class ChunkPos { + + @Override + public boolean equals(Object other) { +- return this == other || other instanceof ChunkPos chunkPos && this.x == chunkPos.x && this.z == chunkPos.z; ++ // DivineMC start - Use standard equals ++ if (other == this) return true; ++ if (other == null || other.getClass() != this.getClass()) return false; ++ ChunkPos thatPos = (ChunkPos) other; ++ return this.x == thatPos.x && this.z == thatPos.z; ++ // DivineMC end - Use standard equals + } + + public int getMiddleBlockX() { +diff --git a/net/minecraft/world/level/biome/TheEndBiomeSource.java b/net/minecraft/world/level/biome/TheEndBiomeSource.java +index cf3172be76fa4c7987ed569138439ff42f92fa7f..bfc65a4d8d1e64f42ff13508020e5e0260e83b98 100644 +--- a/net/minecraft/world/level/biome/TheEndBiomeSource.java ++++ b/net/minecraft/world/level/biome/TheEndBiomeSource.java +@@ -27,6 +27,33 @@ public class TheEndBiomeSource extends BiomeSource { + private final Holder islands; + private final Holder barrens; + ++ // DivineMC start - World gen optimizations ++ private Holder getBiomeForNoiseGenVanilla(int x, int y, int z, Climate.Sampler noise) { ++ int i = QuartPos.toBlock(x); ++ int j = QuartPos.toBlock(y); ++ int k = QuartPos.toBlock(z); ++ int l = SectionPos.blockToSectionCoord(i); ++ int m = SectionPos.blockToSectionCoord(k); ++ if ((long)l * (long)l + (long)m * (long)m <= 4096L) { ++ return this.end; ++ } else { ++ int n = (SectionPos.blockToSectionCoord(i) * 2 + 1) * 8; ++ int o = (SectionPos.blockToSectionCoord(k) * 2 + 1) * 8; ++ double d = noise.erosion().compute(new DensityFunction.SinglePointContext(n, j, o)); ++ if (d > 0.25D) { ++ return this.highlands; ++ } else if (d >= -0.0625D) { ++ return this.midlands; ++ } else { ++ return d < -0.21875D ? this.islands : this.barrens; ++ } ++ } ++ } ++ ++ private final ThreadLocal>> cache = ThreadLocal.withInitial(it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap::new); ++ private final int cacheCapacity = 1024; ++ // DivineMC end - World gen optimizations ++ + public static TheEndBiomeSource create(HolderGetter biomeGetter) { + return new TheEndBiomeSource( + biomeGetter.getOrThrow(Biomes.THE_END), +@@ -55,26 +82,24 @@ public class TheEndBiomeSource extends BiomeSource { + return CODEC; + } + ++ // DivineMC start - World gen optimizations + @Override +- public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { +- int blockPosX = QuartPos.toBlock(x); +- int blockPosY = QuartPos.toBlock(y); +- int blockPosZ = QuartPos.toBlock(z); +- int sectionPosX = SectionPos.blockToSectionCoord(blockPosX); +- int sectionPosZ = SectionPos.blockToSectionCoord(blockPosZ); +- if ((long)sectionPosX * sectionPosX + (long)sectionPosZ * sectionPosZ <= 4096L) { +- return this.end; ++ public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler multiNoiseSampler) { ++ final long key = net.minecraft.world.level.ChunkPos.asLong(biomeX, biomeZ); ++ final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap> cacheThreadLocal = cache.get(); ++ final Holder biome = cacheThreadLocal.get(key); ++ if (biome != null) { ++ return biome; + } else { +- int i = (SectionPos.blockToSectionCoord(blockPosX) * 2 + 1) * 8; +- int i1 = (SectionPos.blockToSectionCoord(blockPosZ) * 2 + 1) * 8; +- double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(i, blockPosY, i1)); +- if (d > 0.25) { +- return this.highlands; +- } else if (d >= -0.0625) { +- return this.midlands; +- } else { +- return d < -0.21875 ? this.islands : this.barrens; ++ final Holder gennedBiome = getBiomeForNoiseGenVanilla(biomeX, biomeY, biomeZ, multiNoiseSampler); ++ cacheThreadLocal.put(key, gennedBiome); ++ if (cacheThreadLocal.size() > cacheCapacity) { ++ for (int i = 0; i < cacheCapacity / 16; i ++) { ++ cacheThreadLocal.removeFirst(); ++ } + } ++ return gennedBiome; + } + } ++ // DivineMC end - World gen optimizations + } +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index c83d0667b19830304f22319a46a23422a8766790..5bc74d860923d6485593cacb67d4c18e20db2634 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -23,6 +23,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + public short tickingFluidCount; + public final PalettedContainer states; + private PalettedContainer> biomes; // CraftBukkit - read/write ++ private static final int sliceSize = 4; // DivineMC - World and Noise gen optimizations + + // Paper start - block counting + private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); +@@ -312,13 +313,15 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + PalettedContainer> palettedContainer = this.biomes.recreate(); + int i = 4; + +- for (int i1 = 0; i1 < 4; i1++) { +- for (int i2 = 0; i2 < 4; i2++) { +- for (int i3 = 0; i3 < 4; i3++) { +- palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); ++ // DivineMC start - World and Noise gen optimizations ++ for (int posY = 0; posY < sliceSize; ++posY) { ++ for (int posZ = 0; posZ < sliceSize; ++posZ) { ++ for (int posX = 0; posX < sliceSize; ++posX) { ++ palettedContainer.getAndSetUnchecked(posX, posY, posZ, biomeResolver.getNoiseBiome(x + posX, y + posY, z + posZ, climateSampler)); + } + } + } ++ // DivineMC end - World and Noise gen optimizations + + this.biomes = palettedContainer; + } +diff --git a/net/minecraft/world/level/levelgen/Aquifer.java b/net/minecraft/world/level/levelgen/Aquifer.java +index c62a15ea4a1bb22e7bcc2fc544acf8a601892029..43dd5f63fe7834d41874ea30651f3fb738d88ba6 100644 +--- a/net/minecraft/world/level/levelgen/Aquifer.java ++++ b/net/minecraft/world/level/levelgen/Aquifer.java +@@ -85,6 +85,15 @@ public interface Aquifer { + private final int minGridZ; + private final int gridSizeX; + private final int gridSizeZ; ++ // DivineMC start - World gen optimizations ++ private int c2me$dist1; ++ private int c2me$dist2; ++ private int c2me$dist3; ++ private long c2me$pos1; ++ private long c2me$pos2; ++ private long c2me$pos3; ++ private double c2me$mutableDoubleThingy; ++ // DivineMC end - World gen optimizations + private static final int[][] SURFACE_SAMPLING_OFFSETS_IN_CHUNKS = new int[][]{ + {0, 0}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1} + }; +@@ -120,6 +129,36 @@ public interface Aquifer { + this.aquiferCache = new Aquifer.FluidStatus[i4]; + this.aquiferLocationCache = new long[i4]; + Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE); ++ // DivineMC start - World gen optimizations ++ if (this.aquiferLocationCache.length % (this.gridSizeX * this.gridSizeZ) != 0) { ++ throw new AssertionError("Array length"); ++ } ++ ++ int sizeY = this.aquiferLocationCache.length / (this.gridSizeX * this.gridSizeZ); ++ ++ final RandomSource random = org.bxteam.divinemc.util.RandomUtil.getRandom(this.positionalRandomFactory); ++ // index: y, z, x ++ for (int y = 0; y < sizeY; y++) { ++ for (int z = 0; z < this.gridSizeZ; z++) { ++ for (int x = 0; x < this.gridSizeX; x++) { ++ final int x1 = x + this.minGridX; ++ final int y1 = y + this.minGridY; ++ final int z1 = z + this.minGridZ; ++ org.bxteam.divinemc.util.RandomUtil.derive(this.positionalRandomFactory, random, x1, y1, z1); ++ int x2 = x1 * 16 + random.nextInt(10); ++ int y2 = y1 * 12 + random.nextInt(9); ++ int z2 = z1 * 16 + random.nextInt(10); ++ int index = this.getIndex(x1, y1, z1); ++ this.aquiferLocationCache[index] = BlockPos.asLong(x2, y2, z2); ++ } ++ } ++ } ++ for (long blockPosition : this.aquiferLocationCache) { ++ if (blockPosition == Long.MAX_VALUE) { ++ throw new AssertionError("Array initialization"); ++ } ++ } ++ // DivineMC end - World gen optimizations + } + + private int getIndex(int gridX, int gridY, int gridZ) { +@@ -132,140 +171,24 @@ public interface Aquifer { + @Nullable + @Override + public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) { ++ // DivineMC start - World gen optimizations + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + if (substance > 0.0) { + this.shouldScheduleFluidUpdate = false; + return null; + } else { +- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, i1, i2); +- if (fluidStatus.at(i1).is(Blocks.LAVA)) { ++ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(i, j, k); ++ if (fluidLevel.at(j).is(Blocks.LAVA)) { + this.shouldScheduleFluidUpdate = false; + return Blocks.LAVA.defaultBlockState(); + } else { +- int i3 = Math.floorDiv(i - 5, 16); +- int i4 = Math.floorDiv(i1 + 1, 12); +- int i5 = Math.floorDiv(i2 - 5, 16); +- int i6 = Integer.MAX_VALUE; +- int i7 = Integer.MAX_VALUE; +- int i8 = Integer.MAX_VALUE; +- int i9 = Integer.MAX_VALUE; +- long l = 0L; +- long l1 = 0L; +- long l2 = 0L; +- long l3 = 0L; +- +- for (int i10 = 0; i10 <= 1; i10++) { +- for (int i11 = -1; i11 <= 1; i11++) { +- for (int i12 = 0; i12 <= 1; i12++) { +- int i13 = i3 + i10; +- int i14 = i4 + i11; +- int i15 = i5 + i12; +- int index = this.getIndex(i13, i14, i15); +- long l4 = this.aquiferLocationCache[index]; +- long l5; +- if (l4 != Long.MAX_VALUE) { +- l5 = l4; +- } else { +- RandomSource randomSource = this.positionalRandomFactory.at(i13, i14, i15); +- l5 = BlockPos.asLong( +- i13 * 16 + randomSource.nextInt(10), i14 * 12 + randomSource.nextInt(9), i15 * 16 + randomSource.nextInt(10) +- ); +- this.aquiferLocationCache[index] = l5; +- } +- +- int i16 = BlockPos.getX(l5) - i; +- int i17 = BlockPos.getY(l5) - i1; +- int i18 = BlockPos.getZ(l5) - i2; +- int i19 = i16 * i16 + i17 * i17 + i18 * i18; +- if (i6 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l; +- l = l5; +- i9 = i8; +- i8 = i7; +- i7 = i6; +- i6 = i19; +- } else if (i7 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l5; +- i9 = i8; +- i8 = i7; +- i7 = i19; +- } else if (i8 >= i19) { +- l3 = l2; +- l2 = l5; +- i9 = i8; +- i8 = i19; +- } else if (i9 >= i19) { +- l3 = l5; +- i9 = i19; +- } +- } +- } +- } +- +- Aquifer.FluidStatus aquiferStatus = this.getAquiferStatus(l); +- double d = similarity(i6, i7); +- BlockState blockState = aquiferStatus.at(i1); +- if (d <= 0.0) { +- if (d >= FLOWING_UPDATE_SIMULARITY) { +- Aquifer.FluidStatus aquiferStatus1 = this.getAquiferStatus(l1); +- this.shouldScheduleFluidUpdate = !aquiferStatus.equals(aquiferStatus1); +- } else { +- this.shouldScheduleFluidUpdate = false; +- } +- +- return blockState; +- } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, i1 - 1, i2).at(i1 - 1).is(Blocks.LAVA)) { +- this.shouldScheduleFluidUpdate = true; +- return blockState; +- } else { +- MutableDouble mutableDouble = new MutableDouble(Double.NaN); +- Aquifer.FluidStatus aquiferStatus2 = this.getAquiferStatus(l1); +- double d1 = d * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus2); +- if (substance + d1 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } else { +- Aquifer.FluidStatus aquiferStatus3 = this.getAquiferStatus(l2); +- double d2 = similarity(i6, i8); +- if (d2 > 0.0) { +- double d3 = d * d2 * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus3); +- if (substance + d3 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- double d3 = similarity(i7, i8); +- if (d3 > 0.0) { +- double d4 = d * d3 * this.calculatePressure(context, mutableDouble, aquiferStatus2, aquiferStatus3); +- if (substance + d4 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- boolean flag = !aquiferStatus.equals(aquiferStatus2); +- boolean flag1 = d3 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus2.equals(aquiferStatus3); +- boolean flag2 = d2 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus.equals(aquiferStatus3); +- if (!flag && !flag1 && !flag2) { +- this.shouldScheduleFluidUpdate = d2 >= FLOWING_UPDATE_SIMULARITY +- && similarity(i6, i9) >= FLOWING_UPDATE_SIMULARITY +- && !aquiferStatus.equals(this.getAquiferStatus(l3)); +- } else { +- this.shouldScheduleFluidUpdate = true; +- } +- +- return blockState; +- } +- } ++ aquiferExtracted$refreshDistPosIdx(i, j, k); ++ return aquiferExtracted$applyPost(context, substance, j, i, k); + } + } ++ // DivineMC end - World gen optimizations + } + + @Override +@@ -278,65 +201,28 @@ public interface Aquifer { + return 1.0 - Math.abs(secondDistance - firstDistance) / 25.0; + } + ++ // DivineMC start - World gen optimizations + private double calculatePressure( +- DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus firstFluid, Aquifer.FluidStatus secondFluid ++ DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 // DivineMC - rename args + ) { + int i = context.blockY(); +- BlockState blockState = firstFluid.at(i); +- BlockState blockState1 = secondFluid.at(i); +- if ((!blockState.is(Blocks.LAVA) || !blockState1.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState1.is(Blocks.LAVA))) { +- int abs = Math.abs(firstFluid.fluidLevel - secondFluid.fluidLevel); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int abs = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); + if (abs == 0) { + return 0.0; + } else { +- double d = 0.5 * (firstFluid.fluidLevel + secondFluid.fluidLevel); +- double d1 = i + 0.5 - d; +- double d2 = abs / 2.0; +- double d3 = 0.0; +- double d4 = 2.5; +- double d5 = 1.5; +- double d6 = 3.0; +- double d7 = 10.0; +- double d8 = 3.0; +- double d9 = d2 - Math.abs(d1); +- double d11; +- if (d1 > 0.0) { +- double d10 = 0.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 1.5; +- } else { +- d11 = d10 / 2.5; +- } +- } else { +- double d10 = 3.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 3.0; +- } else { +- d11 = d10 / 10.0; +- } +- } +- +- double d10x = 2.0; +- double d12; +- if (!(d11 < -2.0) && !(d11 > 2.0)) { +- double value = substance.getValue(); +- if (Double.isNaN(value)) { +- double d13 = this.barrierNoise.compute(context); +- substance.setValue(d13); +- d12 = d13; +- } else { +- d12 = value; +- } +- } else { +- d12 = 0.0; +- } ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, abs); + +- return 2.0 * (d12 + d11); ++ return aquiferExtracted$postCalculateDensity(context, substance, q); + } + } else { + return 2.0; + } + } ++ // DivineMC end - World gen optimizations + + private int gridX(int x) { + return Math.floorDiv(x, 16); +@@ -350,23 +236,25 @@ public interface Aquifer { + return Math.floorDiv(z, 16); + } + +- private Aquifer.FluidStatus getAquiferStatus(long packedPos) { +- int x = BlockPos.getX(packedPos); +- int y = BlockPos.getY(packedPos); +- int z = BlockPos.getZ(packedPos); +- int i = this.gridX(x); +- int i1 = this.gridY(y); +- int i2 = this.gridZ(z); +- int index = this.getIndex(i, i1, i2); +- Aquifer.FluidStatus fluidStatus = this.aquiferCache[index]; +- if (fluidStatus != null) { +- return fluidStatus; ++ // DivineMC start - World gen optimizations ++ private Aquifer.FluidStatus getAquiferStatus(long pos) { ++ int i = BlockPos.getX(pos); ++ int j = BlockPos.getY(pos); ++ int k = BlockPos.getZ(pos); ++ int l = i >> 4; // C2ME - inline: floorDiv(i, 16) ++ int m = Math.floorDiv(j, 12); // C2ME - inline ++ int n = k >> 4; // C2ME - inline: floorDiv(k, 16) ++ int o = this.getIndex(l, m, n); ++ Aquifer.FluidStatus fluidLevel = this.aquiferCache[o]; ++ if (fluidLevel != null) { ++ return fluidLevel; + } else { +- Aquifer.FluidStatus fluidStatus1 = this.computeFluid(x, y, z); +- this.aquiferCache[index] = fluidStatus1; +- return fluidStatus1; ++ Aquifer.FluidStatus fluidLevel2 = this.computeFluid(i, j, k); ++ this.aquiferCache[o] = fluidLevel2; ++ return fluidLevel2; + } + } ++ // DivineMC end - World gen optimizations + + private Aquifer.FluidStatus computeFluid(int x, int y, int z) { + Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(x, y, z); +@@ -406,23 +294,22 @@ public interface Aquifer { + return new Aquifer.FluidStatus(i7, this.computeFluidType(x, y, z, fluidStatus, i7)); + } + ++ // DivineMC start - World gen optimizations + private int computeSurfaceLevel(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int maxSurfaceLevel, boolean fluidPresent) { +- DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(x, y, z); ++ DensityFunction.SinglePointContext unblendedNoisePos = new DensityFunction.SinglePointContext(x, y, z); + double d; + double d1; +- if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, singlePointContext)) { ++ if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, unblendedNoisePos)) { + d = -1.0; + d1 = -1.0; + } else { + int i = maxSurfaceLevel + 8 - y; +- int i1 = 64; +- double d2 = fluidPresent ? Mth.clampedMap((double)i, 0.0, 64.0, 1.0, 0.0) : 0.0; +- double d3 = Mth.clamp(this.fluidLevelFloodednessNoise.compute(singlePointContext), -1.0, 1.0); +- double d4 = Mth.map(d2, 1.0, 0.0, -0.3, 0.8); +- double d5 = Mth.map(d2, 1.0, 0.0, -0.8, 0.4); +- d = d3 - d5; +- d1 = d3 - d4; ++ double f = fluidPresent ? Mth.clampedLerp(1.0, 0.0, ((double) i) / 64.0) : 0.0; // inline ++ double g = Mth.clamp(this.fluidLevelFloodednessNoise.compute(unblendedNoisePos), -1.0, 1.0); ++ d = g + 0.8 + (f - 1.0) * 1.2; // inline ++ d1 = g + 0.3 + (f - 1.0) * 1.1; // inline + } ++ // DivineMC end - World gen optimizations + + int i; + if (d1 > 0.0) { +@@ -466,5 +353,183 @@ public interface Aquifer { + + return blockState; + } ++ ++ // DivineMC start - World gen optimizations ++ private @org.jetbrains.annotations.Nullable BlockState aquiferExtracted$applyPost(DensityFunction.FunctionContext pos, double density, int j, int i, int k) { ++ Aquifer.FluidStatus fluidLevel2 = this.getAquiferStatus(this.c2me$pos1); ++ double d = similarity(this.c2me$dist1, this.c2me$dist2); ++ BlockState blockState = fluidLevel2.at(j); ++ if (d <= 0.0) { ++ this.shouldScheduleFluidUpdate = d >= FLOWING_UPDATE_SIMULARITY; ++ return blockState; ++ } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, j - 1, k).at(j - 1).is(Blocks.LAVA)) { ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } else { ++ this.c2me$mutableDoubleThingy = Double.NaN; ++ Aquifer.FluidStatus fluidLevel3 = this.getAquiferStatus(this.c2me$pos2); ++ double e = d * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel3); ++ if (density + e > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } else { ++ return aquiferExtracted$getFinalBlockState(pos, density, d, fluidLevel2, fluidLevel3, blockState); ++ } ++ } ++ } ++ ++ private BlockState aquiferExtracted$getFinalBlockState(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, Aquifer.FluidStatus fluidLevel3, BlockState blockState) { ++ Aquifer.FluidStatus fluidLevel4 = this.getAquiferStatus(this.c2me$pos3); ++ double f = similarity(this.c2me$dist1, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel2, f, fluidLevel4)) return null; ++ ++ double g = similarity(this.c2me$dist2, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel3, g, fluidLevel4)) return null; ++ ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } ++ ++ private boolean aquiferExtracted$extractedCheckFG(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, double f, Aquifer.FluidStatus fluidLevel4) { ++ if (f > 0.0) { ++ double g = d * f * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel4); ++ if (density + g > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private void aquiferExtracted$refreshDistPosIdx(int x, int y, int z) { ++ int gx = (x - 5) >> 4; ++ int gy = Math.floorDiv(y + 1, 12); ++ int gz = (z - 5) >> 4; ++ int dist1 = Integer.MAX_VALUE; ++ int dist2 = Integer.MAX_VALUE; ++ int dist3 = Integer.MAX_VALUE; ++ long pos1 = 0; ++ long pos2 = 0; ++ long pos3 = 0; ++ ++ for (int offY = -1; offY <= 1; ++offY) { ++ for (int offZ = 0; offZ <= 1; ++offZ) { ++ for (int offX = 0; offX <= 1; ++offX) { ++ int posIdx = this.getIndex(gx + offX, gy + offY, gz + offZ); ++ ++ long position = this.aquiferLocationCache[posIdx]; ++ ++ int dx = BlockPos.getX(position) - x; ++ int dy = BlockPos.getY(position) - y; ++ int dz = BlockPos.getZ(position) - z; ++ int dist = dx * dx + dy * dy + dz * dz; ++ ++ if (dist3 >= dist) { ++ pos3 = position; ++ dist3 = dist; ++ } ++ if (dist2 >= dist) { ++ pos3 = pos2; ++ dist3 = dist2; ++ pos2 = position; ++ dist2 = dist; ++ } ++ if (dist1 >= dist) { ++ pos2 = pos1; ++ dist2 = dist1; ++ pos1 = position; ++ dist1 = dist; ++ } ++ } ++ } ++ } ++ ++ this.c2me$dist1 = dist1; ++ this.c2me$dist2 = dist2; ++ this.c2me$dist3 = dist3; ++ this.c2me$pos1 = pos1; ++ this.c2me$pos2 = pos2; ++ this.c2me$pos3 = pos3; ++ } ++ ++ private double c2me$calculateDensityModified( ++ DensityFunction.FunctionContext pos, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 ++ ) { ++ int i = pos.blockY(); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int j = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); ++ if (j == 0) { ++ return 0.0; ++ } else { ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, j); ++ ++ return aquiferExtracted$postCalculateDensityModified(pos, q); ++ } ++ } else { ++ return 2.0; ++ } ++ } ++ ++ private double aquiferExtracted$postCalculateDensity(DensityFunction.FunctionContext pos, MutableDouble mutableDouble, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = mutableDouble.getValue(); ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ mutableDouble.setValue(t); ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private double aquiferExtracted$postCalculateDensityModified(DensityFunction.FunctionContext pos, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = this.c2me$mutableDoubleThingy; ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ this.c2me$mutableDoubleThingy = t; ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private static double aquiferExtracted$getQ(double i, double d, double j) { ++ double e = i + 0.5 - d; ++ double f = j / 2.0; ++ double o = f - Math.abs(e); ++ double q; ++ if (e > 0.0) { ++ if (o > 0.0) { ++ q = o / 1.5; ++ } else { ++ q = o / 2.5; ++ } ++ } else { ++ double p = 3.0 + o; ++ if (p > 0.0) { ++ q = p / 3.0; ++ } else { ++ q = p / 10.0; ++ } ++ } ++ return q; ++ } ++ // DivineMC end - World gen optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java +index 131923282c9ecbcb1d7f45a826da907c02bd2716..36dd3eb0cb29d546531aec91a9c486be09975797 100644 +--- a/net/minecraft/world/level/levelgen/Beardifier.java ++++ b/net/minecraft/world/level/levelgen/Beardifier.java +@@ -29,6 +29,17 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + }); + private final ObjectListIterator pieceIterator; + private final ObjectListIterator junctionIterator; ++ // DivineMC start - World gen optimizations ++ private Beardifier.Rigid[] c2me$pieceArray; ++ private JigsawJunction[] c2me$junctionArray; ++ ++ private void c2me$initArrays() { ++ this.c2me$pieceArray = com.google.common.collect.Iterators.toArray(this.pieceIterator, Beardifier.Rigid.class); ++ this.pieceIterator.back(Integer.MAX_VALUE); ++ this.c2me$junctionArray = com.google.common.collect.Iterators.toArray(this.junctionIterator, JigsawJunction.class); ++ this.junctionIterator.back(Integer.MAX_VALUE); ++ } ++ // DivineMC end - World gen optimizations + + public static Beardifier forStructuresInChunk(StructureManager structureManager, ChunkPos chunkPos) { + int minBlockX = chunkPos.getMinBlockX(); +@@ -76,50 +87,44 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + this.junctionIterator = junctionIterator; + } + ++ // DivineMC start - World gen optimizations + @Override + public double compute(DensityFunction.FunctionContext context) { ++ if (this.c2me$pieceArray == null || this.c2me$junctionArray == null) { ++ this.c2me$initArrays(); ++ } + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + double d = 0.0; + +- while (this.pieceIterator.hasNext()) { +- Beardifier.Rigid rigid = this.pieceIterator.next(); +- BoundingBox boundingBox = rigid.box(); +- int groundLevelDelta = rigid.groundLevelDelta(); +- int max = Math.max(0, Math.max(boundingBox.minX() - i, i - boundingBox.maxX())); +- int max1 = Math.max(0, Math.max(boundingBox.minZ() - i2, i2 - boundingBox.maxZ())); +- int i3 = boundingBox.minY() + groundLevelDelta; +- int i4 = i1 - i3; +- +- int i5 = switch (rigid.terrainAdjustment()) { +- case NONE -> 0; +- case BURY, BEARD_THIN -> i4; +- case BEARD_BOX -> Math.max(0, Math.max(i3 - i1, i1 - boundingBox.maxY())); +- case ENCAPSULATE -> Math.max(0, Math.max(boundingBox.minY() - i1, i1 - boundingBox.maxY())); +- }; ++ for (Beardifier.Rigid piece : this.c2me$pieceArray) { ++ BoundingBox blockBox = piece.box(); ++ int l = piece.groundLevelDelta(); ++ int m = Math.max(0, Math.max(blockBox.minX() - i, i - blockBox.maxX())); ++ int n = Math.max(0, Math.max(blockBox.minZ() - k, k - blockBox.maxZ())); ++ int o = blockBox.minY() + l; ++ int p = j - o; + +- d += switch (rigid.terrainAdjustment()) { ++ d += switch (piece.terrainAdjustment()) { // 2 switch statement merged + case NONE -> 0.0; +- case BURY -> getBuryContribution(max, i5 / 2.0, max1); +- case BEARD_THIN, BEARD_BOX -> getBeardContribution(max, i5, max1, i4) * 0.8; +- case ENCAPSULATE -> getBuryContribution(max / 2.0, i5 / 2.0, max1 / 2.0) * 0.8; ++ case BURY -> getBuryContribution(m, (double)p / 2.0, n); ++ case BEARD_THIN -> getBeardContribution(m, p, n, p) * 0.8; ++ case BEARD_BOX -> getBeardContribution(m, Math.max(0, Math.max(o - j, j - blockBox.maxY())), n, p) * 0.8; ++ case ENCAPSULATE -> getBuryContribution((double)m / 2.0, (double)Math.max(0, Math.max(blockBox.minY() - j, j - blockBox.maxY())) / 2.0, (double)n / 2.0) * 0.8; + }; + } + +- this.pieceIterator.back(Integer.MAX_VALUE); +- +- while (this.junctionIterator.hasNext()) { +- JigsawJunction jigsawJunction = this.junctionIterator.next(); +- int i6 = i - jigsawJunction.getSourceX(); +- int groundLevelDelta = i1 - jigsawJunction.getSourceGroundY(); +- int max = i2 - jigsawJunction.getSourceZ(); +- d += getBeardContribution(i6, groundLevelDelta, max, groundLevelDelta) * 0.4; ++ for (JigsawJunction jigsawJunction : this.c2me$junctionArray) { ++ int r = i - jigsawJunction.getSourceX(); ++ int l = j - jigsawJunction.getSourceGroundY(); ++ int m = k - jigsawJunction.getSourceZ(); ++ d += getBeardContribution(r, l, m, l) * 0.4; + } + +- this.junctionIterator.back(Integer.MAX_VALUE); + return d; + } ++ // DivineMC end - World gen optimizations + + @Override + public double minValue() { +@@ -132,8 +137,14 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + } + + private static double getBuryContribution(double x, double y, double z) { +- double len = Mth.length(x, y, z); +- return Mth.clampedMap(len, 0.0, 6.0, 1.0, 0.0); ++ // DivineMC start - World gen optimizations ++ double d = Math.sqrt(x * x + y * y + z * z); ++ if (d > 6.0) { ++ return 0.0; ++ } else { ++ return 1.0 - d / 6.0; ++ } ++ // DivineMC end - World gen optimizations + } + + private static double getBeardContribution(int x, int y, int z, int height) { +diff --git a/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +index c67168517774a0ad9ca43422a79ef14a8ea0c2e8..026dfbbb6c3fd5cd274dcbf721e5cf3af889e3d9 100644 +--- a/net/minecraft/world/level/levelgen/LegacyRandomSource.java ++++ b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +@@ -53,13 +53,7 @@ public class LegacyRandomSource implements BitRandomSource { + return this.gaussianSource.nextGaussian(); + } + +- public static class LegacyPositionalRandomFactory implements PositionalRandomFactory { +- private final long seed; +- +- public LegacyPositionalRandomFactory(long seed) { +- this.seed = seed; +- } +- ++ public record LegacyPositionalRandomFactory(long seed) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 65728ef17e63d71833677fdcbd5bb90794b4822b..6ef91bd952d4a0c1ffa6f534e4fcdd5c0a9db40b 100644 +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -65,11 +65,13 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + } + + private static Aquifer.FluidPicker createFluidPicker(NoiseGeneratorSettings settings) { +- Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); +- int seaLevel = settings.seaLevel(); +- Aquifer.FluidStatus fluidStatus1 = new Aquifer.FluidStatus(seaLevel, settings.defaultFluid()); +- Aquifer.FluidStatus fluidStatus2 = new Aquifer.FluidStatus(DimensionType.MIN_Y * 2, Blocks.AIR.defaultBlockState()); +- return (x, y, z) -> y < Math.min(-54, seaLevel) ? fluidStatus : fluidStatus1; ++ // DivineMC start - World gen optimizations ++ Aquifer.FluidStatus fluidLevel = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); ++ int i = settings.seaLevel(); ++ Aquifer.FluidStatus fluidLevel2 = new Aquifer.FluidStatus(i, settings.defaultFluid()); ++ final int min = Math.min(-54, i); ++ return (j, k, lx) -> k < min ? fluidLevel : fluidLevel2; ++ // DivineMC end - World gen optimizations + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/NoiseSettings.java b/net/minecraft/world/level/levelgen/NoiseSettings.java +index 4cf3a364595ba5f81f741295695cb9a449bdf672..44df2ac0bd972c4d97fc89cd0c2d2d83480ca3e1 100644 +--- a/net/minecraft/world/level/levelgen/NoiseSettings.java ++++ b/net/minecraft/world/level/levelgen/NoiseSettings.java +@@ -8,7 +8,7 @@ import net.minecraft.core.QuartPos; + import net.minecraft.world.level.LevelHeightAccessor; + import net.minecraft.world.level.dimension.DimensionType; + +-public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { ++public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical, int horizontalCellBlockCount, int verticalCellBlockCount) { // DivineMC - NoiseSettings optimizations + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + Codec.intRange(DimensionType.MIN_Y, DimensionType.MAX_Y).fieldOf("min_y").forGetter(NoiseSettings::minY), +@@ -16,7 +16,10 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + Codec.intRange(1, 4).fieldOf("size_horizontal").forGetter(NoiseSettings::noiseSizeHorizontal), + Codec.intRange(1, 4).fieldOf("size_vertical").forGetter(NoiseSettings::noiseSizeVertical) + ) +- .apply(instance, NoiseSettings::new) ++ // DivineMC start - NoiseSettings optimizations ++ .apply(instance, (Integer minY1, Integer height1, Integer noiseSizeHorizontal1, Integer noiseSizeVertical1) -> new NoiseSettings(minY1, height1, noiseSizeHorizontal1, noiseSizeVertical1, ++ QuartPos.toBlock(noiseSizeHorizontal1), QuartPos.toBlock(noiseSizeVertical1))) ++ // DivineMC end - NoiseSettings optimizations + ) + .comapFlatMap(NoiseSettings::guardY, Function.identity()); + protected static final NoiseSettings OVERWORLD_NOISE_SETTINGS = create(-64, 384, 1, 2); +@@ -36,7 +39,7 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public static NoiseSettings create(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { +- NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical); ++ NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical, QuartPos.toBlock(noiseSizeHorizontal), QuartPos.toBlock(noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + guardY(noiseSettings).error().ifPresent(error -> { + throw new IllegalStateException(error.message()); + }); +@@ -44,16 +47,16 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public int getCellHeight() { +- return QuartPos.toBlock(this.noiseSizeVertical()); ++ return verticalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public int getCellWidth() { +- return QuartPos.toBlock(this.noiseSizeHorizontal()); ++ return horizontalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public NoiseSettings clampToHeightAccessor(LevelHeightAccessor heightAccessor) { + int max = Math.max(this.minY, heightAccessor.getMinY()); + int i = Math.min(this.minY + this.height, heightAccessor.getMaxY() + 1) - max; +- return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical); ++ return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical, QuartPos.toBlock(this.noiseSizeHorizontal), QuartPos.toBlock(this.noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +index 9d3a9ca1e13cd80f468f1352bbb74345f03903dd..d97b9b43686bda0a95fc02f6ca31b2d07d603a32 100644 +--- a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java ++++ b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +@@ -106,15 +106,7 @@ public class XoroshiroRandomSource implements RandomSource { + return this.randomNumberGenerator.nextLong() >>> 64 - bits; + } + +- public static class XoroshiroPositionalRandomFactory implements PositionalRandomFactory { +- private final long seedLo; +- private final long seedHi; +- +- public XoroshiroPositionalRandomFactory(long seedLo, long seedHi) { +- this.seedLo = seedLo; +- this.seedHi = seedHi; +- } +- ++ public record XoroshiroPositionalRandomFactory(long seedLo, long seedHi) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +index ffac5b7b1eb1364ab8442d7145a7b4ebde68ee10..ef28df96ed569113a9d61f6ac4b4d84d578c02da 100644 +--- a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +@@ -187,7 +187,7 @@ public class PerlinNoise { + } + + public static double wrap(double value) { +- return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7; ++ return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7; // DivineMC - avoid casting + } + + protected int firstOctave() { diff --git a/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch b/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch new file mode 100644 index 0000000..83eb750 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:33:03 +0300 +Subject: [PATCH] Chunk System optimization + + +diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java +index 26c8c1e5598daf3550aef05b12218c47bda6618b..94c824ab1457939c425e1f99929d3222ee2c18a0 100644 +--- a/net/minecraft/world/level/LevelReader.java ++++ b/net/minecraft/world/level/LevelReader.java +@@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste + + @Override + default Holder getNoiseBiome(int x, int y, int z) { +- ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); ++ ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization + return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); + } + ++ // DivineMC start - Chunk System optimization ++ private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { ++ if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { ++ final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); ++ if (holder != null) { ++ final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); ++ final net.minecraft.server.level.ChunkResult either = future.getNow(null); ++ if (either != null) { ++ final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); ++ if (chunk != null) return chunk; ++ } ++ } ++ } ++ return instance.getChunk(x, z, chunkStatus, create); ++ } ++ // DivineMC end - Chunk System optimization ++ + Holder getUncachedNoiseBiome(int x, int y, int z); + + boolean isClientSide(); +diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java +index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 +--- a/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { + }); + } + ++ // DivineMC start - Chunk System optimization ++ private void checkHardLimit() { ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { ++ LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); ++ while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { ++ writeResult0(); ++ } ++ } ++ } ++ ++ private void writeResult0() { ++ java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); ++ if (iterator.hasNext()) { ++ java.util.Map.Entry entry = iterator.next(); ++ iterator.remove(); ++ this.runStore(entry.getKey(), entry.getValue()); ++ } ++ } ++ // DivineMC end - Chunk System optimization ++ + private void storePendingChunk() { ++ // DivineMC start - Chunk System optimization ++ if (!this.pendingWrites.isEmpty()) { ++ checkHardLimit(); ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { ++ int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); ++ for (int i = 0; i < writeFrequency; i++) { ++ writeResult0(); ++ } ++ } ++ } ++ // DivineMC end - Chunk System optimization + Entry entry = this.pendingWrites.pollFirstEntry(); + if (entry != null) { + this.runStore(entry.getKey(), entry.getValue()); +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected + this.folder = folder; +- this.sync = sync; ++ this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes + this.info = info; + this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers + } diff --git a/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch b/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch new file mode 100644 index 0000000..a62fe05 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch @@ -0,0 +1,682 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:55:34 +0300 +Subject: [PATCH] Optimize hoppers + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 527547b98b70429830a3cf82fddba202e0ba8131..ee45df82c3328d5cf91cb3e56786aec2d5263641 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1765,7 +1765,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent + serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + serverLevel.updateLagCompensationTick(); // Paper - lag compensation +- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers + serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables + profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); + profilerFiller.push("tick"); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 92f3e5d929997a974c367ec3ce02cda4acdb5183..5dfab22692947e0e372044c6dca181f6847fc58a 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -195,7 +195,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); + private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); + final Set navigatingMobs = new ObjectOpenHashSet<>(); +- volatile boolean isUpdatingNavigations; ++ final java.util.concurrent.atomic.AtomicBoolean isUpdatingNavigations = new java.util.concurrent.atomic.AtomicBoolean(false); // DivineMC - Optimize Hoppers + protected final Raids raids; + private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); + private final List blockEventsToReschedule = new ArrayList<>(64); +@@ -1771,7 +1771,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { +- if (this.isUpdatingNavigations) { ++ if (this.isUpdatingNavigations.get() && false) { // DivineMC + String string = "recursive call to sendBlockUpdated"; + Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); + } +@@ -1802,13 +1802,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - catch CME see below why + + try { +- this.isUpdatingNavigations = true; ++ this.isUpdatingNavigations.set(true); // DivineMC + + for (PathNavigation pathNavigation : list) { + pathNavigation.recomputePath(); + } + } finally { +- this.isUpdatingNavigations = false; ++ this.isUpdatingNavigations.set(false); // DivineMC + } + } + } // Paper - option to disable pathfinding updates +@@ -2699,7 +2699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob mob) { +- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning ++ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +@@ -2769,7 +2769,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob mob) { +- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning ++ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 5cd1326ad5d046c88b2b3449d610a78fa880b4cd..a0ee6ad6e7a6791605191d20d742e16cc9857a60 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -139,56 +139,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- // Paper start - Perf: Optimize Hoppers +- private static final int HOPPER_EMPTY = 0; +- private static final int HOPPER_HAS_ITEMS = 1; +- private static final int HOPPER_IS_FULL = 2; +- +- private static int getFullState(final HopperBlockEntity hopper) { +- hopper.unpackLootTable(null); +- +- final List hopperItems = hopper.items; +- +- boolean empty = true; +- boolean full = true; +- +- for (int i = 0, len = hopperItems.size(); i < len; ++i) { +- final ItemStack stack = hopperItems.get(i); +- if (stack.isEmpty()) { +- full = false; +- continue; +- } +- +- if (!full) { +- // can't be full +- return HOPPER_HAS_ITEMS; +- } +- +- empty = false; +- +- if (stack.getCount() != stack.getMaxStackSize()) { +- // can't be full or empty +- return HOPPER_HAS_ITEMS; +- } +- } +- +- return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); +- } +- // Paper end - Perf: Optimize Hoppers +- + private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) { + if (level.isClientSide) { + return false; + } else { + if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) { + boolean flag = false; +- final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers +- if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers ++ if (!blockEntity.isEmpty()) { // DivineMC - Optimize hoppers + flag = ejectItems(level, pos, blockEntity); + } + +- if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers +- flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items ++ if (!blockEntity.inventoryFull()) { // DivineMC - Optimize hoppers ++ flag |= validator.getAsBoolean(); // DivineMC - Optimize hoppers + } + + if (flag) { +@@ -212,206 +174,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return true; + } + +- // Paper start - Perf: Optimize Hoppers +- public static boolean skipHopperEvents; +- private static boolean skipPullModeEventFire; +- private static boolean skipPushModeEventFire; +- +- private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { +- skipPushModeEventFire = skipHopperEvents; +- boolean foundItem = false; +- for (int i = 0; i < hopper.getContainerSize(); ++i) { +- final ItemStack item = hopper.getItem(i); +- if (!item.isEmpty()) { +- foundItem = true; +- ItemStack origItemStack = item; +- ItemStack movedItem = origItemStack; +- +- final int originalItemCount = origItemStack.getCount(); +- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); +- origItemStack.setCount(movedItemCount); +- +- // We only need to fire the event once to give protection plugins a chance to cancel this event +- // Because nothing uses getItem, every event call should end up the same result. +- if (!skipPushModeEventFire) { +- movedItem = callPushMoveEvent(destination, movedItem, hopper); +- if (movedItem == null) { // cancelled +- origItemStack.setCount(originalItemCount); +- return false; +- } +- } +- +- final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); +- final int remainingItemCount = remainingItem.getCount(); +- if (remainingItemCount != movedItemCount) { +- origItemStack = origItemStack.copy(true); +- origItemStack.setCount(originalItemCount); +- if (!origItemStack.isEmpty()) { +- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); +- } +- hopper.setItem(i, origItemStack); +- destination.setChanged(); +- return true; +- } +- origItemStack.setCount(originalItemCount); +- } +- } +- if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown +- hopper.setCooldown(level.spigotConfig.hopperTransfer); +- } +- return false; +- } +- +- private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { +- ItemStack movedItem = origItemStack; +- final int originalItemCount = origItemStack.getCount(); +- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); +- container.setChanged(); // original logic always marks source inv as changed even if no move happens. +- movedItem.setCount(movedItemCount); +- +- if (!skipPullModeEventFire) { +- movedItem = callPullMoveEvent(hopper, container, movedItem); +- if (movedItem == null) { // cancelled +- origItemStack.setCount(originalItemCount); +- // Drastically improve performance by returning true. +- // No plugin could have relied on the behavior of false as the other call +- // site for IMIE did not exhibit the same behavior +- return true; +- } +- } +- +- final ItemStack remainingItem = addItem(container, hopper, movedItem, null); +- final int remainingItemCount = remainingItem.getCount(); +- if (remainingItemCount != movedItemCount) { +- origItemStack = origItemStack.copy(true); +- origItemStack.setCount(originalItemCount); +- if (!origItemStack.isEmpty()) { +- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); +- } +- +- ignoreBlockEntityUpdates = true; +- container.setItem(i, origItemStack); +- ignoreBlockEntityUpdates = false; +- container.setChanged(); +- return true; +- } +- origItemStack.setCount(originalItemCount); +- +- if (level.paperConfig().hopper.cooldownWhenFull) { +- applyCooldown(hopper); +- } +- +- return false; +- } +- +- @Nullable +- private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { +- final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); +- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( +- hopper.getOwner(false).getInventory(), +- org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), +- destinationInventory, +- true +- ); +- final boolean result = event.callEvent(); +- if (!event.calledGetItem && !event.calledSetItem) { +- skipPushModeEventFire = true; +- } +- if (!result) { +- applyCooldown(hopper); +- return null; +- } +- +- if (event.calledSetItem) { +- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); +- } else { +- return itemStack; +- } +- } +- +- @Nullable +- private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { +- final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); +- final org.bukkit.inventory.Inventory destination = getInventory(hopper); +- +- // Mirror is safe as no plugins ever use this item +- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); +- final boolean result = event.callEvent(); +- if (!event.calledGetItem && !event.calledSetItem) { +- skipPullModeEventFire = true; +- } +- if (!result) { +- applyCooldown(hopper); +- return null; +- } +- +- if (event.calledSetItem) { +- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); +- } else { +- return itemstack; +- } +- } +- +- private static org.bukkit.inventory.Inventory getInventory(final Container container) { +- final org.bukkit.inventory.Inventory sourceInventory; +- if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) { +- // Have to special-case large chests as they work oddly +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- } else if (container instanceof BlockEntity blockEntity) { +- sourceInventory = blockEntity.getOwner(false).getInventory(); +- } else if (container.getOwner() != null) { +- sourceInventory = container.getOwner().getInventory(); +- } else { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); +- } +- return sourceInventory; +- } +- +- private static void applyCooldown(final Hopper hopper) { +- if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { +- blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); +- } +- } +- +- private static boolean allMatch(Container container, Direction direction, java.util.function.BiPredicate test) { +- if (container instanceof WorldlyContainer) { +- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { +- if (!test.test(container.getItem(slot), slot)) { +- return false; +- } +- } +- } else { +- int size = container.getContainerSize(); +- for (int slot = 0; slot < size; slot++) { +- if (!test.test(container.getItem(slot), slot)) { +- return false; +- } +- } +- } +- return true; +- } +- +- private static boolean anyMatch(Container container, Direction direction, java.util.function.BiPredicate test) { +- if (container instanceof WorldlyContainer) { +- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { +- if (test.test(container.getItem(slot), slot)) { +- return true; +- } +- } +- } else { +- int size = container.getContainerSize(); +- for (int slot = 0; slot < size; slot++) { +- if (test.test(container.getItem(slot), slot)) { +- return true; +- } +- } +- } +- return true; +- } +- private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemStack, i) -> itemStack.getCount() >= itemStack.getMaxStackSize(); +- private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemStack, i) -> itemStack.isEmpty(); +- // Paper end - Perf: Optimize Hoppers +- + private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { + Container attachedContainer = getAttachedContainer(level, pos, blockEntity); + if (attachedContainer == null) { +@@ -421,60 +183,59 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (isFullContainer(attachedContainer, opposite)) { + return false; + } else { +- // Paper start - Perf: Optimize Hoppers +- return hopperPush(level, attachedContainer, opposite, blockEntity); +- //for (int i = 0; i < blockEntity.getContainerSize(); i++) { +- // ItemStack item = blockEntity.getItem(i); +- // if (!item.isEmpty()) { +- // int count = item.getCount(); +- // // CraftBukkit start - Call event when pushing items into other inventories +- // ItemStack original = item.copy(); +- // org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- // blockEntity.removeItem(i, level.spigotConfig.hopperAmount) +- // ); // Spigot +- +- // org.bukkit.inventory.Inventory destinationInventory; +- // // Have to special case large chests as they work oddly +- // if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- // } else if (attachedContainer.getOwner() != null) { +- // destinationInventory = attachedContainer.getOwner().getInventory(); +- // } else { +- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); +- // } +- +- // org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- // blockEntity.getOwner().getInventory(), +- // oitemstack, +- // destinationInventory, +- // true +- // ); +- // if (!event.callEvent()) { +- // blockEntity.setItem(i, original); +- // blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot +- // return false; +- // } +- // int origCount = event.getItem().getAmount(); // Spigot +- // ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); +- // // CraftBukkit end +- +- // if (itemStack.isEmpty()) { +- // attachedContainer.setChanged(); +- // return true; +- // } +- +- // item.setCount(count); +- // // Spigot start +- // item.shrink(origCount - itemStack.getCount()); +- // if (count <= level.spigotConfig.hopperAmount) { +- // // Spigot end +- // blockEntity.setItem(i, item); +- // } +- // } +- //} +- +- //return false; +- // Paper end - Perf: Optimize Hoppers ++ // DivineMC start - Optimize hoppers ++ for (int i = 0; i < blockEntity.getContainerSize(); i++) { ++ ItemStack item = blockEntity.getItem(i); ++ if (!item.isEmpty()) { ++ int count = item.getCount(); ++ // CraftBukkit start - Call event when pushing items into other inventories ++ ItemStack original = item.copy(); ++ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ blockEntity.removeItem(i, level.spigotConfig.hopperAmount) ++ ); // Spigot ++ ++ org.bukkit.inventory.Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (attachedContainer.getOwner() != null) { ++ destinationInventory = attachedContainer.getOwner().getInventory(); ++ } else { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); ++ } ++ ++ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ blockEntity.getOwner().getInventory(), ++ oitemstack, ++ destinationInventory, ++ true ++ ); ++ if (!event.callEvent()) { ++ blockEntity.setItem(i, original); ++ blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); ++ // CraftBukkit end ++ ++ if (itemStack.isEmpty()) { ++ attachedContainer.setChanged(); ++ return true; ++ } ++ ++ item.setCount(count); ++ // Spigot start ++ item.shrink(origCount - itemStack.getCount()); ++ if (count <= level.spigotConfig.hopperAmount) { ++ // Spigot end ++ blockEntity.setItem(i, item); ++ } ++ } ++ } ++ ++ return false; ++ // DivineMC end - Optimize hoppers + } + } + } +@@ -529,7 +290,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); + if (sourceContainer != null) { + Direction direction = Direction.DOWN; +- skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers + + for (int i : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot +@@ -555,59 +315,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction, Level level) { // Spigot + ItemStack item = container.getItem(slot); + if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) { +- // Paper start - Perf: Optimize Hoppers +- return hopperPull(level, hopper, container, item, slot); +- //int count = item.getCount(); +- //// CraftBukkit start - Call event on collection of items from inventories into the hopper +- //ItemStack original = item.copy(); +- //org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- // container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot +- //); +- +- //org.bukkit.inventory.Inventory sourceInventory; +- //// Have to special case large chests as they work oddly +- //if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- //} else if (container.getOwner() != null) { +- // sourceInventory = container.getOwner().getInventory(); +- //} else { +- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); +- //} +- +- //org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- // sourceInventory, +- // oitemstack, +- // hopper.getOwner().getInventory(), +- // false +- //); +- +- //if (!event.callEvent()) { +- // container.setItem(slot, original); +- +- // if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { +- // hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot +- // } +- +- // return false; +- //} +- //int origCount = event.getItem().getAmount(); // Spigot +- //ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); +- //// CraftBukkit end +- +- //if (itemStack.isEmpty()) { +- // container.setChanged(); +- // return true; +- //} +- +- //item.setCount(count); +- //// Spigot start +- //item.shrink(origCount - itemStack.getCount()); +- //if (count <= level.spigotConfig.hopperAmount) { +- // // Spigot end +- // container.setItem(slot, item); +- //} +- // Paper end - Perf: Optimize Hoppers ++ // DivineMC start - Optimize hoppers ++ int count = item.getCount(); ++ // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ ItemStack original = item.copy(); ++ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot ++ ); ++ ++ org.bukkit.inventory.Inventory sourceInventory; ++ // Have to special case large chests as they work oddly ++ if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (container.getOwner() != null) { ++ sourceInventory = container.getOwner().getInventory(); ++ } else { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); ++ } ++ ++ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ sourceInventory, ++ oitemstack, ++ hopper.getOwner().getInventory(), ++ false ++ ); ++ ++ if (!event.callEvent()) { ++ container.setItem(slot, original); ++ ++ if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { ++ hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot ++ } ++ ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); ++ // CraftBukkit end ++ ++ if (itemStack.isEmpty()) { ++ container.setChanged(); ++ return true; ++ } ++ ++ item.setCount(count); ++ // Spigot start ++ item.shrink(origCount - itemStack.getCount()); ++ if (count <= level.spigotConfig.hopperAmount) { ++ // Spigot end ++ container.setItem(slot, item); ++ } + } ++ // DivineMC end - Optimize hoppers + + return false; + } +@@ -615,15 +374,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container container, ItemEntity item) { + boolean flag = false; + // CraftBukkit start +- if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers + org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent( +- getInventory(container), (org.bukkit.entity.Item) item.getBukkitEntity() // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation ++ container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity() // DivineMC - Optimize hoppers + ); + if (!event.callEvent()) { + return false; + } + // CraftBukkit end +- } // Paper - Perf: Optimize Hoppers + ItemStack itemStack = item.getItem().copy(); + ItemStack itemStack1 = addItem(null, container, itemStack, null); + if (itemStack1.isEmpty()) { +@@ -678,9 +435,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(destination.getMaxStackSize()); + } + // Spigot end +- ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers + destination.setItem(slot, stack); +- ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (canMergeItems(item, stack)) { +@@ -768,19 +523,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + public static Container getContainerAt(Level level, BlockPos pos) { +- return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, true); // Paper - Optimize hoppers ++ return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); // DivineMC - Optimize hoppers + } + + @Nullable + private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) { +- // Paper start - Perf: Optimize Hoppers +- return HopperBlockEntity.getContainerAt(level, pos, state, x, y, z, false); +- } +- @Nullable +- private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, final boolean optimizeEntities) { +- // Paper end - Perf: Optimize Hoppers + Container blockContainer = getBlockContainer(level, pos, state); +- if (blockContainer == null && (!optimizeEntities || !level.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers ++ if (blockContainer == null) { // DivineMC - Optimize hoppers + blockContainer = getEntityContainer(level, x, y, z); + } + +@@ -806,14 +555,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static Container getEntityContainer(Level level, double x, double y, double z) { +- List entities = level.getEntitiesOfClass( +- (Class) Container.class, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR // Paper - Perf: Optimize hoppers +- ); ++ List entities = level.getEntities( ++ (Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR ++ ); // DivineMC - Optimize hoppers + return !entities.isEmpty() ? (Container)entities.get(level.random.nextInt(entities.size())) : null; + } + + private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) { +- return stack1.getCount() < stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! ++ return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // DivineMC - Optimize hoppers + } + + @Override +diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java +index faf45ac459f7c25309d6ef6dce371d484a0dae7b..6f0d1b28a45b93c51c5476283f1629a86e3420d1 100644 +--- a/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -17,7 +17,8 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.ListTag; + import net.minecraft.world.level.ChunkPos; + +-public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // Paper - rewrite chunk system ++public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // DivineMC ++ private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LevelChunkTicks.class); // Paper - rewrite chunk system + private final Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); + @Nullable + private List> pendingTicks; +@@ -71,10 +72,18 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + @Nullable + public ScheduledTick poll() { +- ScheduledTick scheduledTick = this.tickQueue.poll(); +- if (scheduledTick != null) { +- this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ // DivineMC start - catch exceptions when polling chunk ticks ++ ScheduledTick scheduledTick = null; ++ try { ++ scheduledTick = this.tickQueue.poll(); ++ if (scheduledTick != null) { ++ this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ } ++ } catch (Exception e) { ++ log.error("Encountered caught exception when polling chunk ticks, blocking and returning null.", e); ++ return null; + } ++ // DivineMC end - catch exceptions when polling chunk ticks + + return scheduledTick; + } diff --git a/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch b/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch new file mode 100644 index 0000000..57d83c9 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 15:59:29 +0300 +Subject: [PATCH] Some optimizations + + +diff --git a/net/minecraft/server/level/ChunkTrackingView.java b/net/minecraft/server/level/ChunkTrackingView.java +index bee90335677f7d8b01589ce5cfd81a40fd422886..a5e488d14fd2016ee188b114d0e681562b5b09cc 100644 +--- a/net/minecraft/server/level/ChunkTrackingView.java ++++ b/net/minecraft/server/level/ChunkTrackingView.java +@@ -73,12 +73,12 @@ public interface ChunkTrackingView { + } + + static boolean isWithinDistance(int centerX, int centerZ, int viewDistance, int x, int z, boolean includeOuterChunksAdjacentToViewBorder) { +- int i = includeOuterChunksAdjacentToViewBorder ? 2 : 1; +- long l = Math.max(0, Math.abs(x - centerX) - i); +- long l1 = Math.max(0, Math.abs(z - centerZ) - i); +- long l2 = l * l + l1 * l1; +- int i1 = viewDistance * viewDistance; +- return l2 < i1; ++ // DivineMC start - Some optimizations ++ int actualViewDistance = viewDistance + (includeOuterChunksAdjacentToViewBorder ? 1 : 0); ++ int xDistance = Math.abs(centerX - x); ++ int zDistance = Math.abs(centerZ - z); ++ return xDistance <= actualViewDistance && zDistance <= actualViewDistance; ++ // DivineMC end - Some optimizations + } + + public record Positioned(ChunkPos center, int viewDistance) implements ChunkTrackingView { +diff --git a/net/minecraft/util/ClassInstanceMultiMap.java b/net/minecraft/util/ClassInstanceMultiMap.java +index 2a708ae0d5bb209650b525e3c56051f8b5655074..762cba15597623f95a242bdd44742d9b892ad042 100644 +--- a/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/net/minecraft/util/ClassInstanceMultiMap.java +@@ -14,9 +14,9 @@ import java.util.Map.Entry; + import net.minecraft.Util; + + public class ClassInstanceMultiMap extends AbstractCollection { +- private final Map, List> byClass = Maps.newHashMap(); ++ private final Map, List> byClass = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // DivineMC - Some optimizations + private final Class baseClass; +- private final List allInstances = Lists.newArrayList(); ++ private final List allInstances = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // DivineMC - Some optimizations + + public ClassInstanceMultiMap(Class baseClass) { + this.baseClass = baseClass; +@@ -56,13 +56,27 @@ public class ClassInstanceMultiMap extends AbstractCollection { + } + + public Collection find(Class type) { ++ // DivineMC start - Some optimizations ++ List cached = this.byClass.get(type); ++ if (cached != null) return (Collection) cached; ++ + if (!this.baseClass.isAssignableFrom(type)) { + throw new IllegalArgumentException("Don't know how to search for " + type); + } else { +- List list = this.byClass +- .computeIfAbsent(type, clazz -> this.allInstances.stream().filter(clazz::isInstance).collect(Util.toMutableList())); +- return (Collection)Collections.unmodifiableCollection(list); ++ List list = this.byClass.computeIfAbsent(type, ++ typeClass -> { ++ it.unimi.dsi.fastutil.objects.ObjectArrayList ts = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(this.allInstances.size()); ++ for (Object _allElement : ((it.unimi.dsi.fastutil.objects.ObjectArrayList) this.allInstances).elements()) { ++ if (typeClass.isInstance(_allElement)) { ++ ts.add((T) _allElement); ++ } ++ } ++ return ts; ++ } ++ ); ++ return (Collection) list; + } ++ // DivineMC end - Some optimizations + } + + @Override +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 70ee86993d381445855ac7e7290da384d6675987..532d71cc1eaee799c193eb43085beb8c5892eac7 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -841,7 +841,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API ++ Entity nearestPlayer = this.divinemc$findNearbyPlayer(this.level(), this, -1.0); // Paper - Affects Spawning API // DivineMC - faster player lookup + if (nearestPlayer != null) { + // Paper start - Configurable despawn distances + final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); +@@ -870,6 +870,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + } + ++ // DivineMC start - faster player lookup ++ private Player divinemc$findNearbyPlayer(Level instance, Entity entity, double maxDistance) { ++ final Player closestPlayer = instance.getNearestPlayer(entity, this.getType().getCategory().getDespawnDistance()); ++ if (closestPlayer != null) { ++ return closestPlayer; ++ } else { ++ final List players = this.level().players(); ++ if (players.isEmpty()) return null; ++ return players.get(0); ++ } ++ } ++ // DivineMC end - faster player lookup ++ + @Override + protected final void serverAiStep() { + this.noActionTime++; +diff --git a/net/minecraft/world/level/LocalMobCapCalculator.java b/net/minecraft/world/level/LocalMobCapCalculator.java +index 9641219c190261dea0db5f95f040a705ba0a3ff9..7ba64e71cfed16f07a9e1283145653745adb6388 100644 +--- a/net/minecraft/world/level/LocalMobCapCalculator.java ++++ b/net/minecraft/world/level/LocalMobCapCalculator.java +@@ -42,14 +42,14 @@ public class LocalMobCapCalculator { + } + + static class MobCounts { +- private final Object2IntMap counts = new Object2IntOpenHashMap<>(MobCategory.values().length); ++ private final int[] spawnGroupDensities = new int[MobCategory.values().length]; // DivineMC - Some optimizations + + public void add(MobCategory category) { +- this.counts.computeInt(category, (key, value) -> value == null ? 1 : value + 1); ++ this.spawnGroupDensities[category.ordinal()] ++; // DivineMC - Some optimizations + } + + public boolean canSpawn(MobCategory category) { +- return this.counts.getOrDefault(category, 0) < category.getMaxInstancesPerChunk(); ++ return this.spawnGroupDensities[category.ordinal()] < category.getMaxInstancesPerChunk(); // DivineMC - Some optimizations + } + } + } +diff --git a/net/minecraft/world/level/storage/DimensionDataStorage.java b/net/minecraft/world/level/storage/DimensionDataStorage.java +index d9a3b5a2e6495b7e22c114506c2bd1e406f58f8f..a6e03345afd6d8a38e06a43c59103209618baa14 100644 +--- a/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -34,7 +34,7 @@ import org.slf4j.Logger; + + public class DimensionDataStorage implements AutoCloseable { + private static final Logger LOGGER = LogUtils.getLogger(); +- public final Map> cache = new HashMap<>(); ++ public final Map> cache = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Concurrent HashMap + private final DataFixer fixerUpper; + private final HolderLookup.Provider registries; + private final Path dataFolder; diff --git a/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch b/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch new file mode 100644 index 0000000..ddf9734 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:05:57 +0300 +Subject: [PATCH] Optimize entity stupid brain + + +diff --git a/net/minecraft/world/entity/AgeableMob.java b/net/minecraft/world/entity/AgeableMob.java +index 179f4e4b9b1eb57f78bbb2f9fa34b11ea79b7a88..143a4ca51a57934bf545e031b10525dedbe9c3bd 100644 +--- a/net/minecraft/world/entity/AgeableMob.java ++++ b/net/minecraft/world/entity/AgeableMob.java +@@ -121,6 +121,16 @@ public abstract class AgeableMob extends PathfinderMob { + public void onSyncedDataUpdated(EntityDataAccessor key) { + if (DATA_BABY_ID.equals(key)) { + this.refreshDimensions(); ++ // DivineMC start - Optimize entity stupid brain ++ if (isBaby()) { ++ org.bxteam.divinemc.util.entity.SensorHelper.enableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT, true); ++ } else { ++ org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT); ++ if (this.getBrain().hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT)) { ++ this.getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT, java.util.Optional.empty()); ++ } ++ } ++ // DivineMC end - Optimize entity stupid brain + } + + super.onSyncedDataUpdated(key); +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index 8f7efe6b2c191f615dfc8394baec44dc0761ff51..406eb049cb22d0736d8b003a2f547cc25c6f68b6 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -45,16 +45,73 @@ public class Brain { + static final Logger LOGGER = LogUtils.getLogger(); + private final Supplier>> codec; + private static final int SCHEDULE_UPDATE_DELAY = 20; +- private final Map, Optional>> memories = Maps.newHashMap(); +- public final Map>, Sensor> sensors = Maps.newLinkedHashMap(); +- private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); ++ private Map, Optional>> memories = Maps.newConcurrentMap(); // DivineMC - concurrent map ++ public Map>, Sensor> sensors = Maps.newLinkedHashMap(); // DivineMC - linked hash map ++ private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); // DivineMC - tree map + private Schedule schedule = Schedule.EMPTY; +- private final Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); ++ private Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); // DivineMC - hash map + private final Map>> activityMemoriesToEraseWhenStopped = Maps.newHashMap(); + private Set coreActivities = Sets.newHashSet(); + private final Set activeActivities = Sets.newHashSet(); + private Activity defaultActivity = Activity.IDLE; + private long lastScheduleUpdate = -9999L; ++ // DivineMC start - Optimize entity stupid brain ++ private java.util.ArrayList> possibleTasks; ++ private org.bxteam.divinemc.util.collections.MaskedList> runningTasks; ++ ++ private void onTasksChanged() { ++ this.runningTasks = null; ++ this.onPossibleActivitiesChanged(); ++ } ++ ++ private void onPossibleActivitiesChanged() { ++ this.possibleTasks = null; ++ } ++ ++ private void initPossibleTasks() { ++ this.possibleTasks = new java.util.ArrayList<>(); ++ for (Map>> map : this.availableBehaviorsByPriority.values()) { ++ for (Map.Entry>> entry : map.entrySet()) { ++ Activity activity = entry.getKey(); ++ if (!this.activeActivities.contains(activity)) { ++ continue; ++ } ++ Set> set = entry.getValue(); ++ for (BehaviorControl task : set) { ++ //noinspection UseBulkOperation ++ this.possibleTasks.add(task); ++ } ++ } ++ } ++ } ++ ++ private java.util.ArrayList> getPossibleTasks() { ++ if (this.possibleTasks == null) { ++ this.initPossibleTasks(); ++ } ++ return this.possibleTasks; ++ } ++ ++ private org.bxteam.divinemc.util.collections.MaskedList> getCurrentlyRunningTasks() { ++ if (this.runningTasks == null) { ++ this.initCurrentlyRunningTasks(); ++ } ++ return this.runningTasks; ++ } ++ ++ private void initCurrentlyRunningTasks() { ++ org.bxteam.divinemc.util.collections.MaskedList> list = new org.bxteam.divinemc.util.collections.MaskedList<>(new ObjectArrayList<>(), false); ++ ++ for (Map>> map : this.availableBehaviorsByPriority.values()) { ++ for (Set> set : map.values()) { ++ for (BehaviorControl task : set) { ++ list.addOrSet(task, task.getStatus() == Behavior.Status.RUNNING); ++ } ++ } ++ } ++ this.runningTasks = list; ++ } ++ // DivineMC end - Optimize entity stupid brain + + public static Brain.Provider provider( + Collection> memoryTypes, Collection>> sensorTypes +@@ -146,6 +203,12 @@ public class Brain { + for (Brain.MemoryValue memoryValue : memoryValues) { + memoryValue.setMemoryInternal(this); + } ++ // DivineMC start - Optimize entity stupid brain ++ this.onTasksChanged(); ++ this.memories = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(this.memories); ++ this.sensors = new it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap<>(this.sensors); ++ this.activityRequirements = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.activityRequirements); ++ // DivineMC end - Optimize entity stupid brain + } + + public DataResult serializeStart(DynamicOps ops) { +@@ -165,6 +228,7 @@ public class Brain { + } + + public void eraseMemory(MemoryModuleType type) { ++ if (!this.memories.containsKey(type)) return; // DivineMC - skip if memory does not contain key + this.setMemory(type, Optional.empty()); + } + +@@ -180,16 +244,33 @@ public class Brain { + this.setMemoryInternal(memoryType, memory.map(ExpirableValue::of)); + } + ++ // DivineMC start - Optimize entity stupid brain + void setMemoryInternal(MemoryModuleType memoryType, Optional> memory) { ++ if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { ++ this.eraseMemory(memoryType); ++ return; ++ } ++ + if (this.memories.containsKey(memoryType)) { +- if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { +- this.eraseMemory(memoryType); +- } else { +- this.memories.put(memoryType, memory); +- } ++ this.increaseMemoryModificationCount(this.memories, memoryType, memory); + } + } + ++ private long memoryModCount = 1; ++ ++ public long getMemoryModCount() { ++ return memoryModCount; ++ } ++ ++ private Object increaseMemoryModificationCount(Map map, T key, A newValue) { ++ Object oldValue = map.put(key, newValue); ++ if (oldValue == null || ((Optional) oldValue).isPresent() != ((Optional) newValue).isPresent()) { ++ this.memoryModCount++; ++ } ++ return oldValue; ++ } ++ // DivineMC end - Optimize entity stupid brain ++ + public Optional getMemory(MemoryModuleType type) { + Optional> optional = this.memories.get(type); + if (optional == null) { +@@ -251,19 +332,7 @@ public class Brain { + @Deprecated + @VisibleForDebug + public List> getRunningBehaviors() { +- List> list = new ObjectArrayList<>(); +- +- for (Map>> map : this.availableBehaviorsByPriority.values()) { +- for (Set> set : map.values()) { +- for (BehaviorControl behaviorControl : set) { +- if (behaviorControl.getStatus() == Behavior.Status.RUNNING) { +- list.add(behaviorControl); +- } +- } +- } +- } +- +- return list; ++ return this.getCurrentlyRunningTasks(); // DivineMC - Optimize entity stupid brain + } + + public void useDefaultActivity() { +@@ -294,6 +363,7 @@ public class Brain { + this.activeActivities.clear(); + this.activeActivities.addAll(this.coreActivities); + this.activeActivities.add(activity); ++ this.onPossibleActivitiesChanged(); // DivineMC - Optimize entity stupid brain + } + } + +@@ -374,11 +444,13 @@ public class Brain { + .computeIfAbsent(activity, activity1 -> Sets.newLinkedHashSet()) + .add((BehaviorControl)pair.getSecond()); + } ++ this.onTasksChanged(); // DivineMC - Optimize entity stupid brain + } + + @VisibleForTesting + public void removeAllBehaviors() { + this.availableBehaviorsByPriority.clear(); ++ this.onTasksChanged(); // DivineMC - Optimize entity stupid brain + } + + public boolean isActive(Activity activity) { +@@ -395,6 +467,7 @@ public class Brain { + } + } + ++ brain.memoryModCount = this.memoryModCount + 1; // DivineMC - Optimize entity stupid brain + return brain; + } + +@@ -429,31 +502,38 @@ public class Brain { + + for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { + behaviorControl.doStop(level, owner, gameTime); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.runningTasks != null) { ++ this.runningTasks.setVisible(behaviorControl, false); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + } + ++ // DivineMC start - Optimize entity stupid brain + private void startEachNonRunningBehavior(ServerLevel level, E entity) { +- long gameTime = level.getGameTime(); +- +- for (Map>> map : this.availableBehaviorsByPriority.values()) { +- for (Entry>> entry : map.entrySet()) { +- Activity activity = entry.getKey(); +- if (this.activeActivities.contains(activity)) { +- for (BehaviorControl behaviorControl : entry.getValue()) { +- if (behaviorControl.getStatus() == Behavior.Status.STOPPED) { +- behaviorControl.tryStart(level, entity, gameTime); +- } +- } ++ long startTime = level.getGameTime(); ++ for (BehaviorControl task : this.getPossibleTasks()) { ++ if (task.getStatus() == Behavior.Status.STOPPED) { ++ task.tryStart(level, entity, startTime); ++ if (this.runningTasks != null && task.getStatus() == Behavior.Status.RUNNING) { ++ this.runningTasks.setVisible(task, true); + } + } + } + } ++ // DivineMC end - Optimize entity stupid brain + + private void tickEachRunningBehavior(ServerLevel level, E entity) { + long gameTime = level.getGameTime(); + + for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { + behaviorControl.tickOrStop(level, entity, gameTime); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.runningTasks != null && behaviorControl.getStatus() != Behavior.Status.RUNNING) { ++ this.runningTasks.setVisible(behaviorControl, false); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + } + +diff --git a/net/minecraft/world/entity/ai/behavior/Behavior.java b/net/minecraft/world/entity/ai/behavior/Behavior.java +index 5b0cadd2544fb2a627822e645ff32fec2e9cfda9..253b9ad671cf0932bb17d468f8b91a15a86ff77a 100644 +--- a/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -14,6 +14,10 @@ public abstract class Behavior implements BehaviorContro + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // DivineMC start - Optimize entity stupid brain ++ private long cachedMemoryModCount = -1; ++ private boolean cachedHasRequiredMemoryState; ++ // DivineMC end - Optimize entity stupid brain + private final String configKey; // Paper - configurable behavior tick rate and timings + + public Behavior(Map, MemoryStatus> entryCondition) { +@@ -27,7 +31,7 @@ public abstract class Behavior implements BehaviorContro + public Behavior(Map, MemoryStatus> entryCondition, int minDuration, int maxDuration) { + this.minDuration = minDuration; + this.maxDuration = maxDuration; +- this.entryCondition = entryCondition; ++ this.entryCondition = new it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap<>(entryCondition); // DivineMC - Optimize entity stupid brain - Use fastutil + // Paper start - configurable behavior tick rate and timings + String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); + int lastSeparator = key.lastIndexOf('.'); +@@ -103,17 +107,26 @@ public abstract class Behavior implements BehaviorContro + return this.getClass().getSimpleName(); + } + +- protected boolean hasRequiredMemories(E owner) { +- for (Entry, MemoryStatus> entry : this.entryCondition.entrySet()) { +- MemoryModuleType memoryModuleType = entry.getKey(); +- MemoryStatus memoryStatus = entry.getValue(); +- if (!owner.getBrain().checkMemory(memoryModuleType, memoryStatus)) { +- return false; ++ // DivineMC start - Optimize entity stupid brain ++ public boolean hasRequiredMemories(E entity) { ++ net.minecraft.world.entity.ai.Brain brain = entity.getBrain(); ++ long modCount = brain.getMemoryModCount(); ++ if (this.cachedMemoryModCount == modCount) { ++ return this.cachedHasRequiredMemoryState; ++ } ++ this.cachedMemoryModCount = modCount; ++ ++ it.unimi.dsi.fastutil.objects.ObjectIterator, net.minecraft.world.entity.ai.memory.MemoryStatus>> fastIterator = ((it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap, net.minecraft.world.entity.ai.memory.MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator(); ++ while (fastIterator.hasNext()) { ++ it.unimi.dsi.fastutil.objects.Reference2ObjectMap.Entry, MemoryStatus> entry = fastIterator.next(); ++ if (!brain.checkMemory(entry.getKey(), entry.getValue())) { ++ return this.cachedHasRequiredMemoryState = false; + } + } + +- return true; ++ return this.cachedHasRequiredMemoryState = true; + } ++ // DivineMC end - Optimize entity stupid brain + + public static enum Status { + STOPPED, +diff --git a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +index ec90ea4e66c6c38d7ad41805a16c63e006e44be4..0204fe68c97d152a7c3201620b6709a8bebefdf6 100644 +--- a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java ++++ b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +@@ -120,6 +120,12 @@ public class LongJumpToRandomPos extends Behavior { + int x = blockPos.getX(); + int y = blockPos.getY(); + int z = blockPos.getZ(); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.maxLongJumpWidth < 128 && this.maxLongJumpHeight < 128) { ++ this.jumpCandidates = org.bxteam.divinemc.util.collections.LongJumpChoiceList.forCenter(blockPos, (byte) this.maxLongJumpWidth, (byte) this.maxLongJumpHeight); ++ return; ++ } ++ // DivineMC end - Optimize entity stupid brain + this.jumpCandidates = BlockPos.betweenClosedStream( + x - this.maxLongJumpWidth, + y - this.maxLongJumpHeight, +@@ -175,11 +181,27 @@ public class LongJumpToRandomPos extends Behavior { + } + } + ++ // DivineMC start - Optimize entity stupid brain + protected Optional getJumpCandidate(ServerLevel level) { +- Optional randomItem = WeightedRandom.getRandomItem(level.random, this.jumpCandidates); +- randomItem.ifPresent(this.jumpCandidates::remove); +- return randomItem; ++ Optional optional = getRandomFast(level.random, this.jumpCandidates); ++ skipRemoveIfAlreadyRemoved(optional, this.jumpCandidates::remove); ++ return optional; ++ } ++ ++ private Optional getRandomFast(net.minecraft.util.RandomSource random, List pool) { ++ if (pool instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList longJumpChoiceList) { ++ return Optional.ofNullable(longJumpChoiceList.removeRandomWeightedByDistanceSq(random)); ++ } else { ++ return WeightedRandom.getRandomItem(random, pool); ++ } ++ } ++ ++ private void skipRemoveIfAlreadyRemoved(Optional result, java.util.function.Consumer removeAction) { ++ if (!(this.jumpCandidates instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList)) { ++ result.ifPresent(removeAction); ++ } + } ++ // DivineMC end - Optimize entity stupid brain + + private boolean isAcceptableLandingPosition(ServerLevel level, E entity, BlockPos pos) { + BlockPos blockPos = entity.blockPosition(); +diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java +index 6f106f10466440f8e65e04511f67d48f082d703f..15728d4fbe7a12c7a3b94a9ef88e7141b1225fa3 100644 +--- a/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/net/minecraft/world/entity/animal/goat/Goat.java +@@ -98,6 +98,13 @@ public class Goat extends Animal { + this.getNavigation().setCanFloat(true); + this.setPathfindingMalus(PathType.POWDER_SNOW, -1.0F); + this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F); ++ // DivineMC start - Optimize entity stupid brain ++ if (!this.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)) { ++ org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, SensorType.NEAREST_ITEMS); ++ } else if (net.minecraft.SharedConstants.IS_RUNNING_IN_IDE) { ++ throw new IllegalStateException("Goat Entity has a nearest visible wanted item memory module! This patch(Optimize-Brain, Goat.java changes) should probably be removed permanently!"); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + + public ItemStack createHorn() { diff --git a/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch b/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch new file mode 100644 index 0000000..63ef6ba --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch @@ -0,0 +1,211 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:47:09 +0300 +Subject: [PATCH] Clump experience orbs + + +diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java +index a43e5190c0f9ae14ccecccd5b58dc0e17f18b0a1..06ffba13f211851e8f6d630a72b41474673e8df8 100644 +--- a/net/minecraft/world/entity/ExperienceOrb.java ++++ b/net/minecraft/world/entity/ExperienceOrb.java +@@ -49,6 +49,10 @@ public class ExperienceOrb extends Entity { + @javax.annotation.Nullable + public java.util.UUID triggerEntityId; + public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // DivineMC start - Clump experience orbs ++ public java.util.Map clumps$clumpedMap; ++ public Optional clumps$currentEntry; ++ // DivineMC end - Clump experience orbs + + private void loadPaperNBT(CompoundTag tag) { + if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) { +@@ -239,6 +243,28 @@ public class ExperienceOrb extends Entity { + } + + private static boolean tryMergeToExisting(ServerLevel level, Vec3 pos, int amount) { ++ // DivineMC start - Clump experience orbs ++ if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ AABB aABB = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); ++ int id = level.getRandom().nextInt(40); ++ List list = level.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), aABB, (experienceOrbx) -> canMerge(experienceOrbx, id, amount)); ++ if(!list.isEmpty()) { ++ ExperienceOrb experienceOrb = list.getFirst(); ++ java.util.Map clumpedMap = (experienceOrb).clumps$getClumpedMap(); ++ (experienceOrb).clumps$setClumpedMap(java.util.stream.Stream.of(clumpedMap, java.util.Collections.singletonMap(amount, 1)) ++ .flatMap(map -> map.entrySet().stream()) ++ .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); ++ (experienceOrb).count = (clumpedMap.values() ++ .stream() ++ .reduce(Integer::sum) ++ .orElse(1)); ++ (experienceOrb).age = (0); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // DivineMC end - Clump experience orbs + // Paper - TODO some other event for this kind of merge + AABB aabb = AABB.ofSize(pos, 1.0, 1.0, 1.0); + int randomInt = level.getRandom().nextInt(40); +@@ -254,11 +280,11 @@ public class ExperienceOrb extends Entity { + } + + private boolean canMerge(ExperienceOrb orb) { +- return orb != this && canMerge(orb, this.getId(), this.value); ++ return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() && !this.is(orb) : orb != this && ExperienceOrb.canMerge(orb, this.getId(), this.value); // DivineMC - Clump experience orbs + } + + private static boolean canMerge(ExperienceOrb orb, int amount, int other) { +- return !orb.isRemoved() && (orb.getId() - amount) % 40 == 0 && orb.value == other; ++ return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() : !orb.isRemoved() && (orb.getId() - amount) % 40 == 0 && orb.value == other; // DivineMC - Clump experience orbs + } + + private void merge(ExperienceOrb orb) { +@@ -267,6 +293,18 @@ public class ExperienceOrb extends Entity { + return; + } + // Paper end - call orb merge event ++ // DivineMC start - Clump experience orbs ++ if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ java.util.Map otherMap = (orb).clumps$getClumpedMap(); ++ this.count = clumps$getClumpedMap().values().stream().reduce(Integer::sum).orElse(1); ++ this.age = Math.min(this.age, (orb).age); ++ clumps$setClumpedMap(java.util.stream.Stream.of(clumps$getClumpedMap(), otherMap) ++ .flatMap(map -> map.entrySet().stream()) ++ .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); ++ orb.discard(EntityRemoveEvent.Cause.MERGE); // DivineMC - add Bukkit remove cause ++ return; ++ } ++ // DivineMC end - Clump experience orbs + this.count = this.count + orb.count; + this.age = Math.min(this.age, orb.age); + orb.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause +@@ -308,6 +346,13 @@ public class ExperienceOrb extends Entity { + compound.putInt("Value", this.value); // Paper - save as Integer + compound.putInt("Count", this.count); + this.savePaperNBT(compound); // Paper ++ // DivineMC start - Clump experience orbs ++ if (clumps$clumpedMap != null) { ++ CompoundTag map = new CompoundTag(); ++ clumps$getClumpedMap().forEach((value, count) -> map.putInt(String.valueOf(value), count)); ++ compound.put("clumpedMap", map); ++ } ++ // DivineMC end - Clump experience orbs + } + + @Override +@@ -317,10 +362,51 @@ public class ExperienceOrb extends Entity { + this.value = compound.getInt("Value"); // Paper - load as Integer + this.count = Math.max(compound.getInt("Count"), 1); + this.loadPaperNBT(compound); // Paper ++ // DivineMC start - Clump experience orbs ++ java.util.Map map = new java.util.HashMap<>(); ++ if (compound.contains("clumpedMap")) { ++ CompoundTag clumpedMap = compound.getCompound("clumpedMap"); ++ for (String s : clumpedMap.getAllKeys()) { ++ map.put(Integer.parseInt(s), clumpedMap.getInt(s)); ++ } ++ } else { ++ map.put(value, count); ++ } ++ ++ clumps$setClumpedMap(map); ++ // DivineMC end - Clump experience orbs + } + + @Override + public void playerTouch(Player entity) { ++ // DivineMC start - Clump experience orbs ++ if(entity instanceof ServerPlayer && org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ entity.takeXpDelay = 0; ++ entity.take(this, 1); ++ ++ if(this.value != 0 || clumps$resolve()) { ++ java.util.concurrent.atomic.AtomicInteger toGive = new java.util.concurrent.atomic.AtomicInteger(); ++ clumps$getClumpedMap().forEach((value, amount) -> { ++ int actualValue = value; ++ ++ for(int i = 0; i < amount; i++) { ++ int leftOver = actualValue; ++ if(leftOver == actualValue) { ++ leftOver = this.repairPlayerItems((ServerPlayer) entity, actualValue); ++ } ++ if(leftOver > 0) { ++ toGive.addAndGet(leftOver); ++ } ++ } ++ }); ++ if(toGive.get() > 0) { ++ entity.giveExperiencePoints(toGive.get()); ++ } ++ } ++ this.discard(); ++ return; ++ } ++ // DivineMC end - Clump experience orbs + if (entity instanceof ServerPlayer serverPlayer) { + if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent + entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay +@@ -338,10 +424,57 @@ public class ExperienceOrb extends Entity { + } + } + +- private int repairPlayerItems(ServerPlayer player, int value) { +- Optional randomItemWith = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith( // Purpur - Add option to mend the most damaged equipment first +- EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged +- ); ++ // DivineMC start - Clump experience orbs ++ public Optional clumps$captureCurrentEntry(Optional entry) { ++ clumps$currentEntry = entry; ++ return entry; ++ } ++ ++ public java.util.Map clumps$getClumpedMap() { ++ if (clumps$clumpedMap == null) { ++ clumps$clumpedMap = new java.util.HashMap<>(); ++ clumps$clumpedMap.put(this.value, 1); ++ } ++ ++ return clumps$clumpedMap; ++ } ++ ++ public void clumps$setClumpedMap(java.util.Map map) { ++ clumps$clumpedMap = map; ++ clumps$resolve(); ++ } ++ ++ public boolean clumps$resolve() { ++ value = clumps$getClumpedMap().entrySet() ++ .stream() ++ .map(entry -> entry.getKey() * entry.getValue()) ++ .reduce(Integer::sum) ++ .orElse(1); ++ ++ return value > 0; ++ } ++ ++ private int repairPlayerItems(ServerPlayer player, int amount) { ++ Optional randomItemWith = clumps$captureCurrentEntry(level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged)); // Purpur - Add option to mend the most damaged equipment first ++ ++ if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ return clumps$currentEntry ++ .map(foundItem -> { ++ ItemStack itemstack = foundItem.itemStack(); ++ int xpToRepair = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, (int) (amount * 1)); ++ int toRepair = Math.min(xpToRepair, itemstack.getDamageValue()); ++ itemstack.setDamageValue(itemstack.getDamageValue() - toRepair); ++ if(toRepair > 0) { ++ int used = amount - toRepair * amount / xpToRepair; ++ if(used > 0) { ++ return this.repairPlayerItems(player, used); ++ } ++ } ++ return 0; ++ }) ++ .orElse(amount); ++ } ++ // DivineMC end - Clump experience orbs + if (randomItemWith.isPresent()) { + ItemStack itemStack = randomItemWith.get().itemStack(); + int i = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemStack, value); diff --git a/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch b/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch new file mode 100644 index 0000000..07902e7 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch @@ -0,0 +1,192 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:57:01 +0300 +Subject: [PATCH] Optimize explosions + + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 278761647a6095581f8d8ff4f94ccc28b6e9c8a7..6d1a73c319e19dbc17122abb508aff462c4a56f4 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -377,6 +377,11 @@ public class ServerExplosion implements Explosion { + } + + private List calculateExplodedPositions() { ++ // DivineMC start - Optimize explosions ++ if (org.bxteam.divinemc.DivineConfig.enableFasterTntOptimization && !level.isClientSide && !(getIndirectSourceEntity() instanceof net.minecraft.world.entity.monster.breeze.Breeze)) { ++ return doExplosionA(this); ++ } ++ // DivineMC end - Optimize explosions + // Paper start - collision optimisations + final ObjectArrayList ret = new ObjectArrayList<>(); + +@@ -475,6 +480,157 @@ public class ServerExplosion implements Explosion { + // Paper end - collision optimisations + } + ++ // DivineMC start - Optimize explosions ++ private static final it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap> densityCache = new it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap<>(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap stateCache = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap fluidCache = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); ++ private static final BlockPos.MutableBlockPos posMutable = new BlockPos.MutableBlockPos(0, 0, 0); ++ private static final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet affectedBlockPositionsSet = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); ++ private static boolean firstRay; ++ private static boolean rayCalcDone; ++ ++ public static @org.jetbrains.annotations.NotNull List doExplosionA(ServerExplosion e) { ++ List toBlow; ++ ++ if (!org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage && e.damageSource != null) { ++ rayCalcDone = false; ++ firstRay = true; ++ getAffectedPositionsOnPlaneY(e, 0, 0, 15, 0, 15); // bottom ++ getAffectedPositionsOnPlaneY(e, 15, 0, 15, 0, 15); // top ++ getAffectedPositionsOnPlaneX(e, 0, 1, 14, 0, 15); // west ++ getAffectedPositionsOnPlaneX(e, 15, 1, 14, 0, 15); // east ++ getAffectedPositionsOnPlaneZ(e, 0, 1, 14, 1, 14); // north ++ getAffectedPositionsOnPlaneZ(e, 15, 1, 14, 1, 14); // south ++ stateCache.clear(); ++ fluidCache.clear(); ++ ++ toBlow = new ArrayList<>(affectedBlockPositionsSet); ++ affectedBlockPositionsSet.clear(); ++ } else { ++ toBlow = java.util.Collections.emptyList(); ++ } ++ densityCache.clear(); ++ ++ return toBlow; ++ } ++ ++ private static void getAffectedPositionsOnPlaneX(Explosion e, int x, int yStart, int yEnd, int zStart, int zEnd) { ++ if (!rayCalcDone) { ++ final double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ for (int z = zStart; z <= zEnd; ++z) { ++ double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int y = yStart; y <= yEnd; ++y) { ++ double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static void getAffectedPositionsOnPlaneY(Explosion e, int y, int xStart, int xEnd, int zStart, int zEnd) { ++ if (!rayCalcDone) { ++ final double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ for (int z = zStart; z <= zEnd; ++z) { ++ double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int x = xStart; x <= xEnd; ++x) { ++ double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static void getAffectedPositionsOnPlaneZ(Explosion e, int z, int xStart, int xEnd, int yStart, int yEnd) { ++ if (!rayCalcDone) { ++ final double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int x = xStart; x <= xEnd; ++x) { ++ double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ for (int y = yStart; y <= yEnd; ++y) { ++ double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static boolean checkAffectedPosition(ServerExplosion e, double xRel, double yRel, double zRel) { ++ double len = Math.sqrt(xRel * xRel + yRel * yRel + zRel * zRel); ++ double xInc = (xRel / len) * 0.3; ++ double yInc = (yRel / len) * 0.3; ++ double zInc = (zRel / len) * 0.3; ++ float rand = e.level().random.nextFloat(); ++ float sizeRand = (org.bxteam.divinemc.DivineConfig.tntRandomRange >= 0 ? (float) org.bxteam.divinemc.DivineConfig.tntRandomRange : rand); ++ float size = e.radius() * (0.7F + sizeRand * 0.6F); ++ Vec3 vec3 = e.center(); ++ double posX = vec3.x; ++ double posY = vec3.y; ++ double posZ = vec3.z; ++ ++ for (float f1 = 0.3F; size > 0.0F; size -= 0.22500001F) { ++ posMutable.set(posX, posY, posZ); ++ ++ // Don't query already cached positions again from the world ++ BlockState state = stateCache.get(posMutable); ++ FluidState fluid = fluidCache.get(posMutable); ++ BlockPos posImmutable = null; ++ ++ if (state == null) { ++ posImmutable = posMutable.immutable(); ++ state = e.level().getBlockState(posImmutable); ++ stateCache.put(posImmutable, state); ++ fluid = e.level().getFluidState(posImmutable); ++ fluidCache.put(posImmutable, fluid); ++ } ++ ++ if (!state.isAir()) { ++ float resistance = Math.max(state.getBlock().getExplosionResistance(), fluid.getExplosionResistance()); ++ ++ if (e.source != null) { ++ resistance = e.source.getBlockExplosionResistance(e, e.level(), posMutable, state, fluid, resistance); ++ } ++ ++ size -= (resistance + 0.3F) * 0.3F; ++ } ++ ++ if (size > 0.0F) { ++ if ((e.source == null || e.source.shouldBlockExplode(e, e.level(), posMutable, state, size))) ++ affectedBlockPositionsSet.add(posImmutable != null ? posImmutable : posMutable.immutable()); ++ } else if (firstRay) { ++ rayCalcDone = true; ++ return true; ++ } ++ ++ firstRay = false; ++ ++ posX += xInc; ++ posY += yInc; ++ posZ += zInc; ++ } ++ ++ return false; ++ } ++ ++ private Optional noBlockCalcsWithNoBLockDamage(final ExplosionDamageCalculator instance, final Explosion explosion, final BlockGetter blockGetter, final BlockPos blockPos, final BlockState blockState, final FluidState fluidState) { ++ if (org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage) return Optional.of(Blocks.BEDROCK.getExplosionResistance()); ++ return instance.getBlockExplosionResistance(explosion, blockGetter, blockPos, blockState, fluidState); ++ } ++ // DivineMC end - Optimize explosions ++ + private void hurtEntities() { + float f = this.radius * 2.0F; + int floor = Mth.floor(this.center.x - f - 1.0); +@@ -567,6 +723,11 @@ public class ServerExplosion implements Explosion { + } + + private void interactWithBlocks(List blocks) { ++ // DivineMC start - Optimize explosions ++ if (org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage) { ++ blocks.clear(); ++ } ++ // DivineMC end - Optimize explosions + List list = new ArrayList<>(); + Util.shuffle(blocks, this.level.random); + diff --git a/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch b/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch new file mode 100644 index 0000000..dd618e6 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:03:13 +0300 +Subject: [PATCH] Stop teleporting players when they move too quickly + + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index aabce23007006fe2dca1e4ac7c0657d2a1cae30e..f52e0ba1ef613895c2f21f39da852593d2f7883f 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1444,18 +1444,22 @@ public class ServerGamePacketListenerImpl + if (this.shouldCheckPlayerMovement(isFallFlying)) { + float f2 = isFallFlying ? 300.0F : 100.0F; + if (d7 - d6 > Math.max(f2, Mth.square(org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed))) { +- // CraftBukkit end +- // Paper start - Add fail move event +- io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, +- toX, toY, toZ, toYaw, toPitch, true); +- if (!event.isAllowed()) { +- if (event.getLogWarning()) { +- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); ++ // DivineMC start - Stop teleporting players when they move too quickly ++ if (!org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && !(org.bxteam.divinemc.DivineConfig.ignoreMovedTooQuicklyWhenLagging && player.serverLevel().getServer().lagging)) { ++ // CraftBukkit end ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ if (event.getLogWarning()) { ++ LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); ++ } ++ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); ++ return; + } +- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +- return; ++ // Paper end - Add fail move event + } +- // Paper end - Add fail move event ++ // DivineMC end - Stop teleporting players when they move too quickly + } + } + } +@@ -1516,6 +1520,7 @@ public class ServerGamePacketListenerImpl + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean movedWrongly = false; // Paper - Add fail move event; rename + if (!this.player.isChangingDimension() ++ && !org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement // DivineMC - Stop teleporting players when they move too quickly + && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold // Spigot + && !this.player.isSleeping() + && !this.player.gameMode.isCreative() diff --git a/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch b/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch new file mode 100644 index 0000000..6478d23 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch @@ -0,0 +1,331 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:38:26 +0300 +Subject: [PATCH] Lag compensation + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index ee45df82c3328d5cf91cb3e56786aec2d5263641..138de3fed3cc7a4dd0633dfdaf9c883f5f6fbd54 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -307,6 +307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1577,6 +1578,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= this.portal.getPortalTransitionTime(level, entity); ++ return canChangeDimensions && lagCompensation(this.portalTime++, level) >= this.portal.getPortalTransitionTime(level, entity); // DivineMC - Lag compensation + } + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, ServerLevel world) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.portalAcceleration) return original; ++ if (world.isClientSide()) return original; ++ ++ portalTime = portalTime + world.tpsCalculator.applicableMissedTicks(); ++ return portalTime; ++ } ++ // DivineMC end - Lag compensation ++ + @Nullable + public TeleportTransition getPortalDestination(ServerLevel level, Entity entity) { + return this.portal.getPortalDestination(level, entity, this.entryPosition); +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 771b169fa360411bb313ae04c7dd55836875c611..e64ed6a23efbe89b8d3dd1e5a2a69ba4b7743369 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -153,8 +153,25 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + // Paper end - EAR 2 + ++ // DivineMC start - Lag compensation ++ private void lagCompensation() { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.pickupAcceleration) return; ++ if ((this).level().isClientSide()) return; ++ ++ if (pickupDelay == 0) return; ++ ++ if (pickupDelay - ((ServerLevel) this.level()).tpsCalculator.applicableMissedTicks() <= 0) { ++ pickupDelay = 0; ++ return; ++ } ++ ++ pickupDelay = pickupDelay - ((ServerLevel) this.level()).tpsCalculator.applicableMissedTicks(); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public void tick() { ++ lagCompensation(); // DivineMC - Lag compensation + if (this.getItem().isEmpty()) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { +diff --git a/net/minecraft/world/item/Item.java b/net/minecraft/world/item/Item.java +index 6821d39a24ef610ea8b04f6dbeca2cdc0b8e7787..a4d76e4aafb98b1bbc0e5a80d65cf0f9a3a53fd5 100644 +--- a/net/minecraft/world/item/Item.java ++++ b/net/minecraft/world/item/Item.java +@@ -258,9 +258,21 @@ public class Item implements FeatureElement, ItemLike { + return consumable != null ? consumable.animation() : ItemUseAnimation.NONE; + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, net.minecraft.server.level.ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.eatingAcceleration || original == 0) return original; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(original, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + public int getUseDuration(ItemStack stack, LivingEntity entity) { + Consumable consumable = stack.get(DataComponents.CONSUMABLE); +- return consumable != null ? consumable.consumeTicks() : 0; ++ int original = consumable != null ? consumable.consumeTicks() : 0; ++ if (entity.level() instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ return lagCompensation(original, serverLevel); ++ } ++ ++ return original; + } + + public boolean releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeLeft) { +diff --git a/net/minecraft/world/level/GameRules.java b/net/minecraft/world/level/GameRules.java +index 02d64a5ea756b2c91a71b7a0fc0f21219983616a..d515ba4e775e1199e1cbf4f79978d318eaa6b336 100644 +--- a/net/minecraft/world/level/GameRules.java ++++ b/net/minecraft/world/level/GameRules.java +@@ -320,8 +320,31 @@ public class GameRules { + } + + public int getInt(GameRules.Key key) { +- return this.getRule(key).get(); ++ return lagCompensation(this.getRule(key).get(), key); // DivineMC - Lag compensation ++ } ++ ++ // DivineMC start - Lag compensation ++ private final java.util.concurrent.atomic.AtomicReference level = new java.util.concurrent.atomic.AtomicReference<>(); ++ ++ private int lagCompensation(int original, GameRules.Key rule) { ++ ServerLevel level = getOrCacheLevel(); ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.randomTickSpeedAcceleration) return original; ++ if (!(rule == GameRules.RULE_RANDOMTICKING)) return original; ++ return (int) (original * org.bxteam.divinemc.util.tps.TPSCalculator.MAX_TPS / (float) level.tpsCalculator.getMostAccurateTPS()); ++ } ++ ++ private ServerLevel getOrCacheLevel() { ++ if (level.get() == null) { ++ for (final ServerLevel level : MinecraftServer.getServer().getAllLevels()) { ++ if (level.getGameRules() == this) { ++ this.level.set(level); ++ break; ++ } ++ } ++ } ++ return level.get(); + } ++ // DivineMC end - Lag compensation + + public static class BooleanValue extends GameRules.Value { + private boolean value; +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index b631e35e965b1914cdeeddab8bd6bdbfd2465079..bb7dab597850fba8f0dff4461fc518e0a33b00c7 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -346,13 +346,21 @@ public abstract class BlockBehaviour implements FeatureElement { + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + } + ++ // DivineMC start - Lag compensation ++ private float lagCompensation(float original, Player player) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.blockBreakingAcceleration) return original; ++ if (player.level().isClientSide) return original; ++ return original * org.bxteam.divinemc.util.tps.TPSCalculator.MAX_TPS / (float) ((ServerLevel) player.level()).tpsCalculator.getMostAccurateTPS(); ++ } ++ // DivineMC end - Lag compensation ++ + protected float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { + float destroySpeed = state.getDestroySpeed(level, pos); + if (destroySpeed == -1.0F) { +- return 0.0F; ++ return lagCompensation(0.0F, player); // DivineMC - Lag compensation + } else { + int i = player.hasCorrectToolForDrops(state) ? 30 : 100; +- return player.getDestroySpeed(state) / destroySpeed / i; ++ return lagCompensation(player.getDestroySpeed(state) / destroySpeed / i, player); // DivineMC - Lag compensation + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 6167f72d1e374a7093f9880ab50e27eda603a680..88dc5f6e6668d802dc1a21bd894f8ddb1e568033 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -913,6 +913,19 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + this.ticker = ticker; + } + ++ // DivineMC start - Lag compensation ++ private void lagCompensation(Runnable original) { ++ original.run(); ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled) return; ++ if (!org.bxteam.divinemc.DivineConfig.blockEntityAcceleration) return; ++ if (LevelChunk.this.level.isClientSide()) return; ++ ++ for (int i = 0; i < ((ServerLevel) this.blockEntity.getLevel()).tpsCalculator.applicableMissedTicks(); i++) { ++ original.run(); ++ } ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public void tick() { + if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) { +@@ -923,7 +936,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + profilerFiller.push(this::getType); + BlockState blockState = LevelChunk.this.getBlockState(blockPos); + if (this.blockEntity.getType().isValid(blockState)) { +- this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity); ++ // DivineMC start - Lag compensation ++ lagCompensation(() -> { ++ this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity); ++ }); ++ // DivineMC end - Lag compensation + this.loggedInvalidBlockState = false; + // Paper start - Remove the Block Entity if it's invalid + } else { +diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java +index 35b5a33c79c883f28c99c992695b188524593b55..6845a1c3b6038700751312d8beb0f9a2844003a5 100644 +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -175,9 +175,22 @@ public abstract class LavaFluid extends FlowingFluid { + return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER); + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.fluidAcceleration) return original; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(original, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public int getTickDelay(LevelReader level) { +- return level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable ++ // DivineMC start - Lag compensation ++ int original = level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable ++ if (level instanceof ServerLevel serverLevel) { ++ return lagCompensation(original, serverLevel); ++ } ++ return original; ++ // DivineMC end - Lag compensation + } + + @Override +diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java +index 2e4fed7c27910b6c886f710f33b0841c2a175837..89f22ebcbaf21df3afb6a00f60d8e00777875aac 100644 +--- a/net/minecraft/world/level/material/WaterFluid.java ++++ b/net/minecraft/world/level/material/WaterFluid.java +@@ -113,8 +113,16 @@ public abstract class WaterFluid extends FlowingFluid { + return 1; + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.fluidAcceleration) return 5; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(5, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public int getTickDelay(LevelReader level) { ++ if (level instanceof ServerLevel serverLevel) return lagCompensation(serverLevel); // DivineMC - Lag compensation + return 5; + } + diff --git a/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch b/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch new file mode 100644 index 0000000..0d714dc --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:55:59 +0300 +Subject: [PATCH] MSPT Tracking for each world + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 138de3fed3cc7a4dd0633dfdaf9c883f5f6fbd54..506f0469d1a9ee58de0e7a34a61a8092e451dcfd 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1778,7 +1778,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 1 Feb 2025 19:14:09 +0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 506f0469d1a9ee58de0e7a34a61a8092e451dcfd..b7815831ff1a2fa9aa52e96f1a50a5aa6823ff8a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -308,6 +308,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1715,17 +1716,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) { +- if (entity.isRemoved()) { +- continue; +- } +- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); +- if (bukkit != null) { +- bukkit.taskScheduler.executeTick(); +- } ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ for (final net.minecraft.world.entity.Entity entity : entitiesWithScheduledTasks) { ++ if (entity.isRemoved()) { ++ continue; + } +- }); ++ ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + // Paper end - Folia scheduler API + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + profilerFiller.push("commandFunctions"); diff --git a/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch b/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch new file mode 100644 index 0000000..26c3bd3 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:17:31 +0300 +Subject: [PATCH] Carpet-Fixes: RecipeManager Optimize + +Original project: https://github.com/fxmorin/carpet-fixes +Optimized the RecipeManager getFirstMatch call to be up to 3x faster +This is a fully vanilla optimization. Improves: [Blast]Furnace/Campfire/Smoker/Stonecutter/Crafting/Sheep Color Choosing +This was mostly made for the auto crafting table, since the performance boost is much more visible while using that mod + +diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java +index aefaac550b58be479cc282f52dea91d4b1e530f6..2877a3229e03285e9ba5ec2bb68e17c9da202816 100644 +--- a/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/net/minecraft/world/item/crafting/RecipeManager.java +@@ -167,7 +167,7 @@ public class RecipeManager extends SimplePreparableReloadListener imp + + public > Optional> getRecipeFor(RecipeType recipeType, I input, Level level) { + // CraftBukkit start +- List> list = this.recipes.getRecipesFor(recipeType, input, level).toList(); ++ List> list = this.recipes.getRecipesForList(recipeType, input, level); // DivineMC - Carpet-Fixes - Remove streams to be faster + return (list.isEmpty()) ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority + // CraftBukkit end + } +diff --git a/net/minecraft/world/item/crafting/RecipeMap.java b/net/minecraft/world/item/crafting/RecipeMap.java +index 098753ddd215b6ef5915fac71d8c4f0b19cf4142..1778e58dca9430756d59d07bf017ebe4cc1f4ed4 100644 +--- a/net/minecraft/world/item/crafting/RecipeMap.java ++++ b/net/minecraft/world/item/crafting/RecipeMap.java +@@ -75,4 +75,24 @@ public class RecipeMap { + public > Stream> getRecipesFor(RecipeType type, I input, Level level) { + return input.isEmpty() ? Stream.empty() : this.byType(type).stream().filter(recipeHolder -> recipeHolder.value().matches(input, level)); + } ++ ++ // DivineMC start - Carpet-Fixes - Remove streams to be faster ++ public > java.util.List> getRecipesForList(RecipeType type, I input, Level world) { ++ java.util.List> list; ++ ++ if (input.isEmpty()) { ++ return java.util.List.of(); ++ } else { ++ list = new java.util.ArrayList<>(); ++ } ++ ++ for (RecipeHolder recipeholder : this.byType(type)) { ++ if (recipeholder.value().matches(input, world)) { ++ list.add(recipeholder); ++ } ++ } ++ ++ return list; ++ } ++ // DivineMC end - Carpet-Fixes - Remove streams to be faster + } diff --git a/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch b/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch new file mode 100644 index 0000000..22cdda0 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:24:47 +0300 +Subject: [PATCH] Snowball and Egg knockback + + +diff --git a/net/minecraft/world/entity/projectile/Snowball.java b/net/minecraft/world/entity/projectile/Snowball.java +index 1d399532c67c213c95c06837b0c7855384f1a25c..cad1f8cb68ef9615587e651a3120f68a3c32add0 100644 +--- a/net/minecraft/world/entity/projectile/Snowball.java ++++ b/net/minecraft/world/entity/projectile/Snowball.java +@@ -54,6 +54,12 @@ public class Snowball extends ThrowableItemProjectile { + Entity entity = result.getEntity(); + int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - Add configurable snowball damage + entity.hurt(this.damageSources().thrown(this, this.getOwner()), i); ++ // DivineMC start - Make snowball can knockback player ++ if (this.level().divineConfig.snowballCanKnockback && entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); ++ serverPlayer.knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); ++ } ++ // DivineMC end - Make snowball can knockback player + } + + // Purpur start - options to extinguish fire blocks with snowballs - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire +diff --git a/net/minecraft/world/entity/projectile/ThrownEgg.java b/net/minecraft/world/entity/projectile/ThrownEgg.java +index 76481c0e77fc3a2e4be8eeb9de8d1e6de5507c64..8f0aa83cc81f36d70a39600a82d0212db70e02ec 100644 +--- a/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -52,7 +52,14 @@ public class ThrownEgg extends ThrowableItemProjectile { + @Override + protected void onHitEntity(EntityHitResult result) { + super.onHitEntity(result); ++ net.minecraft.world.entity.Entity entity = result.getEntity(); // DivineMC - make egg can knockback player + result.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); ++ // DivineMC start - Make egg can knockback player ++ if (this.level().divineConfig.eggCanKnockback && entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); ++ serverPlayer.knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); ++ } ++ // DivineMC end - Make egg can knockback player + } + + @Override diff --git a/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch b/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch new file mode 100644 index 0000000..cb5aeef --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:28:34 +0300 +Subject: [PATCH] Block Log4Shell exploit + + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f52e0ba1ef613895c2f21f39da852593d2f7883f..9e4ea539afcd07294bdc5018f479e496ee011451 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2409,6 +2409,7 @@ public class ServerGamePacketListenerImpl + } + + private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit ++ if (ServerGamePacketListenerImpl.isLog4ShellExploit(message)) return; // DivineMC - Block Log4Shell exploit + if (isChatMessageIllegal(message)) { + this.disconnectAsync(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add proper async disconnect + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales +@@ -2437,6 +2438,15 @@ public class ServerGamePacketListenerImpl + } + } + ++ // DivineMC start - Block Log4Shell exploit ++ public static boolean isLog4ShellExploit(String message) { ++ java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(".*\\$\\{[^}]*}.*"); ++ java.util.regex.Matcher matcher = pattern.matcher(message); ++ ++ return matcher.find(); ++ } ++ // DivineMC end - Block Log4Shell exploit ++ + public static boolean isChatMessageIllegal(String message) { + for (int i = 0; i < message.length(); i++) { + if (!StringUtil.isAllowedChatCharacter(message.charAt(i))) { diff --git a/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch b/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch new file mode 100644 index 0000000..6310c67 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:43:42 +0300 +Subject: [PATCH] Re-Fix MC-117075 + + +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 8f37c27bba829733fb8db5f35470092a76c83e98..595ebb40f47dd55127c630813813d21d8a1274cd 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -115,7 +115,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public ++ public final org.bxteam.divinemc.util.BlockEntityTickersList blockEntityTickers = new org.bxteam.divinemc.util.BlockEntityTickersList(); // Paper - public // DivineMC - optimize block entity removals - Fix MC-117075 + protected final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); + private boolean tickingBlockEntities; +@@ -1527,7 +1527,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); + // Spigot end + if (tickingBlockEntity.isRemoved()) { +- toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll ++ this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // DivineMC - optimize block entity removals - Fix MC-117075 + } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system +@@ -1539,6 +1539,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 + ++ this.blockEntityTickers.removeMarkedEntries(); // DivineMC - optimize block entity removals - Fix MC-117075 + this.tickingBlockEntities = false; + profilerFiller.pop(); + this.spigotConfig.currentPrimedTnt = 0; // Spigot diff --git a/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch b/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch new file mode 100644 index 0000000..4aaea74 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:50:02 +0300 +Subject: [PATCH] Skip distanceToSqr call in ServerEntity#sendChanges if the + delta movement hasn't changed + + +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java +index 3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e..a03dced5231ca47abf5919e3eca358c16a25337b 100644 +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -200,23 +200,27 @@ public class ServerEntity { + + if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) { + Vec3 deltaMovement = this.entity.getDeltaMovement(); +- double d = deltaMovement.distanceToSqr(this.lastSentMovement); +- if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) { +- this.lastSentMovement = deltaMovement; +- if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) { +- this.broadcast +- .accept( +- new ClientboundBundlePacket( +- List.of( +- new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement), +- new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower) ++ // DivineMC start - Skip "distanceToSqr" call in "ServerEntity#sendChanges" if the delta movement hasn't changed ++ if (deltaMovement != this.lastSentMovement) { ++ double d = deltaMovement.distanceToSqr(this.lastSentMovement); ++ if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) { ++ this.lastSentMovement = deltaMovement; ++ if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) { ++ this.broadcast ++ .accept( ++ new ClientboundBundlePacket( ++ List.of( ++ new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement), ++ new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower) ++ ) + ) +- ) +- ); +- } else { +- this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); ++ ); ++ } else { ++ this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); ++ } + } + } ++ // DivineMC end - Skip "distanceToSqr" call in "ServerEntity#sendChanges" if the delta movement hasn't changed + } + + if (packet != null) { diff --git a/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch b/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch new file mode 100644 index 0000000..5f4c2bd --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:52:39 +0300 +Subject: [PATCH] Optimize canSee checks + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index e3427e7aecfc4e1fafb38316824aa1ee50c9901a..9bca28f33aa3b3cb8964c06b2f4d2f0a591e81f5 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -1317,7 +1317,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); + // Paper end - Configurable entity tracking range by Y + // CraftBukkit start - respect vanish API +- if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ if (flag && !player.getBukkitEntity().canSeeChunkMapUpdatePlayer(this.entity.getBukkitEntity())) { // Paper - only consider hits // DivineMC - optimize canSee checks + flag = false; + } + // CraftBukkit end diff --git a/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch b/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch new file mode 100644 index 0000000..f889048 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch @@ -0,0 +1,309 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 4 Feb 2025 01:49:17 +0300 +Subject: [PATCH] Implement NoChatReports + + +diff --git a/net/minecraft/network/FriendlyByteBuf.java b/net/minecraft/network/FriendlyByteBuf.java +index e5e5d9bc095ccd9fbf1c8aaa09e5c4ebb1d1c920..af94dd26b553b5e64f305135328aa30b6fae6b0b 100644 +--- a/net/minecraft/network/FriendlyByteBuf.java ++++ b/net/minecraft/network/FriendlyByteBuf.java +@@ -101,7 +101,28 @@ public class FriendlyByteBuf extends ByteBuf { + return this; + } + ++ @SuppressWarnings({"unchecked", "rawtypes"}) // DivineMC - Implement NoChatReports + public T readJsonWithCodec(Codec codec) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ if (codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { ++ JsonElement jsonElement = GsonHelper.fromJson(GSON, this.readUtf(), JsonElement.class); ++ DataResult dataResult = codec.parse(JsonOps.INSTANCE, jsonElement); ++ Object result; ++ try { ++ result = dataResult.getOrThrow(string -> new DecoderException("Failed to decode json: " + string)); ++ } catch (Throwable e) { ++ throw new RuntimeException("Unable to decode json!", e); ++ } ++ ++ if (jsonElement.getAsJsonObject().has("preventsChatReports")) { ++ ((net.minecraft.network.protocol.status.ServerStatus) result).setPreventsChatReports(jsonElement.getAsJsonObject().get("preventsChatReports").getAsBoolean()); ++ } ++ ++ return (T) (result); ++ } ++ } ++ // DivineMC end - Implement NoChatReports + JsonElement jsonElement = GsonHelper.fromJson(GSON, this.readUtf(), JsonElement.class); + DataResult dataResult = codec.parse(JsonOps.INSTANCE, jsonElement); + return dataResult.getOrThrow(exception -> new DecoderException("Failed to decode json: " + exception)); +@@ -113,6 +134,19 @@ public class FriendlyByteBuf extends ByteBuf { + } + public void writeJsonWithCodec(Codec codec, T value, int maxLength) { + // Paper end - Adventure; add max length parameter ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsAddQueryData && codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { ++ DataResult dataResult = codec.encodeStart(JsonOps.INSTANCE, value); ++ JsonElement element = dataResult.getOrThrow(string -> new EncoderException("Failed to encode: " + string + " " + value)); ++ ++ element.getAsJsonObject().addProperty("preventsChatReports", true); ++ ++ this.writeUtf(GSON.toJson(element)); ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + DataResult dataResult = codec.encodeStart(JsonOps.INSTANCE, value); + this.writeUtf(GSON.toJson(dataResult.getOrThrow(exception -> new EncoderException("Failed to encode: " + exception + " " + value))), maxLength); // Paper - Adventure; add max length parameter + } +diff --git a/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java b/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java +index 07943553b562b95076bdce232d6f0796f469400f..61ecf4c6ae37b13ed42dff8d4165d32f3a5cc0c9 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java +@@ -36,4 +36,15 @@ public record ServerboundChatCommandSignedPacket( + public void handle(ServerGamePacketListener handler) { + handler.handleSignedChatCommand(this); + } ++ ++ // DivineMC start - Implement NoChatReports ++ @Override ++ public ArgumentSignatures argumentSignatures() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ return ArgumentSignatures.EMPTY; ++ } ++ ++ return argumentSignatures; ++ } ++ // DivineMC end - Implement NoChatReports + } +diff --git a/net/minecraft/network/protocol/game/ServerboundChatPacket.java b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +index b5afc05924ae899e020c303c8b86398e1d4ab8a0..3af0436ac2dff04cfaa1b3dda11a5417f2c0890c 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +@@ -36,4 +36,16 @@ public record ServerboundChatPacket(String message, Instant timeStamp, long salt + public void handle(ServerGamePacketListener handler) { + handler.handleChat(this); + } ++ ++ // DivineMC start - Implement NoChatReports ++ @Override ++ @Nullable ++ public MessageSignature signature() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ return null; ++ } ++ ++ return signature; ++ } ++ // DivineMC end - Implement NoChatReports + } +diff --git a/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java b/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java +index 1df628ac0b414511aaed6e09d78f884c4170f730..d94858facc06d57139e953796ee09dad17648dbb 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java +@@ -26,6 +26,19 @@ public record ServerboundChatSessionUpdatePacket(RemoteChatSession.Data chatSess + + @Override + public void handle(ServerGamePacketListener handler) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ var impl = (net.minecraft.server.network.ServerGamePacketListenerImpl) handler; ++ ++ if (!impl.getPlayer().getServer().isSingleplayerOwner(impl.getPlayer().getGameProfile())) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDemandOnClient) { ++ impl.disconnect(net.minecraft.network.chat.Component.literal(org.bxteam.divinemc.DivineConfig.noChatReportsDisconnectDemandOnClientMessage)); ++ } ++ } ++ ++ return; ++ } ++ // DivineMC end - Implement NoChatReports + handler.handleChatSessionUpdate(this); + } + } +diff --git a/net/minecraft/network/protocol/status/ServerStatus.java b/net/minecraft/network/protocol/status/ServerStatus.java +index 30bd254542d631676494f349ff3f44f52d54ab2f..6c728ae3b58bc1b8449d34c6c74091612b79f39e 100644 +--- a/net/minecraft/network/protocol/status/ServerStatus.java ++++ b/net/minecraft/network/protocol/status/ServerStatus.java +@@ -15,13 +15,7 @@ import net.minecraft.network.chat.CommonComponents; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.ComponentSerialization; + +-public record ServerStatus( +- Component description, +- Optional players, +- Optional version, +- Optional favicon, +- boolean enforcesSecureChat +-) { ++public final class ServerStatus { + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), +@@ -33,6 +27,63 @@ public record ServerStatus( + .apply(instance, ServerStatus::new) + ); + ++ // DivineMC start - Implement NoChatReports - convert to class ++ private final Component description; ++ private final Optional players; ++ private final Optional version; ++ private final Optional favicon; ++ private final boolean enforcesSecureChat; ++ private boolean preventsChatReports; ++ ++ public ServerStatus( ++ Component description, ++ Optional players, ++ Optional version, ++ Optional favicon, ++ boolean enforcesSecureChat ++ ) { ++ this.description = description; ++ this.players = players; ++ this.version = version; ++ this.favicon = favicon; ++ this.enforcesSecureChat = enforcesSecureChat; ++ } ++ ++ public Component description() { ++ return description; ++ } ++ ++ public Optional players() { ++ return players; ++ } ++ ++ public Optional version() { ++ return version; ++ } ++ ++ public Optional favicon() { ++ return favicon; ++ } ++ ++ public boolean enforcesSecureChat() { ++ return enforcesSecureChat; ++ } ++ ++ public boolean preventsChatReports() { ++ var self = (ServerStatus) (Object) this; ++ ++ if (self.version().isPresent() && self.version().get().protocol() < 759 ++ && self.version().get().protocol() > 0) ++ return true; ++ ++ return this.preventsChatReports; ++ } ++ ++ public void setPreventsChatReports(boolean prevents) { ++ this.preventsChatReports = prevents; ++ } ++ // DivineMC end - Implement NoChatReports ++ + public record Favicon(byte[] iconBytes) { + private static final String PREFIX = "data:image/png;base64,"; + public static final Codec CODEC = Codec.STRING.comapFlatMap(string -> { +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 697f690305db56ae5a05483aae37994d4e8f9f83..dc6417943ec339326684323c3ec3d132d55be354 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -668,6 +668,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public boolean enforceSecureProfile() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) return false; // DivineMC - Implement NoChatReports + DedicatedServerProperties properties = this.getProperties(); + // Paper start - Add setting for proxy online mode status + return properties.enforceSecureProfile +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 398c1733824b689520170de0be94006731afa5cd..072e9b8810a6ccc292f1eb5ffe02355ab4719c5e 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -312,10 +312,64 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + public void send(Packet packet) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ Object self = this; ++ boolean cancel = false; ++ ++ if (self instanceof ServerGamePacketListenerImpl listener) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDebugLog && packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ MinecraftServer.LOGGER.info("Sending message: {}", chat.unsignedContent() != null ? chat.unsignedContent() ++ : chat.body().content()); ++ } ++ ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsConvertToGameMessage) { ++ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ packet = new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(chat.chatType().decorate( ++ chat.unsignedContent() != null ? chat.unsignedContent() ++ : Component.literal(chat.body().content()) ++ ), false); ++ ++ cancel = true; ++ listener.send(packet); ++ } ++ } ++ } ++ ++ if (cancel) { ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + this.send(packet, null); + } + + public void send(Packet packet, @Nullable PacketSendListener listener) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ Object self = this; ++ boolean cancel = false; ++ ++ if (self instanceof ServerGamePacketListenerImpl listenerImpl) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDebugLog && packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ MinecraftServer.LOGGER.info("Sending message: {}", chat.unsignedContent() != null ? chat.unsignedContent() ++ : chat.body().content()); ++ } ++ ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsConvertToGameMessage) { ++ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat && listener != null) { ++ cancel = true; ++ listenerImpl.send(chat); ++ } ++ } ++ ++ } ++ ++ if (cancel) { ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + // CraftBukkit start + if (packet == null || this.processedDisconnect) { // Spigot + return; +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 6b23cf5122fe65b2ad253ed8536658441297e953..e8c0a87cec53788573748b5e900370bb968e3fab 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -274,7 +274,7 @@ public abstract class PlayerList { + !_boolean, + _boolean2, + player.createCommonSpawnInfo(serverLevel), +- this.server.enforceSecureProfile() ++ org.bxteam.divinemc.DivineConfig.noChatReportsEnabled || this.server.enforceSecureProfile() // DivineMC - Implement NoChatReports + ) + ); + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit +@@ -1328,6 +1328,7 @@ public abstract class PlayerList { + } + + public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) return true; // DivineMC - Implement NoChatReports + return message.hasSignature() && !message.hasExpiredServer(Instant.now()); + } + diff --git a/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch b/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch new file mode 100644 index 0000000..5a6a5d6 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch @@ -0,0 +1,275 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 4 Feb 2025 19:52:24 +0300 +Subject: [PATCH] Optimize Structure Generation + + +diff --git a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +index 63143ceec98f7a84ec4064d05e8f88c11200172f..02f67af9c312f3d9213af1e410986165dcf618a4 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +@@ -4,6 +4,8 @@ import com.google.common.collect.Lists; + import com.mojang.logging.LogUtils; + import java.util.List; + import java.util.Optional; ++import java.util.ArrayList; ++import java.util.LinkedHashSet; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Holder; +@@ -292,6 +294,108 @@ public class JigsawPlacement { + this.random = random; + } + ++ // DivineMC start - Optimize Structure Generation ++ private boolean structureLayoutOptimizer$optimizeJigsawConnecting(StructureTemplate.JigsawBlockInfo jigsaw1, StructureTemplate.JigsawBlockInfo jigsaw2) { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return JigsawBlock.canAttach(jigsaw1, jigsaw2); ++ } ++ return org.bxteam.divinemc.util.structure.GeneralUtils.canJigsawsAttach(jigsaw1, jigsaw2); ++ } ++ ++ private void structureLayoutOptimizer$replaceVoxelShape3(MutableObject instance, BoundingBox pieceBounds) { ++ org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape = new org.bxteam.divinemc.util.structure.TrojanVoxelShape(new org.bxteam.divinemc.util.structure.BoxOctree(AABB.of(pieceBounds))); ++ instance.setValue(trojanVoxelShape); ++ } ++ ++ private void structureLayoutOptimizer$replaceVoxelShape4(MutableObject instance, BoundingBox pieceBounds) { ++ if (instance.getValue() instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ trojanVoxelShape.boxOctree.addBox(AABB.of(pieceBounds)); ++ } ++ } ++ ++ private List structureLayoutOptimizer$removeDuplicateTemplatePoolElementLists(StructureTemplatePool instance, RandomSource random) { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer || !org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList) { ++ return instance.getShuffledTemplates(random); ++ } ++ ++ // Linked hashset keeps order of elements. ++ LinkedHashSet uniquePieces = new LinkedHashSet<>((instance).rawTemplates.size()); ++ ++ // Don't use addAll. Want to keep it simple in case of inefficiency in collection's addAll. ++ // Set will ignore duplicates after first appearance of an element. ++ for (StructurePoolElement piece : instance.getShuffledTemplates(random)) { ++ //noinspection UseBulkOperation ++ uniquePieces.add(piece); ++ } ++ ++ // Move the elements from set to the list in the same order. ++ int uniquePiecesFound = uniquePieces.size(); ++ List deduplicatedListOfPieces = new ArrayList<>(uniquePiecesFound); ++ for (int i = 0; i < uniquePiecesFound; i++) { ++ deduplicatedListOfPieces.add(uniquePieces.removeFirst()); ++ } ++ ++ return deduplicatedListOfPieces; ++ } ++ ++ private ArrayList structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists1() { ++ // Swap with trojan list, so we can record what pieces we visited ++ return org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList ? Lists.newArrayList() : new org.bxteam.divinemc.util.structure.TrojanArrayList<>(); ++ } ++ ++ private List structureLayoutOptimizer$skipBlockedJigsaws( ++ List original, ++ boolean useExpansionHack, ++ MutableObject voxelShapeMutableObject, ++ StructurePoolElement structurePoolElement, ++ StructureTemplate.StructureBlockInfo parentJigsawBlockInfo, ++ BlockPos parentTargetPosition) ++ { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return original; ++ } ++ if (voxelShapeMutableObject.getValue() instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ // If rigid and target position is already an invalid spot, do not run rest of logic. ++ StructureTemplatePool.Projection candidatePlacementBehavior = structurePoolElement.getProjection(); ++ boolean isCandidateRigid = candidatePlacementBehavior == StructureTemplatePool.Projection.RIGID; ++ if (isCandidateRigid && (!trojanVoxelShape.boxOctree.boundaryContains(parentTargetPosition) || trojanVoxelShape.boxOctree.withinAnyBox(parentTargetPosition))) { ++ return new ArrayList<>(); ++ } ++ } ++ return original; ++ } ++ ++ private List structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists2(List original, ++ List list, ++ StructurePoolElement structurepoolelement1) ++ { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return original; ++ } ++ if (!org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList && list instanceof org.bxteam.divinemc.util.structure.TrojanArrayList trojanArrayList) { ++ // Do not run this piece's logic since we already checked its 4 rotations in the past. ++ if (trojanArrayList.elementsAlreadyParsed.contains(structurepoolelement1)) { ++ ++ // Prime the random with the random calls we would've skipped. ++ // Maintains vanilla compat. ++ for (Rotation rotation1 : original) { ++ structurepoolelement1.getShuffledJigsawBlocks(this.structureTemplateManager, BlockPos.ZERO, rotation1, this.random); ++ } ++ ++ // Short circuit the Rotation loop ++ return new ArrayList<>(); ++ } ++ // Record piece as it will go through the 4 rotation checks for spawning. ++ else { ++ trojanArrayList.elementsAlreadyParsed.add(structurepoolelement1); ++ } ++ } ++ ++ // Allow the vanilla code to run normally. ++ return original; ++ } ++ // DivineMC end - Optimize Structure Generation ++ + void tryPlacingChildren( + PoolElementStructurePiece piece, + MutableObject free, +@@ -349,9 +453,9 @@ public class JigsawPlacement { + mutableObject1 = free; + } + +- List list = Lists.newArrayList(); ++ List list = structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists1(); // DivineMC - Optimize Structure Generation + if (depth != this.maxDepth) { +- list.addAll(holder.value().getShuffledTemplates(this.random)); ++ list.addAll(structureLayoutOptimizer$removeDuplicateTemplatePoolElementLists(holder.value(), this.random)); // DivineMC - Optimize Structure Generation + } + + list.addAll(fallback.value().getShuffledTemplates(this.random)); +@@ -362,10 +466,14 @@ public class JigsawPlacement { + break; + } + +- for (Rotation rotation1 : Rotation.getShuffled(this.random)) { +- List shuffledJigsawBlocks = structurePoolElement.getShuffledJigsawBlocks( ++ // DivineMC start - Optimize Structure Generation ++ for (Rotation rotation1 : structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists2(Rotation.getShuffled(this.random), list, structurePoolElement)) { ++ List shuffledJigsawBlocks = structureLayoutOptimizer$skipBlockedJigsaws( ++ structurePoolElement.getShuffledJigsawBlocks( + this.structureTemplateManager, BlockPos.ZERO, rotation1, this.random ++ ), useExpansionHack, mutableObject1, structurePoolElement, structureBlockInfo, blockPos1 + ); ++ // DivineMC end - Optimize Structure Generation + BoundingBox boundingBox1 = structurePoolElement.getBoundingBox(this.structureTemplateManager, BlockPos.ZERO, rotation1); + int i2; + if (useExpansionHack && boundingBox1.getYSpan() <= 16) { +@@ -398,7 +506,7 @@ public class JigsawPlacement { + } + + for (StructureTemplate.JigsawBlockInfo jigsawBlockInfo1 : shuffledJigsawBlocks) { +- if (JigsawBlock.canAttach(jigsawBlockInfo, jigsawBlockInfo1)) { ++ if (structureLayoutOptimizer$optimizeJigsawConnecting(jigsawBlockInfo, jigsawBlockInfo1)) { // DivineMC - Optimize Structure Generation + BlockPos blockPos2 = jigsawBlockInfo1.info().pos(); + BlockPos blockPos3 = blockPos1.subtract(blockPos2); + BoundingBox boundingBox2 = structurePoolElement.getBoundingBox(this.structureTemplateManager, blockPos3, rotation1); +@@ -427,9 +535,26 @@ public class JigsawPlacement { + boundingBox3.encapsulate(new BlockPos(boundingBox3.minX(), boundingBox3.minY() + max, boundingBox3.minZ())); + } + +- if (!Shapes.joinIsNotEmpty( +- mutableObject1.getValue(), Shapes.create(AABB.of(boundingBox3).deflate(0.25)), BooleanOp.ONLY_SECOND +- )) { ++ // DivineMC start - Optimize Structure Generation ++ boolean internal$joinIsNotEmpty; ++ VoxelShape parentBounds = mutableObject1.getValue(); ++ java.util.function.Supplier original = () -> Shapes.joinIsNotEmpty( ++ parentBounds, Shapes.create(AABB.of(boundingBox3).deflate(0.25)), BooleanOp.ONLY_SECOND ++ ); ++ if (org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ if (parentBounds instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ AABB pieceAABB = AABB.of(boundingBox3).deflate(0.25D); ++ ++ // Have to inverse because of an ! outside our wrap ++ internal$joinIsNotEmpty = !trojanVoxelShape.boxOctree.withinBoundsButNotIntersectingChildren(pieceAABB); ++ } else { ++ internal$joinIsNotEmpty = original.get(); ++ } ++ } else { ++ internal$joinIsNotEmpty = original.get(); ++ } ++ if (!internal$joinIsNotEmpty) { ++ // DivineMC end - Optimize Structure Generation + mutableObject1.setValue( + Shapes.joinUnoptimized( + mutableObject1.getValue(), Shapes.create(AABB.of(boundingBox3)), BooleanOp.ONLY_FIRST +diff --git a/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java b/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java +index 4a6da3648c513a6cce16cf71246937d2d0ad014d..6af4c9026e814dee1ed4c7593ad00b5f8af9b979 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java +@@ -119,8 +119,16 @@ public class SinglePoolElement extends StructurePoolElement { + StructureTemplateManager structureTemplateManager, BlockPos pos, Rotation rotation, RandomSource random + ) { + List jigsaws = this.getTemplate(structureTemplateManager).getJigsaws(pos, rotation); +- Util.shuffle(jigsaws, random); +- sortBySelectionPriority(jigsaws); ++ // DivineMC start - Optimize Structure Generation ++ if (org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ structureLayoutOptimizer$fasterJigsawListShuffling1(jigsaws, random); ++ structureLayoutOptimizer$fasterJigsawListShuffling2(jigsaws); ++ } else { ++ Util.shuffle(jigsaws, random); ++ sortBySelectionPriority(jigsaws); ++ } ++ // DivineMC end - Optimize Structure Generation ++ + return jigsaws; + } + +@@ -191,4 +199,12 @@ public class SinglePoolElement extends StructurePoolElement { + public String toString() { + return "Single[" + this.template + "]"; + } ++ ++ // DivineMC start - Optimize Structure Generation ++ private void structureLayoutOptimizer$fasterJigsawListShuffling1(List list, RandomSource randomSource) { ++ org.bxteam.divinemc.util.structure.GeneralUtils.shuffleAndPrioritize(list, randomSource); ++ } ++ ++ private void structureLayoutOptimizer$fasterJigsawListShuffling2(List structureBlockInfos) { } ++ // Quiil end - Optimize Structure Generation + } +diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index ab1dcbe416e2c3c94cfddf04b7ed053425a71806..985d11f0b72858d66ad011d83106730b07e25242 100644 +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -249,6 +249,12 @@ public class StructureTemplate { + return transform(pos, decorator.getMirror(), decorator.getRotation(), decorator.getRotationPivot()); + } + ++ // DivineMC start - Optimize Structure Generation ++ private List structureLayoutOptimizer$shrinkStructureTemplateBlocksList(StructureTemplate.Palette palette, BlockPos offset, StructurePlaceSettings settings) { ++ return org.bxteam.divinemc.util.structure.StructureTemplateOptimizer.getStructureBlockInfosInBounds(palette, offset, settings); ++ } ++ // DivineMC end - Optimize Structure Generation ++ + public boolean placeInWorld(ServerLevelAccessor serverLevel, BlockPos offset, BlockPos pos, StructurePlaceSettings settings, RandomSource random, int flags) { + if (this.palettes.isEmpty()) { + return false; +@@ -266,7 +272,11 @@ public class StructureTemplate { + } + } + // CraftBukkit end +- List list = settings.getRandomPalette(this.palettes, offset).blocks(); ++ // DivineMC start - Optimize Structure Generation ++ List list = org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer ++ ? structureLayoutOptimizer$shrinkStructureTemplateBlocksList(settings.getRandomPalette(this.palettes, offset), offset, settings) ++ : settings.getRandomPalette(this.palettes, offset).blocks(); ++ // DivineMC end - Optimize Structure Generation + if ((!list.isEmpty() || !settings.isIgnoreEntities() && !this.entityInfoList.isEmpty()) + && this.size.getX() >= 1 + && this.size.getY() >= 1 +@@ -890,7 +900,11 @@ public class StructureTemplate { + private List cachedJigsaws; + + Palette(List blocks) { +- this.blocks = blocks; ++ // DivineMC start - Optimize Structure Generation ++ this.blocks = org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer ++ ? new org.bxteam.divinemc.util.structure.PalettedStructureBlockInfoList(blocks) ++ : blocks; ++ // DivineMC end - Optimize Structure Generation + } + + public List jigsaws() { diff --git a/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch b/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch new file mode 100644 index 0000000..7ce0984 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 5 Feb 2025 17:48:56 +0300 +Subject: [PATCH] Verify Minecraft EULA earlier + + +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 680369af59fd2aa36bf1cf4e28b598854383abe3..d415a175ea1e7b5a5bf1149187247dd7b2619c29 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -143,7 +143,6 @@ public class Main { + dedicatedServerSettings.forceSave(); + RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression); + Path path2 = Paths.get("eula.txt"); +- Eula eula = new Eula(path2); + // Paper start - load config files early for access below if needed + org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("bukkit-settings")); + org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings")); +@@ -166,19 +165,6 @@ public class Main { + return; + } + +- // Spigot start +- boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); +- if (eulaAgreed) { +- LOGGER.error("You have used the Spigot command line EULA agreement flag."); +- LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA)."); +- LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately."); +- } +- if (!eula.hasAgreedToEULA() && !eulaAgreed) { +- // Spigot end +- LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); +- return; +- } +- + // Paper start - Detect headless JRE + String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); + if (awtException != null) { diff --git a/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch b/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch new file mode 100644 index 0000000..82848f2 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch @@ -0,0 +1,1205 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 12 Feb 2025 01:05:50 +0300 +Subject: [PATCH] Density Function Compiler + +Implements density function compiler to accelerate world generation from C2ME + +Density function: https://minecraft.wiki/w/Density_function + +This functionality compiles density functions from world generation +datapacks (including vanilla generation) to JVM bytecode to increase +performance by allowing JVM JIT to better optimize the code. +All functions provided by vanilla are implemented. + +Not all server will benefit performance from this feature, as it +can sometimes slow down chunk performance than speed it up + +diff --git a/net/minecraft/util/CubicSpline.java b/net/minecraft/util/CubicSpline.java +index f36f8f2d49d4eba5c80eb243883749d6f831eb8a..b43b7e242ea0a4f87704853c03201144ce355565 100644 +--- a/net/minecraft/util/CubicSpline.java ++++ b/net/minecraft/util/CubicSpline.java +@@ -254,31 +254,47 @@ public interface CubicSpline> extends ToFloatFun + + @Override + public float apply(C object) { +- float f = this.coordinate.apply(object); +- int i = findIntervalStart(this.locations, f); +- int i1 = this.locations.length - 1; +- if (i < 0) { +- return linearExtend(f, this.locations, this.values.get(0).apply(object), this.derivatives, 0); +- } else if (i == i1) { +- return linearExtend(f, this.locations, this.values.get(i1).apply(object), this.derivatives, i1); ++ // DivineMC start - Density Function Compiler ++ float point = this.coordinate.apply(object); ++ int rangeForLocation = findIntervalStart(this.locations, point); ++ int last = this.locations.length - 1; ++ if (rangeForLocation < 0) { ++ return linearExtend(point, this.locations, this.values.get(0).apply(object), this.derivatives, 0); ++ } else if (rangeForLocation == last) { ++ return linearExtend(point, this.locations, this.values.get(last).apply(object), this.derivatives, last); + } else { +- float f1 = this.locations[i]; +- float f2 = this.locations[i + 1]; +- float f3 = (f - f1) / (f2 - f1); +- ToFloatFunction toFloatFunction = (ToFloatFunction)this.values.get(i); +- ToFloatFunction toFloatFunction1 = (ToFloatFunction)this.values.get(i + 1); +- float f4 = this.derivatives[i]; +- float f5 = this.derivatives[i + 1]; +- float f6 = toFloatFunction.apply(object); +- float f7 = toFloatFunction1.apply(object); +- float f8 = f4 * (f2 - f1) - (f7 - f6); +- float f9 = -f5 * (f2 - f1) + (f7 - f6); +- return Mth.lerp(f3, f6, f7) + f3 * (1.0F - f3) * Mth.lerp(f3, f8, f9); ++ float loc0 = this.locations[rangeForLocation]; ++ float loc1 = this.locations[rangeForLocation + 1]; ++ float locDist = loc1 - loc0; ++ float k = (point - loc0) / locDist; ++ float n = this.values.get(rangeForLocation).apply(object); ++ float o = this.values.get(rangeForLocation + 1).apply(object); ++ float onDist = o - n; ++ float p = this.derivatives[rangeForLocation] * locDist - onDist; ++ float q = -this.derivatives[rangeForLocation + 1] * locDist + onDist; ++ return Mth.lerp(k, n, o) + k * (1.0F - k) * Mth.lerp(k, p, q); + } ++ // DivineMC end - Density Function Compiler + } + + private static int findIntervalStart(float[] locations, float start) { +- return Mth.binarySearch(0, locations.length, i -> start < locations[i]) - 1; ++ // DivineMC start - Density Function Compiler ++ int min = 0; ++ int i = locations.length; ++ ++ while (i > 0) { ++ int j = i / 2; ++ int k = min + j; ++ if (start < locations[k]) { ++ i = j; ++ } else { ++ min = k + 1; ++ i -= j + 1; ++ } ++ } ++ ++ return min - 1; ++ // DivineMC end - Density Function Compiler + } + + @VisibleForTesting +@@ -313,5 +329,27 @@ public interface CubicSpline> extends ToFloatFun + this.derivatives + ); + } ++ ++ // DivineMC start - Density Function Compiler ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ Multipoint that = (Multipoint) o; ++ return java.util.Objects.equals(coordinate, that.coordinate()) && java.util.Arrays.equals(locations, that.locations()) && java.util.Objects.equals(values, that.values()) && java.util.Arrays.equals(derivatives, that.derivatives()); ++ } ++ ++ @Override ++ public int hashCode() { ++ int result = 1; ++ ++ result = 31 * result + java.util.Objects.hashCode(coordinate); ++ result = 31 * result + java.util.Arrays.hashCode(locations); ++ result = 31 * result + java.util.Objects.hashCode(values); ++ result = 31 * result + java.util.Arrays.hashCode(derivatives); ++ ++ return result; ++ } ++ // DivineMC end - Density Function Compiler + } + } +diff --git a/net/minecraft/world/level/levelgen/DensityFunctions.java b/net/minecraft/world/level/levelgen/DensityFunctions.java +index 7178013421233d7dab36eb07a768907ce40e8745..f56321eefa5fcdfdb30883beaf97c87ac6fa0183 100644 +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -275,38 +275,66 @@ public final class DensityFunctions { + + @Override + public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- this.argument1.fillArray(array, contextProvider); +- switch (this.type) { +- case ADD: +- double[] doubles = new double[array.length]; +- this.argument2.fillArray(doubles, contextProvider); +- +- for (int i = 0; i < array.length; i++) { +- array[i] += doubles[i]; +- } +- break; +- case MUL: +- for (int i1 = 0; i1 < array.length; i1++) { +- double d = array[i1]; +- array[i1] = d == 0.0 ? 0.0 : d * this.argument2.compute(contextProvider.forIndex(i1)); +- } +- break; +- case MIN: +- double d1 = this.argument2.minValue(); ++ // DivineMC start - Density Function Compiler ++ Runnable run = () -> { ++ this.argument1.fillArray(array, contextProvider); ++ switch (this.type) { ++ case ADD: ++ double[] doubles = new double[array.length]; ++ this.argument2.fillArray(doubles, contextProvider); ++ ++ for (int i = 0; i < array.length; i++) { ++ array[i] += doubles[i]; ++ } ++ break; ++ case MUL: ++ for (int i1 = 0; i1 < array.length; i1++) { ++ double d = array[i1]; ++ array[i1] = d == 0.0 ? 0.0 : d * this.argument2.compute(contextProvider.forIndex(i1)); ++ } ++ break; ++ case MIN: ++ double d1 = this.argument2.minValue(); ++ ++ for (int i2 = 0; i2 < array.length; i2++) { ++ double d2 = array[i2]; ++ array[i2] = d2 < d1 ? d2 : Math.min(d2, this.argument2.compute(contextProvider.forIndex(i2))); ++ } ++ break; ++ case MAX: ++ d1 = this.argument2.maxValue(); ++ ++ for (int i2 = 0; i2 < array.length; i2++) { ++ double d2 = array[i2]; ++ array[i2] = d2 > d1 ? d2 : Math.max(d2, this.argument2.compute(contextProvider.forIndex(i2))); ++ } ++ } ++ }; ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler && this.type == DensityFunctions.TwoArgumentSimpleFunction.Type.ADD) { ++ this.argument1.fillArray(array, contextProvider); ++ double[] ds; + +- for (int i2 = 0; i2 < array.length; i2++) { +- double d2 = array[i2]; +- array[i2] = d2 < d1 ? d2 : Math.min(d2, this.argument2.compute(contextProvider.forIndex(i2))); +- } +- break; +- case MAX: +- d1 = this.argument2.maxValue(); ++ org.bxteam.divinemc.dfc.common.util.ArrayCache arrayCache = contextProvider instanceof org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable arrayCacheCapable ? arrayCacheCapable.c2me$getArrayCache() : null; + +- for (int i2 = 0; i2 < array.length; i2++) { +- double d2 = array[i2]; +- array[i2] = d2 > d1 ? d2 : Math.max(d2, this.argument2.compute(contextProvider.forIndex(i2))); +- } ++ if (arrayCache != null) { ++ ds = arrayCache.getDoubleArray(array.length, false); ++ } else { ++ ds = new double[array.length]; ++ } ++ ++ this.argument2.fillArray(ds, contextProvider); ++ ++ for (int i = 0; i < array.length; i++) { ++ array[i] += ds[i]; ++ } ++ ++ if (arrayCache != null) { ++ arrayCache.recycle(ds); ++ } ++ } else { ++ run.run(); + } ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -704,7 +732,105 @@ public final class DensityFunctions { + } + } + +- protected record Marker(@Override DensityFunctions.Marker.Type type, @Override DensityFunction wrapped) implements DensityFunctions.MarkerOrMarked { ++ // DivineMC start - Density Function Compiler ++ public static final class Marker implements org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike, org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding, MarkerOrMarked { ++ private final Type type; ++ private final DensityFunction wrapped; ++ private Object c2me$optionalEquality; ++ ++ @Override ++ public boolean equals(final Object that) { ++ Function original = (o) -> { ++ if (o == this) return true; ++ if (o == null || o.getClass() != this.getClass()) return false; ++ var a = (Marker) o; ++ return java.util.Objects.equals(this.type, a.type) && ++ java.util.Objects.equals(this.wrapped, a.wrapped); ++ }; ++ if (true) { ++ return original.apply(that); ++ } ++ Object a = this.c2me$getOverriddenEquality(); ++ Object b = that instanceof org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding equalityOverriding ? equalityOverriding.c2me$getOverriddenEquality() : null; ++ if (a == null) { ++ return original.apply(b != null ? b : that); ++ } else { ++ return a.equals(b != null ? b : that); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ java.util.function.Supplier original = () -> java.util.Objects.hash(type, wrapped); ++ Object c2me$optionalEquality1 = this.c2me$optionalEquality; ++ if (c2me$optionalEquality1 != null && false) { ++ return c2me$optionalEquality1.hashCode(); ++ } else { ++ return original.get(); ++ } ++ } ++ ++ public Marker(Type type, DensityFunction wrapped) { ++ this.type = type; ++ this.wrapped = wrapped; ++ } ++ ++ @Override ++ public double c2me$getCached(int x, int y, int z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.wrapped; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ DensityFunctions.Marker wrapping = new DensityFunctions.Marker(this.type(), delegate); ++ ((org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding) (Object) wrapping).c2me$overrideEquality(this); ++ return wrapping; ++ } ++ ++ @Override ++ public void c2me$overrideEquality(Object object) { ++ Object inner = object; ++ while (true) { ++ Object inner1 = inner instanceof org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding e1 ? e1.c2me$getOverriddenEquality() : null; ++ if (inner1 == null) { ++ this.c2me$optionalEquality = inner; ++ break; ++ } ++ inner = inner1; ++ } ++ } ++ ++ @Override ++ public Object c2me$getOverriddenEquality() { ++ return this.c2me$optionalEquality; ++ } ++ ++ @Override ++ public DensityFunction mapAll(Visitor visitor) { ++ return visitor.apply(this.c2me$withDelegate(this.wrapped().mapAll(visitor))); ++ } ++ // DivineMC end - Density Function Compiler ++ + @Override + public double compute(DensityFunction.FunctionContext context) { + return this.wrapped.compute(context); +@@ -725,7 +851,19 @@ public final class DensityFunctions { + return this.wrapped.maxValue(); + } + +- static enum Type implements StringRepresentable { ++ // DivineMC start - Density Function Compiler ++ @Override ++ public Type type() { ++ return type; ++ } ++ ++ @Override ++ public DensityFunction wrapped() { ++ return wrapped; ++ } ++ ++ public static enum Type implements StringRepresentable { // - public ++ // DivineMC end - Density Function Compiler + Interpolated("interpolated"), + FlatCache("flat_cache"), + Cache2D("cache_2d"), +diff --git a/net/minecraft/world/level/levelgen/NoiseChunk.java b/net/minecraft/world/level/levelgen/NoiseChunk.java +index 977b0d595e5637c80e7d4bb20da8896a0b64b844..991025da1005d6c4122897231095dc909a11d8f7 100644 +--- a/net/minecraft/world/level/levelgen/NoiseChunk.java ++++ b/net/minecraft/world/level/levelgen/NoiseChunk.java +@@ -4,9 +4,11 @@ import com.google.common.collect.Lists; + import it.unimi.dsi.fastutil.longs.Long2IntMap; + import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; + import java.util.ArrayList; ++import java.util.Arrays; + import java.util.HashMap; + import java.util.List; + import java.util.Map; ++import java.util.function.Supplier; + import javax.annotation.Nullable; + import net.minecraft.core.QuartPos; + import net.minecraft.core.SectionPos; +@@ -20,7 +22,18 @@ import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.levelgen.blending.Blender; + import net.minecraft.world.level.levelgen.material.MaterialRuleList; + +-public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunction.FunctionContext { ++// DivineMC start - Density Function Compiler ++import org.bxteam.divinemc.dfc.common.ast.EvalType; ++import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; ++import org.bxteam.divinemc.dfc.common.ducks.ICoordinatesFilling; ++import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; ++import org.bxteam.divinemc.dfc.common.gen.DelegatingBlendingAwareVisitor; ++import org.bxteam.divinemc.dfc.common.util.ArrayCache; ++import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; ++import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; ++// DivineMC end - Density Function Compiler ++ ++public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunction.FunctionContext, IArrayCacheCapable, ICoordinatesFilling { + private final NoiseSettings noiseSettings; + final int cellCountXZ; + final int cellCountY; +@@ -56,7 +69,47 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + long interpolationCounter; + long arrayInterpolationCounter; + int arrayIndex; +- private final DensityFunction.ContextProvider sliceFillingContextProvider = new DensityFunction.ContextProvider() { ++ // DivineMC start - Density Function Compiler ++ private final ArrayCache c2me$arrayCache = new ArrayCache(); ++ ++ @Override ++ public ArrayCache c2me$getArrayCache() { ++ return this.c2me$arrayCache != null ? this.c2me$arrayCache : new ArrayCache(); ++ } ++ ++ @Override ++ public void c2me$fillCoordinates(int[] x, int[] y, int[] z) { ++ int index = 0; ++ for (int i = this.cellHeight - 1; i >= 0; i--) { ++ int blockY = this.cellStartBlockY + i; ++ for (int j = 0; j < this.cellWidth; j++) { ++ int blockX = this.cellStartBlockX + j; ++ for (int k = 0; k < this.cellWidth; k++) { ++ int blockZ = this.cellStartBlockZ + k; ++ ++ x[index] = blockX; ++ y[index] = blockY; ++ z[index] = blockZ; ++ ++ index++; ++ } ++ } ++ } ++ } ++ ++ private @org.jetbrains.annotations.NotNull DelegatingBlendingAwareVisitor c2me$getDelegatingBlendingAwareVisitor(DensityFunction.Visitor visitor) { ++ return new DelegatingBlendingAwareVisitor(visitor, this.getBlender() != Blender.empty()); ++ } ++ ++ private DensityFunction.Visitor modifyVisitor1(DensityFunction.Visitor visitor) { ++ return c2me$getDelegatingBlendingAwareVisitor(visitor); ++ } ++ ++ private DensityFunction.Visitor modifyVisitor2(DensityFunction.Visitor visitor) { ++ return c2me$getDelegatingBlendingAwareVisitor(visitor); ++ } ++ ++ public class NoiseChunkSliceFillingContextProvider implements DensityFunction.ContextProvider, IArrayCacheCapable, ICoordinatesFilling { + @Override + public DensityFunction.FunctionContext forIndex(int arrayIndex) { + NoiseChunk.this.cellStartBlockY = (arrayIndex + NoiseChunk.this.cellNoiseMinY) * NoiseChunk.this.cellHeight; +@@ -76,7 +129,23 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + values[i] = function.compute(NoiseChunk.this); + } + } +- }; ++ ++ @Override ++ public ArrayCache c2me$getArrayCache() { ++ return (NoiseChunk.this).c2me$getArrayCache(); ++ } ++ ++ @Override ++ public void c2me$fillCoordinates(int[] x, int[] y, int[] z) { ++ for (int i = 0; i < (NoiseChunk.this).cellCountY + 1; i++) { ++ x[i] = (NoiseChunk.this).cellStartBlockX + (NoiseChunk.this).inCellX; ++ y[i] = (i + (NoiseChunk.this).cellNoiseMinY) * (NoiseChunk.this).cellHeight; ++ z[i] = (NoiseChunk.this).cellStartBlockZ + (NoiseChunk.this).inCellZ; ++ } ++ } ++ } ++ private final DensityFunction.ContextProvider sliceFillingContextProvider = new NoiseChunkSliceFillingContextProvider(); ++ // DivineMC end - Density Function Compiler + + public static NoiseChunk forChunk( + ChunkAccess chunk, +@@ -135,7 +204,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + + NoiseRouter noiseRouter = random.router(); +- NoiseRouter noiseRouter1 = noiseRouter.mapAll(this::wrap); ++ NoiseRouter noiseRouter1 = noiseRouter.mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor1(this::wrap) : this::wrap); // DivineMC - Density Function Compiler + if (!noiseGeneratorSettings.isAquifersEnabled()) { + this.aquifer = Aquifer.createDisabled(fluidPicker); + } else { +@@ -150,7 +219,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + DensityFunction densityFunction = DensityFunctions.cacheAllInCell( + DensityFunctions.add(noiseRouter1.finalDensity(), DensityFunctions.BeardifierMarker.INSTANCE) + ) +- .mapAll(this::wrap); ++ .mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap); // DivineMC - Density Function Compiler + list.add(context -> this.aquifer.computeSubstance(context, densityFunction.compute(context))); + if (noiseGeneratorSettings.oreVeinsEnabled()) { + list.add(OreVeinifier.create(noiseRouter1.veinToggle(), noiseRouter1.veinRidged(), noiseRouter1.veinGap(), random.oreRandom())); +@@ -162,12 +231,14 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + protected Climate.Sampler cachedClimateSampler(NoiseRouter noiseRouter, List points) { + return new Climate.Sampler( +- noiseRouter.temperature().mapAll(this::wrap), +- noiseRouter.vegetation().mapAll(this::wrap), +- noiseRouter.continents().mapAll(this::wrap), +- noiseRouter.erosion().mapAll(this::wrap), +- noiseRouter.depth().mapAll(this::wrap), +- noiseRouter.ridges().mapAll(this::wrap), ++ // DivineMC start - Density Function Compiler ++ noiseRouter.temperature().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.vegetation().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.continents().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.erosion().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.depth().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.ridges().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ // DivineMC end - Density Function Compiler + points + ); + } +@@ -366,6 +437,13 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + + private DensityFunction wrapNew(DensityFunction densityFunction) { ++ // DivineMC start - Density Function Compiler ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler) { ++ if (this.interpolating && densityFunction instanceof DensityFunctions.Marker) { ++ throw new IllegalStateException("Cannot create more wrapping during interpolation loop"); ++ } ++ } ++ // DivineMC end - Density Function Compiler + if (densityFunction instanceof DensityFunctions.Marker marker) { + return (DensityFunction)(switch (marker.type()) { + case Interpolated -> new NoiseChunk.NoiseInterpolator(marker.wrapped()); +@@ -475,10 +553,48 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + BlockState calculate(DensityFunction.FunctionContext context); + } + +- static class Cache2D implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ static class Cache2D implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction function; + private long lastPos2D = ChunkPos.INVALID_CHUNK_POS; + private double lastValue; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ long l = ChunkPos.asLong(x, z); ++ if (this.lastPos2D == l) { ++ return this.lastValue; ++ } else { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ this.lastPos2D = ChunkPos.asLong(x, z); ++ this.lastValue = cached; ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.function; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.function = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + Cache2D(DensityFunction function) { + this.function = function; +@@ -515,9 +631,92 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class CacheAllInCell implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { +- final DensityFunction noiseFiller; ++ class CacheAllInCell implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler ++ DensityFunction noiseFiller; // DivineMC - remove final + final double[] values; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = x - startBlockX; ++ int cellBlockY = y - startBlockY; ++ int cellBlockZ = z - startBlockZ; ++ if (cellBlockX >= 0 && ++ cellBlockY >= 0 && ++ cellBlockZ >= 0 && ++ cellBlockX < horizontalCellBlockCount && ++ cellBlockY < verticalCellBlockCount && ++ cellBlockZ < horizontalCellBlockCount) { ++ return this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) ++ * horizontalCellBlockCount ++ + cellBlockZ]; ++ } ++ } ++ } ++ ++ return CACHE_MISS_NAN_BITS; ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ for (int i = 0; i < res.length; i++) { ++ int cellBlockX = x[i] - startBlockX; ++ int cellBlockY = y[i] - startBlockY; ++ int cellBlockZ = z[i] - startBlockZ; ++ if (cellBlockX >= 0 && ++ cellBlockY >= 0 && ++ cellBlockZ >= 0 && ++ cellBlockX < horizontalCellBlockCount && ++ cellBlockY < verticalCellBlockCount && ++ cellBlockZ < horizontalCellBlockCount) { ++ res[i] = this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) * horizontalCellBlockCount + cellBlockZ]; ++ } else { ++ System.out.println("partial cell cache hit"); ++ return false; // partial hit possible ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + CacheAllInCell(final DensityFunction noiseFilter) { + this.noiseFiller = noiseFilter; +@@ -527,18 +726,51 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.noiseFiller.compute(context); +- } else if (!NoiseChunk.this.interpolating) { +- throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); +- } else { +- int i = NoiseChunk.this.inCellX; +- int i1 = NoiseChunk.this.inCellY; +- int i2 = NoiseChunk.this.inCellZ; +- return i >= 0 && i1 >= 0 && i2 >= 0 && i < NoiseChunk.this.cellWidth && i1 < NoiseChunk.this.cellHeight && i2 < NoiseChunk.this.cellWidth +- ? this.values[((NoiseChunk.this.cellHeight - 1 - i1) * NoiseChunk.this.cellWidth + i) * NoiseChunk.this.cellWidth + i2] ++ // DivineMC start - Density Function Compiler ++ Supplier run = () -> { ++ if (context != NoiseChunk.this) { ++ return this.noiseFiller.compute(context); ++ } else if (!NoiseChunk.this.interpolating) { ++ throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); ++ } else { ++ int i = NoiseChunk.this.inCellX; ++ int i1 = NoiseChunk.this.inCellY; ++ int i2 = NoiseChunk.this.inCellZ; ++ return i >= 0 && i1 >= 0 && i2 >= 0 && i < NoiseChunk.this.cellWidth && i1 < NoiseChunk.this.cellHeight && i2 < NoiseChunk.this.cellWidth ++ ? this.values[((NoiseChunk.this.cellHeight - 1 - i1) * NoiseChunk.this.cellWidth + i) * NoiseChunk.this.cellWidth + i2] ++ : this.noiseFiller.compute(context); ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return run.get(); ++ } ++ if (context instanceof NoisePosVanillaInterface vif && vif.getType() == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (!isInInterpolationLoop) { ++ return run.get(); ++ } ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = context.blockX() - startBlockX; ++ int cellBlockY = context.blockY() - startBlockY; ++ int cellBlockZ = context.blockZ() - startBlockZ; ++ return cellBlockX >= 0 ++ && cellBlockY >= 0 ++ && cellBlockZ >= 0 ++ && cellBlockX < horizontalCellBlockCount ++ && cellBlockY < verticalCellBlockCount ++ && cellBlockZ < horizontalCellBlockCount ++ ? this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) ++ * horizontalCellBlockCount ++ + cellBlockZ] + : this.noiseFiller.compute(context); + } ++ ++ return run.get(); ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -557,13 +789,84 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class CacheOnce implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ class CacheOnce implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction function; + private long lastCounter; + private long lastArrayCounter; + private double lastValue; + @Nullable + private double[] lastArray; ++ // DivineMC start - Density Function Compiler ++ private double c2me$lastValue = Double.NaN; ++ private int c2me$lastX = Integer.MIN_VALUE; ++ private int c2me$lastY = Integer.MIN_VALUE; ++ private int c2me$lastZ = Integer.MIN_VALUE; ++ ++ private int[] c2me$lastXa; ++ private int[] c2me$lastYa; ++ private int[] c2me$lastZa; ++ private double[] c2me$lastValuea; ++ ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (c2me$lastValuea != null) { ++ for (int i = 0; i < this.c2me$lastValuea.length; i ++) { ++ if (c2me$lastXa[i] == x && c2me$lastYa[i] == y && c2me$lastZa[i] == z) { ++ return c2me$lastValuea[i]; ++ } ++ } ++ } ++ if (!Double.isNaN(c2me$lastValue) && c2me$lastX == x && c2me$lastY == y && c2me$lastZ == z) { ++ return c2me$lastValue; ++ } ++ ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (c2me$lastValuea != null && Arrays.equals(y, c2me$lastYa) && Arrays.equals(x, c2me$lastXa) && Arrays.equals(z, c2me$lastZa)) { ++ System.arraycopy(c2me$lastValuea, 0, res, 0, c2me$lastValuea.length); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ c2me$lastValue = cached; ++ c2me$lastX = x; ++ c2me$lastY = y; ++ c2me$lastZ = z; ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (c2me$lastValuea != null && this.c2me$lastValuea.length == res.length) { ++ System.arraycopy(res, 0, this.c2me$lastValuea, 0, this.c2me$lastValuea.length); ++ System.arraycopy(x, 0, this.c2me$lastXa, 0, this.c2me$lastValuea.length); ++ System.arraycopy(y, 0, this.c2me$lastYa, 0, this.c2me$lastValuea.length); ++ System.arraycopy(z, 0, this.c2me$lastZa, 0, this.c2me$lastValuea.length); ++ } else { ++ this.c2me$lastValuea = Arrays.copyOf(res, res.length); ++ this.c2me$lastXa = Arrays.copyOf(x, x.length); ++ this.c2me$lastYa = Arrays.copyOf(y, y.length); ++ this.c2me$lastZa = Arrays.copyOf(z, z.length); ++ } ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.function; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.function = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + CacheOnce(final DensityFunction function) { + this.function = function; +@@ -571,34 +874,82 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.function.compute(context); +- } else if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { +- return this.lastArray[NoiseChunk.this.arrayIndex]; +- } else if (this.lastCounter == NoiseChunk.this.interpolationCounter) { +- return this.lastValue; +- } else { +- this.lastCounter = NoiseChunk.this.interpolationCounter; +- double d = this.function.compute(context); +- this.lastValue = d; +- return d; ++ // DivineMC start - Density Function Compiler ++ Supplier run = () -> { ++ if (context != NoiseChunk.this) { ++ return this.function.compute(context); ++ } else if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { ++ return this.lastArray[NoiseChunk.this.arrayIndex]; ++ } else if (this.lastCounter == NoiseChunk.this.interpolationCounter) { ++ return this.lastValue; ++ } else { ++ this.lastCounter = NoiseChunk.this.interpolationCounter; ++ double d = this.function.compute(context); ++ this.lastValue = d; ++ return d; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return run.get(); + } ++ int blockX = context.blockX(); ++ int blockY = context.blockY(); ++ int blockZ = context.blockZ(); ++ if (c2me$lastValuea != null) { ++ for (int i = 0; i < this.c2me$lastValuea.length; i ++) { ++ if (c2me$lastXa[i] == blockX && c2me$lastYa[i] == blockY && c2me$lastZa[i] == blockZ) { ++ return c2me$lastValuea[i]; ++ } ++ } ++ } ++ if (!Double.isNaN(c2me$lastValue) && c2me$lastX == blockX && c2me$lastY == blockY && c2me$lastZ == blockZ) { ++ return c2me$lastValue; ++ } ++ double sample = this.function.compute(context); ++ c2me$lastValue = sample; ++ c2me$lastX = blockX; ++ c2me$lastY = blockY; ++ c2me$lastZ = blockZ; ++ ++ return sample; ++ // DivineMC end - Density Function Compiler + } + + @Override + public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { +- System.arraycopy(this.lastArray, 0, array, 0, array.length); +- } else { +- this.wrapped().fillArray(array, contextProvider); +- if (this.lastArray != null && this.lastArray.length == array.length) { +- System.arraycopy(array, 0, this.lastArray, 0, array.length); ++ // DivineMC start - Density Function Compiler ++ Runnable run = () -> { ++ if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { ++ System.arraycopy(this.lastArray, 0, array, 0, array.length); + } else { +- this.lastArray = (double[])array.clone(); +- } ++ this.wrapped().fillArray(array, contextProvider); ++ if (this.lastArray != null && this.lastArray.length == array.length) { ++ System.arraycopy(array, 0, this.lastArray, 0, array.length); ++ } else { ++ this.lastArray = (double[]) array.clone(); ++ } + +- this.lastArrayCounter = NoiseChunk.this.arrayInterpolationCounter; ++ this.lastArrayCounter = NoiseChunk.this.arrayInterpolationCounter; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || contextProvider instanceof NoiseChunk) { ++ run.run(); ++ return; ++ } ++ if (contextProvider instanceof EachApplierVanillaInterface ap) { ++ if (c2me$lastValuea != null && Arrays.equals(ap.getY(), c2me$lastYa) && Arrays.equals(ap.getX(), c2me$lastXa) && Arrays.equals(ap.getZ(), c2me$lastZa)) { ++ System.arraycopy(c2me$lastValuea, 0, array, 0, c2me$lastValuea.length); ++ } else { ++ this.function.fillArray(array, contextProvider); ++ this.c2me$lastValuea = Arrays.copyOf(array, array.length); ++ this.c2me$lastXa = ap.getX(); ++ this.c2me$lastYa = ap.getY(); ++ this.c2me$lastZa = ap.getZ(); ++ } ++ return; + } ++ this.function.fillArray(array, contextProvider); ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -612,9 +963,63 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class FlatCache implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ class FlatCache implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction noiseFiller; + final double[][] values; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ int i = QuartPos.fromBlock(x); ++ int j = QuartPos.fromBlock(z); ++ int k = i - (NoiseChunk.this).firstNoiseX; ++ int l = j - (NoiseChunk.this).firstNoiseZ; ++ int m = this.values.length; ++ if (k >= 0 && l >= 0 && k < m && l < m) { ++ return this.values[k][l]; ++ } else { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ for (int i = 0; i < res.length; i ++) { ++ int i1 = QuartPos.fromBlock(x[i]); ++ int j1 = QuartPos.fromBlock(z[i]); ++ int k = i1 - (NoiseChunk.this).firstNoiseX; ++ int l = j1 - (NoiseChunk.this).firstNoiseZ; ++ int m = this.values.length; ++ if (k >= 0 && l >= 0 && k < m && l < m) { ++ res[i] = this.values[k][l]; ++ } else { ++ System.out.println("partial flat cache hit"); ++ return false; // partial hit possible ++ } ++ } ++ return true; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + FlatCache(final DensityFunction noiseFiller, final boolean computeValues) { + this.noiseFiller = noiseFiller; +@@ -673,7 +1078,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- public class NoiseInterpolator implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ public class NoiseInterpolator implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + double[][] slice0; + double[][] slice1; + private DensityFunction noiseFiller; +@@ -692,6 +1097,104 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + private double valueZ0; + private double valueZ1; + private double value; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ if ((NoiseChunk.this).fillingCell) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = x - startBlockX; ++ int cellBlockY = y - startBlockY; ++ int cellBlockZ = z - startBlockZ; ++ return Mth.lerp3( ++ (double) cellBlockX / (double) horizontalCellBlockCount, ++ (double) cellBlockY / (double) verticalCellBlockCount, ++ (double) cellBlockZ / (double) horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ); ++ } else { ++ return this.value; ++ } ++ } ++ } ++ ++ return CACHE_MISS_NAN_BITS; ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ if ((NoiseChunk.this).fillingCell) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ double horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ double verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ for (int i = 0; i < res.length; i ++) { ++ int cellBlockX = x[i] - startBlockX; ++ int cellBlockY = y[i] - startBlockY; ++ int cellBlockZ = z[i] - startBlockZ; ++ res[i] = Mth.lerp3( ++ (double)cellBlockX / horizontalCellBlockCount, ++ (double)cellBlockY / verticalCellBlockCount, ++ (double)cellBlockZ / horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ); ++ } ++ return true; ++ } else { ++ Arrays.fill(res, this.value); ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + NoiseInterpolator(final DensityFunction noiseFilter) { + this.noiseFiller = noiseFilter; +@@ -741,16 +1244,18 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.noiseFiller.compute(context); +- } else if (!NoiseChunk.this.interpolating) { +- throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); +- } else { +- return NoiseChunk.this.fillingCell +- ? Mth.lerp3( +- (double)NoiseChunk.this.inCellX / NoiseChunk.this.cellWidth, +- (double)NoiseChunk.this.inCellY / NoiseChunk.this.cellHeight, +- (double)NoiseChunk.this.inCellZ / NoiseChunk.this.cellWidth, ++ // DivineMC start - Density Function Compiler ++ Supplier original = () -> { ++ if (context != NoiseChunk.this) { ++ return this.noiseFiller.compute(context); ++ } else if (!NoiseChunk.this.interpolating) { ++ throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); ++ } else { ++ return NoiseChunk.this.fillingCell ++ ? Mth.lerp3( ++ (double) NoiseChunk.this.inCellX / NoiseChunk.this.cellWidth, ++ (double) NoiseChunk.this.inCellY / NoiseChunk.this.cellHeight, ++ (double) NoiseChunk.this.inCellZ / NoiseChunk.this.cellWidth, + this.noise000, + this.noise100, + this.noise010, +@@ -760,8 +1265,45 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + this.noise011, + this.noise111 + ) +- : this.value; ++ : this.value; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return original.get(); + } ++ if (context instanceof NoisePosVanillaInterface vif && vif.getType() == EvalType.INTERPOLATION) { ++ NoiseChunk field = NoiseChunk.this; ++ boolean isInInterpolationLoop = field.interpolating; ++ boolean isSamplingForCaches = field.fillingCell; ++ if (!isInInterpolationLoop) { ++ return original.get(); ++ } ++ int startBlockX = field.cellStartBlockX; ++ int startBlockY = field.cellStartBlockY; ++ int startBlockZ = field.cellStartBlockZ; ++ int horizontalCellBlockCount = field.cellWidth(); ++ int verticalCellBlockCount = field.cellHeight(); ++ int cellBlockX = context.blockX() - startBlockX; ++ int cellBlockY = context.blockY() - startBlockY; ++ int cellBlockZ = context.blockZ() - startBlockZ; ++ return isSamplingForCaches ++ ? Mth.lerp3( ++ (double)cellBlockX / (double)horizontalCellBlockCount, ++ (double)cellBlockY / (double)verticalCellBlockCount, ++ (double)cellBlockZ / (double)horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ) : this.value; ++ } ++ ++ return original.get(); ++ // DivineMC end - Density Function Compiler + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/RandomState.java b/net/minecraft/world/level/levelgen/RandomState.java +index f1e089ecfffa40cd794c49db30fcedf138d3fee9..4e98103b35b03cd67ab9b4bdb91c39a9c94312df 100644 +--- a/net/minecraft/world/level/levelgen/RandomState.java ++++ b/net/minecraft/world/level/levelgen/RandomState.java +@@ -122,6 +122,41 @@ public final class RandomState { + this.router.ridges().mapAll(visitor), + settings.spawnTarget() + ); ++ ++ // DivineMC start - Density Function Compiler ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler) { ++ com.google.common.base.Stopwatch stopwatch = com.google.common.base.Stopwatch.createStarted(); ++ it.unimi.dsi.fastutil.objects.Reference2ReferenceMap tempCache = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(); ++ this.router = new NoiseRouter( ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.barrierNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.fluidLevelFloodednessNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.fluidLevelSpreadNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.lavaNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.temperature(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.vegetation(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.continents(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.erosion(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.depth(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.ridges(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.initialDensityWithoutJaggedness(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.finalDensity(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinToggle(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinRidged(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinGap(), tempCache) ++ ); ++ this.sampler = new Climate.Sampler( ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.temperature(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.humidity(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.continentalness(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.erosion(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.depth(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.weirdness(), tempCache), ++ this.sampler.spawnTarget() ++ ); ++ stopwatch.stop(); ++ System.out.printf("Density function compilation finished in %s%n", stopwatch); ++ } ++ // DivineMC end - Density Function Compiler + } + + public NormalNoise getOrCreateNoise(ResourceKey resourceKey) { diff --git a/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch b/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch new file mode 100644 index 0000000..c7d5458 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch @@ -0,0 +1,15 @@ +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -381,8 +_,10 @@ + final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; + + return Integer.compare( +- Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), +- Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) ++ // DivineMC start - Chunk Loading Priority Optimization ++ (c1x - centerX) * (c1x - centerX) + (c1z - centerZ) * (c1z - centerZ), ++ (c2x - centerX) * (c2x - centerX) + (c2z - centerZ) * (c2z - centerZ) ++ // DivineMC end - Chunk Loading Priority Optimization + ); + }; + private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); diff --git a/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch b/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch new file mode 100644 index 0000000..502b179 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch @@ -0,0 +1,32 @@ +--- a/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -95,17 +_,22 @@ + ((StarLightLightingProvider)lightengine).starlight$serverRelightChunks(chunks, + (final ChunkPos chunkPos) -> { + ++relitChunks[0]; +- sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( +- text("Relit chunk ", BLUE), text(chunkPos.toString()), +- text(", progress: ", BLUE), text(ONE_DECIMAL_PLACES.get().format(100.0 * (double) (relitChunks[0]) / (double) pending[0]) + "%") +- )); ++ // DivineMC start - Make FixLight use action bar ++ sender.getBukkitEntity().sendActionBar(text().color(DARK_AQUA).append( ++ text("Relighting Chunks: ", DARK_AQUA), text(chunkPos.toString()), ++ text(" " + relitChunks[0], net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text("/", DARK_AQUA), ++ text(pending[0] + " ", net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text("(" + (int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%)", net.kyori.adventure.text.format.NamedTextColor.YELLOW) ++ )); // DivineMC end - Make FixLight use action bar + }, + (final int totalRelit) -> { + final long end = System.nanoTime(); + sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( +- text("Relit ", BLUE), text(totalRelit), +- text(" chunks. Took ", BLUE), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms") +- )); ++ // DivineMC start - Make FixLight use action bar ++ text("Relit ", DARK_AQUA), text(totalRelit, net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text(" chunks. Took ", DARK_AQUA), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms", net.kyori.adventure.text.format.NamedTextColor.YELLOW) ++ )); // DivineMC end - Make FixLight use action bar + if (done != null) { + done.run(); + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch new file mode 100644 index 0000000..2ee5bd7 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -19,7 +_,7 @@ + + @Override + public final void addPlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener listener) { +- playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener); // Paper - fix PlayerAdvancements leak ++ playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newConcurrentHashSet()).add(listener); // Paper - fix PlayerAdvancements leak // DivineMC - use concurrent set + } + + @Override diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch new file mode 100644 index 0000000..05a9f23 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -994,6 +_,13 @@ + if (this.hasStopped) return; + this.hasStopped = true; + } ++ // DivineMC start - Respawn players that were dead on server restart ++ for (ServerPlayer player : this.playerList.players) { ++ if (player.isDeadOrDying() || (player.isRemoved() && player.getRemovalReason() == net.minecraft.world.entity.Entity.RemovalReason.KILLED)) { ++ this.playerList.respawn(player, false, net.minecraft.world.entity.Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH); ++ } ++ } ++ // DivineMC end - Respawn players that were dead on server restart + if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging + shutdownThread = Thread.currentThread(); // Paper - Improved watchdog support + org.spigotmc.WatchdogThread.doStop(); // Paper - Improved watchdog support diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch new file mode 100644 index 0000000..2facf9c --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -60,7 +_,7 @@ + private AdvancementHolder lastSelectedTab; + private boolean isFirstPacket = true; + private final Codec codec; +- public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage ++ public final Map, Set>> criterionData = new org.bxteam.divinemc.util.map.ConcurrentReferenceHashMap<>(); // Paper - fix advancement data player leakage // DivineMC - Use ConcurrentReferenceHashMap + + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { + this.playerList = playerList; diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch new file mode 100644 index 0000000..f759c2f --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -2259,6 +_,7 @@ + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { + this.removeEntitiesOnShoulder(); ++ this.stopSleeping(); // DivineMC - Fix MC-119417 + this.stopRiding(); + EnchantmentHelper.stopLocationBasedEffects(this); + } else { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch new file mode 100644 index 0000000..deb2c27 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/stats/ServerStatsCounter.java ++++ b/net/minecraft/stats/ServerStatsCounter.java +@@ -81,12 +_,6 @@ + this.dirty.add(stat); + } + +- private Set> getDirty() { +- Set> set = Sets.newHashSet(this.dirty); +- this.dirty.clear(); +- return set; +- } +- + public void parseLocal(DataFixer fixerUpper, String json) { + try { + try (JsonReader jsonReader = new JsonReader(new StringReader(json))) { +@@ -190,9 +_,11 @@ + public void sendStats(ServerPlayer player) { + Object2IntMap> map = new Object2IntOpenHashMap<>(); + +- for (Stat stat : this.getDirty()) { ++ for (Stat stat : this.dirty) { // DivineMC - Skip dirty stats copy when requesting player stats + map.put(stat, this.getValue(stat)); + } ++ ++ this.dirty.clear(); // DivineMC - Skip dirty stats copy when requesting player stats + + player.connection.send(new ClientboundAwardStatsPacket(map)); + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch new file mode 100644 index 0000000..2933219 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/util/Mth.java ++++ b/net/minecraft/util/Mth.java +@@ -46,11 +_,11 @@ + private static final double[] COS_TAB = new double[257]; + + public static float sin(float value) { +- return SIN[(int)(value * 10430.378F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.sin(value); // DivineMC - lithium: CompactSineLUT + } + + public static float cos(float value) { +- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.cos(value); // DivineMC - lithium: CompactSineLUT + } + + public static float sqrt(float value) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch new file mode 100644 index 0000000..e8a5a70 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/net/minecraft/util/thread/BlockableEventLoop.java +@@ -22,7 +_,7 @@ + import org.slf4j.Logger; + + public abstract class BlockableEventLoop implements ProfilerMeasured, TaskScheduler, Executor { +- public static final long BLOCK_TIME_NANOS = 100000L; ++ public static final long BLOCK_TIME_NANOS = 2000000L; // DivineMC - Fix MC-183518 + private final String name; + private static final Logger LOGGER = LogUtils.getLogger(); + private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); +@@ -146,8 +_,7 @@ + } + + protected void waitForTasks() { +- Thread.yield(); +- LockSupport.parkNanos("waiting for tasks", 100000L); ++ LockSupport.parkNanos("waiting for tasks", 2000000L); // DivineMC - Fix MC-183518 + } + + protected void doRunTask(R task) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch new file mode 100644 index 0000000..61dc5bd --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1385,7 +_,7 @@ + player.setRealHealth(health); + } + +- player.updateScaledHealth(false); ++ this.entityData.set(LivingEntity.DATA_HEALTH_ID, player.getScaledHealth()); // DivineMC - Fix sprint glitch + return; + } + // CraftBukkit end +@@ -2666,6 +_,7 @@ + } + + protected void updateSwingTime() { ++ if (!this.swinging && this.swingTime == 0) return; // DivineMC - lithium: entity.fast_hand_swing + int currentSwingDuration = this.getCurrentSwingDuration(); + if (this.swinging) { + this.swingTime++; +@@ -3635,6 +_,7 @@ + protected void updateFallFlying() { + this.checkSlowFallDistance(); + if (!this.level().isClientSide) { ++ if (!this.isFallFlying() && this.fallFlyTicks == 0) return; // DivineMC - lithium: entity.fast_elytra_check + if (!this.canGlide()) { + if (this.getSharedFlag(7) != false && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit + this.setSharedFlag(7, false); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch new file mode 100644 index 0000000..494bacd --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +@@ -114,6 +_,7 @@ + } + + protected void alertOther(Mob mob, LivingEntity target) { ++ if (mob == target) return; // DivineMC - Fix MC-110386 + mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch new file mode 100644 index 0000000..f300a3f --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +@@ -22,6 +_,12 @@ + + @Override + protected void doTick(ServerLevel level, Villager entity) { ++ // DivineMC start - skip useless secondary poi sensor ++ if (org.bxteam.divinemc.DivineConfig.skipUselessSecondaryPoiSensor && entity.getVillagerData().getProfession().secondaryPoi().isEmpty()) { ++ entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); ++ return; ++ } ++ // DivineMC end - skip useless secondary poi sensor + // Purpur start - Option for Villager Clerics to farm Nether Wart - make sure clerics don't wander to soul sand when the option is off + Brain brain = entity.getBrain(); + if (!level.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch new file mode 100644 index 0000000..abf709e --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -320,6 +_,12 @@ + if (!this.isSilent()) { + serverLevel.levelEvent(null, 1027, this.blockPosition(), 0); + } ++ ++ // DivineMC start - Fix MC-200418 ++ if (villager.isPassenger() && villager.getVehicle() instanceof net.minecraft.world.entity.animal.Chicken && villager.isBaby()) { ++ villager.removeVehicle(); ++ } ++ // DivineMC end - Fix MC-200418 + // CraftBukkit start + }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.CURED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED // CraftBukkit + ); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch new file mode 100644 index 0000000..b431131 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -1879,6 +_,11 @@ + } + + public void causeFoodExhaustion(float exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason reason) { ++ // DivineMC start - Fix MC-31819 ++ if (this.level().getDifficulty() == Difficulty.PEACEFUL) { ++ return; ++ } ++ // DivineMC end - Fix MC-31819 + // CraftBukkit end + if (!this.abilities.invulnerable) { + if (!this.level().isClientSide) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch new file mode 100644 index 0000000..56a0ade --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/GameRules.java ++++ b/net/minecraft/world/level/GameRules.java +@@ -249,7 +_,7 @@ + } + + private GameRules(Map, GameRules.Value> rules, FeatureFlagSet enabledFeatures) { +- this.rules = rules; ++ this.rules = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(rules); // DivineMC - lithium: collections.gamerules + this.enabledFeatures = enabledFeatures; + + // Paper start - Perf: Use array for gamerule storage diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch new file mode 100644 index 0000000..4cd6154 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/Blocks.java ++++ b/net/minecraft/world/level/block/Blocks.java +@@ -6632,6 +_,7 @@ + .mapColor(MapColor.COLOR_ORANGE) + .instrument(NoteBlockInstrument.BASEDRUM) + .requiresCorrectToolForDrops() ++ .sound(SoundType.COPPER) // DivineMC - Fix MC-223153 + .strength(5.0F, 6.0F) + ); + public static final Block RAW_GOLD_BLOCK = register( diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch new file mode 100644 index 0000000..adeb412 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/level/block/LeavesBlock.java ++++ b/net/minecraft/world/level/block/LeavesBlock.java +@@ -82,7 +_,23 @@ + + @Override + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { +- level.setBlock(pos, updateDistance(state, level, pos), 3); ++ // DivineMC start - Make leaves not suffocate the server ++ int newValue = 7; ++ int oldValue = state.getValue(DISTANCE); ++ BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); ++ ++ for (Direction direction : Direction.values()) { ++ mutable.setWithOffset(pos, direction); ++ newValue = Math.min(newValue, getDistanceAt(level.getBlockState(mutable)) + 1); ++ if (newValue == 1) { ++ break; ++ } ++ } ++ ++ if (newValue != oldValue) { ++ level.setBlock(pos, state.setValue(DISTANCE, newValue), 3); ++ } ++ // DivineMC end - Make leaves not suffocate the server + } + + @Override diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch new file mode 100644 index 0000000..42d13f8 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -267,11 +_,18 @@ + public BlockState getBlockStateFinal(final int x, final int y, final int z) { + // Copied and modified from below + final int sectionIndex = this.getSectionIndex(y); +- if (sectionIndex < 0 || sectionIndex >= this.sections.length +- || this.sections[sectionIndex].nonEmptyBlockCount == 0) { +- return Blocks.AIR.defaultBlockState(); +- } +- return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15); ++ // DivineMC start - optimize block state lookup ++ if (sectionIndex < 0 || sectionIndex >= this.sections.length) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ++ final LevelChunkSection section = this.sections[sectionIndex]; ++ if (section.nonEmptyBlockCount == 0) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ++ return section.states.get((y & 15) << 8 | (z & 15) << 4 | (x & 15)); ++ // DivineMC end - optimize block state lookup + } + @Override + public BlockState getBlockState(BlockPos pos) { diff --git a/divinemc-server/paper-patches/features/0001-Rebrand.patch b/divinemc-server/paper-patches/features/0001-Rebrand.patch index 3ac9f3b..7fdb2fa 100644 --- a/divinemc-server/paper-patches/features/0001-Rebrand.patch +++ b/divinemc-server/paper-patches/features/0001-Rebrand.patch @@ -1,36 +1,14 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 11 Jan 2025 22:11:40 +0300 +Date: Mon, 27 Jan 2025 01:35:41 +0300 Subject: [PATCH] Rebrand -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index be1bb14dca9367b9685841985b6198376986c496..58f16cdf593dbf874652a46799ecfa5c418c0486 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -592,7 +592,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()); // Pufferfish // Purpur - Purpur config files -+ Metrics metrics = new Metrics("DivineMC", serverUUID, logFailedRequests, Bukkit.getLogger()); // DivineMC - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -@@ -602,7 +602,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 - Purpur config files -- metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files -+ metrics.addCustomChart(new Metrics.SimplePie("divinemc_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // DivineMC - - metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(); diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea91df2cfd7 100644 +index fe66e43c27e0798770e102d1385bacbaa90bda07..5aef1f7aa8c614e50b34747456e7d5a3f0045aff 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -36,13 +36,13 @@ public class PaperVersionFetcher implements VersionFetcher { +@@ -36,7 +36,7 @@ public class PaperVersionFetcher implements VersionFetcher { private static final int DISTANCE_ERROR = -1; private static final int DISTANCE_UNKNOWN = -2; // Purpur start - Rebrand @@ -39,19 +17,12 @@ index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea9 private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } // Purpur end - Rebrand - @Override - public long getCacheTime() { -- return 720000; -+ return 600000; // DivineMC - Decrease cache time to 10 minutes - } - - @Override @@ -52,7 +52,7 @@ public class PaperVersionFetcher implements VersionFetcher { if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); } else { - updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur - Rebrand -+ updateMessage = getUpdateStatusMessage("BX-Team/DivineMC", build); // DivineMC - Branding ++ updateMessage = getUpdateStatusMessage("BX-Team/DivineMC", build); // DivineMC - Rebrand } final @Nullable Component history = this.getHistory(); @@ -70,13 +41,13 @@ index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea9 - if (gitBranch.isPresent() && gitCommit.isPresent()) { - distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); - } -+ // DivineMC start - Branding ++ // DivineMC start - Rebrand + final Optional gitBranch = build.gitBranch(); + final Optional gitCommit = build.gitCommit(); + if (gitBranch.isPresent() && gitCommit.isPresent()) { + distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); } -+ // DivineMC end - Branding ++ // DivineMC end - Rebrand return switch (distance) { case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur - Rebrand @@ -121,31 +92,50 @@ index bc7e4e5560708fea89c584b1d8b471f4966f311a..6567ff18cb1c21230565c2d92caf3a7f .completer(new ConsoleCommandCompleter(this.server)) .option(LineReader.Option.COMPLETE_IN_WORD, true); diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index b36e30fd4057a938e4d90cb42a2dca661f16478e..fd860b1f3acbffa923dcfd39f7cb6f23cacbd408 100644 +index b36e30fd4057a938e4d90cb42a2dca661f16478e..4e29f5f55b9a894099bef6f7c7f11e2a96b02fc8 100644 --- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -32,6 +32,7 @@ public record ServerBuildInfoImpl( +@@ -23,15 +23,18 @@ public record ServerBuildInfoImpl( + Optional gitBranch, + Optional gitCommit + ) implements ServerBuildInfo { ++ public static boolean IS_EXPERIMENTAL = false; // DivineMC - Rebrand + private static final String ATTRIBUTE_BRAND_ID = "Brand-Id"; + private static final String ATTRIBUTE_BRAND_NAME = "Brand-Name"; + private static final String ATTRIBUTE_BUILD_TIME = "Build-Time"; + private static final String ATTRIBUTE_BUILD_NUMBER = "Build-Number"; + private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; + private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; ++ private static final String ATTRIBUTE_EXPERIMENTAL = "Experimental"; // DivineMC - Rebrand private static final String BRAND_PAPER_NAME = "Paper"; private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - Rebrand -+ private static final String BRAND_DIVINEMC_NAME = "DivineMC"; // DivineMC - Branding ++ private static final String BRAND_DIVINEMC_NAME = "DivineMC"; // DivineMC - Rebrand private static final String BUILD_DEV = "DEV"; -@@ -43,9 +44,9 @@ public record ServerBuildInfoImpl( +@@ -43,9 +46,9 @@ public record ServerBuildInfoImpl( this( getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) .map(Key::key) - .orElse(BRAND_PURPUR_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(BRAND_DIVINEMC_ID), // DivineMC - Branding ++ .orElse(BRAND_DIVINEMC_ID), // DivineMC - Rebrand getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) - .orElse(BRAND_PURPUR_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(BRAND_DIVINEMC_NAME), // DivineMC - Branding ++ .orElse(BRAND_DIVINEMC_NAME), // DivineMC - Rebrand SharedConstants.getCurrentVersion().getId(), SharedConstants.getCurrentVersion().getName(), getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) +@@ -58,6 +61,7 @@ public record ServerBuildInfoImpl( + getManifestAttribute(manifest, ATTRIBUTE_GIT_BRANCH), + getManifestAttribute(manifest, ATTRIBUTE_GIT_COMMIT) + ); ++ IS_EXPERIMENTAL = Boolean.parseBoolean(getManifestAttribute(manifest, ATTRIBUTE_EXPERIMENTAL).orElse("false")); // DivineMC - Rebrand + } + + @Override diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..7f3d6dc1c37ffce387ddd04bdd7328e0154b33ed 100644 +index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..a839dbbb72f48b8f8736d9f4693c528686570732 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -491,7 +491,7 @@ public class CraftScheduler implements BukkitScheduler { @@ -153,7 +143,7 @@ index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..7f3d6dc1c37ffce387ddd04bdd7328e0 } else { // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper - task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Purpur"); // Paper // Purpur - Rebrand -+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to DivineMC"); // Paper // DivineMC - Rebrand ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to DivineMC"); // DivineMC - Rebrand // We don't need to parse pending // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) } @@ -171,7 +161,7 @@ index 99eb04643fce44c37fd96c99756837ccafe7b559..4aef151bd162c4c99a3eaec1854b5463 if (stream != null) { diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb26485c8d87ab 100644 +index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..3731ca80ed58cd385cd66ffbe67f2eeaae642d0f 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -77,14 +77,14 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre @@ -179,7 +169,7 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 // Paper end logger.log(Level.SEVERE, "------------------------------"); - logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug."); // Paper // Purpur - Rebrand -+ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a DivineMC bug."); // Paper // DivineMC - Rebrand ++ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a DivineMC bug."); // DivineMC - Rebrand logger.log(Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author"); logger.log(Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring"); logger.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"); @@ -203,7 +193,7 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 // Paper end - Different message for short timeout logger.log(Level.SEVERE, "------------------------------"); - logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Purpur!):" ); // Paper // Purpur - Rebrand -+ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to DivineMC!):" ); // Paper // DivineMC - Rebrand ++ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to DivineMC!):" ); // DivineMC - Rebrand FeatureHooks.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - log detailed tick information WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger); logger.log(Level.SEVERE, "------------------------------"); @@ -216,315 +206,3 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 } logger.log(Level.SEVERE, "------------------------------"); -diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png -index 518591dd83289e041a16e2c2e7d7e7640d4b2e1b..f54753531b3bf2e8b5377f342465e727c7da98f2 100644 -GIT binary patch -literal 6677 -zcmZ`ecT^MIvk65y(vr|S0wD$n1T=z3m0m+wF=$9cr3j%}P!W;dMd>0)Cy6mZP(Xpu -z`KXEn5Jf2hLO#R>ih{iOJMX-A-XCxG+?_jjX7=vRopyIq-Cd813CjzEKp-&(dmB#> -z2#omK1bMk5u9dOJxq$DSrHds9#LO1i@#p8_sw8_)7Z51s00J|A35u(#^8|4ULRXr{u5ar-vE3hmqE3r=>bw!l> -zCLnNFgew+2R&lAAi)cmJ0#RrDqXICbhyX4Cp$t%{gN6nNQN~z96O4fg24$*eV1O|& -z`24~m`2Pr82s;ya_R9Y+a5FP`iYwq7063g=aRI@(eL)aESPJx4yI}4K0?UK`s+8LU -zIf51br|${Y`EMQ`5Qs-kkkt%z@DqvQS0OciO<6(B~X-Ttj8^Ly;I -zo6TV6_jdf1w>dz}4f>(bF>w9Nl+(tmyuNjboV5a{jFW#sdhZ3z{C6FC!V#*o?@AZ* -z6@#2M7W4S(D=_e$&8q=XmdM!qgOR%?ntYTjPFBys6aK5aZOW9A-@mVhM4$Tn#+hCB -zNpgxYSKw4B-@=Ml8HJRuwG70o3_~8YcHFp3NRQPixJk)8jWlBG`$)0fR?<9ouc(Iq -z@M>AWUQmUPC;T+To4^Nb6>2S#hFq7L&p%!1Cs%tkSst0NNYZm;6BFuVg$q)J!)F7( -z%(e_;?{knc?~C+ODT}S?y_c3y-kj+)Wgt5{`{U-T!|fUc+rlYhRs);U0u{fq@D7>$ -z_>Lp=enScNrA5b3pV^mFIh4g1^)G(S4aMb`J2kRvHwN--6UVxbA8uFjF<}pE*7ZMK -zD7IlonkOy3AIJvqJ+^v3B{iHk+09aQ#>kl{ -zmaISJQBQP8%5~(PD4NTU;H% -zN4-1eLAj|t8F9I*tOJKM3*6eu_ik{zyQXVr`+F=q1YAwrdHH=#%BbwO{yyyY;)#O9 -zGoK`Ujx6-fNTj483E1!FGtYVz6zXJkTq}>qH=7wK@z<-uMz+lx_|+jFzKR&rwU*h7 -z-SX~-k+=i6A(lrB*0r`9T8GUNIhfO&-z;_$ZArSIsvPp?xN$s|$dHe^`OM^F>B%4e -z+B+^26JapEmg)E{;0#))&Eg{uHb-Q`84A;T-g<0_D1txOzm@fAkH7Bi)a>I9KNL*w -z8u{ixFO8tyq?{r4KmQm -zSNiE?)c%L{xJxhP4&5oKhr-|T5c1%mU!3b24|v+PPTx7XiKE<3_og)W8hwd4bQ{h> -zw@BZ$Vn;F^AsxsUQYH4)WS37yHJ(jv^McSfJ!KYWI{j#p^1Z|(l4aXFHm%b)_D;p6 -zeK-F2{zQ2YFT{AfE%kmcleZA9 -zmujwY7rUA^wck?=KmRZL&VyCf#rO}){K)5E`+yI=l9Y}ctF9gtg(oT>q(z-%>r+DF -zn{-;VUxR+9B~Sb8RDV(I_k9?;D(8Gj$fHRTu6IN%?`EkCa{IH0=Sw41*UgZwulPmn -z)laGv2^e*bcaeZY?uLkYS|UaAAk@XKvKj$Q#dtIDN(~iqeqPuo)DqqwP}x9whzk*E -z)NIrkYyrM~c0&+-xfVm0sHynpwS>3uWK3zFR@OKpKT68yUK+jEWMymEb@OX(VqRCZ -zco5SyxoF6ae@doaqTnW6VQ3Jd$a`kjNV=eO@Zm$xZF6G}7ghtgdVi5!%XnRn%eXw- -z5iK<2&Q31*PFo0_vOm)B@jz8@r(e!mQfMwD=&?h;ZzRnDPg}ScJk3G;N{Twj!p|gP -zZ1NJDPDRo}kV&8^LC@`mm!?g5{Tm`pfl(A^nzA4%Aly)})h>skcaj#dh-WUB+2v)j -zr95b|Q+IN*So~Kd)bW>BXBm)nA4YzIBN`l>!5-{FPw;~?1hk@%>!qrYPf1$tE?pFO -zR(1GIYDWAQ2|+onsA2wkJGv%3*?Z~)U_KQDSyW$#ffv=J6c6A``RiloHWXzl{V~&* -z&V~V*Q_`REbq*sw5LhB1AB)yLbGR_mdQ53v;X9z)~53wn#^ -zhY>6hMd{+ahRAWu&JX=eM}xe;S4x+E`U2F-MVhwq3$bl6tAdP#!*Q#rrb=PvH*R#*&lq@s -zX-}W#0!mYyVBv!INAOqqnnSQTPBlFE`K%FwcSl|yCbF%4rsak5(c8l<0|B)Wcnq5b -z;qZ#jE*)l^din^5G{0FHZdjz6H_Zrec>!FyViZ*%hx6hIKAA!_w~WU-FWRLUK+M}s -z&5}N$I)jR>0Ig;`n;K!5sc^81J5~A2&&QeWdELc>{Q7I^uvg7OwuOV?sn{?c+$)*B -z8QeC$v1qV4G*@HNZ_CAf((f$bV#JG!Hl+XLiK%Y#@}Sie32{*PFZ_*S1y+g7;i{{F299LdV- -zt=1$T@Z7b<@1rPlc1ua?PK5P~ih4zQIUB|H=+IdkHnFWElD69}f$R77-p1elK4(L@ -zKV-kTwe@F|x%E39IEPG%K2+FJ@fv&4&$-g`BnJcOnb{B_t@F`}n$J(hLMf7*<_?KT -zQGAry`2+CeXtPriR0G&2@TfhTq?8ToYlwWfIjk9v_?J+L-R5aa8nyhmwNEWVs`qQVt|6;3F8s7O6OWRPGL -zZA@%BSq|u50S>~t9apT3%Ds`89)E=-_d3~pj+{CzRza4tq@^Iz(|;TZD?%b8UUnLB -zxPrC}d>`1q<>4_}sHTa^kjhCF6-jz&U+?my5q-yBP=50^sl^#56D&Jl7LO)hzh=`s -zsyB%5$`rJuZ$%&Q9%15n&C{07W;yq)H6sAjl-ep?><1=2&7Zxw#I<-6L@H!fhEC7b^>|R<>!C4mhUdkjh+LNZ%J|%ty%H-cJRe6Y;427PH?td -zdXro(qo(HkAzXhi1D?grRmzh67%+V#iFBgg?hb+`wT;M%US0ruLy);kyxtm;>YYP2JXBAWeEP7Y^my -zYXNvj{uB>TsCR$jfy?hh`BrE!wOw?Qd_WyBz~6V{Zk70k0M{k#9t`$TIO+b;c)nnK -z+`gfns;r5?3FZus4z>uGbxZRmvaGm%{+cmUAj7GYVqlP9s(zdmq&N4SY%Gz6syrf} -zKb7Wx6Kw`vB|d{3&e!7-J1N#+rlVNmDnNQ>9N*h)Ultw`Cy5AS>tIK_t1~2VehSRN -zOofI~k+m&ZrXXz92oC&FOaNTEnQz@~e19ssc3R?I=*ckp{xp)!dq(uLmRd9sr=MEW -zu5&pge3Wsoh7%Ga(d{2R>2@q0e7RT+VcUeahll^`d~VMbVD^9@E>-tX -zj+T|~-zErreB@U5m64RGN^2e1L7=@C-;_@Dr9>~I2)Dfw-Ioa4BOuzR%-7sBV2v=Q -z!P7RMtN@pnyHp8wyb536D8DO48TO=TxlnZzmPRd8Y-_K4$4+X9DwXrsI>f`?uWiW*uF_OqO=E=V+Wc^KhHSdWr -zZm9Z+ED>0IGDwULbEkdLg%_?>0E^dIA_S#u#b6!XKAz*#3N@_;`FcCO%$YLU(hKeq -zMd9VC`hOCczshK~gG~F`Xb*(NhH!yFRQD@U013Dy`?Kv3u|9w#?Phx3=kjn{CXQ-M -zV^+}wN>kZ?P|~>mfKJa}W{T962LH}njNYD;X!_XwFCC*v(aR0~y1Ri{?1&Q#vV#Dn -zQc`3~%sl^hfE}&7Go_l3TahgQja9sPVj`ES>erdt*vs4pmt3^Mvq(QFQX(-a`>n@lv=SVs;4kaY-sY6PPep8F~-g -zQgBMDVkT*k{{dDu4E!JD3b*_)m}D9f<@S;jTi2xsp5R_t_I-XImdpCvJ)v) -zw>Y#Hk4QbRbbgVW?Y{!Yb<5DS!t830X1KQ|wBM>M3h?^F^!@k4LYR)2&%DqRD??2n5d!c1yKT9OsEZ -zFmFEgOx|Du`q~L)Tz_7=7TMcT5a?b%g2~5z0Of&)uKSow7U`*^hX(>|9BZw)f)`po -zU`f&8rX*1CW>D{p!vQEu0Fh}VNxE7|Svvo@a$T%B^jXpq=4+Y0+~$uO2a`1DbLZ{M=x<2ZLQ&Ob8-J3X&*xUTe$D!E2I!8j(atSZO62+_N-WbxQkJyi$~6X4Z1+W86N&e4c>?g{XF9eX7*0hyBh_omBt^uQ8Gj^-Z3+_t -zs&jo6z7eCV{iE|FMMRe`YyQwH3J>fXEFWYwI3Lbsj4t^DpPuUAvu7D91siR*el5@qzBKGX1L3jBzot|R8f -zOghP<*WXDf=oRu9qax~wuD6^DLnj42-NSs#lY*|+n00Y*Z`OgX5Qjc$ -z+aWFtdZ6+jl}9jY0+gQYMiszi-#qN<%}-=}&^lTBZK70*$9|}P} -zL1bvT9F}?m$U}qNtBKWS6Xg`>ml9>Uk0L^XMD;zOX@4%$w)>L$Ho$IA?Ln7^Ut@y=i<=6`!lo*YCm-mo`gO+r4}D86(0j*eZ|*J -zOEX6CiHtdGgLpuV(gpCsbbs<8PVFrb=Czaf7+u&q4DQqR2biXkUIqtapx2+Lr9mpc -z0%M2&+DG*AOeiD|fp{D0)GNLPKrB#255Aa)+Ll!u!2!~;-@IA{9` -z&twsmLc_lXsMexXDC6^Xoy&hN#To~4`q$pA97VW-Y`pHkQI&LN1+{Psu+33s53Tzx_I~=cZ51O5zD3q|%l{lPufGVQ%>V -zc9Jjc;PWozkX7+AoNr#-!27Y-gJue*2J#_}KEyrvs!_jGXN4D#R}7vb77W=SUZ6Gs -z7Y)KXZ)A=Tq#u5)I#Nkocxpv^o_H{6Oa?Krs5W~x0i89Q0W2V}7T+q(^7^6=>EO#W -z`DpX-^DK4qz{Ir-et}j+Xc=(gmYnT3_kZw%i$RC-!!A91v3jnPs4JfsG&~GKU@Uk% -zENNX>DcG=i<rB<}b8O6NIoLHgvK2Tj%WZ=ep`+B)qH1538Kw(zLYpwj?RL -zKQC*8IZKJ85gXf`D@XmrZ}z0B4$-% -z#5u_aJlT~Rz#nDlIJ6PugYP20>(80*@KTjt2Lvz5mZ0M7$)CcUk@p_s$^h?o_rF$>!zEoV+I-e)}5CY2o9PEjay* -zV>Cj8ZChL}gx7qpb>?}aNx}GS69W2l#$e=$@mq@2rNQ3ZaqlTtH2F0b9YVEg_&cmp -z9vx$cf7u~q1PkZir56Xw60;6fM=X)hnQ`bvgB}QZiFos!aZPc!EHRvJhPYeZiG3_? -zjZbnKVSm#-9*)S}?Zz7Ix5mdiDb3_8pG#x{I61G8qoRfu1(?S9zqVOT5UPa0RFVoy -zxGZG6EN57Ym|95?5w#v3susU+2$|LdW!O*>lhl?!cqW?wb$~FN*e&rbyxv*?dCe5X -zA3X1$($YNfAX74Uu7Tv&Y0zVaUwg5SyFa7>J}6Pc<8{{Dz36a2cWZ@ziU|3oYtGnA -znJD06B5HU9wr-RKbRVXY{N@dMhVhN*V%+a3VjRb0wX;hVazYu=%el;UduXEp%w^7< -zb|+!8yUq+Ya!HO6tIB5eqD~poR2H$D+@pe77pwERgEIA -z0!|y>AQ6FFu;Cr?4;OGC36S8`-RHRs!ojv|9~nbh^^c7~^@OJH?SB5}xeQZzNnGTp -zU${G$vNFg^JlLjxT2*m!{P!2zieBFsmDoj7By5jYgbdVWx_kcpnE-OIb+w^e5#s*~ -DA-Nv< - -literal 9260 -zcmWk!Wmptl7+qlLS~|W3b}1=IK|*5b5@|#_l0AUP -zC6t~Ie$33hbMG7HJ?G9mbDv4n)lnlSVI~2AK;# -z4OMQt9~6LaN1#s_Fh>FQQNXigV2~Fm&;xWcfNA!dm*#+B)G7nz8bF6QFw6s>Er4EOK&%?bF$4;AfiW&% -zoCBC(2Ns!cigf^wAiz2NZqBnLz$Fx@R=#VuNdgNjK)EK+fB@F$fJ{?BsqoI$ED5Mo -z1rqE4Uti#zCSVl@%tLSX$$)HQ;Jq4~ho`CY^a7vP8(5UvMwUftX}AK<&Iwsa{VUUgUh -z2@c>vB_LZ0XlDV+Z-B?IZf;?2Q{C)P0>ZVx9Q3a7hXhz;0*3DaKX~ub_6P=EnF2ok -zcR7!90-gbPNmi@e3BV~F2=oSCMBV|pg>WmCTgLr-01Q8169d>q-)SS&0%(`GBd$p2 -zE<~Lo5bOy|!S8arrBXcyc!~mEJ_FLt@08Ob4a7eO9#jGH#Xy)lfU>@0405w;9)s13%z-g3FI09k4gZC@SEGh21MSdMw@%zE`WMpeH{Z3F$F%s4K+W4gOWCo -zM2#hp$Mq4nbl;~?VJ4luX8W7#1E`a&x!wY&zb88l -zN1p;u{w(({h5e>J1%Y6K8p;U6z`4mh7wrra#_v{h<9beb_Uu_ssLuklU5mIC>bM*t -zcnDp3!57GGcIWN~=GwhPk`|qj+4X29PCWJ%!mm8dv^)i!1KFLpM5tu4Zu)l4x1`+4 -zuool8`RpVod-L>kH&(y$T!#xcuSSC206r^ApC6SB-*ez^5f|E=>CVr`YArBlJ*aH= -zv9&F3YdaKfe*MZ~i5LpP{mi>QT}i><&#tzbf_0y?pPchedeE97X-j8))~zv`+-R^c -zXW+`~MJv5ye2+%Kvb|-$zve#6Fq3j>e(Z$inlS;)z#xljVJ?019I=wkGZKCb>mY`{ -zC8KIq#mXl@3vR}_b~1^fB_&=iJ$$n&3S*05tirE<8!l{ZZCzis{#|hXxvFTM`o}iR -zq}ASGAx!t@bKd6MjeH3iBEIB}%Yan>#e?6>$^PrcHJlA)z3Hwi9`j3t3g5m_#CTR2 -zJWL`g(S4CEDe2|vYOlPr-diIV^oy)GsZk29-%}jHck!wdB5f==uk13XY9^5>Drj(Jal(oLc`8 -z7D=J9cm#rVYK(uzdmUKaOiVbY*(WAOkmLNmxVRVy4%oluYjcE3ejCZtD>w+KsRMI; -z87X<1i$@!2V<|7#NM2W6ZEQ5}eehW7L)rSeirL`4wOgYhpON3G_`a-$K8jR7b1;WL -z!~?6_@enfHT1uN@893H{iZNi1|9834^3*)D`mGffWf%2a)wvAK&u>(jHANwD#zmPX -zb+%6usxsHvd2-@g4UX8e(%gfc=yCIMYT;LE&!w^Hm;hjMmbIls&Ko=!h)JjeBiK7j -zJg8+Wwf=llHxatzP37JYP2MYV!uiXB_G}g(g)9DJKdnb9^WJMK(TfEmW8+Fb{$ocI -zd_IJNIpT-3}l*&5tlx{G{r8LcDpgZGm&_LCN*L6GgC7m* -z9Ckq~9^@3|Q4>gQQNDOZ2HyVd9pW<)EoE)RyU2C+kH**{1?C-Xq|NDRXjCBSZu!dW -zkWoaF%%=W+tXxD?vrJiW1>QdCcU?IwSAAVKXSF08Ri&so^yCRhU8OJJ-7;U}%kOXh -z;uh`2naxh}@bu)!tqCu)Xf)+K3I8Q5)ZcBvHOp43NX8R{ud%yohLd|fFsJPE=-WnM -zy9)c2e5+K_=dT7ym;4dn(y2MkPN*C+c_fH{ZfAWh`@@h_TYs$2*8y!HC~F*!SDduQi((Yakpx9^)x -zJx6lr@C*IFi6C32_c%iv6B`G$;M6Q37L;6Uui3x9r{WSzT7DwjzcfM?D1LbD#A$qd -zu4>LOEHC&2!$+8)#A2;xEg(p|Jdy(=RRcM>kUoYG*Qz;+&oyU511wbY(v-jRqxsns -z%#|?EAim6T>_zCj2<-iPVp$G)<1~m`qSX7m3`(@)$3!@_Il;X3RXlkq+00DbtS7gY -z>#I3K0|TFc5y9cN<3t=oP_kgwm69oEvtnYz&nRnvO7Njr5W~StAj9NIY?n)l*H$Iz -z2YURl{gS{bBrJXar($8yfT4n#w=5%{AEV-@Grkiy0_J -z=#2;?ULHsk`lZB!Pq?X9XYS_xO{hASO}zKeLGkV?N1%z -z>{sH;{En>vxtZU%K&AU5%Gwj}B-+Pkl=AbytOz{;6ed;B5v9!jh)%X6>$P18-6BW0 -z9}ExHska~pvMsOcE?mMPyWj`s*pbbrIhx_ij%6Esl?wROHn#vuvN1k)+M*(8X8`A{ -zS4o(sjn)kE#;i6MtveA?5(&g98!Mcr!J5=}Qa@!L1e14aJEQmcLzXKl5nn2bAJ$tZtP$P8Z1 -zZ>}d+7jWYR^n<^KOqhO^N==PYrD)O9EEFNs=im5ICPNsHVP!Zx`PfqbpT-5=sn650 -zHSeafmr~){Zr!JcC1%$s2t+!}=}Ip+y3BYtETmoWiR}1P><_9ZZ0FT%gEZStrgdbp -zI0aw6jiD;PvU!g!GH_nZQDTX%5h%9pk4-fdm}0u~yzd;=Cni~sWY3r;O}XL;q&8-M -z47pHP%S*mY4oBx}PU^}#j%4iThs7lM7N=#@Fdn{XdiIW0L(=C2~Fu=G5&Vq%Yr -zY&qWfrH35E^L#BnOgMwRc8B)xMX+GlbUbtr`nPrR=jS(Tb=kQ6o7eVqUZd`0fo9D@ -z`vyo59#*A!saE#!$G7Cxfh7n>ZWX!0gkro``0W{b9=XxNGqu#u6^e-7Kk1lUvA@1| -zbiNNGZAv0UHvN5m{KO8QNS~$+u-B>s_5L`L`jBdrke`Xe{0N -zj*umKyN|_A>O3@@8y|M457h5tzNEqIDV$3|c%QOiW7;H(RNLM9W0najA-;HEwdD>u -zkW^fo^J0f?_u(^NWw(nT>JS~~smIbnC8QxFcuQkHNQUJyj;eYMTmvVN^O|RPDvczq -z>kslY@C$0YBmZR=Y@IEsCWJ9}K@HOu`c4n=$kr9!;n+9We=f8unlK5@c*o|I7(=zG -zS}lWvH_fp;i}4qt9>h_?QzN~ap@7nLh*7DJwfvLZAS*_S4BW3*FwwCv)lD1Vg1t7>@!DLp5Su@bc$d;D_VX! -z57eDlJm5IkuTyc>aw+Wt#V5hkX-B+t+|!Fa@@x7=`8`3dZm3$aHF*y2VcD$(D>J3y -z%YwpO#gY%mqyin8q9uH?7OBC}Uv-@)Hl0)Z2+y3x`{XluljPt{<4TU-m$XDvYU9HDGKSg&cT(@MVe23D -z<wCdy-3tTl}j4kzaCmrni9Diizn#a{@0 -zu;<>ZBofe$`#ML)Z*Aj8mbPS3-(VgSdF3LA8LJgBPj{3IaB~uLBuylKO*>ev*i|tuS(Wff3`Ogj^{GsE -zb-&NcY_)d|4#vv+AW}skSWrN{p$KOp?oug)BPoM*O}*h=wrr-PGYCt{qwP-&I!~hn -z&+*{EI5^09slk8i%kFcTjCcO6Oc}M9iiS}cuLLw0fyMPfjZ_M`;HWDHP^ke=f*5^! -z(0!2p!N9nZ8J+AP5sfunmbw64l@2)3@53_`Q@g&4FYKeDLbz2F-Pl@Er#INAtM()n -z<;MFT;osC}L2=Y4P1?cm)V{7nauSgh -zx4)k@#lLT}(uM?{Z&NvzT8pnJk@-MDvv4wOKsY+l9S8OdOw}cex#$t*B6ppAEloQy -z8u`9L`Ky$*$*G}A=)h6BjVn=_YuPie5epXe0vLKZP=Pxps%a&uGrdg4y#R{YobnDn -zWlMl5J;9|5ev`f`BO3Uh(yuK%@i^`+fAimq+_>*)z(;wrFdXn2nVJw1y_>PcV%p-) -zt#ZCU`}~DIE5y1BiI$qSd5XIebq(uRB-FnL#^x=bDHO-ls3({4R}-PgePOPH8ggGN -z^EAdT|N11qATWCZQ}ZY#=hum&;_xeyp*|O{VN?%jM$?&+DK=8LzLP4?U!YQ_JT0`M -z`m!W@TCB5mlr9&jJ{^cX4Ye=uf(7g!BDG1LQfZM#P5u-^$L1K~rOMk7oWI24W>ZJVoZ8!*NX>jC%2pgw@7$v*amGL8JnA`*TjN)y=JPix$ -zHom^xjfr~ZFvhO?xbJHg&jfEZgboIqN@pk*%G-#&cX5DM>_>dMo{P<(#6bOBlgEyG -zzi7pMrVsz<*TT+n%Xo*+rUVN -ztJqe<9EEg`YU)i$PQ+gn%oA5sG>b*6QUjZVf+Ju4QKWr32^B^>#t1pQ*dNT#SxuC8 -zGNJ9mH6R-92O+m#!DECXMrB|3+|cZ$?^qoag;w8-vr)Z5hx@2MVoazOwqdLo2-lu( -ziwF(a3SNH{+U_=d6KFp}-N^eInrfTZ{p%Yc;K{NXO^Sl0qy(%)%sUXL -zA6Ic}&$4}O+ulD-?|#%otX@aO$zD_RW;|WXeL`u5;iz`2Ja937zabkT*wwyB&2HIU -z%>tpNcvDWCsG=IjRr(V|Z7N`9^tphbhiC}fIrVcS>=>)uMStm;g&z-HK{fFz2ud&X -zKo;y87PhB3IZGZ&D%bJ2dZ<-Z7I$fCrPWl6O0ir})>f~+rM8k@!Q(EpWCD9f!k>me -zRhs@Q3_eZ!{s3QHUMaN0uShhn$y!N+)a -z6F#K#O21}#Wz2NGar1LUq3S|3=0~&VTjxVeqcysO1QIGwgglMcjXc3^9R9i556MW! -zA%G1+Y)mPX16RcVB#B?R~Xl -zIeR6G9EBEoU+Sk|=Q=4gQdT~j9ecG~G45m|E+IkhfuBxT`M%^wZkNkXpL0AjS!$%u -zg-={lr6QW@$p<#-f}T{y|0rAEpY~$9`Ffd=BP+`1#;?ge=@mLGkdmI~u?Dai2i+}> -zr-d}7M&xTHsK7@<3$tPd^Yg3fLzxDa!oOJ_N!hGt&$}5!9&TOIuzhIP>)^&CM1CIF -zY>A-293)fzq2lbB(1wKk=>#3=G1eTT&8(iBSJab=V@AGnDSwOhxKg{m8sa~TR||^x -zH?*-f-l|Zq;GkYEt~~O`56i(Z6gi`*^TxMZuc-f=NMlvry~%u2a>kz*@R^tJ@{7%-A@wfa)Z -z0~dL|Z*tTjEX7ED(M%2gZ|Zi_L4TWr%=BR=y4%;?-COo)8t(xaW)#f?Uhc9^d-1?K -z=UDW-cu8J#%~t1$>T7o{K5NXC7Z^ -z;*V>fuSp>3%L~xxUi%mG*w7fJS9FTtSX8!B_=C>_+{-q^3-7Yf_esz?&oN2)zkaPH -z=7a>>$VgQh6LDY?h_Px)L~(27=d%@0UX!M7G~G|aMRJuU!|xkIYM`l6jEi407=MtW -z4YA)qqP8SmCj;2g*%y+BObhJICRimEtC(yhX|B>fl9#O|OsP0h{YfJM(l{DjCSf;_ -zp3jZ#S5YfJ*b;u0&zd}UOl+UMYMCH3kOBnUGziK -zo__#J<(BObj|aew5%LI%9K>!}5UVhW*2b`jvkYQXN*iuj#{{Oatl}s!dyhU&jWEAS -zdKj4YrSTiJ_b6i{@5nk1r@W#B=YNdtri~y>StL>N%B6Gj6O_fwN*T-2>1Q1KuQAXE -zF|^na>CtL$iCMfa_^Bio0%XY3)>F9Uqzf-cky+Lb_H)#4=L+?00yMptx;^o9v}fkd -zWN%~1Sj%)#ggvPi=IpG67#q)qrgFi21qV^a=2vb1s|%}692RR2)zei^W-5JD7Y!4^ -zKfyt1dP(!slOA11Of$_QuYE4W{Uu+DO027{)!7+#Y;Ih{M{`=>H7?QlKD!&7BO7X`Q1_b071zS3lNp -z@&Iv~QHe@3e;q{$b7pE51fo%ee_qC4)CR?kSh?lvkF}D2UuE>>^dhlmktVcN14leO -zB6JLU^U}S?*F=`1q~sTqli@HC^RzgoXWg7-*R1R~2xL-K^`XjV%3c-JMf~;^ILFoK -z%NIwJAU)}8T@nI{XRp;rWaD7cuvC1dj!R2oZ(K-<6)PFEe1C`3{NVSlqPF<~-&+*O -z8>_z7E5JY^sY(1!jgGp<)OE9!*nx9!RX3U}7tvu5m2XXS(V8#x+t;nz?=xkI(DDsz -z;3foX=tMqziX%?kztoj_MYP1$@9}OUi0@Z>26`$9-KBzz0d+H_Z`PU9?gSA=7?H}0 -z{c+|*bet;11)bfWS<)JER^z_5PKJN$@9unBRX+W*oX^32ly -zKo%s>e!Q4Gb1DeiV*R&fzDz|t8*WBSo1ove3somHjybczT^qp^D^Tpx97n^9WuuqI -zTCFUlzMc<9(@Ng!L9ebpnk>0!&~jV#PR~Lz8p-9KECK6 -z;70`vR#D6@#_MoD+$Wl*AAbY|FVo2J6vOlP={WYAqT#$Q!S^VW9|! -zl45qcN$prx36zxggrb_z0Ri=i%$I(SJ6jHx(uR<;b(=zb>GDa-cZTt=EWQ$^+AKKp -z!qtgInkt}{k=PN-erHn(dHX?T{g44@;}a%oYTE7q*K<=mU`H~sCkX#6pyH?M+0W>N -zUr<(Whv)VbXit9iJY4V&;_6xGNVK9d_P*yRzW_7PgbR$4WPdDP8K}{V;@u;E04A#lw1fxY9qPYX%78 -z?2?m&UC)5IREFbfd%r<*8}KZbL-)2J(s!Ni>DER#ah4jLH0*2GPn -zFP?pTI`d2+=5;~B0pYU=P03Hk<$aGh?7&|CDV>N4L&S*{xjJ6{dn!jPj@;!U64ZH$ -zxcv;Jen{NFvhysj-qs<-RN>Q}{r7$Q(|cMV#FVvG1ygSsC^0)`=DwB(4Z7$pIxu3h -zGr}-!!|aUp$DUaVeC9=coIL@I)g|Fm7a7u6JRy|q_3SIEsEkSTn?R2}SIm-}g0D#x -zbFUgC%_08XTvJ@!3#F5}f?b~Rz6CpeASaHdWnNs?a*HD&&M#AO;^>?RDV?gRN(Q&_ -zi^f)H(H|$Fg#yiJBfM*7wTkz_6un^+@bW^qdO0rV -z#80Zot!buo$fS$UtrjI&1`2u(g>;~*Z@I;ZrS@=@)pCyAT-4)ZtWEg^;2QgIBuYCv -zCOT@SpdJ=?l}vNgolDevUl8e7VBri&69qGAFHzdn>vz;Z2PIH&X0IdJxq5&G|lD{8;FN) -zyoId^NoH`da-U0H$$EV_2u9QwI2NQ6xr#xsr4!7>PZTS-+^Ser28(?H>pkyr(t|p)Hl*Rcc?7hPzry4)mHP -zWLX9>Jcr0gs2&(aNg9b2@BIl*r~2X;kn=eI%P577nIX=auf8fXGcC+->CfU?wHoJM -z@{%ht0!KC%-tamvUWKvNx!08PvLF(w-EK-u?Lgd+WfX$LOYI=5t00MKD|Q)#@9mL5 -zWZQ?eQkgCCqwANoAm&K4|fw*k7ipV -zB1v2pR!seE-oV-2@pAb2ne;%k;&0+LB7z16@)VH1MO9)MHU9kSp)dCNVB91j?4mmO -zv3NP2%#IeX=+W7Ni#8IjoTpc(!3T*dt7!EX8VlAzQq6uvFcs%W{$71OuAvW8Wpseg+*PVYLv?WMN=C|H@$;E_S5fVp$L;GyupU7jZ$kfvC58X?uLqEZijH!v -HqBZh=dic2S - diff --git a/divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch b/divinemc-server/paper-patches/features/0002-Configuration.patch similarity index 64% rename from divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch rename to divinemc-server/paper-patches/features/0002-Configuration.patch index d720b57..f3d36e0 100644 --- a/divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch +++ b/divinemc-server/paper-patches/features/0002-Configuration.patch @@ -1,45 +1,45 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 16:19:01 +0300 -Subject: [PATCH] DivineMC Configuration +Date: Mon, 27 Jan 2025 20:53:24 +0300 +Subject: [PATCH] Configuration diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 472c649e08fa1fb5f050d178d1c49fbb1e5c6adf..0c03829de02f31a15b5f2686d03bf3977e3710cb 100644 +index 6a08a42acdb2ee24b5e403b15fb825f3cb49c968..c3c6e23b4da16025d0f6472290183732f5eb9880 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1104,6 +1104,7 @@ public final class CraftServer implements Server { +@@ -1103,6 +1103,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files -+ org.bxteam.divinemc.configuration.DivineConfig.init((File) console.options.valueOf("divinemc-settings")); // DivineMC - DivineMC config files ++ org.bxteam.divinemc.DivineConfig.init((File) console.options.valueOf("divinemc-settings")); // DivineMC - Configuration for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) -@@ -1120,6 +1121,7 @@ public final class CraftServer implements Server { +@@ -1119,6 +1120,7 @@ public final class CraftServer implements Server { } world.spigotConfig.init(); // Spigot world.purpurConfig.init(); // Purpur - Purpur config files -+ world.divinemcConfig.init(); // DivineMC - DivineMC config files ++ world.divineConfig.init(); // DivineMC - Configuration } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -3126,6 +3128,13 @@ public final class CraftServer implements Server { - return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); +@@ -3134,6 +3136,13 @@ public final class CraftServer implements Server { } + // Purpur end - Purpur config files -+ // DivineMC start - DivineMC configuration ++ // DivineMC start - Configuration + @Override + public YamlConfiguration getDivineConfig() { -+ return org.bxteam.divinemc.configuration.DivineConfig.config; ++ return org.bxteam.divinemc.DivineConfig.config; + } -+ // DivineMC end - DivineMC configuration ++ // DivineMC end - Configuration + - // Purpur start - Purpur config files @Override - public YamlConfiguration getPurpurConfig() { + public void restart() { + org.spigotmc.RestartCommand.restart(); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..8c619b7d72cb153a3204cb9e215f7f5de83e8347 100644 +index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..5eb36ddf8eea7a84299a91f28a031e2b750975ce 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -183,6 +183,15 @@ public class Main { @@ -47,13 +47,13 @@ index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..8c619b7d72cb153a3204cb9e215f7f5d .describedAs("Yml file"); // Purpur end - Purpur config files + -+ // DivineMC start - DivineMC config files -+ acceptsAll(asList("divinemc", "divinemc-settings"), "File for divinemc settings") ++ // DivineMC start - Configuration ++ acceptsAll(asList("divinemc", "divinemc-settings"), "File for DivineMC settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("divinemc.yml")) + .describedAs("Yml file"); -+ // DivineMC end - DivineMC config files ++ // DivineMC end - Configuration + // Paper start acceptsAll(asList("server-name"), "Name of the server") diff --git a/divinemc-server/paper-patches/features/0003-Delete-timings.patch b/divinemc-server/paper-patches/features/0003-Delete-timings.patch new file mode 100644 index 0000000..eb2dbc2 --- /dev/null +++ b/divinemc-server/paper-patches/features/0003-Delete-timings.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Mon, 27 Jan 2025 18:42:29 +0300 +Subject: [PATCH] Delete timings + + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 14949b9b0a14e4b6f38bf04a6246f181db2a7b3f..534510a94694d013fc11f6985088c6cf14dbb695 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -1,6 +1,5 @@ + package io.papermc.paper.plugin.manager; + +-import co.aikar.timings.TimedEventExecutor; + import com.destroystokyo.paper.event.server.ServerExceptionEvent; + import com.destroystokyo.paper.exception.ServerEventException; + import com.google.common.collect.Sets; +@@ -102,7 +101,6 @@ class PaperEventManager { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + +- executor = new TimedEventExecutor(executor, plugin, null, event); + this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } + +@@ -189,7 +187,7 @@ class PaperEventManager { + } + } + +- EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); ++ EventExecutor executor = EventExecutor.create(method, eventClass); // DivineMC - Delete timings + eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } + return ret; +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +index 097500a59336db1bbfffcd1aa4cff7a8586e46ec..69341cb3b11409e41b9ff756b11d9bd1b9e6da10 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +@@ -232,7 +232,7 @@ public class PaperPluginManagerImpl implements PluginManager, DependencyContext + + @Override + public boolean useTimings() { +- return co.aikar.timings.Timings.isTimingsEnabled(); ++ return false; // DivineMC - Delete timings + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c3c6e23b4da16025d0f6472290183732f5eb9880..652acceb96843e8242a0989518dec5c65fbcf953 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1048,10 +1048,8 @@ public final class CraftServer implements Server { + commands.performCommand(results, commandLine, commandLine, true); + } catch (CommandException ex) { + this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper +- //target.timings.stopTiming(); // Spigot // Paper + throw ex; + } catch (Throwable ex) { +- //target.timings.stopTiming(); // Spigot // Paper + String msg = "Unhandled exception executing '" + commandLine + "' in " + target; + this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper + throw new CommandException(msg, ex); diff --git a/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch b/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch deleted file mode 100644 index 2129618..0000000 --- a/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch +++ /dev/null @@ -1,377 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 11 Jan 2025 23:18:11 +0300 -Subject: [PATCH] Optimize default values for configs - - -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 42777adb028fe282c1619aeb5431c442ad5df0d0..3afcf93d1e9519577ca9b6974f23f2258ecaad3e 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -343,9 +343,9 @@ public class GlobalConfiguration extends ConfigurationPart { - public boolean fixEntityPositionDesync = true; - public boolean loadPermissionsYmlBeforePlugins = true; - @Constraints.Min(4) -- public int regionFileCacheSize = 256; -+ public int regionFileCacheSize = 512; // DivineMC - Optimize default values for configs - @Comment("See https://luckformula.emc.gs") -- public boolean useAlternativeLuckFormula = false; -+ public boolean useAlternativeLuckFormula = true; // DivineMC - Optimize default values for configs - public boolean useDimensionTypeForCustomSpawners = false; - public boolean strictAdvancementDimensionCheck = false; - public IntOr.Default compressionLevel = IntOr.Default.USE_DEFAULT; -diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -index a426ba82af695426952bb5e04fa721e6ccff2f89..6ae624f873c77625c7ff9a1b94ff015cc6d321f0 100644 ---- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -145,9 +145,9 @@ public class WorldConfiguration extends ConfigurationPart { - - public ArmorStands armorStands; - -- public class ArmorStands extends ConfigurationPart { -- public boolean doCollisionEntityLookups = true; -- public boolean tick = true; -+ public class ArmorStands extends ConfigurationPart { // DivineMC - optimize default values for configs -+ public boolean doCollisionEntityLookups = false; -+ public boolean tick = false; - } - - public Markers markers; -@@ -270,8 +270,8 @@ public class WorldConfiguration extends ConfigurationPart { - public Behavior behavior; - - public class Behavior extends ConfigurationPart { -- public boolean disableChestCatDetection = false; -- public boolean spawnerNerfedMobsShouldJump = false; -+ public boolean disableChestCatDetection = true; // DivineMC - Optimize default values for configs -+ public boolean spawnerNerfedMobsShouldJump = true; // DivineMC - Optimize default values for configs - public int experienceMergeMaxValue = -1; - public boolean shouldRemoveDragon = false; - public boolean zombiesTargetTurtleEggs = true; -@@ -295,7 +295,7 @@ public class WorldConfiguration extends ConfigurationPart { - public int playerInsomniaStartTicks = 72000; - public int phantomsSpawnAttemptMinSeconds = 60; - public int phantomsSpawnAttemptMaxSeconds = 119; -- public boolean parrotsAreUnaffectedByPlayerMovement = false; -+ public boolean parrotsAreUnaffectedByPlayerMovement = true; // DivineMC - Optimize default values for configs - @BelowZeroToEmpty - public DoubleOr.Default zombieVillagerInfectionChance = DoubleOr.Default.USE_DEFAULT; - public MobsCanAlwaysPickUpLoot mobsCanAlwaysPickUpLoot; -@@ -306,7 +306,7 @@ public class WorldConfiguration extends ConfigurationPart { - } - - public boolean disablePlayerCrits = false; -- public boolean nerfPigmenFromNetherPortals = false; -+ public boolean nerfPigmenFromNetherPortals = true; // DivineMC - Optimize default values for configs - @Comment("Prevents merging items that are not on the same y level, preventing potential visual artifacts.") - public boolean onlyMergeItemsHorizontally = false; - public PillagerPatrols pillagerPatrols; -@@ -404,7 +404,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class Environment extends ConfigurationPart { - public boolean disableThunder = false; - public boolean disableIceAndSnow = false; -- public boolean optimizeExplosions = false; -+ public boolean optimizeExplosions = true; // DivineMC - Optimize default values for configs - public boolean disableExplosionKnockback = false; - public boolean generateFlatBedrock = false; - public FrostedIce frostedIce; -@@ -453,7 +453,7 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Maps extends ConfigurationPart { - public int itemFrameCursorLimit = 128; -- public int itemFrameCursorUpdateInterval = 10; -+ public int itemFrameCursorUpdateInterval = 20; // DivineMC - Optimize default values for configs - } - - public Fixes fixes; -@@ -479,7 +479,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class Hopper extends ConfigurationPart { - public boolean cooldownWhenFull = true; - public boolean disableMoveEvent = false; -- public boolean ignoreOccludingBlocks = false; -+ public boolean ignoreOccludingBlocks = true; // DivineMC - Optimize default values for configs - } - - public Collisions collisions; -@@ -487,9 +487,9 @@ public class WorldConfiguration extends ConfigurationPart { - public class Collisions extends ConfigurationPart { - public boolean onlyPlayersCollide = false; - public boolean allowVehicleCollisions = true; -- public boolean fixClimbingBypassingCrammingRule = false; -+ public boolean fixClimbingBypassingCrammingRule = true; // DivineMC - Optimize default values for configs - @RequiresSpigotInitialization(MaxEntityCollisionsInitializer.class) -- public int maxEntityCollisions = 8; -+ public int maxEntityCollisions = 2; // DivineMC - Optimize default values for configs - public boolean allowPlayerCrammingDamage = false; - } - -@@ -497,18 +497,31 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Chunks extends ConfigurationPart { - public AutosavePeriod autoSaveInterval = AutosavePeriod.def(); -- public int maxAutoSaveChunksPerTick = 24; -+ public int maxAutoSaveChunksPerTick = 12; // DivineMC - Optimize default values for configs - public int fixedChunkInhabitedTime = -1; -- public boolean preventMovingIntoUnloadedChunks = false; -- public Duration delayChunkUnloadsBy = Duration.of("10s"); -+ public boolean preventMovingIntoUnloadedChunks = true; -+ public Duration delayChunkUnloadsBy = Duration.of("5s"); - public Reference2IntMap> entityPerChunkSaveLimit = Util.make(new Reference2IntOpenHashMap<>(BuiltInRegistries.ENTITY_TYPE.size()), map -> { -- map.defaultReturnValue(-1); -- map.put(EntityType.EXPERIENCE_ORB, -1); -- map.put(EntityType.SNOWBALL, -1); -- map.put(EntityType.ENDER_PEARL, -1); -- map.put(EntityType.ARROW, -1); -- map.put(EntityType.FIREBALL, -1); -- map.put(EntityType.SMALL_FIREBALL, -1); -+ // DivineMC start - Optimize default values for configs -+ map.put(EntityType.EXPERIENCE_ORB, 16); -+ map.put(EntityType.SNOWBALL, 8); -+ map.put(EntityType.ENDER_PEARL, 8); -+ map.put(EntityType.ARROW, 16); -+ map.put(EntityType.FIREBALL, 8); -+ map.put(EntityType.SMALL_FIREBALL, 8); -+ map.put(EntityType.DRAGON_FIREBALL, 3); -+ map.put(EntityType.EGG, 8); -+ map.put(EntityType.EYE_OF_ENDER, 8); -+ map.put(EntityType.FIREWORK_ROCKET, 8); -+ map.put(EntityType.POTION, 8); -+ map.put(EntityType.LLAMA_SPIT, 3); -+ map.put(EntityType.SHULKER_BULLET, 8); -+ map.put(EntityType.SPECTRAL_ARROW, 16); -+ map.put(EntityType.EXPERIENCE_BOTTLE, 3); -+ map.put(EntityType.TRIDENT, 16); -+ map.put(EntityType.WITHER_SKULL, 4); -+ map.put(EntityType.AREA_EFFECT_CLOUD, 8); -+ // DivineMC end - }); - public boolean flushRegionsOnSave = false; - } -@@ -523,13 +536,13 @@ public class WorldConfiguration extends ConfigurationPart { - public TickRates tickRates; - - public class TickRates extends ConfigurationPart { -- public int grassSpread = 1; -- public int containerUpdate = 1; -- public int mobSpawner = 1; -+ public int grassSpread = 4; // DivineMC - Optimize default values for configs -+ public int containerUpdate = 3; // DivineMC - Optimize default values for configs -+ public int mobSpawner = 2; // DivineMC - Optimize default values for configs - public int wetFarmland = 1; - public int dryFarmland = 1; -- public Table, String, Integer> sensor = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "secondarypoisensor", 40)); -- public Table, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", -1)); -+ public Table, String, Integer> sensor = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "secondarypoisensor", 80)); // DivineMC - Optimize default values for configs -+ public Table, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", 60)); // DivineMC - Optimize default values for configs - } - - @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY) -@@ -538,7 +551,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class FeatureSeeds extends ConfigurationPart { - @SuppressWarnings("unused") // Is used in FeatureSeedsGeneration - @Setting(FeatureSeedsGeneration.GENERATE_KEY) -- public boolean generateRandomSeedsForAll = false; -+ public boolean generateRandomSeedsForAll = true; // DivineMC - Optimize default values for configs - @Setting(FeatureSeedsGeneration.FEATURES_KEY) - public Reference2LongMap>> features = new Reference2LongOpenHashMap<>(); - -@@ -559,9 +572,9 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Misc extends ConfigurationPart { - public int lightQueueSize = 20; -- public boolean updatePathfindingOnBlockUpdate = true; -+ public boolean updatePathfindingOnBlockUpdate = false; // DivineMC - Optimize default values for configs - public boolean showSignClickCommandFailureMsgsToPlayer = false; -- public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; -+ public RedstoneImplementation redstoneImplementation = RedstoneImplementation.ALTERNATE_CURRENT; // DivineMC - Optimize default values for configs - public AlternateCurrentUpdateOrder alternateCurrentUpdateOrder = AlternateCurrentUpdateOrder.HORIZONTAL_FIRST_OUTWARD; - public boolean disableEndCredits = false; - public DoubleOr.Default maxLeashDistance = DoubleOr.Default.USE_DEFAULT; -diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index e0d4222a99f22d7130d95cf29b034a98f2f3b76e..ecedb1ba79fa40180ff7eb16d12a4602ab357de3 100644 ---- a/src/main/java/org/spigotmc/SpigotConfig.java -+++ b/src/main/java/org/spigotmc/SpigotConfig.java -@@ -269,7 +269,7 @@ public class SpigotConfig { - - public static boolean saveUserCacheOnStopOnly; - private static void saveUserCacheOnStopOnly() { -- SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", false); -+ SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", true); // DivineMC - Optimize default values for configs - } - - public static double movedWronglyThreshold; -@@ -323,9 +323,9 @@ public class SpigotConfig { - - public static boolean logVillagerDeaths; - public static boolean logNamedDeaths; -- private static void logDeaths() { -- SpigotConfig.logVillagerDeaths = SpigotConfig.getBoolean("settings.log-villager-deaths", true); -- SpigotConfig.logNamedDeaths = SpigotConfig.getBoolean("settings.log-named-deaths", true); -+ private static void logDeaths() { // DivineMC - Optimize default values for configs -+ SpigotConfig.logVillagerDeaths = SpigotConfig.getBoolean("settings.log-villager-deaths", false); -+ SpigotConfig.logNamedDeaths = SpigotConfig.getBoolean("settings.log-named-deaths", false); - } - - public static boolean disablePlayerDataSaving; -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 89e2adbc1e1a0709d03e151e3ffcdbff10a44098..5b305092a808c2b9b339b9072bf7f7bfc00f0b8a 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -135,13 +135,13 @@ public class SpigotWorldConfig { - - public double itemMerge; - private void itemMerge() { -- this.itemMerge = this.getDouble("merge-radius.item", 0.5); -+ this.itemMerge = this.getDouble("merge-radius.item", 3.5); // DivineMC - Optimize default values for configs - this.log("Item Merge Radius: " + this.itemMerge); - } - - public double expMerge; - private void expMerge() { -- this.expMerge = this.getDouble("merge-radius.exp", -1); -+ this.expMerge = this.getDouble("merge-radius.exp", 4.0); // DivineMC - Optimize default values for configs - this.log("Experience Merge Radius: " + this.expMerge); - } - -@@ -174,7 +174,7 @@ public class SpigotWorldConfig { - - public byte mobSpawnRange; - private void mobSpawnRange() { -- this.mobSpawnRange = (byte) getInt("mob-spawn-range", 8); // Paper - Vanilla -+ this.mobSpawnRange = (byte) getInt("mob-spawn-range", 2); // DivineMC - Optimize default values for configs - this.log("Mob Spawn Range: " + this.mobSpawnRange); - } - -@@ -184,14 +184,16 @@ public class SpigotWorldConfig { - this.log("Item Despawn Rate: " + this.itemDespawnRate); - } - -- public int animalActivationRange = 32; -- public int monsterActivationRange = 32; -- public int raiderActivationRange = 64; -- public int miscActivationRange = 16; -+ // DivineMC start - Optimize default values for configs -+ public int animalActivationRange = 16; -+ public int monsterActivationRange = 24; -+ public int raiderActivationRange = 48; -+ public int miscActivationRange = 8; -+ // DivineMC end - Optimize default values for configs - // Paper start - public int flyingMonsterActivationRange = 32; -- public int waterActivationRange = 16; -- public int villagerActivationRange = 32; -+ public int waterActivationRange = 8; // DivineMC - Optimize default values for configs -+ public int villagerActivationRange = 16; // DivineMC - Optimize default values for configs - public int wakeUpInactiveAnimals = 4; - public int wakeUpInactiveAnimalsEvery = 60 * 20; - public int wakeUpInactiveAnimalsFor = 5 * 20; -@@ -208,7 +210,7 @@ public class SpigotWorldConfig { - public int villagersWorkImmunityFor = 20; - public boolean villagersActiveForPanic = true; - // Paper end -- public boolean tickInactiveVillagers = true; -+ public boolean tickInactiveVillagers = false; // DivineMC - Optimize default values for configs - public boolean ignoreSpectatorActivation = false; - - private void activationRange() { -@@ -273,7 +275,7 @@ public class SpigotWorldConfig { - if (SpigotConfig.version < 11) { - this.set("ticks-per.hopper-check", 1); - } -- this.hopperCheck = this.getInt("ticks-per.hopper-check", 1); -+ this.hopperCheck = this.getInt("ticks-per.hopper-check", 8); // DivineMC - Optimize default values for configs - this.hopperAmount = this.getInt("hopper-amount", 1); - this.hopperCanLoadChunks = this.getBoolean("hopper-can-load-chunks", false); - this.log("Hopper Transfer: " + this.hopperTransfer + " Hopper Check: " + this.hopperCheck + " Hopper Amount: " + this.hopperAmount + " Hopper Can Load Chunks: " + this.hopperCanLoadChunks); -@@ -282,7 +284,7 @@ public class SpigotWorldConfig { - public int arrowDespawnRate; - public int tridentDespawnRate; - private void arrowDespawnRate() { -- this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 1200); -+ this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 300); // DivineMC - Optimize default values for configs - this.tridentDespawnRate = this.getInt("trident-despawn-rate", this.arrowDespawnRate); - this.log("Arrow Despawn Rate: " + this.arrowDespawnRate + " Trident Respawn Rate:" + this.tridentDespawnRate); - } -@@ -295,13 +297,13 @@ public class SpigotWorldConfig { - - public boolean nerfSpawnerMobs; - private void nerfSpawnerMobs() { -- this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", false); -+ this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", true); // DivineMC - Optimize default values for configs - this.log("Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs); - } - - public boolean enableZombiePigmenPortalSpawns; - private void enableZombiePigmenPortalSpawns() { -- this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", true); -+ this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", false); // DivineMC - Optimize default values for configs - this.log("Allow Zombie Pigmen to spawn from portal blocks: " + this.enableZombiePigmenPortalSpawns); - } - -@@ -413,7 +415,7 @@ public class SpigotWorldConfig { - - public int hangingTickFrequency; - private void hangingTickFrequency() { -- this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 100); -+ this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 200); // DivineMC - Optimize default values for configs - } - - public int tileMaxTickTime; -diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml -index eef7c125b2689f29cae5464659eacdf33f5695b2..c6b04acb5371a0ac454c5e377bccad5b0972aed8 100644 ---- a/src/main/resources/configurations/bukkit.yml -+++ b/src/main/resources/configurations/bukkit.yml -@@ -18,28 +18,28 @@ settings: - update-folder: update - plugin-profiling: false - connection-throttle: 4000 -- query-plugins: true -+ query-plugins: false - deprecated-verbose: default - shutdown-message: Server closed - minimum-api: none - use-map-color-cache: true - spawn-limits: -- monsters: 70 -- animals: 10 -- water-animals: 5 -- water-ambient: 20 -- water-underground-creature: 5 -- axolotls: 5 -- ambient: 15 -+ monsters: 20 -+ animals: 5 -+ water-animals: 2 -+ water-ambient: 2 -+ water-underground-creature: 3 -+ axolotls: 3 -+ ambient: 1 - chunk-gc: -- period-in-ticks: 600 -+ period-in-ticks: 400 - ticks-per: - animal-spawns: 400 -- monster-spawns: 1 -- water-spawns: 1 -- water-ambient-spawns: 1 -- water-underground-creature-spawns: 1 -- axolotl-spawns: 1 -- ambient-spawns: 1 -+ monster-spawns: 10 -+ water-spawns: 400 -+ water-ambient-spawns: 400 -+ water-underground-creature-spawns: 400 -+ axolotl-spawns: 400 -+ ambient-spawns: 400 - autosave: 6000 - aliases: now-in-commands.yml -diff --git a/src/main/resources/configurations/commands.yml b/src/main/resources/configurations/commands.yml -index 18f54571200e2eca09a39b88f170fe7b99d8618f..1b57d51d92c5c69286800d10baeaa936fa208cae 100644 ---- a/src/main/resources/configurations/commands.yml -+++ b/src/main/resources/configurations/commands.yml -@@ -12,5 +12,3 @@ - command-block-overrides: [] - ignore-vanilla-permissions: false - aliases: -- icanhasbukkit: -- - "version $1-" diff --git a/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch b/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch new file mode 100644 index 0000000..1f343f3 --- /dev/null +++ b/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 00:54:57 +0300 +Subject: [PATCH] Implement Secure Seed + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index de8b9048c8395c05b8688bc9d984b8ad680f15b3..98bd60111797225f3be5e2a19e25d654379ca30d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -206,7 +206,12 @@ public class CraftChunk implements Chunk { + @Override + public boolean isSlimeChunk() { + // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk +- return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper ++ // DivineMC start - Implement Secure Seed ++ boolean isSlimeChunk = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? worldServer.getChunk(this.getX(), this.getZ()).isSlimeChunk() ++ : WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper ++ return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; ++ // DivineMC end - Implement Secure Seed + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 652acceb96843e8242a0989518dec5c65fbcf953..be2859b2bb31bdf342c1e8fb14ccac8b6d215439 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1402,7 +1402,11 @@ public final class CraftServer implements Server { + registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess(); + } else { + LevelSettings levelSettings; +- WorldOptions worldOptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // DivineMC start - Implement Secure Seed ++ WorldOptions worldOptions = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new WorldOptions(creator.seed(), su.plo.matter.Globals.createRandomWorldSeed(), creator.generateStructures(), false) ++ : new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // DivineMC end - Implement Secure Seed + WorldDimensions worldDimensions; + + DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); diff --git a/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch b/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch deleted file mode 100644 index f1c0c6a..0000000 --- a/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 01:47:27 +0300 -Subject: [PATCH] Remove Spigot tick limiter - - -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 5b305092a808c2b9b339b9072bf7f7bfc00f0b8a..c9ff04efcdf063326119b9f65630979d38dc7abf 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -418,6 +418,7 @@ public class SpigotWorldConfig { - this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 200); // DivineMC - Optimize default values for configs - } - -+ /* DivineMC - remove tick limiter - public int tileMaxTickTime; - public int entityMaxTickTime; - private void maxTickTimes() { -@@ -425,6 +426,7 @@ public class SpigotWorldConfig { - this.entityMaxTickTime = this.getInt("max-tick-time.entity", 50); - this.log("Tile Max Tick Time: " + this.tileMaxTickTime + "ms Entity max Tick Time: " + this.entityMaxTickTime + "ms"); - } -+ */ - - public int thunderChance; - private void thunderChance() { -diff --git a/src/main/java/org/spigotmc/TickLimiter.java b/src/main/java/org/spigotmc/TickLimiter.java -deleted file mode 100644 -index 961489499e220d71339771dcabf151edeaf6d231..0000000000000000000000000000000000000000 ---- a/src/main/java/org/spigotmc/TickLimiter.java -+++ /dev/null -@@ -1,20 +0,0 @@ --package org.spigotmc; -- --public class TickLimiter { -- -- private final int maxTime; -- private long startTime; -- -- public TickLimiter(int maxTime) { -- this.maxTime = maxTime; -- } -- -- public void initTick() { -- this.startTime = System.currentTimeMillis(); -- } -- -- public boolean shouldContinue() { -- long remaining = System.currentTimeMillis() - this.startTime; -- return remaining < this.maxTime; -- } --} diff --git a/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch new file mode 100644 index 0000000..a2bdc34 --- /dev/null +++ b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch @@ -0,0 +1,613 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 01:41:25 +0300 +Subject: [PATCH] Parallel world ticking + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09357f2ef583af04f6b8dc5ba67ef7e1d83e3462 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; + public class TickThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); ++ public static final boolean HARD_THROW = !Boolean.getBoolean("divinemc.disableHardThrow"); // DivineMC - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION! + + private static String getThreadContext() { + return "thread=" + Thread.currentThread().getName(); +@@ -26,14 +27,14 @@ public class TickThread extends Thread { + public static void ensureTickThread(final String reason) { + if (!isTickThread()) { + LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); +- throw new IllegalStateException(reason); ++ if (HARD_THROW) throw new IllegalStateException(reason); // DivineMC - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION! + } + } + + public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { + if (!isTickThreadFor(world, pos)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -42,7 +43,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) { + if (!isTickThreadFor(world, pos, blockRadius)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -60,7 +61,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { + if (!isTickThreadFor(world, chunkX, chunkZ)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -69,7 +70,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Entity entity, final String reason) { + if (!isTickThreadFor(entity)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); ++ reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -78,7 +79,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { + if (!isTickThreadFor(world, aabb)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -87,12 +88,69 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { + if (!isTickThreadFor(world, blockX, blockZ)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ); ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } + } + ++ // DivineMC start - parallel world ticking ++ public static void ensureTickThread(final net.minecraft.server.level.ServerLevel world, final String reason) { ++ if (!isTickThreadFor(world)) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureOnlyTickThread(final String reason) { ++ boolean isTickThread = isTickThread(); ++ boolean isServerLevelTickThread = isServerLevelTickThread(); ++ if (!isTickThread || isServerLevelTickThread) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureTickThreadOrAsyncThread(final net.minecraft.server.level.ServerLevel world, final String reason) { ++ boolean isValidTickThread = isTickThreadFor(world); ++ boolean isAsyncThread = !isTickThread(); ++ boolean isValid = isAsyncThread || isValidTickThread; ++ if (!isValid) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { ++ StringBuilder sb = new StringBuilder(); ++ Thread currentThread = Thread.currentThread(); ++ sb.append("Is tick thread? "); ++ sb.append(currentThread instanceof TickThread); ++ sb.append("; Is server level tick thread? "); ++ sb.append(currentThread instanceof ServerLevelTickThread); ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ sb.append("; Currently ticking level: "); ++ if (serverLevelTickThread.currentlyTickingServerLevel != null) { ++ sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName()); ++ } else { ++ sb.append("null"); ++ } ++ } ++ sb.append("; Is iterating over levels? "); ++ sb.append(minecraftServer.isIteratingOverLevels); ++ sb.append("; Are we going to hard throw? "); ++ sb.append(HARD_THROW); ++ return sb.toString(); ++ } ++ ++ public static boolean isServerLevelTickThread() { ++ return Thread.currentThread() instanceof ServerLevelTickThread; ++ } ++ // DivineMC end - parallel world ticking ++ + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); +@@ -126,8 +184,13 @@ public class TickThread extends Thread { + return false; + } + ++ // DivineMC start - parallel world ticking + public static boolean isTickThreadFor(final Level world, final BlockPos pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { +@@ -135,38 +198,99 @@ public class TickThread extends Thread { + } + + public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final Vec3 pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final AABB aabb) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; ++ } ++ ++ public static boolean isTickThreadFor(final Level world) { ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Entity entity) { +- return isTickThread(); ++ if (entity == null) { ++ return true; ++ } ++ ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == entity.level(); ++ } else return currentThread instanceof TickThread; ++ } ++ ++ public static class ServerLevelTickThread extends TickThread { ++ public ServerLevelTickThread(String name) { ++ super(name); ++ } ++ ++ public ServerLevelTickThread(Runnable run, String name) { ++ super(run, name); ++ } ++ ++ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; + } ++ // DivineMC end - parallel world ticking + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ca60f91ef012c94174a0803eb77699ba9ecff5e1..35afd7268a6f8c5c9d0da751459867c1d8e404bf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -446,7 +446,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean unloadChunkRequest(int x, int z) { +- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + if (this.isChunkLoaded(x, z)) { + this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); + } +@@ -472,6 +472,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean refreshChunk(int x, int z) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +@@ -522,7 +523,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean loadChunk(int x, int z, boolean generate) { +- org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + +@@ -750,6 +751,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + this.world.captureTreeGeneration = true; + this.world.captureBlockStates = true; + boolean grownTree = this.generateTree(loc, type); +@@ -865,6 +867,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { + // Paper end - expand explosion API ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + net.minecraft.world.level.Level.ExplosionInteraction explosionType; + if (!breakBlocks) { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks +@@ -956,6 +959,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); +@@ -986,6 +990,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public void setBiome(int x, int y, int z, Holder bb) { + BlockPos pos = new BlockPos(x, 0, z); ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + if (this.world.hasChunkAt(pos)) { + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); + +@@ -2289,6 +2294,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); + } + // Paper end +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b330450fbc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -75,6 +75,11 @@ public class CraftBlock implements Block { + } + + public net.minecraft.world.level.block.state.BlockState getNMS() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.world.getBlockState(this.position); + } + +@@ -157,6 +162,11 @@ public class CraftBlock implements Block { + } + + private void setData(final byte data, int flag) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); + } + +@@ -198,6 +208,11 @@ public class CraftBlock implements Block { + } + + public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup + if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes + // SPIGOT-4612: faster - just clear tile +@@ -343,18 +358,33 @@ public class CraftBlock implements Block { + + @Override + public Biome getBiome() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + + // Paper start + @Override + public Biome getComputedBiome() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); + } + // Paper end + + @Override + public void setBiome(Biome bio) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); + } + +@@ -402,6 +432,11 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); + + Block relative = this.getRelative(face); +@@ -414,6 +449,11 @@ public class CraftBlock implements Block { + + @Override + public int getBlockPower(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + int power = 0; + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); + int x = this.getX(); +@@ -484,6 +524,11 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.breakNaturally(null); + } + +@@ -543,6 +588,11 @@ public class CraftBlock implements Block { + + @Override + public boolean applyBoneMeal(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Direction direction = CraftBlock.blockFaceToNotch(face); + BlockFertilizeEvent event = null; + ServerLevel world = this.getCraftWorld().getHandle(); +@@ -554,8 +604,10 @@ public class CraftBlock implements Block { + world.captureTreeGeneration = false; + + if (world.capturedBlockStates.size() > 0) { +- TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ TreeType treeType = SaplingBlock.treeTypeRT.get(); ++ SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + List blocks = new ArrayList<>(world.capturedBlockStates.values()); + world.capturedBlockStates.clear(); + StructureGrowEvent structureEvent = null; +@@ -644,6 +696,11 @@ public class CraftBlock implements Block { + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Preconditions.checkArgument(start != null, "Location start cannot be null"); + Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); + start.checkFinite(); +@@ -685,6 +742,11 @@ public class CraftBlock implements Block { + + @Override + public boolean canPlace(BlockData data) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Preconditions.checkArgument(data != null, "BlockData cannot be null"); + net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); +@@ -719,6 +781,11 @@ public class CraftBlock implements Block { + + @Override + public void tick() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + final ServerLevel level = this.world.getMinecraftWorld(); + this.getNMS().tick(level, this.position, level.random); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 768d3f93da2522d467183654260a8bd8653588b1..762bb2827dc1c0c0649a4cb3d8b0c8c0c9ea95d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState extends Craft + private final T tileEntity; + private final T snapshot; + public boolean snapshotDisabled; // Paper +- public static boolean DISABLE_SNAPSHOT = false; // Paper ++ public static ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + + public CraftBlockEntityState(World world, T tileEntity) { + super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); +@@ -34,8 +34,10 @@ public abstract class CraftBlockEntityState extends Craft + + try { // Paper - Show blockstate location if we failed to read it + // Paper start +- this.snapshotDisabled = DISABLE_SNAPSHOT; +- if (DISABLE_SNAPSHOT) { ++ // DivineMC start - parallel world ticking ++ this.snapshotDisabled = DISABLE_SNAPSHOT.get(); ++ if (DISABLE_SNAPSHOT.get()) { ++ // DivineMC end - parallel world ticking + this.snapshot = this.tileEntity; + } else { + this.snapshot = this.createSnapshot(tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..951e47811e861dffd59cc39e2dcd6fd68900fc72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState { + LevelAccessor access = this.getWorldHandle(); + CraftBlock block = this.getBlock(); + ++ // DivineMC start - parallel world ticking ++ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking ++ + if (block.getType() != this.getType()) { + if (!force) { + return false; +@@ -350,6 +356,7 @@ public class CraftBlockState implements BlockState { + + @Override + public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking + this.requirePlaced(); + net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..cfc4237211b994a222983a2d1f879cb0f515b581 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -249,8 +249,8 @@ public final class CraftBlockStates { + net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); + BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); + // Paper start - block state snapshots +- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; +- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; ++ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get(); // DivineMC - parallel world ticking ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); // DivineMC - parallel world ticking + try { + // Paper end + CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); +@@ -258,7 +258,7 @@ public final class CraftBlockStates { + return blockState; + // Paper start + } finally { +- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // DivineMC - parallel world ticking + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 822ee4a2515ad1d4400bafeaf7177622e88b4aaf..69c44017e7ca2861200c83a16fa9dacaa822d505 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -960,7 +960,7 @@ public class CraftEventFactory { + return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); + } + +- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // DivineMC - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts) + + public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { + // Suppress during worldgen +@@ -972,7 +972,7 @@ public class CraftEventFactory { + CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); + state.setData(block); + +- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // DivineMC - parallel world ticking + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +@@ -2242,7 +2242,7 @@ public class CraftEventFactory { + CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); +- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + if (!event.callEvent()) { + return itemStack; + } diff --git a/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch new file mode 100644 index 0000000..346e7cf --- /dev/null +++ b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:10:42 +0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + +Original project: https://github.com/SparklyPower/SparklyPaper + +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +index c03608fec96b51e1867f43d8f42e5aefb1520e46..eda35b81c36ca8ebe4f9487cb41e2b0c4cbfc686 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java ++++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +@@ -36,6 +36,7 @@ public final class EntityScheduler { + * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. + */ + public final CraftEntity entity; ++ public final net.minecraft.server.MinecraftServer server; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + private static final record ScheduledTask(Consumer run, Consumer retired) {} + +@@ -46,7 +47,8 @@ public final class EntityScheduler { + + private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); + +- public EntityScheduler(final CraftEntity entity) { ++ public EntityScheduler(final net.minecraft.server.MinecraftServer server, final CraftEntity entity) { // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ this.server = Validate.notNull(server); + this.entity = Validate.notNull(entity); + } + +@@ -61,15 +63,15 @@ public final class EntityScheduler { + * @throws IllegalStateException If the scheduler is already retired. + */ + public void retire() { ++ final Entity thisEntity = this.entity.getHandleRaw(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + synchronized (this.stateLock) { + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Already retired"); + } + this.tickCount = RETIRED_TICK_COUNT; ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + +- final Entity thisEntity = this.entity.getHandleRaw(); +- + // correctly handle and order retiring while running executeTick + for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { + final ScheduledTask task = this.currentlyExecuting.pollFirst(); +@@ -124,6 +126,7 @@ public final class EntityScheduler { + if (this.tickCount == RETIRED_TICK_COUNT) { + return false; + } ++ this.server.entitiesWithScheduledTasks.add(this.entity.getHandleRaw()); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { + return new ArrayList<>(); + }).add(task); +@@ -143,6 +146,12 @@ public final class EntityScheduler { + TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); + final List toRun; + synchronized (this.stateLock) { ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ if (this.currentlyExecuting.isEmpty() && this.oneTimeDelayed.isEmpty()) { ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); ++ return; ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Ticking retired scheduler"); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 8635cd772c5c2ae0ba326812ff2a1a179285a86f..614e407814fe47dab58fbcbc49d8e9dd54b4245e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -75,7 +75,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); + protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + // Paper start - Folia shedulers +- public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); ++ public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); + + @Override +@@ -88,6 +88,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.server = server; + this.entity = entity; + this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); ++ this.taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this.entity.getServer(), this); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + + // Purpur start - Fire Immunity API diff --git a/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch b/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch new file mode 100644 index 0000000..161fad0 --- /dev/null +++ b/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:52:39 +0300 +Subject: [PATCH] Optimize canSee checks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 36fc0ec6e21af31e10f63b6bb3952008530b8f81..e744c4a13092f20a591d092fe537f790d23d1db1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -214,7 +214,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private boolean hasPlayedBefore = false; + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new HashSet(); +- private final Map>> invertedVisibilityEntities = new HashMap<>(); ++ private final Map>> invertedVisibilityEntities = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // DivineMC - optimize canSee checks + private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; +@@ -2270,9 +2270,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(org.bukkit.entity.Entity entity) { +- return this.equals(entity) || entity.isVisibleByDefault() ^ this.invertedVisibilityEntities.containsKey(entity.getUniqueId()); // SPIGOT-7312: Can always see self ++ return this.equals(entity) || entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); // SPIGOT-7312: Can always see self // DivineMC - optimize canSee checks + } + ++ // DivineMC start - optimize canSee checks ++ public boolean canSeeChunkMapUpdatePlayer(org.bukkit.entity.Entity entity) { ++ return entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); // SPIGOT-7312: Can always see self // SparklyPaper - optimize canSee checks ++ } ++ // DivineMC end - optimize canSee checks ++ + public boolean canSeePlayer(UUID uuid) { + org.bukkit.entity.Entity entity = this.getServer().getPlayer(uuid); + diff --git a/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch b/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch new file mode 100644 index 0000000..f0f4e07 --- /dev/null +++ b/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 5 Feb 2025 17:48:56 +0300 +Subject: [PATCH] Verify Minecraft EULA earlier + + +diff --git a/src/main/java/io/papermc/paper/PaperBootstrap.java b/src/main/java/io/papermc/paper/PaperBootstrap.java +index d543b1b107ab8d3eeb1fc3c1cadf489928d2786e..b25afd2a33a61cfbe3dabe65a26aca0669329e32 100644 +--- a/src/main/java/io/papermc/paper/PaperBootstrap.java ++++ b/src/main/java/io/papermc/paper/PaperBootstrap.java +@@ -1,8 +1,11 @@ + package io.papermc.paper; + ++import java.nio.file.Path; ++import java.nio.file.Paths; + import java.util.List; + import joptsimple.OptionSet; + import net.minecraft.SharedConstants; ++import net.minecraft.server.Eula; + import net.minecraft.server.Main; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; +@@ -16,6 +19,22 @@ public final class PaperBootstrap { + public static void boot(final OptionSet options) { + SharedConstants.tryDetectVersion(); + ++ // DivineMC start - Verify Minecraft EULA earlier ++ Path path2 = Paths.get("eula.txt"); ++ Eula eula = new Eula(path2); ++ boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); ++ if (eulaAgreed) { ++ LOGGER.error("You have used the Spigot command line EULA agreement flag."); ++ LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA)."); ++ LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately."); ++ } ++ if (!eula.hasAgreedToEULA() && !eulaAgreed) { ++ LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); ++ return; ++ } ++ System.out.println("Loading libraries, please wait..."); // Restore CraftBukkit log ++ // DivineMC end - Verify Minecraft EULA earlier ++ + getStartupVersionMessages().forEach(LOGGER::info); + + Main.main(options); diff --git a/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch b/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch new file mode 100644 index 0000000..998469b --- /dev/null +++ b/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 22 Feb 2025 02:33:28 +0300 +Subject: [PATCH] Add chunk worker algorithm + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +index 632920e04686d8a0fd0a60e87348be1fe7862a3c..38b8cdac418ab2308c0392be49289356cbe81fb7 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +@@ -3,6 +3,8 @@ package ca.spottedleaf.moonrise.common.util; + import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; + import ca.spottedleaf.moonrise.common.PlatformHooks; + import com.mojang.logging.LogUtils; ++import org.bxteam.divinemc.DivineConfig; ++import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; + import org.slf4j.Logger; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; +@@ -38,26 +40,16 @@ public final class MoonriseCommon { + public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); + + public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { +- int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; +- if (defaultWorkerThreads <= 4) { +- defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; +- } else { +- defaultWorkerThreads = defaultWorkerThreads / 2; +- } +- defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); +- +- int workerThreads = configWorkerThreads; +- +- if (workerThreads <= 0) { +- workerThreads = defaultWorkerThreads; +- } +- +- final int ioThreads = Math.max(1, configIoThreads); ++ // DivineMC start - Add chunk worker algorithm ++ ChunkSystemAlgorithms algorithm = DivineConfig.chunkWorkerAlgorithm; ++ int workerThreads = algorithm.evalWorkers(configWorkerThreads, configIoThreads); ++ int ioThreads = algorithm.evalIO(configWorkerThreads, configIoThreads); + + WORKER_POOL.adjustThreadCount(workerThreads); + IO_POOL.adjustThreadCount(ioThreads); + +- LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ LOGGER.info("ChunkSystem using '{}' algorithm, {} worker threads, {} I/O threads", algorithm.asDebugString(), workerThreads, ioThreads); ++ // DivineMC end - Add chunk worker algorithm + } + + public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch new file mode 100644 index 0000000..017d982 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +@@ -4,7 +_,7 @@ + + public final class MoonriseConstants { + +- public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", org.bxteam.divinemc.DivineConfig.maxViewDistance); // DivineMC - Configurable view distance + + private MoonriseConstants() {} + diff --git a/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch new file mode 100644 index 0000000..9b55c68 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch @@ -0,0 +1,50 @@ +--- a/src/main/java/io/papermc/paper/command/MSPTCommand.java ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -78,6 +_,47 @@ + ) + ) + ); ++ ++ // DivineMC start - MSPT Tracking for each world ++ sender.sendMessage(text()); ++ sender.sendMessage(text().content("World tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) { ++ List worldTimes = new ArrayList<>(); ++ worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("◴ " + serverLevel.getWorld().getName() + ": ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ worldTimes.get(0), SLASH, worldTimes.get(1), SLASH, worldTimes.get(2), text(", ", YELLOW), ++ worldTimes.get(3), SLASH, worldTimes.get(4), SLASH, worldTimes.get(5), text(", ", YELLOW), ++ worldTimes.get(6), SLASH, worldTimes.get(7), SLASH, worldTimes.get(8) ++ ) ++ ) ++ ); ++ } ++ // DivineMC end - MSPT Tracking for each world ++ + return true; + } + diff --git a/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch new file mode 100644 index 0000000..a2caa3a --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch @@ -0,0 +1,27 @@ +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -36,14 +_,21 @@ + + // SimplePluginManager + public void callEvent(@NotNull Event event) { ++ // DivineMC start - Skip event if no listeners ++ RegisteredListener[] listeners = event.getHandlers().getRegisteredListeners(); ++ if (listeners.length == 0) return; ++ // DivineMC end - Skip event if no listeners + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); ++ return; ++ } ++ // DivineMC end - Multithreaded tracker + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } +- +- HandlerList handlers = event.getHandlers(); +- RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { diff --git a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch new file mode 100644 index 0000000..6a7636e --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -989,7 +_,7 @@ + + @Override + public List getWorlds() { +- return new ArrayList(this.worlds.values()); ++ return new it.unimi.dsi.fastutil.objects.ObjectArrayList(this.worlds.values()); // DivineMC - optimize getWorlds + } + + @Override diff --git a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch new file mode 100644 index 0000000..ddc0dd6 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1976,7 +_,7 @@ + BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState); + world.getCraftServer().getPluginManager().callEvent(event); + +- if (!event.isCancelled()) { ++ if (!event.isCancelled() && (BlockFormEvent.getHandlerList().getRegisteredListeners().length != 0)) { // DivineMC - skip block update if no listeners + blockState.update(true); + } + diff --git a/divinemc-server/src/c/CMakeLists.txt b/divinemc-server/src/c/CMakeLists.txt new file mode 100644 index 0000000..30597be --- /dev/null +++ b/divinemc-server/src/c/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.25) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + +project(c2me-opts-natives-math C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_COMPILER clang) + +SET(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g") + +set(CMAKE_C_STANDARD_LIBRARIES "") +set(CMAKE_CXX_STANDARD_LIBRARIES "") + +add_library(c2me-opts-natives-math SHARED + exports.c + system_isa_x86_64.c + exports_x86_64_nogather.c + system_isa_aarch64.c + flibc.c +) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") + set_source_files_properties(exports_x86_64_nogather.c PROPERTIES COMPILE_FLAGS "-mno-gather -mno-scatter") +endif() + +execute_process(COMMAND llvm-config --prefix OUTPUT_VARIABLE LLVM_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE) + +target_include_directories(c2me-opts-natives-math PRIVATE includes/) +target_compile_options(c2me-opts-natives-math PRIVATE $<$:-Wall -Wextra -Wpedantic -ffreestanding -ffile-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-compilation-dir=. -fdebug-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-prefix-map=${LLVM_PREFIX}=.../llvm-prefix -fno-math-errno -mprefer-vector-width=512 -ffp-contract=off -Rpass-analysis=loop-vectorize -mno-stack-arg-probe -fsave-optimization-record "SHELL:-mllvm -extra-vectorizer-passes" "SHELL:-mllvm -slp-vectorize-hor-store" "SHELL:-mllvm -slp-min-tree-size=1" "SHELL:-mllvm -slp-min-reg-size=64" "SHELL:-mllvm -slp-threshold=-1" "SHELL:-mllvm -enable-epilogue-vectorization">) +target_link_options(c2me-opts-natives-math PRIVATE -v -nostdlib -fuse-ld=lld -ffile-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-compilation-dir=. -fdebug-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-prefix-map=${LLVM_PREFIX}=.../llvm-prefix) + diff --git a/divinemc-server/src/c/exports.c b/divinemc-server/src/c/exports.c new file mode 100644 index 0000000..6d5dfd8 --- /dev/null +++ b/divinemc-server/src/c/exports.c @@ -0,0 +1,34 @@ +#include +#include + +TARGET_IMPL(c2me_natives_noise_perlin_sample, double, (const aligned_uint32_ptr permutations, const double originX, + const double originY, const double originZ, const double x, + const double y, const double z, const double yScale, + const double yMax) { + return math_noise_perlin_sample(permutations, originX, originY, originZ, x, y, z, yScale, yMax); +}) + +TARGET_IMPL(c2me_natives_noise_perlin_double, double, (const double_octave_sampler_data_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_double_octave_sample(data, x, y, z); +}) + +TARGET_IMPL(c2me_natives_noise_perlin_double_batch, void, (const double_octave_sampler_data_t *const data, + double *const res, const double *const x, + const double *const y, const double *const z, + const uint32_t length) { + math_noise_perlin_double_octave_sample_batch(data, res, x, y, z, length); +}) + +TARGET_IMPL(c2me_natives_noise_interpolated, double, (const interpolated_noise_sampler_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_interpolated_sample(data, x, y, z); +}) + +TARGET_IMPL(c2me_natives_end_islands_sample, float, (const aligned_uint32_ptr simplex_permutations, const int32_t x, const int32_t z) { + return math_end_islands_sample(simplex_permutations, x, z); +}) + +TARGET_IMPL(c2me_natives_biome_access_sample, uint32_t, (const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) { + return math_biome_access_sample(theSeed, x, y, z); +}) diff --git a/divinemc-server/src/c/exports_x86_64_nogather.c b/divinemc-server/src/c/exports_x86_64_nogather.c new file mode 100644 index 0000000..11ab8cd --- /dev/null +++ b/divinemc-server/src/c/exports_x86_64_nogather.c @@ -0,0 +1,9 @@ +#ifdef __x86_64__ + +#define GATHER_DISABLED 1 + +#include "exports.c" + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/flibc.c b/divinemc-server/src/c/flibc.c new file mode 100644 index 0000000..0ae6de6 --- /dev/null +++ b/divinemc-server/src/c/flibc.c @@ -0,0 +1,672 @@ +#include +#include +#include + +typedef int make_iso_compilers_happy; + +#ifdef WIN32 + +// ld.lld: error: : undefined symbol: DllMainCRTStartup +int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) +{ + (void) instance; + (void) reason; + (void) reserved; + return 1; +} + +// ld.lld: error: undefined symbol: _fltused +int _fltused = 0; + +// ld.lld: error: undefined symbol: abort +void abort(void) +{ + __builtin_trap(); +} + +#endif // WIN32 + +/* +The following code is from musl, original license below: + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + */ + +// src/internal/libm.h +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t m; + uint16_t se; + } i; +}; +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +/* This is the m68k variant of 80-bit long double, and this definition only works + * on archs where the alignment requirement of uint64_t is <= 4. */ +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t pad; + uint64_t m; + } i; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t lo; + uint32_t mid; + uint16_t top; + uint16_t se; + } i; + struct { + uint64_t lo; + uint64_t hi; + } i2; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t top; + uint32_t mid; + uint64_t lo; + } i; + struct { + uint64_t hi; + uint64_t lo; + } i2; +}; +#else +#error Unsupported long double representation +#endif + +/* Support non-nearest rounding mode. */ +#define WANT_ROUNDING 1 +/* Support signaling NaNs. */ +#define WANT_SNAN 0 + +#if WANT_SNAN +#error SNaN is unsupported +#else +#define issignalingf_inline(x) 0 +#define issignaling_inline(x) 0 +#endif + +#ifndef TOINT_INTRINSICS +#define TOINT_INTRINSICS 0 +#endif + +#if TOINT_INTRINSICS +/* Round x to nearest int in all rounding modes, ties have to be rounded + consistently with converttoint so the results match. If the result + would be outside of [-2^31, 2^31-1] then the semantics is unspecified. */ +static double_t roundtoint(double_t); + +/* Convert x to nearest int in all rounding modes, ties have to be rounded + consistently with roundtoint. If the result is not representible in an + int32_t then the semantics is unspecified. */ +static int32_t converttoint(double_t); +#endif + +/* Helps static branch prediction so hot path can be better optimized. */ +#ifdef __GNUC__ +#define predict_true(x) __builtin_expect(!!(x), 1) +#define predict_false(x) __builtin_expect(x, 0) +#else +#define predict_true(x) (x) +#define predict_false(x) (x) +#endif + +/* Evaluate an expression as the specified type. With standard excess + precision handling a type cast or assignment is enough (with + -ffloat-store an assignment is required, in old compilers argument + passing and return statement may not drop excess precision). */ + +static inline float eval_as_float(float x) { + float y = x; + return y; +} + +static inline double eval_as_double(double x) { + double y = x; + return y; +} + +/* fp_barrier returns its input, but limits code transformations + as if it had a side-effect (e.g. observable io) and returned + an arbitrary value. */ + +#ifndef fp_barrierf +#define fp_barrierf fp_barrierf + +static inline float fp_barrierf(float x) { + volatile float y = x; + return y; +} + +#endif + +#ifndef fp_barrier +#define fp_barrier fp_barrier + +static inline double fp_barrier(double x) { + volatile double y = x; + return y; +} + +#endif + +#ifndef fp_barrierl +#define fp_barrierl fp_barrierl + +static inline long double fp_barrierl(long double x) { + volatile long double y = x; + return y; +} + +#endif + +/* fp_force_eval ensures that the input value is computed when that's + otherwise unused. To prevent the constant folding of the input + expression, an additional fp_barrier may be needed or a compilation + mode that does so (e.g. -frounding-math in gcc). Then it can be + used to evaluate an expression for its fenv side-effects only. */ + +#ifndef fp_force_evalf +#define fp_force_evalf fp_force_evalf + +static inline void fp_force_evalf(float x) { + volatile float y; + y = x; +} + +#endif + +#ifndef fp_force_eval +#define fp_force_eval fp_force_eval + +static inline void fp_force_eval(double x) { + volatile double y; + y = x; +} + +#endif + +#ifndef fp_force_evall +#define fp_force_evall fp_force_evall + +static inline void fp_force_evall(long double x) { + volatile long double y; + y = x; +} + +#endif + +#define FORCE_EVAL(x) do { \ + if (sizeof(x) == sizeof(float)) { \ + fp_force_evalf(x); \ + } else if (sizeof(x) == sizeof(double)) { \ + fp_force_eval(x); \ + } else { \ + fp_force_evall(x); \ + } \ +} while(0) + +#define asuint(f) ((union{float _f; uint32_t _i;}){f})._i +#define asfloat(i) ((union{uint32_t _i; float _f;}){i})._f +#define asuint64(f) ((union{double _f; uint64_t _i;}){f})._i +#define asdouble(i) ((union{uint64_t _i; double _f;}){i})._f + +#define EXTRACT_WORDS(hi, lo, d) \ +do { \ + uint64_t __u = asuint64(d); \ + (hi) = __u >> 32; \ + (lo) = (uint32_t)__u; \ +} while (0) + +#define GET_HIGH_WORD(hi, d) \ +do { \ + (hi) = asuint64(d) >> 32; \ +} while (0) + +#define GET_LOW_WORD(lo, d) \ +do { \ + (lo) = (uint32_t)asuint64(d); \ +} while (0) + +#define INSERT_WORDS(d, hi, lo) \ +do { \ + (d) = asdouble(((uint64_t)(hi)<<32) | (uint32_t)(lo)); \ +} while (0) + +#define SET_HIGH_WORD(d, hi) \ + INSERT_WORDS(d, hi, (uint32_t)asuint64(d)) + +#define SET_LOW_WORD(d, lo) \ + INSERT_WORDS(d, asuint64(d)>>32, lo) + +#define GET_FLOAT_WORD(w, d) \ +do { \ + (w) = asuint(d); \ +} while (0) + +#define SET_FLOAT_WORD(d, w) \ +do { \ + (d) = asfloat(w); \ +} while (0) + +static int __rem_pio2_large(double *, double *, int, int, int); + +static int __rem_pio2(double, double *); + +static double __sin(double, double, int); + +static double __cos(double, double); + +static double __tan(double, double, int); + +static double __expo2(double, double); + +static int __rem_pio2f(float, double *); + +static float __sindf(double); + +static float __cosdf(double); + +static float __tandf(double, int); + +static float __expo2f(float, float); + +static int __rem_pio2l(long double, long double *); + +static long double __sinl(long double, long double, int); + +static long double __cosl(long double, long double); + +static long double __tanl(long double, long double, int); + +static long double __polevll(long double, const long double *, int); + +static long double __p1evll(long double, const long double *, int); + +//extern int __signgam; +static double __lgamma_r(double, int *); + +static float __lgammaf_r(float, int *); + +/* error handling functions */ +static float __math_xflowf(uint32_t, float); + +static float __math_uflowf(uint32_t); + +static float __math_oflowf(uint32_t); + +static float __math_divzerof(uint32_t); + +static float __math_invalidf(float); + +static double __math_xflow(uint32_t, double); + +static double __math_uflow(uint32_t); + +static double __math_oflow(uint32_t); + +static double __math_divzero(uint32_t); + +static double __math_invalid(double); + +#if LDBL_MANT_DIG != DBL_MANT_DIG + +static long double __math_invalidl(long double); + +#endif + +// src/math/__math_invalidf.c +static float __math_invalidf(float x) +{ + return (x - x) / (x - x); +} + +// src/math/truncf.c + +float truncf(float x) { + union { + float f; + uint32_t i; + } u = {x}; + int e = (int) (u.i >> 23 & 0xff) - 0x7f + 9; + uint32_t m; + + if (e >= 23 + 9) + return x; + if (e < 9) + e = 1; + m = -1U >> e; + if ((u.i & m) == 0) + return x; + FORCE_EVAL(x + 0x1p120f); + u.i &= ~m; + return u.f; +} + +// src/math/floor.c + +#if FLT_EVAL_METHOD == 0 || FLT_EVAL_METHOD == 1 +#define EPS DBL_EPSILON +#elif FLT_EVAL_METHOD == 2 +#define EPS LDBL_EPSILON +#endif +static const double toint = 1 / EPS; + +double floor(double x) { + union { + double f; + uint64_t i; + } u = {x}; + int e = u.i >> 52 & 0x7ff; + double y; + + if (e >= 0x3ff + 52 || x == 0) + return x; + /* y = int(x) - x, where int(x) is an integer neighbor of x */ + if (u.i >> 63) + y = x - toint + toint - x; + else + y = x + toint - toint - x; + /* special case because of non-nearest rounding modes */ + if (e <= 0x3ff - 1) { + FORCE_EVAL(y); + return u.i >> 63 ? -1 : 0; + } + if (y > 0) + return x + y - 1; + return x + y; +} + +// src/math/fmodf.c + +float fmodf(float x, float y) { + union { + float f; + uint32_t i; + } ux = {x}, uy = {y}; + int ex = ux.i >> 23 & 0xff; + int ey = uy.i >> 23 & 0xff; + uint32_t sx = ux.i & 0x80000000; + uint32_t i; + uint32_t uxi = ux.i; + + if (uy.i << 1 == 0 || __builtin_isnan(y) || ex == 0xff) + return (x * y) / (x * y); + if (uxi << 1 <= uy.i << 1) { + if (uxi << 1 == uy.i << 1) + return 0 * x; + return x; + } + + /* normalize x and y */ + if (!ex) { + for (i = uxi << 9; i >> 31 == 0; ex--, i <<= 1); + uxi <<= -ex + 1; + } else { + uxi &= -1U >> 9; + uxi |= 1U << 23; + } + if (!ey) { + for (i = uy.i << 9; i >> 31 == 0; ey--, i <<= 1); + uy.i <<= -ey + 1; + } else { + uy.i &= -1U >> 9; + uy.i |= 1U << 23; + } + + /* x mod y */ + for (; ex > ey; ex--) { + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + uxi <<= 1; + } + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + for (; uxi >> 23 == 0; uxi <<= 1, ex--); + + /* scale result up */ + if (ex > 0) { + uxi -= 1U << 23; + uxi |= (uint32_t) ex << 23; + } else { + uxi >>= -ex + 1; + } + uxi |= sx; + ux.i = uxi; + return ux.f; +} + +// src/string/memset.c + +void *memset(void *dest, int c, size_t n) { + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) return dest; + s[0] = c; + s[n - 1] = c; + if (n <= 2) return dest; + s[1] = c; + s[2] = c; + s[n - 2] = c; + s[n - 3] = c; + if (n <= 6) return dest; + s[3] = c; + s[n - 4] = c; + if (n <= 8) return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t) s & 3; + s += k; + n -= k; + n &= -4; + +#ifdef __GNUC__ + typedef uint32_t __attribute__((__may_alias__)) u32; + typedef uint64_t __attribute__((__may_alias__)) u64; + + u32 c32 = ((u32) -1) / 255 * (unsigned char) c; + + /* In preparation to copy 32 bytes at a time, aligned on + * an 8-byte bounary, fill head/tail up to 28 bytes each. + * As in the initial byte-based head/tail fill, each + * conditional below ensures that the subsequent offsets + * are valid (e.g. !(n<=24) implies n>=28). */ + + *(u32 *) (s + 0) = c32; + *(u32 *) (s + n - 4) = c32; + if (n <= 8) return dest; + *(u32 *) (s + 4) = c32; + *(u32 *) (s + 8) = c32; + *(u32 *) (s + n - 12) = c32; + *(u32 *) (s + n - 8) = c32; + if (n <= 24) return dest; + *(u32 *) (s + 12) = c32; + *(u32 *) (s + 16) = c32; + *(u32 *) (s + 20) = c32; + *(u32 *) (s + 24) = c32; + *(u32 *) (s + n - 28) = c32; + *(u32 *) (s + n - 24) = c32; + *(u32 *) (s + n - 20) = c32; + *(u32 *) (s + n - 16) = c32; + + /* Align to a multiple of 8 so we can fill 64 bits at a time, + * and avoid writing the same bytes twice as much as is + * practical without introducing additional branching. */ + + k = 24 + ((uintptr_t) s & 4); + s += k; + n -= k; + + /* If this loop is reached, 28 tail bytes have already been + * filled, so any remainder when n drops below 32 can be + * safely ignored. */ + + u64 c64 = c32 | ((u64) c32 << 32); + for (; n >= 32; n -= 32, s += 32) { + *(u64 *) (s + 0) = c64; + *(u64 *) (s + 8) = c64; + *(u64 *) (s + 16) = c64; + *(u64 *) (s + 24) = c64; + } +#else + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; +#endif + + return dest; +} + +// src/math/sqrt_data.[c|h] + +/* if x in [1,2): i = (int)(64*x); + if x in [2,4): i = (int)(32*x-64); + __rsqrt_tab[i]*2^-16 is estimating 1/sqrt(x) with small relative error: + |__rsqrt_tab[i]*0x1p-16*sqrt(x) - 1| < -0x1.fdp-9 < 2^-8 */ +extern const uint16_t __rsqrt_tab[128] = { + 0xb451, 0xb2f0, 0xb196, 0xb044, 0xaef9, 0xadb6, 0xac79, 0xab43, + 0xaa14, 0xa8eb, 0xa7c8, 0xa6aa, 0xa592, 0xa480, 0xa373, 0xa26b, + 0xa168, 0xa06a, 0x9f70, 0x9e7b, 0x9d8a, 0x9c9d, 0x9bb5, 0x9ad1, + 0x99f0, 0x9913, 0x983a, 0x9765, 0x9693, 0x95c4, 0x94f8, 0x9430, + 0x936b, 0x92a9, 0x91ea, 0x912e, 0x9075, 0x8fbe, 0x8f0a, 0x8e59, + 0x8daa, 0x8cfe, 0x8c54, 0x8bac, 0x8b07, 0x8a64, 0x89c4, 0x8925, + 0x8889, 0x87ee, 0x8756, 0x86c0, 0x862b, 0x8599, 0x8508, 0x8479, + 0x83ec, 0x8361, 0x82d8, 0x8250, 0x81c9, 0x8145, 0x80c2, 0x8040, + 0xff02, 0xfd0e, 0xfb25, 0xf947, 0xf773, 0xf5aa, 0xf3ea, 0xf234, + 0xf087, 0xeee3, 0xed47, 0xebb3, 0xea27, 0xe8a3, 0xe727, 0xe5b2, + 0xe443, 0xe2dc, 0xe17a, 0xe020, 0xdecb, 0xdd7d, 0xdc34, 0xdaf1, + 0xd9b3, 0xd87b, 0xd748, 0xd61a, 0xd4f1, 0xd3cd, 0xd2ad, 0xd192, + 0xd07b, 0xcf69, 0xce5b, 0xcd51, 0xcc4a, 0xcb48, 0xca4a, 0xc94f, + 0xc858, 0xc764, 0xc674, 0xc587, 0xc49d, 0xc3b7, 0xc2d4, 0xc1f4, + 0xc116, 0xc03c, 0xbf65, 0xbe90, 0xbdbe, 0xbcef, 0xbc23, 0xbb59, + 0xba91, 0xb9cc, 0xb90a, 0xb84a, 0xb78c, 0xb6d0, 0xb617, 0xb560, +}; + +// src/math/sqrtf.c +#define FENV_SUPPORT 1 + +static inline uint32_t mul32(uint32_t a, uint32_t b) { + return (uint64_t) a * b >> 32; +} + +/* see sqrt.c for more detailed comments. */ + +float sqrtf(float x) { + uint32_t ix, m, m1, m0, even, ey; + + ix = asuint(x); + if (predict_false(ix - 0x00800000 >= 0x7f800000 - 0x00800000)) { + /* x < 0x1p-126 or inf or nan. */ + if (ix * 2 == 0) + return x; + if (ix == 0x7f800000) + return x; + if (ix > 0x7f800000) + return __math_invalidf(x); + /* x is subnormal, normalize it. */ + ix = asuint(x * 0x1p23f); + ix -= 23 << 23; + } + + /* x = 4^e m; with int e and m in [1, 4). */ + even = ix & 0x00800000; + m1 = (ix << 8) | 0x80000000; + m0 = (ix << 7) & 0x7fffffff; + m = even ? m0 : m1; + + /* 2^e is the exponent part of the return value. */ + ey = ix >> 1; + ey += 0x3f800000 >> 1; + ey &= 0x7f800000; + + /* compute r ~ 1/sqrt(m), s ~ sqrt(m) with 2 goldschmidt iterations. */ + static const uint32_t three = 0xc0000000; + uint32_t r, s, d, u, i; + i = (ix >> 17) % 128; + r = (uint32_t) __rsqrt_tab[i] << 16; + /* |r*sqrt(m) - 1| < 0x1p-8 */ + s = mul32(m, r); + /* |s/sqrt(m) - 1| < 0x1p-8 */ + d = mul32(s, r); + u = three - d; + r = mul32(r, u) << 1; + /* |r*sqrt(m) - 1| < 0x1.7bp-16 */ + s = mul32(s, u) << 1; + /* |s/sqrt(m) - 1| < 0x1.7bp-16 */ + d = mul32(s, r); + u = three - d; + s = mul32(s, u); + /* -0x1.03p-28 < s/sqrt(m) - 1 < 0x1.fp-31 */ + s = (s - 1) >> 6; + /* s < sqrt(m) < s + 0x1.08p-23 */ + + /* compute nearest rounded result. */ + uint32_t d0, d1, d2; + float y, t; + d0 = (m << 16) - s * s; + d1 = s - d0; + d2 = d1 + s + 1; + s += d1 >> 31; + s &= 0x007fffff; + s |= ey; + y = asfloat(s); + if (FENV_SUPPORT) { + /* handle rounding and inexact exception. */ + uint32_t tiny = predict_false(d2 == 0) ? 0 : 0x01000000; + tiny |= (d1 ^ d2) & 0x80000000; + t = asfloat(tiny); + y = eval_as_float(y + t); + } + return y; +} + diff --git a/divinemc-server/src/c/includes/ext_math.h b/divinemc-server/src/c/includes/ext_math.h new file mode 100644 index 0000000..ba9e936 --- /dev/null +++ b/divinemc-server/src/c/includes/ext_math.h @@ -0,0 +1,744 @@ +#pragma once + +#include +#include +#include +#include + +__attribute__((aligned(64))) static const double FLAT_SIMPLEX_GRAD[] = { + 1, 1, 0, 0, + -1, 1, 0, 0, + 1, -1, 0, 0, + -1, -1, 0, 0, + 1, 0, 1, 0, + -1, 0, 1, 0, + 1, 0, -1, 0, + -1, 0, -1, 0, + 0, 1, 1, 0, + 0, -1, 1, 0, + 0, 1, -1, 0, + 0, -1, -1, 0, + 1, 1, 0, 0, + 0, -1, 1, 0, + -1, 1, 0, 0, + 0, -1, -1, 0, +}; + +static const double SQRT_3 = 1.7320508075688772; +// 0.5 * (SQRT_3 - 1.0) +static const double SKEW_FACTOR_2D = 0.3660254037844386; +// (3.0 - SQRT_3) / 6.0 +static const double UNSKEW_FACTOR_2D = 0.21132486540518713; + +typedef const double *aligned_double_ptr __attribute__((align_value(64))); +typedef const uint8_t *aligned_uint8_ptr __attribute__((align_value(64))); +typedef const uint32_t *aligned_uint32_ptr __attribute__((align_value(64))); + +#pragma clang attribute push (__attribute__((always_inline)), apply_to = function) + +static inline __attribute__((const)) float fminf(const float x, const float y) { + return __builtin_fminf(x, y); +} + +static inline __attribute__((const)) float fmaxf(const float x, const float y) { + return __builtin_fmaxf(x, y); +} + +static inline __attribute__((const)) float fabsf(const float x) { + union { + float f; + uint32_t i; + } u = {x}; + u.i &= 0x7fffffff; + return u.f; +} + +static inline __attribute__((const)) int64_t labs(const int64_t x) { + return __builtin_labs(x); +} + +static inline __attribute__((const)) double floor(double x) { + return __builtin_floor(x); +} + +static inline __attribute__((const)) float sqrtf(float x) { + return __builtin_sqrtf(x); +} + +static inline __attribute__((const)) float fmodf(float x, float y) { + return __builtin_fmodf(x, y); +} + +static inline __attribute__((const)) int32_t math_floorDiv(const int32_t x, const int32_t y) { + int r = x / y; + // if the signs are different and modulo not zero, round down + if ((x ^ y) < 0 && (r * y != x)) { + r--; + } + return r; +} + +static inline __attribute__((const)) float clampf(const float value, const float min, const float max) { + return fminf(fmaxf(value, min), max); +} + +static inline __attribute__((const)) double math_octave_maintainPrecision(const double value) { + return value - floor(value / 3.3554432E7 + 0.5) * 3.3554432E7; +} + +static inline __attribute__((const)) double math_simplex_grad(const int32_t hash, const double x, const double y, + const double z, const double distance) { + double d = distance - x * x - y * y - z * z; + if (d < 0.0) { + return 0.0; + } else { + int32_t i = hash << 2; + double var0 = FLAT_SIMPLEX_GRAD[i | 0] * x; + double var1 = FLAT_SIMPLEX_GRAD[i | 1] * y; + double var2 = FLAT_SIMPLEX_GRAD[i | 2] * z; + return d * d * d * d * (var0 + var1 + var2); + } +} + +static inline __attribute__((const)) double math_lerp(const double delta, const double start, const double end) { + return start + delta * (end - start); +} + +static inline __attribute__((const)) float math_lerpf(const float delta, const float start, const float end) { + return start + delta * (end - start); +} + +static inline __attribute__((const)) double math_clampedLerp(const double start, const double end, const double delta) { + if (delta < 0.0) { + return start; + } else { + return delta > 1.0 ? end : math_lerp(delta, start, end); + } +} + +static inline __attribute__((const)) double math_square(const double operand) { + return operand * operand; +} + +static inline __attribute__((const)) double math_lerp2(const double deltaX, const double deltaY, const double x0y0, + const double x1y0, const double x0y1, const double x1y1) { + return math_lerp(deltaY, math_lerp(deltaX, x0y0, x1y0), math_lerp(deltaX, x0y1, x1y1)); +} + +static inline __attribute__((const)) double math_lerp3( + const double deltaX, + const double deltaY, + const double deltaZ, + const double x0y0z0, + const double x1y0z0, + const double x0y1z0, + const double x1y1z0, + const double x0y0z1, + const double x1y0z1, + const double x0y1z1, + const double x1y1z1 +) { + return math_lerp(deltaZ, math_lerp2(deltaX, deltaY, x0y0z0, x1y0z0, x0y1z0, x1y1z0), + math_lerp2(deltaX, deltaY, x0y0z1, x1y0z1, x0y1z1, x1y1z1)); +} + +static inline __attribute__((const)) double math_getLerpProgress(const double value, const double start, + const double end) { + return (value - start) / (end - start); +} + +static inline __attribute__((const)) double +math_clampedLerpFromProgress(const double lerpValue, const double lerpStart, const double lerpEnd, const double start, + const double end) { + return math_clampedLerp(start, end, math_getLerpProgress(lerpValue, lerpStart, lerpEnd)); +} + +static inline __attribute__((const)) int32_t math_floorMod(const int32_t x, const int32_t y) { + int32_t mod = x % y; + // if the signs are different and modulo not zero, adjust result + if ((mod ^ y) < 0 && mod != 0) { + mod += y; + } + return mod; +} + +static inline __attribute__((const)) int32_t math_biome2block(const int32_t biomeCoord) { + return biomeCoord << 2; +} + +static inline __attribute__((const)) int32_t math_block2biome(const int32_t blockCoord) { + return blockCoord >> 2; +} + +static inline __attribute__((const)) uint32_t +__math_simplex_map(const aligned_uint32_ptr permutations, const int32_t input) { + return permutations[input & 0xFF]; +} + +static inline __attribute__((const)) double math_simplex_dot(const int32_t hash, const double x, const double y, + const double z) { + const int32_t loc = hash << 2; + return FLAT_SIMPLEX_GRAD[loc + 0] * x + FLAT_SIMPLEX_GRAD[loc + 1] * y + FLAT_SIMPLEX_GRAD[loc + 2] * z; +} + +static inline __attribute__((const)) double __math_simplex_grad(const int32_t hash, const double x, const double y, + const double z, const double distance) { + double d = distance - x * x - y * y - z * z; + double e; + if (d < 0.0) { + e = 0.0; + } else { + d *= d; + e = d * d * math_simplex_dot(hash, x, y, z); + } + return e; + // double tmp = d * d; // speculative execution + + // return d < 0.0 ? 0.0 : tmp * tmp * math_simplex_dot(hash, x, y, z); +} + +static inline double __attribute__((const)) +math_noise_simplex_sample2d(const aligned_uint32_ptr permutations, const double x, const double y) { + const double d = (x + y) * SKEW_FACTOR_2D; + const double i = floor(x + d); + const double j = floor(y + d); + const double e = (i + j) * UNSKEW_FACTOR_2D; + const double f = i - e; + const double g = j - e; + const double h = x - f; + const double k = y - g; + double l; + int32_t li; + double m; + int32_t mi; + if (h > k) { + l = 1; + li = 1; + m = 0; + mi = 0; + } else { + l = 0; + li = 1; + m = 1; + mi = 1; + } + + const double n = h - (double) l + UNSKEW_FACTOR_2D; + const double o = k - (double) m + UNSKEW_FACTOR_2D; + const double p = h - 1.0 + 2.0 * UNSKEW_FACTOR_2D; + const double q = k - 1.0 + 2.0 * UNSKEW_FACTOR_2D; + const int32_t r = (int32_t) i & 0xFF; + const int32_t s = (int32_t) j & 0xFF; + const int32_t t = __math_simplex_map(permutations, r + __math_simplex_map(permutations, s)) % 12; + const int32_t u = __math_simplex_map(permutations, r + li + __math_simplex_map(permutations, s + mi)) % 12; + const int32_t v = __math_simplex_map(permutations, r + 1 + __math_simplex_map(permutations, s + 1)) % 12; + const double w = __math_simplex_grad(t, h, k, 0.0, 0.5); + const double z = __math_simplex_grad(u, n, o, 0.0, 0.5); + const double aa = __math_simplex_grad(v, p, q, 0.0, 0.5); + return 70.0 * (w + z + aa); +} + +static inline __attribute__((const)) double math_perlinFade(const double value) { + return value * value * value * (value * (value * 6.0 - 15.0) + 10.0); +} + +static inline __attribute__((const)) double __math_perlin_grad(const aligned_uint32_ptr permutations, const int32_t px, + const int32_t py, const int32_t pz, const double fx, + const double fy, const double fz) { + const double f[3] = {fx, fy, fz}; + const int32_t p[3] = {px, py, pz}; + const uint32_t q[3] = {p[0] & 0xFF, p[1] & 0xFF, p[2] & 0xFF}; + const uint32_t hash = permutations[(permutations[(permutations[q[0]] + q[1]) & 0xFF] + q[2]) & 0xFF] & 0xF; + const double *const grad = FLAT_SIMPLEX_GRAD + (hash << 2); + return grad[0] * f[0] + grad[1] * f[1] + grad[2] * f[2]; +} + +static inline __attribute__((const)) double +math_noise_perlin_sampleScalar(const aligned_uint32_ptr permutations, + const int32_t px0, const int32_t py0, const int32_t pz0, + const double fx0, const double fy0, const double fz0, const double fadeLocalY) { + const int32_t px1 = px0 + 1; + const int32_t py1 = py0 + 1; + const int32_t pz1 = pz0 + 1; + const double fx1 = fx0 - 1; + const double fy1 = fy0 - 1; + const double fz1 = fz0 - 1; + + const double f000 = __math_perlin_grad(permutations, px0, py0, pz0, fx0, fy0, fz0); + const double f100 = __math_perlin_grad(permutations, px1, py0, pz0, fx1, fy0, fz0); + const double f010 = __math_perlin_grad(permutations, px0, py1, pz0, fx0, fy1, fz0); + const double f110 = __math_perlin_grad(permutations, px1, py1, pz0, fx1, fy1, fz0); + const double f001 = __math_perlin_grad(permutations, px0, py0, pz1, fx0, fy0, fz1); + const double f101 = __math_perlin_grad(permutations, px1, py0, pz1, fx1, fy0, fz1); + const double f011 = __math_perlin_grad(permutations, px0, py1, pz1, fx0, fy1, fz1); + const double f111 = __math_perlin_grad(permutations, px1, py1, pz1, fx1, fy1, fz1); + + const double dx = math_perlinFade(fx0); + const double dy = math_perlinFade(fadeLocalY); + const double dz = math_perlinFade(fz0); + return math_lerp3(dx, dy, dz, f000, f100, f010, f110, f001, f101, f011, f111); +} + + +static inline __attribute__((const)) double +math_noise_perlin_sample(const aligned_uint32_ptr permutations, + const double originX, const double originY, const double originZ, + const double x, const double y, const double z, + const double yScale, const double yMax) { + const double d = x + originX; + const double e = y + originY; + const double f = z + originZ; + const double i = floor(d); + const double j = floor(e); + const double k = floor(f); + const double g = d - i; + const double h = e - j; + const double l = f - k; + const double o = yScale != 0 ? floor(((yMax >= 0.0 && yMax < h) ? yMax : h) / yScale + 1.0E-7) * yScale : 0; + + return math_noise_perlin_sampleScalar(permutations, (int32_t) i, (int32_t) j, (int32_t) k, g, h - o, l, h); +} + + +typedef const struct double_octave_sampler_data { + const uint64_t length; + const double amplitude; + const bool *const need_shift; + const aligned_double_ptr lacunarity_powd; + const aligned_double_ptr persistence_powd; + const aligned_uint32_ptr sampler_permutations; + const aligned_double_ptr sampler_originX; + const aligned_double_ptr sampler_originY; + const aligned_double_ptr sampler_originZ; + const aligned_double_ptr amplitudes; +} double_octave_sampler_data_t; + +static inline __attribute__((const)) double +math_noise_perlin_double_octave_sample_impl(const double_octave_sampler_data_t *const data, + const double x, const double y, const double z, + const double yScale, const double yMax, const uint8_t useOrigin) { + double ds[data->length]; + +#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) + for (uint32_t i = 0; i < data->length; i++) { + const double e = data->lacunarity_powd[i]; + const double f = data->persistence_powd[i]; + const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * i; + const double sampleX = data->need_shift[i] ? x * 1.0181268882175227 : x; + const double sampleY = data->need_shift[i] ? y * 1.0181268882175227 : y; + const double sampleZ = data->need_shift[i] ? z * 1.0181268882175227 : z; + const double g = math_noise_perlin_sample( + permutations, + data->sampler_originX[i], + data->sampler_originY[i], + data->sampler_originZ[i], + math_octave_maintainPrecision(sampleX * e), + useOrigin ? -(data->sampler_originY[i]) : math_octave_maintainPrecision(sampleY * e), + math_octave_maintainPrecision(sampleZ * e), + yScale * e, + yMax * e); + ds[i] = data->amplitudes[i] * g * f; + } + + double d1 = 0.0; + double d2 = 0.0; + for (uint32_t i = 0; i < data->length; i++) { + if (!data->need_shift[i]) { + d1 += ds[i]; + } else { + d2 += ds[i]; + } + } + + return (d1 + d2) * data->amplitude; +} + +//static inline void +//math_noise_perlin_double_octave_sample_impl_batch(const double_octave_sampler_data_t *const data, double *const res, +// const double *const x, const double *const y, const double *const z, +// const uint32_t length) { +// double ds[data->length][length]; +// +// for (uint32_t si = 0; si < data->length; si ++) { +//#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) +// for (uint32_t bi = 0; bi < length; bi++) { +// const double e = data->lacunarity_powd[si]; +// const double f = data->persistence_powd[si]; +// const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * si; +// const double sampleX = data->need_shift[si] ? x[bi] * 1.0181268882175227 : x[bi]; +// const double sampleY = data->need_shift[si] ? y[bi] * 1.0181268882175227 : y[bi]; +// const double sampleZ = data->need_shift[si] ? z[bi] * 1.0181268882175227 : z[bi]; +// const double g = math_noise_perlin_sample( +// permutations, +// data->sampler_originX[si], +// data->sampler_originY[si], +// data->sampler_originZ[si], +// math_octave_maintainPrecision(sampleX * e), +// math_octave_maintainPrecision(sampleY * e), +// math_octave_maintainPrecision(sampleZ * e), +// 0.0, +// 0.0); +// ds[si][bi] = data->amplitudes[si] * g * f; +// } +// } +// +// double d1[length]; +// double d2[length]; +// for (uint32_t i = 0; i < length; i ++) { +// d1[i] = 0.0; +// d2[i] = 0.0; +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// for (uint32_t si = 0; si < data->length; si ++) { +// if (!data->need_shift[si]) { +// d1[bi] += ds[si][bi]; +// } else { +// d2[bi] += ds[si][bi]; +// } +// } +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// res[bi] = (d1[bi] + d2[bi]) * data->amplitude; +// } +//} + +//static inline void +//math_noise_perlin_double_octave_sample_impl_batch(const double_octave_sampler_data_t *restrict const data, +// double *restrict const res, const double *restrict const x, +// const double *restrict const y, const double *restrict const z, +// const uint32_t length) { +// const uint32_t total_len = data->length * length; +// +// double ds[total_len]; +// uint32_t sia[total_len]; // sampler index array +// uint32_t bia[total_len]; // batch index array +// double xa[total_len]; // x array +// double ya[total_len]; // y array +// double za[total_len]; // z array +// +// double lacunarity_powd[total_len]; +// double persistence_powd[total_len]; +// bool need_shift[total_len]; +// double sampler_originX[total_len]; +// double sampler_originY[total_len]; +// double sampler_originZ[total_len]; +// double amplitudes[total_len]; +// +// { +// uint32_t idx = 0; +// for (uint32_t si = 0; si < data->length; si++) { +// for (uint32_t bi = 0; bi < length; bi++) { +// sia[idx] = si; +// bia[idx] = bi; +// xa[idx] = x[bi]; +// ya[idx] = y[bi]; +// za[idx] = z[bi]; +// lacunarity_powd[idx] = data->lacunarity_powd[si]; +// persistence_powd[idx] = data->persistence_powd[si]; +// need_shift[idx] = data->need_shift[si]; +// sampler_originX[idx] = data->sampler_originX[si]; +// sampler_originY[idx] = data->sampler_originY[si]; +// sampler_originZ[idx] = data->sampler_originZ[si]; +// amplitudes[idx] = data->amplitudes[si]; +// idx++; +// } +// } +// } +// +//#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) +// for (uint32_t idx = 0; idx < total_len; idx++) { +// const uint32_t si = sia[idx]; +// const double xi = xa[idx]; +// const double yi = ya[idx]; +// const double zi = za[idx]; +// const double e = lacunarity_powd[idx]; +// const double f = persistence_powd[idx]; +// const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * si; +// const double sampleX = need_shift[idx] ? xi * 1.0181268882175227 : xi; +// const double sampleY = need_shift[idx] ? yi * 1.0181268882175227 : yi; +// const double sampleZ = need_shift[idx] ? zi * 1.0181268882175227 : zi; +// const double g = math_noise_perlin_sample( +// permutations, +// sampler_originX[idx], +// sampler_originY[idx], +// sampler_originZ[idx], +// math_octave_maintainPrecision(sampleX * e), +// math_octave_maintainPrecision(sampleY * e), +// math_octave_maintainPrecision(sampleZ * e), +// 0.0, +// 0.0); +// ds[idx] = amplitudes[idx] * g * f; +// } +// +// double d1[length]; +// double d2[length]; +// for (uint32_t i = 0; i < length; i++) { +// d1[i] = 0.0; +// d2[i] = 0.0; +// } +// for (uint32_t idx = 0; idx < total_len; idx++) { +// const uint32_t si = sia[idx]; +// const uint32_t bi = bia[idx]; +// if (!data->need_shift[si]) { +// d1[bi] += ds[idx]; +// } else { +// d2[bi] += ds[idx]; +// } +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// res[bi] = (d1[bi] + d2[bi]) * data->amplitude; +// } +//} + +static inline __attribute__((const)) double +math_noise_perlin_double_octave_sample(const double_octave_sampler_data_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_double_octave_sample_impl(data, x, y, z, 0.0, 0.0, 0); +} + +static inline void +math_noise_perlin_double_octave_sample_batch(const double_octave_sampler_data_t *const data, double *const res, + const double *const x, const double *const y, const double *const z, + const uint32_t length) { +// math_noise_perlin_double_octave_sample_impl_batch(data, res, x, y, z, length); + for (uint32_t i = 0; i < length; i ++) { + res[i] = math_noise_perlin_double_octave_sample_impl(data, x[i], y[i], z[i], 0.0, 0.0, 0); + } +} + +typedef const struct interpolated_noise_sub_sampler { + const aligned_uint32_ptr sampler_permutations; + const aligned_double_ptr sampler_originX; + const aligned_double_ptr sampler_originY; + const aligned_double_ptr sampler_originZ; + const aligned_double_ptr sampler_mulFactor; + const uint32_t length; +} interpolated_noise_sub_sampler_t; + +typedef const struct interpolated_noise_sampler { + const double scaledXzScale; + const double scaledYScale; + const double xzFactor; + const double yFactor; + const double smearScaleMultiplier; + const double xzScale; + const double yScale; + + const interpolated_noise_sub_sampler_t lower; + const interpolated_noise_sub_sampler_t upper; + const interpolated_noise_sub_sampler_t normal; +} interpolated_noise_sampler_t; + + +static inline __attribute__((const)) double +math_noise_perlin_interpolated_sample(const interpolated_noise_sampler_t *const data, + const double x, const double y, const double z) { + const double d = x * data->scaledXzScale; + const double e = y * data->scaledYScale; + const double f = z * data->scaledXzScale; + const double g = d / data->xzFactor; + const double h = e / data->yFactor; + const double i = f / data->xzFactor; + const double j = data->scaledYScale * data->smearScaleMultiplier; + const double k = j / data->yFactor; + double l = 0.0; + double m = 0.0; + double n = 0.0; + + double ns[data->normal.length]; +#pragma clang loop vectorize(enable) + for (uint32_t offset = 0; offset < data->normal.length; offset++) { + ns[offset] = math_noise_perlin_sample( + data->normal.sampler_permutations + 256 * offset, + data->normal.sampler_originX[offset], + data->normal.sampler_originY[offset], + data->normal.sampler_originZ[offset], + math_octave_maintainPrecision(g * data->normal.sampler_mulFactor[offset]), + math_octave_maintainPrecision(h * data->normal.sampler_mulFactor[offset]), + math_octave_maintainPrecision(i * data->normal.sampler_mulFactor[offset]), + k * data->normal.sampler_mulFactor[offset], + h * data->normal.sampler_mulFactor[offset] + ) / data->normal.sampler_mulFactor[offset]; + } + + for (uint32_t offset = 0; offset < data->normal.length; offset++) { + n += ns[offset]; + } + + const double q = (n / 10.0 + 1.0) / 2.0; + const uint8_t bl2 = q >= 1.0; + const uint8_t bl3 = q <= 0.0; + + if (!bl2) { + double ls[data->lower.length]; +#pragma clang loop vectorize(enable) interleave_count(2) + for (uint32_t offset = 0; offset < data->lower.length; offset++) { + ls[offset] = math_noise_perlin_sample( + data->lower.sampler_permutations + 256 * offset, + data->lower.sampler_originX[offset], + data->lower.sampler_originY[offset], + data->lower.sampler_originZ[offset], + math_octave_maintainPrecision(d * data->lower.sampler_mulFactor[offset]), + math_octave_maintainPrecision(e * data->lower.sampler_mulFactor[offset]), + math_octave_maintainPrecision(f * data->lower.sampler_mulFactor[offset]), + j * data->lower.sampler_mulFactor[offset], + e * data->lower.sampler_mulFactor[offset] + ) / data->lower.sampler_mulFactor[offset]; + } + + for (uint32_t offset = 0; offset < data->lower.length; offset++) { + l += ls[offset]; + } + } + + if (!bl3) { + double ms[data->upper.length]; +#pragma clang loop vectorize(enable) interleave_count(2) + for (uint32_t offset = 0; offset < data->upper.length; offset++) { + ms[offset] = math_noise_perlin_sample( + data->upper.sampler_permutations + 256 * offset, + data->upper.sampler_originX[offset], + data->upper.sampler_originY[offset], + data->upper.sampler_originZ[offset], + math_octave_maintainPrecision(d * data->upper.sampler_mulFactor[offset]), + math_octave_maintainPrecision(e * data->upper.sampler_mulFactor[offset]), + math_octave_maintainPrecision(f * data->upper.sampler_mulFactor[offset]), + j * data->upper.sampler_mulFactor[offset], + e * data->upper.sampler_mulFactor[offset] + ) / data->upper.sampler_mulFactor[offset]; + } + for (uint32_t offset = 0; offset < data->upper.length; offset++) { + m += ms[offset]; + } + } + + return math_clampedLerp(l / 512.0, m / 512.0, q) / 128.0; +} + +static inline __attribute__((const)) float +math_end_islands_sample(const aligned_uint32_ptr simplex_permutations, const int32_t x, const int32_t z) { + const int32_t i = x / 2; + const int32_t j = z / 2; + const int32_t k = x % 2; + const int32_t l = z % 2; + const int32_t muld = x * x + z * z; // int32_t intentionally + if (muld < 0) { + return __builtin_nanf(""); + } + float f = 100.0F - sqrtf((float) muld) * 8.0F; + f = clampf(f, -100.0F, 80.0F); + + int8_t ms[25 * 25], ns[25 * 25], hit[25 * 25]; + const int64_t omin = labs(i) - 12LL; + const int64_t pmin = labs(j) - 12LL; + const int64_t omax = labs(i) + 12LL; + const int64_t pmax = labs(j) + 12LL; + + { + uint32_t idx = 0; +#pragma clang loop vectorize(enable) + for (int8_t m = -12; m < 13; m++) { + for (int8_t n = -12; n < 13; n++) { + ms[idx] = m; + ns[idx] = n; + idx++; + } + } + if (idx != 25 * 25) { + __builtin_trap(); + } + } + + if (omin * omin + pmin * pmin > 4096LL) { + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + const int64_t o = (int64_t) i + (int64_t) ms[idx]; + const int64_t p = (int64_t) j + (int64_t) ns[idx]; + hit[idx] = math_noise_simplex_sample2d(simplex_permutations, (double) o, (double) p) < -0.9F; + } + } else { + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + const int64_t o = (int64_t) i + (int64_t) ms[idx]; + const int64_t p = (int64_t) j + (int64_t) ns[idx]; + hit[idx] = (o * o + p * p > 4096LL) && math_noise_simplex_sample2d( + simplex_permutations, (double) o, (double) p) < -0.9F; + } + } + +#pragma clang loop vectorize(enable) interleave(enable) + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + if (hit[idx]) { + const int32_t m = ms[idx]; + const int32_t n = ns[idx]; + const int64_t o = (int64_t) i + (int64_t) m; + const int64_t p = (int64_t) j + (int64_t) n; + const float g1 = fabsf((float) o) * 3439.0F; + const float g2 = fabsf((float) p) * 147.0F; + const float g = fmodf((g1 + g2), 13.0F) + 9.0F; + const float h = (float) (k - m * 2); + const float q = (float) (l - n * 2); + float r = 100.0F - sqrtf(h * h + q * q) * g; + r = clampf(r, -100.0F, 80.0F); + f = fmaxf(f, r); + } + } + + return f; +} + +static inline __attribute__((const)) uint32_t +math_biome_access_sample(const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) { + const int32_t var0 = x - 2; + const int32_t var1 = y - 2; + const int32_t var2 = z - 2; + const int32_t var3 = var0 >> 2; + const int32_t var4 = var1 >> 2; + const int32_t var5 = var2 >> 2; + const double var6 = (double) (var0 & 3) / 4.0; + const double var7 = (double) (var1 & 3) / 4.0; + const double var8 = (double) (var2 & 3) / 4.0; + uint32_t var9 = 0; + double var10 = DBL_MAX; + + double var28s[8]; + +#pragma clang loop interleave_count(2) + for (uint32_t var11 = 0; var11 < 8; ++var11) { + uint32_t var12 = var11 & 4; + uint32_t var13 = var11 & 2; + uint32_t var14 = var11 & 1; + int64_t var15 = var12 ? var3 + 1 : var3; + int64_t var16 = var13 ? var4 + 1 : var4; + int64_t var17 = var14 ? var5 + 1 : var5; + double var18 = var12 ? var6 - 1.0 : var6; + double var19 = var13 ? var7 - 1.0 : var7; + double var20 = var14 ? var8 - 1.0 : var8; + int64_t var21 = theSeed * (theSeed * 6364136223846793005L + 1442695040888963407L) + var15; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var15; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; + double var22 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var23 = (var22 - 0.5) * 0.9; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + theSeed; + double var24 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var25 = (var24 - 0.5) * 0.9; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + theSeed; + double var26 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var27 = (var26 - 0.5) * 0.9; + double var28 = math_square(var20 + var27) + math_square(var19 + var25) + math_square(var18 + var23); + var28s[var11] = var28; + } + + for (int i = 0; i < 8; ++i) { + if (var10 > var28s[i]) { + var9 = i; + var10 = var28s[i]; + } + } + + return var9; +} + +#pragma clang attribute pop + diff --git a/divinemc-server/src/c/includes/target_macros.h b/divinemc-server/src/c/includes/target_macros.h new file mode 100644 index 0000000..8883059 --- /dev/null +++ b/divinemc-server/src/c/includes/target_macros.h @@ -0,0 +1,28 @@ +#pragma once + +#define TARGET_IMPL_ARCH(suffix, func_prefix, func_ret, func_call) \ + func_ret func_prefix##_##suffix func_call + +#ifdef __x86_64__ + +#ifdef GATHER_DISABLED +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=haswell"))) TARGET_IMPL_ARCH(avx2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=skylake-avx512"))) TARGET_IMPL_ARCH(avx512skx, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=icelake-server"))) TARGET_IMPL_ARCH(avx512icl, func_prefix, func_ret, func_call) +#else +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=x86-64"))) TARGET_IMPL_ARCH(sse2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=x86-64-v2"))) TARGET_IMPL_ARCH(sse4_2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=sandybridge"))) TARGET_IMPL_ARCH(avx, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=alderlake"))) TARGET_IMPL_ARCH(avx2adl, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=sapphirerapids"))) TARGET_IMPL_ARCH(avx512spr, func_prefix, func_ret, func_call) +#endif + +#else + +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure)) TARGET_IMPL_ARCH(generic, func_prefix, func_ret, func_call) + +#endif + diff --git a/divinemc-server/src/c/system_isa_aarch64.c b/divinemc-server/src/c/system_isa_aarch64.c new file mode 100644 index 0000000..effa824 --- /dev/null +++ b/divinemc-server/src/c/system_isa_aarch64.c @@ -0,0 +1,11 @@ +#ifdef __aarch64__ + +#include + +int32_t c2me_natives_get_system_isa(_Bool allowAVX512) { + return 0; +} + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/system_isa_x86_64.c b/divinemc-server/src/c/system_isa_x86_64.c new file mode 100644 index 0000000..09ffd01 --- /dev/null +++ b/divinemc-server/src/c/system_isa_x86_64.c @@ -0,0 +1,154 @@ +#ifdef __x86_64__ + +#include + +static void __cpuid(int info[4], int infoType) { + __asm__ __volatile__("cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "0"(infoType)); +} + +static void __cpuidex(int info[4], int level, int count) { + __asm__ __volatile__("cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "0"(level), "2"(count)); +} + +static int __os_has_avx_support(void) { + // Check xgetbv; this uses a .byte sequence instead of the instruction + // directly because older assemblers do not include support for xgetbv and + // there is no easy way to conditionally compile based on the assembler used. + int rEAX, rEDX; + __asm__ __volatile__(".byte 0x0f, 0x01, 0xd0" : "=a"(rEAX), "=d"(rEDX) : "c"(0)); + return (rEAX & 6) == 6; +} + +#if !defined(__APPLE__) +static int __os_has_avx512_support(void) { + // Check if the OS saves the XMM, YMM and ZMM registers, i.e. it supports AVX2 and AVX512. + // See section 2.1 of software.intel.com/sites/default/files/managed/0d/53/319433-022.pdf + // Check xgetbv; this uses a .byte sequence instead of the instruction + // directly because older assemblers do not include support for xgetbv and + // there is no easy way to conditionally compile based on the assembler used. + int rEAX, rEDX; + __asm__ __volatile__(".byte 0x0f, 0x01, 0xd0" : "=a"(rEAX), "=d"(rEDX) : "c"(0)); + return (rEAX & 0xE6) == 0xE6; +} +#endif // !__APPLE__ + +// __get_system_isa should return a value corresponding to one of the +// Target::ISA enumerant values that gives the most capable ISA that the +// current system can run. +int32_t c2me_natives_get_system_isa(_Bool allowAVX512) { + int info[4]; + __cpuid(info, 1); + + // Call cpuid with eax=7, ecx=0 + int info2[4]; + __cpuidex(info2, 7, 0); + + int info3[4] = {0, 0, 0, 0}; + int max_subleaf = info2[0]; + // Call cpuid with eax=7, ecx=1 + if (max_subleaf >= 1) + __cpuidex(info3, 7, 1); + + // clang-format off + _Bool sse2 = (info[3] & (1 << 26)) != 0; + _Bool sse41 = (info[2] & (1 << 19)) != 0; + _Bool sse42 = (info[2] & (1 << 20)) != 0; + _Bool avx = (info[2] & (1 << 28)) != 0; + _Bool avx2 = (info2[1] & (1 << 5)) != 0; + _Bool avx_vnni = (info3[0] & (1 << 4)) != 0; + _Bool avx_f16c = (info[2] & (1 << 29)) != 0; + _Bool avx_rdrand = (info[2] & (1 << 30)) != 0; + _Bool osxsave = (info[2] & (1 << 27)) != 0; + _Bool avx512_f = (info2[1] & (1 << 16)) != 0; + // clang-format on + + // NOTE: the values returned below must be the same as the + // corresponding enumerant values in Target::ISA. + if (allowAVX512 && osxsave && avx2 && avx512_f +#if !defined(__APPLE__) + && __os_has_avx512_support() +#endif // !__APPLE__ + ) { + // We need to verify that AVX2 is also available, + // as well as AVX512, because our targets are supposed + // to use both. + + // clang-format off + _Bool avx512_dq = (info2[1] & (1 << 17)) != 0; + _Bool avx512_pf = (info2[1] & (1 << 26)) != 0; + _Bool avx512_er = (info2[1] & (1 << 27)) != 0; + _Bool avx512_cd = (info2[1] & (1 << 28)) != 0; + _Bool avx512_bw = (info2[1] & (1 << 30)) != 0; + _Bool avx512_vl = (info2[1] & (1 << 31)) != 0; +#if !defined(__APPLE__) + _Bool avx512_vbmi2 = (info2[2] & (1 << 6)) != 0; + _Bool avx512_gfni = (info2[2] & (1 << 8)) != 0; + _Bool avx512_vaes = (info2[2] & (1 << 9)) != 0; + _Bool avx512_vpclmulqdq = (info2[2] & (1 << 10)) != 0; + _Bool avx512_vnni = (info2[2] & (1 << 11)) != 0; + _Bool avx512_bitalg = (info2[2] & (1 << 12)) != 0; + _Bool avx512_vpopcntdq = (info2[2] & (1 << 14)) != 0; + _Bool avx512_bf16 = (info3[0] & (1 << 5)) != 0; + _Bool avx512_vp2intersect = (info2[3] & (1 << 8)) != 0; + _Bool avx512_amx_bf16 = (info2[3] & (1 << 22)) != 0; + _Bool avx512_amx_tile = (info2[3] & (1 << 24)) != 0; + _Bool avx512_amx_int8 = (info2[3] & (1 << 25)) != 0; + _Bool avx512_fp16 = (info2[3] & (1 << 23)) != 0; +#endif // !__APPLE__ + // clang-format on + + // Knights Landing: KNL = F + PF + ER + CD + // Skylake server: SKX = F + DQ + CD + BW + VL + // Cascade Lake server: CLX = SKX + VNNI + // Cooper Lake server: CPX = CLX + BF16 + // Ice Lake client & server: ICL = CLX + VBMI2 + GFNI + VAES + VPCLMULQDQ + BITALG + VPOPCNTDQ + // Tiger Lake: TGL = ICL + VP2INTERSECT + // Sapphire Rapids: SPR = ICL + BF16 + AMX_BF16 + AMX_TILE + AMX_INT8 + AVX_VNNI + FP16 + _Bool knl = avx512_pf && avx512_er && avx512_cd; + _Bool skx = avx512_dq && avx512_cd && avx512_bw && avx512_vl; +#if !defined(__APPLE__) + _Bool clx = skx && avx512_vnni; + _Bool cpx = clx && avx512_bf16; + _Bool icl = + clx && avx512_vbmi2 && avx512_gfni && avx512_vaes && avx512_vpclmulqdq && avx512_bitalg && avx512_vpopcntdq; + _Bool tgl = icl && avx512_vp2intersect; + _Bool spr = + icl && avx512_bf16 && avx512_amx_bf16 && avx512_amx_tile && avx512_amx_int8 && avx_vnni && avx512_fp16; + if (spr) { + return 9; // SPR + } else if (icl) { + return 8; // ICL + } +#endif // !__APPLE__ + if (skx) { + return 7; // SKX + } else if (knl) { + return 6; // KNL + } + // If it's unknown AVX512 target, fall through and use AVX2 + // or whatever is available in the machine. + } + + if (osxsave && avx && __os_has_avx_support()) { +// if (avx_vnni) { +// return 5; // ADL +// } + if (avx_f16c && avx_rdrand && avx2) { + return 4; + } + // Regular AVX + return 3; + } else if (sse42) { + return 2; // SSE4.2 + } else if (sse41) { + return 1; // SSE4.1 + } else if (sse2) { + return 0; // SSE2 + } else { + __builtin_trap(); + } +} + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/targets/darwin-x86_64.cmake b/divinemc-server/src/c/targets/darwin-x86_64.cmake new file mode 100644 index 0000000..4bd3ee9 --- /dev/null +++ b/divinemc-server/src/c/targets/darwin-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Darwin) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(triple x86_64-apple-darwin) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS "-march=x86-64 -Wl,-no_uuid") diff --git a/divinemc-server/src/c/targets/linux-aarch_64.cmake b/divinemc-server/src/c/targets/linux-aarch_64.cmake new file mode 100644 index 0000000..aa0bb36 --- /dev/null +++ b/divinemc-server/src/c/targets/linux-aarch_64.cmake @@ -0,0 +1,10 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm64) + +set(triple aarch64-unknown-linux-musl) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + diff --git a/divinemc-server/src/c/targets/linux-x86_64.cmake b/divinemc-server/src/c/targets/linux-x86_64.cmake new file mode 100644 index 0000000..eebd66a --- /dev/null +++ b/divinemc-server/src/c/targets/linux-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(triple x86_64-unknown-linux-musl) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS -march=x86-64) diff --git a/divinemc-server/src/c/targets/windows-x86_64.cmake b/divinemc-server/src/c/targets/windows-x86_64.cmake new file mode 100644 index 0000000..92adf50 --- /dev/null +++ b/divinemc-server/src/c/targets/windows-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +set(triple x86_64-pc-windows-gnu) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS "-march=x86-64 -Wl,--no-insert-timestamp") diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java new file mode 100644 index 0000000..f57983f --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java @@ -0,0 +1,89 @@ +package net.caffeinemc.mods.lithium.common.util.math; + +import net.minecraft.util.Mth; + +/** + * A replacement for the sine angle lookup table used in {@link Mth}, both reducing the size of LUT and improving + * the access patterns for common paired sin/cos operations. + *

+ * sin(-x) = -sin(x) + * ... to eliminate negative angles from the LUT. + *

+ * sin(x) = sin(pi/2 - x) + * ... to eliminate supplementary angles from the LUT. + *

+ * Using these identities allows us to reduce the LUT from 64K entries (256 KB) to just 16K entries (64 KB), enabling + * it to better fit into the CPU's caches at the expense of some cycles on the fast path. The implementation has been + * tightly optimized to avoid branching where possible and to use very quick integer operations. + *

+ * Generally speaking, reducing the size of a lookup table is always a good optimization, but since we need to spend + * extra CPU cycles trying to maintain parity with vanilla, there is the potential risk that this implementation ends + * up being slower than vanilla when the lookup table is able to be kept in cache memory. + *

+ * Unlike other "fast math" implementations, the values returned by this class are *bit-for-bit identical* with those + * from {@link Mth}. Validation is performed during runtime to ensure that the table is correct. + * + * @author coderbot16 Author of the original (and very clever) implementation in Rust + * @author jellysquid3 Additional optimizations, port to Java + */ +public class CompactSineLUT { + private static final int[] SINE_TABLE_INT = new int[16384 + 1]; + private static final float SINE_TABLE_MIDPOINT; + + static { + final float[] SINE_TABLE = Mth.SIN; + // Copy the sine table, covering to raw int bits + for (int i = 0; i < SINE_TABLE_INT.length; i++) { + SINE_TABLE_INT[i] = Float.floatToRawIntBits(SINE_TABLE[i]); + } + + SINE_TABLE_MIDPOINT = SINE_TABLE[SINE_TABLE.length / 2]; + + // Test that the lookup table is correct during runtime + for (int i = 0; i < SINE_TABLE.length; i++) { + float expected = SINE_TABLE[i]; + float value = lookup(i); + + if (expected != value) { + throw new IllegalArgumentException(String.format("LUT error at index %d (expected: %s, found: %s)", i, expected, value)); + } + } + } + + // [VanillaCopy] MathHelper#sin(float) + public static float sin(float f) { + return lookup((int) (f * 10430.378f) & 0xFFFF); + } + + // [VanillaCopy] MathHelper#cos(float) + public static float cos(float f) { + return lookup((int) (f * 10430.378f + 16384.0f) & 0xFFFF); + } + + private static float lookup(int index) { + // A special case... Is there some way to eliminate this? + if (index == 32768) { + return SINE_TABLE_MIDPOINT; + } + + // Trigonometric identity: sin(-x) = -sin(x) + // Given a domain of 0 <= x <= 2*pi, just negate the value if x > pi. + // This allows the sin table size to be halved. + int neg = (index & 0x8000) << 16; + + // All bits set if (pi/2 <= x), none set otherwise + // Extracts the 15th bit from 'half' + int mask = (index << 17) >> 31; + + // Trigonometric identity: sin(x) = sin(pi/2 - x) + int pos = (0x8001 & mask) + (index ^ mask); + + // Wrap the position in the table. Moving this down to immediately before the array access + // seems to help the Hotspot compiler optimize the bit math better. + pos &= 0x7fff; + + // Fetch the corresponding value from the LUT and invert the sign bit as needed + // This directly manipulate the sign bit on the float bits to simplify logic + return Float.intBitsToFloat(SINE_TABLE_INT[pos] ^ neg); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java new file mode 100644 index 0000000..8a032ea --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java @@ -0,0 +1,372 @@ +package org.bxteam.divinemc; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; +import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +@SuppressWarnings("unused") +@NullMarked +public final class DivineConfig { + private DivineConfig() { + throw new IllegalStateException("Utility class"); + } + + private static final String HEADER = "This is the main configuration file for DivineMC.\n" + + "If you need help with the configuration or have any questions related to DivineMC,\n" + + "join us in our Discord server.\n" + + "\n" + + "Discord: https://discord.gg/p7cxhw7E2M \n" + + "Docs: https://bxteam.org/docs/divinemc \n" + + "New builds: https://github.com/BX-Team/DivineMC/releases/latest"; + private static File configFile; + public static YamlConfiguration config; + + private static Map commands; + + public static int version; + static boolean verbose; + + public static void init(File configFile) { + DivineConfig.configFile = configFile; + config = new YamlConfiguration(); + try { + config.load(configFile); + } catch (IOException ignored) { + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load divinemc.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + verbose = getBoolean(config, "verbose", false); + + version = getInt(config, "config-version", 5); + set(config, "config-version", 5); + + readConfig(DivineConfig.class, null); + } + + public static void log(String s) { + if (verbose) { + log(Level.INFO, s); + } + } + + public static void log(Level level, String s) { + Bukkit.getLogger().log(level, s); + } + + static void readConfig(Class clazz, @Nullable Object instance) { + readConfig(configFile, config, clazz, instance); + } + + public static void readConfig(File configFile, YamlConfiguration config, Class clazz, @Nullable Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isPrivate(method.getModifiers())) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() != Void.TYPE) continue; + + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); + } + } + + try { + config.save(configFile); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not save " + configFile, ex); + } + } + + private static void set(YamlConfiguration config, String path, Object val, String... comments) { + config.addDefault(path, val); + config.set(path, val); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + } + + private static String getString(YamlConfiguration config, String path, String def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getString(path, config.getString(path)); + } + + private static boolean getBoolean(YamlConfiguration config, String path, boolean def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(YamlConfiguration config, String path, double def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getDouble(path, config.getDouble(path)); + } + + private static int getInt(YamlConfiguration config, String path, int def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getInt(path, config.getInt(path)); + } + + private static long getLong(YamlConfiguration config, String path, long def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getLong(path, config.getLong(path)); + } + + private static List getList(YamlConfiguration config, String path, List def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return (List) config.getList(path, def); + } + + static Map getMap(YamlConfiguration config, String path, Map def, String... comments) { + if (def != null && config.getConfigurationSection(path) == null) { + config.addDefault(path, def); + return def; + } + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return toMap(config.getConfigurationSection(path)); + } + + private static Map toMap(ConfigurationSection section) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (section != null) { + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj != null) { + builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); + } + } + } + return builder.build(); + } + + public static int parallelThreadCount = 4; + public static boolean logContainerCreationStacktraces = false; + private static void parallelWorldTicking() { + parallelThreadCount = getInt(config, "settings.parallel-world-ticking.thread-count", parallelThreadCount); + logContainerCreationStacktraces = getBoolean(config, "settings.parallel-world-ticking.log-container-creation-stacktraces", logContainerCreationStacktraces); + } + + public static boolean nativeAccelerationEnabled = true; + public static boolean allowAVX512 = false; + public static int isaTargetLevelOverride = -1; + public static long chunkDataCacheSoftLimit = 8192L; + public static long chunkDataCacheLimit = 32678L; + public static int maxViewDistance = 32; + public static ChunkSystemAlgorithms chunkWorkerAlgorithm = ChunkSystemAlgorithms.C2ME; + public static boolean enableSecureSeed = false; + public static boolean enableDensityFunctionCompiler = false; + public static boolean enableStructureLayoutOptimizer = true; + public static boolean deduplicateShuffledTemplatePoolElementList = false; + + private static void chunkGeneration() { + nativeAccelerationEnabled = getBoolean(config, "settings.chunk-generation.native-acceleration-enabled", nativeAccelerationEnabled); + + allowAVX512 = getBoolean(config, "settings.chunk-generation.allow-avx512", allowAVX512, + "Enables AVX512 support for natives-math optimizations", + "", + "Read more about AVX512: https://en.wikipedia.org/wiki/AVX-512"); + isaTargetLevelOverride = getInt(config, "settings.chunk-generation.isa-target-level-override", isaTargetLevelOverride, + "Overrides the ISA target located by the native loader, which allows forcing AVX512 (must be a value between 6-9 for AVX512 support).", + "Value must be between 1-9, and -1 to disable override"); + + if (isaTargetLevelOverride < -1 || isaTargetLevelOverride > 9) { + log(Level.WARNING, "Invalid ISA target level override: " + isaTargetLevelOverride + ", resetting to -1"); + isaTargetLevelOverride = -1; + } + + chunkDataCacheSoftLimit = getLong(config, "settings.chunk-generation.chunk-data-cache-soft-limit", chunkDataCacheSoftLimit); + chunkDataCacheLimit = getLong(config, "settings.chunk-generation.chunk-data-cache-limit", chunkDataCacheLimit); + maxViewDistance = getInt(config, "settings.chunk-generation.max-view-distance", maxViewDistance, + "Changes the maximum view distance for the server, allowing clients to have render distances higher than 32"); + + chunkWorkerAlgorithm = ChunkSystemAlgorithms.valueOf(getString(config, "settings.chunk-generation.chunk-worker-algorithm", chunkWorkerAlgorithm.name(), + "Modifies what algorithm the chunk system will use to define thread counts. values: MOONRISE, C2ME, C2ME_AGGRESSIVE")); + + enableSecureSeed = getBoolean(config, "settings.misc.enable-secure-seed", enableSecureSeed, + "This feature is based on Secure Seed mod by Earthcomputer.", + "", + "Terrain and biome generation remains the same, but all the ores and structures are generated with 1024-bit seed, instead of the usual 64-bit seed.", + "This seed is almost impossible to crack, and there are no weird links between structures."); + + enableDensityFunctionCompiler = getBoolean(config, "settings.chunk-generation.experimental.enable-density-function-compiler", enableDensityFunctionCompiler, + "Whether to use density function compiler to accelerate world generation", + "", + "Density function: https://minecraft.wiki/w/Density_function", + "", + "This functionality compiles density functions from world generation", + "datapacks (including vanilla generation) to JVM bytecode to increase", + "performance by allowing JVM JIT to better optimize the code.", + "All functions provided by vanilla are implemented.", + "", + "Please test if this optimization actually benefits your server, as", + "it can sometimes slow down chunk performance than speed it up."); + + enableStructureLayoutOptimizer = getBoolean(config, "settings.chunk-generation.experimental.enable-structure-layout-optimizer", enableStructureLayoutOptimizer, + "Enables a port of the mod StructureLayoutOptimizer, which optimizes general Jigsaw structure generation"); + deduplicateShuffledTemplatePoolElementList = getBoolean(config, "settings.chunk-generation.experimental.deduplicate-shuffled-template-pool-element-list", deduplicateShuffledTemplatePoolElementList, + "Whether to use an alternative strategy to make structure layouts generate slightly even faster than", + "the default optimization this mod has for template pool weights. This alternative strategy works by", + "changing the list of pieces that structures collect from the template pool to not have duplicate entries.", + "", + "This will not break the structure generation, but it will make the structure layout different than", + "if this config was off (breaking vanilla seed parity). The cost of speed may be worth it in large", + "modpacks where many structure mods are using very high weight values in their template pools."); + } + + public static boolean skipUselessSecondaryPoiSensor = true; + public static boolean clumpOrbs = true; + public static boolean ignoreMovedTooQuicklyWhenLagging = true; + public static boolean alwaysAllowWeirdMovement = true; + + private static void miscSettings() { + skipUselessSecondaryPoiSensor = getBoolean(config, "settings.misc.skip-useless-secondary-poi-sensor", skipUselessSecondaryPoiSensor); + clumpOrbs = getBoolean(config, "settings.misc.clump-orbs", clumpOrbs, + "Clumps experience orbs together to reduce entity count"); + ignoreMovedTooQuicklyWhenLagging = getBoolean(config, "settings.misc.ignore-moved-too-quickly-when-lagging", ignoreMovedTooQuicklyWhenLagging, + "Improves general gameplay experience of the player when the server is lagging, as they won't get lagged back (message 'moved too quickly')"); + alwaysAllowWeirdMovement = getBoolean(config, "settings.misc.always-allow-weird-movement", alwaysAllowWeirdMovement, + "Means ignoring messages like 'moved too quickly' and 'moved wrongly'"); + } + + public static boolean enableFasterTntOptimization = true; + public static boolean explosionNoBlockDamage = false; + public static double tntRandomRange = -1; + + private static void tntOptimization() { + enableFasterTntOptimization = getBoolean(config, "settings.tnt-optimization.enable-faster-tnt-optimization", enableFasterTntOptimization); + explosionNoBlockDamage = getBoolean(config, "settings.tnt-optimization.explosion-no-block-damage", explosionNoBlockDamage); + tntRandomRange = getDouble(config, "settings.tnt-optimization.tnt-random-range", tntRandomRange); + } + + public static boolean lagCompensationEnabled = true; + public static boolean blockEntityAcceleration = false; + public static boolean blockBreakingAcceleration = true; + public static boolean eatingAcceleration = true; + public static boolean potionEffectAcceleration = true; + public static boolean fluidAcceleration = true; + public static boolean pickupAcceleration = true; + public static boolean portalAcceleration = true; + public static boolean timeAcceleration = true; + public static boolean randomTickSpeedAcceleration = true; + + private static void lagCompensation() { + lagCompensationEnabled = getBoolean(config, "settings.lag-compensation.enabled", lagCompensationEnabled, "Improves the player experience when TPS is low"); + blockEntityAcceleration = getBoolean(config, "settings.lag-compensation.block-entity-acceleration", blockEntityAcceleration); + blockBreakingAcceleration = getBoolean(config, "settings.lag-compensation.block-breaking-acceleration", blockBreakingAcceleration); + eatingAcceleration = getBoolean(config, "settings.lag-compensation.eating-acceleration", eatingAcceleration); + potionEffectAcceleration = getBoolean(config, "settings.lag-compensation.potion-effect-acceleration", potionEffectAcceleration); + fluidAcceleration = getBoolean(config, "settings.lag-compensation.fluid-acceleration", fluidAcceleration); + pickupAcceleration = getBoolean(config, "settings.lag-compensation.pickup-acceleration", pickupAcceleration); + portalAcceleration = getBoolean(config, "settings.lag-compensation.portal-acceleration", portalAcceleration); + timeAcceleration = getBoolean(config, "settings.lag-compensation.time-acceleration", timeAcceleration); + randomTickSpeedAcceleration = getBoolean(config, "settings.lag-compensation.random-tick-speed-acceleration", randomTickSpeedAcceleration); + } + + public static boolean noChatReportsEnabled = false; + public static boolean noChatReportsAddQueryData = true; + public static boolean noChatReportsConvertToGameMessage = true; + public static boolean noChatReportsDebugLog = false; + public static boolean noChatReportsDemandOnClient = false; + public static String noChatReportsDisconnectDemandOnClientMessage = "You do not have No Chat Reports, and this server is configured to require it on client!"; + + private static void noChatReports() { + noChatReportsEnabled = getBoolean(config, "settings.no-chat-reports.enabled", noChatReportsEnabled, + "Enables or disables the No Chat Reports feature"); + noChatReportsAddQueryData = getBoolean(config, "settings.no-chat-reports.add-query-data", noChatReportsAddQueryData, + "Should server include extra query data to help clients know that your server is secure"); + noChatReportsConvertToGameMessage = getBoolean(config, "settings.no-chat-reports.convert-to-game-message", noChatReportsConvertToGameMessage, + "Should the server convert all player messages to system messages"); + noChatReportsDebugLog = getBoolean(config, "settings.no-chat-reports.debug-log", noChatReportsDebugLog); + noChatReportsDemandOnClient = getBoolean(config, "settings.no-chat-reports.demand-on-client", noChatReportsDemandOnClient, + "Should the server require No Chat Reports on the client side"); + noChatReportsDisconnectDemandOnClientMessage = getString(config, "settings.no-chat-reports.disconnect-demand-on-client-message", noChatReportsDisconnectDemandOnClientMessage, + "Message to send to the client when they are disconnected for not having No Chat Reports"); + } + + public static boolean asyncPathfinding = true; + public static int asyncPathfindingMaxThreads = 2; + public static int asyncPathfindingKeepalive = 60; + + private static void asyncPathfinding() { + asyncPathfinding = getBoolean(config, "settings.async-pathfinding.enable", asyncPathfinding); + asyncPathfindingMaxThreads = getInt(config, "settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads); + asyncPathfindingKeepalive = getInt(config, "settings.async-pathfinding.keepalive", asyncPathfindingKeepalive); + + if (asyncPathfindingMaxThreads < 0) { + asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1); + } else if (asyncPathfindingMaxThreads == 0) { + asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); + } + + if (!asyncPathfinding) { + asyncPathfindingMaxThreads = 0; + } else { + Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding"); + } + } + + public static boolean multithreadedEnabled = true; + public static boolean multithreadedCompatModeEnabled = false; + public static int asyncEntityTrackerMaxThreads = 1; + public static int asyncEntityTrackerKeepalive = 60; + + private static void multithreadedTracker() { + multithreadedEnabled = getBoolean(config, "settings.multithreaded-tracker.enable", multithreadedEnabled); + multithreadedCompatModeEnabled = getBoolean(config, "settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled); + asyncEntityTrackerMaxThreads = getInt(config, "settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads); + asyncEntityTrackerKeepalive = getInt(config, "settings.multithreaded-tracker.keepalive", asyncEntityTrackerKeepalive); + + if (asyncEntityTrackerMaxThreads < 0) { + asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); + } else if (asyncEntityTrackerMaxThreads == 0) { + asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); + } + + if (!multithreadedEnabled) { + asyncEntityTrackerMaxThreads = 0; + } else { + Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker"); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java new file mode 100644 index 0000000..2ef9680 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java @@ -0,0 +1,82 @@ +package org.bxteam.divinemc; + +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jspecify.annotations.NullMarked; + +import java.util.List; +import java.util.Map; + +import static org.bxteam.divinemc.DivineConfig.log; + +@NullMarked +public final class DivineWorldConfig { + private final YamlConfiguration config; + private final String worldName; + private final World.Environment environment; + + public DivineWorldConfig(String worldName, World.Environment environment) { + this.config = DivineConfig.config; + this.worldName = worldName; + this.environment = environment; + init(); + } + + public void init() { + log("-------- World Settings For [" + worldName + "] --------"); + DivineConfig.readConfig(DivineWorldConfig.class, this); + } + + private void set(String path, Object val) { + this.config.addDefault("world-settings.default." + path, val); + this.config.set("world-settings.default." + path, val); + if (this.config.get("world-settings." + worldName + "." + path) != null) { + this.config.addDefault("world-settings." + worldName + "." + path, val); + this.config.set("world-settings." + worldName + "." + path, val); + } + } + + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = this.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : this.config.getConfigurationSection("world-settings.default." + path); + } + + private String getString(String path, String def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getString("world-settings." + worldName + "." + path, this.config.getString("world-settings.default." + path)); + } + + private boolean getBoolean(String path, boolean def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getBoolean("world-settings." + worldName + "." + path, this.config.getBoolean("world-settings.default." + path)); + } + + private double getDouble(String path, double def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getDouble("world-settings." + worldName + "." + path, this.config.getDouble("world-settings.default." + path)); + } + + private int getInt(String path, int def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getInt("world-settings." + worldName + "." + path, this.config.getInt("world-settings.default." + path)); + } + + private List getList(String path, T def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getList("world-settings." + worldName + "." + path, this.config.getList("world-settings.default." + path)); + } + + private Map getMap(String path, Map def) { + final Map fallback = this.getMap("world-settings.default." + path, def); + final Map value = this.getMap("world-settings." + worldName + "." + path, null); + return value.isEmpty() ? fallback : value; + } + + public boolean snowballCanKnockback = true; + public boolean eggCanKnockback = true; + private void setSnowballAndEggKnockback() { + snowballCanKnockback = getBoolean("gameplay-mechanics.projectiles.snowball.knockback", snowballCanKnockback); + eggCanKnockback = getBoolean("gameplay-mechanics.projectiles.egg.knockback", eggCanKnockback); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java b/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java index 7949f98..c2cd80e 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java @@ -2,7 +2,6 @@ package org.bxteam.divinemc.command; import net.minecraft.server.MinecraftServer; import org.bukkit.command.Command; -import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.framework.qual.DefaultQualifier; @@ -11,7 +10,7 @@ import java.util.Map; @DefaultQualifier(NonNull.class) public final class DivineCommands { - public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.DIVINEMC_ROOT + ".command"; + public static final String COMMAND_BASE_PERM = "divinemc.command"; private DivineCommands() {} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java b/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java index 9999658..9efae38 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java @@ -8,7 +8,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.permissions.PermissionDefault; import org.bxteam.divinemc.command.DivineCommand; import org.bxteam.divinemc.command.DivineSubCommandPermission; -import org.bxteam.divinemc.configuration.DivineConfig; +import org.bxteam.divinemc.DivineConfig; import java.io.File; @@ -37,7 +37,7 @@ public final class ReloadCommand extends DivineSubCommandPermission { MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); DivineConfig.init((File) server.options.valueOf("divinemc-settings")); for (ServerLevel level : server.getAllLevels()) { - level.divinemcConfig.init(); + level.divineConfig.init(); level.resetBreedingCooldowns(); } server.server.reloadCount++; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java deleted file mode 100644 index 2e6a9d0..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java +++ /dev/null @@ -1,218 +0,0 @@ -package org.bxteam.divinemc.configuration; - -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableMap; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockBehaviour; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; - -@SuppressWarnings("unused") -public class DivineConfig { - private static final String HEADER = "This is the main configuration file for DivineMC.\n" - + "If you need help with the configuration or have any questions related to DivineMC,\n" - + "join us in our Discord server.\n" - + "\n" - + "Discord: https://discord.gg/p7cxhw7E2M \n" - + "Docs: https://bxteam.org/docs/divinemc \n" - + "New builds: https://github.com/BX-Team/DivineMC/releases/latest"; - private static File CONFIG_FILE; - public static YamlConfiguration config; - - private static Map commands; - - public static int version; - static boolean verbose; - - public static void init(File configFile) { - CONFIG_FILE = configFile; - config = new YamlConfiguration(); - try { - config.load(CONFIG_FILE); - } catch (IOException ignore) { - } catch (InvalidConfigurationException ex) { - Bukkit.getLogger().log(Level.SEVERE, "Could not load divinemc.yml, please correct your syntax errors", ex); - throw Throwables.propagate(ex); - } - config.options().header(HEADER); - config.options().copyDefaults(true); - verbose = getBoolean("verbose", false); - - version = getInt("config-version", 4); - set("config-version", 4); - - readConfig(DivineConfig.class, null); - - Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache); - } - - protected static void log(String s) { - if (verbose) { - log(Level.INFO, s); - } - } - - protected static void log(Level level, String s) { - Bukkit.getLogger().log(level, s); - } - - static void readConfig(Class clazz, Object instance) { - for (Method method : clazz.getDeclaredMethods()) { - if (Modifier.isPrivate(method.getModifiers())) { - if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { - try { - method.setAccessible(true); - method.invoke(instance); - } catch (InvocationTargetException ex) { - throw Throwables.propagate(ex.getCause()); - } catch (Exception ex) { - Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); - } - } - } - } - - try { - config.save(CONFIG_FILE); - } catch (IOException ex) { - Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); - } - } - - private static void set(String path, Object val) { - config.addDefault(path, val); - config.set(path, val); - } - - private static String getString(String path, String def) { - config.addDefault(path, def); - return config.getString(path, config.getString(path)); - } - - private static boolean getBoolean(String path, boolean def) { - config.addDefault(path, def); - return config.getBoolean(path, config.getBoolean(path)); - } - - private static double getDouble(String path, double def) { - config.addDefault(path, def); - return config.getDouble(path, config.getDouble(path)); - } - - private static int getInt(String path, int def) { - config.addDefault(path, def); - return config.getInt(path, config.getInt(path)); - } - - private static List getList(String path, T def) { - config.addDefault(path, def); - return config.getList(path, config.getList(path)); - } - - static Map getMap(String path, Map def) { - if (def != null && config.getConfigurationSection(path) == null) { - config.addDefault(path, def); - return def; - } - return toMap(config.getConfigurationSection(path)); - } - - private static Map toMap(ConfigurationSection section) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - if (section != null) { - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj != null) { - builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); - } - } - } - return builder.build(); - } - - public static boolean noChatSign = true; - private static void chatMessageSignatures() { - noChatSign = getBoolean("settings.no-chat-sign", noChatSign); - } - - public static boolean optimizedDragonRespawn = true; - public static boolean lagCompensationEnabled = false; - public static boolean lagCompensationEnableForWater = false; - public static boolean lagCompensationEnableForLava = false; - private static void optimizations() { - optimizedDragonRespawn = getBoolean("settings.optimizations.optimized-dragon-respawn", optimizedDragonRespawn); - lagCompensationEnabled = getBoolean("settings.optimizations.lag-compensation.enabled", lagCompensationEnabled); - lagCompensationEnableForWater = getBoolean("settings.optimizations.lag-compensation.enable-for-water", lagCompensationEnableForWater); - lagCompensationEnableForLava = getBoolean("settings.optimizations.lag-compensation.enable-for-lava", lagCompensationEnableForLava); - } - - public static boolean disableNonEditableSignWarning = true; - public static boolean removeVanillaUsernameCheck = false; - public static boolean disableMovedWronglyThreshold = false; - public static boolean enableSecureSeed = false; - public static boolean asyncPlayerDataSaveEnabled = false; - private static void miscSettings() { - disableNonEditableSignWarning = getBoolean("settings.misc.disable-non-editable-sign-warning", disableNonEditableSignWarning); - removeVanillaUsernameCheck = getBoolean("settings.misc.remove-vanilla-username-check", removeVanillaUsernameCheck); - disableMovedWronglyThreshold = getBoolean("settings.misc.disable-moved-wrongly-threshold", disableMovedWronglyThreshold); - enableSecureSeed = getBoolean("settings.misc.enable-secure-seed", enableSecureSeed); - asyncPlayerDataSaveEnabled = getBoolean("settings.misc.experimental.async-player-data-save-enabled", asyncPlayerDataSaveEnabled); - } - - public static int linearFlushFrequency = 5; - public static boolean throwOnUnknownExtension = false; - private static void linearSettings() { - linearFlushFrequency = getInt("settings.region-format.linear.flush-frequency", linearFlushFrequency); - throwOnUnknownExtension = getBoolean("settings.region-format.linear.throw-on-unknown-extension", throwOnUnknownExtension); - } - - public static boolean asyncPathfinding = true; - public static int asyncPathfindingMaxThreads = 0; - public static int asyncPathfindingKeepalive = 60; - private static void asyncPathfinding() { - asyncPathfinding = getBoolean("settings.async-pathfinding.enable", asyncPathfinding); - asyncPathfindingMaxThreads = getInt("settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads); - asyncPathfindingKeepalive = getInt("settings.async-pathfinding.keepalive", asyncPathfindingKeepalive); - if (asyncPathfindingMaxThreads < 0) - asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1); - else if (asyncPathfindingMaxThreads == 0) - asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); - if (!asyncPathfinding) - asyncPathfindingMaxThreads = 0; - else - Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding"); - } - - public static boolean multithreadedEnabled = false; - public static boolean multithreadedCompatModeEnabled = false; - public static int asyncEntityTrackerMaxThreads = 0; - public static int asyncEntityTrackerKeepalive = 60; - private static void multithreadedTracker() { - multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled); - multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled); - asyncEntityTrackerMaxThreads = getInt("settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads); - asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.keepalive", asyncEntityTrackerKeepalive); - - if (asyncEntityTrackerMaxThreads < 0) - asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); - else if (asyncEntityTrackerMaxThreads == 0) - asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); - - if (!multithreadedEnabled) - asyncEntityTrackerMaxThreads = 0; - else - Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker"); - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java deleted file mode 100644 index f68f4b4..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.bxteam.divinemc.configuration; - -import org.apache.commons.lang.BooleanUtils; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bxteam.divinemc.region.RegionFileFormat; - -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.logging.Level; - -import static org.bxteam.divinemc.configuration.DivineConfig.log; - -@SuppressWarnings("unused") -public class DivineWorldConfig { - private final String worldName; - private final World.Environment environment; - - public DivineWorldConfig(String worldName, World.Environment environment) { - this.worldName = worldName; - this.environment = environment; - init(); - } - - public void init() { - log("-------- World Settings For [" + worldName + "] --------"); - DivineConfig.readConfig(DivineWorldConfig.class, this); - } - - private void set(String path, Object val) { - DivineConfig.config.addDefault("world-settings.default." + path, val); - DivineConfig.config.set("world-settings.default." + path, val); - if (DivineConfig.config.get("world-settings." + worldName + "." + path) != null) { - DivineConfig.config.addDefault("world-settings." + worldName + "." + path, val); - DivineConfig.config.set("world-settings." + worldName + "." + path, val); - } - } - - private ConfigurationSection getConfigurationSection(String path) { - ConfigurationSection section = DivineConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); - return section != null ? section : DivineConfig.config.getConfigurationSection("world-settings.default." + path); - } - - private String getString(String path, String def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getString("world-settings." + worldName + "." + path, DivineConfig.config.getString("world-settings.default." + path)); - } - - private boolean getBoolean(String path, boolean def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getBoolean("world-settings." + worldName + "." + path, DivineConfig.config.getBoolean("world-settings.default." + path)); - } - - private boolean getBoolean(String path, Predicate predicate) { - String val = getString(path, "default").toLowerCase(); - Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); - return predicate.test(bool); - } - - private double getDouble(String path, double def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getDouble("world-settings." + worldName + "." + path, DivineConfig.config.getDouble("world-settings.default." + path)); - } - - private int getInt(String path, int def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getInt("world-settings." + worldName + "." + path, DivineConfig.config.getInt("world-settings.default." + path)); - } - - private List getList(String path, T def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getList("world-settings." + worldName + "." + path, DivineConfig.config.getList("world-settings.default." + path)); - } - - private Map getMap(String path, Map def) { - final Map fallback = DivineConfig.getMap("world-settings.default." + path, def); - final Map value = DivineConfig.getMap("world-settings." + worldName + "." + path, null); - return value.isEmpty() ? fallback : value; - } - - public boolean despawnShulkerBulletsOnOwnerDeath = true; - private void despawnShulkerBulletsOnOwnerDeath() { - despawnShulkerBulletsOnOwnerDeath = getBoolean("gameplay-mechanics.mob.shulker.despawn-bullets-on-player-death", despawnShulkerBulletsOnOwnerDeath); - } - - public boolean saveFireworks = false; - private void projectiles() { - saveFireworks = getBoolean("gameplay-mechanics.should-save-fireworks", saveFireworks); - } - - public boolean suppressErrorsFromDirtyAttributes = true; - private void suppressErrorsFromDirtyAttributes() { - suppressErrorsFromDirtyAttributes = getBoolean("suppress-errors-from-dirty-attributes", suppressErrorsFromDirtyAttributes); - } - - public boolean snowballCanKnockback = true; - public boolean eggCanKnockback = true; - private void setSnowballAndEggKnockback() { - snowballCanKnockback = getBoolean("gameplay-mechanics.projectiles.snowball.knockback", snowballCanKnockback); - eggCanKnockback = getBoolean("gameplay-mechanics.projectiles.egg.knockback", eggCanKnockback); - } - - public RegionFileFormat regionFormatName = RegionFileFormat.MCA; - public int linearCompressionLevel = 1; - private void regionFormatSettings() { - regionFormatName = RegionFileFormat.fromExtension(getString("region-format.format", regionFormatName.name())); - if (regionFormatName.equals(RegionFileFormat.UNKNOWN)) { - log(Level.SEVERE, "Unknown region file type!"); - log(Level.SEVERE, "Falling back to ANVIL region file format."); - regionFormatName = RegionFileFormat.MCA; - } - - linearCompressionLevel = getInt("region-format.linear.compression-level", linearCompressionLevel); - if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { - log(Level.SEVERE, "Linear region compression level should be between 1 and 22 in config: " + linearCompressionLevel); - log(Level.SEVERE, "Falling back to compression level 1."); - linearCompressionLevel = 1; - } - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java new file mode 100644 index 0000000..1fa43ca --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java @@ -0,0 +1,13 @@ +package org.bxteam.divinemc.dfc.common; + +import net.minecraft.world.level.levelgen.NoiseRouterData; + +public interface IDensityFunctionsCaveScaler { + static double invokeScaleCaves(double value) { + return NoiseRouterData.QuantizedSpaghettiRarity.getSphaghettiRarity2D(value); + } + + static double invokeScaleTunnels(double value) { + return NoiseRouterData.QuantizedSpaghettiRarity.getSpaghettiRarity3D(value); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java new file mode 100644 index 0000000..c61fcb7 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java @@ -0,0 +1,22 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.commons.InstructionAdapter; + +public interface AstNode { + double evalSingle(int var1, int var2, int var3, EvalType var4); + + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + AstNode[] getChildren(); + + AstNode transform(AstTransformer var1); + + void doBytecodeGenSingle(BytecodeGen.Context var1, InstructionAdapter var2, BytecodeGen.Context.LocalVarConsumer var3); + + void doBytecodeGenMulti(BytecodeGen.Context var1, InstructionAdapter var2, BytecodeGen.Context.LocalVarConsumer var3); + + boolean relaxedEquals(AstNode var1); + + int relaxedHashCode(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java new file mode 100644 index 0000000..81017d3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ast; + +public interface AstTransformer { + AstNode transform(AstNode var1); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java new file mode 100644 index 0000000..3e19f35 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java @@ -0,0 +1,25 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; + +public enum EvalType { + NORMAL, + INTERPOLATION; + + private EvalType() { + } + + public static EvalType from(DensityFunction.FunctionContext pos) { + return pos instanceof NoiseChunk ? INTERPOLATION : NORMAL; + } + + public static EvalType from(DensityFunction.ContextProvider applier) { + if (applier instanceof EachApplierVanillaInterface vif) { + return vif.getType(); + } else { + return applier instanceof NoiseChunk ? INTERPOLATION : NORMAL; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java new file mode 100644 index 0000000..532e6fb --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java @@ -0,0 +1,106 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.ast.binary.AddNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MaxNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MaxShortNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MinNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MinShortNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MulNode; +import org.bxteam.divinemc.dfc.common.ast.misc.CacheLikeNode; +import org.bxteam.divinemc.dfc.common.ast.misc.ConstantNode; +import org.bxteam.divinemc.dfc.common.ast.misc.DelegateNode; +import org.bxteam.divinemc.dfc.common.ast.misc.RangeChoiceNode; +import org.bxteam.divinemc.dfc.common.ast.misc.YClampedGradientNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTNoiseNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftANode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftBNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTWeirdScaledSamplerNode; +import org.bxteam.divinemc.dfc.common.ast.noise.ShiftedNoiseNode; +import org.bxteam.divinemc.dfc.common.ast.spline.SplineAstNode; +import org.bxteam.divinemc.dfc.common.ast.unary.AbsNode; +import org.bxteam.divinemc.dfc.common.ast.unary.CubeNode; +import org.bxteam.divinemc.dfc.common.ast.unary.NegMulNode; +import org.bxteam.divinemc.dfc.common.ast.unary.SquareNode; +import org.bxteam.divinemc.dfc.common.ast.unary.SqueezeNode; +import org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import org.bxteam.divinemc.dfc.common.vif.AstVanillaInterface; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import net.minecraft.world.level.levelgen.NoiseChunk; +import org.jetbrains.annotations.NotNull; + +public class McToAst { + public McToAst() { + } + + public static AstNode toAst(DensityFunction df) { + Objects.requireNonNull(df); + return switch (df) { + case AstVanillaInterface f -> f.getAstNode(); + + case NoiseChunk.BlendAlpha f -> new ConstantNode(1.0); + case NoiseChunk.BlendOffset f -> new ConstantNode(0.0); + case DensityFunctions.BlendAlpha f -> new ConstantNode(1.0); + case DensityFunctions.BlendOffset f -> new ConstantNode(0.0); + case DensityFunctions.TwoArgumentSimpleFunction f -> switch (f.type()) { + case ADD -> new AddNode(toAst(f.argument1()), toAst(f.argument2())); + case MUL -> new MulNode(toAst(f.argument1()), toAst(f.argument2())); + case MIN -> { + double rightMin = f.argument2().minValue(); + if (f.argument1().minValue() < rightMin) { + yield new MinShortNode(toAst(f.argument1()), toAst(f.argument2()), rightMin); + } else { + yield new MinNode(toAst(f.argument1()), toAst(f.argument2())); + } + } + case MAX -> { + double rightMax = f.argument2().maxValue(); + if (f.argument1().maxValue() > rightMax) { + yield new MaxShortNode(toAst(f.argument1()), toAst(f.argument2()), rightMax); + } else { + yield new MaxNode(toAst(f.argument1()), toAst(f.argument2())); + } + } + }; + case DensityFunctions.BlendDensity f -> toAst(f.input()); + case DensityFunctions.Clamp f -> new MaxNode(new ConstantNode(f.minValue()), new MinNode(new ConstantNode(f.maxValue()), toAst(f.input()))); + case DensityFunctions.Constant f -> new ConstantNode(f.value()); + case DensityFunctions.HolderHolder f -> toAst(f.function().value()); + case DensityFunctions.Mapped f -> switch (f.type()) { + case ABS -> new AbsNode(toAst(f.input())); + case SQUARE -> new SquareNode(toAst(f.input())); + case CUBE -> new CubeNode(toAst(f.input())); + case HALF_NEGATIVE -> new NegMulNode(toAst(f.input()), 0.5); + case QUARTER_NEGATIVE -> new NegMulNode(toAst(f.input()), 0.25); + case SQUEEZE -> new SqueezeNode(toAst(f.input())); + }; + case DensityFunctions.RangeChoice f -> new RangeChoiceNode(toAst(f.input()), f.minInclusive(), f.maxExclusive(), toAst(f.whenInRange()), toAst(f.whenOutOfRange())); + case DensityFunctions.Marker f -> { + DensityFunctions.Marker wrapping = new DensityFunctions.Marker(f.type(), new AstVanillaInterface(toAst(f.wrapped()), null)); + ((IEqualityOverriding) (Object) wrapping).c2me$overrideEquality(wrapping); + yield new DelegateNode(wrapping); + } + case IFastCacheLike f -> new CacheLikeNode(f, toAst(f.c2me$getDelegate())); + case DensityFunctions.ShiftedNoise f -> new ShiftedNoiseNode(toAst(f.shiftX()), toAst(f.shiftY()), toAst(f.shiftZ()), f.xzScale(), f.yScale(), f.noise()); + case DensityFunctions.Noise f -> new DFTNoiseNode(f.noise(), f.xzScale(), f.yScale()); + case DensityFunctions.Shift f -> new DFTShiftNode(f.offsetNoise()); + case DensityFunctions.ShiftA f -> new DFTShiftANode(f.offsetNoise()); + case DensityFunctions.ShiftB f -> new DFTShiftBNode(f.offsetNoise()); + case DensityFunctions.YClampedGradient f -> new YClampedGradientNode(f.fromY(), f.toY(), f.fromValue(), f.toValue()); + case DensityFunctions.WeirdScaledSampler f -> new DFTWeirdScaledSamplerNode(toAst(f.input()), f.noise(), f.rarityValueMapper()); + case DensityFunctions.Spline f -> new SplineAstNode(f.spline()); + + default -> { +// delegateStatistics.computeIfAbsent(df.getClass(), unused -> new LongAdder()).increment();; + yield new DelegateNode(df); + } + }; + } + + public static @NotNull DensityFunction wrapVanilla(DensityFunction densityFunction) { + return new AstVanillaInterface(toAst(densityFunction), densityFunction); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java new file mode 100644 index 0000000..7c01c36 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java @@ -0,0 +1,106 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public abstract class AbstractBinaryNode implements AstNode { + protected final AstNode left; + protected final AstNode right; + + public AbstractBinaryNode(AstNode left, AstNode right) { + this.left = (AstNode)Objects.requireNonNull(left); + this.right = (AstNode)Objects.requireNonNull(right); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.left, this.right}; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractBinaryNode that = (AbstractBinaryNode)o; + return Objects.equals(this.left, that.left) && Objects.equals(this.right, that.right); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.left.hashCode(); + result = 31 * result + this.right.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractBinaryNode that = (AbstractBinaryNode)o; + return this.left.relaxedEquals(that.left) && this.right.relaxedEquals(that.right); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.left.relaxedHashCode(); + result = 31 * result + this.right.relaxedHashCode(); + return result; + } + + protected abstract AstNode newInstance(AstNode var1, AstNode var2); + + public AstNode transform(AstTransformer transformer) { + AstNode left = this.left.transform(transformer); + AstNode right = this.right.transform(transformer); + return left == this.left && right == this.right ? transformer.transform(this) : transformer.transform(this.newInstance(left, right)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + context.callDelegateSingle(m, leftMethod); + context.callDelegateSingle(m, rightMethod); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethod = context.newMultiMethod(this.right); + int res1 = localVarConsumer.createLocalVariable("res1", Type.getDescriptor(double[].class)); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res1, InstructionAdapter.OBJECT_TYPE); + context.callDelegateMulti(m, leftMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethod, Context.MULTI_DESC, false); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + this.bytecodeGenMultiBody(m, idx, res1); + }); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.areturn(Type.VOID_TYPE); + } + + protected abstract void bytecodeGenMultiBody(InstructionAdapter var1, int var2, int var3); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java new file mode 100644 index 0000000..53b50d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class AddNode extends AbstractBinaryNode { + public AddNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new AddNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.left.evalSingle(x, y, z, type) + this.right.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] += res1[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.add(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java new file mode 100644 index 0000000..bed1a4c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MaxNode extends AbstractBinaryNode { + public MaxNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MaxNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.max(this.left.evalSingle(x, y, z, type), this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] = Math.max(res[i], res1[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java new file mode 100644 index 0000000..8cab854 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MaxShortNode extends AbstractBinaryNode { + private final double rightMax; + + public MaxShortNode(AstNode left, AstNode right, double rightMax) { + super(left, right); + this.rightMax = rightMax; + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MaxShortNode(left, right, this.rightMax); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled >= this.rightMax ? evaled : Math.max(evaled, this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] >= this.rightMax ? res[i] : Math.max(res[i], this.right.evalSingle(x[i], y[i], z[i], type)); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label minLabel = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(this.rightMax); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(minLabel); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(minLabel); + context.callDelegateSingle(m, rightMethod); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(this.rightMax); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(minLabel); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java new file mode 100644 index 0000000..909180b --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MinNode extends AbstractBinaryNode { + public MinNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MinNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.min(this.left.evalSingle(x, y, z, type), this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] = Math.min(res[i], res1[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java new file mode 100644 index 0000000..615ca64 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MinShortNode extends AbstractBinaryNode { + private final double rightMin; + + public MinShortNode(AstNode left, AstNode right, double rightMin) { + super(left, right); + this.rightMin = rightMin; + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MinShortNode(left, right, this.rightMin); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled <= this.rightMin ? evaled : Math.min(evaled, this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] <= this.rightMin ? res[i] : Math.min(res[i], this.right.evalSingle(x[i], y[i], z[i], type)); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label minLabel = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(this.rightMin); + m.cmpg(Type.DOUBLE_TYPE); + m.ifgt(minLabel); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(minLabel); + context.callDelegateSingle(m, rightMethod); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(this.rightMin); + m.cmpg(Type.DOUBLE_TYPE); + m.ifgt(minLabel); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java new file mode 100644 index 0000000..87cc44c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MulNode extends AbstractBinaryNode { + public MulNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MulNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled == 0.0 ? 0.0 : evaled * this.right.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] == 0.0 ? 0.0 : res[i] * this.right.evalSingle(x[i], y[i], z[i], type); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label notZero = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifne(notZero); + m.dconst(0.0); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(notZero); + context.callDelegateSingle(m, rightMethod); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifne(minLabel); + m.pop2(); + m.dconst(0.0); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java new file mode 100644 index 0000000..1144bfa --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java @@ -0,0 +1,23 @@ +package org.bxteam.divinemc.dfc.common.ast.dfvisitor; + +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import net.minecraft.world.level.levelgen.NoiseChunk; +import org.jetbrains.annotations.NotNull; + +public class StripBlending implements DensityFunction.Visitor { + public static final StripBlending INSTANCE = new StripBlending(); + + private StripBlending() { + } + + public @NotNull DensityFunction apply(@NotNull DensityFunction densityFunction) { + return switch (densityFunction) { + case NoiseChunk.BlendAlpha _ -> DensityFunctions.constant(1.0); + case NoiseChunk.BlendOffset _ -> DensityFunctions.constant(0.0); + case DensityFunctions.BlendAlpha _ -> DensityFunctions.constant(1.0); + case DensityFunctions.BlendOffset _ -> DensityFunctions.constant(0.0); + default -> densityFunction; + }; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java new file mode 100644 index 0000000..d9a2689 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java @@ -0,0 +1,256 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.gen.IMultiMethod; +import org.bxteam.divinemc.dfc.common.gen.ISingleMethod; +import org.bxteam.divinemc.dfc.common.gen.SubCompiledDensityFunction; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class CacheLikeNode implements AstNode { + private final IFastCacheLike cacheLike; + private final AstNode delegate; + + public CacheLikeNode(IFastCacheLike cacheLike, AstNode delegate) { + this.cacheLike = cacheLike; + this.delegate = (AstNode)Objects.requireNonNull(delegate); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + if (this.cacheLike == null) { + return this.delegate.evalSingle(x, y, z, type); + } else { + double cached = this.cacheLike.c2me$getCached(x, y, z, type); + if (Double.doubleToRawLongBits(cached) != 9222769054270909007L) { + return cached; + } else { + double eval = this.delegate.evalSingle(x, y, z, type); + this.cacheLike.c2me$cache(x, y, z, type, eval); + return eval; + } + } + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (this.cacheLike == null) { + this.delegate.evalMulti(res, x, y, z, type); + } else { + boolean cached = this.cacheLike.c2me$getCached(res, x, y, z, type); + if (!cached) { + this.delegate.evalMulti(res, x, y, z, type); + this.cacheLike.c2me$cache(res, x, y, z, type); + } + + } + } + + public AstNode[] getChildren() { + return new AstNode[]{this.delegate}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode delegate = this.delegate.transform(transformer); + return this.delegate == delegate ? transformer.transform(this) : transformer.transform(new CacheLikeNode(this.cacheLike, delegate)); + } + + public void doBytecodeGenSingle(@NotNull Context context, @NotNull InstructionAdapter m, Context.@NotNull LocalVarConsumer localVarConsumer) { + String delegateMethod = context.newSingleMethod(this.delegate); + String cacheLikeField = context.newField(IFastCacheLike.class, this.cacheLike); + this.genPostprocessingMethod(context, cacheLikeField); + int eval = localVarConsumer.createLocalVariable("eval", Type.DOUBLE_TYPE.getDescriptor()); + Label cacheExists = new Label(); + Label cacheMiss = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.ifnonnull(cacheExists); + context.callDelegateSingle(m, delegateMethod); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(cacheExists); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$getCached", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)})); + m.dup2(); + m.invokestatic(Type.getInternalName(Double.class), "doubleToRawLongBits", Type.getMethodDescriptor(Type.LONG_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.lconst(9222769054270909007L); + m.lcmp(); + m.ifeq(cacheMiss); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(cacheMiss); + m.pop2(); + context.callDelegateSingle(m, delegateMethod); + m.store(eval, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(eval, Type.DOUBLE_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$cache", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class), Type.DOUBLE_TYPE})); + m.load(eval, Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(@NotNull Context context, @NotNull InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String delegateMethod = context.newMultiMethod(this.delegate); + String cacheLikeField = context.newField(IFastCacheLike.class, this.cacheLike); + this.genPostprocessingMethod(context, cacheLikeField); + Label cacheExists = new Label(); + Label cacheMiss = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.ifnonnull(cacheExists); + context.callDelegateMulti(m, delegateMethod); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheExists); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$getCached", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class)})); + m.ifeq(cacheMiss); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheMiss); + context.callDelegateMulti(m, delegateMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$cache", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class)})); + m.areturn(Type.VOID_TYPE); + } + + private void genPostprocessingMethod(@NotNull Context context, String cacheLikeField) { + String methodName = String.format("postProcessing_%s", cacheLikeField); + String delegateSingle = context.newSingleMethod(this.delegate); + String delegateMulti = context.newMultiMethod(this.delegate); + context.genPostprocessingMethod(methodName, (m) -> { + Label cacheExists = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.dup(); + m.ifnonnull(cacheExists); + m.pop(); + m.pop(); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheExists); + m.anew(Type.getType(SubCompiledDensityFunction.class)); + m.dup(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokedynamic("evalSingle", Type.getMethodDescriptor(Type.getType(ISingleMethod.class), new Type[]{Type.getType(context.classDesc)}), new Handle(6, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[]{Type.getMethodType(Context.SINGLE_DESC), new Handle(5, context.className, delegateSingle, Context.SINGLE_DESC, false), Type.getMethodType(Context.SINGLE_DESC)}); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokedynamic("evalMulti", Type.getMethodDescriptor(Type.getType(IMultiMethod.class), new Type[]{Type.getType(context.classDesc)}), new Handle(6, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[]{Type.getMethodType(Context.MULTI_DESC), new Handle(5, context.className, delegateMulti, Context.MULTI_DESC, false), Type.getMethodType(Context.MULTI_DESC)}); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.checkcast(Type.getType(DensityFunction.class)); + m.invokespecial(Type.getInternalName(SubCompiledDensityFunction.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(ISingleMethod.class), Type.getType(IMultiMethod.class), Type.getType(DensityFunction.class)}), false); + m.checkcast(Type.getType(DensityFunction.class)); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$withDelegate", Type.getMethodDescriptor(Type.getType(DensityFunction.class), new Type[]{Type.getType(DensityFunction.class)})); + m.putfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.areturn(Type.VOID_TYPE); + }); + } + + public IFastCacheLike getCacheLike() { + return this.cacheLike; + } + + public AstNode getDelegate() { + return this.delegate; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CacheLikeNode that = (CacheLikeNode)o; + return equals(this.cacheLike, that.cacheLike) && Objects.equals(this.delegate, that.delegate); + } else { + return false; + } + } + + private static boolean equals(IFastCacheLike a, IFastCacheLike b) { + if (a instanceof DensityFunctions.Marker wrappingA) { + if (b instanceof DensityFunctions.Marker wrappingB) { + return wrappingA.type() == wrappingB.type(); + } + } + + return a.equals(b); + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + hashCode(this.cacheLike); + result = 31 * result + this.delegate.hashCode(); + return result; + } + + private static int hashCode(IFastCacheLike o) { + if (o instanceof DensityFunctions.Marker wrapping) { + return wrapping.type().hashCode(); + } else { + return o.hashCode(); + } + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CacheLikeNode that = (CacheLikeNode)o; + return relaxedEquals(this.cacheLike, that.cacheLike) && this.delegate.relaxedEquals(that.delegate); + } else { + return false; + } + } + + private static boolean relaxedEquals(IFastCacheLike a, IFastCacheLike b) { + if (a instanceof DensityFunctions.Marker wrappingA) { + if (b instanceof DensityFunctions.Marker wrappingB) { + return wrappingA.type() == wrappingB.type(); + } + } + + return a.getClass() == b.getClass(); + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + relaxedHashCode(this.cacheLike); + result = 31 * result + this.delegate.relaxedHashCode(); + return result; + } + + private static int relaxedHashCode(IFastCacheLike o) { + if (o instanceof DensityFunctions.Marker wrapping) { + return wrapping.type().hashCode(); + } else { + return o.getClass().hashCode(); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java new file mode 100644 index 0000000..558bc3a --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java @@ -0,0 +1,72 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Arrays; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class ConstantNode implements AstNode { + private final double value; + + public ConstantNode(double value) { + this.value = value; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.value; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + Arrays.fill(res, this.value); + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.dconst(this.value); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.dconst(this.value); + m.invokestatic(Type.getInternalName(Arrays.class), "fill", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.DOUBLE_TYPE}), false); + m.areturn(Type.VOID_TYPE); + } + + public double getValue() { + return this.value; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ConstantNode that = (ConstantNode)o; + return Double.compare(this.value, that.value) == 0; + } else { + return false; + } + } + + public int hashCode() { + return Double.hashCode(this.value); + } + + public boolean relaxedEquals(AstNode o) { + return this.equals(o); + } + + public int relaxedHashCode() { + return this.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java new file mode 100644 index 0000000..050d9ff --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java @@ -0,0 +1,140 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DelegateNode implements AstNode { + private final DensityFunction densityFunction; + + public DelegateNode(DensityFunction densityFunction) { + this.densityFunction = Objects.requireNonNull(densityFunction); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.densityFunction.compute(new NoisePosVanillaInterface(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = this.evalSingle(x[0], y[0], z[0], type); + } else { + this.densityFunction.fillArray(res, new EachApplierVanillaInterface(x, y, z, type)); + } + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String newField = context.newField(DensityFunction.class, this.densityFunction); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.anew(Type.getType(NoisePosVanillaInterface.class)); + m.dup(); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(NoisePosVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "compute", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.getType(DensityFunction.FunctionContext.class))); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String newField = context.newField(DensityFunction.class, this.densityFunction); + Label moreThanTwoLabel = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(1); + m.ificmpgt(moreThanTwoLabel); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.anew(Type.getType(NoisePosVanillaInterface.class)); + m.dup(); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(NoisePosVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "compute", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.getType(DensityFunction.FunctionContext.class))); + m.astore(Type.DOUBLE_TYPE); + m.areturn(Type.VOID_TYPE); + m.visitLabel(moreThanTwoLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.anew(Type.getType(EachApplierVanillaInterface.class)); + m.dup(); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(EachApplierVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class), Type.getType(ArrayCache.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "fillArray", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(double[].class), Type.getType(DensityFunction.ContextProvider.class))); + m.areturn(Type.VOID_TYPE); + } + + public DensityFunction getDelegate() { + return this.densityFunction; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DelegateNode that = (DelegateNode)o; + return Objects.equals(this.densityFunction, that.densityFunction); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.getClass()); + result = 31 * result + Objects.hashCode(this.densityFunction); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DelegateNode that = (DelegateNode)o; + return this.densityFunction.getClass() == that.densityFunction.getClass(); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.getClass()); + result = 31 * result + Objects.hashCode(this.densityFunction.getClass()); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java new file mode 100644 index 0000000..b3ce05b --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java @@ -0,0 +1,337 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import java.util.Objects; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class RangeChoiceNode implements AstNode { + private final AstNode input; + private final double minInclusive; + private final double maxExclusive; + private final AstNode whenInRange; + private final AstNode whenOutOfRange; + + public RangeChoiceNode(AstNode input, double minInclusive, double maxExclusive, AstNode whenInRange, AstNode whenOutOfRange) { + this.input = (AstNode)Objects.requireNonNull(input); + this.minInclusive = minInclusive; + this.maxExclusive = maxExclusive; + this.whenInRange = (AstNode)Objects.requireNonNull(whenInRange); + this.whenOutOfRange = (AstNode)Objects.requireNonNull(whenOutOfRange); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.input.evalSingle(x, y, z, type); + return v >= this.minInclusive && v < this.maxExclusive ? this.whenInRange.evalSingle(x, y, z, type) : this.whenOutOfRange.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.input.evalMulti(res, x, y, z, type); + int numInRange = 0; + + int numOutOfRange; + for(numOutOfRange = 0; numOutOfRange < x.length; ++numOutOfRange) { + double v = res[numOutOfRange]; + if (v >= this.minInclusive && v < this.maxExclusive) { + ++numInRange; + } + } + + numOutOfRange = res.length - numInRange; + if (numInRange == 0) { + this.evalChildMulti(this.whenOutOfRange, res, x, y, z, type); + } else if (numInRange == res.length) { + this.evalChildMulti(this.whenInRange, res, x, y, z, type); + } else { + int idx1 = 0; + int[] i1 = new int[numInRange]; + double[] res1 = new double[numInRange]; + int[] x1 = new int[numInRange]; + int[] y1 = new int[numInRange]; + int[] z1 = new int[numInRange]; + int idx2 = 0; + int[] i2 = new int[numOutOfRange]; + double[] res2 = new double[numOutOfRange]; + int[] x2 = new int[numOutOfRange]; + int[] y2 = new int[numOutOfRange]; + int[] z2 = new int[numOutOfRange]; + + int i; + for(i = 0; i < res.length; ++i) { + double v = res[i]; + int index; + if (v >= this.minInclusive && v < this.maxExclusive) { + index = idx1++; + i1[index] = i; + x1[index] = x[i]; + y1[index] = y[i]; + z1[index] = z[i]; + } else { + index = idx2++; + i2[index] = i; + x2[index] = x[i]; + y2[index] = y[i]; + z2[index] = z[i]; + } + } + + this.evalChildMulti(this.whenInRange, res1, x1, y1, z1, type); + this.evalChildMulti(this.whenOutOfRange, res2, x2, y2, z2, type); + + for(i = 0; i < numInRange; ++i) { + res[i1[i]] = res1[i]; + } + + for(i = 0; i < numOutOfRange; ++i) { + res[i2[i]] = res2[i]; + } + } + + } + + public static void evalMultiStatic(double[] res, int[] x, int[] y, int[] z, EvalType type, double minInclusive, double maxExclusive, BytecodeGen.EvalSingleInterface whenInRangeSingle, BytecodeGen.EvalSingleInterface whenOutOfRangeSingle, BytecodeGen.EvalMultiInterface inputMulti, BytecodeGen.EvalMultiInterface whenInRangeMulti, BytecodeGen.EvalMultiInterface whenOutOfRangeMulti) { + inputMulti.evalMulti(res, x, y, z, type); + int numInRange = 0; + + int numOutOfRange; + for(numOutOfRange = 0; numOutOfRange < x.length; ++numOutOfRange) { + double v = res[numOutOfRange]; + if (v >= minInclusive && v < maxExclusive) { + ++numInRange; + } + } + + numOutOfRange = res.length - numInRange; + if (numInRange == 0) { + evalChildMulti(whenOutOfRangeSingle, whenOutOfRangeMulti, res, x, y, z, type); + } else if (numInRange == res.length) { + evalChildMulti(whenInRangeSingle, whenInRangeMulti, res, x, y, z, type); + } else { + int idx1 = 0; + int[] i1 = new int[numInRange]; + double[] res1 = new double[numInRange]; + int[] x1 = new int[numInRange]; + int[] y1 = new int[numInRange]; + int[] z1 = new int[numInRange]; + int idx2 = 0; + int[] i2 = new int[numOutOfRange]; + double[] res2 = new double[numOutOfRange]; + int[] x2 = new int[numOutOfRange]; + int[] y2 = new int[numOutOfRange]; + int[] z2 = new int[numOutOfRange]; + + int i; + for(i = 0; i < res.length; ++i) { + double v = res[i]; + int index; + if (v >= minInclusive && v < maxExclusive) { + index = idx1++; + i1[index] = i; + x1[index] = x[i]; + y1[index] = y[i]; + z1[index] = z[i]; + } else { + index = idx2++; + i2[index] = i; + x2[index] = x[i]; + y2[index] = y[i]; + z2[index] = z[i]; + } + } + + evalChildMulti(whenInRangeSingle, whenInRangeMulti, res1, x1, y1, z1, type); + evalChildMulti(whenOutOfRangeSingle, whenOutOfRangeMulti, res2, x2, y2, z2, type); + + for(i = 0; i < numInRange; ++i) { + res[i1[i]] = res1[i]; + } + + for(i = 0; i < numOutOfRange; ++i) { + res[i2[i]] = res2[i]; + } + } + + } + + private static void evalChildMulti(BytecodeGen.EvalSingleInterface single, BytecodeGen.EvalMultiInterface multi, double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = single.evalSingle(x[0], y[0], z[0], type); + } else { + multi.evalMulti(res, x, y, z, type); + } + + } + + private void evalChildMulti(AstNode child, double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = child.evalSingle(x[0], y[0], z[0], type); + } else { + child.evalMulti(res, x, y, z, type); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.input, this.whenInRange, this.whenOutOfRange}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode input = this.input.transform(transformer); + AstNode whenInRange = this.whenInRange.transform(transformer); + AstNode whenOutOfRange = this.whenOutOfRange.transform(transformer); + return this.input == input && this.whenInRange == whenInRange && this.whenOutOfRange == whenOutOfRange ? transformer.transform(this) : transformer.transform(new RangeChoiceNode(input, this.minInclusive, this.maxExclusive, whenInRange, whenOutOfRange)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String inputMethod = context.newSingleMethod(this.input); + String whenInRangeMethod = context.newSingleMethod(this.whenInRange); + String whenOutOfRangeMethod = context.newSingleMethod(this.whenOutOfRange); + int inputValue = localVarConsumer.createLocalVariable("inputValue", Type.DOUBLE_TYPE.getDescriptor()); + context.callDelegateSingle(m, inputMethod); + m.store(inputValue, Type.DOUBLE_TYPE); + Label whenOutOfRangeLabel = new Label(); + Label end = new Label(); + m.load(inputValue, Type.DOUBLE_TYPE); + m.dconst(this.minInclusive); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(whenOutOfRangeLabel); + m.load(inputValue, Type.DOUBLE_TYPE); + m.dconst(this.maxExclusive); + m.cmpg(Type.DOUBLE_TYPE); + m.ifge(whenOutOfRangeLabel); + if (whenInRangeMethod.equals(inputMethod)) { + m.load(inputValue, Type.DOUBLE_TYPE); + } else { + context.callDelegateSingle(m, whenInRangeMethod); + } + + m.goTo(end); + m.visitLabel(whenOutOfRangeLabel); + if (whenOutOfRangeMethod.equals(inputMethod)) { + m.load(inputValue, Type.DOUBLE_TYPE); + } else { + context.callDelegateSingle(m, whenOutOfRangeMethod); + } + + m.visitLabel(end); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String inputSingle = context.newSingleMethod(this.input); + String whenInRangeSingle = context.newSingleMethod(this.whenInRange); + String whenOutOfRangeSingle = context.newSingleMethod(this.whenOutOfRange); + String inputMulti = context.newMultiMethod(this.input); + context.callDelegateMulti(m, inputMulti); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label whenOutOfRangeLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dconst(this.minInclusive); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(whenOutOfRangeLabel); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dconst(this.maxExclusive); + m.cmpg(Type.DOUBLE_TYPE); + m.ifge(whenOutOfRangeLabel); + if (whenInRangeSingle.equals(inputSingle)) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, whenInRangeSingle, Context.SINGLE_DESC, false); + } + + m.goTo(end); + m.visitLabel(whenOutOfRangeLabel); + if (whenOutOfRangeSingle.equals(inputSingle)) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, whenOutOfRangeSingle, Context.SINGLE_DESC, false); + } + + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RangeChoiceNode that = (RangeChoiceNode)o; + return Double.compare(this.minInclusive, that.minInclusive) == 0 && Double.compare(this.maxExclusive, that.maxExclusive) == 0 && Objects.equals(this.input, that.input) && Objects.equals(this.whenInRange, that.whenInRange) && Objects.equals(this.whenOutOfRange, that.whenOutOfRange); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.hashCode(); + result = 31 * result + Double.hashCode(this.minInclusive); + result = 31 * result + Double.hashCode(this.maxExclusive); + result = 31 * result + this.whenInRange.hashCode(); + result = 31 * result + this.whenOutOfRange.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RangeChoiceNode that = (RangeChoiceNode)o; + return Double.compare(this.minInclusive, that.minInclusive) == 0 && Double.compare(this.maxExclusive, that.maxExclusive) == 0 && this.input.relaxedEquals(that.input) && this.whenInRange.relaxedEquals(that.whenInRange) && this.whenOutOfRange.relaxedEquals(that.whenOutOfRange); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.relaxedHashCode(); + result = 31 * result + Double.hashCode(this.minInclusive); + result = 31 * result + Double.hashCode(this.maxExclusive); + result = 31 * result + this.whenInRange.relaxedHashCode(); + result = 31 * result + this.whenOutOfRange.relaxedHashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java new file mode 100644 index 0000000..4c6001f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java @@ -0,0 +1,82 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class RootNode implements AstNode { + private final AstNode next; + + public RootNode(AstNode next) { + this.next = (AstNode)Objects.requireNonNull(next); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.next.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.next.evalMulti(res, x, y, z, type); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.next}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode next = this.next.transform(transformer); + return next == this.next ? transformer.transform(this) : transformer.transform(new RootNode(next)); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String nextMethod = context.newSingleMethod(this.next); + context.callDelegateSingle(m, nextMethod); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String nextMethod = context.newMultiMethod(this.next); + context.callDelegateMulti(m, nextMethod); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RootNode that = (RootNode)o; + return Objects.equals(this.next, that.next); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.next.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RootNode that = (RootNode)o; + return this.next.relaxedEquals(that.next); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.next.relaxedHashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java new file mode 100644 index 0000000..0eb4c48 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java @@ -0,0 +1,100 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import net.minecraft.util.Mth; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class YClampedGradientNode implements AstNode { + private final double fromY; + private final double toY; + private final double fromValue; + private final double toValue; + + public YClampedGradientNode(double fromY, double toY, double fromValue, double toValue) { + this.fromY = fromY; + this.toY = toY; + this.fromValue = fromValue; + this.toValue = toValue; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Mth.clampedMap((double)y, this.fromY, this.toY, this.fromValue, this.toValue); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = Mth.clampedMap((double)y[i], this.fromY, this.toY, this.fromValue, this.toValue); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.fromY); + m.dconst(this.toY); + m.dconst(this.fromValue); + m.dconst(this.toValue); + m.invokestatic(Type.getInternalName(Mth.class), "clampedMap", "(DDDDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.fromY); + m.dconst(this.toY); + m.dconst(this.fromValue); + m.dconst(this.toValue); + m.invokestatic(Type.getInternalName(Mth.class), "clampedMap", "(DDDDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + YClampedGradientNode that = (YClampedGradientNode)o; + return Double.compare(this.fromY, that.fromY) == 0 && Double.compare(this.toY, that.toY) == 0 && Double.compare(this.fromValue, that.fromValue) == 0 && Double.compare(this.toValue, that.toValue) == 0; + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + Double.hashCode(this.fromY); + result = 31 * result + Double.hashCode(this.toY); + result = 31 * result + Double.hashCode(this.fromValue); + result = 31 * result + Double.hashCode(this.toValue); + return result; + } + + public boolean relaxedEquals(AstNode o) { + return this.equals(o); + } + + public int relaxedHashCode() { + return this.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java new file mode 100644 index 0000000..76b1899 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java @@ -0,0 +1,131 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTNoiseNode implements AstNode { + private final DensityFunction.NoiseHolder noise; + private final double xzScale; + private final double yScale; + + public DFTNoiseNode(DensityFunction.NoiseHolder noise, double xzScale, double yScale) { + this.noise = (DensityFunction.NoiseHolder)Objects.requireNonNull(noise); + this.xzScale = xzScale; + this.yScale = yScale; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.noise.getValue((double)x * this.xzScale, (double)y * this.yScale, (double)z * this.xzScale); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.noise.getValue((double)x[i] * this.xzScale, (double)y[i] * this.yScale, (double)z[i] * this.xzScale); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTNoiseNode that = (DFTNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && Objects.equals(this.noise, that.noise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.noise.hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTNoiseNode that = (DFTNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0; + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java new file mode 100644 index 0000000..499c09f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java @@ -0,0 +1,115 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftANode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftANode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)x * 0.25, 0.0, (double)z * 0.25) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)x[i] * 0.25, 0.0, (double)z[i] * 0.25) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftANode that = (DFTShiftANode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java new file mode 100644 index 0000000..1611540 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java @@ -0,0 +1,115 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftBNode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftBNode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)z * 0.25, (double)x * 0.25, 0.0) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)z[i] * 0.25, (double)x[i] * 0.25, 0.0) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftBNode that = (DFTShiftBNode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java new file mode 100644 index 0000000..cff0089 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java @@ -0,0 +1,123 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftNode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftNode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)x * 0.25, (double)y * 0.25, (double)z * 0.25) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)x[i] * 0.25, (double)y[i] * 0.25, (double)z[i] * 0.25) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftNode that = (DFTShiftNode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java new file mode 100644 index 0000000..9f9c368 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java @@ -0,0 +1,169 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.IDensityFunctionsCaveScaler; +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTWeirdScaledSamplerNode implements AstNode { + private final AstNode input; + private final DensityFunction.NoiseHolder noise; + private final DensityFunctions.WeirdScaledSampler.RarityValueMapper mapper; + + public DFTWeirdScaledSamplerNode(AstNode input, DensityFunction.NoiseHolder noise, DensityFunctions.WeirdScaledSampler.RarityValueMapper mapper) { + this.input = Objects.requireNonNull(input); + this.noise = Objects.requireNonNull(noise); + this.mapper = Objects.requireNonNull(mapper); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.input.evalSingle(x, y, z, type); + double d = (this.mapper.mapper).get(v); + return d * Math.abs(this.noise.getValue((double)x / d, (double)y / d, (double)z / d)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.input.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double d = (this.mapper.mapper).get(res[i]); + res[i] = d * Math.abs(this.noise.getValue((double)x[i] / d, (double)y[i] / d, (double)z[i] / d)); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.input}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode input = this.input.transform(transformer); + return input == this.input ? transformer.transform(this) : transformer.transform(new DFTWeirdScaledSamplerNode(input, this.noise, this.mapper)); + } + + public void doBytecodeGenSingle(BytecodeGen.@NotNull Context context, InstructionAdapter m, BytecodeGen.Context.@NotNull LocalVarConsumer localVarConsumer) { + String inputMethod = context.newSingleMethod(this.input); + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + int scale = localVarConsumer.createLocalVariable("scale", Type.DOUBLE_TYPE.getDescriptor()); + context.callDelegateSingle(m, inputMethod); + switch (this.mapper) { + case TYPE1 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleTunnels", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + case TYPE2 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleCaves", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + default -> throw new UnsupportedOperationException(String.format("Unknown mapper %s", this.mapper)); + } + + m.store(scale, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.invokestatic(Type.getInternalName(Math.class), "abs", "(D)D", false); + m.load(scale, Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String inputMethod = context.newMultiMethod(this.input); + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + context.callDelegateMulti(m, inputMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int scale = localVarConsumer.createLocalVariable("scale", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + switch (this.mapper) { + case TYPE1 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleTunnels", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + case TYPE2 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleCaves", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + default -> throw new UnsupportedOperationException(String.format("Unknown mapper %s", this.mapper)); + } + + m.store(scale, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.invokestatic(Type.getInternalName(Math.class), "abs", "(D)D", false); + m.load(scale, Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTWeirdScaledSamplerNode that = (DFTWeirdScaledSamplerNode)o; + return Objects.equals(this.input, that.input) && Objects.equals(this.noise, that.noise) && this.mapper == that.mapper; + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.hashCode(); + result = 31 * result + this.noise.hashCode(); + result = 31 * result + this.mapper.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTWeirdScaledSamplerNode that = (DFTWeirdScaledSamplerNode)o; + return this.input.relaxedEquals(that.input) && this.mapper == that.mapper; + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.relaxedHashCode(); + result = 31 * result + this.mapper.hashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java new file mode 100644 index 0000000..75c82a6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java @@ -0,0 +1,215 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class ShiftedNoiseNode implements AstNode { + private final AstNode shiftX; + private final AstNode shiftY; + private final AstNode shiftZ; + private final double xzScale; + private final double yScale; + private final DensityFunction.NoiseHolder noise; + + public ShiftedNoiseNode(AstNode shiftX, AstNode shiftY, AstNode shiftZ, double xzScale, double yScale, DensityFunction.NoiseHolder noise) { + this.shiftX = (AstNode)Objects.requireNonNull(shiftX); + this.shiftY = (AstNode)Objects.requireNonNull(shiftY); + this.shiftZ = (AstNode)Objects.requireNonNull(shiftZ); + this.xzScale = xzScale; + this.yScale = yScale; + this.noise = (DensityFunction.NoiseHolder)Objects.requireNonNull(noise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double d = (double)x * this.xzScale + this.shiftX.evalSingle(x, y, z, type); + double e = (double)y * this.yScale + this.shiftY.evalSingle(x, y, z, type); + double f = (double)z * this.xzScale + this.shiftZ.evalSingle(x, y, z, type); + return this.noise.getValue(d, e, f); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + double[] res2 = new double[res.length]; + this.shiftX.evalMulti(res, x, y, z, type); + this.shiftY.evalMulti(res1, x, y, z, type); + this.shiftZ.evalMulti(res2, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = this.noise.getValue((double)x[i] * this.xzScale + res[i], (double)y[i] * this.yScale + res1[i], (double)z[i] * this.xzScale + res2[i]); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.shiftX, this.shiftY, this.shiftZ}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode shiftX = this.shiftX.transform(transformer); + AstNode shiftY = this.shiftY.transform(transformer); + AstNode shiftZ = this.shiftZ.transform(transformer); + return shiftX == this.shiftX && shiftY == this.shiftY && shiftZ == this.shiftZ ? transformer.transform(this) : transformer.transform(new ShiftedNoiseNode(shiftX, shiftY, shiftZ, this.xzScale, this.yScale, this.noise)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + String shiftXMethod = context.newSingleMethod(this.shiftX); + String shiftYMethod = context.newSingleMethod(this.shiftY); + String shiftZMethod = context.newSingleMethod(this.shiftZ); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftXMethod); + m.add(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftYMethod); + m.add(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftZMethod); + m.add(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + String shiftXMethod = context.newMultiMethod(this.shiftX); + String shiftYMethod = context.newMultiMethod(this.shiftY); + String shiftZMethod = context.newMultiMethod(this.shiftZ); + int res1 = localVarConsumer.createLocalVariable("res1", Type.getDescriptor(double[].class)); + int res2 = localVarConsumer.createLocalVariable("res2", Type.getDescriptor(double[].class)); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res1, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res2, InstructionAdapter.OBJECT_TYPE); + context.callDelegateMulti(m, shiftXMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, shiftYMethod, Context.MULTI_DESC, false); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, shiftZMethod, Context.MULTI_DESC, false); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ShiftedNoiseNode that = (ShiftedNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && Objects.equals(this.shiftX, that.shiftX) && Objects.equals(this.shiftY, that.shiftY) && Objects.equals(this.shiftZ, that.shiftZ) && Objects.equals(this.noise, that.noise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.shiftX.hashCode(); + result = 31 * result + this.shiftY.hashCode(); + result = 31 * result + this.shiftZ.hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + result = 31 * result + this.noise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ShiftedNoiseNode that = (ShiftedNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && this.shiftX.relaxedEquals(that.shiftX) && this.shiftY.relaxedEquals(that.shiftY) && this.shiftZ.relaxedEquals(that.shiftZ); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.shiftX.relaxedHashCode(); + result = 31 * result + this.shiftY.relaxedHashCode(); + result = 31 * result + this.shiftZ.relaxedHashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java new file mode 100644 index 0000000..176b6a1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java @@ -0,0 +1,453 @@ +package org.bxteam.divinemc.dfc.common.ast.spline; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.McToAst; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import net.minecraft.util.CubicSpline; +import net.minecraft.util.Mth; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.bxteam.divinemc.util.Assertions; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AnalyzerAdapter; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SplineAstNode implements AstNode { + public static final String SPLINE_METHOD_DESC; + private final CubicSpline spline; + + public SplineAstNode(CubicSpline spline) { + this.spline = spline; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return (double)this.spline.apply(new DensityFunctions.Spline.Point(new NoisePosVanillaInterface(x, y, z, type))); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.evalSingle(x[i], y[i], z[i], type); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + ValuesMethodDef splineMethod = doBytecodeGenSpline(context, this.spline); + callSplineSingle(context, m, splineMethod); + m.cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + private static ValuesMethodDef doBytecodeGenSpline(BytecodeGen.Context context, CubicSpline spline) { + String name = context.getCachedSplineMethod(spline); + if (name != null) { + return new ValuesMethodDef(false, name, 0.0F); + } else if (spline instanceof CubicSpline.Constant) { + CubicSpline.Constant spline1 = (CubicSpline.Constant)spline; + return new ValuesMethodDef(true, (String)null, spline1.value()); + } else { + name = context.nextMethodName("Spline"); + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 18, name, SPLINE_METHOD_DESC, context.classWriter.visitMethod(18, name, SPLINE_METHOD_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList(); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + BytecodeGen.Context.LocalVarConsumer localVarConsumer = (localName, localDesc) -> { + int ordinal = extraLocals.size() + 5; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }; + if (spline instanceof CubicSpline.Multipoint) { + CubicSpline.Multipoint impl = (CubicSpline.Multipoint)spline; + ValuesMethodDef[] valuesMethods = (ValuesMethodDef[])impl.values().stream().map((spline1x) -> { + return doBytecodeGenSpline(context, spline1x); + }).toArray((x$0) -> { + return new ValuesMethodDef[x$0]; + }); + String locations = context.newField(float[].class, impl.locations()); + String derivatives = context.newField(float[].class, impl.derivatives()); + int point = localVarConsumer.createLocalVariable("point", Type.FLOAT_TYPE.getDescriptor()); + int rangeForLocation = localVarConsumer.createLocalVariable("rangeForLocation", Type.INT_TYPE.getDescriptor()); + int lastConst = impl.locations().length - 1; + String locationFunction = context.newSingleMethod(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)impl.coordinate()).function().value())); + context.callDelegateSingle(m, locationFunction); + m.cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE); + m.store(point, Type.FLOAT_TYPE); + if (valuesMethods.length == 1) { + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[0]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(0); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(point, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(SplineSupport.class), "findRangeForLocation", Type.getMethodDescriptor(Type.INT_TYPE, new Type[]{Type.getType(float[].class), Type.FLOAT_TYPE}), false); + m.store(rangeForLocation, Type.INT_TYPE); + Label label1 = new Label(); + Label label2 = new Label(); + m.load(rangeForLocation, Type.INT_TYPE); + m.ifge(label1); + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[0]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(0); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label1); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(lastConst); + m.ificmpne(label2); + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[lastConst]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(lastConst); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label2); + int loc0 = localVarConsumer.createLocalVariable("loc0", Type.FLOAT_TYPE.getDescriptor()); + int loc1 = localVarConsumer.createLocalVariable("loc1", Type.FLOAT_TYPE.getDescriptor()); + int locDist = localVarConsumer.createLocalVariable("locDist", Type.FLOAT_TYPE.getDescriptor()); + int k = localVarConsumer.createLocalVariable("k", Type.FLOAT_TYPE.getDescriptor()); + int n = localVarConsumer.createLocalVariable("n", Type.FLOAT_TYPE.getDescriptor()); + int o = localVarConsumer.createLocalVariable("o", Type.FLOAT_TYPE.getDescriptor()); + int onDist = localVarConsumer.createLocalVariable("onDist", Type.FLOAT_TYPE.getDescriptor()); + int p = localVarConsumer.createLocalVariable("p", Type.FLOAT_TYPE.getDescriptor()); + int q = localVarConsumer.createLocalVariable("q", Type.FLOAT_TYPE.getDescriptor()); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.store(loc0, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(1); + m.add(Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.store(loc1, Type.FLOAT_TYPE); + m.load(loc1, Type.FLOAT_TYPE); + m.load(loc0, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(locDist, Type.FLOAT_TYPE); + m.load(point, Type.FLOAT_TYPE); + m.load(loc0, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.div(Type.FLOAT_TYPE); + m.store(k, Type.FLOAT_TYPE); + Label[] jumpLabels = new Label[valuesMethods.length - 1]; + boolean[] jumpGenerated = new boolean[valuesMethods.length - 1]; + + for(int i = 0; i < valuesMethods.length - 1; ++i) { + jumpLabels[i] = new Label(); + } + + Label defaultLabel = new Label(); + Label label3 = new Label(); + m.load(rangeForLocation, Type.INT_TYPE); + m.tableswitch(0, valuesMethods.length - 2, defaultLabel, jumpLabels); + + for(int i = 0; i < valuesMethods.length - 1; ++i) { + if (!jumpGenerated[i]) { + m.visitLabel(jumpLabels[i]); + jumpGenerated[i] = true; + + for(int j = i + 1; j < valuesMethods.length - 1; ++j) { + if (valuesMethods[i].equals(valuesMethods[j]) && valuesMethods[i + 1].equals(valuesMethods[j + 1])) { + m.visitLabel(jumpLabels[j]); + jumpGenerated[j] = true; + } + } + + callSplineSingle(context, m, valuesMethods[i]); + if (valuesMethods[i].equals(valuesMethods[i + 1])) { + m.dup(); + m.store(n, Type.FLOAT_TYPE); + m.store(o, Type.FLOAT_TYPE); + } else { + m.store(n, Type.FLOAT_TYPE); + callSplineSingle(context, m, valuesMethods[i + 1]); + m.store(o, Type.FLOAT_TYPE); + } + + m.goTo(label3); + } + } + + m.visitLabel(defaultLabel); + m.iconst(0); + m.aconst("boom"); + m.invokestatic(Type.getInternalName(Assertions.class), "assertTrue", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.BOOLEAN_TYPE, Type.getType(String.class)}), false); + m.fconst(Float.NaN); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label3); + m.load(o, Type.FLOAT_TYPE); + m.load(n, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(onDist, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(onDist, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(p, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(1); + m.add(Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.neg(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(onDist, Type.FLOAT_TYPE); + m.add(Type.FLOAT_TYPE); + m.store(q, Type.FLOAT_TYPE); + m.load(k, Type.FLOAT_TYPE); + m.load(n, Type.FLOAT_TYPE); + m.load(o, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(Mth.class), "lerp", "(FFF)F", false); + m.load(k, Type.FLOAT_TYPE); + m.fconst(1.0F); + m.load(k, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(k, Type.FLOAT_TYPE); + m.load(p, Type.FLOAT_TYPE); + m.load(q, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(Mth.class), "lerp", "(FFF)F", false); + m.mul(Type.FLOAT_TYPE); + m.add(Type.FLOAT_TYPE); + m.areturn(Type.FLOAT_TYPE); + } + } else { + if (!(spline instanceof CubicSpline.Constant)) { + throw new UnsupportedOperationException(String.format("Unsupported spline implementation: %s", spline.getClass().getName())); + } + + CubicSpline.Constant floatFunction = (CubicSpline.Constant)spline; + m.fconst(floatFunction.value()); + m.areturn(Type.FLOAT_TYPE); + } + + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("x", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 1); + m.visitLocalVariable("y", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 2); + m.visitLocalVariable("z", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 3); + m.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 4); + Iterator var35 = extraLocals.iterator(); + + while(var35.hasNext()) { + IntObjectPair> local = (IntObjectPair)var35.next(); + m.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + m.visitMaxs(0, 0); + context.cacheSplineMethod(spline, name); + return new ValuesMethodDef(false, name, 0.0F); + } + } + + private static void callSplineSingle(BytecodeGen.Context context, InstructionAdapter m, ValuesMethodDef target) { + if (target.isConst()) { + m.fconst(target.constValue()); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, target.generatedMethod(), SPLINE_METHOD_DESC, false); + } + + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + context.delegateToSingle(m, localVarConsumer, this); + m.areturn(Type.VOID_TYPE); + } + + private static boolean deepEquals(CubicSpline a, CubicSpline b) { + if (a instanceof CubicSpline.Constant a1) { + if (b instanceof CubicSpline.Constant b1) { + return a1.value() == b1.value(); + } + } + + if (a instanceof CubicSpline.Multipoint a1) { + if (b instanceof CubicSpline.Multipoint b1) { + boolean equals1 = Arrays.equals(a1.derivatives(), b1.derivatives()) && Arrays.equals(a1.locations(), b1.locations()) && a1.values().size() == b1.values().size() && McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).equals(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)b1.coordinate()).function().value())); + if (!equals1) { + return false; + } + + int size = a1.values().size(); + + for(int i = 0; i < size; ++i) { + if (!deepEquals((CubicSpline)a1.values().get(i), (CubicSpline)b1.values().get(i))) { + return false; + } + } + + return true; + } + } + + return false; + } + + private static boolean deepRelaxedEquals(CubicSpline a, CubicSpline b) { + if (a instanceof CubicSpline.Constant a1) { + if (b instanceof CubicSpline.Constant b1) { + return a1.value() == b1.value(); + } + } + + if (a instanceof CubicSpline.Multipoint a1) { + if (b instanceof CubicSpline.Multipoint b1) { + boolean equals1 = a1.values().size() == b1.values().size() && McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).relaxedEquals(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)b1.coordinate()).function().value())); + if (!equals1) { + return false; + } + + int size = a1.values().size(); + + for(int i = 0; i < size; ++i) { + if (!deepRelaxedEquals((CubicSpline)a1.values().get(i), (CubicSpline)b1.values().get(i))) { + return false; + } + } + + return true; + } + } + + return false; + } + + private static int deepHashcode(CubicSpline a) { + if (a instanceof CubicSpline.Constant a1) { + return Float.hashCode(a1.value()); + } else if (!(a instanceof CubicSpline.Multipoint a1)) { + return a.hashCode(); + } else { + int result = 1; + result = 31 * result + Arrays.hashCode(a1.derivatives()); + result = 31 * result + Arrays.hashCode(a1.locations()); + + CubicSpline spline; + for(Iterator var4 = a1.values().iterator(); var4.hasNext(); result = 31 * result + deepHashcode(spline)) { + spline = (CubicSpline)var4.next(); + } + + result = 31 * result + McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).hashCode(); + return result; + } + } + + private static int deepRelaxedHashcode(CubicSpline a) { + if (a instanceof CubicSpline.Constant a1) { + return Float.hashCode(a1.value()); + } else if (!(a instanceof CubicSpline.Multipoint a1)) { + return a.hashCode(); + } else { + int result = 1; + + CubicSpline spline; + for(Iterator var4 = a1.values().iterator(); var4.hasNext(); result = 31 * result + deepRelaxedHashcode(spline)) { + spline = (CubicSpline)var4.next(); + } + + result = 31 * result + McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).relaxedHashCode(); + return result; + } + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SplineAstNode that = (SplineAstNode)o; + return deepEquals(this.spline, that.spline); + } else { + return false; + } + } + + public int hashCode() { + return deepHashcode(this.spline); + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SplineAstNode that = (SplineAstNode)o; + return deepRelaxedEquals(this.spline, that.spline); + } else { + return false; + } + } + + public int relaxedHashCode() { + return deepRelaxedHashcode(this.spline); + } + + static { + SPLINE_METHOD_DESC = Type.getMethodDescriptor(Type.getType(Float.TYPE), new Type[]{Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(EvalType.class)}); + } + + private static record ValuesMethodDef(boolean isConst, String generatedMethod, float constValue) { + private ValuesMethodDef(boolean isConst, String generatedMethod, float constValue) { + this.isConst = isConst; + this.generatedMethod = generatedMethod; + this.constValue = constValue; + } + + public boolean isConst() { + return this.isConst; + } + + public String generatedMethod() { + return this.generatedMethod; + } + + public float constValue() { + return this.constValue; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java new file mode 100644 index 0000000..ee23110 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java @@ -0,0 +1,29 @@ +package org.bxteam.divinemc.dfc.common.ast.spline; + +public class SplineSupport { + public SplineSupport() { + } + + public static int findRangeForLocation(float[] locations, float x) { + int min = 0; + int i = locations.length; + + while(i > 0) { + int j = i / 2; + int k = min + j; + if (x < locations[k]) { + i = j; + } else { + min = k + 1; + i -= j + 1; + } + } + + return min - 1; + } + + public static float sampleOutsideRange(float point, float[] locations, float value, float[] derivatives, int i) { + float f = derivatives[i]; + return f == 0.0F ? value : value + f * (point - locations[i]); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java new file mode 100644 index 0000000..e3a91a5 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java @@ -0,0 +1,49 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class AbsNode extends AbstractUnaryNode { + public AbsNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new AbsNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.abs(this.operand.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = Math.abs(res[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "abs", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "abs", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java new file mode 100644 index 0000000..a48ca64 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java @@ -0,0 +1,72 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import org.objectweb.asm.commons.InstructionAdapter; + +public abstract class AbstractUnaryNode implements AstNode { + protected final AstNode operand; + + public AbstractUnaryNode(AstNode operand) { + this.operand = (AstNode)Objects.requireNonNull(operand); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.operand}; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractUnaryNode that = (AbstractUnaryNode)o; + return Objects.equals(this.operand, that.operand); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.operand.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractUnaryNode that = (AbstractUnaryNode)o; + return this.operand.relaxedEquals(that.operand); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.operand.relaxedHashCode(); + return result; + } + + protected abstract AstNode newInstance(AstNode var1); + + public AstNode transform(AstTransformer transformer) { + AstNode operand = this.operand.transform(transformer); + return this.operand == operand ? transformer.transform(this) : transformer.transform(this.newInstance(operand)); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String operandMethod = context.newSingleMethod(this.operand); + context.callDelegateSingle(m, operandMethod); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String operandMethod = context.newMultiMethod(this.operand); + context.callDelegateMulti(m, operandMethod); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java new file mode 100644 index 0000000..8bd4d3f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java @@ -0,0 +1,56 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class CubeNode extends AbstractUnaryNode { + public CubeNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new CubeNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v * v * v; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] * res[i] * res[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java new file mode 100644 index 0000000..4be770d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java @@ -0,0 +1,83 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class NegMulNode extends AbstractUnaryNode { + private final double negMul; + + public NegMulNode(AstNode operand, double negMul) { + super(operand); + this.negMul = negMul; + } + + protected AstNode newInstance(AstNode operand) { + return new NegMulNode(operand, this.negMul); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v > 0.0 ? v : v * this.negMul; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double v = res[i]; + res[i] = v > 0.0 ? v : v * this.negMul; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.store(v, Type.DOUBLE_TYPE); + Label negMulLabel = new Label(); + Label end = new Label(); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifle(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.goTo(end); + m.visitLabel(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(this.negMul); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.store(v, Type.DOUBLE_TYPE); + Label negMulLabel = new Label(); + Label end = new Label(); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifle(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.goTo(end); + m.visitLabel(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(this.negMul); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java new file mode 100644 index 0000000..284ff98 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java @@ -0,0 +1,52 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SquareNode extends AbstractUnaryNode { + public SquareNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new SquareNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v * v; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] *= res[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java new file mode 100644 index 0000000..ae7e8b1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java @@ -0,0 +1,84 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import net.minecraft.util.Mth; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SqueezeNode extends AbstractUnaryNode { + public SqueezeNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new SqueezeNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = Mth.clamp(this.operand.evalSingle(x, y, z, type), -1.0, 1.0); + return v / 2.0 - v * v * v / 24.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double v = Mth.clamp(res[i], -1.0, 1.0); + res[i] = v / 2.0 - v * v * v / 24.0; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dconst(1.0); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.dconst(-1.0); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.store(v, Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(2.0); + m.div(Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.dconst(24.0); + m.div(Type.DOUBLE_TYPE); + m.sub(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dconst(1.0); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.dconst(-1.0); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.store(v, Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(2.0); + m.div(Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.dconst(24.0); + m.div(Type.DOUBLE_TYPE); + m.sub(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java new file mode 100644 index 0000000..2afddfa --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +import org.bxteam.divinemc.dfc.common.util.ArrayCache; + +public interface IArrayCacheCapable { + ArrayCache c2me$getArrayCache(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java new file mode 100644 index 0000000..1454273 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface IBlendingAwareVisitor { + boolean c2me$isBlendingEnabled(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java new file mode 100644 index 0000000..4fa5595 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface ICoordinatesFilling { + void c2me$fillCoordinates(int[] var1, int[] var2, int[] var3); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java new file mode 100644 index 0000000..9236883 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface IEqualityOverriding { + void c2me$overrideEquality(Object var1); + + Object c2me$getOverriddenEquality(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java new file mode 100644 index 0000000..8289c85 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java @@ -0,0 +1,20 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import net.minecraft.world.level.levelgen.DensityFunction; + +public interface IFastCacheLike extends DensityFunction { + long CACHE_MISS_NAN_BITS = 9222769054270909007L; + + double c2me$getCached(int var1, int var2, int var3, EvalType var4); + + boolean c2me$getCached(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + void c2me$cache(int var1, int var2, int var3, EvalType var4, double var5); + + void c2me$cache(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + DensityFunction c2me$getDelegate(); + + DensityFunction c2me$withDelegate(DensityFunction var1); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java new file mode 100644 index 0000000..365ed1a --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java @@ -0,0 +1,499 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.io.Files; +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.McToAst; +import org.bxteam.divinemc.dfc.common.ast.dfvisitor.StripBlending; +import org.bxteam.divinemc.dfc.common.ast.misc.ConstantNode; +import org.bxteam.divinemc.dfc.common.ast.misc.RootNode; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.AstVanillaInterface; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMaps; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.stream.Collectors; +import net.minecraft.util.CubicSpline; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AnalyzerAdapter; +import org.objectweb.asm.commons.InstructionAdapter; + +public class BytecodeGen { + private static final File exportDir = new File("./cache/c2me-dfc"); + private static final AtomicLong ordinal = new AtomicLong(); + public static final Hash.Strategy RELAXED_STRATEGY; + private static final Object2ReferenceMap> compilationCache; + + public BytecodeGen() { + } + + public static DensityFunction compile(DensityFunction densityFunction, Reference2ReferenceMap tempCache) { + DensityFunction cached = (DensityFunction)tempCache.get(densityFunction); + if (cached != null) { + return cached; + } else if (densityFunction instanceof AstVanillaInterface) { + AstVanillaInterface vif = (AstVanillaInterface)densityFunction; + AstNode ast = vif.getAstNode(); + return new CompiledDensityFunction(compile0(ast), vif.getBlendingFallback()); + } else { + AstNode ast = McToAst.toAst(densityFunction.mapAll(StripBlending.INSTANCE)); + if (ast instanceof ConstantNode) { + ConstantNode constantNode = (ConstantNode)ast; + return DensityFunctions.constant(constantNode.getValue()); + } else { + CompiledDensityFunction compiled = new CompiledDensityFunction(compile0(ast), densityFunction); + tempCache.put(densityFunction, compiled); + return compiled; + } + } + } + + public static synchronized CompiledEntry compile0(AstNode node) { + Class cached = (Class)compilationCache.get(node); + ClassWriter writer = new ClassWriter(3); + String name = cached != null ? String.format("DfcCompiled_discarded") : String.format("DfcCompiled_%d", ordinal.getAndIncrement()); + writer.visit(65, 17, name, (String)null, Type.getInternalName(Object.class), new String[]{Type.getInternalName(CompiledEntry.class)}); + RootNode rootNode = new RootNode(node); + Context genContext = new Context(writer, name); + genContext.newSingleMethod0((adapter, localVarConsumer) -> { + rootNode.doBytecodeGenSingle(genContext, adapter, localVarConsumer); + }, "evalSingle", true); + genContext.newMultiMethod0((adapter, localVarConsumer) -> { + rootNode.doBytecodeGenMulti(genContext, adapter, localVarConsumer); + }, "evalMulti", true); + List args = (List)genContext.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).map(Map.Entry::getKey).collect(Collectors.toCollection(ArrayList::new)); + if (cached != null) { + try { + return (CompiledEntry)cached.getConstructor(List.class).newInstance(args); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException var11) { + ReflectiveOperationException e = var11; + throw new RuntimeException(e); + } + } else { + genConstructor(genContext); + genGetArgs(genContext); + genNewInstance(genContext); + + Object var8; + for(ListIterator iterator = args.listIterator(); iterator.hasNext(); var8 = iterator.next()) { + } + + byte[] bytes = writer.toByteArray(); + dumpClass(genContext.className, bytes); + Class defined = defineClass(genContext.className, bytes); + compilationCache.put(node, defined); + + try { + return (CompiledEntry)defined.getConstructor(List.class).newInstance(args); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException var12) { + ReflectiveOperationException e = var12; + throw new RuntimeException(e); + } + } + } + + private static void genConstructor(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 1, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), context.classWriter.visitMethod(1, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(Object.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]), false); + Iterator var4 = context.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).toList().iterator(); + + while(var4.hasNext()) { + Map.Entry entry = (Map.Entry)var4.next(); + String name = ((Context.FieldRecord)entry.getValue()).name(); + Class type = ((Context.FieldRecord)entry.getValue()).type(); + int ordinal = ((Context.FieldRecord)entry.getValue()).ordinal(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.iconst(ordinal); + m.invokeinterface(Type.getInternalName(List.class), "get", Type.getMethodDescriptor(InstructionAdapter.OBJECT_TYPE, new Type[]{Type.INT_TYPE})); + m.checkcast(Type.getType(type)); + m.putfield(context.className, name, Type.getDescriptor(type)); + } + + var4 = context.postProcessMethods.stream().sorted().toList().iterator(); + + while(var4.hasNext()) { + String postProcessingMethod = (String)var4.next(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, postProcessingMethod, "()V", false); + } + + m.areturn(Type.VOID_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void genGetArgs(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 17, "getArgs", Type.getMethodDescriptor(Type.getType(List.class), new Type[0]), context.classWriter.visitMethod(17, "getArgs", Type.getMethodDescriptor(Type.getType(List.class), new Type[0]), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.anew(Type.getType(ArrayList.class)); + m.dup(); + m.iconst(context.args.size()); + m.invokespecial(Type.getInternalName(ArrayList.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.INT_TYPE}), false); + m.store(1, InstructionAdapter.OBJECT_TYPE); + Iterator var4 = context.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).toList().iterator(); + + while(var4.hasNext()) { + Map.Entry entry = (Map.Entry)var4.next(); + String name = ((Context.FieldRecord)entry.getValue()).name(); + Class type = ((Context.FieldRecord)entry.getValue()).type(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, name, Type.getDescriptor(type)); + m.invokeinterface(Type.getInternalName(List.class), "add", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[]{InstructionAdapter.OBJECT_TYPE})); + m.pop(); + } + + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.areturn(InstructionAdapter.OBJECT_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void genNewInstance(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 17, "newInstance", Type.getMethodDescriptor(Type.getType(CompiledEntry.class), new Type[]{Type.getType(List.class)}), context.classWriter.visitMethod(17, "newInstance", Type.getMethodDescriptor(Type.getType(CompiledEntry.class), new Type[]{Type.getType(List.class)}), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.anew(Type.getType(context.classDesc)); + m.dup(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(context.className, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), false); + m.areturn(InstructionAdapter.OBJECT_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void dumpClass(String className, byte[] bytes) { + File outputFile = new File(exportDir, className + ".class"); + outputFile.getParentFile().mkdirs(); + + try { + Files.write(bytes, outputFile); + } catch (IOException var4) { + IOException e = var4; + e.printStackTrace(); + } + + } + + private static Class defineClass(final String className, final byte[] bytes) { + ClassLoader classLoader = new ClassLoader(BytecodeGen.class.getClassLoader()) { + public Class loadClass(String name) throws ClassNotFoundException { + return name.equals(className) ? super.defineClass(name, bytes, 0, bytes.length) : super.loadClass(name); + } + }; + + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException var4) { + ClassNotFoundException e = var4; + throw new RuntimeException(e); + } + } + + static { + try { + org.bxteam.divinemc.util.Files.deleteRecursively(exportDir); + } catch (IOException var1) { + IOException e = var1; + e.printStackTrace(); + } + + RELAXED_STRATEGY = new Hash.Strategy() { + public int hashCode(AstNode o) { + return o.relaxedHashCode(); + } + + public boolean equals(AstNode a, AstNode b) { + return a.relaxedEquals(b); + } + }; + compilationCache = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenCustomHashMap<>(RELAXED_STRATEGY)); + } + + public static class Context { + public static final String SINGLE_DESC; + public static final String MULTI_DESC; + public final ClassWriter classWriter; + public final String className; + public final String classDesc; + private int methodIdx = 0; + private final Object2ReferenceOpenHashMap singleMethods = new Object2ReferenceOpenHashMap<>(); + private final Object2ReferenceOpenHashMap multiMethods = new Object2ReferenceOpenHashMap<>(); + private final Object2ReferenceOpenHashMap, String> splineMethods = new Object2ReferenceOpenHashMap<>(); + private final ObjectOpenHashSet postProcessMethods = new ObjectOpenHashSet<>(); + private final Reference2ObjectOpenHashMap args = new Reference2ObjectOpenHashMap<>(); + + public Context(ClassWriter classWriter, String className) { + this.classWriter = (ClassWriter)Objects.requireNonNull(classWriter); + this.className = (String)Objects.requireNonNull(className); + this.classDesc = String.format("L%s;", this.className); + } + + public String nextMethodName() { + return String.format("method_%d", this.methodIdx++); + } + + public String nextMethodName(String suffix) { + return String.format("method_%d_%s", this.methodIdx++, suffix); + } + + public String newSingleMethod(AstNode node) { + return this.singleMethods.computeIfAbsent(node, (AstNode node1) -> this.newSingleMethod((adapter, localVarConsumer) -> node1.doBytecodeGenSingle(this, adapter, localVarConsumer), nextMethodName(node.getClass().getSimpleName()))); + } + + public String newSingleMethod(BiConsumer generator) { + return this.newSingleMethod(generator, this.nextMethodName()); + } + + public String newSingleMethod(BiConsumer generator, String name) { + this.newSingleMethod0(generator, name, false); + return name; + } + + private void newSingleMethod0(BiConsumer generator, String name, boolean isPublic) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, (isPublic ? 1 : 2) | 16, name, SINGLE_DESC, this.classWriter.visitMethod((isPublic ? 1 : 2) | 16, name, SINGLE_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList<>(); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter, (localName, localDesc) -> { + int ordinal = extraLocals.size() + 5; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }); + adapter.visitLabel(end); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + adapter.visitLocalVariable("x", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 1); + adapter.visitLocalVariable("y", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 2); + adapter.visitLocalVariable("z", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 3); + adapter.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 4); + Iterator var8 = extraLocals.iterator(); + + while(var8.hasNext()) { + IntObjectPair> local = (IntObjectPair)var8.next(); + adapter.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + adapter.visitMaxs(0, 0); + } + + public String newMultiMethod(AstNode node) { + return this.multiMethods.computeIfAbsent(node, (AstNode node1) -> this.newMultiMethod((adapter, localVarConsumer) -> node1.doBytecodeGenMulti(this, adapter, localVarConsumer), nextMethodName(node.getClass().getSimpleName()))); + } + + public String newMultiMethod(BiConsumer generator) { + return this.newMultiMethod(generator, this.nextMethodName()); + } + + public String newMultiMethod(BiConsumer generator, String name) { + this.newMultiMethod0(generator, name, false); + return name; + } + + private void newMultiMethod0(BiConsumer generator, String name, boolean isPublic) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, (isPublic ? 1 : 2) | 16, name, MULTI_DESC, this.classWriter.visitMethod((isPublic ? 1 : 2) | 16, name, MULTI_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList(); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter, (localName, localDesc) -> { + int ordinal = extraLocals.size() + 7; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }); + adapter.visitLabel(end); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + adapter.visitLocalVariable("res", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 1); + adapter.visitLocalVariable("x", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 2); + adapter.visitLocalVariable("y", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 3); + adapter.visitLocalVariable("z", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 4); + adapter.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 5); + adapter.visitLocalVariable("arrayCache", Type.getType(ArrayCache.class).getDescriptor(), (String)null, start, end, 6); + Iterator var8 = extraLocals.iterator(); + + while(var8.hasNext()) { + IntObjectPair> local = (IntObjectPair)var8.next(); + adapter.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + adapter.visitMaxs(0, 0); + } + + public String getCachedSplineMethod(CubicSpline spline) { + return (String)this.splineMethods.get(spline); + } + + public void cacheSplineMethod(CubicSpline spline, String method) { + this.splineMethods.put(spline, method); + } + + public void callDelegateSingle(InstructionAdapter m, String target) { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, target, SINGLE_DESC, false); + } + + public void callDelegateMulti(InstructionAdapter m, String target) { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, target, MULTI_DESC, false); + } + + public String newField(Class type, T data) { + FieldRecord existing = (FieldRecord)this.args.get(data); + if (existing != null) { + return existing.name(); + } else { + int size = this.args.size(); + String name = String.format("field_%d", size); + this.classWriter.visitField(2, name, Type.getDescriptor(type), (String)null, (Object)null); + this.args.put(data, new FieldRecord(name, size, type)); + return name; + } + } + + public void doCountedLoop(InstructionAdapter m, LocalVarConsumer localVarConsumer, IntConsumer bodyGenerator) { + int loopIdx = localVarConsumer.createLocalVariable("loopIdx", Type.INT_TYPE.getDescriptor()); + m.iconst(0); + m.store(loopIdx, Type.INT_TYPE); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.load(loopIdx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.ificmpge(end); + bodyGenerator.accept(loopIdx); + m.iinc(loopIdx, 1); + m.goTo(start); + m.visitLabel(end); + } + + public void delegateToSingle(InstructionAdapter m, LocalVarConsumer localVarConsumer, AstNode current) { + String singleMethod = this.newSingleMethod(current); + this.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, singleMethod, SINGLE_DESC, false); + m.astore(Type.DOUBLE_TYPE); + }); + } + + public void genPostprocessingMethod(String name, Consumer generator) { + if (!this.postProcessMethods.contains(name)) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, 18, name, "()V", this.classWriter.visitMethod(18, name, "()V", (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter); + adapter.visitLabel(end); + adapter.visitMaxs(0, 0); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + this.postProcessMethods.add(name); + } + } + + static { + SINGLE_DESC = Type.getMethodDescriptor(Type.getType(Double.TYPE), new Type[]{Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(EvalType.class)}); + MULTI_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class), Type.getType(ArrayCache.class)}); + } + + public interface LocalVarConsumer { + int createLocalVariable(String var1, String var2); + } + + private static record FieldRecord(String name, int ordinal, Class type) { + private FieldRecord(String name, int ordinal, Class type) { + this.name = name; + this.ordinal = ordinal; + this.type = type; + } + + public String name() { + return this.name; + } + + public int ordinal() { + return this.ordinal; + } + + public Class type() { + return this.type; + } + } + } + + @FunctionalInterface + public interface EvalMultiInterface { + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + } + + @FunctionalInterface + public interface EvalSingleInterface { + double evalSingle(int var1, int var2, int var3, EvalType var4); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java new file mode 100644 index 0000000..15b6c16 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.base.Suppliers; +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.function.Supplier; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class CompiledDensityFunction extends SubCompiledDensityFunction { + private final CompiledEntry compiledEntry; + + public CompiledDensityFunction(CompiledEntry compiledEntry, DensityFunction blendingFallback) { + super(compiledEntry, compiledEntry, blendingFallback); + this.compiledEntry = (CompiledEntry)Objects.requireNonNull(compiledEntry); + } + + private CompiledDensityFunction(CompiledEntry compiledEntry, Supplier blendingFallback) { + super(compiledEntry, compiledEntry, blendingFallback); + this.compiledEntry = (CompiledEntry)Objects.requireNonNull(compiledEntry); + } + + public DensityFunction mapAll(Visitor visitor) { + if (visitor instanceof IBlendingAwareVisitor blendingAwareVisitor) { + if (blendingAwareVisitor.c2me$isBlendingEnabled()) { + DensityFunction fallback1 = this.getFallback(); + if (fallback1 == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + return fallback1.mapAll(visitor); + } + } + + boolean modified = false; + List args = this.compiledEntry.getArgs(); + ListIterator iterator = args.listIterator(); + + Object next; + while(iterator.hasNext()) { + next = iterator.next(); + if (next instanceof DensityFunction df) { + if (!(df instanceof IFastCacheLike)) { + DensityFunction applied = df.mapAll(visitor); + if (df != applied) { + iterator.set(applied); + modified = true; + } + } + } + + if (next instanceof NoiseHolder noise) { + NoiseHolder applied = visitor.visitNoise(noise); + if (noise != applied) { + iterator.set(applied); + modified = true; + } + } + } + + iterator = args.listIterator(); + + while(iterator.hasNext()) { + next = iterator.next(); + if (next instanceof IFastCacheLike cacheLike) { + DensityFunction applied = visitor.apply(cacheLike); + if (applied == cacheLike.c2me$getDelegate()) { + iterator.set((Object)null); + modified = true; + } else { + if (!(applied instanceof IFastCacheLike)) { + throw new UnsupportedOperationException("Unsupported transformation on Wrapping node"); + } + + IFastCacheLike newCacheLike = (IFastCacheLike)applied; + iterator.set(newCacheLike); + modified = true; + } + } + } + + Supplier fallback = this.blendingFallback != null ? Suppliers.memoize(() -> { + DensityFunction densityFunction = (DensityFunction)this.blendingFallback.get(); + return densityFunction != null ? densityFunction.mapAll(visitor) : null; + }) : null; + if (fallback != this.blendingFallback) { + modified = true; + } + + if (modified) { + return new CompiledDensityFunction(this.compiledEntry.newInstance(args), fallback); + } else { + return this; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java new file mode 100644 index 0000000..35b0867 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java @@ -0,0 +1,15 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.List; + +public interface CompiledEntry extends ISingleMethod, IMultiMethod { + double evalSingle(int var1, int var2, int var3, EvalType var4); + + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5, ArrayCache var6); + + CompiledEntry newInstance(List var1); + + List getArgs(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java new file mode 100644 index 0000000..1b4ec4e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java @@ -0,0 +1,28 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.jetbrains.annotations.NotNull; + +public class DelegatingBlendingAwareVisitor implements IBlendingAwareVisitor, DensityFunction.Visitor { + private final DensityFunction.Visitor delegate; + private final boolean blendingEnabled; + + public DelegatingBlendingAwareVisitor(DensityFunction.Visitor delegate, boolean blendingEnabled) { + this.delegate = Objects.requireNonNull(delegate); + this.blendingEnabled = blendingEnabled; + } + + public @NotNull DensityFunction apply(@NotNull DensityFunction densityFunction) { + return this.delegate.apply(densityFunction); + } + + public DensityFunction.@NotNull NoiseHolder visitNoise(DensityFunction.@NotNull NoiseHolder noiseDensityFunction) { + return this.delegate.visitNoise(noiseDensityFunction); + } + + public boolean c2me$isBlendingEnabled() { + return this.blendingEnabled; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java new file mode 100644 index 0000000..21b2796 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java @@ -0,0 +1,9 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; + +@FunctionalInterface +public interface IMultiMethod { + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5, ArrayCache var6); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java new file mode 100644 index 0000000..f946553 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java @@ -0,0 +1,8 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; + +@FunctionalInterface +public interface ISingleMethod { + double evalSingle(int var1, int var2, int var3, EvalType var4); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java new file mode 100644 index 0000000..aec0c62 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java @@ -0,0 +1,142 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.base.Suppliers; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import org.bxteam.divinemc.dfc.common.ducks.ICoordinatesFilling; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import java.util.Objects; +import java.util.function.Supplier; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.Blender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SubCompiledDensityFunction implements DensityFunction { + private static final Logger LOGGER = LoggerFactory.getLogger(SubCompiledDensityFunction.class); + private final ISingleMethod singleMethod; + private final IMultiMethod multiMethod; + protected final Supplier blendingFallback; + + public SubCompiledDensityFunction(ISingleMethod singleMethod, IMultiMethod multiMethod, DensityFunction blendingFallback) { + this(singleMethod, multiMethod, unwrap(blendingFallback)); + } + + protected SubCompiledDensityFunction(ISingleMethod singleMethod, IMultiMethod multiMethod, Supplier blendingFallback) { + this.singleMethod = (ISingleMethod)Objects.requireNonNull(singleMethod); + this.multiMethod = (IMultiMethod)Objects.requireNonNull(multiMethod); + this.blendingFallback = blendingFallback; + } + + private static Supplier unwrap(DensityFunction densityFunction) { + if (densityFunction instanceof SubCompiledDensityFunction scdf) { + return scdf.blendingFallback; + } else { + return densityFunction != null ? Suppliers.ofInstance(densityFunction) : null; + } + } + + public double compute(FunctionContext pos) { + if (pos.getBlender() != Blender.empty()) { + DensityFunction fallback = this.getFallback(); + if (fallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } else { + return fallback.compute(pos); + } + } else { + return this.singleMethod.evalSingle(pos.blockX(), pos.blockY(), pos.blockZ(), EvalType.from(pos)); + } + } + + public void fillArray(double[] densities, ContextProvider applier) { + if (applier instanceof NoiseChunk sampler) { + if (sampler.getBlender() != Blender.empty()) { + DensityFunction fallback = this.getFallback(); + if (fallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + fallback.fillArray(densities, applier); + return; + } + } + + if (applier instanceof EachApplierVanillaInterface vanillaInterface) { + this.multiMethod.evalMulti(densities, vanillaInterface.getX(), vanillaInterface.getY(), vanillaInterface.getZ(), EvalType.from(applier), vanillaInterface.c2me$getArrayCache()); + } else { + ArrayCache var10000; + if (applier instanceof IArrayCacheCapable cacheCapable) { + var10000 = cacheCapable.c2me$getArrayCache(); + } else { + var10000 = new ArrayCache(); + } + + ArrayCache cache = var10000; + int[] x = cache.getIntArray(densities.length, false); + int[] y = cache.getIntArray(densities.length, false); + int[] z = cache.getIntArray(densities.length, false); + if (applier instanceof ICoordinatesFilling coordinatesFilling) { + coordinatesFilling.c2me$fillCoordinates(x, y, z); + } else { + for(int i = 0; i < densities.length; ++i) { + FunctionContext pos = applier.forIndex(i); + x[i] = pos.blockX(); + y[i] = pos.blockY(); + z[i] = pos.blockZ(); + } + } + + this.multiMethod.evalMulti(densities, x, y, z, EvalType.from(applier), cache); + } + } + + public DensityFunction mapAll(Visitor visitor) { + if (this.getClass() != SubCompiledDensityFunction.class) { + throw new AbstractMethodError(); + } else { + if (visitor instanceof IBlendingAwareVisitor) { + IBlendingAwareVisitor blendingAwareVisitor = (IBlendingAwareVisitor)visitor; + if (blendingAwareVisitor.c2me$isBlendingEnabled()) { + DensityFunction fallback1 = this.getFallback(); + if (fallback1 == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + return fallback1.mapAll(visitor); + } + } + + boolean modified = false; + Supplier fallback = this.blendingFallback != null ? Suppliers.memoize(() -> { + DensityFunction densityFunction = (DensityFunction)this.blendingFallback.get(); + return densityFunction != null ? densityFunction.mapAll(visitor) : null; + }) : null; + if (fallback != this.blendingFallback) { + modified = true; + } + + return modified ? new SubCompiledDensityFunction(this.singleMethod, this.multiMethod, fallback) : this; + } + } + + public double minValue() { + return Double.MIN_VALUE; + } + + public double maxValue() { + return Double.MAX_VALUE; + } + + public KeyDispatchDataCodec codec() { + throw new UnsupportedOperationException(); + } + + protected DensityFunction getFallback() { + return this.blendingFallback != null ? (DensityFunction)this.blendingFallback.get() : null; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java new file mode 100644 index 0000000..a3813ca --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java @@ -0,0 +1,57 @@ +package org.bxteam.divinemc.dfc.common.util; + +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import java.util.Arrays; + +public class ArrayCache { + private final Int2ReferenceArrayMap> doubleArrayCache = new Int2ReferenceArrayMap<>(); + private final Int2ReferenceArrayMap> intArrayCache = new Int2ReferenceArrayMap<>(); + + public ArrayCache() { + } + + public double[] getDoubleArray(int size, boolean zero) { + ReferenceArrayList list = this.doubleArrayCache.computeIfAbsent(size, (k) -> { + return new ReferenceArrayList<>(); + }); + if (list.isEmpty()) { + return new double[size]; + } else { + double[] popped = list.pop(); + if (zero) { + Arrays.fill(popped, 0.0); + } + + return popped; + } + } + + public int[] getIntArray(int size, boolean zero) { + ReferenceArrayList list = this.intArrayCache.computeIfAbsent(size, (k) -> { + return new ReferenceArrayList<>(); + }); + if (list.isEmpty()) { + return new int[size]; + } else { + int[] popped = list.pop(); + if (zero) { + Arrays.fill(popped, 0); + } + + return popped; + } + } + + public void recycle(double[] array) { + this.doubleArrayCache.computeIfAbsent(array.length, (k) -> { + return new ReferenceArrayList<>(); + }).add(array); + } + + public void recycle(int[] array) { + this.intArrayCache.computeIfAbsent(array.length, (k) -> { + return new ReferenceArrayList<>(); + }).add(array); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java new file mode 100644 index 0000000..5b8c9ef --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.misc.CacheLikeNode; +import org.bxteam.divinemc.dfc.common.ast.misc.DelegateNode; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import java.util.Objects; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.Blender; + +public class AstVanillaInterface implements DensityFunction { + private final AstNode astNode; + private final DensityFunction blendingFallback; + + public AstVanillaInterface(AstNode astNode, DensityFunction blendingFallback) { + this.astNode = (AstNode)Objects.requireNonNull(astNode); + this.blendingFallback = blendingFallback; + } + + public double compute(FunctionContext pos) { + if (pos.getBlender() != Blender.empty()) { + if (this.blendingFallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } else { + return this.blendingFallback.compute(pos); + } + } else { + return this.astNode.evalSingle(pos.blockX(), pos.blockY(), pos.blockZ(), EvalType.from(pos)); + } + } + + public void fillArray(double[] densities, ContextProvider applier) { + if (applier instanceof NoiseChunk sampler) { + if (sampler.getBlender() != Blender.empty()) { + if (this.blendingFallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + this.blendingFallback.fillArray(densities, applier); + return; + } + } + + if (applier instanceof EachApplierVanillaInterface vanillaInterface) { + this.astNode.evalMulti(densities, vanillaInterface.getX(), vanillaInterface.getY(), vanillaInterface.getZ(), EvalType.from(applier)); + } else { + int[] x = new int[densities.length]; + int[] y = new int[densities.length]; + int[] z = new int[densities.length]; + + for(int i = 0; i < densities.length; ++i) { + FunctionContext pos = applier.forIndex(i); + x[i] = pos.blockX(); + y[i] = pos.blockY(); + z[i] = pos.blockZ(); + } + + this.astNode.evalMulti(densities, x, y, z, EvalType.from(applier)); + } + } + + public DensityFunction mapAll(Visitor visitor) { + AstNode transformed = this.astNode.transform((astNode) -> { + if (astNode instanceof DelegateNode delegateNode) { + return new DelegateNode(delegateNode.getDelegate().mapAll(visitor)); + } else if (astNode instanceof CacheLikeNode cacheLikeNode) { + return new CacheLikeNode((IFastCacheLike)cacheLikeNode.getCacheLike().mapAll(visitor), cacheLikeNode.getDelegate()); + } else { + return astNode; + } + }); + DensityFunction blendingFallback1 = this.blendingFallback != null ? this.blendingFallback.mapAll(visitor) : null; + return transformed == this.astNode && blendingFallback1 == this.blendingFallback ? this : new AstVanillaInterface(transformed, blendingFallback1); + } + + public double minValue() { + return this.blendingFallback.minValue(); + } + + public double maxValue() { + return this.blendingFallback.maxValue(); + } + + public KeyDispatchDataCodec codec() { + throw new UnsupportedOperationException(); + } + + public AstNode getAstNode() { + return this.astNode; + } + + public DensityFunction getBlendingFallback() { + return this.blendingFallback; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java new file mode 100644 index 0000000..d557a82 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java @@ -0,0 +1,58 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class EachApplierVanillaInterface implements DensityFunction.ContextProvider, IArrayCacheCapable { + private final int[] x; + private final int[] y; + private final int[] z; + private final EvalType type; + private final ArrayCache cache; + + public EachApplierVanillaInterface(int[] x, int[] y, int[] z, EvalType type) { + this(x, y, z, type, new ArrayCache()); + } + + public EachApplierVanillaInterface(int[] x, int[] y, int[] z, EvalType type, ArrayCache cache) { + this.x = (int[])Objects.requireNonNull(x); + this.y = (int[])Objects.requireNonNull(y); + this.z = (int[])Objects.requireNonNull(z); + this.type = (EvalType)Objects.requireNonNull(type); + this.cache = (ArrayCache)Objects.requireNonNull(cache); + } + + public DensityFunction.FunctionContext forIndex(int index) { + return new NoisePosVanillaInterface(this.x[index], this.y[index], this.z[index], this.type); + } + + public void fillAllDirectly(double[] densities, DensityFunction densityFunction) { + for(int i = 0; i < this.x.length; ++i) { + densities[i] = densityFunction.compute(this.forIndex(i)); + } + + } + + public int[] getX() { + return this.x; + } + + public int[] getY() { + return this.y; + } + + public int[] getZ() { + return this.z; + } + + public EvalType getType() { + return this.type; + } + + public ArrayCache c2me$getArrayCache() { + return this.cache; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java new file mode 100644 index 0000000..88b83cc --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class NoisePosVanillaInterface implements DensityFunction.FunctionContext { + private final int x; + private final int y; + private final int z; + private final EvalType type; + + public NoisePosVanillaInterface(int x, int y, int z, EvalType type) { + this.x = x; + this.y = y; + this.z = z; + this.type = (EvalType)Objects.requireNonNull(type); + } + + public int blockX() { + return this.x; + } + + public int blockY() { + return this.y; + } + + public int blockZ() { + return this.z; + } + + public EvalType getType() { + return this.type; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java new file mode 100644 index 0000000..81b7b27 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java @@ -0,0 +1,284 @@ +package org.bxteam.divinemc.entity.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.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +public class AsyncPath extends Path { + /** + * marks whether this async path has been processed + */ + private volatile PathProcessState processState = PathProcessState.WAITING; + + /** + * runnables waiting for this to be processed + */ + private final List postProcessing = new ArrayList<>(0); + + /** + * a list of positions that this path could path towards + */ + private final Set positions; + + /** + * the supplier of the real processed path + */ + private final Supplier pathSupplier; + + /* + * Processed values + */ + + /** + * this is a reference to the nodes list in the parent `Path` object + */ + private final List 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 emptyNodeList, @NotNull Set positions, @NotNull Supplier pathSupplier) { + //noinspection ConstantConditions + super(emptyNodeList, null, false); + + this.nodes = emptyNodeList; + this.positions = positions; + this.pathSupplier = pathSupplier; + + AsyncPathProcessor.queue(this); + } + + @Override + public boolean isProcessed() { + return this.processState == PathProcessState.COMPLETED; + } + + /** + * returns the future representing the processing state of this path + */ + public synchronized void postProcessing(@NotNull Runnable runnable) { + if (isProcessed()) { + runnable.run(); + } else { + this.postProcessing.add(runnable); + } + } + + /** + * 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 positions) { + if (this.positions.size() != positions.size()) { + return false; + } + + return this.positions.containsAll(positions); + } + + /** + * starts processing this path + */ + public synchronized void process() { + if (this.processState == PathProcessState.COMPLETED || + this.processState == PathProcessState.PROCESSING) { + return; + } + + processState = PathProcessState.PROCESSING; + + 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(); + + processState = PathProcessState.COMPLETED; + + for (Runnable runnable : this.postProcessing) { + runnable.run(); + } // Run tasks after processing + } + + /** + * if this path is accessed while it hasn't processed, just process it in-place + */ + private void checkProcessed() { + if (this.processState == PathProcessState.WAITING || + this.processState == PathProcessState.PROCESSING) { // Block if we are on processing + 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.processState == PathProcessState.COMPLETED && 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(); + } + + public PathProcessState getProcessState() { + return processState; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java new file mode 100644 index 0000000..ad33ab3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java @@ -0,0 +1,48 @@ +package org.bxteam.divinemc.entity.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.*; +import java.util.function.Consumer; + +/** + * used to handle the scheduling of async path processing + */ +public class AsyncPathProcessor { + private static final Executor pathProcessingExecutor = new ThreadPoolExecutor( + 1, + org.bxteam.divinemc.DivineConfig.asyncPathfindingMaxThreads, + org.bxteam.divinemc.DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setNameFormat("DivineMC Async Pathfinding Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); + + protected static CompletableFuture 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.postProcessing(() -> + MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path)) + ); + } else { + afterProcessing.accept(path); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java new file mode 100644 index 0000000..5700f5c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java @@ -0,0 +1,44 @@ +package org.bxteam.divinemc.entity.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> threadLocalNodeEvaluators = new ConcurrentHashMap<>(); + private static final Map nodeEvaluatorToGenerator = new ConcurrentHashMap<>(); + + private static @NotNull Queue getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) { + return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new ConcurrentLinkedQueue<>()); + } + + public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) { + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator); + NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll(); + + if (nodeEvaluator == null) { + nodeEvaluator = generator.generate(nodeEvaluatorFeatures); + } + + nodeEvaluatorToGenerator.put(nodeEvaluator, generator); + + return nodeEvaluator; + } + + public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { + final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator); + Validate.notNull(generator, "NodeEvaluator already returned"); + + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator); + getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator); + } + + public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { + nodeEvaluatorToGenerator.remove(nodeEvaluator); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java new file mode 100644 index 0000000..1f0f1a3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java @@ -0,0 +1,23 @@ +package org.bxteam.divinemc.entity.pathfinding; + +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import net.minecraft.world.level.pathfinder.SwimNodeEvaluator; + +public record NodeEvaluatorFeatures( + NodeEvaluatorType type, + boolean canPassDoors, + boolean canFloat, + boolean canWalkOverFences, + boolean canOpenDoors, + boolean allowBreaching +) { + public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) { + NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator); + boolean canPassDoors = nodeEvaluator.canPassDoors(); + boolean canFloat = nodeEvaluator.canFloat(); + boolean canWalkOverFences = nodeEvaluator.canWalkOverFences(); + boolean canOpenDoors = nodeEvaluator.canOpenDoors(); + boolean allowBreaching = nodeEvaluator instanceof SwimNodeEvaluator swimNodeEvaluator && swimNodeEvaluator.allowBreaching; + return new NodeEvaluatorFeatures(type, canPassDoors, canFloat, canWalkOverFences, canOpenDoors, allowBreaching); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java new file mode 100644 index 0000000..0fa93c7 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java @@ -0,0 +1,8 @@ +package org.bxteam.divinemc.entity.pathfinding; + +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import org.jetbrains.annotations.NotNull; + +public interface NodeEvaluatorGenerator { + @NotNull NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java new file mode 100644 index 0000000..c9f3d8e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java @@ -0,0 +1,17 @@ +package org.bxteam.divinemc.entity.pathfinding; + +import net.minecraft.world.level.pathfinder.*; + +public enum NodeEvaluatorType { + WALK, + SWIM, + AMPHIBIOUS, + FLY; + + public static NodeEvaluatorType fromNodeEvaluator(NodeEvaluator nodeEvaluator) { + if (nodeEvaluator instanceof SwimNodeEvaluator) return SWIM; + if (nodeEvaluator instanceof FlyNodeEvaluator) return FLY; + if (nodeEvaluator instanceof AmphibiousNodeEvaluator) return AMPHIBIOUS; + return WALK; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java new file mode 100644 index 0000000..9211740 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.entity.pathfinding; + +public enum PathProcessState { + WAITING, + PROCESSING, + COMPLETED +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java new file mode 100644 index 0000000..626cbc8 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java @@ -0,0 +1,141 @@ +package org.bxteam.divinemc.entity.tracking; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; +import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; +import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class MultithreadedTracker { + private static final Logger LOGGER = LogManager.getLogger("MultithreadedTracker"); + + public static class MultithreadedTrackerThread extends Thread { + @Override + public void run() { + super.run(); + } + } + + private static final Executor trackerExecutor = new ThreadPoolExecutor( + 1, + org.bxteam.divinemc.DivineConfig.asyncEntityTrackerMaxThreads, + org.bxteam.divinemc.DivineConfig.asyncEntityTrackerKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setThreadFactory( + r -> new MultithreadedTrackerThread() { + @Override + public void run() { + r.run(); + } + } + ) + .setNameFormat("DivineMC Async Tracker Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build()); + + private MultithreadedTracker() { } + + public static Executor getTrackerExecutor() { + return trackerExecutor; + } + + public static void tick(ChunkSystemServerLevel level) { + try { + if (!org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) { + tickAsync(level); + } else { + tickAsyncWithCompatMode(level); + } + } catch (Exception e) { + LOGGER.error("Error occurred while executing async task.", e); + } + } + + private static void tickAsync(ChunkSystemServerLevel level) { + final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); + final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); + + final ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + + // Move tracking to off-main + trackerExecutor.execute(() -> { + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + + ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + tracker.serverEntity.sendChanges(); + } + }); + } + + private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { + final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); + final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); + + final ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; + int index = 0; + + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + + ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + } + + // batch submit tasks + trackerExecutor.execute(() -> { + for (final Runnable sendChanges : sendChangesTasks) { + if (sendChanges == null) continue; + + sendChanges.run(); + } + }); + } + + // Original ChunkMap#newTrackerTick of Paper + // Just for diff usage for future update + @SuppressWarnings("DuplicatedCode") + private static void tickOriginal(ServerLevel level) { + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); + + final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + for (int i = 0, len = trackerEntities.size(); i < len; ++i) { + final Entity entity = trackerEntitiesRaw[i]; + final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + if (tracker == null) { + continue; + } + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers); + if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers() + || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { + tracker.serverEntity.sendChanges(); + } + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java new file mode 100644 index 0000000..29c2693 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java @@ -0,0 +1,105 @@ +package org.bxteam.divinemc.math; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; + +public class Bindings { + private static final Logger LOGGER = LoggerFactory.getLogger(Bindings.class); + + private static final MethodHandle MH_c2me_natives_noise_perlin_double = bind(BindingsTemplate.c2me_natives_noise_perlin_double, "c2me_natives_noise_perlin_double"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_ptr = bind(BindingsTemplate.c2me_natives_noise_perlin_double_ptr, "c2me_natives_noise_perlin_double"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_batch = bind(BindingsTemplate.c2me_natives_noise_perlin_double_batch, "c2me_natives_noise_perlin_double_batch"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_batch_partial_ptr = bind(BindingsTemplate.c2me_natives_noise_perlin_double_batch_ptr, "c2me_natives_noise_perlin_double_batch"); + private static final MethodHandle MH_c2me_natives_noise_interpolated = bind(BindingsTemplate.c2me_natives_noise_interpolated, "c2me_natives_noise_interpolated"); + private static final MethodHandle MH_c2me_natives_noise_interpolated_ptr = bind(BindingsTemplate.c2me_natives_noise_interpolated_ptr, "c2me_natives_noise_interpolated"); + private static final MethodHandle MH_c2me_natives_end_islands_sample = bind(BindingsTemplate.c2me_natives_end_islands_sample, "c2me_natives_end_islands_sample"); + private static final MethodHandle MH_c2me_natives_end_islands_sample_ptr = bind(BindingsTemplate.c2me_natives_end_islands_sample_ptr, "c2me_natives_end_islands_sample"); + private static final MethodHandle MH_c2me_natives_biome_access_sample = bind(BindingsTemplate.c2me_natives_biome_access_sample, "c2me_natives_biome_access_sample"); + + private static @Nullable MethodHandle bind(@NotNull MethodHandle template, String prefix) { + if (NativeLoader.currentMachineTarget == null) { + LOGGER.warn("Call to bindings was found! Please disable native acceleration in config, as your system may be incompatible"); + return null; + } + return template.bindTo(NativeLoader.lookup.find(prefix + NativeLoader.currentMachineTarget.getSuffix()).get()); + } + + public static double c2me_natives_noise_perlin_double(MemorySegment data, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_perlin_double.invokeExact(data, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_perlin_double(long data_ptr, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_perlin_double_ptr.invokeExact(data_ptr, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static void c2me_natives_noise_perlin_double_batch(MemorySegment data, MemorySegment res, MemorySegment x, MemorySegment y, MemorySegment z, int length) { + try { + MH_c2me_natives_noise_perlin_double_batch.invokeExact(data, res, x, y, z, length); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static void c2me_natives_noise_perlin_double_batch(long data_ptr, MemorySegment res, MemorySegment x, MemorySegment y, MemorySegment z, int length) { + try { + MH_c2me_natives_noise_perlin_double_batch_partial_ptr.invokeExact(data_ptr, res, x, y, z, length); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_interpolated(MemorySegment data, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_interpolated.invokeExact(data, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_interpolated(long data_ptr, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_interpolated_ptr.invokeExact(data_ptr, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static float c2me_natives_end_islands_sample(MemorySegment data, int x, int z) { + try { + return (float) MH_c2me_natives_end_islands_sample.invokeExact(data, x, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static float c2me_natives_end_islands_sample(long data_ptr, int x, int z) { + if ((x * x + z * z) < 0) { // workaround some compiler bugs + return Float.NaN; + } + try { + return (float) MH_c2me_natives_end_islands_sample_ptr.invokeExact(data_ptr, x, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static int c2me_natives_biome_access_sample(long seed, int x, int y, int z) { + try { + return (int) MH_c2me_natives_biome_access_sample.invokeExact(seed, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java new file mode 100644 index 0000000..cbb97f0 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java @@ -0,0 +1,407 @@ +package org.bxteam.divinemc.math; + +import net.minecraft.world.level.levelgen.synth.BlendedNoise; +import net.minecraft.world.level.levelgen.synth.ImprovedNoise; +import net.minecraft.world.level.levelgen.synth.PerlinNoise; +import org.jetbrains.annotations.NotNull; +import org.bxteam.divinemc.util.MemoryUtil; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.Objects; +import java.util.stream.IntStream; + +public class BindingsTemplate { + // double c2me_natives_noise_perlin_sample (const uint8_t *permutations, double originX, double originY, double originZ, double x, double y, double z, double yScale, double yMax) + public static final MethodHandle c2me_natives_noise_perlin_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(true) + ); + + // c2me_natives_noise_perlin_double, double, (const double_octave_sampler_data_t *data, double x, double y, double z) + public static final MethodHandle c2me_natives_noise_perlin_double = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + public static final MethodHandle c2me_natives_noise_perlin_double_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + + // c2me_natives_noise_perlin_double_batch, void, (const double_octave_sampler_data_t *const data, + // double *const res, const double *const x, + // const double *const y, const double *const z, + // const uint32_t length) + public static final MethodHandle c2me_natives_noise_perlin_double_batch = NativeLoader.linker.downcallHandle( + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + public static final MethodHandle c2me_natives_noise_perlin_double_batch_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.ofVoid( + ValueLayout.JAVA_LONG, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + + + public static final StructLayout double_octave_sampler_data = MemoryLayout.structLayout( + ValueLayout.JAVA_LONG.withName("length"), + ValueLayout.JAVA_DOUBLE.withName("amplitude"), + ValueLayout.ADDRESS.withName("need_shift"), + ValueLayout.ADDRESS.withName("lacunarity_powd"), + ValueLayout.ADDRESS.withName("persistence_powd"), + ValueLayout.ADDRESS.withName("sampler_permutations"), + ValueLayout.ADDRESS.withName("sampler_originX"), + ValueLayout.ADDRESS.withName("sampler_originY"), + ValueLayout.ADDRESS.withName("sampler_originZ"), + ValueLayout.ADDRESS.withName("amplitudes") + ).withByteAlignment(32).withName("double_double_octave_sampler_data"); + public static final VarHandle double_octave_sampler_data$length = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle double_octave_sampler_data$amplitude = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("amplitude")); + public static final VarHandle double_octave_sampler_data$need_shift = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("need_shift")); + public static final VarHandle double_octave_sampler_data$lacunarity_powd = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("lacunarity_powd")); + public static final VarHandle double_octave_sampler_data$persistence_powd = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("persistence_powd")); + public static final VarHandle double_octave_sampler_data$sampler_permutations = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle double_octave_sampler_data$sampler_originX = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle double_octave_sampler_data$sampler_originY = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle double_octave_sampler_data$sampler_originZ = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle double_octave_sampler_data$amplitudes = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("amplitudes")); + + public static MemorySegment double_octave_sampler_data$create(Arena arena, @NotNull PerlinNoise firstSampler, PerlinNoise secondSampler, double amplitude) { + long nonNullSamplerCount = 0; + for (ImprovedNoise sampler : (firstSampler.noiseLevels)) { + if (sampler != null) { + nonNullSamplerCount++; + } + } + for (ImprovedNoise sampler : (secondSampler.noiseLevels)) { + if (sampler != null) { + nonNullSamplerCount++; + } + } + final MemorySegment data = arena.allocate(double_octave_sampler_data.byteSize(), 64); + final MemorySegment need_shift = arena.allocate(nonNullSamplerCount, 64); + final MemorySegment lacunarity_powd = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment persistence_powd = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_permutations = arena.allocate(nonNullSamplerCount * 256 * 4, 64); + final MemorySegment sampler_originX = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_originY = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_originZ = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment amplitudes = arena.allocate(nonNullSamplerCount * 8, 64); + double_octave_sampler_data$length.set(data, 0L, nonNullSamplerCount); + double_octave_sampler_data$amplitude.set(data, 0L, amplitude); + double_octave_sampler_data$need_shift.set(data, 0L, need_shift); + double_octave_sampler_data$lacunarity_powd.set(data, 0L, lacunarity_powd); + double_octave_sampler_data$persistence_powd.set(data, 0L, persistence_powd); + double_octave_sampler_data$sampler_permutations.set(data, 0L, sampler_permutations); + double_octave_sampler_data$sampler_originX.set(data, 0L, sampler_originX); + double_octave_sampler_data$sampler_originY.set(data, 0L, sampler_originY); + double_octave_sampler_data$sampler_originZ.set(data, 0L, sampler_originZ); + double_octave_sampler_data$amplitudes.set(data, 0L, amplitudes); + long index = 0; + { + ImprovedNoise[] octaveSamplers = (firstSampler.noiseLevels); + for (int i = 0, octaveSamplersLength = octaveSamplers.length; i < octaveSamplersLength; i++) { + ImprovedNoise sampler = octaveSamplers[i]; + if (sampler != null) { + need_shift.set(ValueLayout.JAVA_BOOLEAN, index, false); + lacunarity_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.lowestFreqInputFactor) * Math.pow(2.0, i)); + persistence_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.lowestFreqValueFactor) * Math.pow(2.0, -i)); + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.zo); + amplitudes.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.amplitudes).getDouble(i)); + index++; + } + } + } + { + ImprovedNoise[] octaveSamplers = (secondSampler.noiseLevels); + for (int i = 0, octaveSamplersLength = octaveSamplers.length; i < octaveSamplersLength; i++) { + ImprovedNoise sampler = octaveSamplers[i]; + if (sampler != null) { + need_shift.set(ValueLayout.JAVA_BOOLEAN, index, true); + lacunarity_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.lowestFreqInputFactor) * Math.pow(2.0, i)); + persistence_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.lowestFreqValueFactor) * Math.pow(2.0, -i)); + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.zo); + amplitudes.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.amplitudes).getDouble(i)); + index++; + } + } + } + + VarHandle.fullFence(); + + return data; + } + + // c2me_natives_noise_interpolated, double, (const interpolated_noise_sampler_t *const data, const double x, const double y, const double z) + public static final MethodHandle c2me_natives_noise_interpolated = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + public static final MethodHandle c2me_natives_noise_interpolated_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + + // typedef const struct interpolated_noise_sub_sampler { + // const aligned_uint8_ptr sampler_permutations; + // const aligned_double_ptr sampler_originX; + // const aligned_double_ptr sampler_originY; + // const aligned_double_ptr sampler_originZ; + // const aligned_double_ptr sampler_mulFactor; + // const uint32_t length; + // } interpolated_noise_sub_sampler_t; + public static final StructLayout interpolated_noise_sub_sampler = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("sampler_permutations"), + ValueLayout.ADDRESS.withName("sampler_originX"), + ValueLayout.ADDRESS.withName("sampler_originY"), + ValueLayout.ADDRESS.withName("sampler_originZ"), + ValueLayout.ADDRESS.withName("sampler_mulFactor"), + ValueLayout.JAVA_INT.withName("length"), + MemoryLayout.paddingLayout(4) + ).withName("interpolated_noise_sub_sampler_t"); + + // typedef const struct interpolated_noise_sampler { + // const double scaledXzScale; + // const double scaledYScale; + // const double xzFactor; + // const double yFactor; + // const double smearScaleMultiplier; + // const double xzScale; + // const double yScale; + // + // const interpolated_noise_sub_sampler_t lower; + // const interpolated_noise_sub_sampler_t upper; + // const interpolated_noise_sub_sampler_t normal; + // } interpolated_noise_sampler_t; + public static final StructLayout interpolated_noise_sampler = MemoryLayout.structLayout( + ValueLayout.JAVA_DOUBLE.withName("scaledXzScale"), + ValueLayout.JAVA_DOUBLE.withName("scaledYScale"), + ValueLayout.JAVA_DOUBLE.withName("xzFactor"), + ValueLayout.JAVA_DOUBLE.withName("yFactor"), + ValueLayout.JAVA_DOUBLE.withName("smearScaleMultiplier"), + ValueLayout.JAVA_DOUBLE.withName("xzScale"), + ValueLayout.JAVA_DOUBLE.withName("yScale"), + + interpolated_noise_sub_sampler.withName("lower"), + interpolated_noise_sub_sampler.withName("upper"), + interpolated_noise_sub_sampler.withName("normal") + ).withByteAlignment(32).withName("interpolated_noise_sampler_t"); + + public static final VarHandle interpolated_noise_sampler$scaledXzScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("scaledXzScale")); + public static final VarHandle interpolated_noise_sampler$scaledYScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("scaledYScale")); + public static final VarHandle interpolated_noise_sampler$xzFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("xzFactor")); + public static final VarHandle interpolated_noise_sampler$yFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("yFactor")); + public static final VarHandle interpolated_noise_sampler$smearScaleMultiplier = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("smearScaleMultiplier")); + public static final VarHandle interpolated_noise_sampler$xzScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("xzScale")); + public static final VarHandle interpolated_noise_sampler$yScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("yScale")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$lower$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$upper$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$normal$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("length")); + + public static boolean interpolated_noise_sampler$isSpecializedBase3dNoiseFunction(BlendedNoise interpolated) { + return IntStream.range(0, 16).mapToObj((interpolated).minLimitNoise::getOctaveNoise).filter(Objects::nonNull).count() == 16 && + IntStream.range(0, 16).mapToObj((interpolated).maxLimitNoise::getOctaveNoise).filter(Objects::nonNull).count() == 16 && + IntStream.range(0, 8).mapToObj((interpolated).mainNoise::getOctaveNoise).filter(Objects::nonNull).count() == 8; + } + + public static MemorySegment interpolated_noise_sampler$create(@NotNull Arena arena, BlendedNoise interpolated) { + final MemorySegment data = arena.allocate(interpolated_noise_sampler.byteSize(), 64); + interpolated_noise_sampler$scaledXzScale.set(data, 0L, (interpolated).xzMultiplier); + interpolated_noise_sampler$scaledYScale.set(data, 0L, (interpolated).yMultiplier); + interpolated_noise_sampler$xzFactor.set(data, 0L, (interpolated).xzFactor); + interpolated_noise_sampler$yFactor.set(data, 0L, (interpolated).yFactor); + interpolated_noise_sampler$smearScaleMultiplier.set(data, 0L, (interpolated).smearScaleMultiplier); + interpolated_noise_sampler$xzScale.set(data, 0L, (interpolated).xzScale); + interpolated_noise_sampler$yScale.set(data, 0L, (interpolated).yScale); + +// if (true) { +// System.out.println(String.format("Interpolated total: %d", countNonNull)); +// System.out.println(String.format("lower: %d", IntStream.range(0, 16).mapToObj(((IInterpolatedNoiseSampler) interpolated).getLowerInterpolatedNoise()::getOctave).filter(Objects::nonNull).count())); +// System.out.println(String.format("upper: %d", IntStream.range(0, 16).mapToObj(((IInterpolatedNoiseSampler) interpolated).getUpperInterpolatedNoise()::getOctave).filter(Objects::nonNull).count())); +// System.out.println(String.format("normal: %d", IntStream.range(0, 8).mapToObj(((IInterpolatedNoiseSampler) interpolated).getInterpolationNoise()::getOctave).filter(Objects::nonNull).count())); +// } + + final MemorySegment sampler_permutations = arena.allocate(40 * 256L * 4L, 64); + final MemorySegment sampler_originX = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_originY = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_originZ = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_mulFactor = arena.allocate(40 * 8L, 64); + + int index = 0; + + { + int startIndex = index; + + for (int i = 0; i < 8; i++) { + ImprovedNoise sampler = (interpolated.mainNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$normal$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$length.set(data, 0L, index - startIndex); + } + + { + int startIndex = index = 8; + + for (int i = 0; i < 16; i++) { + ImprovedNoise sampler = (interpolated.minLimitNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$lower$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$length.set(data, 0L, index - startIndex); + } + + { + int startIndex = index = 8 + 16; + + for (int i = 0; i < 16; i++) { + ImprovedNoise sampler = (interpolated.maxLimitNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$upper$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$length.set(data, 0L, index - startIndex); + } + + VarHandle.fullFence(); + + return data; + } + + // c2me_natives_end_islands_sample, float, (const int32_t *const simplex_permutations, const int32_t x, const int32_t z) + public static final MethodHandle c2me_natives_end_islands_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_FLOAT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + public static final MethodHandle c2me_natives_end_islands_sample_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_FLOAT, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(false) + ); + + // c2me_natives_biome_access_sample, uint32_t, (const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) + public static final MethodHandle c2me_natives_biome_access_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(false) + ); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java new file mode 100644 index 0000000..8c8824d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java @@ -0,0 +1,20 @@ +package org.bxteam.divinemc.math; + +import org.bxteam.divinemc.math.isa.ISA_aarch64; +import org.bxteam.divinemc.math.isa.ISA_x86_64; + +public interface ISATarget { + int ordinal(); + + String getSuffix(); + + boolean isNativelySupported(); + + static Class> getInstance() { + return switch (NativeLoader.NORMALIZED_ARCH) { + case "x86_64" -> ISA_x86_64.class; + case "aarch_64" -> ISA_aarch64.class; + default -> null; + }; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java new file mode 100644 index 0000000..5c680d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java @@ -0,0 +1,179 @@ +package org.bxteam.divinemc.math; + +import io.netty.util.internal.SystemPropertyUtil; +import org.bxteam.divinemc.DivineConfig; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.lang.foreign.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Locale; + +public class NativeLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(NativeLoader.class); + + public static final String NORMALIZED_ARCH = normalizeArch(SystemPropertyUtil.get("os.arch", "")); + public static final String NORMALIZED_OS = normalizeOs(SystemPropertyUtil.get("os.name", "")); + + private static final Arena arena = Arena.ofAuto(); + public static final SymbolLookup lookup; + public static final Linker linker = Linker.nativeLinker(); + public static final ISATarget currentMachineTarget; + + static { + String libName = String.format("%s-%s-%s", NORMALIZED_OS, NORMALIZED_ARCH, System.mapLibraryName("c2me-opts-natives-math")); + lookup = load0(libName); + if (lookup == null) { + currentMachineTarget = null; + DivineConfig.nativeAccelerationEnabled = false; + LOGGER.warn("Disabling native math optimization due to unsupported platform."); + } else { + try { + LOGGER.info("Attempting to call native library. If your game crashes right after this point, native acceleration may not be available for your system."); + int level = (int) linker.downcallHandle( + lookup.find("c2me_natives_get_system_isa").get(), + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.JAVA_BOOLEAN + ) + ).invokeExact(DivineConfig.allowAVX512); + ISATarget target; + if (DivineConfig.isaTargetLevelOverride != -1) { + target = (ISATarget) ISATarget.getInstance().getEnumConstants()[DivineConfig.isaTargetLevelOverride]; + } else { + target = (ISATarget) ISATarget.getInstance().getEnumConstants()[level]; + while (!target.isNativelySupported()) target = (ISATarget) ISATarget.getInstance().getEnumConstants()[target.ordinal() - 1]; + } + currentMachineTarget = target; + LOGGER.info("Detected maximum supported ISA target: {}", currentMachineTarget); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + + @Contract(pure = true) + public static @NotNull String getAvailabilityString() { + if (lookup != null) { + return String.format("Available, with ISA target %s", currentMachineTarget); + } else { + return "Unavailable"; + } + } + + private static @Nullable SymbolLookup load0(String libName) { + // load from resources + try (final InputStream in = NativeLoader.class.getClassLoader().getResourceAsStream(libName)) { + if (in == null) { + LOGGER.warn("Cannot find native library {}, possibly unsupported platform for native acceleration", libName); + return null; + } + final Path tempFile; + if (Boolean.getBoolean("vectorizedgen.preserveNative")) { + tempFile = Path.of(".", libName); + } else { + tempFile = Files.createTempFile(null, libName); + tempFile.toFile().deleteOnExit(); + } + Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); + return SymbolLookup.libraryLookup(tempFile, arena); + } catch (Throwable e) { + LOGGER.warn("Failed to load native library", e); + return null; + } + } + + private static @NotNull String normalize(@NotNull String value) { + return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); + } + + private static @NotNull String normalizeArch(String value) { + value = normalize(value); + if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { + return "x86_64"; + } + if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { + return "x86_32"; + } + if (value.matches("^(ia64|itanium64)$")) { + return "itanium_64"; + } + if (value.matches("^(sparc|sparc32)$")) { + return "sparc_32"; + } + if (value.matches("^(sparcv9|sparc64)$")) { + return "sparc_64"; + } + if (value.matches("^(arm|arm32)$")) { + return "arm_32"; + } + if ("aarch64".equals(value)) { + return "aarch_64"; + } + if (value.matches("^(ppc|ppc32)$")) { + return "ppc_32"; + } + if ("ppc64".equals(value)) { + return "ppc_64"; + } + if ("ppc64le".equals(value)) { + return "ppcle_64"; + } + if ("s390".equals(value)) { + return "s390_32"; + } + if ("s390x".equals(value)) { + return "s390_64"; + } + if ("loongarch64".equals(value)) { + return "loongarch_64"; + } + + return "unknown"; + } + + private static @NotNull String normalizeOs(String value) { + value = normalize(value); + if (value.startsWith("aix")) { + return "aix"; + } + if (value.startsWith("hpux")) { + return "hpux"; + } + if (value.startsWith("os400")) { + // Avoid the names such as os4000 + if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) { + return "os400"; + } + } + if (value.startsWith("linux")) { + return "linux"; + } + if (value.startsWith("macosx") || value.startsWith("osx") || value.startsWith("darwin")) { + return "osx"; + } + if (value.startsWith("freebsd")) { + return "freebsd"; + } + if (value.startsWith("openbsd")) { + return "openbsd"; + } + if (value.startsWith("netbsd")) { + return "netbsd"; + } + if (value.startsWith("solaris") || value.startsWith("sunos")) { + return "sunos"; + } + if (value.startsWith("windows")) { + return "windows"; + } + + return "unknown"; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java new file mode 100644 index 0000000..7c17bfd --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java @@ -0,0 +1,25 @@ +package org.bxteam.divinemc.math.isa; + +import org.bxteam.divinemc.math.ISATarget; + +public enum ISA_aarch64 implements ISATarget { + GENERIC("_generic", true); + + private final String suffix; + private final boolean nativelySupported; + + ISA_aarch64(String suffix, boolean nativelySupported) { + this.suffix = suffix; + this.nativelySupported = nativelySupported; + } + + @Override + public String getSuffix() { + return this.suffix; + } + + @Override + public boolean isNativelySupported() { + return this.nativelySupported; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java new file mode 100644 index 0000000..817b317 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java @@ -0,0 +1,34 @@ +package org.bxteam.divinemc.math.isa; + +import org.bxteam.divinemc.math.ISATarget; + +public enum ISA_x86_64 implements ISATarget { + SSE2("_sse2", true), // 0 + SSE4_1("_sse2", false), // 1, not implemented + SSE4_2("_sse4_2", true), // 2 + AVX("_avx", true), // 3 + AVX2("_avx2", true), // 4 + AVX2ADL("_avx2adl", true), // 5 + AVX512KNL("_avx2", false), // 6, not implemented + AVX512SKX("_avx512skx", true), // 7 + AVX512ICL("_avx512icl", true), // 8 + AVX512SPR("_avx512spr", true); // 9 + + private final String suffix; + private final boolean nativelySupported; + + ISA_x86_64(String suffix, boolean nativelySupported) { + this.suffix = suffix; + this.nativelySupported = nativelySupported; + } + + @Override + public String getSuffix() { + return this.suffix; + } + + @Override + public boolean isNativelySupported() { + return this.nativelySupported; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java new file mode 100644 index 0000000..7f5fc08 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java @@ -0,0 +1,27 @@ +package org.bxteam.divinemc.server; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import java.util.concurrent.ThreadFactory; + +public class ServerLevelTickExecutorThreadFactory implements ThreadFactory { + private final String worldName; + + public ServerLevelTickExecutorThreadFactory(String worldName) { + this.worldName = worldName; + } + + @Override + public Thread newThread(Runnable runnable) { + TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "serverlevel-tick-worker [" + worldName + "]"); + + if (tickThread.isDaemon()) { + tickThread.setDaemon(false); + } + + if (tickThread.getPriority() != 5) { + tickThread.setPriority(5); + } + + return tickThread; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java new file mode 100644 index 0000000..4692fb3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java @@ -0,0 +1,116 @@ +package org.bxteam.divinemc.server.chunk; + +import ca.spottedleaf.moonrise.common.PlatformHooks; +import io.netty.util.internal.PlatformDependent; +import net.objecthunter.exp4j.ExpressionBuilder; +import net.objecthunter.exp4j.function.Function; +import org.jetbrains.annotations.NotNull; +import oshi.util.tuples.Pair; +import java.util.function.BiFunction; + +public enum ChunkSystemAlgorithms { + MOONRISE((configWorkerThreads, configIoThreads) -> { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } + defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + + int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + final int ioThreads = Math.max(1, configIoThreads); + return new Pair<>(workerThreads, ioThreads); + }), + C2ME_AGGRESSIVE((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6), + (cpus / 1.3) + ) - if(is_client, 1, 0), + ( ( mem_gb - (if(is_client, 1.0, 0.5)) ) / 0.6 ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }), + C2ME((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6 - 2), + (cpus / 1.2 - 2) + ) - if(is_client, 2, 0), + if( is_j9vm, + ( ( mem_gb - (if(is_client, 0.6, 0.2)) ) / 0.4 ), + ( ( mem_gb - (if(is_client, 1.2, 0.6)) ) / 0.6 ) + ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }); + + private final BiFunction> eval; + + ChunkSystemAlgorithms(BiFunction> eval) { + this.eval = eval; + } + + private static int tryEvaluateExpression(String expression) { + return (int) Math.max(1, + new ExpressionBuilder(expression) + .variables("is_windows", "is_j9vm", "is_client", "cpus", "mem_gb") + .function(new Function("max", 2) { + @Override + public double apply(double... args) { + return Math.max(args[0], args[1]); + } + }) + .function(new Function("min", 2) { + @Override + public double apply(double... args) { + return Math.min(args[0], args[1]); + } + }) + .function(new Function("if", 3) { + @Override + public double apply(double... args) { + return args[0] != 0 ? args[1] : args[2]; + } + }) + .build() + .setVariable("is_windows", PlatformDependent.isWindows() ? 1 : 0) + .setVariable("is_j9vm", PlatformDependent.isJ9Jvm() ? 1 : 0) + .setVariable("is_client", 0) + .setVariable("cpus", Runtime.getRuntime().availableProcessors()) + .setVariable("mem_gb", Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0 / 1024.0) + .evaluate() + ); + } + + public int evalWorkers(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getA(); + } + + public int evalIO(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getB(); + } + + public @NotNull String asDebugString() { + return this + "(" + evalWorkers(-1, -1) + ")"; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java new file mode 100644 index 0000000..4ea68e0 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java @@ -0,0 +1,421 @@ +package org.bxteam.divinemc.util; + +import java.util.Collection; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; + +/** + * Assertion utility class that assists in validating arguments. + * + *

Useful for identifying programmer errors early and clearly at runtime. + * + *

For example, if the contract of a public method states it does not + * allow {@code null} arguments, {@code Assert} can be used to validate that + * contract. Doing this clearly indicates a contract violation when it + * occurs and protects the class's invariants. + * + *

Typically used to validate method arguments rather than configuration + * properties, to check for cases that are usually programmer errors rather + * than configuration errors. In contrast to configuration initialization + * code, there is usually no point in falling back to defaults in such methods. + * + *

This class is similar to JUnit's assertion library. If an argument value is + * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). + * For example: + * + *

+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ * + *

Mainly for internal use within the framework; for a more comprehensive suite + * of assertion utilities consider {@code org.apache.commons.lang3.Validate} from + * Apache Commons Lang, + * Google Guava's + * Preconditions, + * or similar third-party libraries. + * + * @author Keith Donald + * @author Juergen Hoeller + * @author Sam Brannen + * @author Colin Sampaleanu + * @author Rob Harrop + * @author Sebastien Deleuze + */ +public abstract class Assert { + /** + * Assert a boolean expression, throwing an {@code IllegalStateException} + * if the expression evaluates to {@code false}. + *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} + * on an assertion failure. + *

Assert.state(id == null, "The id property must not already be initialized");
+ * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalStateException if {@code expression} is {@code false} + */ + @Contract("false, _ -> fail") + public static void state(boolean expression, String message) { + if (!expression) { + throw new IllegalStateException(message); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalStateException} + * if the expression evaluates to {@code false}. + *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} + * on an assertion failure. + *

+     * Assert.state(entity.getId() == null,
+     *     () -> "ID for entity " + entity.getName() + " must not already be initialized");
+     * 
+ * @param expression a boolean expression + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalStateException if {@code expression} is {@code false} + * @since 5.0 + */ + @Contract("false, _ -> fail") + public static void state(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalStateException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalArgumentException} + * if the expression evaluates to {@code false}. + *
Assert.isTrue(i > 0, "The value must be greater than zero");
+ * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if {@code expression} is {@code false} + */ + @Contract("false, _ -> fail") + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalArgumentException} + * if the expression evaluates to {@code false}. + *
+     * Assert.isTrue(i > 0, () -> "The value '" + i + "' must be greater than zero");
+     * 
+ * @param expression a boolean expression + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if {@code expression} is {@code false} + * @since 5.0 + */ + @Contract("false, _ -> fail") + public static void isTrue(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an object is {@code null}. + *
Assert.isNull(value, "The value must be null");
+ * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is not {@code null} + */ + @Contract("!null, _ -> fail") + public static void isNull(@Nullable Object object, String message) { + if (object != null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that an object is {@code null}. + *
+     * Assert.isNull(value, () -> "The value '" + value + "' must be null");
+     * 
+ * @param object the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object is not {@code null} + * @since 5.0 + */ + @Contract("!null, _ -> fail") + public static void isNull(@Nullable Object object, Supplier messageSupplier) { + if (object != null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an object is not {@code null}. + *
Assert.notNull(clazz, "The class must not be null");
+ * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is {@code null} + */ + @Contract("null, _ -> fail") + public static void notNull(@Nullable Object object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that an object is not {@code null}. + *
+     * Assert.notNull(entity.getId(),
+     *     () -> "ID for entity " + entity.getName() + " must not be null");
+     * 
+ * @param object the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object is {@code null} + * @since 5.0 + */ + @Contract("null, _ -> fail") + public static void notNull(@Nullable Object object, Supplier messageSupplier) { + if (object == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an array contains no {@code null} elements. + *

Note: Does not complain if the array is empty! + *

Assert.noNullElements(array, "The array must contain non-null elements");
+ * @param array the array to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object array contains a {@code null} element + */ + public static void noNullElements(Object @Nullable [] array, String message) { + if (array != null) { + for (Object element : array) { + if (element == null) { + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Assert that an array contains no {@code null} elements. + *

Note: Does not complain if the array is empty! + *

+     * Assert.noNullElements(array, () -> "The " + arrayType + " array must contain non-null elements");
+     * 
+ * @param array the array to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object array contains a {@code null} element + * @since 5.0 + */ + public static void noNullElements(Object @Nullable [] array, Supplier messageSupplier) { + if (array != null) { + for (Object element : array) { + if (element == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + } + } + + /** + * Assert that a collection contains no {@code null} elements. + *

Note: Does not complain if the collection is empty! + *

Assert.noNullElements(collection, "Collection must contain non-null elements");
+ * @param collection the collection to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the collection contains a {@code null} element + * @since 5.2 + */ + public static void noNullElements(@Nullable Collection collection, String message) { + if (collection != null) { + for (Object element : collection) { + if (element == null) { + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Assert that a collection contains no {@code null} elements. + *

Note: Does not complain if the collection is empty! + *

+     * Assert.noNullElements(collection, () -> "Collection " + collectionName + " must contain non-null elements");
+     * 
+ * @param collection the collection to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the collection contains a {@code null} element + * @since 5.2 + */ + public static void noNullElements(@Nullable Collection collection, Supplier messageSupplier) { + if (collection != null) { + for (Object element : collection) { + if (element == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo, "Foo expected");
+ * @param type the type to check against + * @param obj the object to check + * @param message a message which will be prepended to provide further context. + * If it is empty or ends in ":" or ";" or "," or ".", a full exception message + * will be appended. If it ends in a space, the name of the offending object's + * type will be appended. In any other case, a ":" with a space and the name + * of the offending object's type will be appended. + * @throws IllegalArgumentException if the object is not an instance of type + */ + @Contract("_, null, _ -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj, String message) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + instanceCheckFailed(type, obj, message); + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
+     * Assert.instanceOf(Foo.class, foo, () -> "Processing " + Foo.class.getSimpleName() + ":");
+     * 
+ * @param type the type to check against + * @param obj the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details. + * @throws IllegalArgumentException if the object is not an instance of type + * @since 5.0 + */ + @Contract("_, null, _ -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj, Supplier messageSupplier) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + instanceCheckFailed(type, obj, nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo);
+ * @param type the type to check against + * @param obj the object to check + * @throws IllegalArgumentException if the object is not an instance of type + */ + @Contract("_, null -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj) { + isInstanceOf(type, obj, ""); + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
Assert.isAssignable(Number.class, myClass, "Number expected");
+ * @param superType the supertype to check against + * @param subType the subtype to check + * @param message a message which will be prepended to provide further context. + * If it is empty or ends in ":" or ";" or "," or ".", a full exception message + * will be appended. If it ends in a space, the name of the offending subtype + * will be appended. In any other case, a ":" with a space and the name of the + * offending subtype will be appended. + * @throws IllegalArgumentException if the classes are not assignable + */ + @Contract("_, null, _ -> fail") + public static void isAssignable(Class superType, @Nullable Class subType, String message) { + notNull(superType, "Supertype to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + assignableCheckFailed(superType, subType, message); + } + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
+     * Assert.isAssignable(Number.class, myClass, () -> "Processing " + myAttributeName + ":");
+     * 
+ * @param superType the supertype to check against + * @param subType the subtype to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails. See {@link #isAssignable(Class, Class, String)} for details. + * @throws IllegalArgumentException if the classes are not assignable + * @since 5.0 + */ + @Contract("_, null, _ -> fail") + public static void isAssignable(Class superType, @Nullable Class subType, Supplier messageSupplier) { + notNull(superType, "Supertype to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
Assert.isAssignable(Number.class, myClass);
+ * @param superType the supertype to check + * @param subType the subtype to check + * @throws IllegalArgumentException if the classes are not assignable + */ + @Contract("_, null -> fail") + public static void isAssignable(Class superType, @Nullable Class subType) { + isAssignable(superType, subType, ""); + } + + + private static void instanceCheckFailed(Class type, @Nullable Object obj, @Nullable String msg) { + String className = (obj != null ? obj.getClass().getName() : "null"); + String result = ""; + boolean defaultMessage = true; + if (StringUtil.hasLength(msg)) { + if (endsWithSeparator(msg)) { + result = msg + " "; + } + else { + result = messageWithTypeName(msg, className); + defaultMessage = false; + } + } + if (defaultMessage) { + result = result + ("Object of class [" + className + "] must be an instance of " + type); + } + throw new IllegalArgumentException(result); + } + + private static void assignableCheckFailed(Class superType, @Nullable Class subType, @Nullable String msg) { + String result = ""; + boolean defaultMessage = true; + if (StringUtil.hasLength(msg)) { + if (endsWithSeparator(msg)) { + result = msg + " "; + } + else { + result = messageWithTypeName(msg, subType); + defaultMessage = false; + } + } + if (defaultMessage) { + result = result + (subType + " is not assignable to " + superType); + } + throw new IllegalArgumentException(result); + } + + private static boolean endsWithSeparator(@NotNull String msg) { + return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith(".")); + } + + @Contract(pure = true) + private static @NotNull String messageWithTypeName(@NotNull String msg, @Nullable Object typeName) { + return msg + (msg.endsWith(" ") ? "" : ": ") + typeName; + } + + private static @Nullable String nullSafeGet(@Nullable Supplier messageSupplier) { + return (messageSupplier != null ? messageSupplier.get() : null); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java new file mode 100644 index 0000000..98975c5 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java @@ -0,0 +1,27 @@ +package org.bxteam.divinemc.util; + +public final class Assertions { + public static void assertTrue(boolean value, String message) { + if (!value) { + final AssertionError error = new AssertionError(message); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean state, String format, Object... args) { + if (!state) { + final AssertionError error = new AssertionError(String.format(format, args)); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean value) { + if (!value) { + final AssertionError error = new AssertionError(); + error.printStackTrace(); + throw error; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java new file mode 100644 index 0000000..328f27d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.util; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list is behaving identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int j = startSearchFromIndex; + int matches = 0; + for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions + if (!c.contains(i)) { + // TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements, + // this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing + // "arraycopy" for each element + a[j++] = a[i]; + } else { + matches++; + } + + if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else + // We need to update the final size here, because we know that we already found everything! + // Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value + // However, we need to copy the rest of the stuff over + if (i != (size - 1)) { // If it isn't the last index... + // i + 1 because we want to copy the *next* element over + // and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element) + System.arraycopy(a, i + 1, a, j, size - i - 1); + } + j = size - requiredMatches; + break; + } + } + Arrays.fill(a, j, size, null); + size = j; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java new file mode 100644 index 0000000..7d02eb2 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.util; + +import java.io.File; +import java.io.IOException; + +public final class Files { + public static void deleteRecursively(File dir) throws IOException { + if (dir == null || !dir.isDirectory()) { + return; + } + + try { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("Error enumerating directory during recursive delete operation: " + dir.getAbsolutePath()); + } + + for (File child : files) { + if (child.isDirectory()) { + Files.deleteRecursively(child); + } else if (child.isFile()) { + if (!child.delete()) { + throw new IOException("Error deleting file during recursive delete operation: " + child.getAbsolutePath()); + } + } + } + + if (!dir.delete()) { + throw new IOException("Error deleting directory during recursive delete operation: " + dir.getAbsolutePath()); + } + } catch (SecurityException ex) { + throw new IOException("Security error during recursive delete operation", ex); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java new file mode 100644 index 0000000..1c84493 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java @@ -0,0 +1,12 @@ +package org.bxteam.divinemc.util; + +public final class MemoryUtil { + public static int[] byte2int(byte[] data) { + if (data == null) return null; + int[] ints = new int[data.length]; + for (int i = 0; i < data.length; i++) { + ints[i] = data[i] & 0xff; + } + return ints; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java new file mode 100644 index 0000000..358a393 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java @@ -0,0 +1,108 @@ +package org.bxteam.divinemc.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import java.util.Arrays; + +public class ObjectUtil { + @Contract("null, null -> true; null, _ -> false; _, null -> false") + public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + return arrayEquals(o1, o2); + } + return false; + } + + /** + * Compare the given arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * @param o1 first array to compare + * @param o2 second array to compare + * @return whether the given objects are equal + * @see #nullSafeEquals(Object, Object) + * @see java.util.Arrays#equals + */ + private static boolean arrayEquals(Object o1, Object o2) { + if (o1 instanceof Object[] objects1 && o2 instanceof Object[] objects2) { + return Arrays.equals(objects1, objects2); + } + if (o1 instanceof boolean[] booleans1 && o2 instanceof boolean[] booleans2) { + return Arrays.equals(booleans1, booleans2); + } + if (o1 instanceof byte[] bytes1 && o2 instanceof byte[] bytes2) { + return Arrays.equals(bytes1, bytes2); + } + if (o1 instanceof char[] chars1 && o2 instanceof char[] chars2) { + return Arrays.equals(chars1, chars2); + } + if (o1 instanceof double[] doubles1 && o2 instanceof double[] doubles2) { + return Arrays.equals(doubles1, doubles2); + } + if (o1 instanceof float[] floats1 && o2 instanceof float[] floats2) { + return Arrays.equals(floats1, floats2); + } + if (o1 instanceof int[] ints1 && o2 instanceof int[] ints2) { + return Arrays.equals(ints1, ints2); + } + if (o1 instanceof long[] longs1 && o2 instanceof long[] longs2) { + return Arrays.equals(longs1, longs2); + } + if (o1 instanceof short[] shorts1 && o2 instanceof short[] shorts2) { + return Arrays.equals(shorts1, shorts2); + } + return false; + } + + /** + * Return a hash code for the given object; typically the value of + * {@code Object#hashCode()}}. If the object is an array, + * this method will delegate to any of the {@code Arrays.hashCode} + * methods. If the object is {@code null}, this method returns 0. + * @see Object#hashCode() + * @see Arrays + */ + public static int nullSafeHashCode(@Nullable Object obj) { + if (obj == null) { + return 0; + } + if (obj.getClass().isArray()) { + if (obj instanceof Object[] objects) { + return Arrays.hashCode(objects); + } + if (obj instanceof boolean[] booleans) { + return Arrays.hashCode(booleans); + } + if (obj instanceof byte[] bytes) { + return Arrays.hashCode(bytes); + } + if (obj instanceof char[] chars) { + return Arrays.hashCode(chars); + } + if (obj instanceof double[] doubles) { + return Arrays.hashCode(doubles); + } + if (obj instanceof float[] floats) { + return Arrays.hashCode(floats); + } + if (obj instanceof int[] ints) { + return Arrays.hashCode(ints); + } + if (obj instanceof long[] longs) { + return Arrays.hashCode(longs); + } + if (obj instanceof short[] shorts) { + return Arrays.hashCode(shorts); + } + } + return obj.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java new file mode 100644 index 0000000..5f47188 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java @@ -0,0 +1,40 @@ +package org.bxteam.divinemc.util; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import net.minecraft.world.level.levelgen.SingleThreadedRandomSource; +import net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus; +import net.minecraft.world.level.levelgen.XoroshiroRandomSource; +import org.jetbrains.annotations.NotNull; + +public final class RandomUtil { + public static @NotNull RandomSource getRandom(PositionalRandomFactory deriver) { + if (deriver instanceof XoroshiroRandomSource.XoroshiroPositionalRandomFactory) { + return new XoroshiroRandomSource(0L, 0L); + } + if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory) { + return new SingleThreadedRandomSource(0L); + } + throw new IllegalArgumentException(); + } + + private static final ThreadLocal xoroshiro = ThreadLocal.withInitial(() -> new XoroshiroRandomSource(0L, 0L)); + private static final ThreadLocal simple = ThreadLocal.withInitial(() -> new SingleThreadedRandomSource(0L)); + + public static void derive(PositionalRandomFactory deriver, RandomSource random, int x, int y, int z) { + if (deriver instanceof final XoroshiroRandomSource.XoroshiroPositionalRandomFactory deriver1) { + final Xoroshiro128PlusPlus implementation = ((XoroshiroRandomSource) random).randomNumberGenerator; + implementation.seedLo = (Mth.getSeed(x, y, z) ^ deriver1.seedLo()); + implementation.seedHi = (deriver1.seedHi()); + return; + } + if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory(long seed)) { + final SingleThreadedRandomSource random1 = (SingleThreadedRandomSource) random; + random1.setSeed(Mth.getSeed(x, y, z) ^ seed); + return; + } + throw new IllegalArgumentException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java new file mode 100644 index 0000000..1d1ba14 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java @@ -0,0 +1,9 @@ +package org.bxteam.divinemc.util; + +import org.jetbrains.annotations.Nullable; + +public final class StringUtil { + public static boolean hasLength(@Nullable String str) { + return (str != null && !str.isEmpty()); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java new file mode 100644 index 0000000..d06a9b1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java @@ -0,0 +1,217 @@ +package org.bxteam.divinemc.util.collections; + +import it.unimi.dsi.fastutil.bytes.ByteBytePair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.ai.behavior.LongJumpToRandomPos; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +public class LongJumpChoiceList extends AbstractList { + /** + * A cache of choice lists for different ranges. The elements must not be mutated, but copied instead. + * In vanilla minecraft there should be two elements, one for frog jumps and one for goat jumps. + */ + private static final ConcurrentHashMap CHOICE_LISTS = new ConcurrentHashMap<>(); + /** + * The choice list for frog jumps. Skipping the hash map access. Must not be mutated, but copied instead. + */ + private static final LongJumpChoiceList FROG_JUMP = new LongJumpChoiceList((byte) 4, (byte) 2); + /** + * The choice list for goat jumps. Skipping the hash map access. Must not be mutated, but copied instead. + */ + private static final LongJumpChoiceList GOAT_JUMP = new LongJumpChoiceList((byte) 5, (byte) 5); + + + private final BlockPos origin; + private final IntArrayList[] packedOffsetsByDistanceSq; + private final int[] weightByDistanceSq; + private int totalWeight; + + /** + * Constructs a new LongJumpChoiceList with the given horizontal and vertical range. + * We avoid creating too many objects here, e.g. LongJumpTask.Target is not created yet. + * @param horizontalRange the horizontal range + * @param verticalRange the vertical range + */ + public LongJumpChoiceList(byte horizontalRange, byte verticalRange) { + if (horizontalRange < 0 || verticalRange < 0) { + throw new IllegalArgumentException("The ranges must be within 0..127!"); + } + + this.origin = BlockPos.ZERO; + int maxSqDistance = horizontalRange*horizontalRange * 2 + verticalRange*verticalRange; + this.packedOffsetsByDistanceSq = new IntArrayList[maxSqDistance]; + this.weightByDistanceSq = new int[maxSqDistance]; + + for (int x = -horizontalRange; x <= horizontalRange; x++) { + for (int y = -verticalRange; y <= verticalRange; y++) { + for (int z = -horizontalRange; z <= horizontalRange; z++) { + int squaredDistance = x * x + y * y + z * z; + int index = squaredDistance - 1; + if (index >= 0) { //exclude origin (distance 0) + int packedOffset = this.packOffset(x, y, z); + IntArrayList offsets = this.packedOffsetsByDistanceSq[index]; + if (offsets == null) { + this.packedOffsetsByDistanceSq[index] = offsets = new IntArrayList(); + } + offsets.add(packedOffset); + this.weightByDistanceSq[index] += squaredDistance; + this.totalWeight += squaredDistance; + } + } + } + } + } + + public LongJumpChoiceList(BlockPos origin, IntArrayList[] packedOffsetsByDistanceSq, int[] weightByDistanceSq, int totalWeight) { + this.origin = origin; + this.packedOffsetsByDistanceSq = packedOffsetsByDistanceSq; + this.weightByDistanceSq = weightByDistanceSq; + this.totalWeight = totalWeight; + } + + private int packOffset(int x, int y, int z) { + return (x + 128) | ((y + 128) << 8) | ((z + 128) << 16); + } + + private int unpackX(int packedOffset) { + return (packedOffset & 0xFF) - 128; + } + + private int unpackY(int packedOffset) { + return ((packedOffset >>> 8) & 0xFF) - 128; + } + + private int unpackZ(int packedOffset) { + return ((packedOffset >>> 16) & 0xFF) - 128; + } + + /** + * Returns a LongJumpChoiceList for the given center position and ranges. + * Quickly creates the list by copying an existing, memoized list. + * @param centerPos the center position + * @param horizontalRange the horizontal range + * @param verticalRange the vertical range + * @return a LongJumpChoiceList for the given parameters + */ + public static LongJumpChoiceList forCenter(BlockPos centerPos, byte horizontalRange, byte verticalRange) { + if (horizontalRange < 0 || verticalRange < 0) { + throw new IllegalArgumentException("The ranges must be within 0..127!"); + } + + LongJumpChoiceList jumpDestinationsList; + short range = (short) ((horizontalRange << 8) | verticalRange); + if (range == ((4 << 8) | 2)) { + //Frog jump + jumpDestinationsList = LongJumpChoiceList.FROG_JUMP; + } else if (range == ((5 << 8) | 5)) { + //Goat jump + jumpDestinationsList = LongJumpChoiceList.GOAT_JUMP; + } else { + jumpDestinationsList = LongJumpChoiceList.CHOICE_LISTS.computeIfAbsent( + ByteBytePair.of(horizontalRange, verticalRange), + key -> new LongJumpChoiceList(key.leftByte(), key.rightByte()) + ); + } + + return jumpDestinationsList.offsetCopy(centerPos); + } + + private LongJumpChoiceList offsetCopy(BlockPos offset) { + IntArrayList[] packedOffsetsByDistanceSq = new IntArrayList[this.packedOffsetsByDistanceSq.length]; + for (int i = 0; i < packedOffsetsByDistanceSq.length; i++) { + IntArrayList packedOffsets = this.packedOffsetsByDistanceSq[i]; + if (packedOffsets != null) { + packedOffsetsByDistanceSq[i] = packedOffsets.clone(); + } + } + + return new LongJumpChoiceList( + this.origin.offset(offset), + packedOffsetsByDistanceSq, + Arrays.copyOf(this.weightByDistanceSq, this.weightByDistanceSq.length), this.totalWeight); + } + + /** + * Removes and returns a random target from the list, weighted by squared distance. + * @param random the random number generator + * @return a random target + */ + public LongJumpToRandomPos.PossibleJump removeRandomWeightedByDistanceSq(RandomSource random) { + int targetWeight = random.nextInt(this.totalWeight); + for (int index = 0; targetWeight >= 0 && index < this.weightByDistanceSq.length; index++) { + targetWeight -= this.weightByDistanceSq[index]; + if (targetWeight < 0) { + int distanceSq = index + 1; + IntArrayList elementsOfDistance = this.packedOffsetsByDistanceSq[index]; + int elementIndex = random.nextInt(elementsOfDistance.size()); + + //fast remove by swapping to end and removing, order does not matter + elementsOfDistance.set(elementIndex, elementsOfDistance.set(elementsOfDistance.size() - 1, elementsOfDistance.getInt(elementIndex))); + int packedOffset = elementsOfDistance.removeInt(elementsOfDistance.size() - 1); + this.weightByDistanceSq[index] -= distanceSq; + this.totalWeight -= distanceSq; + + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + } + return null; + } + + @Override + public LongJumpToRandomPos.PossibleJump get(int index) { + int elementIndex = index; + IntArrayList[] offsetsByDistanceSq = this.packedOffsetsByDistanceSq; + for (int distanceSq = 0; distanceSq < offsetsByDistanceSq.length; distanceSq++) { + IntArrayList packedOffsets = offsetsByDistanceSq[distanceSq]; + if (packedOffsets != null) { + if (elementIndex < packedOffsets.size()) { + int packedOffset = packedOffsets.getInt(elementIndex); + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + elementIndex -= packedOffsets.size(); + } + } + throw new IndexOutOfBoundsException(); + } + + @Override + public boolean isEmpty() { + return this.totalWeight == 0; + } + + @Override + public int size() { + int size = 0; + for (IntArrayList packedOffsets : this.packedOffsetsByDistanceSq) { + if (packedOffsets != null) { + size += packedOffsets.size(); + } + } + return size; + } + + @Override + public LongJumpToRandomPos.PossibleJump remove(int index) { + int elementIndex = index; + IntArrayList[] offsetsByDistanceSq = this.packedOffsetsByDistanceSq; + for (int distanceSq = 0; distanceSq < offsetsByDistanceSq.length; distanceSq++) { + IntArrayList packedOffsets = offsetsByDistanceSq[distanceSq]; + if (packedOffsets != null) { + if (elementIndex < packedOffsets.size()) { + int packedOffset = packedOffsets.getInt(elementIndex); + packedOffsets.set(elementIndex, packedOffsets.set(packedOffsets.size() - 1, packedOffsets.getInt(elementIndex))); + packedOffsets.removeInt(packedOffsets.size() - 1); + this.weightByDistanceSq[distanceSq] -= distanceSq; + this.totalWeight -= distanceSq; + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + elementIndex -= packedOffsets.size(); + } + } + throw new IndexOutOfBoundsException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java new file mode 100644 index 0000000..e60f8d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java @@ -0,0 +1,150 @@ +package org.bxteam.divinemc.util.collections; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.AbstractList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; + +public class MaskedList extends AbstractList { + private final ObjectArrayList allElements; + private final BitSet visibleMask; + private final Object2IntOpenHashMap element2Index; + private final boolean defaultVisibility; + private int numCleared; + + public MaskedList(ObjectArrayList allElements, boolean defaultVisibility) { + this.allElements = new ObjectArrayList<>(); + this.visibleMask = new BitSet(); + this.defaultVisibility = defaultVisibility; + this.element2Index = new Object2IntOpenHashMap<>(); + this.element2Index.defaultReturnValue(-1); + + this.addAll(allElements); + } + + public MaskedList() { + this(new ObjectArrayList<>(), true); + } + + public int totalSize() { + return this.allElements.size(); + } + + public void addOrSet(E element, boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } else { + this.add(element); + this.setVisible(element, visible); + } + } + + public void setVisible(E element, final boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + int nextIndex = 0; + int cachedNext = -1; + + @Override + public boolean hasNext() { + return (this.cachedNext = MaskedList.this.visibleMask.nextSetBit(this.nextIndex)) != -1; + } + + @Override + public E next() { + int index = this.cachedNext; + this.cachedNext = -1; + this.nextIndex = index + 1; + return MaskedList.this.allElements.get(index); + } + }; + } + + @Override + public Spliterator spliterator() { + return new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) { + int nextIndex = 0; + + @Override + public boolean tryAdvance(Consumer action) { + int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex); + if (index == -1) { + return false; + } + this.nextIndex = index + 1; + action.accept(MaskedList.this.allElements.get(index)); + return true; + } + }; + } + + @Override + public boolean add(E e) { + int oldIndex = this.element2Index.put(e, this.allElements.size()); + if (oldIndex != -1) { + throw new IllegalStateException("MaskedList must not contain duplicates! Trying to add " + e + " but it is already present at index " + oldIndex + ". Current size: " + this.allElements.size()); + } + this.visibleMask.set(this.allElements.size(), this.defaultVisibility); + return this.allElements.add(e); + } + + @Override + public boolean remove(Object o) { + int index = this.element2Index.removeInt(o); + if (index == -1) { + return false; + } + this.visibleMask.clear(index); + this.allElements.set(index, null); + this.numCleared++; + + + if (this.numCleared * 2 > this.allElements.size()) { + ObjectArrayList clonedElements = this.allElements.clone(); + BitSet clonedVisibleMask = (BitSet) this.visibleMask.clone(); + this.allElements.clear(); + this.visibleMask.clear(); + this.element2Index.clear(); + for (int i = 0; i < clonedElements.size(); i++) { + E element = clonedElements.get(i); + int newIndex = this.allElements.size(); + this.allElements.add(element); + this.visibleMask.set(newIndex, clonedVisibleMask.get(i)); + this.element2Index.put(element, newIndex); + } + this.numCleared = 0; + } + return true; + } + + @Override + public E get(int index) { + if (index < 0 || index >= this.size()) { + throw new IndexOutOfBoundsException(index); + } + + int i = 0; + while (index >= 0) { + index--; + i = this.visibleMask.nextSetBit(i + 1); + } + return this.allElements.get(i); + } + + @Override + public int size() { + return this.visibleMask.cardinality(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java new file mode 100644 index 0000000..d8636f4 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java @@ -0,0 +1,53 @@ +package org.bxteam.divinemc.util.entity; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.Brain; +import net.minecraft.world.entity.ai.sensing.Sensor; +import net.minecraft.world.entity.ai.sensing.SensorType; + +public class SensorHelper { + public static void disableSensor(LivingEntity brainedEntity, SensorType sensorType) { + if (brainedEntity.level().isClientSide()) { + return; + } + Brain brain = brainedEntity.getBrain(); + Sensor sensor = (brain).sensors.get(sensorType); + if (sensor != null) { + long lastSenseTime = sensor.timeToTick; + int senseInterval = sensor.scanRate; + + long maxMultipleOfSenseInterval = Long.MAX_VALUE - (Long.MAX_VALUE % senseInterval); + maxMultipleOfSenseInterval -= senseInterval; + maxMultipleOfSenseInterval += lastSenseTime; + + sensor.timeToTick = (maxMultipleOfSenseInterval); + } + } + + public static > void enableSensor(T brainedEntity, SensorType sensorType) { + enableSensor(brainedEntity, sensorType, false); + } + + public static > void enableSensor(T brainedEntity, SensorType sensorType, boolean extraTick) { + if (brainedEntity.level().isClientSide()) { + return; + } + + Brain brain = brainedEntity.getBrain(); + U sensor = (U) (brain).sensors.get(sensorType); + if (sensor != null) { + long lastSenseTime = sensor.timeToTick; + int senseInterval = sensor.scanRate; + + if (lastSenseTime > senseInterval) { + lastSenseTime = lastSenseTime % senseInterval; + if (extraTick) { + (sensor).timeToTick = (0L); + sensor.tick((ServerLevel) brainedEntity.level(), brainedEntity); + } + } + sensor.timeToTick = (lastSenseTime); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java new file mode 100644 index 0000000..4271658 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java @@ -0,0 +1,1054 @@ +package org.bxteam.divinemc.util.map; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +import org.bxteam.divinemc.util.Assert; +import org.bxteam.divinemc.util.ObjectUtil; + +/** + * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or + * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}. + * + *

This class can be used as an alternative to + * {@code Collections.synchronizedMap(new WeakHashMap>())} in order to + * support better performance when accessed concurrently. This implementation follows the + * same design constraints as {@link ConcurrentHashMap} with the exception that + * {@code null} values and {@code null} keys are supported. + * + *

NOTE: The use of references means that there is no guarantee that items + * placed into the map will be subsequently available. The garbage collector may discard + * references at any time, so it may appear that an unknown thread is silently removing + * entries. + * + *

If not explicitly specified, this implementation will use + * {@linkplain SoftReference soft entry references}. + * + * @param the key type + * @param the value type + * @author Phillip Webb + * @author Juergen Hoeller + * @author Brian Clozel + */ +public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; + private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; + private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; + + /** + * Array of segments indexed using the high order bits from the hash. + */ + private final Segment[] segments; + + /** + * When the average number of references per table exceeds this value resize will be attempted. + */ + private final float loadFactor; + + /** + * The reference type: SOFT or WEAK. + */ + private final ReferenceType referenceType; + + /** + * The shift value used to calculate the size of the segments array and an index from the hash. + */ + private final int shift; + + /** + * Late binding entry set. + */ + private volatile @Nullable Set> entrySet; + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + */ + public ConcurrentReferenceHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + */ + public ConcurrentReferenceHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per table + * exceeds this value resize will be attempted + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param referenceType the reference type used for entries (soft or weak) + */ + public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { + this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + * @param referenceType the reference type used for entries (soft or weak) + */ + @SuppressWarnings("unchecked") + public ConcurrentReferenceHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { + + Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); + Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); + Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); + Assert.notNull(referenceType, "Reference type must not be null"); + this.loadFactor = loadFactor; + this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); + int size = 1 << this.shift; + this.referenceType = referenceType; + int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); + int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE); + Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size); + int resizeThreshold = (int) (initialSize * getLoadFactor()); + for (int i = 0; i < segments.length; i++) { + segments[i] = new Segment(initialSize, resizeThreshold); + } + this.segments = segments; + } + + /** + * Calculate a shift value that can be used to create a power-of-two value between + * the specified maximum and minimum values. + * + * @param minimumValue the minimum value + * @param maximumValue the maximum value + * @return the calculated shift (use {@code 1 << shift} to obtain a value) + */ + protected static int calculateShift(int minimumValue, int maximumValue) { + int shift = 0; + int value = 1; + while (value < minimumValue && value < maximumValue) { + value <<= 1; + shift++; + } + return shift; + } + + protected final float getLoadFactor() { + return this.loadFactor; + } + + protected final int getSegmentsSize() { + return this.segments.length; + } + + protected final Segment getSegment(int index) { + return this.segments[index]; + } + + /** + * Factory method that returns the {@link ReferenceManager}. + * This method will be called once for each {@link Segment}. + * + * @return a new reference manager + */ + protected ReferenceManager createReferenceManager() { + return new ReferenceManager(); + } + + /** + * Get the hash for a given object, apply an additional hash function to reduce + * collisions. This implementation uses the same Wang/Jenkins algorithm as + * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. + * + * @param o the object to hash (may be null) + * @return the resulting hash code + */ + protected int getHash(@Nullable Object o) { + int hash = (o != null ? o.hashCode() : 0); + hash += (hash << 15) ^ 0xffffcd7d; + hash ^= (hash >>> 10); + hash += (hash << 3); + hash ^= (hash >>> 6); + hash += (hash << 2) + (hash << 14); + hash ^= (hash >>> 16); + return hash; + } + + @Override + public @Nullable V get(@Nullable Object key) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null ? entry.getValue() : null); + } + + @Override + public @Nullable V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null ? entry.getValue() : defaultValue); + } + + @Override + public boolean containsKey(@Nullable Object key) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key)); + } + + /** + * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, + * or {@code null} if not found. + * + * @param key the key (can be {@code null}) + * @param restructure types of restructure allowed during this call + * @return the reference, or {@code null} if not found + */ + protected final @Nullable Reference getReference(@Nullable Object key, Restructure restructure) { + int hash = getHash(key); + return getSegmentForHash(hash).getReference(key, hash, restructure); + } + + @Override + public @Nullable V put(@Nullable K key, @Nullable V value) { + return put(key, value, true); + } + + @Override + public @Nullable V putIfAbsent(@Nullable K key, @Nullable V value) { + return put(key, value, false); + } + + private @Nullable V put(final @Nullable K key, final @Nullable V value, final boolean overwriteExisting) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { + if (entry != null) { + V oldValue = entry.getValue(); + if (overwriteExisting) { + entry.setValue(value); + } + return oldValue; + } + Assert.state(entries != null, "No entries segment"); + entries.add(value); + return null; + } + }); + } + + @Override + public @Nullable V remove(@Nullable Object key) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null) { + if (ref != null) { + ref.release(); + } + return entry.value; + } + return null; + } + }); + } + + @Override + public boolean remove(@Nullable Object key, final @Nullable Object value) { + Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), value)) { + if (ref != null) { + ref.release(); + } + return true; + } + return false; + } + }); + return (Boolean.TRUE.equals(result)); + } + + @Override + public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) { + Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), oldValue)) { + entry.setValue(newValue); + return true; + } + return false; + } + }); + return (Boolean.TRUE.equals(result)); + } + + @Override + public @Nullable V replace(@Nullable K key, final @Nullable V value) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null) { + V oldValue = entry.getValue(); + entry.setValue(value); + return oldValue; + } + return null; + } + }); + } + + @Override + public void clear() { + for (Segment segment : this.segments) { + segment.clear(); + } + } + + /** + * Remove any entries that have been garbage collected and are no longer referenced. + * Under normal circumstances garbage collected entries are automatically purged as + * items are added or removed from the Map. This method can be used to force a purge, + * and is useful when the Map is read frequently but updated less often. + */ + public void purgeUnreferencedEntries() { + for (Segment segment : this.segments) { + segment.restructureIfNecessary(false); + } + } + + @Override + public int size() { + int size = 0; + for (Segment segment : this.segments) { + size += segment.getCount(); + } + return size; + } + + @Override + public boolean isEmpty() { + for (Segment segment : this.segments) { + if (segment.getCount() > 0) { + return false; + } + } + return true; + } + + @Override + public Set> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new EntrySet(); + this.entrySet = entrySet; + } + return entrySet; + } + + private @Nullable T doTask(@Nullable Object key, Task task) { + int hash = getHash(key); + return getSegmentForHash(hash).doTask(hash, key, task); + } + + private Segment getSegmentForHash(int hash) { + return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; + } + + + /** + * Various reference types supported by this map. + */ + public enum ReferenceType { + + /** + * Use {@link SoftReference SoftReferences}. + */ + SOFT, + + /** + * Use {@link WeakReference WeakReferences}. + */ + WEAK + } + + + /** + * Various options supported by a {@code Task}. + */ + private enum TaskOption { + + RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE + } + + + /** + * The types of restructuring that can be performed. + */ + protected enum Restructure { + + WHEN_NECESSARY, NEVER + } + + + /** + * A reference to an {@link Entry} contained in the map. Implementations are usually + * wrappers around specific Java reference implementations (for example, {@link SoftReference}). + * + * @param the key type + * @param the value type + */ + protected interface Reference { + + /** + * Return the referenced entry, or {@code null} if the entry is no longer available. + */ + @Nullable + Entry get(); + + /** + * Return the hash for the reference. + */ + int getHash(); + + /** + * Return the next reference in the chain, or {@code null} if none. + */ + @Nullable + Reference getNext(); + + /** + * Release this entry and ensure that it will be returned from + * {@code ReferenceManager#pollForPurge()}. + */ + void release(); + } + + + /** + * Allows a task access to {@link ConcurrentReferenceHashMap.Segment} entries. + */ + private interface Entries { + + /** + * Add a new entry with the specified value. + * + * @param value the value to add + */ + void add(@Nullable V value); + } + + /** + * A single map entry. + * + * @param the key type + * @param the value type + */ + protected static final class Entry implements Map.Entry { + + private final @Nullable K key; + + private volatile @Nullable V value; + + public Entry(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Override + public @Nullable K getKey() { + return this.key; + } + + @Override + public @Nullable V getValue() { + return this.value; + } + + @Override + public @Nullable V setValue(@Nullable V value) { + V previous = this.value; + this.value = value; + return previous; + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof Map.Entry that && + ObjectUtil.nullSafeEquals(getKey(), that.getKey()) && + ObjectUtil.nullSafeEquals(getValue(), that.getValue()))); + } + + @Override + public int hashCode() { + return (ObjectUtil.nullSafeHashCode(this.key) ^ ObjectUtil.nullSafeHashCode(this.value)); + } + + @Contract(pure = true) + @Override + public @NotNull String toString() { + return (this.key + "=" + this.value); + } + } + + /** + * Internal {@link Reference} implementation for {@link SoftReference SoftReferences}. + */ + private static final class SoftEntryReference extends SoftReference> implements Reference { + + private final int hash; + + private final @Nullable Reference nextReference; + + public SoftEntryReference(Entry entry, int hash, @Nullable Reference next, + ReferenceQueue> queue) { + + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public @Nullable Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + } + } + + /** + * Internal {@link Reference} implementation for {@link WeakReference WeakReferences}. + */ + private static final class WeakEntryReference extends WeakReference> implements Reference { + + private final int hash; + + private final @Nullable Reference nextReference; + + public WeakEntryReference(Entry entry, int hash, @Nullable Reference next, + ReferenceQueue> queue) { + + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public @Nullable Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + } + } + + /** + * A single segment used to divide the map to allow better concurrent performance. + */ + @SuppressWarnings("serial") + protected final class Segment extends ReentrantLock { + + private final ReferenceManager referenceManager; + + private final int initialSize; + /** + * The total number of references contained in this segment. This includes chained + * references and references that have been garbage collected but not purged. + */ + private final AtomicInteger count = new AtomicInteger(); + /** + * Array of references indexed using the low order bits from the hash. + * This property should only be set along with {@code resizeThreshold}. + */ + private volatile @Nullable Reference[] references; + /** + * The threshold when resizing of the references should occur. When {@code count} + * exceeds this value references will be resized. + */ + private int resizeThreshold; + + public Segment(int initialSize, int resizeThreshold) { + this.referenceManager = createReferenceManager(); + this.initialSize = initialSize; + this.references = createReferenceArray(initialSize); + this.resizeThreshold = resizeThreshold; + } + + public @Nullable Reference getReference(@Nullable Object key, int hash, Restructure restructure) { + if (restructure == Restructure.WHEN_NECESSARY) { + restructureIfNecessary(false); + } + if (this.count.get() == 0) { + return null; + } + // Use a local copy to protect against other threads writing + @Nullable Reference[] references = this.references; + int index = getIndex(hash, references); + Reference head = references[index]; + return findInChain(head, key, hash); + } + + /** + * Apply an update operation to this segment. + * The segment will be locked during the update. + * + * @param hash the hash of the key + * @param key the key + * @param task the update operation + * @return the result of the operation + */ + public @Nullable T doTask(final int hash, final @Nullable Object key, final @NotNull Task task) { + boolean resize = task.hasOption(TaskOption.RESIZE); + if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { + restructureIfNecessary(resize); + } + if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) { + return task.execute(null, null, null); + } + lock(); + try { + final int index = getIndex(hash, this.references); + final Reference head = this.references[index]; + Reference ref = findInChain(head, key, hash); + Entry entry = (ref != null ? ref.get() : null); + Entries entries = value -> { + @SuppressWarnings("unchecked") + Entry newEntry = new Entry<>((K) key, value); + Reference newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); + Segment.this.references[index] = newReference; + Segment.this.count.incrementAndGet(); + }; + return task.execute(ref, entry, entries); + } finally { + unlock(); + if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { + restructureIfNecessary(resize); + } + } + } + + /** + * Clear all items from this segment. + */ + public void clear() { + if (this.count.get() == 0) { + return; + } + lock(); + try { + this.references = createReferenceArray(this.initialSize); + this.resizeThreshold = (int) (this.references.length * getLoadFactor()); + this.count.set(0); + } finally { + unlock(); + } + } + + /** + * Restructure the underlying data structure when it becomes necessary. This + * method can increase the size of the references table as well as purge any + * references that have been garbage collected. + * + * @param allowResize if resizing is permitted + */ + void restructureIfNecessary(boolean allowResize) { + int currCount = this.count.get(); + boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold); + Reference ref = this.referenceManager.pollForPurge(); + if (ref != null || (needsResize)) { + restructure(allowResize, ref); + } + } + + private void restructure(boolean allowResize, @Nullable Reference ref) { + boolean needsResize; + lock(); + try { + int expectedCount = this.count.get(); + Set> toPurge = Collections.emptySet(); + if (ref != null) { + toPurge = new HashSet<>(); + while (ref != null) { + toPurge.add(ref); + ref = this.referenceManager.pollForPurge(); + } + } + expectedCount -= toPurge.size(); + + // Estimate new count, taking into account count inside lock and items that + // will be purged. + needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold); + boolean resizing = false; + int restructureSize = this.references.length; + if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { + restructureSize <<= 1; + resizing = true; + } + + int newCount = 0; + // Restructure the resized reference array + if (resizing) { + Reference[] restructured = createReferenceArray(restructureSize); + for (Reference reference : this.references) { + ref = reference; + while (ref != null) { + if (!toPurge.contains(ref)) { + Entry entry = ref.get(); + // Also filter out null references that are now null + // they should be polled from the queue in a later restructure call. + if (entry != null) { + int index = getIndex(ref.getHash(), restructured); + restructured[index] = this.referenceManager.createReference( + entry, ref.getHash(), restructured[index]); + newCount++; + } + } + ref = ref.getNext(); + } + } + // Replace volatile members + this.references = restructured; + this.resizeThreshold = (int) (this.references.length * getLoadFactor()); + } + // Restructure the existing reference array "in place" + else { + for (int i = 0; i < this.references.length; i++) { + Reference purgedRef = null; + ref = this.references[i]; + while (ref != null) { + if (!toPurge.contains(ref)) { + Entry entry = ref.get(); + // Also filter out null references that are now null + // they should be polled from the queue in a later restructure call. + if (entry != null) { + purgedRef = this.referenceManager.createReference( + entry, ref.getHash(), purgedRef); + } + newCount++; + } + ref = ref.getNext(); + } + this.references[i] = purgedRef; + } + } + this.count.set(Math.max(newCount, 0)); + } finally { + unlock(); + } + } + + private @Nullable Reference findInChain(@Nullable Reference ref, @Nullable Object key, int hash) { + Reference currRef = ref; + while (currRef != null) { + if (currRef.getHash() == hash) { + Entry entry = currRef.get(); + if (entry != null) { + K entryKey = entry.getKey(); + if (ObjectUtil.nullSafeEquals(entryKey, key)) { + return currRef; + } + } + } + currRef = currRef.getNext(); + } + return null; + } + + @Contract(value = "_ -> new", pure = true) + @SuppressWarnings({"unchecked"}) + private Reference @NotNull [] createReferenceArray(int size) { + return new Reference[size]; + } + + private int getIndex(int hash, @Nullable Reference[] references) { + return (hash & (references.length - 1)); + } + + /** + * Return the size of the current references array. + */ + public int getSize() { + return this.references.length; + } + + /** + * Return the total number of references in this segment. + */ + public int getCount() { + return this.count.get(); + } + } + + /** + * A task that can be {@link Segment#doTask run} against a {@link Segment}. + */ + private abstract class Task { + + private final EnumSet options; + + public Task(TaskOption... options) { + this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); + } + + public boolean hasOption(TaskOption option) { + return this.options.contains(option); + } + + /** + * Execute the task. + * + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @param entries access to the underlying entries + * @return the result of the task + * @see #execute(Reference, Entry) + */ + protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { + return execute(ref, entry); + } + + /** + * Convenience method that can be used for tasks that do not need access to {@link Entries}. + * + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @return the result of the task + * @see #execute(Reference, Entry, Entries) + */ + protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry) { + return null; + } + } + + /** + * Internal entry-set implementation. + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(@Nullable Object o) { + if (o instanceof Map.Entry entry) { + Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); + Entry otherEntry = (ref != null ? ref.get() : null); + if (otherEntry != null) { + return ObjectUtil.nullSafeEquals(entry.getValue(), otherEntry.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry entry) { + return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public int size() { + return ConcurrentReferenceHashMap.this.size(); + } + + @Override + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + } + + /** + * Internal entry iterator implementation. + */ + private class EntryIterator implements Iterator> { + + private int segmentIndex; + + private int referenceIndex; + + private @Nullable Reference @Nullable [] references; + + private @Nullable Reference reference; + + private @Nullable Entry next; + + private @Nullable Entry last; + + public EntryIterator() { + moveToNextSegment(); + } + + @Override + public boolean hasNext() { + getNextIfNecessary(); + return (this.next != null); + } + + @Override + public Entry next() { + getNextIfNecessary(); + if (this.next == null) { + throw new NoSuchElementException(); + } + this.last = this.next; + this.next = null; + return this.last; + } + + private void getNextIfNecessary() { + while (this.next == null) { + moveToNextReference(); + if (this.reference == null) { + return; + } + this.next = this.reference.get(); + } + } + + private void moveToNextReference() { + if (this.reference != null) { + this.reference = this.reference.getNext(); + } + while (this.reference == null && this.references != null) { + if (this.referenceIndex >= this.references.length) { + moveToNextSegment(); + this.referenceIndex = 0; + } else { + this.reference = this.references[this.referenceIndex]; + this.referenceIndex++; + } + } + } + + private void moveToNextSegment() { + this.reference = null; + this.references = null; + if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { + this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; + this.segmentIndex++; + } + } + + @Override + public void remove() { + Assert.state(this.last != null, "No element to remove"); + ConcurrentReferenceHashMap.this.remove(this.last.getKey()); + this.last = null; + } + } + + /** + * Strategy class used to manage {@link Reference References}. + * This class can be overridden if alternative reference types need to be supported. + */ + protected class ReferenceManager { + + private final ReferenceQueue> queue = new ReferenceQueue<>(); + + /** + * Factory method used to create a new {@link Reference}. + * + * @param entry the entry contained in the reference + * @param hash the hash + * @param next the next reference in the chain, or {@code null} if none + * @return a new {@link Reference} + */ + public Reference createReference(Entry entry, int hash, @Nullable Reference next) { + if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { + return new WeakEntryReference<>(entry, hash, next, this.queue); + } + return new SoftEntryReference<>(entry, hash, next, this.queue); + } + + /** + * Return any reference that has been garbage collected and can be purged from the + * underlying structure or {@code null} if no references need purging. This + * method must be thread safe and ideally should not block when returning + * {@code null}. References should be returned once and only once. + * + * @return a reference to purge or {@code null} + */ + @SuppressWarnings("unchecked") + public @Nullable Reference pollForPurge() { + return (Reference) this.queue.poll(); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java new file mode 100644 index 0000000..183ed6e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java @@ -0,0 +1,165 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +public class BoxOctree { + private static final int subdivideThreshold = 10; + private static final int maximumDepth = 3; + private final AABB boundary; + private final Vec3i size; + private final int depth; + private final List innerBoxes = new ArrayList<>(); + private final List childrenOctants = new ArrayList<>(); + + public BoxOctree(AABB axisAlignedBB) { + this(axisAlignedBB, 0); + } + + private BoxOctree(@NotNull AABB axisAlignedBB, int parentDepth) { + boundary = axisAlignedBB.move(0, 0, 0); // deep copy + size = new Vec3i(roundAwayFromZero(boundary.getXsize()), roundAwayFromZero(boundary.getYsize()), roundAwayFromZero(boundary.getZsize())); + depth = parentDepth + 1; + } + + private int roundAwayFromZero(double value) { + return (value >= 0) ? (int)Math.ceil(value) : (int)Math.floor(value); + } + + private void subdivide() { + if (!childrenOctants.isEmpty()) { + throw new UnsupportedOperationException("Tried to subdivide when there are already children octants."); + } + + int halfXSize = size.getX()/2; + int halfYSize = size.getY()/2; + int halfZSize = size.getZ()/2; + // Lower Left Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY, boundary.minZ, + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ + halfZSize), + depth)); + // Lower Left Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY, boundary.minZ + halfZSize, + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.maxZ), + depth)); + // Lower Right Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY, boundary.minZ, + boundary.maxX, boundary.minY + halfYSize, boundary.minZ + halfZSize), + depth)); + // Lower Right Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY, boundary.minZ + halfZSize, + boundary.maxX, boundary.minY + halfYSize, boundary.maxZ), + depth)); + // Upper Left Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY + halfYSize, boundary.minZ, + boundary.minX + halfXSize, boundary.maxY, boundary.minZ + halfZSize), + depth)); + // Upper Left Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY + halfYSize, boundary.minZ + halfZSize, + boundary.minX + halfXSize, boundary.maxY, boundary.maxZ), + depth)); + // Upper Right Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ, + boundary.maxX, boundary.maxY, boundary.minZ + halfZSize), + depth)); + // Upper Right Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ + halfZSize, + boundary.maxX, boundary.maxY, boundary.maxZ), + depth)); + + for (AABB parentInnerBox : innerBoxes) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(parentInnerBox)) { + octree.addBox(parentInnerBox); + } + } + } + + innerBoxes.clear(); + } + + public void addBox(AABB axisAlignedBB) { + if (depth < maximumDepth && innerBoxes.size() > subdivideThreshold) { + subdivide(); + } if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(axisAlignedBB)) { + octree.addBox(axisAlignedBB); + } + } + } else { + // Prevent re-adding the same box if it already exists + for (AABB parentInnerBox : innerBoxes) { + if (parentInnerBox.equals(axisAlignedBB)) { + return; + } + } + innerBoxes.add(axisAlignedBB); + } + } + + public boolean boundaryEntirelyContains(@NotNull AABB axisAlignedBB) { + return boundary.contains(axisAlignedBB.minX, axisAlignedBB.minY, axisAlignedBB.minZ) && + boundary.contains(axisAlignedBB.maxX, axisAlignedBB.maxY, axisAlignedBB.maxZ); + } + + public boolean boundaryIntersects(AABB axisAlignedBB) { + return boundary.intersects(axisAlignedBB); + } + + public boolean withinBoundsButNotIntersectingChildren(AABB axisAlignedBB) { + return this.boundaryEntirelyContains(axisAlignedBB) && !this.intersectsAnyBox(axisAlignedBB); + } + + public boolean intersectsAnyBox(AABB axisAlignedBB) { + if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(axisAlignedBB) && octree.intersectsAnyBox(axisAlignedBB)) { + return true; + } + } + } + else { + for (AABB innerBox : innerBoxes) { + if (innerBox.intersects(axisAlignedBB)) { + return true; + } + } + } + return false; + } + + public boolean boundaryContains(@NotNull BlockPos position) { + return boundary.contains(position.getX(), position.getY(), position.getZ()); + } + + public boolean withinAnyBox(BlockPos position) { + if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryContains(position) && octree.withinAnyBox(position)) { + return true; + } + } + } + else { + for (AABB innerBox : innerBoxes) { + if (innerBox.contains(position.getX(), position.getY(), position.getZ())) { + return true; + } + } + } + return false; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java new file mode 100644 index 0000000..3298792 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java @@ -0,0 +1,90 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntComparators; +import net.minecraft.Util; +import net.minecraft.core.FrontAndTop; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NumericTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.JigsawBlock; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +public final class GeneralUtils { + private GeneralUtils() {} + + // More optimized with checking if the jigsaw blocks can connect + public static boolean canJigsawsAttach(StructureTemplate.@NotNull JigsawBlockInfo jigsaw1, StructureTemplate.@NotNull JigsawBlockInfo jigsaw2) { + FrontAndTop prop1 = jigsaw1.info().state().getValue(JigsawBlock.ORIENTATION); + FrontAndTop prop2 = jigsaw2.info().state().getValue(JigsawBlock.ORIENTATION); + + return prop1.front() == prop2.front().getOpposite() && + (prop1.top() == prop2.top() || isRollableJoint(jigsaw1, prop1)) && + getStringMicroOptimised(jigsaw1.info().nbt(), "target").equals(getStringMicroOptimised(jigsaw2.info().nbt(), "name")); + } + + private static boolean isRollableJoint(StructureTemplate.@NotNull JigsawBlockInfo jigsaw1, FrontAndTop prop1) { + String joint = getStringMicroOptimised(jigsaw1.info().nbt(), "joint"); + if(!joint.equals("rollable") && !joint.equals("aligned")) { + return !prop1.front().getAxis().isHorizontal(); + } + else { + return joint.equals("rollable"); + } + } + + public static void shuffleAndPrioritize(@NotNull List list, RandomSource random) { + Int2ObjectArrayMap> buckets = new Int2ObjectArrayMap<>(); + + // Add entries to the bucket + for (StructureTemplate.JigsawBlockInfo structureBlockInfo : list) { + int key = 0; + if (structureBlockInfo.info().nbt() != null) { + key = getIntMicroOptimised(structureBlockInfo.info().nbt(), "selection_priority"); + } + + buckets.computeIfAbsent(key, k -> new ArrayList<>()).add(structureBlockInfo); + } + + // Shuffle the entries in the bucket + for (List bucketList : buckets.values()) { + Util.shuffle(bucketList, random); + } + + if (buckets.size() == 1) { + list.clear(); + copyAll(buckets.int2ObjectEntrySet().fastIterator().next().getValue(), list); + } + else if (buckets.size() > 1) { + // Priorities found. Concat them into a single new master list in reverse order to match vanilla behavior + list.clear(); + + IntArrayList keys = new IntArrayList(buckets.keySet()); + keys.sort(IntComparators.OPPOSITE_COMPARATOR); + + for (int i = 0; i < keys.size(); i++) { + copyAll(buckets.get(keys.getInt(i)), list); + } + } + } + + public static int getIntMicroOptimised(@NotNull CompoundTag tag, String key) { + return tag.get(key) instanceof NumericTag numericTag ? numericTag.getAsInt() : 0; + } + + public static @NotNull String getStringMicroOptimised(@NotNull CompoundTag tag, String key) { + return tag.get(key) instanceof StringTag stringTag ? stringTag.getAsString() : ""; + } + + public static void copyAll(@NotNull List src, List dest) { + // Do not listen to IDE. This is faster than addAll + for (int i = 0; i < src.size(); i++) { + dest.add(src.get(i)); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java new file mode 100644 index 0000000..4f6021c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java @@ -0,0 +1,274 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.function.Predicate; + +public class PalettedStructureBlockInfoList implements List { + private static final long[] EMPTY_DATA = new long[0]; + private static final CompoundTag[] NULL_TAGS = new CompoundTag[]{null}; + private static final String UNSUPPORTED_OPERATION_ERROR_MESSAGE = "Structure Layout Optimizer: No mod should be modifying a StructureTemplate's Palette itself. Please reach out to Structure Layout Optimizer dev for this crash to investigate this mod compat issue."; + + protected final long[] data; + protected final BlockState[] states; + protected final CompoundTag[] nbts; + protected final int xBits, yBits, zBits; + protected final int stateBits, nbtBits; + protected final int bitsPerEntry; + protected final int size; + private WeakReference> cachedStructureBlockInfoList = new WeakReference<>(null); + + public PalettedStructureBlockInfoList(List infos) { + this(infos, null); + } + + public PalettedStructureBlockInfoList(List infos, Predicate predicate) { + List entries = new ArrayList<>(); + List states = new ArrayList<>(); + List tags = new ArrayList<>(); + + int maxX = 0; + int maxY = 0; + int maxZ = 0; + + for (StructureTemplate.StructureBlockInfo info : infos) { + if (predicate != null && !predicate.test(info)) { + continue; + } + + int state = states.indexOf(info.state()); + if (state == -1) { + state = states.size(); + states.add(info.state()); + } + + int tag = indexOf(tags, info.nbt()); + if (tag == -1) { + tag = tags.size(); + tags.add(info.nbt()); + } + + int x = info.pos().getX(); + int y = info.pos().getY(); + int z = info.pos().getZ(); + + if (x < 0 || y < 0 || z < 0) { + throw new RuntimeException("StructureLayoutOptimizer: Invalid StructureBlockInfo position: " + info.pos()); + } + if (x > maxX) { + maxX = x; + } + if (y > maxY) { + maxY = y; + } + if (z > maxZ) { + maxZ = z; + } + + entries.add(new Entry(x, y, z, state, tag)); + } + + this.xBits = bits(maxX); + this.yBits = bits(maxY); + this.zBits = bits(maxZ); + this.stateBits = bits(states.size() - 1); + this.nbtBits = bits(tags.size() - 1); + this.bitsPerEntry = this.xBits + this.yBits + this.zBits + this.stateBits + this.nbtBits; + + if (this.bitsPerEntry > 64) { + throw new RuntimeException("StructureLayoutOptimizer: Too many bits per entry: " + this.bitsPerEntry); + } + + this.size = entries.size(); + if (this.bitsPerEntry != 0) { + int entriesPerLong = 64 / this.bitsPerEntry; + this.data = new long[(this.size + entriesPerLong - 1) / entriesPerLong]; + for (int i = 0; i < this.size; i++) { + this.data[i / entriesPerLong] |= entries.get(i).compress(this.xBits, this.yBits, this.zBits, this.stateBits) << ((i % entriesPerLong) * this.bitsPerEntry); + } + } else { + this.data = EMPTY_DATA; + } + this.states = states.toArray(new BlockState[0]); + this.nbts = tags.size() == 1 && tags.get(0) == null ? NULL_TAGS : tags.toArray(new CompoundTag[0]); + } + + private static int bits(int i) { + int bits = 0; + while (i >= 1 << bits) { + bits++; + } + return bits; + } + + private static int indexOf(@NotNull List list, T o) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) == o) { + return i; + } + } + return -1; + } + + private @NotNull List convertBackToStructureBlockInfoListAndCache() { + synchronized(data) { + List structureBlockInfos = cachedStructureBlockInfoList.get(); + if (structureBlockInfos != null) { + return structureBlockInfos; + } + + structureBlockInfos = new ObjectArrayList<>(new PalettedStructureBlockInfoListIterator(this)); + cachedStructureBlockInfoList = new WeakReference<>(structureBlockInfos); + return structureBlockInfos; + } + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return this.size == 0; + } + + @NotNull + @Override + public Iterator iterator() { + return convertBackToStructureBlockInfoListAndCache().iterator(); + } + + @NotNull + @Override + public ListIterator listIterator() { + return convertBackToStructureBlockInfoListAndCache().listIterator(); + } + + @NotNull + @Override + public ListIterator listIterator(int index) { + return convertBackToStructureBlockInfoListAndCache().listIterator(index); + } + + + @Override + public boolean contains(Object o) { + return convertBackToStructureBlockInfoListAndCache().contains(o); + } + + @Override + public boolean containsAll(@NotNull Collection c) { + return new HashSet<>(convertBackToStructureBlockInfoListAndCache()).containsAll(c); + } + + @NotNull + @Override + public Object @NotNull [] toArray() { + return convertBackToStructureBlockInfoListAndCache().toArray(); + } + + @NotNull + @Override + public T @NotNull [] toArray(@NotNull T @NotNull [] a) { + return convertBackToStructureBlockInfoListAndCache().toArray(a); + } + + @Override + public StructureTemplate.StructureBlockInfo get(int index) { + return convertBackToStructureBlockInfoListAndCache().get(index); + } + + @Override + public int indexOf(Object o) { + return convertBackToStructureBlockInfoListAndCache().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return convertBackToStructureBlockInfoListAndCache().lastIndexOf(o); + } + + @NotNull + @Override + public List subList(int fromIndex, int toIndex) { + return convertBackToStructureBlockInfoListAndCache().subList(fromIndex, toIndex); + } + + @Override + public StructureTemplate.StructureBlockInfo set(int index, StructureTemplate.StructureBlockInfo element) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public void add(int index, StructureTemplate.StructureBlockInfo element) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean add(StructureTemplate.StructureBlockInfo info) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public StructureTemplate.StructureBlockInfo remove(int index) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean addAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean addAll(int index, @NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + private static class Entry { + private final int x, y, z; + private final int state, nbt; + + private Entry(int x, int y, int z, int state, int nbt) { + this.x = x; + this.y = y; + this.z = z; + this.state = state; + this.nbt = nbt; + } + + private long compress(int xBits, int yBits, int zBits, int stateBits) { + return this.x + ((this.y + ((this.z + ((this.state + ((long) this.nbt << stateBits)) << zBits)) << yBits)) << xBits); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java new file mode 100644 index 0000000..00db7b9 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java @@ -0,0 +1,65 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.Iterator; + +public class PalettedStructureBlockInfoListIterator implements Iterator { + private final PalettedStructureBlockInfoList infos; + + private final int xOffset, yOffset, zOffset, stateOffset; + private final int xMask, yMask, zMask, stateMask, tagMask; + + private int index = 0; + + public PalettedStructureBlockInfoListIterator(@NotNull PalettedStructureBlockInfoList infos) { + this.infos = infos; + this.xOffset = infos.xBits; + this.yOffset = this.xOffset + infos.yBits; + this.zOffset = this.yOffset + infos.zBits; + this.stateOffset = this.zOffset + infos.stateBits; + this.xMask = (1 << infos.xBits) - 1; + this.yMask = (1 << infos.yBits) - 1; + this.zMask = (1 << infos.zBits) - 1; + this.stateMask = (1 << infos.stateBits) - 1; + this.tagMask = (1 << infos.nbtBits) - 1; + } + + @Override + public boolean hasNext() { + return this.index < this.size(); + } + + @Override + public StructureTemplate.StructureBlockInfo next() { + int index = this.toIndex(this.index++); + if (index >= this.infos.size) { + throw new IndexOutOfBoundsException(); + } + + int bitsPerEntry = this.infos.bitsPerEntry; + if (bitsPerEntry == 0) { + return new StructureTemplate.StructureBlockInfo(new BlockPos(0, 0, 0), this.infos.states[0], this.infos.nbts[0]); + } + + int entriesPerLong = 64 / bitsPerEntry; + long entry = this.infos.data[index / entriesPerLong] >>> ((index % entriesPerLong) * bitsPerEntry); + + int x = (int) (entry & this.xMask); + int y = (int) (entry >> this.xOffset & this.yMask); + int z = (int) (entry >> this.yOffset & this.zMask); + int state = (int) (entry >> this.zOffset & this.stateMask); + int tag = (int) (entry >> this.stateOffset & this.tagMask); + + return new StructureTemplate.StructureBlockInfo(new BlockPos(x, y, z), this.infos.states[state], this.infos.nbts[tag]); + } + + protected int toIndex(int index) { + return index; + } + + protected int size() { + return this.infos.size; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java new file mode 100644 index 0000000..f1aa0da --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java @@ -0,0 +1,105 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.objects.Object2BooleanMaps; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class StructureTemplateOptimizer { + private static final Map FINALIZE_PROCESSING_PROCESSORS = Object2BooleanMaps.synchronize(new Object2BooleanOpenHashMap<>()); + + public static @NotNull List getStructureBlockInfosInBounds(StructureTemplate.@NotNull Palette palette, BlockPos offset, @NotNull StructurePlaceSettings structurePlaceSettings) { + BoundingBox boundingBox = structurePlaceSettings.getBoundingBox(); + List originalPositions = palette.blocks(); + if (boundingBox == null) { + return originalPositions; + } + + // Capped processor needs full nbt block lists + for (StructureProcessor processor : structurePlaceSettings.getProcessors()) { + if (FINALIZE_PROCESSING_PROCESSORS.computeIfAbsent(processor, StructureTemplateOptimizer::isFinalizeProcessor)) { + return palette.blocks(); + } + } + + Mirror mirror = structurePlaceSettings.getMirror(); + Rotation rotation = structurePlaceSettings.getRotation(); + BlockPos pivot = structurePlaceSettings.getRotationPivot(); + + List listOfInBoundsRelativePositions = new ArrayList<>(); + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + + for (StructureTemplate.StructureBlockInfo blockInfo : originalPositions) { + mutableBlockPos.set(blockInfo.pos()); + transform(mutableBlockPos, mirror, rotation, pivot); + mutableBlockPos.move(offset); + + if (boundingBox.isInside(mutableBlockPos)) { + listOfInBoundsRelativePositions.add(blockInfo); + } + } + + // DO NOT REMOVE. This is required because the Template will return false for an entirely empty list and then remove the structure piece + // out of the structure start, preventing it from placing blocks into any other side chunks that the piece was supposed to place blocks in. + if (listOfInBoundsRelativePositions.isEmpty() && !originalPositions.isEmpty()) { + listOfInBoundsRelativePositions.add(originalPositions.get(0)); + } + + return listOfInBoundsRelativePositions; + } + + private static @NotNull Boolean isFinalizeProcessor(@NotNull StructureProcessor structureProcessor) { + try { + var method = structureProcessor.getClass().getMethod( + "finalizeProcessing", ServerLevelAccessor.class, BlockPos.class, BlockPos.class, List.class, List.class, StructurePlaceSettings.class); + + return method.getDeclaringClass() != StructureProcessor.class; + } + catch (NoSuchMethodException e) { + throw new RuntimeException("StructureProcessor does not have finalizeProcessing method", e); + } + } + + private static void transform(BlockPos.@NotNull MutableBlockPos mutableBlockPos, @NotNull Mirror mirror, Rotation rotation, BlockPos pivot) { + int i = mutableBlockPos.getX(); + int j = mutableBlockPos.getY(); + int k = mutableBlockPos.getZ(); + boolean flag = true; + switch (mirror) { + case LEFT_RIGHT: + k = -k; + break; + case FRONT_BACK: + i = -i; + break; + default: + flag = false; + } + + int l = pivot.getX(); + int i1 = pivot.getZ(); + switch (rotation) { + case COUNTERCLOCKWISE_90: + mutableBlockPos.set(l - i1 + k, j, l + i1 - i); + return; + case CLOCKWISE_90: + mutableBlockPos.set(l + i1 - k, j, i1 - l + i); + return; + case CLOCKWISE_180: + mutableBlockPos.set(l + l - i, j, i1 + i1 - k); + return; + default: + if (flag) mutableBlockPos.set(i, j, k); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java new file mode 100644 index 0000000..d12e100 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java @@ -0,0 +1,11 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class TrojanArrayList extends ArrayList { + public final Set elementsAlreadyParsed = new HashSet<>(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java new file mode 100644 index 0000000..b6ff5cf --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java @@ -0,0 +1,21 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.doubles.DoubleList; +import net.minecraft.core.Direction; +import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; + +public class TrojanVoxelShape extends VoxelShape { + public final BoxOctree boxOctree; + + public TrojanVoxelShape(BoxOctree boxOctree) { + super(BitSetDiscreteVoxelShape.withFilledBounds(0 ,0, 0, 0, 0, 0, 0, 0, 0)); + this.boxOctree = boxOctree; + } + + @Override + public DoubleList getCoords(Direction.@NotNull Axis axis) { + return null; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java new file mode 100644 index 0000000..0167a1e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java @@ -0,0 +1,5 @@ +/** + * This package was ported from the Structure Layout Optimizer mod by TelepathicGrunt.
+ * Original source code - https://github.com/TelepathicGrunt/StructureLayoutOptimizer (MIT License) + */ +package org.bxteam.divinemc.util.structure; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java new file mode 100644 index 0000000..5b1a4b9 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java @@ -0,0 +1,83 @@ +package org.bxteam.divinemc.util.tps; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class TPSCalculator { + public Long lastTick; + public Long currentTick; + private double allMissedTicks = 0; + private final List tpsHistory = new CopyOnWriteArrayList<>(); + private static final int historyLimit = 40; + + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + public TPSCalculator() {} + + public void doTick() { + if (currentTick != null) { + lastTick = currentTick; + } + + currentTick = System.currentTimeMillis(); + addToHistory(getTPS()); + clearMissedTicks(); + missedTick(); + } + + private void addToHistory(double tps) { + if (tpsHistory.size() >= historyLimit) { + tpsHistory.remove(0); + } + + tpsHistory.add(tps); + } + + public long getMSPT() { + return currentTick - lastTick; + } + + public double getAverageTPS() { + return tpsHistory.stream() + .mapToDouble(Double::doubleValue) + .average() + .orElse(0.1); + } + + public double getTPS() { + if (lastTick == null) return -1; + if (getMSPT() <= 0) return 0.1; + + double tps = 1000 / (double) getMSPT(); + return tps > MAX_TPS ? MAX_TPS : tps; + } + + public void missedTick() { + if (lastTick == null) return; + + long mspt = getMSPT() <= 0 ? 50 : getMSPT(); + double missedTicks = (mspt / (double) FULL_TICK) - 1; + allMissedTicks += missedTicks <= 0 ? 0 : missedTicks; + } + + public double getMostAccurateTPS() { + return getTPS() > getAverageTPS() ? getAverageTPS() : getTPS(); + } + + public double getAllMissedTicks() { + return allMissedTicks; + } + + public int applicableMissedTicks() { + return (int) Math.floor(allMissedTicks); + } + + public void clearMissedTicks() { + allMissedTicks -= applicableMissedTicks(); + } + + public void resetMissedTicks() { + allMissedTicks = 0; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java new file mode 100644 index 0000000..3380e67 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.util.tps; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +public class TPSUtil { + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + public static float tt20(float ticks, boolean limitZero, @Nullable ServerLevel level) { + float newTicks = (float) rawTT20(ticks, level); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static int tt20(int ticks, boolean limitZero, @Nullable ServerLevel level) { + int newTicks = (int) Math.ceil(rawTT20(ticks, level)); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double tt20(double ticks, boolean limitZero, @Nullable ServerLevel level) { + double newTicks = (double) rawTT20(ticks, level); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double rawTT20(double ticks, @Nullable ServerLevel level) { + return ticks == 0 ? 0 : ticks * (level == null ? MinecraftServer.getServer().tpsCalculator.getMostAccurateTPS() : level.tpsCalculator.getMostAccurateTPS()) / MAX_TPS; + } +} diff --git a/divinemc-server/src/main/java/su/plo/matter/Globals.java b/divinemc-server/src/main/java/su/plo/matter/Globals.java new file mode 100644 index 0000000..6a8134b --- /dev/null +++ b/divinemc-server/src/main/java/su/plo/matter/Globals.java @@ -0,0 +1,95 @@ +package su.plo.matter; + +import com.google.common.collect.Iterables; +import net.minecraft.server.level.ServerLevel; +import org.bxteam.divinemc.DivineConfig; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Optional; + +public class Globals { + public static final int WORLD_SEED_LONGS = 16; + public static final int WORLD_SEED_BITS = WORLD_SEED_LONGS * 64; + + public static final long[] worldSeed = new long[WORLD_SEED_LONGS]; + public static final ThreadLocal dimension = ThreadLocal.withInitial(() -> 0); + + public enum Salt { + UNDEFINED, + BASTION_FEATURE, + WOODLAND_MANSION_FEATURE, + MINESHAFT_FEATURE, + BURIED_TREASURE_FEATURE, + NETHER_FORTRESS_FEATURE, + PILLAGER_OUTPOST_FEATURE, + GEODE_FEATURE, + NETHER_FOSSIL_FEATURE, + OCEAN_MONUMENT_FEATURE, + RUINED_PORTAL_FEATURE, + POTENTIONAL_FEATURE, + GENERATE_FEATURE, + JIGSAW_PLACEMENT, + STRONGHOLDS, + POPULATION, + DECORATION, + SLIME_CHUNK + } + + public static void setupGlobals(ServerLevel world) { + if (!DivineConfig.enableSecureSeed) return; + + long[] seed = world.getServer().getWorldData().worldGenOptions().featureSeed(); + System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS); + int worldIndex = Iterables.indexOf(world.getServer().levelKeys(), it -> it == world.dimension()); + if (worldIndex == -1) + worldIndex = world.getServer().levelKeys().size(); // if we are in world construction it may not have been added to the map yet + dimension.set(worldIndex); + } + + public static long[] createRandomWorldSeed() { + long[] seed = new long[WORLD_SEED_LONGS]; + SecureRandom rand = new SecureRandom(); + for (int i = 0; i < WORLD_SEED_LONGS; i++) { + seed[i] = rand.nextLong(); + } + return seed; + } + + // 1024-bit string -> 16 * 64 long[] + public static Optional parseSeed(String seedStr) { + if (seedStr.isEmpty()) return Optional.empty(); + + if (seedStr.length() != WORLD_SEED_BITS) { + throw new IllegalArgumentException("Secure seed length must be " + WORLD_SEED_BITS + "-bit but found " + seedStr.length() + "-bit."); + } + + long[] seed = new long[WORLD_SEED_LONGS]; + + for (int i = 0; i < WORLD_SEED_LONGS; i++) { + int start = i * 64; + int end = start + 64; + String seedSection = seedStr.substring(start, end); + + BigInteger seedInDecimal = new BigInteger(seedSection, 2); + seed[i] = seedInDecimal.longValue(); + } + + return Optional.of(seed); + } + + // 16 * 64 long[] -> 1024-bit string + public static String seedToString(long[] seed) { + StringBuilder sb = new StringBuilder(); + + for (long longV : seed) { + // Convert to 64-bit binary string per long + // Use format to keep 64-bit length, and use 0 to complete space + String binaryStr = String.format("%64s", Long.toBinaryString(longV)).replace(' ', '0'); + + sb.append(binaryStr); + } + + return sb.toString(); + } +} diff --git a/divinemc-server/src/main/java/su/plo/matter/Hashing.java b/divinemc-server/src/main/java/su/plo/matter/Hashing.java new file mode 100644 index 0000000..edf696f --- /dev/null +++ b/divinemc-server/src/main/java/su/plo/matter/Hashing.java @@ -0,0 +1,73 @@ +package su.plo.matter; + +public class Hashing { + // https://en.wikipedia.org/wiki/BLAKE_(hash_function) + // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java + + private final static long[] blake2b_IV = { + 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, + 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, + 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L + }; + + private final static byte[][] blake2b_sigma = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3} + }; + + public static long[] hashWorldSeed(long[] worldSeed) { + long[] result = blake2b_IV.clone(); + result[0] ^= 0x01010040; + hash(worldSeed, result, new long[16], 0, false); + return result; + } + + public static void hash(long[] message, long[] chainValue, long[] internalState, long messageOffset, boolean isFinal) { + assert message.length == 16; + assert chainValue.length == 8; + assert internalState.length == 16; + + System.arraycopy(chainValue, 0, internalState, 0, chainValue.length); + System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4); + internalState[12] = messageOffset ^ blake2b_IV[4]; + internalState[13] = blake2b_IV[5]; + if (isFinal) internalState[14] = ~blake2b_IV[6]; + internalState[15] = blake2b_IV[7]; + + for (int round = 0; round < 12; round++) { + G(message[blake2b_sigma[round][0]], message[blake2b_sigma[round][1]], 0, 4, 8, 12, internalState); + G(message[blake2b_sigma[round][2]], message[blake2b_sigma[round][3]], 1, 5, 9, 13, internalState); + G(message[blake2b_sigma[round][4]], message[blake2b_sigma[round][5]], 2, 6, 10, 14, internalState); + G(message[blake2b_sigma[round][6]], message[blake2b_sigma[round][7]], 3, 7, 11, 15, internalState); + G(message[blake2b_sigma[round][8]], message[blake2b_sigma[round][9]], 0, 5, 10, 15, internalState); + G(message[blake2b_sigma[round][10]], message[blake2b_sigma[round][11]], 1, 6, 11, 12, internalState); + G(message[blake2b_sigma[round][12]], message[blake2b_sigma[round][13]], 2, 7, 8, 13, internalState); + G(message[blake2b_sigma[round][14]], message[blake2b_sigma[round][15]], 3, 4, 9, 14, internalState); + } + + for (int i = 0; i < 8; i++) { + chainValue[i] ^= internalState[i] ^ internalState[i + 8]; + } + } + + private static void G(long m1, long m2, int posA, int posB, int posC, int posD, long[] internalState) { + internalState[posA] = internalState[posA] + internalState[posB] + m1; + internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE + internalState[posA] = internalState[posA] + internalState[posB] + m2; + internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE + } +} diff --git a/divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java b/divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java new file mode 100644 index 0000000..fab359a --- /dev/null +++ b/divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java @@ -0,0 +1,159 @@ +package su.plo.matter; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.WorldgenRandom; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public class WorldgenCryptoRandom extends WorldgenRandom { + // hash the world seed to guard against badly chosen world seeds + private static final long[] HASHED_ZERO_SEED = Hashing.hashWorldSeed(new long[Globals.WORLD_SEED_LONGS]); + private static final ThreadLocal LAST_SEEN_WORLD_SEED = ThreadLocal.withInitial(() -> new long[Globals.WORLD_SEED_LONGS]); + private static final ThreadLocal HASHED_WORLD_SEED = ThreadLocal.withInitial(() -> HASHED_ZERO_SEED); + + private final long[] worldSeed = new long[Globals.WORLD_SEED_LONGS]; + private final long[] randomBits = new long[8]; + private int randomBitIndex; + private static final int MAX_RANDOM_BIT_INDEX = 64 * 8; + private static final int LOG2_MAX_RANDOM_BIT_INDEX = 9; + private long counter; + private final long[] message = new long[16]; + private final long[] cachedInternalState = new long[16]; + + public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) { + super(new LegacyRandomSource(0L)); + if (typeSalt != null) { + this.setSecureSeed(x, z, typeSalt, salt); + } + } + + public void setSecureSeed(int x, int z, Globals.Salt typeSalt, long salt) { + System.arraycopy(Globals.worldSeed, 0, this.worldSeed, 0, Globals.WORLD_SEED_LONGS); + message[0] = ((long) x << 32) | ((long) z & 0xffffffffL); + message[1] = ((long) Globals.dimension.get() << 32) | ((long) salt & 0xffffffffL); + message[2] = typeSalt.ordinal(); + message[3] = counter = 0; + randomBitIndex = MAX_RANDOM_BIT_INDEX; + } + + private long[] getHashedWorldSeed() { + if (!Arrays.equals(worldSeed, LAST_SEEN_WORLD_SEED.get())) { + HASHED_WORLD_SEED.set(Hashing.hashWorldSeed(worldSeed)); + System.arraycopy(worldSeed, 0, LAST_SEEN_WORLD_SEED.get(), 0, Globals.WORLD_SEED_LONGS); + } + return HASHED_WORLD_SEED.get(); + } + + private void moreRandomBits() { + message[3] = counter++; + System.arraycopy(getHashedWorldSeed(), 0, randomBits, 0, 8); + Hashing.hash(message, randomBits, cachedInternalState, 64, true); + } + + private long getBits(int count) { + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { + moreRandomBits(); + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + } + + int alignment = randomBitIndex & 63; + if ((randomBitIndex >>> 6) == ((randomBitIndex + count) >>> 6)) { + long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << count) - 1); + randomBitIndex += count; + return result; + } else { + long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << (64 - alignment)) - 1); + randomBitIndex += count; + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { + moreRandomBits(); + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + } + alignment = randomBitIndex & 63; + result <<= alignment; + result |= (randomBits[randomBitIndex >>> 6] >>> (64 - alignment)) & ((1L << alignment) - 1); + + return result; + } + } + + @Override + public @NotNull RandomSource fork() { + WorldgenCryptoRandom fork = new WorldgenCryptoRandom(0, 0, null, 0); + + System.arraycopy(Globals.worldSeed, 0, fork.worldSeed, 0, Globals.WORLD_SEED_LONGS); + fork.message[0] = this.message[0]; + fork.message[1] = this.message[1]; + fork.message[2] = this.message[2]; + fork.message[3] = this.message[3]; + fork.randomBitIndex = this.randomBitIndex; + fork.counter = this.counter; + fork.nextLong(); + + return fork; + } + + @Override + public int next(int bits) { + return (int) getBits(bits); + } + + @Override + public void consumeCount(int count) { + randomBitIndex += count; + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX * 2) { + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + counter += randomBitIndex >>> LOG2_MAX_RANDOM_BIT_INDEX; + randomBitIndex &= MAX_RANDOM_BIT_INDEX - 1; + randomBitIndex += MAX_RANDOM_BIT_INDEX; + } + } + + @Override + public int nextInt(int bound) { + int bits = Mth.ceillog2(bound); + int result; + do { + result = (int) getBits(bits); + } while (result >= bound); + + return result; + } + + @Override + public long nextLong() { + return getBits(64); + } + + @Override + public double nextDouble() { + return getBits(53) * 0x1.0p-53; + } + + @Override + public long setDecorationSeed(long worldSeed, int blockX, int blockZ) { + setSecureSeed(blockX, blockZ, Globals.Salt.POPULATION, 0); + return ((long) blockX << 32) | ((long) blockZ & 0xffffffffL); + } + + @Override + public void setFeatureSeed(long populationSeed, int index, int step) { + setSecureSeed((int) (populationSeed >> 32), (int) populationSeed, Globals.Salt.DECORATION, index + 10000L * step); + } + + @Override + public void setLargeFeatureSeed(long worldSeed, int chunkX, int chunkZ) { + super.setLargeFeatureSeed(worldSeed, chunkX, chunkZ); + } + + @Override + public void setLargeFeatureWithSalt(long worldSeed, int regionX, int regionZ, int salt) { + super.setLargeFeatureWithSalt(worldSeed, regionX, regionZ, salt); + } + + public static RandomSource seedSlimeChunk(int chunkX, int chunkZ) { + return new WorldgenCryptoRandom(chunkX, chunkZ, Globals.Salt.SLIME_CHUNK, 0); + } +} diff --git a/divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so b/divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so new file mode 100644 index 0000000..6a3dafd Binary files /dev/null and b/divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so differ diff --git a/divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll b/divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll new file mode 100644 index 0000000..65ffb81 Binary files /dev/null and b/divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll differ