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:
@@ -9,10 +9,10 @@ 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
|
||||
index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..7ad36864d700f3012a3791bf3814465e5e9deffa 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 {
|
||||
@@ -94,21 +94,18 @@ public class AcquirePoi {
|
||||
}
|
||||
}
|
||||
// Paper end - optimise POI access
|
||||
@@ -27,38 +27,37 @@ index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2
|
||||
- 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<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);
|
||||
+ });
|
||||
+ processPath(acquirablePois, entityEventId, (Long2ObjectMap<JitteredLinearRetry>) map, memoryAccessor, level, mob, time, poiManager, set, path);
|
||||
});
|
||||
} else {
|
||||
- for (Pair<Holder<PoiType>, BlockPos> pair : set) {
|
||||
- map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time));
|
||||
- }
|
||||
+ 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()) {
|
||||
+ BlockPos target = path.getTarget();
|
||||
+ poiManager.getType(target).ifPresent(holder -> {
|
||||
@@ -70,14 +69,15 @@ index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2
|
||||
+ });
|
||||
+ } else {
|
||||
+ 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
|
||||
|
||||
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
|
||||
index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac99260b9ad 100644
|
||||
--- 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) {
|
||||
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
|
||||
index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..1e9383442d0a0adf4bf621fc35108b35b1375bc7 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 {
|
||||
@@ -60,17 +60,18 @@ public class SetClosestHomeAsWalkTarget {
|
||||
poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -236,26 +236,38 @@ index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c
|
||||
- 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());
|
||||
+ // 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<Holder<PoiType>> optional2 = poiManager.getType(blockPos);
|
||||
+ if (optional2.isPresent()) {
|
||||
+ walkTarget.set(new WalkTarget(blockPos, speedModifier, 1));
|
||||
+ DebugPackets.sendPoiTicketCountPacket(level, blockPos);
|
||||
+ }
|
||||
+ processPath(speedModifier, map, mutableLong, walkTarget, level, poiManager, mutableInt, path);
|
||||
+ });
|
||||
+ } else {
|
||||
+ 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()) {
|
||||
+ BlockPos target = path.getTarget();
|
||||
+ Optional<Holder<PoiType>> type = poiManager.getType(target);
|
||||
@@ -265,14 +277,10 @@ index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..5e4e4ab9b50fcddfd022dbab15620c9c
|
||||
+ }
|
||||
+ } 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
|
||||
@@ -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
|
||||
index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..3a8b3c71f6fd0c0f57a3190dc8a0f808c6d1002b 100644
|
||||
index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..9494dd144ff1115c9f0862614ef618035ee2565f 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<Mob> {
|
||||
@@ -57,17 +57,32 @@ public class NearestBedSensor extends Sensor<Mob> {
|
||||
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
|
||||
// don't ask me why it's unbounded. ask mojang.
|
||||
io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 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
|
||||
- // Paper end - optimise POI access
|
||||
- if (path != null && path.canReach()) {
|
||||
- BlockPos target = path.getTarget();
|
||||
- Optional<Holder<PoiType>> 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);
|
||||
+ // 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<Holder<PoiType>> optional = poiManager.getType(blockPos);
|
||||
+ if (optional.isPresent()) {
|
||||
+ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, blockPos);
|
||||
+ }
|
||||
+ processPath(entity, poiManager, path);
|
||||
+ });
|
||||
+ } else {
|
||||
+ 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()) {
|
||||
+ BlockPos target = path.getTarget();
|
||||
+ Optional<Holder<PoiType>> 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
|
||||
|
||||
@@ -4,8 +4,25 @@ Date: Tue, 28 Jan 2025 01:18:49 +0300
|
||||
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
|
||||
index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee50c9901a 100644
|
||||
index da2921268a9aa4545a12c155a33bed9fccb4f19c..3bf5f76dd61f6d20819440b28cbf46ed8ba34670 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
|
||||
@@ -30,27 +47,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
|
||||
}
|
||||
|
||||
// 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<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
|
||||
@@ -972,6 +982,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
// Paper end - optimise entity tracker
|
||||
|
||||
protected void tick() {
|
||||
@@ -64,7 +61,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
|
||||
// Paper start - optimise entity tracker
|
||||
if (true) {
|
||||
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;
|
||||
private final int range;
|
||||
SectionPos lastSectionPos;
|
||||
@@ -77,7 +74,7 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
|
||||
|
||||
// Paper start - optimise entity tracker
|
||||
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;
|
||||
|
||||
final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
|
||||
@@ -143,29 +140,23 @@ index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee
|
||||
}
|
||||
|
||||
@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) {
|
||||
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { // DivineMC - Multithreaded tracker
|
||||
serverPlayerConnection.send(packet);
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
public void broadcastAndSend(Packet<?> packet) {
|
||||
@@ -1210,21 +1280,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
@@ -1210,21 +1265,20 @@ 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])) {
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { // DivineMC - Multithreaded tracker
|
||||
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
@@ -200,10 +191,10 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..3b4dff8867e91884b5720ca8a9cb64af
|
||||
public boolean visible = true;
|
||||
|
||||
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
|
||||
+++ b/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -110,8 +110,13 @@ public class ServerEntity {
|
||||
@@ -110,8 +110,16 @@ public class ServerEntity {
|
||||
.forEach(
|
||||
removedPassenger -> {
|
||||
if (removedPassenger instanceof ServerPlayer serverPlayer1) {
|
||||
@@ -211,59 +202,30 @@ index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..3ee43ca5c49af83a067c7ffe74d3f2bc
|
||||
- .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()));
|
||||
+ 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());
|
||||
+ 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<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
|
||||
}
|
||||
|
||||
@@ -410,12 +418,13 @@ public class ServerEntity {
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
|
||||
if (!attributesToSync.isEmpty()) {
|
||||
- // CraftBukkit start - Send scaled max health
|
||||
- if (this.entity instanceof ServerPlayer serverPlayer) {
|
||||
+ final Set<AttributeInstance> copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); // DivineMC - Multithreaded tracker
|
||||
// CraftBukkit start - Send scaled max health
|
||||
if (this.entity instanceof ServerPlayer serverPlayer) {
|
||||
- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
|
||||
- }
|
||||
- // CraftBukkit end
|
||||
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); // DivineMC - Multithreaded tracker
|
||||
}
|
||||
// CraftBukkit end
|
||||
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
|
||||
+ // DivineMC start - 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
|
||||
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); // DivineMC - Multithreaded tracker
|
||||
}
|
||||
|
||||
attributesToSync.clear();
|
||||
|
||||
@@ -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
|
||||
index 3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e..a03dced5231ca47abf5919e3eca358c16a25337b 100644
|
||||
index b6053158f5d9b6ad325ea075ab7c60f9966ba496..c7ad6bf5dd8cb8a27de3e3ed82c6eefaa36c5c08 100644
|
||||
--- a/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()) {
|
||||
Vec3 deltaMovement = this.entity.getDeltaMovement();
|
||||
|
||||
@@ -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
|
||||
index e3427e7aecfc4e1fafb38316824aa1ee50c9901a..9bca28f33aa3b3cb8964c06b2f4d2f0a591e81f5 100644
|
||||
index 3bf5f76dd61f6d20819440b28cbf46ed8ba34670..a2742f6c5af8ad128b84121727798ea249e7ef51 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
|
||||
@@ -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);
|
||||
// Paper end - Configurable entity tracking range by Y
|
||||
// CraftBukkit start - respect vanish API
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bxteam.divinemc.entity.pathfinding.PathfindTaskRejectPolicy;
|
||||
import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleyaml.configuration.comments.CommentType;
|
||||
@@ -316,33 +317,52 @@ public class DivineConfig {
|
||||
public static boolean asyncPathfinding = true;
|
||||
public static int asyncPathfindingMaxThreads = 2;
|
||||
public static int asyncPathfindingKeepalive = 60;
|
||||
public static int asyncPathfindingQueueSize = 0;
|
||||
public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL;
|
||||
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);
|
||||
asyncPathfindingQueueSize = getInt("settings.async-pathfinding.queue-size", asyncPathfindingQueueSize);
|
||||
|
||||
final int maxThreads = Runtime.getRuntime().availableProcessors();
|
||||
if (asyncPathfindingMaxThreads < 0) {
|
||||
asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1);
|
||||
asyncPathfindingMaxThreads = Math.max(maxThreads + asyncPathfindingMaxThreads, 1);
|
||||
} else if (asyncPathfindingMaxThreads == 0) {
|
||||
asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
|
||||
asyncPathfindingMaxThreads = Math.max(maxThreads / 4, 1);
|
||||
}
|
||||
|
||||
if (!asyncPathfinding) {
|
||||
asyncPathfindingMaxThreads = 0;
|
||||
} 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 multithreadedCompatModeEnabled = false;
|
||||
public static int asyncEntityTrackerMaxThreads = 1;
|
||||
public static int asyncEntityTrackerKeepalive = 60;
|
||||
public static int asyncEntityTrackerQueueSize = 0;
|
||||
private static void multithreadedTracker() {
|
||||
multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled);
|
||||
multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled);
|
||||
multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled,
|
||||
"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);
|
||||
asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.keepalive", asyncEntityTrackerKeepalive);
|
||||
asyncEntityTrackerQueueSize = getInt("settings.multithreaded-tracker.queue-size", asyncEntityTrackerQueueSize);
|
||||
|
||||
if (asyncEntityTrackerMaxThreads < 0) {
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
|
||||
@@ -353,7 +373,9 @@ public class DivineConfig {
|
||||
if (!multithreadedEnabled) {
|
||||
asyncEntityTrackerMaxThreads = 0;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,76 @@ 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.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bxteam.divinemc.DivineConfig;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
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 {
|
||||
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,
|
||||
org.bxteam.divinemc.DivineConfig.asyncPathfindingMaxThreads,
|
||||
org.bxteam.divinemc.DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
DivineConfig.asyncPathfindingMaxThreads,
|
||||
DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("DivineMC Async Pathfinding Thread - %d")
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
.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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockingQueue<Runnable> getQueueImpl() {
|
||||
final int queueCapacity = DivineConfig.asyncPathfindingQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.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;
|
||||
@@ -13,41 +12,32 @@ 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 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.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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 {
|
||||
@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() { }
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
getThreadFactory(),
|
||||
getRejectedPolicy()
|
||||
);
|
||||
|
||||
public static Executor getTrackerExecutor() {
|
||||
return trackerExecutor;
|
||||
@@ -55,7 +45,7 @@ public class MultithreadedTracker {
|
||||
|
||||
public static void tick(ChunkSystemServerLevel level) {
|
||||
try {
|
||||
if (!org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) {
|
||||
if (!DivineConfig.multithreadedCompatModeEnabled) {
|
||||
tickAsync(level);
|
||||
} else {
|
||||
tickAsyncWithCompatMode(level);
|
||||
@@ -81,7 +71,7 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
});
|
||||
@@ -103,7 +93,7 @@ public class MultithreadedTracker {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user