diff --git a/gradle.properties b/gradle.properties index ce9a2ce..b9167c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ group=host.bloom.petal version=1.19-R0.1-SNAPSHOT mcVersion=1.19 packageVersion=1_19_R1 -purpurRef=8aa5c259c5cb5ebdc0b8e081834aef5aeee0dac6 +purpurRef=2b3486efc2478cb2be0a0b3eaeedbee9154303cc diff --git a/patches/server/0002-feat-async-path-processing.patch b/patches/server/0002-feat-async-path-processing.patch index 2e611ab..213626c 100644 --- a/patches/server/0002-feat-async-path-processing.patch +++ b/patches/server/0002-feat-async-path-processing.patch @@ -6,10 +6,10 @@ Subject: [PATCH] feat: async path processing diff --git a/src/main/java/host/bloom/pathfinding/AsyncPath.java b/src/main/java/host/bloom/pathfinding/AsyncPath.java new file mode 100644 -index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eeced76de761 +index 0000000000000000000000000000000000000000..db264161dfa5ef288f6d79a0031b56f95f17dd9d --- /dev/null +++ b/src/main/java/host/bloom/pathfinding/AsyncPath.java -@@ -0,0 +1,280 @@ +@@ -0,0 +1,288 @@ +package host.bloom.pathfinding; + +import net.minecraft.core.BlockPos; @@ -20,9 +20,9 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece +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.concurrent.CompletableFuture; +import java.util.function.Supplier; + +/** @@ -36,9 +36,9 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece + private volatile boolean processed = false; + + /** -+ * a future representing the processing state of this path ++ * runnables waiting for this to be processed + */ -+ private final @NotNull CompletableFuture processingFuture; ++ private final List postProcessing = new ArrayList<>(0); + + /** + * a list of positions that this path could path towards @@ -85,7 +85,7 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece + this.positions = positions; + this.pathSupplier = pathSupplier; + -+ this.processingFuture = AsyncPathProcessor.queue(this).thenApply((unused) -> this); ++ AsyncPathProcessor.queue(this); + } + + @Override @@ -97,8 +97,12 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece + * returns the future representing the processing state of this path + * @return a future + */ -+ public @NotNull CompletableFuture getProcessingFuture() { -+ return this.processingFuture; ++ public synchronized void postProcessing(@NotNull Runnable runnable) { ++ if (this.processed) { ++ runnable.run(); ++ } else { ++ this.postProcessing.add(runnable); ++ } + } + + /** @@ -131,6 +135,10 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece + this.canReach = bestPath.canReach(); + + this.processed = true; ++ ++ for (Runnable runnable : this.postProcessing) { ++ runnable.run(); ++ } + } + + /** @@ -292,7 +300,7 @@ index 0000000000000000000000000000000000000000..c6d9d79707d7953f30e7373ca6e3eece +} diff --git a/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java b/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java new file mode 100644 -index 0000000000000000000000000000000000000000..e900f8fb637c0d69f94c5a24fb17f794513a482b +index 0000000000000000000000000000000000000000..421670f967568c10e1e9052d0fc25818538d3b51 --- /dev/null +++ b/src/main/java/host/bloom/pathfinding/AsyncPathProcessor.java @@ -0,0 +1,44 @@ @@ -334,7 +342,7 @@ index 0000000000000000000000000000000000000000..e900f8fb637c0d69f94c5a24fb17f794 + */ + public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) { + if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) { -+ asyncPath.getProcessingFuture().thenAcceptAsync(afterProcessing, mainThreadExecutor); ++ asyncPath.postProcessing(() -> mainThreadExecutor.execute(() -> afterProcessing.accept(path))); + } else { + afterProcessing.accept(path); + } @@ -446,78 +454,170 @@ index bf3b8ccb3e031e0ad24cd51e28ea8cbd4f8a8030..e0453df8c0fcdb40ef0ed5ae8865d45d } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -index 18364ce4c60172529b10bc9e3a813dcedc4b766f..e6cd04bc4e19a54b1bd621ce1c880e932207bce3 100644 +index 18364ce4c60172529b10bc9e3a813dcedc4b766f..b91abb2c5f06b3b81c242f16f738bed450d923b7 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -@@ -58,6 +58,7 @@ public class MoveToTargetSink extends Behavior { +@@ -3,6 +3,7 @@ package net.minecraft.world.entity.ai.behavior; + import com.google.common.collect.ImmutableMap; + import java.util.Optional; + import javax.annotation.Nullable; ++ + import net.minecraft.core.BlockPos; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Mob; +@@ -16,11 +17,13 @@ import net.minecraft.world.entity.ai.util.DefaultRandomPos; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; + ++// petal start + public class MoveToTargetSink extends Behavior { + private static final int MAX_COOLDOWN_BEFORE_RETRYING = 40; + private int remainingCooldown; + @Nullable + private Path path; ++ private boolean finishedProcessing; // petal - track when path is processed + @Nullable + private BlockPos lastTargetPos; + private float speedModifier; +@@ -39,11 +42,11 @@ public class MoveToTargetSink extends Behavior { + --this.remainingCooldown; + return false; + } else { ++ // petal start - async path processing means we cant know if the path is reachable here + Brain brain = entity.getBrain(); + WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); + boolean bl = this.reachedTarget(entity, walkTarget); +- if (!bl && this.tryComputePath(entity, walkTarget, world.getGameTime())) { +- this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ if (!bl) { + return true; + } else { + brain.eraseMemory(MemoryModuleType.WALK_TARGET); +@@ -53,11 +56,14 @@ public class MoveToTargetSink extends Behavior { + + return false; + } ++ // petal end + } + } @Override protected boolean canStillUse(ServerLevel serverLevel, Mob mob, long l) { -+ if (this.path != null && !this.path.isProcessed()) return true; // petal - wait for path to process ++ if (!this.finishedProcessing) return true; // petal - wait for processing ++ if (this.path != null && this.lastTargetPos != null) { Optional optional = mob.getBrain().getMemory(MemoryModuleType.WALK_TARGET); PathNavigation pathNavigation = mob.getNavigation(); -@@ -87,6 +88,8 @@ public class MoveToTargetSink extends Behavior { +@@ -81,59 +87,79 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected void start(ServerLevel serverLevel, Mob mob, long l) { +- mob.getBrain().setMemory(MemoryModuleType.PATH, this.path); +- mob.getNavigation().moveTo(this.path, (double)this.speedModifier); ++ Brain brain = mob.getBrain(); ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); ++ ++ this.finishedProcessing = false; ++ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ this.path = this.computePath(mob, walkTarget); + } @Override protected void tick(ServerLevel world, Mob entity, long time) { + if (this.path != null && !this.path.isProcessed()) return; // petal - wait for processing + - Path path = entity.getNavigation().getPath(); - Brain brain = entity.getBrain(); - if (this.path != path) { -@@ -94,6 +97,12 @@ public class MoveToTargetSink extends Behavior { - brain.setMemory(MemoryModuleType.PATH, path); - } - -+ // petal start - periodically recall moveTo to ensure we're moving towards the correct path -+ if (time % 8 == 0) { ++ if (!this.finishedProcessing) { ++ this.finishedProcessing = true; ++ ++ Brain brain = entity.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, time); ++ } ++ ++ if (!canReach) { ++ Optional walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET); ++ ++ if (walkTarget.isPresent()) { ++ BlockPos blockPos = walkTarget.get().getTarget().currentBlockPosition(); ++ Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob)entity, 10, 7, Vec3.atBottomCenterOf(blockPos), (double)((float)Math.PI / 2F)); ++ if (vec3 != null) { ++ // try recalculating the path using a random position ++ this.path = entity.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); ++ this.finishedProcessing = false; ++ return; ++ } ++ } ++ ++ // we failed, erase and move on ++ brain.eraseMemory(MemoryModuleType.WALK_TARGET); ++ this.path = null; ++ ++ return; ++ } ++ ++ entity.getBrain().setMemory(MemoryModuleType.PATH, this.path); + entity.getNavigation().moveTo(this.path, (double)this.speedModifier); + } -+ // petal end + - if (path != null && this.lastTargetPos != null) { + Path path = entity.getNavigation().getPath(); + Brain brain = entity.getBrain(); +- if (this.path != path) { +- this.path = path; +- brain.setMemory(MemoryModuleType.PATH, path); +- } + +- if (path != null && this.lastTargetPos != null) { ++ if (path != null && this.lastTargetPos != null && brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) { WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); - if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D && this.tryComputePath(entity, walkTarget, world.getGameTime())) { -@@ -112,6 +121,20 @@ public class MoveToTargetSink extends Behavior { +- if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D && this.tryComputePath(entity, walkTarget, world.getGameTime())) { +- this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D) { + this.start(world, entity, time); + } + + } + } + +- private boolean tryComputePath(Mob entity, WalkTarget walkTarget, long time) { ++ private Path computePath(Mob entity, WalkTarget walkTarget) { + BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); +- this.path = entity.getNavigation().createPath(blockPos, 0); + this.speedModifier = walkTarget.getSpeedModifier(); + Brain brain = entity.getBrain(); if (this.reachedTarget(entity, walkTarget)) { brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); - } else { -+ // petal start - move this out to postProcessPath -+ host.bloom.pathfinding.AsyncPathProcessor.awaitProcessing(this.path, (unusedPath) -> { -+ this.postProcessPath(entity, walkTarget, time); -+ }); -+ return true; -+ } -+ -+ return false; -+ } -+ -+ private boolean postProcessPath(Mob entity, WalkTarget walkTarget, long time) { -+ Brain brain = entity.getBrain(); -+ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); -+ - boolean bl = this.path != null && this.path.canReach(); - if (bl) { - brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); -@@ -128,10 +151,17 @@ public class MoveToTargetSink extends Behavior { - this.path = entity.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); - return this.path != null; - } -+ -+ // We failed, so erase and move on -+ brain.eraseMemory(MemoryModuleType.WALK_TARGET); -+ if (bl) { -+ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); +- } else { +- boolean bl = this.path != null && this.path.canReach(); +- if (bl) { +- 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, time); +- } +- +- if (this.path != null) { +- return true; +- } +- +- Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob)entity, 10, 7, Vec3.atBottomCenterOf(blockPos), (double)((float)Math.PI / 2F)); +- if (vec3 != null) { +- this.path = entity.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); +- return this.path != null; +- } } -+ this.path = null; - return false; +- return false; ++ return entity.getNavigation().createPath(blockPos, 0); } -+ // petal end private boolean reachedTarget(Mob entity, WalkTarget walkTarget) { return walkTarget.getTarget().currentBlockPosition().distManhattan(entity.blockPosition()) <= walkTarget.getCloseEnoughDist(); + } + } ++// petal end +\ No newline at end of file diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java index 9bd6d4f7b86daaaa9cfbad454dde06b797e3f667..dc9dca72a22df9acadb8cdae8383522c996cbe10 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java diff --git a/patches/server/0003-feat-multithreaded-tracker.patch b/patches/server/0003-feat-multithreaded-tracker.patch index c6d0bbd..73e97c1 100644 --- a/patches/server/0003-feat-multithreaded-tracker.patch +++ b/patches/server/0003-feat-multithreaded-tracker.patch @@ -256,7 +256,7 @@ index ca42c2642a729b90d22b968af7258f3aee72e14b..40261b80d947a6be43465013fae55321 public boolean visible = true; diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 3441339e1ba5efb0e25c16fa13cb65d2fbdafc42..9d0cb0468800cbd88f43424466e9a29a05d3945d 100644 +index 3441339e1ba5efb0e25c16fa13cb65d2fbdafc42..555336cf6700566e8a99e0f0cd6f0cc41b6c5ba0 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -249,14 +249,18 @@ public class ServerEntity { @@ -280,3 +280,37 @@ index 3441339e1ba5efb0e25c16fa13cb65d2fbdafc42..9d0cb0468800cbd88f43424466e9a29a this.entity.startSeenByPlayer(player); } +@@ -362,19 +366,30 @@ public class ServerEntity { + SynchedEntityData datawatcher = this.entity.getEntityData(); + + if (datawatcher.isDirty()) { +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false)); ++ // Petal start - sync ++ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false)) ++ ); ++ // Petal end + } + + if (this.entity instanceof LivingEntity) { + Set set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes(); + + if (!set.isEmpty()) { ++ // Petal start - sync ++ final var copy = Lists.newArrayList(set); ++ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> { ++ + // CraftBukkit start - Send scaled max health + if (this.entity instanceof ServerPlayer) { +- ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false); ++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(copy, false); + } + // CraftBukkit end +- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set)); ++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); ++ ++ }); ++ // Petal end + } + + set.clear();