From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:54:00 +0800 Subject: [PATCH] Asynchronous locator Original license: MIT Original project: https://github.com/thebrightspark/AsyncLocator 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 11b7f15755dde766140c29bedca456c80d53293f..749d00449ac3f3c79bfc73a5517ea3a07675e447 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java @@ -80,7 +80,7 @@ public class TickThread extends Thread { this(run, name, ID_GENERATOR.incrementAndGet()); } - private TickThread(final Runnable run, final String name, final int id) { + protected TickThread(final Runnable run, final String name, final int id) { // Leaf - private -> protected super(run, name); this.id = id; } 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 6676be8304e9415099ed423d3315180cafebd928..30b56382e9574004e344c1c8289d7dcbb177386b 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java @@ -48,6 +48,12 @@ class PaperEventManager { return; } // Leaf end - Multithreaded tracker + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); + return; + } + // Leaf end - Async locator throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } // Leaves start - skip photographer diff --git a/src/main/java/net/minecraft/server/commands/LocateCommand.java b/src/main/java/net/minecraft/server/commands/LocateCommand.java index 39f5deea47d8f573c3cfec5df431216ee806c32c..51994f272737f8754aac41dc0c55f43f45617519 100644 --- a/src/main/java/net/minecraft/server/commands/LocateCommand.java +++ b/src/main/java/net/minecraft/server/commands/LocateCommand.java @@ -106,6 +106,37 @@ public class LocateCommand { BlockPos blockPos = BlockPos.containing(source.getPosition()); ServerLevel serverLevel = source.getLevel(); Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { + net.minecraft.commands.CommandSource locatorSource = source.source; + if (locatorSource instanceof net.minecraft.server.level.ServerPlayer || + locatorSource instanceof net.minecraft.server.MinecraftServer) { + BlockPos originPos = BlockPos.containing(source.getPosition()); + org.dreeam.leaf.async.locate.AsyncLocator.locate(source.getLevel(), holderSet, originPos, 100, false) + .thenOnServerThread(pair -> { + stopwatch.stop(); + if (pair != null) { + showLocateResult( + source, + predicate, + originPos, + pair, + "commands.locate.structure.success", + false, + stopwatch.elapsed() + ); + } else { + source.sendFailure( + Component.literal( + ERROR_STRUCTURE_NOT_FOUND.create(predicate.asPrintable()).getMessage() + ) + ); + } + }); + return 0; + } + } + // Leaf end - Async locator Pair> pair = serverLevel.getChunkSource() .getGenerator() .findNearestMapStructure(serverLevel, holderSet, blockPos, 100, false); diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java index ef0124ceb7cafd58c01c7f0b4b542f38a383ab88..061d020c08b722b92187ba9042ab4084ecd72b06 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java @@ -466,6 +466,8 @@ public class Dolphin extends WaterAnimal { private final Dolphin dolphin; private boolean stuck; + @Nullable + private org.dreeam.leaf.async.locate.AsyncLocator.LocateTask asyncLocator$locateTask; DolphinSwimToTreasureGoal(Dolphin dolphin) { this.dolphin = dolphin; @@ -485,6 +487,11 @@ public class Dolphin extends WaterAnimal { @Override public boolean canContinueToUse() { + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled && this.asyncLocator$locateTask != null) { + return true; + } + // Leaf end - Async locator BlockPos blockposition = this.dolphin.getTreasurePos(); return !BlockPos.containing((double) blockposition.getX(), this.dolphin.getY(), (double) blockposition.getZ()).closerToCenterThan(this.dolphin.position(), 4.0D) && !this.stuck && this.dolphin.getAirSupply() >= 100; @@ -498,6 +505,21 @@ public class Dolphin extends WaterAnimal { this.stuck = false; this.dolphin.getNavigation().stop(); BlockPos blockposition = this.dolphin.blockPosition(); + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { + asyncLocator$locateTask = org.dreeam.leaf.async.locate.AsyncLocator.locate(worldserver, StructureTags.DOLPHIN_LOCATED, blockposition, 50, false) + .thenOnServerThread(pos -> { + asyncLocator$locateTask = null; + if (pos != null) { + this.dolphin.setTreasurePos(pos); + worldserver.broadcastEntityEvent(this.dolphin, (byte) 38); + } else { + this.stuck = true; + } + }); + return; + } + // Leaf end - Async locator BlockPos blockposition1 = worldserver.findNearestMapStructure(StructureTags.DOLPHIN_LOCATED, blockposition, 50, false); if (blockposition1 != null) { @@ -511,6 +533,12 @@ public class Dolphin extends WaterAnimal { @Override public void stop() { + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled && this.asyncLocator$locateTask != null) { + this.asyncLocator$locateTask.cancel(); + this.asyncLocator$locateTask = null; + } + // Leaf end - Async locator BlockPos blockposition = this.dolphin.getTreasurePos(); if (BlockPos.containing((double) blockposition.getX(), this.dolphin.getY(), (double) blockposition.getZ()).closerToCenterThan(this.dolphin.position(), 4.0D) || this.stuck) { @@ -521,6 +549,11 @@ public class Dolphin extends WaterAnimal { @Override public void tick() { + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled && this.asyncLocator$locateTask != null) { + return; + } + // Leaf end - Async locator Level world = this.dolphin.level(); if (this.dolphin.closeToNextPos() || this.dolphin.getNavigation().isDone()) { diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java index fca3786d0a3f99a3e61e7a4b2251361276eff9d7..cb4ff1e98418c651ef21f04f3c74cac7065031ae 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java @@ -27,6 +27,7 @@ public class EyeOfEnder extends Entity implements ItemSupplier { public double tz; public int life; public boolean surviveAfterDeath; + public boolean asyncLocator$locateTaskOngoing = false; // Leaf - Async locator public EyeOfEnder(EntityType type, Level world) { super(type, world); @@ -114,6 +115,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier { @Override public void tick() { super.tick(); + // Leaf start - Async locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled && this.asyncLocator$locateTaskOngoing) { + return; + } + // Leaf end - Async locator Vec3 vec3d = this.getDeltaMovement(); double d0 = this.getX() + vec3d.x; double d1 = this.getY() + vec3d.y; diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java index d8ce44a180f848f4c9c04967470c4359af979b2f..90abb83a6baa60bbcbedc7d818c3bc9f4317f04f 100644 --- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java +++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java @@ -113,20 +113,54 @@ public class EnderEyeItem extends Item { user.startUsingItem(hand); if (world instanceof ServerLevel) { ServerLevel worldserver = (ServerLevel) world; - BlockPos blockposition = worldserver.findNearestMapStructure(StructureTags.EYE_OF_ENDER_LOCATED, user.blockPosition(), 100, false); + // Leaf start - Async locator + BlockPos blockposition; + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { + blockposition = BlockPos.ZERO; + } else { + blockposition = worldserver.findNearestMapStructure(StructureTags.EYE_OF_ENDER_LOCATED, user.blockPosition(), 100, false); + } + // Leaf end - Async locator if (blockposition != null) { EyeOfEnder entityendersignal = new EyeOfEnder(world, user.getX(), user.getY(0.5D), user.getZ()); + // Leaf start - Async locator + final boolean isAsyncLocatorEnabled = org.dreeam.leaf.config.modules.async.AsyncLocator.enabled; + if (isAsyncLocatorEnabled) { + entityendersignal.asyncLocator$locateTaskOngoing = true; + org.dreeam.leaf.async.locate.AsyncLocator.locate( + worldserver, + StructureTags.EYE_OF_ENDER_LOCATED, + user.blockPosition(), + 100, + false + ).thenOnServerThread(pos -> { + entityendersignal.asyncLocator$locateTaskOngoing = false; + if (pos != null) { + entityendersignal.signalTo(pos); + CriteriaTriggers.USED_ENDER_EYE.trigger((ServerPlayer) user, pos); + user.awardStat(Stats.ITEM_USED.get(this)); + } else { + // Set the entity's life to long enough that it dies + entityendersignal.life = Integer.MAX_VALUE - 100; + } + }); + } + // Leaf end - Async locator entityendersignal.setItem(itemstack); - entityendersignal.signalTo(blockposition); + // Leaf start - Async locator + if (!isAsyncLocatorEnabled) { + entityendersignal.signalTo(blockposition); + } + // Leaf end - Async locator world.gameEvent((Holder) GameEvent.PROJECTILE_SHOOT, entityendersignal.position(), GameEvent.Context.of((Entity) user)); // CraftBukkit start if (!world.addFreshEntity(entityendersignal)) { return new InteractionResultHolder(InteractionResult.FAIL, itemstack); } // CraftBukkit end - if (user instanceof ServerPlayer) { + if (!isAsyncLocatorEnabled && user instanceof ServerPlayer) { // Leaf - Async locator ServerPlayer entityplayer = (ServerPlayer) user; CriteriaTriggers.USED_ENDER_EYE.trigger(entityplayer, blockposition); @@ -136,7 +170,11 @@ public class EnderEyeItem extends Item { world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_EYE_LAUNCH, SoundSource.NEUTRAL, 1.0F, f); itemstack.consume(1, user); - user.awardStat(Stats.ITEM_USED.get(this)); + // Leaf start - Async locator + if (!isAsyncLocatorEnabled) { + user.awardStat(Stats.ITEM_USED.get(this)); + } + // Leaf end - Async locator user.swing(hand, true); return InteractionResultHolder.success(itemstack); } diff --git a/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java b/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java new file mode 100644 index 0000000000000000000000000000000000000000..0eaa9b60ac139db47a028970557c3a84166efc49 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java @@ -0,0 +1,160 @@ +package org.dreeam.leaf.async.locate; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.structure.Structure; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +// Original project: https://github.com/thebrightspark/AsyncLocator +public class AsyncLocator { + private static final ExecutorService LOCATING_EXECUTOR_SERVICE; + + private AsyncLocator() {} + + public static class AsyncLocatorThread extends TickThread { + private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0); + public AsyncLocatorThread(Runnable run, String name) { + super(run, name, THREAD_COUNTER.incrementAndGet()); + } + + @Override + public void run() { + super.run(); + } + } + + static { + int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads; + LOCATING_EXECUTOR_SERVICE = Executors.newFixedThreadPool( + threads, + new ThreadFactoryBuilder() + .setThreadFactory( + r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") { + @Override + public void run() { + r.run(); + } + } + ) + .setNameFormat("Leaf Async Locator Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); + } + + public static void shutdownExecutorService() { + if (LOCATING_EXECUTOR_SERVICE != null) { + LOCATING_EXECUTOR_SERVICE.shutdown(); + } + } + + /** + * Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} + * and returns a {@link LocateTask} with the futures for it. + */ + public static LocateTask locate( + ServerLevel level, + TagKey structureTag, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + CompletableFuture completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + /** + * Queues a task to locate a feature using + * {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a + * {@link LocateTask} with the futures for it. + */ + public static LocateTask>> locate( + ServerLevel level, + HolderSet structureSet, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + CompletableFuture>> completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + private static void doLocateLevel( + CompletableFuture completableFuture, + ServerLevel level, + TagKey structureTag, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks); + completableFuture.complete(foundPos); + } + + private static void doLocateChunkGenerator( + CompletableFuture>> completableFuture, + ServerLevel level, + HolderSet structureSet, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + Pair> foundPair = level.getChunkSource().getGenerator() + .findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks); + completableFuture.complete(foundPair); + } + + /** + * Holder of the futures for an async locate task as well as providing some helper functions. + * The completableFuture will be completed once the call to + * {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the + * result of it. + * The taskFuture is the future for the {@link Runnable} itself in the executor service. + */ + public record LocateTask(MinecraftServer server, CompletableFuture completableFuture, Future taskFuture) { + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action. + * Bear in mind that the action will be executed from the task's thread. If you intend to change any game data, + * it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed + * on the main server thread instead. + */ + public LocateTask then(Consumer action) { + completableFuture.thenAccept(action); + return this; + } + + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server + * thread. + */ + public LocateTask thenOnServerThread(Consumer action) { + completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos))); + return this; + } + + /** + * Helper function that cancels both completableFuture and taskFuture. + */ + public void cancel() { + taskFuture.cancel(true); + completableFuture.cancel(false); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2e562af7f7b08f6e95702f26f97f2c6f6aeb02 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java @@ -0,0 +1,37 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +public class AsyncLocator extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator"; + } + + public static boolean enabled = false; + public static int asyncLocatorThreads = 0; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Whether or not asynchronous locator should be enabled. + This offloads structure locating to other threads. + Only for locate command, dolphin treasure finding and eye of ender currently.""", + """ + 是否启用异步结构搜索. + 目前可用于 /locate 指令, 海豚寻宝和末影之眼."""); + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads); + + if (asyncLocatorThreads < 0) + asyncLocatorThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncLocatorThreads, 1); + else if (asyncLocatorThreads == 0) + asyncLocatorThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); + if (!enabled) + asyncLocatorThreads = 0; + else + LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads); + } +}