Compare commits
14
Commits
1192f2e674
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
231bb8705b | ||
|
|
6b7b8863f0 | ||
|
|
d52bcc8c1d | ||
|
|
6fdd8eee4a | ||
|
|
6f7ba53119 | ||
|
|
e660a6239b | ||
|
|
c1c62df3df | ||
|
|
20c4112564 | ||
|
|
87451df0c0 | ||
|
|
899927a935 | ||
|
|
0cc941be27 | ||
|
|
2edb70257d | ||
|
|
c9f8dd4595 | ||
|
|
9eb5c767ed |
+1
-1
@@ -9,7 +9,7 @@ release=pre
|
||||
# true for push to repo, false for skip push repo, auto for detect by release value
|
||||
pushRepo=auto
|
||||
|
||||
foliaRef=24c5c95dc45e02caff98a97ed6ffee7565523464
|
||||
foliaRef=14b7fee5c866fca9a40ede4a58998fb928140f65
|
||||
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.caching=true
|
||||
|
||||
@@ -48,12 +48,19 @@
|
||||
}
|
||||
}
|
||||
val log4jPlugins = sourceSets.create("log4jPlugins") {
|
||||
@@ -134,7 +_,16 @@
|
||||
@@ -134,7 +_,7 @@
|
||||
}
|
||||
|
||||
dependencies {
|
||||
- implementation(project(":folia-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
|
||||
+ implementation("com.electronwill.night-config:toml:3.8.4")
|
||||
+ implementation("net.openhft:affinity:3.23.3")
|
||||
@@ -63,9 +70,9 @@
|
||||
+ implementation("tools.profiler:async-profiler:4.5")
|
||||
+ implementation("tools.profiler:jfr-converter:4.5")
|
||||
+ // 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 @@
|
||||
val git = Git(rootProject.layout.projectDirectory.path)
|
||||
val mcVersion = rootProject.providers.gradleProperty("mcVersion").get()
|
||||
|
||||
+71
-2
@@ -150,7 +150,7 @@ index 16017d819c28b077f732e2ef571eac179d24e323..2e248e4fcdec5830d83ecabf3df16311
|
||||
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);
|
||||
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
|
||||
+++ b/net/minecraft/world/entity/ai/memory/MemorySlot.java
|
||||
@@ -13,7 +13,7 @@ public class MemorySlot<T> {
|
||||
@@ -162,7 +162,7 @@ index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c
|
||||
if (this.hasValue() && this.canExpire()) {
|
||||
if (this.hasExpired()) {
|
||||
this.clear();
|
||||
@@ -21,6 +21,41 @@ public class MemorySlot<T> {
|
||||
@@ -21,6 +21,57 @@ public class MemorySlot<T> {
|
||||
this.timeToLive--;
|
||||
}
|
||||
}
|
||||
@@ -176,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
|
||||
+ 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)) {
|
||||
@@ -204,6 +220,59 @@ index 88a89b4c72cc99dc89d3f3cc928b2dfda4125759..b4bdc7eff97b058ee8ae2c9e4321b53c
|
||||
}
|
||||
|
||||
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
|
||||
index e44814cfb6afb594456b8215bd13a92e93de2c85..d4b65f13c137495d436b7e8133e0ce09ee2e08c0 100644
|
||||
--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
|
||||
|
||||
+21
-11
@@ -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
|
||||
index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f791d49917 100644
|
||||
index 037697e6364c73057b1fbac354bd78d8bf35bc7b..3a7e4b57dc41030295f5e9da58a0fcd1270c521f 100644
|
||||
--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
|
||||
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
|
||||
@@ -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,
|
||||
final Runnable run) {
|
||||
@@ -155,6 +156,58 @@ public final class RegionizedTaskQueue {
|
||||
@@ -155,6 +156,68 @@ public final class RegionizedTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,23 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
|
||||
+
|
||||
+ final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1];
|
||||
+
|
||||
+ for (ConcurrentChainedLong2ReferenceHashTable.TableEntry<ReferenceCountData> counterEntry : this.referenceCounters.entrySet()) {
|
||||
+ final long coord = counterEntry.getKey();
|
||||
+ final ReferenceCountData counterData = counterEntry.getValue();
|
||||
+ final it.unimi.dsi.fastutil.longs.LongIterator sectionsIterator = currentRegion.getOwnedSectionsUnsynchronised();
|
||||
+ while (sectionsIterator.hasNext()) {
|
||||
+ final long sectionKey = sectionsIterator.nextLong();
|
||||
+ final int sectionX = CoordinateUtils.getChunkX(sectionKey);
|
||||
+ final int sectionZ = CoordinateUtils.getChunkZ(sectionKey);
|
||||
+
|
||||
+ // only tick for our region
|
||||
+ if (currentRegion == this.world.regioniser.getRegionAtUnsynchronised(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord))) {
|
||||
+ final int chunkX = sectionX << this.world.regioniser.sectionChunkShift;
|
||||
+ final int chunkZ = sectionZ << this.world.regioniser.sectionChunkShift;
|
||||
+ final long coord = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
+ final ReferenceCountData counterData = this.referenceCounters.get(coord);
|
||||
+
|
||||
+ // removed
|
||||
+ if (counterData == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // do ttl
|
||||
+ long curr = counterData.referenceTTL.get();
|
||||
+ // successfully decreased ttl
|
||||
+ if (curr == (curr = counterData.referenceTTL.compareAndExchange(curr, curr - 1))) {
|
||||
@@ -69,13 +80,12 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // Shiroha end - RegionizedTaskQueue queue TTL optimization
|
||||
+
|
||||
private void decrementReference(final ReferenceCountData referenceCountData, final long coord) {
|
||||
if (!referenceCountData.decreaseReferenceCount()) {
|
||||
return;
|
||||
@@ -212,9 +265,10 @@ public final class RegionizedTaskQueue {
|
||||
@@ -212,9 +275,10 @@ public final class RegionizedTaskQueue {
|
||||
private final long id = ID_GENERATOR.getAndIncrement();
|
||||
|
||||
public final AtomicLong referenceCount = new AtomicLong(1L);
|
||||
@@ -87,7 +97,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
|
||||
public boolean addCount() {
|
||||
int failures = 0;
|
||||
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))) {
|
||||
@@ -115,7 +125,7 @@ index 037697e6364c73057b1fbac354bd78d8bf35bc7b..0e88bc2776615f8e3e1e6a05c5ffb8f7
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,11 +309,11 @@ public final class RegionizedTaskQueue {
|
||||
@@ -234,11 +319,11 @@ public final class RegionizedTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
@@ -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
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ Subject: [PATCH] Add config to enable tick command
|
||||
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
|
||||
index aa575f3b76ef70ffb9f0410e7e5cfe7af384bfbd..5f6d6533067863d609484d1463fe4e64ff5f683c 100644
|
||||
index 4b8287ab2c674b773c5ed254f5c6e72ece851684..feb21d91691e849c15976ff1f9482ad1049c4ab8 100644
|
||||
--- a/io/papermc/paper/threadedregions/RegionizedServer.java
|
||||
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
|
||||
@@ -233,6 +233,11 @@ public final class RegionizedServer {
|
||||
+44
-42
@@ -1,15 +1,17 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: MrHua269 <mrhua269@gmail.com>
|
||||
Date: Thu, 9 Jul 2026 11:54:24 +0800
|
||||
Subject: [PATCH] Leaves Vanilla Hopper
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
Date: Mon, 17 Aug 2026 23:38:00 +0800
|
||||
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
|
||||
Origin license: https://github.com/LeavesMC/Leaves/blob/master/LICENSE.md
|
||||
Original license: GPL-3.0-only
|
||||
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
|
||||
index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08a5200c95 100644
|
||||
index a858cb658af70f07614fa1f4d9e8a3435d5c161f..5ab92e215c41f02c2a8260071f64dddae35dd15f 100644
|
||||
--- a/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
|
||||
@@ -18,7 +20,24 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
|
||||
final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount);
|
||||
- container.setChanged(); // original logic always marks source inv as changed even if no move happens.
|
||||
- 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
|
||||
- movedItem = callPullMoveEvent(hopper, container, movedItem);
|
||||
- if (movedItem == null) { // cancelled
|
||||
@@ -26,20 +45,11 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
|
||||
- // 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
|
||||
+ // Leaves start - fix vanilla hopper
|
||||
+ if (movedItem.getCount() <= movedItemCount) {
|
||||
+ if (!worldData.skipPullModeEventFire) {
|
||||
+ movedItem = callPullMoveEvent(hopper, container, movedItem);
|
||||
+ if (movedItem == null) { // cancelled
|
||||
+ 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()) {
|
||||
+ final int itemCountToMove = movedItem.getCount();
|
||||
+ final ItemStack remainingItem = addItem(container, hopper, movedItem, null);
|
||||
+ if (remainingItem.getCount() != itemCountToMove) {
|
||||
+ origItemStack.setCount(removeOriginalItem ? originalItemCount - movedItemCount : originalItemCount);
|
||||
+ container.setItem(i, origItemStack);
|
||||
+ container.setChanged();
|
||||
return true;
|
||||
}
|
||||
@@ -49,23 +59,10 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
|
||||
- final int remainingItemCount = remainingItem.getCount();
|
||||
- if (remainingItemCount != movedItemCount) {
|
||||
- origItemStack = origItemStack.copy(true);
|
||||
- origItemStack.setCount(originalItemCount);
|
||||
origItemStack.setCount(originalItemCount);
|
||||
- if (!origItemStack.isEmpty()) {
|
||||
- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
|
||||
+ if (remainingItemCount != toRemove) {
|
||||
+ 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;
|
||||
+ container.setItem(i, origItemStack);
|
||||
+ } else {
|
||||
+ container.setChanged(); // original logic always marks source inv as changed even if no move happens.
|
||||
+ movedItem.setCount(movedItemCount);
|
||||
@@ -79,8 +76,13 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
|
||||
+ // site for IMIE did not exhibit the same behavior
|
||||
+ 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 int remainingItemCount = remainingItem.getCount();
|
||||
+ if (remainingItemCount != movedItemCount) {
|
||||
@@ -90,16 +92,16 @@ index a858cb658af70f07614fa1f4d9e8a3435d5c161f..95f0b3276137cb9d1fc296e97cc25a08
|
||||
+ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
|
||||
+ }
|
||||
+
|
||||
+ IGNORE_TILE_UPDATES.set(true);
|
||||
+ IGNORE_TILE_UPDATES.set(true); // Folia - region threading
|
||||
+ container.setItem(i, origItemStack);
|
||||
+ IGNORE_TILE_UPDATES.set(false);
|
||||
+ IGNORE_TILE_UPDATES.set(false); // Folia - region threading
|
||||
+ container.setChanged();
|
||||
+ return true;
|
||||
+ }
|
||||
+ origItemStack.setCount(originalItemCount);
|
||||
}
|
||||
- origItemStack.setCount(originalItemCount);
|
||||
+ // Leaves end - fix vanilla hopper
|
||||
+ // Leaves end - Vanilla hopper
|
||||
|
||||
if (level.paperConfig().hopper.cooldownWhenFull) {
|
||||
applyCooldown(hopper);
|
||||
+1
-1
@@ -1573,7 +1573,7 @@ index 6109d8da984b398bb1eb6cd6ab9a55f0511a615c..d3d6b3aeb24da7f3bf92c327095a9dc9
|
||||
+ // 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
|
||||
index 95f0b3276137cb9d1fc296e97cc25a08a5200c95..26afe1f74b50b60d6c04e08967610a3d50183483 100644
|
||||
index 5ab92e215c41f02c2a8260071f64dddae35dd15f..a847bcf24b7ac1ed702027b43438e28037daba84 100644
|
||||
--- a/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;
|
||||
+2
-2
@@ -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)
|
||||
|
||||
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
|
||||
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
|
||||
@@ -101,6 +101,14 @@ public final class CollisionUtil {
|
||||
@@ -35,7 +35,7 @@ index 8d2518600ad518999b75124f0a87db9efe541f2e..d66e65fdee6117b0da7368a3ef9c3ae0
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2087,6 +2095,18 @@ public final class CollisionUtil {
|
||||
@@ -2088,6 +2096,18 @@ public final class CollisionUtil {
|
||||
return ret;
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ index 724332d074bd7d3dfdcb60e83cf1fd6ce706000e..eb65077d18d86fce3ae1131445ebdcb7
|
||||
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 ab94ea7f18799d9dd3cf164a1332db69f40a9278..863f78680150d87c5b82132e471baac527452b6b 100644
|
||||
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
|
||||
+6
@@ -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")
|
||||
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")
|
||||
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")
|
||||
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;
|
||||
}
|
||||
+4
-4
@@ -96,22 +96,22 @@ public class CpuAffinityConfig implements IConfigModule {
|
||||
|
||||
if (enabledForTickRegion) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -459,6 +459,9 @@ public class BufferedLinearRegionFile implements io.nanachiyo0721.shiroha.data.R
|
||||
return;
|
||||
}
|
||||
|
||||
// remove from flusher
|
||||
this.markClosed();
|
||||
|
||||
IOException failure = null;
|
||||
|
||||
// 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 {
|
||||
this.markClosed();
|
||||
|
||||
this.swapFileChannel.close();
|
||||
} catch (IOException ex) {
|
||||
if (failure == null) failure = ex;
|
||||
@@ -488,9 +489,6 @@ public class BufferedLinearRegionFile implements io.nanachiyo0721.shiroha.data.R
|
||||
else failure.addSuppressed(e);
|
||||
}
|
||||
|
||||
// finalize
|
||||
this.markClosed();
|
||||
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
}
|
||||
|
||||
+4
-13
@@ -20,9 +20,9 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
|
||||
private final Executor ioWorkerPool;
|
||||
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(checkIntervalMs > 0, "Check interval must > 0");
|
||||
Validate.isTrue(scanInterval > 0, "Scan interval must > 0");
|
||||
Validate.isTrue(flushOfWriteTimeoutMs > 0, "Flush of write timeout must > 0");
|
||||
|
||||
this.ioWorkerPool = Executors.newFixedThreadPool(nIoThreads, new ThreadFactoryBuilder()
|
||||
@@ -34,7 +34,7 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
|
||||
.setNameFormat("BufferedLinearRegionFile Flusher Checker")
|
||||
.setDaemon(true)
|
||||
.build())
|
||||
.scheduleWithFixedDelay(this, checkIntervalMs, checkIntervalMs, TimeUnit.MILLISECONDS);
|
||||
.scheduleWithFixedDelay(this, scanInterval, scanInterval, TimeUnit.MILLISECONDS);
|
||||
this.flushOfWriteTimeoutMs = flushOfWriteTimeoutMs;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
|
||||
copied = this.inManagement.toArray(new BufferedLinearRegionFile[0]);
|
||||
}
|
||||
|
||||
final List<BufferedLinearRegionFile> toRemove = new ObjectArrayList<>();
|
||||
for (BufferedLinearRegionFile file : copied) {
|
||||
// try acquiring the read lock
|
||||
if (!file.softReadLock()) {
|
||||
@@ -80,8 +79,7 @@ public class BufferedLinearRegionFileFlusher implements Runnable {
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
// add to pending remove list so that we could clean the closed file correctly
|
||||
toRemove.add(file);
|
||||
// closed already, should be removed soon
|
||||
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) {
|
||||
|
||||
+5
-10
@@ -5,14 +5,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class AffinityRunnableWrapper {
|
||||
private final BitSet affinity;
|
||||
private final String name;
|
||||
|
||||
public AffinityRunnableWrapper(String name, BitSet affinity) {
|
||||
this.name = name;
|
||||
this.affinity = affinity;
|
||||
}
|
||||
public record AffinityRunnableWrapper(String name, BitSet affinity) {
|
||||
|
||||
public Runnable wrap(Runnable original) {
|
||||
return () -> {
|
||||
@@ -22,13 +15,15 @@ public class AffinityRunnableWrapper {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
public String name() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BitSet getAffinity() {
|
||||
public BitSet affinity() {
|
||||
return this.affinity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
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<>();
|
||||
String packageDirName = packag3.replace('.', '/');
|
||||
Enumeration<URL> dirs;
|
||||
|
||||
Reference in New Issue
Block a user