9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-23 08:49:18 +00:00

update Async Pathfinding and Multithreaded Tracker

This commit is contained in:
NONPLAYT
2025-02-24 15:06:22 +03:00
parent 7c4d0ebc0d
commit 1b9c969f1c
9 changed files with 540 additions and 220 deletions

View File

@@ -9,10 +9,10 @@ You can find the original code on https://github.com/Bloom-host/Petal
Makes most pathfinding-related work happen asynchronously 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 diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2a361e097 100644 index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..7ad36864d700f3012a3791bf3814465e5e9deffa 100644
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java --- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -94,21 +94,54 @@ public class AcquirePoi { @@ -94,21 +94,18 @@ public class AcquirePoi {
} }
} }
// Paper end - optimise POI access // Paper end - optimise POI access
@@ -27,38 +27,37 @@ index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2
- DebugPackets.sendPoiTicketCountPacket(level, target); - DebugPackets.sendPoiTicketCountPacket(level, target);
+ // DivineMC start - Async path processing + // DivineMC start - Async path processing
+ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { + if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) {
+ // await on path async
+ Path possiblePath = findPathToPois(mob, set); + Path possiblePath = findPathToPois(mob, set);
+ +
+ // wait on the path to be processed
+ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { + org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ // read canReach check + processPath(acquirablePois, entityEventId, (Long2ObjectMap<JitteredLinearRetry>) map, memoryAccessor, level, mob, time, poiManager, set, path);
+ if (path == null || !path.canReach()) {
+ for (Pair<Holder<PoiType>, 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 { } else {
- for (Pair<Holder<PoiType>, BlockPos> pair : set) { - for (Pair<Holder<PoiType>, BlockPos> pair : set) {
- map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); - map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time));
- }
+ Path path = findPathToPois(mob, set); + Path path = findPathToPois(mob, set);
+ processPath(acquirablePois, entityEventId, (Long2ObjectMap<JitteredLinearRetry>) map, memoryAccessor, level, mob, time, poiManager, set, path);
}
+ // DivineMC end - Async path processing
return true;
}
@@ -120,6 +117,34 @@ public class AcquirePoi {
: BehaviorBuilder.create(instance -> instance.group(instance.absent(existingAbsentMemory)).apply(instance, memoryAccessor -> oneShot));
}
+ // DivineMC start - Async path processing
+ private static void processPath(Predicate<Holder<PoiType>> acquirablePois,
+ Optional<Byte> entityEventId,
+ Long2ObjectMap<JitteredLinearRetry> map,
+ net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor<com.mojang.datafixers.kinds.Const.Mu<com.mojang.datafixers.util.Unit>, GlobalPos> memoryAccessor,
+ ServerLevel level,
+ PathfinderMob mob,
+ long time,
+ PoiManager poiManager,
+ Set<Pair<Holder<PoiType>, BlockPos>> set,
+ Path path) {
+ if (path != null && path.canReach()) { + if (path != null && path.canReach()) {
+ BlockPos target = path.getTarget(); + BlockPos target = path.getTarget();
+ poiManager.getType(target).ifPresent(holder -> { + poiManager.getType(target).ifPresent(holder -> {
@@ -70,14 +69,15 @@ index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2
+ }); + });
+ } else { + } else {
+ for (Pair<Holder<PoiType>, BlockPos> pair : set) { + for (Pair<Holder<PoiType>, BlockPos> pair : set) {
+ map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); + map.computeIfAbsent(pair.getSecond().asLong(), l -> new JitteredLinearRetry(level.random, time));
+ }
+ }
+ } + }
}
}
+ // DivineMC end - Async path processing + // DivineMC end - Async path processing
+
return true; @Nullable
} public static Path findPathToPois(Mob mob, Set<Pair<Holder<PoiType>, BlockPos>> poiPositions) {
if (poiPositions.isEmpty()) {
diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac99260b9ad 100644 index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac99260b9ad 100644
--- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java --- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
@@ -222,10 +222,10 @@ index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac9
private boolean tryComputePath(Mob mob, WalkTarget target, long time) { private boolean tryComputePath(Mob mob, WalkTarget target, long time) {
BlockPos blockPos = target.getTarget().currentBlockPosition(); 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 diff --git a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c0cb53645 100644 index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..1e9383442d0a0adf4bf621fc35108b35b1375bc7 100644
--- a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java --- a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
+++ b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +++ b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
@@ -60,17 +60,38 @@ public class SetClosestHomeAsWalkTarget { @@ -60,17 +60,18 @@ public class SetClosestHomeAsWalkTarget {
poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY
) )
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@@ -236,26 +236,38 @@ index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c
- if (type.isPresent()) { - if (type.isPresent()) {
- walkTarget.set(new WalkTarget(target, speedModifier, 1)); - walkTarget.set(new WalkTarget(target, speedModifier, 1));
- DebugPackets.sendPoiTicketCountPacket(level, target); - DebugPackets.sendPoiTicketCountPacket(level, target);
- }
- } else if (mutableInt.getValue() < 5) {
- map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue());
+ // DivineMC start - async path processing + // DivineMC start - async path processing
+ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { + if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) {
+ // await on path async
+ Path possiblePath = AcquirePoi.findPathToPois(mob, set); + Path possiblePath = AcquirePoi.findPathToPois(mob, set);
+ +
+ // wait on the path to be processed
+ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { + org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ if (path == null || !path.canReach() || mutableInt.getValue() < 5) { // read canReach check + processPath(speedModifier, map, mutableLong, walkTarget, level, poiManager, mutableInt, path);
+ map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue());
+ return;
+ }
+ BlockPos blockPos = path.getTarget();
+ Optional<Holder<PoiType>> optional2 = poiManager.getType(blockPos);
+ if (optional2.isPresent()) {
+ walkTarget.set(new WalkTarget(blockPos, speedModifier, 1));
+ DebugPackets.sendPoiTicketCountPacket(level, blockPos);
+ }
+ }); + });
+ } else { + } else {
+ Path path = AcquirePoi.findPathToPois(mob, set); + Path path = AcquirePoi.findPathToPois(mob, set);
+ processPath(speedModifier, map, mutableLong, walkTarget, level, poiManager, mutableInt, path);
}
+ // DivineMC end - async path processing
return true;
} else {
@@ -81,4 +82,26 @@ public class SetClosestHomeAsWalkTarget {
)
);
}
+
+ // DivineMC start - async path processing
+ private static void processPath(float speedModifier,
+ Long2LongMap map,
+ MutableLong mutableLong,
+ net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor<com.mojang.datafixers.kinds.Const.Mu<com.mojang.datafixers.util.Unit>, WalkTarget> walkTarget,
+ net.minecraft.server.level.ServerLevel level,
+ PoiManager poiManager,
+ MutableInt mutableInt,
+ @org.jetbrains.annotations.Nullable Path path) {
+ if (path != null && path.canReach()) { + if (path != null && path.canReach()) {
+ BlockPos target = path.getTarget(); + BlockPos target = path.getTarget();
+ Optional<Holder<PoiType>> type = poiManager.getType(target); + Optional<Holder<PoiType>> type = poiManager.getType(target);
@@ -265,14 +277,10 @@ index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c
+ } + }
+ } else if (mutableInt.getValue() < 5) { + } else if (mutableInt.getValue() < 5) {
+ map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); + 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 + // 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 diff --git a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java
index d8f532c5e68ff4dff933556c4f981e9474c044e6..37f3d3888ea2a862d006cf2b201f9715bcb8ce1e 100644 index d8f532c5e68ff4dff933556c4f981e9474c044e6..37f3d3888ea2a862d006cf2b201f9715bcb8ce1e 100644
--- a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java --- a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java
@@ -504,55 +512,52 @@ index 2979846853898d78a2df19df2287da16dbe4ae71..504911c14aad5c53aeea2c71bda3978f
} }
diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..3a8b3c71f6fd0c0f57a3190dc8a0f808c6d1002b 100644 index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..9494dd144ff1115c9f0862614ef618035ee2565f 100644
--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java --- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/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<Mob> { @@ -57,17 +57,32 @@ public class NearestBedSensor extends Sensor<Mob> {
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>(); java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
// don't ask me why it's unbounded. ask mojang. // 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 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)); - Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
// Paper end - optimise POI access - // Paper end - optimise POI access
- if (path != null && path.canReach()) { - if (path != null && path.canReach()) {
- BlockPos target = path.getTarget(); - BlockPos target = path.getTarget();
- Optional<Holder<PoiType>> type = poiManager.getType(target); - Optional<Holder<PoiType>> type = poiManager.getType(target);
- if (type.isPresent()) { - if (type.isPresent()) {
- entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); - entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target);
- }
- } else if (this.triedCount < 5) {
- this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate);
+ // DivineMC start - async pathfinding + // DivineMC start - async pathfinding
+ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { + if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) {
+ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); + Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
+ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { + org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> {
+ // read canReach check + processPath(entity, poiManager, path);
+ 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<Holder<PoiType>> optional = poiManager.getType(blockPos);
+ if (optional.isPresent()) {
+ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, blockPos);
+ }
+ }); + });
+ } else { + } else {
+ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); + Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
+ // Paper end - optimise POI access
+ processPath(entity, poiManager, path);
+ }
+ // DivineMC end - async pathfinding
+ }
+ }
+
+ // DivineMC start - async pathfinding
+ private void processPath(Mob entity, PoiManager poiManager, @org.jetbrains.annotations.Nullable Path path) {
+ if (path != null && path.canReach()) { + if (path != null && path.canReach()) {
+ BlockPos target = path.getTarget(); + BlockPos target = path.getTarget();
+ Optional<Holder<PoiType>> type = poiManager.getType(target); + Optional<Holder<PoiType>> type = poiManager.getType(target);
+ if (type.isPresent()) { + if (type.isPresent()) {
+ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); + entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target);
+ } }
+ } else if (this.triedCount < 5) { + } else if (this.triedCount < 5) {
+ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); + 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 + // DivineMC end - async pathfinding
} }
}
}
diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java
index d5727999eb67ff30dbf47865d59452483338e170..ddbee0f0f42fae0a26321bb324d22f5e7520ae72 100644 index d5727999eb67ff30dbf47865d59452483338e170..ddbee0f0f42fae0a26321bb324d22f5e7520ae72 100644
--- a/net/minecraft/world/entity/animal/Bee.java --- a/net/minecraft/world/entity/animal/Bee.java

View File

@@ -4,8 +4,25 @@ Date: Tue, 28 Jan 2025 01:18:49 +0300
Subject: [PATCH] Multithreaded Tracker Subject: [PATCH] Multithreaded Tracker
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
index c06784f401dbdc8c215876aa86cd1a7289bd4332..f81cc357618c70f2fcf0bc24b0b25be566ffffcc 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
@@ -342,7 +342,11 @@ public final class RegionizedPlayerChunkLoader {
private boolean canGenerateChunks = true;
private final ArrayDeque<ChunkHolderManager.TicketOperation<?, ?>> delayedTicketOps = new ArrayDeque<>();
- private final LongOpenHashSet sentChunks = new LongOpenHashSet();
+ // DivineMC start - Multithreaded tracker
+ private final LongOpenHashSet sentChunks = org.bxteam.divinemc.DivineConfig.multithreadedEnabled && !org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled
+ ? new org.bxteam.divinemc.util.map.ConcurrentLongHashSet()
+ : new LongOpenHashSet();
+ // DivineMC end - Multithreaded tracker
private static final byte CHUNK_TICKET_STAGE_NONE = 0;
private static final byte CHUNK_TICKET_STAGE_LOADING = 1;
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee50c9901a 100644 index da2921268a9aa4545a12c155a33bed9fccb4f19c..3bf5f76dd61f6d20819440b28cbf46ed8ba34670 100644
--- a/net/minecraft/server/level/ChunkMap.java --- a/net/minecraft/server/level/ChunkMap.java
+++ b/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 @@ -264,9 +264,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -30,27 +47,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
} }
// Paper start - per player mob count backoff // Paper start - per player mob count backoff
@@ -950,6 +960,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -972,6 +982,13 @@ 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<Runnable> 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 // Paper end - optimise entity tracker
protected void tick() { protected void tick() {
@@ -64,7 +61,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
// Paper start - optimise entity tracker // Paper start - optimise entity tracker
if (true) { if (true) {
this.newTrackerTick(); this.newTrackerTick();
@@ -1094,7 +1124,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -1094,7 +1111,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final Entity entity; final Entity entity;
private final int range; private final int range;
SectionPos lastSectionPos; SectionPos lastSectionPos;
@@ -77,7 +74,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
// Paper start - optimise entity tracker // Paper start - optimise entity tracker
private long lastChunkUpdate = -1L; private long lastChunkUpdate = -1L;
@@ -1121,21 +1155,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -1121,21 +1142,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lastTrackedChunk = chunk; this.lastTrackedChunk = chunk;
final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
@@ -143,29 +140,23 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
} }
@Override @Override
@@ -1197,9 +1265,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -1197,7 +1252,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} }
public void broadcast(Packet<?> packet) { public void broadcast(Packet<?> packet) {
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
+ // DivineMC start - Multithreaded tracker + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { // DivineMC - Multithreaded tracker
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
serverPlayerConnection.send(packet); serverPlayerConnection.send(packet);
} }
+ // DivineMC end - Multithreaded tracker
} }
@@ -1210,21 +1265,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public void broadcastAndSend(Packet<?> packet) {
@@ -1210,21 +1280,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} }
public void broadcastRemoved() { public void broadcastRemoved() {
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
+ // DivineMC start - Multithreaded tracker + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { // DivineMC - Multithreaded tracker
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
} }
+ // DivineMC end - Multithreaded tracker
} }
public void removePlayer(ServerPlayer player) { public void removePlayer(ServerPlayer player) {
@@ -200,10 +191,10 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..3b4dff8867e91884b5720ca8a9cb64af
public boolean visible = true; public boolean visible = true;
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e 100644 index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..b6053158f5d9b6ad325ea075ab7c60f9966ba496 100644
--- a/net/minecraft/server/level/ServerEntity.java --- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java
@@ -110,8 +110,13 @@ public class ServerEntity { @@ -110,8 +110,16 @@ public class ServerEntity {
.forEach( .forEach(
removedPassenger -> { removedPassenger -> {
if (removedPassenger instanceof ServerPlayer serverPlayer1) { if (removedPassenger instanceof ServerPlayer serverPlayer1) {
@@ -211,59 +202,30 @@ index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..3ee43ca5c49af83a067c7ffe74d3f2bc
- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); - .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
+ // DivineMC start - Multithreaded tracker + // DivineMC start - Multithreaded tracker
+ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && Thread.currentThread() instanceof org.bxteam.divinemc.entity.tracking.MultithreadedTracker.MultithreadedTrackerThread) { + 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())); + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> serverPlayer1.connection
+ .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot())
+ );
+ } else { + } else {
+ serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); + serverPlayer1.connection
+ .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
+ } + }
+ // DivineMC end - Multithreaded tracker + // DivineMC end - Multithreaded tracker
} }
} }
); );
@@ -304,7 +309,11 @@ public class ServerEntity { @@ -410,12 +418,13 @@ 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<SynchedEntityData.DataValue<?>> 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) { if (this.entity instanceof LivingEntity) {
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
if (!attributesToSync.isEmpty()) { if (!attributesToSync.isEmpty()) {
- // CraftBukkit start - Send scaled max health + final Set<AttributeInstance> copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); // DivineMC - Multithreaded tracker
- if (this.entity instanceof ServerPlayer serverPlayer) { // CraftBukkit start - Send scaled max health
if (this.entity instanceof ServerPlayer serverPlayer) {
- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); - serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
- } + serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); // DivineMC - Multithreaded tracker
- // CraftBukkit end }
// CraftBukkit end
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
+ // DivineMC start - Multithreaded tracker + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); // DivineMC - Multithreaded tracker
+ final Set<AttributeInstance> 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(); attributesToSync.clear();

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Skip distanceToSqr call in ServerEntity#sendChanges if the
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e..a03dced5231ca47abf5919e3eca358c16a25337b 100644 index b6053158f5d9b6ad325ea075ab7c60f9966ba496..c7ad6bf5dd8cb8a27de3e3ed82c6eefaa36c5c08 100644
--- a/net/minecraft/server/level/ServerEntity.java --- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java
@@ -200,23 +200,27 @@ public class ServerEntity { @@ -203,23 +203,27 @@ public class ServerEntity {
if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) { if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) {
Vec3 deltaMovement = this.entity.getDeltaMovement(); Vec3 deltaMovement = this.entity.getDeltaMovement();

View File

@@ -5,10 +5,10 @@ Subject: [PATCH] Optimize canSee checks
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index e3427e7aecfc4e1fafb38316824aa1ee50c9901a..9bca28f33aa3b3cb8964c06b2f4d2f0a591e81f5 100644 index 3bf5f76dd61f6d20819440b28cbf46ed8ba34670..a2742f6c5af8ad128b84121727798ea249e7ef51 100644
--- a/net/minecraft/server/level/ChunkMap.java --- a/net/minecraft/server/level/ChunkMap.java
+++ b/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 @@ -1300,7 +1300,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); 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 // Paper end - Configurable entity tracking range by Y
// CraftBukkit start - respect vanish API // CraftBukkit start - respect vanish API

View File

@@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.configuration.MemoryConfiguration;
import org.bxteam.divinemc.entity.pathfinding.PathfindTaskRejectPolicy;
import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.simpleyaml.configuration.comments.CommentType; import org.simpleyaml.configuration.comments.CommentType;
@@ -316,33 +317,52 @@ public class DivineConfig {
public static boolean asyncPathfinding = true; public static boolean asyncPathfinding = true;
public static int asyncPathfindingMaxThreads = 2; public static int asyncPathfindingMaxThreads = 2;
public static int asyncPathfindingKeepalive = 60; public static int asyncPathfindingKeepalive = 60;
public static int asyncPathfindingQueueSize = 0;
public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL;
private static void asyncPathfinding() { private static void asyncPathfinding() {
asyncPathfinding = getBoolean("settings.async-pathfinding.enable", asyncPathfinding); asyncPathfinding = getBoolean("settings.async-pathfinding.enable", asyncPathfinding);
asyncPathfindingMaxThreads = getInt("settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads); asyncPathfindingMaxThreads = getInt("settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads);
asyncPathfindingKeepalive = getInt("settings.async-pathfinding.keepalive", asyncPathfindingKeepalive); asyncPathfindingKeepalive = getInt("settings.async-pathfinding.keepalive", asyncPathfindingKeepalive);
asyncPathfindingQueueSize = getInt("settings.async-pathfinding.queue-size", asyncPathfindingQueueSize);
final int maxThreads = Runtime.getRuntime().availableProcessors();
if (asyncPathfindingMaxThreads < 0) { if (asyncPathfindingMaxThreads < 0) {
asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1); asyncPathfindingMaxThreads = Math.max(maxThreads + asyncPathfindingMaxThreads, 1);
} else if (asyncPathfindingMaxThreads == 0) { } else if (asyncPathfindingMaxThreads == 0) {
asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); asyncPathfindingMaxThreads = Math.max(maxThreads / 4, 1);
} }
if (!asyncPathfinding) { if (!asyncPathfinding) {
asyncPathfindingMaxThreads = 0; asyncPathfindingMaxThreads = 0;
} else { } else {
Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding"); LOGGER.info("Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding");
} }
if (asyncPathfindingQueueSize <= 0) asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256;
asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(getString("settings.async-pathfinding.reject-policy", maxThreads >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString(),
"The policy to use when the queue is full and a new task is submitted.",
"FLUSH_ALL: All pending tasks will be run on server thread.",
"CALLER_RUNS: Newly submitted task will be run on server thread."));
} }
public static boolean multithreadedEnabled = true; public static boolean multithreadedEnabled = true;
public static boolean multithreadedCompatModeEnabled = false; public static boolean multithreadedCompatModeEnabled = false;
public static int asyncEntityTrackerMaxThreads = 1; public static int asyncEntityTrackerMaxThreads = 1;
public static int asyncEntityTrackerKeepalive = 60; public static int asyncEntityTrackerKeepalive = 60;
public static int asyncEntityTrackerQueueSize = 0;
private static void multithreadedTracker() { private static void multithreadedTracker() {
multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled); multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled,
multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled); "Make entity tracking saving asynchronously, can improve performance significantly,",
"especially in some massive entities in small area situations.");
multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled,
"Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed.",
"Compat mode fixes visible issues with player type NPCs of Citizens.",
"But we recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC and etc.");
asyncEntityTrackerMaxThreads = getInt("settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads); asyncEntityTrackerMaxThreads = getInt("settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads);
asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.keepalive", asyncEntityTrackerKeepalive); asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.keepalive", asyncEntityTrackerKeepalive);
asyncEntityTrackerQueueSize = getInt("settings.multithreaded-tracker.queue-size", asyncEntityTrackerQueueSize);
if (asyncEntityTrackerMaxThreads < 0) { if (asyncEntityTrackerMaxThreads < 0) {
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
@@ -353,7 +373,9 @@ public class DivineConfig {
if (!multithreadedEnabled) { if (!multithreadedEnabled) {
asyncEntityTrackerMaxThreads = 0; asyncEntityTrackerMaxThreads = 0;
} else { } else {
Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker"); LOGGER.info("Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker");
} }
if (asyncEntityTrackerQueueSize <= 0) asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
} }
} }

View File

@@ -3,29 +3,76 @@ package org.bxteam.divinemc.entity.pathfinding;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.level.pathfinder.Path;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bxteam.divinemc.DivineConfig;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
* used to handle the scheduling of async path processing * Used to handle the scheduling of async path processing
*/ */
@SuppressWarnings("DuplicatedCode")
public class AsyncPathProcessor { public class AsyncPathProcessor {
private static final Executor pathProcessingExecutor = new ThreadPoolExecutor( private static final String THREAD_PREFIX = "DivineMC Async Pathfinding";
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
private static long lastWarnMillis = System.currentTimeMillis();
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
1, 1,
org.bxteam.divinemc.DivineConfig.asyncPathfindingMaxThreads, DivineConfig.asyncPathfindingMaxThreads,
org.bxteam.divinemc.DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS, DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), getQueueImpl(),
new ThreadFactoryBuilder() new ThreadFactoryBuilder()
.setNameFormat("DivineMC Async Pathfinding Thread - %d") .setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2) .setPriority(Thread.NORM_PRIORITY - 2)
.build() .build(),
new RejectedTaskHandler()
); );
private static class RejectedTaskHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
switch (DivineConfig.asyncPathfindingRejectPolicy) {
case FLUSH_ALL -> {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
case CALLER_RUNS -> rejectedTask.run();
}
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in DivineMC config may help.");
lastWarnMillis = System.currentTimeMillis();
}
}
}
protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) { protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
return CompletableFuture.runAsync(path::process, pathProcessingExecutor); return CompletableFuture.runAsync(path::process, pathProcessingExecutor)
.orTimeout(60L, TimeUnit.SECONDS)
.exceptionally(throwable -> {
if (throwable instanceof TimeoutException e) {
LOGGER.warn("Async Pathfinding process timed out", e);
} else LOGGER.warn("Error occurred while processing async path", throwable);
return null;
});
} }
/** /**
@@ -45,4 +92,10 @@ public class AsyncPathProcessor {
afterProcessing.accept(path); afterProcessing.accept(path);
} }
} }
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = DivineConfig.asyncPathfindingQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}
} }

View File

@@ -0,0 +1,18 @@
package org.bxteam.divinemc.entity.pathfinding;
import org.bxteam.divinemc.DivineConfig;
import java.util.Locale;
public enum PathfindTaskRejectPolicy {
FLUSH_ALL,
CALLER_RUNS;
public static PathfindTaskRejectPolicy fromString(String policy) {
try {
return PathfindTaskRejectPolicy.valueOf(policy.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
DivineConfig.LOGGER.warn("Invalid pathfind task reject policy: {}, falling back to {}.", policy, FLUSH_ALL.toString());
return FLUSH_ALL;
}
}
}

View File

@@ -5,7 +5,6 @@ import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; 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.chunk_system.level.entity.server.ServerEntityLookup;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.FullChunkStatus;
@@ -13,41 +12,32 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bxteam.divinemc.DivineConfig;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class MultithreadedTracker { public class MultithreadedTracker {
private static final Logger LOGGER = LogManager.getLogger("MultithreadedTracker"); private static final String THREAD_PREFIX = "DivineMC Async Tracker";
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
public static class MultithreadedTrackerThread extends Thread { private static long lastWarnMillis = System.currentTimeMillis();
@Override private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
public void run() { getCorePoolSize(),
super.run(); getMaxPoolSize(),
} getKeepAliveTime(), TimeUnit.SECONDS,
} getQueueImpl(),
getThreadFactory(),
private static final Executor trackerExecutor = new ThreadPoolExecutor( getRejectedPolicy()
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() { public static Executor getTrackerExecutor() {
return trackerExecutor; return trackerExecutor;
@@ -55,7 +45,7 @@ public class MultithreadedTracker {
public static void tick(ChunkSystemServerLevel level) { public static void tick(ChunkSystemServerLevel level) {
try { try {
if (!org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) { if (!DivineConfig.multithreadedCompatModeEnabled) {
tickAsync(level); tickAsync(level);
} else { } else {
tickAsyncWithCompatMode(level); tickAsyncWithCompatMode(level);
@@ -81,7 +71,7 @@ public class MultithreadedTracker {
if (tracker == null) continue; if (tracker == null) continue;
((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
tracker.serverEntity.sendChanges(); tracker.serverEntity.sendChanges();
} }
}); });
@@ -103,7 +93,7 @@ public class MultithreadedTracker {
if (tracker == null) continue; if (tracker == null) continue;
((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
} }
@@ -138,4 +128,61 @@ public class MultithreadedTracker {
} }
} }
} }
private static int getCorePoolSize() {
return 1;
}
private static int getMaxPoolSize() {
return DivineConfig.asyncEntityTrackerMaxThreads;
}
private static long getKeepAliveTime() {
return DivineConfig.asyncEntityTrackerKeepalive;
}
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = DivineConfig.asyncEntityTrackerQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}
private static @NotNull ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setThreadFactory(MultithreadedTrackerThread::new)
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build();
}
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {
return (rejectedTask, executor) -> {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async entity tracker is busy! Tracking tasks will be done in the server thread. Increasing max-threads in DivineMC config may help.");
lastWarnMillis = System.currentTimeMillis();
}
};
}
public static class MultithreadedTrackerThread extends Thread {
public MultithreadedTrackerThread(Runnable runnable) {
super(runnable);
}
}
} }

View File

@@ -0,0 +1,213 @@
package org.bxteam.divinemc.util.map;
import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* A thread-safe implementation of {@link LongOpenHashSet} using ConcurrentHashMap.KeySetView as backing storage.
* This implementation provides concurrent access and high performance for concurrent operations.
*
* @author HaHaWTH at Leaf
*/
@SuppressWarnings({"unused", "deprecation"})
public final class ConcurrentLongHashSet extends LongOpenHashSet implements LongSet {
private final ConcurrentHashMap.KeySetView<Long, Boolean> backing;
/**
* Creates a new empty concurrent long set.
*/
public ConcurrentLongHashSet() {
this.backing = ConcurrentHashMap.newKeySet();
}
@Override
public int size() {
return backing.size();
}
@Override
public boolean isEmpty() {
return backing.isEmpty();
}
@Override
public @NotNull LongIterator iterator() {
return new WrappingLongIterator(backing.iterator());
}
@NotNull
@Override
public Object @NotNull [] toArray() {
return backing.toArray();
}
@NotNull
@Override
public <T> T @NotNull [] toArray(@NotNull T @NotNull [] array) {
Objects.requireNonNull(array, "Array cannot be null");
return backing.toArray(array);
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
return backing.containsAll(collection);
}
@Override
public boolean addAll(@NotNull Collection<? extends Long> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
return backing.addAll(collection);
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
return backing.removeAll(collection);
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
return backing.retainAll(collection);
}
@Override
public void clear() {
backing.clear();
}
@Override
public boolean add(long key) {
return backing.add(key);
}
@Override
public boolean contains(long key) {
return backing.contains(key);
}
@Override
public long[] toLongArray() {
int size = backing.size();
long[] result = new long[size];
int i = 0;
for (Long value : backing) {
result[i++] = value;
}
return result;
}
@Override
public long[] toArray(long[] array) {
Objects.requireNonNull(array, "Array cannot be null");
long[] result = toLongArray();
if (array.length < result.length) {
return result;
}
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = 0;
}
return array;
}
@Override
public boolean addAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
boolean modified = false;
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
modified |= add(iterator.nextLong());
}
return modified;
}
@Override
public boolean containsAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
if (!contains(iterator.nextLong())) {
return false;
}
}
return true;
}
@Override
public boolean removeAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
boolean modified = false;
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
modified |= remove(iterator.nextLong());
}
return modified;
}
@Override
public boolean retainAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
return backing.retainAll(c);
}
@Override
public boolean remove(long k) {
return backing.remove(k);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LongSet that)) return false;
if (size() != that.size()) return false;
return containsAll(that);
}
@Override
public int hashCode() {
return backing.hashCode();
}
@Override
public String toString() {
return backing.toString();
}
static class WrappingLongIterator implements LongIterator {
private final Iterator<Long> backing;
WrappingLongIterator(Iterator<Long> backing) {
this.backing = Objects.requireNonNull(backing);
}
@Override
public boolean hasNext() {
return backing.hasNext();
}
@Override
public long nextLong() {
return backing.next();
}
@Override
public Long next() {
return backing.next();
}
@Override
public void remove() {
backing.remove();
}
}
}