Compare commits

...
23 Commits
Author SHA1 Message Date
NanaChiyo0721 231bb8705b Updated Folia
Shiroha CI / build (push) Waiting to run
Shiroha CI / Event File (push) Waiting to run
2026-08-26 23:05:00 +08:00
NanaChiyo0721 6b7b8863f0 Improve "Fix region threading with entity ai data access" patch
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-23 22:59:04 +08:00
NanaChiyo0721 d52bcc8c1d Reduce conflict blocking for unnecessary situations
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-23 00:07:52 +08:00
NanaChiyo0721 6fdd8eee4a Revert "Optimize iteration in reference chunk loader"
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-22 23:20:22 +08:00
NanaChiyo0721 6f7ba53119 Optimize iteration in task queue ttl optimization
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-22 23:17:26 +08:00
NanaChiyo0721 e660a6239b Optimize iteration in reference chunk loader 2026-08-22 23:13:58 +08:00
Suisuroru c1c62df3df fix: move down container.setChanged() to fix cud
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-22 19:53:17 +08:00
Suisuroru 20c4112564 Update Folia
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-20 00:13:55 +08:00
NanaChiyo0721 87451df0c0 [ci skip] Check interval -> Scan interval 2026-08-19 11:37:47 +08:00
NanaChiyo0721 899927a935 Optimize blinear closed file cleanup logic
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-19 11:36:01 +08:00
NanaChiyo0721 0cc941be27 Fix compile err
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-17 23:55:29 +08:00
NanaChiyo0721andCreeam 2edb70257d Fixup leaves vanilla hopper patch
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
Co-Authored-By: Creeam <102713261+HaHaWTH@users.noreply.github.com>
2026-08-17 23:41:15 +08:00
NanaChiyo0721 c9f8dd4595 Annotations 2026-08-17 14:00:50 +08:00
NanaChiyo0721 9eb5c767ed Make ARW to record class 2026-08-17 13:59:46 +08:00
NanaChiyo0721 1192f2e674 Fix typo in filed name of RegionScheduleProfilerManager
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-17 13:56:38 +08:00
NanaChiyo0721 376e02f880 Do not fire teleport or respawn events anymore
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-17 12:24:18 +08:00
NanaChiyo0721 50e9fe5eba Remove config of already removed functions
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-17 01:40:19 +08:00
Suisuroru 8554d3b962 refactor: improve error handling in ProfilerCommand registration and update block argument handling
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-16 14:15:00 +08:00
Suisuroru 8331d07690 refactor code 2026-08-16 13:26:04 +08:00
NanaChiyo0721 4a865c122c Fix mis removed speed modifier in ai fixes patch
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
2026-08-16 12:48:10 +08:00
NanaChiyo0721 7545e09559 Patch diff 2026-08-16 12:41:25 +08:00
NanaChiyo0721 c92cb53b26 Per region async profiler
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
这一坨给我干的头大了()

指令交互部分是gpt写的(review了(讲真这个指令系统真乱套()))

由于async profiler的问题目前可能不支持瘟斗士
2026-08-16 01:53:49 +08:00
NanaChiyo0721 b82cbf07cf Fixup comments 2026-08-15 16:28:12 +08:00
100 changed files with 1493 additions and 153 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ release=pre
# true for push to repo, false for skip push repo, auto for detect by release value # true for push to repo, false for skip push repo, auto for detect by release value
pushRepo=auto pushRepo=auto
foliaRef=24c5c95dc45e02caff98a97ed6ffee7565523464 foliaRef=14b7fee5c866fca9a40ede4a58998fb928140f65
org.gradle.configuration-cache=true org.gradle.configuration-cache=true
org.gradle.caching=true org.gradle.caching=true
+13 -4
View File
@@ -48,22 +48,31 @@
} }
} }
val log4jPlugins = sourceSets.create("log4jPlugins") { val log4jPlugins = sourceSets.create("log4jPlugins") {
@@ -134,7 +_,14 @@ @@ -134,7 +_,7 @@
} }
dependencies { dependencies {
- implementation(project(":folia-api")) - implementation(project(":folia-api"))
+ implementation(project(":shiroha-api")) + implementation(project(":shiroha-api"))
implementation("ca.spottedleaf:leafpile:1.2.0")
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
@@ -182,6 +_,15 @@
// Spark
implementation("me.lucko:spark-api:0.1-20240720.200737-2")
implementation("me.lucko:spark-paper:1.10.177")
+ // Shiroha start - dependencies + // Shiroha start - dependencies
+ implementation("com.electronwill.night-config:toml:3.8.4") + implementation("com.electronwill.night-config:toml:3.8.4")
+ implementation("net.openhft:affinity:3.23.3") + implementation("net.openhft:affinity:3.23.3")
+ implementation("com.github.luben:zstd-jni:1.5.4-1") + implementation("com.github.luben:zstd-jni:1.5.4-1")
+ implementation("net.openhft:zero-allocation-hashing:0.16") + implementation("net.openhft:zero-allocation-hashing:0.16")
+ implementation("net.objecthunter:exp4j:0.4.8") + implementation("net.objecthunter:exp4j:0.4.8")
+ implementation("tools.profiler:async-profiler:4.5")
+ implementation("tools.profiler:jfr-converter:4.5")
+ // Shiroha end + // Shiroha end
implementation("ca.spottedleaf:leafpile:1.0.0") }
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 tasks.jar {
@@ -189,21 +_,21 @@ @@ -189,21 +_,21 @@
val git = Git(rootProject.layout.projectDirectory.path) val git = Git(rootProject.layout.projectDirectory.path)
val mcVersion = rootProject.providers.gradleProperty("mcVersion").get() val mcVersion = rootProject.providers.gradleProperty("mcVersion").get()
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Mon, 17 Aug 2026 12:14:42 +0800
Subject: [PATCH] Do not fire teleport or respawn events anymore
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index c86b1fd5e200c478d178be32791b6e8c960d47e2..626761226aaacc7b6e429026b248be980a21d749 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1931,7 +1931,7 @@ public class ServerGamePacketListenerImpl
}
PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags));
// Paper end - Teleport API
- this.cserver.getPluginManager().callEvent(event);
+ // this.cserver.getPluginManager().callEvent(event); // Shiroha - Do not fire teleport or respawn events anymore
if (event.isCancelled() || !to.equals(event.getTo())) {
relatives = Set.of(); // target pos is absolute
@@ -93,7 +93,7 @@ index 3ac52b025ac3e1a3f9135b8e593a385a847afe0f..d2c68c78d0a2f334169a92171081eeb4
+ // Shiroha end + // Shiroha end
} }
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 b40da004b5281a041ee896ae176bfb9c4660f353..32df97c0e5e886d2c7ab98506c8a950d4034b472 100644 index b40da004b5281a041ee896ae176bfb9c4660f353..5b1baab6d52647ebec5ecb48189256b3eb4f8754 100644
--- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java --- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+++ b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +++ b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
@@ -52,7 +52,7 @@ public class MoveToTargetSink extends Behavior<Mob> { @@ -52,7 +52,7 @@ public class MoveToTargetSink extends Behavior<Mob> {
@@ -114,14 +114,6 @@ index b40da004b5281a041ee896ae176bfb9c4660f353..32df97c0e5e886d2c7ab98506c8a950d
this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); this.lastTargetPos = walkTarget.getTarget().currentBlockPosition();
this.start(level, body, timestamp); this.start(level, body, timestamp);
} }
@@ -118,7 +118,6 @@ public class MoveToTargetSink extends Behavior<Mob> {
private boolean tryComputePath(final Mob body, final WalkTarget walkTarget, final long timestamp) {
BlockPos targetPos = walkTarget.getTarget().currentBlockPosition();
this.path = body.getNavigation().createPath(targetPos, 0);
- this.speedModifier = walkTarget.getSpeedModifier();
Brain<?> brain = body.getBrain();
if (this.reachedTarget(body, walkTarget)) {
brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
diff --git a/net/minecraft/world/entity/ai/behavior/PositionTracker.java b/net/minecraft/world/entity/ai/behavior/PositionTracker.java diff --git a/net/minecraft/world/entity/ai/behavior/PositionTracker.java b/net/minecraft/world/entity/ai/behavior/PositionTracker.java
index ce6cf5ecfb190428e3ef9b7dd39c98e3d27a7b9d..3eea48aad910760683e30594a9c1851aa17ce88c 100644 index ce6cf5ecfb190428e3ef9b7dd39c98e3d27a7b9d..3eea48aad910760683e30594a9c1851aa17ce88c 100644
--- a/net/minecraft/world/entity/ai/behavior/PositionTracker.java --- a/net/minecraft/world/entity/ai/behavior/PositionTracker.java
@@ -158,7 +150,7 @@ index 16017d819c28b077f732e2ef571eac179d24e323..2e248e4fcdec5830d83ecabf3df16311
if (blockState == null) return false; // Paper - Prevent sync chunk loads when villagers try to find beds if (blockState == null) return false; // Paper - Prevent sync chunk loads when villagers try to find beds
return target.pos().closerToCenterThan(body.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED); return target.pos().closerToCenterThan(body.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
diff --git a/net/minecraft/world/entity/ai/memory/MemorySlot.java b/net/minecraft/world/entity/ai/memory/MemorySlot.java diff --git a/net/minecraft/world/entity/ai/memory/MemorySlot.java b/net/minecraft/world/entity/ai/memory/MemorySlot.java
index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c824478c5 100644 index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..9f85ba205d4e7b69e56ba538c00b1be6e1a5a617 100644
--- a/net/minecraft/world/entity/ai/memory/MemorySlot.java --- a/net/minecraft/world/entity/ai/memory/MemorySlot.java
+++ b/net/minecraft/world/entity/ai/memory/MemorySlot.java +++ b/net/minecraft/world/entity/ai/memory/MemorySlot.java
@@ -13,7 +13,7 @@ public class MemorySlot<T> { @@ -13,7 +13,7 @@ public class MemorySlot<T> {
@@ -170,7 +162,7 @@ index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c
if (this.hasValue() && this.canExpire()) { if (this.hasValue() && this.canExpire()) {
if (this.hasExpired()) { if (this.hasExpired()) {
this.clear(); this.clear();
@@ -21,6 +21,41 @@ public class MemorySlot<T> { @@ -21,6 +21,57 @@ public class MemorySlot<T> {
this.timeToLive--; this.timeToLive--;
} }
} }
@@ -184,6 +176,22 @@ index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c
+ } + }
+ } + }
+ +
+ // type: list of entity
+ if (io.nanachiyo0721.shiroha.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForEntityListed && this.value instanceof java.util.List<?> list) {
+ // check first if it's matched type
+ if (!list.isEmpty() && list.getFirst() instanceof net.minecraft.world.entity.Entity) {
+ // matched, do check
+ for (Object entityInObject : list) {
+ final net.minecraft.world.entity.Entity entity = (net.minecraft.world.entity.Entity) entityInObject;
+
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ this.clear();
+ break;
+ }
+ }
+ }
+ }
+
+ // type: block_pos + // type: block_pos
+ if (io.nanachiyo0721.shiroha.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForBlockPos && this.value instanceof net.minecraft.core.BlockPos blockPos) { + if (io.nanachiyo0721.shiroha.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enabledForBlockPos && this.value instanceof net.minecraft.core.BlockPos blockPos) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ownerLevel, blockPos)) { + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ownerLevel, blockPos)) {
@@ -212,6 +220,59 @@ index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c
} }
public static <T> MemorySlot<T> create() { public static <T> MemorySlot<T> create() {
diff --git a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
index 5c967b55f6ad3b660e9cdf74fadd90c5ea67afb9..7af28b58afa6538aa47fc25da4f3b911942b7c5a 100644
--- a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
+++ b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
@@ -39,7 +39,7 @@ public class NearestVisibleLivingEntities {
public Optional<LivingEntity> findClosest(final Predicate<LivingEntity> filter) {
for (LivingEntity nearbyEntity : this.nearbyEntities) {
- if (filter.test(nearbyEntity) && this.lineOfSightTest.test(nearbyEntity)) {
+ if (this.getExtraFilterForThreadCheck(filter).test(nearbyEntity) && this.lineOfSightTest.test(nearbyEntity)) { // Shiroha - Fix region threading with entity ai data access (one side is enough for thread checking)
return Optional.of(nearbyEntity);
}
}
@@ -48,24 +48,35 @@ public class NearestVisibleLivingEntities {
}
public Iterable<LivingEntity> findAll(final Predicate<LivingEntity> filter) {
- return Iterables.filter(this.nearbyEntities, entity -> filter.test(entity) && this.lineOfSightTest.test(entity));
+ return Iterables.filter(this.nearbyEntities, entity -> this.getExtraFilterForThreadCheck(filter).test(entity) && this.lineOfSightTest.test(entity)); // Shiroha - Fix region threading with entity ai data access (one side is enough for thread checking)
}
public Stream<LivingEntity> find(final Predicate<LivingEntity> filter) {
- return this.nearbyEntities.stream().filter(entity -> filter.test(entity) && this.lineOfSightTest.test(entity));
+ return this.nearbyEntities.stream().filter(entity -> this.getExtraFilterForThreadCheck(filter).test(entity) && this.lineOfSightTest.test(entity)); // Shiroha - Fix region threading with entity ai data access (one side is enough for thread checking)
}
public boolean contains(final LivingEntity targetEntity) {
- return this.nearbyEntities.contains(targetEntity) && this.lineOfSightTest.test(targetEntity);
+ return this.nearbyEntities.contains(targetEntity) && this.getExtraFilterForThreadCheck(this.lineOfSightTest).test(targetEntity); // Shiroha - Fix region threading with entity ai data access
}
public boolean contains(final Predicate<LivingEntity> filter) {
for (LivingEntity nearbyEntity : this.nearbyEntities) {
- if (filter.test(nearbyEntity) && this.lineOfSightTest.test(nearbyEntity)) {
+ if (this.getExtraFilterForThreadCheck(filter).test(nearbyEntity) && this.lineOfSightTest.test(nearbyEntity)) { // Shiroha - Fix region threading with entity ai data access (one side is enough for thread checking)
return true;
}
}
return false;
}
+ // Shiroha start - Fix region threading with entity ai data access
+ private Predicate<LivingEntity> getExtraFilterForThreadCheck(Predicate<LivingEntity> original) {
+ return io.nanachiyo0721.shiroha.config.modules.fixes.ForceCleanupEntityBrainMemoryConfig.enableForNearestLivingEntities ? ent -> {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ent)) {
+ return false;
+ }
+
+ return original.test(ent);
+ } : original;
+ }
+ // Shiroha end - Fix region threading with entity ai data access
}
diff --git a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java diff --git a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
index e44814cfb6afb594456b8215bd13a92e93de2c85..d4b65f13c137495d436b7e8133e0ce09ee2e08c0 100644 index e44814cfb6afb594456b8215bd13a92e93de2c85..d4b65f13c137495d436b7e8133e0ce09ee2e08c0 100644
--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java --- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
@@ -5,7 +5,7 @@ Subject: [PATCH] RegionizedTaskQueue queue TTL optimization
diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f791d49917 100644 index 037697e6364c73057b1fbac354bd78d8bf35bc7b..3a7e4b57dc41030295f5e9da58a0fcd1270c521f 100644
--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java --- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong; @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong;
@@ -16,7 +16,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
public PrioritisedExecutor.PrioritisedTask createChunkTask(final ServerLevel world, final int chunkX, final int chunkZ, public PrioritisedExecutor.PrioritisedTask createChunkTask(final ServerLevel world, final int chunkX, final int chunkZ,
final Runnable run) { final Runnable run) {
@@ -155,6 +156,58 @@ public final class RegionizedTaskQueue { @@ -155,6 +156,68 @@ public final class RegionizedTaskQueue {
} }
} }
@@ -30,41 +30,51 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
+ +
+ final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1]; + final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1];
+ +
+ for (ConcurrentChainedLong2ReferenceHashTable.TableEntry<ReferenceCountData> counterEntry : this.referenceCounters.entrySet()) { + final it.unimi.dsi.fastutil.longs.LongIterator sectionsIterator = currentRegion.getOwnedSectionsUnsynchronised();
+ final long coord = counterEntry.getKey(); + while (sectionsIterator.hasNext()) {
+ final ReferenceCountData counterData = counterEntry.getValue(); + final long sectionKey = sectionsIterator.nextLong();
+ final int sectionX = CoordinateUtils.getChunkX(sectionKey);
+ final int sectionZ = CoordinateUtils.getChunkZ(sectionKey);
+ +
+ // only tick for our region + final int chunkX = sectionX << this.world.regioniser.sectionChunkShift;
+ if (currentRegion == this.world.regioniser.getRegionAtUnsynchronised(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord))) { + final int chunkZ = sectionZ << this.world.regioniser.sectionChunkShift;
+ long curr = counterData.referenceTTL.get(); + final long coord = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+ // successfully decreased ttl + final ReferenceCountData counterData = this.referenceCounters.get(coord);
+ if (curr == (curr = counterData.referenceTTL.compareAndExchange(curr, curr - 1))) {
+ if (counterData.referenceCount.get() != 0L) {
+ // still has reference, pump back
+ counterData.referenceTTL.set(QUEUE_MAX_TTL_TICKS);
+ continue;
+ }
+ +
+ // dead + // removed
+ if (curr <= 0) { + if (counterData == null) {
+ // parsed from decrementReference + continue;
+ this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> { + }
+ // might be increased again
+ if (valueInMap.referenceCount.get() != 0L) {
+ valueInMap.referenceTTL.set(QUEUE_MAX_TTL_TICKS); // still has reference, pump back
+ return valueInMap; // directly, the ttl was already charged in add logic
+ }
+ +
+ // note: valueInMap may not be referenceCountData + // do ttl
+ toRemoveTicket[0] = valueInMap; + long curr = counterData.referenceTTL.get();
+ // successfully decreased ttl
+ if (curr == (curr = counterData.referenceTTL.compareAndExchange(curr, curr - 1))) {
+ if (counterData.referenceCount.get() != 0L) {
+ // still has reference, pump back
+ counterData.referenceTTL.set(QUEUE_MAX_TTL_TICKS);
+ continue;
+ }
+ +
+ return null; + // dead
+ }); + if (curr <= 0) {
+ + // parsed from decrementReference
+ if (toRemoveTicket[0] != null) { + this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> {
+ this.removeTicket(coord, toRemoveTicket[0].id); + // might be increased again
+ toRemoveTicket[0] = null; + if (valueInMap.referenceCount.get() != 0L) {
+ valueInMap.referenceTTL.set(QUEUE_MAX_TTL_TICKS); // still has reference, pump back
+ return valueInMap; // directly, the ttl was already charged in add logic
+ } + }
+
+ // note: valueInMap may not be referenceCountData
+ toRemoveTicket[0] = valueInMap;
+
+ return null;
+ });
+
+ if (toRemoveTicket[0] != null) {
+ this.removeTicket(coord, toRemoveTicket[0].id);
+ toRemoveTicket[0] = null;
+ } + }
+ } + }
+ } + }
@@ -75,7 +85,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
private void decrementReference(final ReferenceCountData referenceCountData, final long coord) { private void decrementReference(final ReferenceCountData referenceCountData, final long coord) {
if (!referenceCountData.decreaseReferenceCount()) { if (!referenceCountData.decreaseReferenceCount()) {
return; return;
@@ -212,9 +265,10 @@ public final class RegionizedTaskQueue { @@ -212,9 +275,10 @@ public final class RegionizedTaskQueue {
private final long id = ID_GENERATOR.getAndIncrement(); private final long id = ID_GENERATOR.getAndIncrement();
public final AtomicLong referenceCount = new AtomicLong(1L); public final AtomicLong referenceCount = new AtomicLong(1L);
@@ -87,7 +97,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
public boolean addCount() { public boolean addCount() {
int failures = 0; int failures = 0;
for (long curr = this.referenceCount.get();;) { for (long curr = this.referenceCount.get();;) {
@@ -227,6 +281,27 @@ public final class RegionizedTaskQueue { @@ -227,6 +291,27 @@ public final class RegionizedTaskQueue {
} }
if (curr == (curr = this.referenceCount.compareAndExchange(curr, curr + 1L))) { if (curr == (curr = this.referenceCount.compareAndExchange(curr, curr + 1L))) {
@@ -115,7 +125,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
return true; return true;
} }
@@ -234,11 +309,11 @@ public final class RegionizedTaskQueue { @@ -234,11 +319,11 @@ public final class RegionizedTaskQueue {
} }
} }
@@ -0,0 +1,122 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Sat, 22 Aug 2026 23:28:32 +0800
Subject: [PATCH] Reduce conflict blocking for unnecessary situations in ticket
update processing
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
index 352325aeea9e4f797893911d81703262b198ecd5..b4184b794c1e2a152531cffba493edc455399ff5 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
@@ -1566,7 +1566,12 @@ public final class ChunkHolderManager {
BLOCK_TICKET_UPDATES.set(before);
}
+ // Shiroha start - Reduce conflict blocking for unnecessary situations
public boolean processTicketUpdates() {
+ return processTicketUpdates(true);
+ }
+ // Shiroha end - Reduce conflict blocking for unnecessary situations
+ public boolean processTicketUpdates(boolean blockForUnacquirable) { // Shiroha - Reduce conflict blocking for unnecessary situations
final ca.spottedleaf.leafprofiler.RegionizedProfiler.Handle profiler = io.papermc.paper.threadedregions.TickRegionScheduler.getProfiler(); profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.TICKET_LEVEL_UPDATE_PROCESSING); try { // Folia - profiler
if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) {
throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager");
@@ -1587,7 +1592,7 @@ public final class ChunkHolderManager {
try {
ret |= this.ticketLevelPropagator.performUpdates(
this.ticketLockArea, this.taskScheduler.schedulingLockArea,
- scheduledTasks, changedFullStatus
+ scheduledTasks, changedFullStatus, blockForUnacquirable // Shiroha - Reduce conflict blocking for unnecessary situations
);
} finally {
this.unblockTicketUpdates(Boolean.FALSE);
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
index 3922616c82a9a38fb51038cd4f4c10d7921b649a..d88e7a9d681554e264429226bd695a1ce75b3876 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
@@ -339,8 +339,15 @@ public abstract class ThreadedTicketLevelPropagator {
return ret;
}
+ // Shiroha start - Reduce conflict blocking for unnecessary situations
public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock,
final List<ChunkProgressionTask> scheduledTasks, final List<NewChunkHolder> changedFullStatus) {
+ return this.performUpdates(ticketLock, schedulingLock, scheduledTasks, changedFullStatus, true);
+ }
+ // Shiroha end - Reduce conflict blocking for unnecessary situations
+
+ public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock,
+ final List<ChunkProgressionTask> scheduledTasks, final List<NewChunkHolder> changedFullStatus, boolean blockForUnacquirableNode) { // Shiroha - Reduce conflict blocking for unnecessary situations
if (this.updateQueue.isEmpty()) {
return false;
}
@@ -351,9 +358,9 @@ public abstract class ThreadedTicketLevelPropagator {
Propagator propagator = null;
for (;;) {
- final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextOrWait(maxOrder);
+ final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextOrWait(maxOrder, blockForUnacquirableNode); // Shiroha - Reduce conflict blocking for unnecessary situations
if (toUpdate == null) {
- if (!this.updateQueue.hasRemainingUpdates(maxOrder)) {
+ if (!this.updateQueue.hasRemainingUpdates(maxOrder) || !blockForUnacquirableNode) { // Shiroha - Reduce conflict blocking for unnecessary situations
if (propagator != null) {
Propagator.returnPropagator(propagator);
}
@@ -467,7 +474,7 @@ public abstract class ThreadedTicketLevelPropagator {
}
}
- public UpdateQueueNode acquireNextOrWait(final long maxOrder) {
+ public UpdateQueueNode acquireNextOrWait(final long maxOrder, boolean blockForUnacquirable) { // Shiroha - Reduce conflict blocking for unnecessary situations
final List<UpdateQueueNode> blocking = new ArrayList<>();
node_search:
@@ -497,7 +504,7 @@ public abstract class ThreadedTicketLevelPropagator {
return curr;
}
- if (!blocking.isEmpty()) {
+ if (!blocking.isEmpty() && blockForUnacquirable) { // Shiroha - Reduce conflict blocking for unnecessary situations
await(blocking.get(0));
}
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
index aa575f3b76ef70ffb9f0410e7e5cfe7af384bfbd..4b8287ab2c674b773c5ed254f5c6e72ece851684 100644
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -371,7 +371,7 @@ public final class RegionizedServer {
world.updateTickData();
- world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // required to eventually process ticket updates
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(false); // required to eventually process ticket updates // Shiroha - Reduce conflict blocking for unnecessary situations
this.autoSaveMaps(world);
}
diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
index 3a7e4b57dc41030295f5e9da58a0fcd1270c521f..863421cedc33204a60ac77af30b67cca8a268dea 100644
--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
@@ -386,7 +386,7 @@ public final class RegionizedTaskQueue {
if (processedChunkTask) { // Shiroha - Fix wrong ticket update determination in RegionizedTaskQueue
// if we executed chunk tasks, we should try to process ticket updates for full status changes
- this.worldRegionTaskData.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates();
+ this.worldRegionTaskData.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(false); // Shiroha - Reduce conflict blocking for unnecessary situations
}
}
diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java
index ab94ea7f18799d9dd3cf164a1332db69f40a9278..ca24971a1dc7576e6f216dfc0fe56d2e5195a14f 100644
--- a/io/papermc/paper/threadedregions/TickRegions.java
+++ b/io/papermc/paper/threadedregions/TickRegions.java
@@ -508,7 +508,7 @@ public final class TickRegions implements ThreadedRegionizer.RegionCallbacks<Tic
if (processedChunkTask) {
// if we processed any chunk tasks, try to process ticket level updates for full status changes
- this.region.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates();
+ this.region.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(false); // Shiroha - Reduce conflict blocking for unnecessary situations
}
} finally { profiler.stopInBetweenTick(); } // Folia - profiler
}
@@ -171,7 +171,7 @@ index 0485ce2a56adedc200cc0cd441df7cce88da66a8..344d2aa66f1fb720ca7abcc426ef438e
@Override @Override
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index c86b1fd5e200c478d178be32791b6e8c960d47e2..862a764889ed9ca985afdf672aebeb342c18b0c2 100644 index 626761226aaacc7b6e429026b248be980a21d749..0bcc7c157b32cdef930c195def8f6be77fddec0a 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2898,7 +2898,13 @@ public class ServerGamePacketListenerImpl @@ -2898,7 +2898,13 @@ public class ServerGamePacketListenerImpl
@@ -8,7 +8,7 @@ is for Anarchy servers or Crystal PVP servers this allows them to pvp
without stopping the item mid animation. without stopping the item mid animation.
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 862a764889ed9ca985afdf672aebeb342c18b0c2..acb148f14593bf8a468a4623b68ba1c7935b5ba6 100644 index 0bcc7c157b32cdef930c195def8f6be77fddec0a..4f2ea3d0a3e15dd78bd4cece163d37bf773958bd 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2183,7 +2183,9 @@ public class ServerGamePacketListenerImpl @@ -2183,7 +2183,9 @@ public class ServerGamePacketListenerImpl
@@ -6,7 +6,7 @@ Subject: [PATCH] Add config to enable tick command
only freeze/unfreeze/step/query can run when enabled only freeze/unfreeze/step/query can run when enabled
diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java
index aa575f3b76ef70ffb9f0410e7e5cfe7af384bfbd..5f6d6533067863d609484d1463fe4e64ff5f683c 100644 index 4b8287ab2c674b773c5ed254f5c6e72ece851684..feb21d91691e849c15976ff1f9482ad1049c4ab8 100644
--- a/io/papermc/paper/threadedregions/RegionizedServer.java --- a/io/papermc/paper/threadedregions/RegionizedServer.java
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java +++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -233,6 +233,11 @@ public final class RegionizedServer { @@ -233,6 +233,11 @@ public final class RegionizedServer {
@@ -1,15 +1,17 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com> From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Thu, 9 Jul 2026 11:54:24 +0800 Date: Mon, 17 Aug 2026 23:38:00 +0800
Subject: [PATCH] Leaves Vanilla Hopper Subject: [PATCH] Leaves Vanilla hopper
A part from leaves Co-Authored-By: Creeam <102713261+HaHaWTH@users.noreply.github.com>
Origin patch link: https://github.com/LeavesMC/Leaves/blob/master/leaves-server/minecraft-patches/features/0092-Vanilla-hopper.patch Original license: GPL-3.0-only
Origin license: https://github.com/LeavesMC/Leaves/blob/master/LICENSE.md Original project: https://github.com/LeavesMC/Leaves
This is a temporary solution designed to attempt to restore the vanilla behavior of the funnel while preserving optimizations as much as possible. It should ultimately be replaced by the optimization solution provided by lithium.
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08a5200c95 100644 index a858cb658af70f07614fa1f4d9e8a3435d5c161f..5ab92e215c41f02c2a8260071f64dddae35dd15f 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -281,36 +281,67 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @@ -281,36 +281,67 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -18,7 +20,24 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount);
- container.setChanged(); // original logic always marks source inv as changed even if no move happens. - container.setChanged(); // original logic always marks source inv as changed even if no move happens.
- movedItem.setCount(movedItemCount); - movedItem.setCount(movedItemCount);
- + // Leaves start - Vanilla hopper
+ if (movedItemCount == 1) {
+ movedItem.setCount(movedItemCount);
+ if (!worldData.skipPullModeEventFire) {
+ movedItem = callPullMoveEvent(hopper, container, movedItem);
+ if (movedItem == null) { // cancelled
+ origItemStack.setCount(originalItemCount);
+ // Drastically improve performance by returning true.
+ // No plugin could have relied on the behavior of false as the other call
+ // site for IMIE did not exhibit the same behavior
+ return true;
+ }
+ }
+ final boolean removeOriginalItem = movedItem == origItemStack;
+ if (removeOriginalItem) {
+ movedItem = container.removeItem(i, movedItemCount);
+ }
- if (!worldData.skipPullModeEventFire) { // Folia - region threading - if (!worldData.skipPullModeEventFire) { // Folia - region threading
- movedItem = callPullMoveEvent(hopper, container, movedItem); - movedItem = callPullMoveEvent(hopper, container, movedItem);
- if (movedItem == null) { // cancelled - if (movedItem == null) { // cancelled
@@ -26,20 +45,11 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
- // Drastically improve performance by returning true. - // Drastically improve performance by returning true.
- // No plugin could have relied on the behavior of false as the other call - // No plugin could have relied on the behavior of false as the other call
- // site for IMIE did not exhibit the same behavior - // site for IMIE did not exhibit the same behavior
+ // Leaves start - fix vanilla hopper + final int itemCountToMove = movedItem.getCount();
+ if (movedItem.getCount() <= movedItemCount) { + final ItemStack remainingItem = addItem(container, hopper, movedItem, null);
+ if (!worldData.skipPullModeEventFire) { + if (remainingItem.getCount() != itemCountToMove) {
+ movedItem = callPullMoveEvent(hopper, container, movedItem); + origItemStack.setCount(removeOriginalItem ? originalItemCount - movedItemCount : originalItemCount);
+ if (movedItem == null) { // cancelled + container.setItem(i, origItemStack);
+ origItemStack.setCount(originalItemCount);
+ container.setChanged(); // keep parity with vanilla: source inv always marked changed
+ return true;
+ }
+ }
+ final int toRemove = Math.min(movedItem.getCount(), container.getItem(i).getCount());
+ final ItemStack remainingItem = addItem(container, hopper, container.removeItem(i, toRemove), null);
+ final int remainingItemCount = remainingItem.getCount();
+ if (remainingItem.isEmpty()) {
+ container.setChanged(); + container.setChanged();
return true; return true;
} }
@@ -49,23 +59,10 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
- final int remainingItemCount = remainingItem.getCount(); - final int remainingItemCount = remainingItem.getCount();
- if (remainingItemCount != movedItemCount) { - if (remainingItemCount != movedItemCount) {
- origItemStack = origItemStack.copy(true); - origItemStack = origItemStack.copy(true);
- origItemStack.setCount(originalItemCount); origItemStack.setCount(originalItemCount);
- if (!origItemStack.isEmpty()) { - if (!origItemStack.isEmpty()) {
- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); - origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
+ if (remainingItemCount != toRemove) { + container.setItem(i, origItemStack);
+ IGNORE_TILE_UPDATES.set(true); // Folia - region threading
+ container.setItem(i, remainingItem);
+ IGNORE_TILE_UPDATES.set(false); // Folia - region threading
+ container.setChanged();
+ return true;
}
-
IGNORE_TILE_UPDATES.set(true); // Folia - region threading
- container.setItem(i, origItemStack);
+ container.setItem(i, remainingItem);
IGNORE_TILE_UPDATES.set(false); // Folia - region threading
container.setChanged();
- return true;
+ } else { + } else {
+ container.setChanged(); // original logic always marks source inv as changed even if no move happens. + container.setChanged(); // original logic always marks source inv as changed even if no move happens.
+ movedItem.setCount(movedItemCount); + movedItem.setCount(movedItemCount);
@@ -79,8 +76,13 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
+ // site for IMIE did not exhibit the same behavior + // site for IMIE did not exhibit the same behavior
+ return true; + return true;
+ } + }
+ } }
+
- IGNORE_TILE_UPDATES.set(true); // Folia - region threading
- container.setItem(i, origItemStack);
- IGNORE_TILE_UPDATES.set(false); // Folia - region threading
- container.setChanged();
- return true;
+ final ItemStack remainingItem = addItem(container, hopper, movedItem, null); + final ItemStack remainingItem = addItem(container, hopper, movedItem, null);
+ final int remainingItemCount = remainingItem.getCount(); + final int remainingItemCount = remainingItem.getCount();
+ if (remainingItemCount != movedItemCount) { + if (remainingItemCount != movedItemCount) {
@@ -90,16 +92,16 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
+ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); + origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
+ } + }
+ +
+ IGNORE_TILE_UPDATES.set(true); + IGNORE_TILE_UPDATES.set(true); // Folia - region threading
+ container.setItem(i, origItemStack); + container.setItem(i, origItemStack);
+ IGNORE_TILE_UPDATES.set(false); + IGNORE_TILE_UPDATES.set(false); // Folia - region threading
+ container.setChanged(); + container.setChanged();
+ return true; + return true;
+ } + }
+ origItemStack.setCount(originalItemCount); + origItemStack.setCount(originalItemCount);
} }
- origItemStack.setCount(originalItemCount); - origItemStack.setCount(originalItemCount);
+ // Leaves end - fix vanilla hopper + // Leaves end - Vanilla hopper
if (level.paperConfig().hopper.cooldownWhenFull) { if (level.paperConfig().hopper.cooldownWhenFull) {
applyCooldown(hopper); applyCooldown(hopper);
@@ -1573,7 +1573,7 @@ index 6109d8da984b398bb1eb6cd6ab9a55f0511a615c..d3d6b3aeb24da7f3bf92c327095a9dc9
+ // Leaves end - Lithium Sleeping Block Entity + // Leaves end - Lithium Sleeping Block Entity
} }
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 95f0b3276137cb9d1fc296e97cc25a08a5200c95..26afe1f74b50b60d6c04e08967610a3d50183483 100644 index 5ab92e215c41f02c2a8260071f64dddae35dd15f..a847bcf24b7ac1ed702027b43438e28037daba84 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -27,8 +27,29 @@ import net.minecraft.world.level.storage.ValueInput; @@ -27,8 +27,29 @@ import net.minecraft.world.level.storage.ValueInput;
@@ -21,7 +21,7 @@ index cc9459273762cde3e6b87a7eaf225607f527315c..ddcb9370b316441a78e95c19bc34812e
synchronized (PACKET_LIMIT_LOCK) { synchronized (PACKET_LIMIT_LOCK) {
if (this.allPacketCounts != null) { if (this.allPacketCounts != null) {
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index acb148f14593bf8a468a4623b68ba1c7935b5ba6..be8d2a76ec1b93f6110f2a65c701bcbdc3dfe79d 100644 index 4f2ea3d0a3e15dd78bd4cece163d37bf773958bd..85c74dd76c34a816ddb1f84fa572d4396bfda9fe 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -890,7 +890,7 @@ public class ServerGamePacketListenerImpl @@ -890,7 +890,7 @@ public class ServerGamePacketListenerImpl
@@ -8,7 +8,7 @@ As part of: Leaves (https://github.com/LeavesMC/Leaves/blob/c5f18b7864206cea4411
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
index 8d2518600ad518999b75124f0a87db9efe541f2e..d66e65fdee6117b0da7368a3ef9c3ae063974afa 100644 index bc91b87da9c8034fc142007bfb0be4927bd958d6..a299de892feb1b0c37ba3d7e2a63e20076fcc1e2 100644
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java --- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -101,6 +101,14 @@ public final class CollisionUtil { @@ -101,6 +101,14 @@ public final class CollisionUtil {
@@ -35,7 +35,7 @@ index 8d2518600ad518999b75124f0a87db9efe541f2e..d66e65fdee6117b0da7368a3ef9c3ae0
continue; continue;
} }
} }
@@ -2087,6 +2095,18 @@ public final class CollisionUtil { @@ -2088,6 +2096,18 @@ public final class CollisionUtil {
return ret; return ret;
} }
@@ -150,7 +150,7 @@ index 306bac1feb35b2957a6acd2b394bdd3d500e6191..f169316ddef764a43ea38d8cdc017004
this.getInventory().equipment.set(value, net.minecraft.world.item.ItemStack.EMPTY); this.getInventory().equipment.set(value, net.minecraft.world.item.ItemStack.EMPTY);
} }
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index be8d2a76ec1b93f6110f2a65c701bcbdc3dfe79d..b7f80b325ff796970f43dbc3ce47a811b140440d 100644 index 85c74dd76c34a816ddb1f84fa572d4396bfda9fe..5cb1d887ed7f436d2eaf9ed4249df6d5bc83304b 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3023,7 +3023,7 @@ public class ServerGamePacketListenerImpl @@ -3023,7 +3023,7 @@ public class ServerGamePacketListenerImpl
@@ -0,0 +1,83 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Sun, 16 Aug 2026 01:31:31 +0800
Subject: [PATCH] Per region async profiler
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index 724332d074bd7d3dfdcb60e83cf1fd6ce706000e..eb65077d18d86fce3ae1131445ebdcb736f5dc93 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -220,6 +220,7 @@ public final class TickRegionScheduler {
*/
public void scheduleRegion(final RegionScheduleHandle region) {
region.scheduler = this;
+ region.scheduleProfiler = TickRegions.getScheduleProfilerManager().register(region.id); // Shiroha - per region async profiler
this.scheduler.schedule(region);
}
@@ -230,6 +231,7 @@ public final class TickRegionScheduler {
public void descheduleRegion(final RegionScheduleHandle region) {
// To avoid acquiring any of the locks the scheduler may be using, we
// simply cancel the next action.
+ TickRegions.getScheduleProfilerManager().deRegister(region.id); region.scheduleProfiler = null; // Shiroha - per region async profiler
region.markNonSchedulable();
}
@@ -296,6 +298,7 @@ public final class TickRegionScheduler {
}
public static abstract class RegionScheduleHandle extends SchedulableTick {
+ public io.nanachiyo0721.shiroha.utils.profiling.RegionScheduleProfiler scheduleProfiler = null; // Shiroha - per region async profiler
protected long currentTick;
protected long lastTickStart;
@@ -391,6 +394,7 @@ public final class TickRegionScheduler {
final FoliaWatchdogThread.RunningTick runningTick = new FoliaWatchdogThread.RunningTick(tickStart, this, Thread.currentThread()); // Folia - watchdog
WATCHDOG_THREAD.addTick(runningTick); // Folia - watchdog
+ if (this.scheduleProfiler != null) this.scheduleProfiler.taskBegin(); // Shiroha - per region async profiler
try {
this.runRegionTasks(() -> {
return !RegionScheduleHandle.this.cancelled.get() && canContinue.getAsBoolean();
@@ -400,6 +404,7 @@ public final class TickRegionScheduler {
// don't release region for another tick
return false;
} finally {
+ if (this.scheduleProfiler != null) this.scheduleProfiler.taskEnd(); // Shiroha - per region async profiler
WATCHDOG_THREAD.removeTick(runningTick); // Folia - watchdog
final long tickEnd = System.nanoTime();
final long cpuEnd = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
@@ -476,6 +481,7 @@ public final class TickRegionScheduler {
final FoliaWatchdogThread.RunningTick runningTick = new FoliaWatchdogThread.RunningTick(tickStart, this, Thread.currentThread()); // Folia - region threading
WATCHDOG_THREAD.addTick(runningTick); // Folia - region threading
+ if (this.scheduleProfiler != null) this.scheduleProfiler.tickBegin(); // Shiroha - per region async profiler
try {
// next start isn't updated until the end of this tick
this.tickRegion(tickCount, tickStart, scheduledEnd);
@@ -489,6 +495,7 @@ public final class TickRegionScheduler {
// regionFailed will schedule a shutdown, so we should avoid letting this region tick further
return false;
} finally {
+ if (this.scheduleProfiler != null) this.scheduleProfiler.tickEnd(); // Shiroha - per region async profiler
WATCHDOG_THREAD.removeTick(runningTick); // Folia - region threading
final long tickEnd = System.nanoTime();
final long cpuEnd = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java
index ca24971a1dc7576e6f216dfc0fe56d2e5195a14f..aacf39922a7e338351a7fe89956162ba736d2cc2 100644
--- a/io/papermc/paper/threadedregions/TickRegions.java
+++ b/io/papermc/paper/threadedregions/TickRegions.java
@@ -36,10 +36,12 @@ public final class TickRegions implements ThreadedRegionizer.RegionCallbacks<Tic
private static boolean initialised;
private static boolean started;
private static TickRegionScheduler scheduler;
+ private static final io.nanachiyo0721.shiroha.utils.profiling.RegionScheduleProfilerManager scheduleProfilerManager = new io.nanachiyo0721.shiroha.utils.profiling.RegionScheduleProfilerManager(); // Shiroha - per region async profiler
public static TickRegionScheduler getScheduler() {
return scheduler;
}
+ public static io.nanachiyo0721.shiroha.utils.profiling.RegionScheduleProfilerManager getScheduleProfilerManager() { return scheduleProfilerManager; } // Shiroha - per region async profiler
private static int getTickThreads(final GlobalConfiguration.ThreadedRegions config) {
int tickThreads;
@@ -1,6 +1,10 @@
package io.nanachiyo0721.shiroha.commands; package io.nanachiyo0721.shiroha.commands;
import com.mojang.logging.LogUtils;
import io.nanachiyo0721.shiroha.commands.bar.BarCommand; import io.nanachiyo0721.shiroha.commands.bar.BarCommand;
import io.nanachiyo0721.shiroha.commands.profiler.ProfilerCommand;
import io.nanachiyo0721.shiroha.config.modules.function.ProfilerConfig;
import io.papermc.paper.threadedregions.TickRegions;
public class CommandRegister { public class CommandRegister {
/** /**
@@ -10,5 +14,13 @@ public class CommandRegister {
*/ */
public static void register() { public static void register() {
new BarCommand().register(); new BarCommand().register();
if (ProfilerConfig.enabled) {
try {
TickRegions.getScheduleProfilerManager().init();
new ProfilerCommand(TickRegions.getScheduleProfilerManager()).register();
} catch (UnsupportedOperationException e) {
LogUtils.getLogger().warn("Profiler command registration failed: {}", e.getMessage());
}
}
} }
} }
@@ -0,0 +1,372 @@
package io.nanachiyo0721.shiroha.commands.profiler;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.nanachiyo0721.shiroha.enums.EnumProfilingCategory;
import io.nanachiyo0721.shiroha.enums.EnumProfilingType;
import io.nanachiyo0721.shiroha.utils.profiling.RegionScheduleProfilerManager;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.threadedregions.RegionizedServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument;
import net.minecraft.commands.arguments.coordinates.Coordinates;
import net.minecraft.core.BlockPos;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftWorld;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.LiteralNode;
import org.leavesmc.leaves.command.RootNode;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class ProfilerCommand extends RootNode {
private static final String PERM_BASE = "shiroha.commands.sprofiler";
private static final TextColor SUCCESS = TextColor.color(0, 255, 0);
private static final TextColor ERROR = TextColor.color(255, 0, 0);
private final RegionScheduleProfilerManager profilerManager;
public ProfilerCommand(RegionScheduleProfilerManager profilerManager) {
super("sprofiler", PERM_BASE);
this.profilerManager = profilerManager;
children(
StartCommand::new,
StartAtCommand::new,
StopCommand::new
);
}
public static boolean hasPermission(@NotNull CommandSender sender, String... subcommand) {
return hasPermission(PERM_BASE, sender, subcommand);
}
@Override
protected boolean execute(@NotNull CommandContext context) {
context.getSender().sendMessage(Component.text(
"Usage: /sprofiler start <type> <category> <regionId> <seconds> | "
+ "/sprofiler start-at <type> <category> <world> <blockX> <blockZ> <seconds> | "
+ "/sprofiler stop <sessionId>"
));
return true;
}
private static Throwable unwrap(Throwable throwable) {
while (throwable.getCause() != null
&& (throwable instanceof java.util.concurrent.CompletionException
|| throwable instanceof java.util.concurrent.ExecutionException)) {
throwable = throwable.getCause();
}
return throwable;
}
private static void sendMessage(CommandSender sender, Component message) {
RegionizedServer.getInstance().addTask(() -> sender.sendMessage(message));
}
private static void sendFailure(CommandSender sender, String operation, Throwable throwable) {
final Throwable cause = unwrap(throwable);
final String detail = cause.getMessage() == null ? cause.getClass().getSimpleName() : cause.getMessage();
sendMessage(sender, Component.text("Failed to " + operation + ": " + detail).color(ERROR));
}
private void startProfiling(
CommandSender sender,
String typeInput,
String categoryInput,
long regionId,
int seconds,
String targetDescription
) {
final EnumProfilingType profilingType;
final EnumProfilingCategory category;
try {
profilingType = EnumProfilingType.valueOf(typeInput.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException exception) {
sender.sendMessage(Component.text("Unknown profiling type: " + typeInput).color(ERROR));
return;
}
try {
category = EnumProfilingCategory.valueOf(categoryInput.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException exception) {
sender.sendMessage(Component.text("Unknown profiling category: " + categoryInput).color(ERROR));
return;
}
final long profilingEnd;
try {
final long durationNanos = Math.multiplyExact((long) seconds, 1_000_000_000L);
profilingEnd = Math.addExact(System.nanoTime(), durationNanos);
} catch (ArithmeticException exception) {
sender.sendMessage(Component.text("Profiling duration is too long").color(ERROR));
return;
}
this.profilerManager.startProfilingSession(profilingType, category, regionId, profilingEnd)
.whenComplete((session, startFailure) -> {
if (startFailure != null) {
sendFailure(sender, "start profiler", startFailure);
return;
}
sendMessage(sender, Component.text(
"Started profiler session " + session.sessionId()
+ " for " + targetDescription
+ " for " + seconds + " seconds"
).color(SUCCESS));
session.output().whenComplete((output, exportFailure) -> {
if (exportFailure != null) {
sendFailure(sender, "export profiler session " + session.sessionId(), exportFailure);
return;
}
if (output == null) {
sendMessage(sender, Component.text(
"Profiler session " + session.sessionId() + " ended without an output file"
).color(ERROR));
return;
}
final Path absoluteOutput = output.toAbsolutePath().normalize();
sendMessage(sender, Component.text(
"Profiler session " + session.sessionId() + " exported to " + absoluteOutput
).color(SUCCESS));
});
});
}
private final class StartCommand extends LiteralNode {
private StartCommand() {
super("start");
children(TypeArg::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return ProfilerCommand.hasPermission(source.getSender(), this.name);
}
}
private final class TypeArg extends ArgumentNode<String> {
private TypeArg() {
super("type", StringArgumentType.word());
children(CategoryArg::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(
@NotNull CommandContext context,
@NotNull SuggestionsBuilder builder
) {
Arrays.stream(EnumProfilingType.values())
.map(value -> value.name().toLowerCase(Locale.ROOT))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
private final class CategoryArg extends ArgumentNode<String> {
private CategoryArg() {
super("category", StringArgumentType.word());
children(RegionArg::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(
@NotNull CommandContext context,
@NotNull SuggestionsBuilder builder
) {
Arrays.stream(EnumProfilingCategory.values())
.map(value -> value.name().toLowerCase(Locale.ROOT))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
private final class RegionArg extends ArgumentNode<Long> {
private RegionArg() {
super("regionId", LongArgumentType.longArg(0L));
children(SecondsArg::new);
}
}
private final class SecondsArg extends ArgumentNode<Integer> {
private SecondsArg() {
super("seconds", IntegerArgumentType.integer(1));
}
@Override
protected boolean execute(@NotNull CommandContext context) {
final CommandSender sender = context.getSender();
final String typeInput = context.getArgument(TypeArg.class);
final String categoryInput = context.getArgument(CategoryArg.class);
final long regionId = context.getArgument("regionId", Long.class);
final int seconds = context.getArgument(SecondsArg.class);
startProfiling(sender, typeInput, categoryInput, regionId, seconds, "region " + regionId);
return true;
}
}
private final class StartAtCommand extends LiteralNode {
private StartAtCommand() {
super("start-at");
children(CoordinateTypeArg::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return ProfilerCommand.hasPermission(source.getSender(), this.name);
}
}
private final class CoordinateTypeArg extends ArgumentNode<String> {
private CoordinateTypeArg() {
super("coordinateType", StringArgumentType.word());
children(CoordinateCategoryArg::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(
@NotNull CommandContext context,
@NotNull SuggestionsBuilder builder
) {
Arrays.stream(EnumProfilingType.values())
.map(value -> value.name().toLowerCase(Locale.ROOT))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
private final class CoordinateCategoryArg extends ArgumentNode<String> {
private CoordinateCategoryArg() {
super("coordinateCategory", StringArgumentType.word());
children(WorldArg::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(
@NotNull CommandContext context,
@NotNull SuggestionsBuilder builder
) {
Arrays.stream(EnumProfilingCategory.values())
.map(value -> value.name().toLowerCase(Locale.ROOT))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
private final class WorldArg extends ArgumentNode<String> {
private WorldArg() {
super("world", StringArgumentType.word());
children(BlockArg::new);
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(
@NotNull CommandContext context,
@NotNull SuggestionsBuilder builder
) {
Bukkit.getWorlds().stream().map(World::getName).forEach(builder::suggest);
return builder.buildFuture();
}
}
private final class BlockArg extends ArgumentNode<Coordinates> {
private BlockArg() {
super("blockPos", ColumnPosArgument.columnPos());
children(CoordinateSecondsArg::new);
}
}
private final class CoordinateSecondsArg extends ArgumentNode<Integer> {
private CoordinateSecondsArg() {
super("coordinateSeconds", IntegerArgumentType.integer(1));
}
@Override
protected boolean execute(@NotNull CommandContext context) {
final CommandSender sender = context.getSender();
final String worldName = context.getArgument(WorldArg.class);
final BlockPos blockPos = context.getArgument(BlockArg.class).getBlockPos((net.minecraft.commands.CommandSourceStack) context.getMojangContext().getSource());
final int blockX = blockPos.getX();
final int blockZ = blockPos.getZ();
final World world = Bukkit.getWorld(worldName);
if (world == null) {
sender.sendMessage(Component.text("Unknown world: " + worldName).color(ERROR));
return true;
}
final var region = ((CraftWorld) world).getHandle().regioniser
.getRegionAtSynchronised(blockX >> 4, blockZ >> 4);
if (region == null) {
sender.sendMessage(Component.text(
"No active region at " + worldName + " " + blockX + " " + blockZ
).color(ERROR));
return true;
}
final long regionId = region.getData().getRegionSchedulingHandle().id;
final int seconds = context.getArgument(CoordinateSecondsArg.class);
startProfiling(
sender,
context.getArgument(CoordinateTypeArg.class),
context.getArgument(CoordinateCategoryArg.class),
regionId,
seconds,
"region " + regionId + " at " + worldName + " " + blockX + " " + blockZ
);
return true;
}
}
private final class StopCommand extends LiteralNode {
private StopCommand() {
super("stop");
children(SessionArg::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return ProfilerCommand.hasPermission(source.getSender(), this.name);
}
}
private final class SessionArg extends ArgumentNode<Integer> {
private SessionArg() {
super("sessionId", IntegerArgumentType.integer(1));
}
@Override
protected boolean execute(@NotNull CommandContext context) {
final CommandSender sender = context.getSender();
final int sessionId = context.getInteger("sessionId");
profilerManager.endProfilingAsync(sessionId).whenComplete((stopped, failure) -> {
if (failure != null) {
sendFailure(sender, "stop profiler session " + sessionId, failure);
} else if (stopped) {
sendMessage(sender, Component.text(
"Stopped profiler session " + sessionId + "; exporting the result"
).color(SUCCESS));
} else {
sendMessage(sender, Component.text(
"Profiler session " + sessionId + " is not active"
).color(ERROR));
}
});
return true;
}
}
}
@@ -10,9 +10,15 @@ public class ForceCleanupEntityBrainMemoryConfig implements IConfigModule {
@ConfigInfo(name = "enabled_for_entity", comments = "When enabled, the entity's brain will clean the memory which is typed of entity and not belong to current tickregion") @ConfigInfo(name = "enabled_for_entity", comments = "When enabled, the entity's brain will clean the memory which is typed of entity and not belong to current tickregion")
public static boolean enabledForEntity = false; public static boolean enabledForEntity = false;
@ConfigInfo(name = "enabled_for_entity_listed", comments = "When enabled, the entity's brain will clean the memory which is typed of list of entity and not belong to current tickregion")
public static boolean enabledForEntityListed = false;
@ConfigInfo(name = "enabled_for_block_pos", comments = "When enabled, the entity's brain will clean the memory which is typed of block_pos and not belong to current tickregion") @ConfigInfo(name = "enabled_for_block_pos", comments = "When enabled, the entity's brain will clean the memory which is typed of block_pos and not belong to current tickregion")
public static boolean enabledForBlockPos = false; public static boolean enabledForBlockPos = false;
@ConfigInfo(name = "enabled_for_position_tracker", comments = "When enabled, the entity's brain will clean the memory which is typed of position_tracker and not belong to current tickregion") @ConfigInfo(name = "enabled_for_position_tracker", comments = "When enabled, the entity's brain will clean the memory which is typed of position_tracker and not belong to current tickregion")
public static boolean enabledForPositionTracker = false; public static boolean enabledForPositionTracker = false;
@ConfigInfo(name = "enable_for_nearest_living_entities", comments = "When enabled, any entities out of current region will be ignored for memory inspection")
public static boolean enableForNearestLivingEntities = false;
} }
@@ -9,7 +9,7 @@ import io.nanachiyo0721.shiroha.enums.EnumConfigCategory;
public class POIRangeFixes implements IConfigModule { public class POIRangeFixes implements IConfigModule {
@ConfigInfo(name = "do_not_compete_poi_if_unloaded", comments = """ @ConfigInfo(name = "do_not_compete_poi_if_unloaded", comments = """
Do not compete POI if it's unloaded Do not compete POI if it's unloaded
Related with https://github.com/PaperMC/Folia/issues/292 Related with https://github.com/PaperMC/Folia/issues/292
""") """)
public static boolean doNotCompetePOIIfUnloaded = false; public static boolean doNotCompetePOIIfUnloaded = false;
} }
@@ -0,0 +1,34 @@
package io.nanachiyo0721.shiroha.config.modules.function;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import io.nanachiyo0721.shiroha.config.IConfigModule;
import io.nanachiyo0721.shiroha.config.flags.ConfigClassInfo;
import io.nanachiyo0721.shiroha.config.flags.ConfigInfo;
import io.nanachiyo0721.shiroha.config.flags.DoNotLoad;
import io.nanachiyo0721.shiroha.config.flags.HotReloadUnsupported;
import io.nanachiyo0721.shiroha.enums.EnumConfigCategory;
import io.papermc.paper.threadedregions.TickRegions;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
@ConfigClassInfo(name = "shiroha_profiler", category = EnumConfigCategory.FUNCTION)
public class ProfilerConfig implements IConfigModule {
@ConfigInfo(name = "enabled")
@HotReloadUnsupported
public static boolean enabled = false;
@DoNotLoad
private static boolean shutdownHookAttached = false;
@Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> e) {
if (enabled) {
if (!shutdownHookAttached) {
shutdownHookAttached = true;
Runtime.getRuntime().addShutdownHook(new Thread(() -> TickRegions.getScheduleProfilerManager().shutdown()));
}
}
}
}
@@ -96,22 +96,22 @@ public class CpuAffinityConfig implements IConfigModule {
if (enabledForTickRegion) { if (enabledForTickRegion) {
tickRegionRunnableWrapper = new AffinityRunnableWrapper("tick_region", parseAffinity(tickRegionAffinity)); tickRegionRunnableWrapper = new AffinityRunnableWrapper("tick_region", parseAffinity(tickRegionAffinity));
LOGGER.info("Tick region thread now bound to: {}", tickRegionRunnableWrapper.getAffinity()); LOGGER.info("Tick region thread now bound to: {}", tickRegionRunnableWrapper.affinity());
} }
if (enabledForChunkSystemIo) { if (enabledForChunkSystemIo) {
chunkSystemIoRunnableWrapper = new AffinityRunnableWrapper("chunk_system_io", parseAffinity(chunkSystemIoAffinity)); chunkSystemIoRunnableWrapper = new AffinityRunnableWrapper("chunk_system_io", parseAffinity(chunkSystemIoAffinity));
LOGGER.info("Chunk system I/O thread now bound to: {}", chunkSystemIoRunnableWrapper.getAffinity()); LOGGER.info("Chunk system I/O thread now bound to: {}", chunkSystemIoRunnableWrapper.affinity());
} }
if (enabledForChunkSystemWorker) { if (enabledForChunkSystemWorker) {
chunkSystemWorkerRunnableWrapper = new AffinityRunnableWrapper("chunk_system_worker", parseAffinity(chunkSystemWorkerAffinity)); chunkSystemWorkerRunnableWrapper = new AffinityRunnableWrapper("chunk_system_worker", parseAffinity(chunkSystemWorkerAffinity));
LOGGER.info("Chunk system worker thread now bound to: {}", chunkSystemWorkerRunnableWrapper.getAffinity()); LOGGER.info("Chunk system worker thread now bound to: {}", chunkSystemWorkerRunnableWrapper.affinity());
} }
if (enableForNettyIo) { if (enableForNettyIo) {
nettyIoRunnableWrapper = new AffinityRunnableWrapper("netty_io", parseAffinity(nettyIoAffinity)); nettyIoRunnableWrapper = new AffinityRunnableWrapper("netty_io", parseAffinity(nettyIoAffinity));
LOGGER.info("Netty I/O thread now bound to: {}", nettyIoRunnableWrapper.getAffinity()); LOGGER.info("Netty I/O thread now bound to: {}", nettyIoRunnableWrapper.affinity());
} }
} }
@@ -1,18 +0,0 @@
package io.nanachiyo0721.shiroha.config.modules.optimizations;
import io.nanachiyo0721.shiroha.config.IConfigModule;
import io.nanachiyo0721.shiroha.config.flags.ConfigClassInfo;
import io.nanachiyo0721.shiroha.config.flags.ConfigInfo;
import io.nanachiyo0721.shiroha.enums.EnumConfigCategory;
@ConfigClassInfo(name = "frustum_filtering_tracker", category = EnumConfigCategory.OPTIMIZATIONS)
public class FrustumFilteringTrackerConfig implements IConfigModule {
@ConfigInfo(name = "enabled")
public static boolean enabled = false;
@ConfigInfo(name = "min_force_visible_distance")
public static double minForceVisibleDistance = 5.0D;
@ConfigInfo(name = "camera_fov")
public static double camera_fov = 90;
@ConfigInfo(name = "aspect")
public static double aspect = 1.0;
}
@@ -459,6 +459,9 @@ public class BufferedLinearRegionFile implements io.nanachiyo0721.shiroha.data.R
return; return;
} }
// remove from flusher
this.markClosed();
IOException failure = null; IOException failure = null;
// final sync so no buffered data is lost; holding syncLock also guarantees no // final sync so no buffered data is lost; holding syncLock also guarantees no
@@ -473,8 +476,6 @@ public class BufferedLinearRegionFile implements io.nanachiyo0721.shiroha.data.R
} }
try { try {
this.markClosed();
this.swapFileChannel.close(); this.swapFileChannel.close();
} catch (IOException ex) { } catch (IOException ex) {
if (failure == null) failure = ex; if (failure == null) failure = ex;
@@ -488,9 +489,6 @@ public class BufferedLinearRegionFile implements io.nanachiyo0721.shiroha.data.R
else failure.addSuppressed(e); else failure.addSuppressed(e);
} }
// finalize
this.markClosed();
if (failure != null) { if (failure != null) {
throw failure; throw failure;
} }
@@ -20,9 +20,9 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
private final Executor ioWorkerPool; private final Executor ioWorkerPool;
private final long flushOfWriteTimeoutMs; private final long flushOfWriteTimeoutMs;
public BufferedLinearRegionFileFlusher(int nIoThreads, long checkIntervalMs, long flushOfWriteTimeoutMs) { public BufferedLinearRegionFileFlusher(int nIoThreads, long scanInterval, long flushOfWriteTimeoutMs) {
Validate.isTrue(nIoThreads > 0, "Number of I/O threads must > 0!"); Validate.isTrue(nIoThreads > 0, "Number of I/O threads must > 0!");
Validate.isTrue(checkIntervalMs > 0, "Check interval must > 0"); Validate.isTrue(scanInterval > 0, "Scan interval must > 0");
Validate.isTrue(flushOfWriteTimeoutMs > 0, "Flush of write timeout must > 0"); Validate.isTrue(flushOfWriteTimeoutMs > 0, "Flush of write timeout must > 0");
this.ioWorkerPool = Executors.newFixedThreadPool(nIoThreads, new ThreadFactoryBuilder() this.ioWorkerPool = Executors.newFixedThreadPool(nIoThreads, new ThreadFactoryBuilder()
@@ -34,7 +34,7 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
.setNameFormat("BufferedLinearRegionFile Flusher Checker") .setNameFormat("BufferedLinearRegionFile Flusher Checker")
.setDaemon(true) .setDaemon(true)
.build()) .build())
.scheduleWithFixedDelay(this, checkIntervalMs, checkIntervalMs, TimeUnit.MILLISECONDS); .scheduleWithFixedDelay(this, scanInterval, scanInterval, TimeUnit.MILLISECONDS);
this.flushOfWriteTimeoutMs = flushOfWriteTimeoutMs; this.flushOfWriteTimeoutMs = flushOfWriteTimeoutMs;
} }
@@ -62,7 +62,6 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
copied = this.inManagement.toArray(new BufferedLinearRegionFile[0]); copied = this.inManagement.toArray(new BufferedLinearRegionFile[0]);
} }
final List<BufferedLinearRegionFile> toRemove = new ObjectArrayList<>();
for (BufferedLinearRegionFile file : copied) { for (BufferedLinearRegionFile file : copied) {
// try acquiring the read lock // try acquiring the read lock
if (!file.softReadLock()) { if (!file.softReadLock()) {
@@ -80,8 +79,7 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
} }
if (closed) { if (closed) {
// add to pending remove list so that we could clean the closed file correctly // closed already, should be removed soon
toRemove.add(file);
continue; continue;
} }
@@ -109,13 +107,6 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
}); });
} }
} }
synchronized (this) {
// clean closed files
for (BufferedLinearRegionFile file : toRemove) {
this.inManagement.remove(file);
}
}
} }
public void removeFile(BufferedLinearRegionFile fileToRemove) { public void removeFile(BufferedLinearRegionFile fileToRemove) {
@@ -0,0 +1,16 @@
package io.nanachiyo0721.shiroha.enums;
public enum EnumProfilingCategory {
TICK("tick"),
TASK("task");
private final String name;
EnumProfilingCategory(String name) {
this.name = name;
}
public String getTagCategory() {
return this.name;
}
}
@@ -0,0 +1,17 @@
package io.nanachiyo0721.shiroha.enums;
public enum EnumProfilingType {
ALLOC("alloc"),
CPU("cpu"),
WALL("wall");
private final String eventName;
EnumProfilingType(String eventName) {
this.eventName = eventName;
}
public String getEventName() {
return this.eventName;
}
}
@@ -5,14 +5,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.BitSet; import java.util.BitSet;
public class AffinityRunnableWrapper { public record AffinityRunnableWrapper(String name, BitSet affinity) {
private final BitSet affinity;
private final String name;
public AffinityRunnableWrapper(String name, BitSet affinity) {
this.name = name;
this.affinity = affinity;
}
public Runnable wrap(Runnable original) { public Runnable wrap(Runnable original) {
return () -> { return () -> {
@@ -22,13 +15,15 @@ public class AffinityRunnableWrapper {
}; };
} }
@Override
@NotNull @NotNull
public String getName() { public String name() {
return this.name; return this.name;
} }
@Override
@NotNull @NotNull
public BitSet getAffinity() { public BitSet affinity() {
return this.affinity; return this.affinity;
} }
} }
@@ -17,7 +17,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
public class ClassScanUtil { public class ClassScanUtil {
public static @NotNull Collection<Class<?>> scanClassesUnder(String packag3, ClassLoader loader) { public static @NotNull Collection<Class<?>> scanClassesUnder(@NonNull String packag3, @NonNull ClassLoader loader) {
Set<Class<?>> classes = new HashSet<>(); Set<Class<?>> classes = new HashSet<>();
String packageDirName = packag3.replace('.', '/'); String packageDirName = packag3.replace('.', '/');
Enumeration<URL> dirs; Enumeration<URL> dirs;
@@ -0,0 +1,118 @@
package io.nanachiyo0721.shiroha.utils.profiling;
import io.nanachiyo0721.shiroha.enums.EnumProfilingCategory;
import one.profiler.Span;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NonNull;
/**
* note: 1.a single instance should be called single threadedly(except method "toggle")
* 2.tickEnd, tickBegin, taskEnd, taskBegin are called serially not concurrently, if not, exception or unexpected behavior would be
* surely happened
*/
public class RegionScheduleProfiler {
private final long id;
private final String tagNameTick;
private final String tagNameTask;
private ExecutionRecord executionState;
private volatile boolean enabled = false;
private volatile long lastExecutionTime = Long.MIN_VALUE;
public RegionScheduleProfiler(long id) {
this.id = id;
this.tagNameTick = "region-" + EnumProfilingCategory.TICK.getTagCategory() + "-" + this.id;
this.tagNameTask = "region-" + EnumProfilingCategory.TASK.getTagCategory() + "-" + this.id;
}
public void toggle(boolean enabled) {
synchronized (this) {
this.enabled = enabled;
}
}
public boolean tryToggle(boolean enabled) {
boolean oldVal = this.enabled;
if (oldVal == enabled) {
return false;
}
synchronized (this) {
oldVal = this.enabled;
if (oldVal == enabled) {
return false;
}
this.enabled = enabled;
return true;
}
}
public void tickBegin() {
if (!this.enabled) {
return;
}
this.executionState = ExecutionRecord.of(Span.start(), System.nanoTime());
}
public void tickEnd() {
final long end = System.nanoTime();
if (this.executionState != null) {
Span.end(this.executionState.span, this.tagNameTick);
this.lastExecutionTime = end - this.executionState.start;
this.executionState = null;
}
}
public void taskBegin() {
if (!this.enabled) {
return;
}
this.executionState = ExecutionRecord.of(Span.start(), System.nanoTime());
}
public void taskEnd() {
final long end = System.nanoTime();
if (this.executionState != null) {
Span.end(this.executionState.span, this.tagNameTask);
this.lastExecutionTime = end - this.executionState.start;
this.executionState = null;
}
}
/**
* Gets the execution time of last operation
*
* @return -1 -> no execution history yet, otherwise the execution time of last operation
*/
public long lastExecutionTime() {
long executionTime = this.lastExecutionTime;
if (executionTime == Long.MIN_VALUE) {
return -1;
}
return executionTime;
}
public long profilerId() {
return this.id;
}
private record ExecutionRecord(long span, long start) {
@Contract("_,_ -> new")
public static @NonNull ExecutionRecord of(long span, long start) {
return new ExecutionRecord(span, start);
}
}
}
@@ -0,0 +1,493 @@
package io.nanachiyo0721.shiroha.utils.profiling;
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
import com.mojang.logging.LogUtils;
import io.nanachiyo0721.shiroha.enums.EnumProfilingCategory;
import io.nanachiyo0721.shiroha.enums.EnumProfilingType;
import it.unimi.dsi.fastutil.Pair;
import one.convert.Arguments;
import one.convert.Main;
import one.profiler.AsyncProfiler;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class RegionScheduleProfilerManager {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Path PROFILER_FOLDER = Path.of("./shiroha_profiler");
private static final Pattern SESSION_FOLDER_PATTERN = Pattern.compile("session-([1-9]\\d*)");
private final Map<Long, RegionScheduleProfiler> registeredProfilers = new ConcurrentHashMap<>();
private final OpThread opThread = new OpThread();
// note: following fields are managed by the OpThread
private boolean profiling = false;
private int activeSessionId = -1;
private long profilingStartNs = Long.MIN_VALUE;
private String profilingOutput = "";
private List<Long> lastProfilingIds = Collections.emptyList();
private Consumer<Throwable> lastOnEnd;
// end
public record ProfilingSession(int sessionId, CompletableFuture<Path> output) {
public ProfilingSession {
Objects.requireNonNull(output, "output");
}
}
// we do not rely on the serialized operation to be executed on the global region
// as we are also profiling that
private static final class OpThread extends Thread {
private volatile boolean shutdown = false;
private final MultiThreadedQueue<Runnable> ops = new MultiThreadedQueue<>();
private final MultiThreadedQueue<Runnable> afterShutdown = new MultiThreadedQueue<>();
public void signalShutdown() {
this.shutdown = true;
LockSupport.unpark(this);
}
public void shutdownAndAwait() {
this.signalShutdown();
final Thread toUnpark = Thread.currentThread();
if (!afterShutdown.offer(() -> LockSupport.unpark(toUnpark))) {
return;
}
LockSupport.park();
}
public boolean postOpIfNotOnOpThread(Runnable op) {
if (Thread.currentThread() == this) {
op.run();
return true;
}
return this.postOp(op);
}
public boolean postOp(Runnable op) {
final boolean posted = this.ops.offer(op);
if (posted) {
LockSupport.unpark(this);
}
return posted;
}
private void postShutdown() {
Runnable op;
while ((op = this.afterShutdown.pollOrBlockAdds()) != null) {
try {
op.run();
} catch (Throwable ex) {
LOGGER.warn("Failed to execute operation!", ex);
}
}
}
private void flushOps() {
Runnable op;
while ((op = (this.shutdown ? this.ops.pollOrBlockAdds() : this.ops.poll())) != null) {
try {
op.run();
} catch (Throwable ex) {
LOGGER.warn("Failed to execute operation!", ex);
}
}
}
@Override
public void run() {
while (true) {
this.flushOps();
if (this.shutdown) {
// final flush
this.flushOps();
break;
}
// idle
LockSupport.park();
}
this.postShutdown();
}
}
public void init() {
// call for a simple bootstrap
AsyncProfiler.getInstance();
// kick off op thread
this.opThread.start();
}
public void shutdown() {
this.opThread.postOpIfNotOnOpThread(this::haltAllProfiler);
this.opThread.shutdownAndAwait();
}
private void haltAllProfiler() {
if (this.profiling) {
this.stopActiveProfiling();
return;
}
for (RegionScheduleProfiler profiler : this.registeredProfilers.values()) {
profiler.toggle(false);
}
}
public void endProfiling(int sessionId) {
this.endProfilingAsync(sessionId);
}
public CompletableFuture<Boolean> endProfilingAsync(int sessionId) {
final CompletableFuture<Boolean> callback = new CompletableFuture<>();
if (!this.opThread.postOpIfNotOnOpThread(() -> {
if (!this.profiling || sessionId != this.activeSessionId) {
callback.complete(false);
return;
}
final Throwable stopFailure = this.stopActiveProfiling();
if (stopFailure == null) {
callback.complete(true);
} else {
callback.completeExceptionally(stopFailure);
}
})) {
callback.completeExceptionally(new IllegalStateException("Profiler manager has been shut down"));
}
return callback;
}
private Throwable stopActiveProfiling() {
for (long id : this.lastProfilingIds) {
final RegionScheduleProfiler profiler = this.registeredProfilers.get(id);
// may be it was death, so no need to warn
if (profiler == null) {
continue;
}
final boolean success = profiler.tryToggle(false);
if (!success) {
LOGGER.warn("Failed to deactivate async profiler for region of id {}!", id);
}
}
final long profiledTime = System.nanoTime() - this.profilingStartNs;
final long profiledTimeSeconds = profiledTime / 1_000_000_000L;
Throwable stopFailure = null;
String commandResult = "";
try {
commandResult = AsyncProfiler.getInstance().execute("stop");
} catch (Throwable throwable) {
stopFailure = throwable;
LOGGER.warn("Failed to stop async profiler!", throwable);
}
LOGGER.info(
"Profiler stopped with ret: {}, executed for {} seconds, saved to {}",
commandResult,
profiledTimeSeconds,
this.profilingOutput
);
final Consumer<Throwable> onEnd = this.lastOnEnd;
this.profiling = false;
this.activeSessionId = -1;
this.profilingStartNs = Long.MIN_VALUE;
this.profilingOutput = "";
this.lastProfilingIds = Collections.emptyList();
this.lastOnEnd = null;
if (onEnd != null) {
try {
onEnd.accept(stopFailure);
} catch (Throwable throwable) {
LOGGER.warn("Failed to retire profiling callback!", throwable);
}
}
return stopFailure;
}
public CompletableFuture<Pair<Integer, Path>> startProfiling(
EnumProfilingType profilingType,
EnumProfilingCategory category,
long regionId,
long profilingEnd
) {
final CompletableFuture<Pair<Integer, Path>> callback = new CompletableFuture<>();
try {
this.startProfilingSession(profilingType, category, regionId, profilingEnd).whenComplete((session, startFailure) -> {
if (startFailure != null) {
callback.completeExceptionally(startFailure);
return;
}
session.output().whenComplete((output, exportFailure) -> {
if (exportFailure != null) {
LOGGER.warn("Error occurred while profiling!", exportFailure);
callback.complete(Pair.of(session.sessionId(), null));
} else {
callback.complete(Pair.of(session.sessionId(), output));
}
});
});
} catch (Throwable throwable) {
callback.completeExceptionally(throwable);
}
return callback;
}
public CompletableFuture<ProfilingSession> startProfilingSession(
EnumProfilingType profilingType,
EnumProfilingCategory category,
long regionId,
long profilingEnd
) {
Objects.requireNonNull(profilingType, "profilingType");
Objects.requireNonNull(category, "category");
final CompletableFuture<ProfilingSession> callback = new CompletableFuture<>();
// prevent scheduling ahead
if (profilingEnd <= System.nanoTime()) {
callback.completeExceptionally(new IllegalArgumentException("profilingEnd must be in the future"));
return callback;
}
if (!this.opThread.postOpIfNotOnOpThread(() -> {
// prevent duplicated profiling
if (this.profiling) {
callback.completeExceptionally(new IllegalStateException("Another profiler session is already active"));
return;
}
// check for existence
final RegionScheduleProfiler profiler = this.registeredProfilers.get(regionId);
if (profiler == null) {
callback.completeExceptionally(new IllegalArgumentException("No registered region profiler with id " + regionId));
return;
}
final AllocatedSession allocatedSession;
try {
allocatedSession = this.reserveSessionDirectory();
} catch (IOException exception) {
LOGGER.warn("Failed to reserve profiler session directory", exception);
callback.completeExceptionally(exception);
return;
}
final int sessionId = allocatedSession.sessionId();
final Path outputFolder = allocatedSession.folder();
final Path jfrOutput = outputFolder.resolve(regionId + ".jfr");
final CompletableFuture<Path> convertCallback = new CompletableFuture<>();
if (!profiler.tryToggle(true)) {
LOGGER.warn("Failed to activate async profiler for region of id {}!", regionId);
}
final String builtCommand = "start,event=" + profilingType.getEventName() + ",jfr,file=" + jfrOutput;
String commandResult;
try {
commandResult = AsyncProfiler.getInstance().execute(builtCommand);
} catch (Throwable throwable) {
profiler.toggle(false);
LOGGER.error("Failed to start async profiler!", throwable);
callback.completeExceptionally(throwable);
return;
}
// my shit()
this.profiling = true;
this.activeSessionId = sessionId;
this.profilingStartNs = System.nanoTime();
this.profilingOutput = jfrOutput.toString();
this.lastProfilingIds = List.of(regionId);
this.lastOnEnd = stopFailure -> {
if (stopFailure != null) {
convertCallback.completeExceptionally(stopFailure);
return;
}
this.export(jfrOutput, outputFolder, regionId, profilingType, category).whenComplete((path, exportFailure) -> {
if (exportFailure == null) {
convertCallback.complete(path);
} else {
convertCallback.completeExceptionally(exportFailure);
}
});
};
LOGGER.info(
"Started profiling for region with id {} at {} system ns, output: {}, profiling event: {}, return message: {}",
regionId,
this.profilingStartNs,
jfrOutput,
profilingType,
commandResult
);
callback.complete(new ProfilingSession(sessionId, convertCallback));
if (this.profiling && this.activeSessionId == sessionId) {
final long delay = profilingEnd - System.nanoTime();
if (delay <= 0) {
this.endProfiling(sessionId);
return;
}
CompletableFuture.delayedExecutor(delay, TimeUnit.NANOSECONDS)
.execute(() -> this.endProfiling(sessionId));
}
})) {
callback.completeExceptionally(new IllegalStateException("Profiler manager has been shut down"));
}
return callback;
}
private record AllocatedSession(int sessionId, Path folder) {
}
@Contract(" -> new")
private RegionScheduleProfilerManager.@NonNull AllocatedSession reserveSessionDirectory() throws IOException {
Files.createDirectories(PROFILER_FOLDER);
int maxSessionId = 0;
try (Stream<Path> children = Files.list(PROFILER_FOLDER)) {
for (Path child : children.toList()) {
if (!Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) {
continue;
}
final Matcher matcher = SESSION_FOLDER_PATTERN.matcher(child.getFileName().toString());
if (!matcher.matches()) {
continue;
}
try {
final int sessionId = Integer.parseInt(matcher.group(1));
if (sessionId > maxSessionId) {
maxSessionId = sessionId;
}
} catch (NumberFormatException ignored) {
}
}
}
while (maxSessionId < Integer.MAX_VALUE) {
final int sessionId = maxSessionId + 1;
final Path sessionPath = PROFILER_FOLDER.resolve("session-" + sessionId);
try {
Files.createDirectory(sessionPath);
return new AllocatedSession(sessionId, sessionPath);
} catch (FileAlreadyExistsException ignored) {
maxSessionId = sessionId;
}
}
throw new IOException("Profiler session IDs are exhausted");
}
public RegionScheduleProfiler register(long id) {
RegionScheduleProfiler ret = new RegionScheduleProfiler(id);
if (this.registeredProfilers.putIfAbsent(id, ret) != null) {
throw new IllegalStateException("Already registed profiler for region of id " + id + "!");
}
return ret;
}
public void deRegister(long id) {
this.registeredProfilers.remove(id);
}
public CompletableFuture<Path> export(
Path jfrDataFile,
Path outputFolder,
long regionId,
EnumProfilingType exportType,
EnumProfilingCategory profilingCategory
) {
return CompletableFuture.supplyAsync(() -> {
try {
Files.createDirectories(outputFolder);
final String tag = "region-"
+ profilingCategory.getTagCategory()
+ "-"
+ regionId;
final String outputName =
"region-" + regionId
+ "-" + profilingCategory.getTagCategory()
+ "-" + exportType.name().toLowerCase(Locale.ROOT)
+ ".html";
final Path output = outputFolder.resolve(outputName);
final Arguments args = new Arguments();
args.output = "html";
args.tag = tag;
switch (exportType) {
case CPU -> args.cpu = true;
case ALLOC -> args.alloc = true;
case WALL -> args.wall = true;
default -> throw new IllegalArgumentException(
"Unsupported export type: " + exportType
);
}
Main.convert(
jfrDataFile.toString(),
output.toString(),
args
);
return output;
} catch (IOException e) {
throw new CompletionException(e);
}
});
}
}