Move file patches to feature patches

This is to assist in updating upstream, as the current file patch
system is brittle and fails to handle conflicts well.
This commit is contained in:
Spottedleaf 2025-02-16 12:17:53 -08:00
parent 710d77e354
commit d17fb532f1
404 changed files with 25167 additions and 24357 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ index 6173f704b0d093813ec67eb231c75be49a462e7d..159f2f169d26b436a70006f7bc9bdc48
// CraftBukkit start // CraftBukkit start
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 65835fa09cdcd3bb158025f7d8b3cb29ac8b2549..bcfe3af0b0df25d4b308b11a64c361da1ab3eeca 100644 index 7b13a9e7d38efe7786023747f55ebf5a2ba80688..b5e609a81ee0445226b61ba5bed69ad9f100f7af 100644
--- a/net/minecraft/server/players/PlayerList.java --- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java
@@ -151,6 +151,17 @@ public abstract class PlayerList { @@ -151,6 +151,17 @@ public abstract class PlayerList {

View File

@ -58,7 +58,7 @@ index 6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2..4ad647a9f98cf1c11c45f85edcba3c29
chunk = wrappedFull.getWrapped(); chunk = wrappedFull.getWrapped();
} else { } else {
diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
index 3bcb1dc98c61e025874cc9e008faa722581a530c..012d3a7da7fe483393a0888c823bd2e78f5c3908 100644 index ebe2592a78e996fa2d415663bd6436effec1ca29..5e6b490ee58a90fd7c02fa09093830c0d9c67f6b 100644
--- a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java --- a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
+++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
@@ -170,6 +170,9 @@ public final class CommandServerHealth extends Command { @@ -170,6 +170,9 @@ public final class CommandServerHealth extends Command {

View File

@ -9,7 +9,7 @@ add explicit block update suppression techniques, it's better
than the server crashing. than the server crashing.
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index d36b5ad7b386391e617895a33b919e29266220e2..e16a824488d2b43c430f12b8416fdeb590e66d28 100644 index db4ce98706bf69dcd8144faba1780f83ca1f6787..4adc38a633180cdc04523d46f5e0932f1c60f411 100644
--- a/net/minecraft/world/level/Level.java --- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java
@@ -2053,7 +2053,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -2053,7 +2053,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl

View File

@ -7,7 +7,7 @@ This allows the player to be re-positioned before logging into
the world without causing thread checks to trip on Folia. the world without causing thread checks to trip on Folia.
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index df0c10880c5aea110a4eade57d257d3a46e8c180..ca3770e9f77e583dfa6cef8ca884eaf6a43f5ffa 100644 index f5615c7f7127edda460db9158d6bd4ddad9193f7..423534a1ff02bd0d0f9baacfe2428f45c7d9acb9 100644
--- a/net/minecraft/server/level/ServerPlayer.java --- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java
@@ -775,8 +775,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -775,8 +775,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc

View File

@ -1544,7 +1544,7 @@ index 9c9de462eb7187d6cc3562c796e3bcf69fb20783..faf72dd6dff74296c73cb058aaabd1f9
//this.playerList.tick(); // Folia - region threading //this.playerList.tick(); // Folia - region threading
if (SharedConstants.IS_RUNNING_IN_IDE && this.tickRateManager.runsNormally()) { if (SharedConstants.IS_RUNNING_IN_IDE && this.tickRateManager.runsNormally()) {
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 17d124acb7149f6c19f9295ad173108d5070e55b..4bdb05948e9102304364f3681ce353f1cf2a0aee 100644 index 329e57af5cbd38425e80dba96eb972fdfb0ce5ce..5c507be097051de9a43a31bbc6190c3db7688667 100644
--- a/net/minecraft/server/level/ChunkMap.java --- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java
@@ -410,12 +410,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -410,12 +410,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@ -1838,7 +1838,7 @@ index ce310e2dc1bb46e17143bffc5e6cec7d43a0389d..d34ad333b6ea3855a24a58fcd80ccf19
} }
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index bcfe3af0b0df25d4b308b11a64c361da1ab3eeca..422e4cd1606a612056ae335d92786d8a5c46fa1d 100644 index b5e609a81ee0445226b61ba5bed69ad9f100f7af..548f7a2b0b020ff7fff27396942cc0bc9e755afe 100644
--- a/net/minecraft/server/players/PlayerList.java --- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java
@@ -1163,6 +1163,7 @@ public abstract class PlayerList { @@ -1163,6 +1163,7 @@ public abstract class PlayerList {
@ -1904,7 +1904,7 @@ index 49201d6664656ebe34c84c1c84b5ea4878729062..d9cc1d7e56c37d5ce92544edc10e89db
} }
} }
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index e16a824488d2b43c430f12b8416fdeb590e66d28..5ea7fdf1e337da4c207dd6a53ca942480dd31922 100644 index 4adc38a633180cdc04523d46f5e0932f1c60f411..dafd90502937019b616ac0a79465e1dbc578cf66 100644
--- a/net/minecraft/world/level/Level.java --- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java
@@ -200,6 +200,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -200,6 +200,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl

View File

@ -117,7 +117,7 @@ index 0000000000000000000000000000000000000000..258d82ab2c78482e1561343e8e1f81fc
+ } + }
+} +}
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index a18da3f3f245031f0547efe9b52a1f2a219ef04a..056fb1ca7b07d5e713dcbd951830b14fc9025f4c 100644 index 2fd27993b445cd8c3b517a91746c3f303a35238d..7123b3eb2f2e52946b8ef9de993a6828eb0bb6f7 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java --- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java +++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -34,6 +34,13 @@ public final class TickRegionScheduler { @@ -34,6 +34,13 @@ public final class TickRegionScheduler {

View File

@ -1,23 +0,0 @@
--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
+++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
@@ -244,7 +_,7 @@
created.addPlayer(parameter, type);
type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
- ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created;
+ //((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; // Folia - region threading
}
}
@@ -263,10 +_,7 @@
if (chunk.isEmpty()) {
NearbyPlayers.this.byChunk.remove(chunkKey);
- final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey);
- if (chunkData != null) {
- chunkData.nearbyPlayers = null;
- }
+ // Folia - region threading
}
}
}

View File

@ -1,20 +0,0 @@
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -105,7 +_,7 @@
}
for (final EnderDragonPart part : parts) {
- if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
+ if (part != entity && part.getBoundingBox().intersects(boundingBox) && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(part) && (predicate == null || predicate.test(part))) { // Folia - region threading
into.add(part);
}
}
@@ -127,7 +_,7 @@
continue;
}
final T casted = (T)entityTypeTest.tryCast(part);
- if (casted != null && (predicate == null || predicate.test(casted))) {
+ if (casted != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(part) && (predicate == null || predicate.test(casted))) { // Folia - region threading
into.add(casted);
if (into.size() >= maxCount) {
break;

View File

@ -1,87 +0,0 @@
--- a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
+++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
@@ -80,18 +_,23 @@
@Override
public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
-
+ // Folia start - threaded regions
+ level.regioniser.addChunk(holder.getPos().x, holder.getPos().z);
+ // Folia end - threaded regions
}
@Override
public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
// Update progress listener for LevelLoadingScreen
- final net.minecraft.server.level.progress.ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener;
+ final net.minecraft.server.level.progress.ChunkProgressListener progressListener = null; // Folia - threaded regions - cannot schedule chunk task here; as it would create a chunkholder
if (progressListener != null) {
this.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> {
progressListener.onStatusChange(holder.getPos(), null);
});
}
+ // Folia start - threaded regions
+ level.regioniser.removeChunk(holder.getPos().x, holder.getPos().z);
+ // Folia end - threaded regions
}
@Override
@@ -102,17 +_,13 @@
@Override
public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().addChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
chunk.loadCallback();
}
@Override
public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().removeChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
chunk.unloadCallback();
}
@@ -124,9 +_,7 @@
@Override
public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().addTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
if (!((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
}
@@ -137,24 +_,18 @@
@Override
public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().removeTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
}
@Override
public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().addEntityTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
}
@Override
public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove(
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
- );
+ chunk.getLevel().getCurrentWorldData().removeEntityTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading
}
@Override

View File

@ -1,11 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
@@ -5,7 +_,7 @@
public final class ChunkData {
private int referenceCount = 0;
- public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players
+ //public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players // Folia - region threading
public ChunkData() {

View File

@ -1,32 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
@@ -460,6 +_,19 @@
return slices == null || !slices.isPreventingStatusUpdates();
}
+ // Folia start - region threading
+ // only appropriate to use when in shutdown, as this performs no logic hooks to properly add to world
+ public boolean addEntityForShutdownTeleportComplete(final Entity entity) {
+ final BlockPos pos = entity.blockPosition();
+ final int sectionX = pos.getX() >> 4;
+ final int sectionY = Mth.clamp(pos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world));
+ final int sectionZ = pos.getZ() >> 4;
+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ);
+
+ return slices.addEntity(entity, sectionY);
+ }
+ // Folia end - region threading
+
protected void removeEntity(final Entity entity) {
final int sectionX = ((ChunkSystemEntity)entity).moonrise$getSectionX();
final int sectionY = ((ChunkSystemEntity)entity).moonrise$getSectionY();
@@ -986,6 +_,9 @@
EntityLookup.this.removeEntityCallback(entity);
this.entity.setLevelCallback(NoOpCallback.INSTANCE);
+
+ // only AFTER full removal callbacks, so that thread checking will work. // Folia - region threading
+ EntityLookup.this.world.getCurrentWorldData().removeEntity(entity); // Folia - region threading
}
}

View File

@ -1,36 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
@@ -17,7 +_,7 @@
private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0];
private final ServerLevel serverWorld;
- public final ReferenceList<Entity> trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
+ // Folia - move to regionized world data
public ServerEntityLookup(final ServerLevel world, final LevelCallback<Entity> worldCallback) {
super(world, worldCallback);
@@ -75,6 +_,7 @@
if (entity instanceof ServerPlayer player) {
((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().addPlayer(player);
}
+ this.world.getCurrentWorldData().addEntity(entity); // Folia - region threading
}
@Override
@@ -87,14 +_,14 @@
@Override
protected void entityStartLoaded(final Entity entity) {
// Moonrise start - entity tracker
- this.trackerEntities.add(entity);
+ this.world.getCurrentWorldData().trackerEntities.add(entity); // Folia - region threading
// Moonrise end - entity tracker
}
@Override
protected void entityEndLoaded(final Entity entity) {
// Moonrise start - entity tracker
- this.trackerEntities.remove(entity);
+ this.world.getCurrentWorldData().trackerEntities.remove(entity); // Folia - region threading
// Moonrise end - entity tracker
}

View File

@ -1,20 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
@@ -216,7 +_,7 @@
final PlayerChunkLoaderData loader = ((ChunkSystemServerPlayer)player).moonrise$getChunkLoader();
if (loader == null) {
- return;
+ throw new IllegalStateException("Player is already removed from player chunk loader"); // Folia - region threading
}
loader.remove();
@@ -304,7 +_,7 @@
public void tick() {
TickThread.ensureTickThread("Cannot tick player chunk loader async");
long currTime = System.nanoTime();
- for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) {
+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.getLocalPlayers())) { // Folia - region threding
final PlayerChunkLoaderData loader = ((ChunkSystemServerPlayer)player).moonrise$getChunkLoader();
if (loader == null || loader.removed || loader.world != this.world) {
// not our problem anymore

View File

@ -1,42 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
@@ -29,6 +_,39 @@
public static record SectionToUnload(int sectionX, int sectionZ, long order, int count) {}
+ // Folia start - threaded regions
+ public List<SectionToUnload> retrieveForCurrentRegion() {
+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> region =
+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion();
+ final io.papermc.paper.threadedregions.ThreadedRegionizer<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> regionizer = region.regioniser;
+ final int shift = this.coordinateShift;
+
+ final List<SectionToUnload> ret = new ArrayList<>();
+
+ for (final Iterator<ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection>> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) {
+ final ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection> entry = iterator.next();
+ final long key = entry.getKey();
+ final UnloadSection section = entry.getValue();
+ final int sectionX = CoordinateUtils.getChunkX(key);
+ final int sectionZ = CoordinateUtils.getChunkZ(key);
+ final int chunkX = sectionX << shift;
+ final int chunkZ = sectionZ << shift;
+
+ if (regionizer.getRegionAtUnsynchronised(chunkX, chunkZ) != region) {
+ continue;
+ }
+
+ ret.add(new SectionToUnload(sectionX, sectionZ, section.order, section.chunks.size()));
+ }
+
+ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> {
+ return Long.compare(s1.order, s2.order);
+ });
+
+ return ret;
+ }
+ // Folia end - threaded regions
+
public List<SectionToUnload> retrieveForAllRegions() {
final List<SectionToUnload> ret = new ArrayList<>();

View File

@ -1,391 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
@@ -56,6 +_,14 @@
import java.util.concurrent.locks.LockSupport;
import java.util.function.Predicate;
+// Folia start - region threading
+import io.papermc.paper.threadedregions.RegionizedServer;
+import io.papermc.paper.threadedregions.ThreadedRegionizer;
+import io.papermc.paper.threadedregions.TickRegionScheduler;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+// Folia end - region threading
+
public final class ChunkHolderManager {
private static final Logger LOGGER = LogUtils.getClassLogger();
@@ -78,29 +_,83 @@
private final ConcurrentLong2ReferenceChainedHashTable<NewChunkHolder> chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f);
private final ServerLevel world;
private final ChunkTaskScheduler taskScheduler;
- private long currentTick;
-
- private final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = new ArrayDeque<>();
- private final ObjectRBTreeSet<NewChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> {
- if (c1 == c2) {
- return 0;
- }
-
- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave);
-
- if (saveTickCompare != 0) {
- return saveTickCompare;
- }
-
- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ);
- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ);
-
- if (coord1 == coord2) {
- throw new IllegalStateException("Duplicate chunkholder in auto save queue");
- }
-
- return Long.compare(coord1, coord2);
- });
+ // Folia start - region threading
+ public static final class HolderManagerRegionData {
+ private final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = new ArrayDeque<>();
+ private final ObjectRBTreeSet<NewChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> {
+ if (c1 == c2) {
+ return 0;
+ }
+
+ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave);
+
+ if (saveTickCompare != 0) {
+ return saveTickCompare;
+ }
+
+ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ);
+ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ);
+
+ if (coord1 == coord2) {
+ throw new IllegalStateException("Duplicate chunkholder in auto save queue");
+ }
+
+ return Long.compare(coord1, coord2);
+ });
+
+ public void merge(final HolderManagerRegionData into, final long tickOffset) {
+ // Order doesn't really matter for the pending full update...
+ into.pendingFullLoadUpdate.addAll(this.pendingFullLoadUpdate);
+
+ // We need to copy the set to iterate over, because modifying the field used in compareTo while iterating
+ // will destroy the result from compareTo (However, the set is not destroyed _after_ iteration because a constant
+ // addition to every entry will not affect compareTo).
+ for (final NewChunkHolder holder : new ArrayList<>(this.autoSaveQueue)) {
+ holder.lastAutoSave += tickOffset;
+ into.autoSaveQueue.add(holder);
+ }
+ }
+
+ public void split(final int chunkToRegionShift, final Long2ReferenceOpenHashMap<HolderManagerRegionData> regionToData,
+ final ReferenceOpenHashSet<HolderManagerRegionData> dataSet) {
+ for (final NewChunkHolder fullLoadUpdate : this.pendingFullLoadUpdate) {
+ final int regionCoordinateX = fullLoadUpdate.chunkX >> chunkToRegionShift;
+ final int regionCoordinateZ = fullLoadUpdate.chunkZ >> chunkToRegionShift;
+
+ final HolderManagerRegionData data = regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ));
+ if (data != null) {
+ data.pendingFullLoadUpdate.add(fullLoadUpdate);
+ } // else: fullLoadUpdate is an unloaded chunk holder
+ }
+
+ for (final NewChunkHolder autoSave : this.autoSaveQueue) {
+ final int regionCoordinateX = autoSave.chunkX >> chunkToRegionShift;
+ final int regionCoordinateZ = autoSave.chunkZ >> chunkToRegionShift;
+
+ final HolderManagerRegionData data = regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ));
+ if (data != null) {
+ data.autoSaveQueue.add(autoSave);
+ } // else: autoSave is an unloaded chunk holder
+ }
+ }
+ }
+
+ private ChunkHolderManager.HolderManagerRegionData getCurrentRegionData() {
+ final ThreadedRegionizer.ThreadedRegion<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> region =
+ TickRegionScheduler.getCurrentRegion();
+
+ if (region == null) {
+ return null;
+ }
+
+ if (this.world != null && this.world != region.getData().world) {
+ throw new IllegalStateException("World check failed: expected world: " + this.world.getWorld().getKey() + ", region world: " + region.getData().world.getWorld().getKey());
+ }
+
+ return region.getData().getHolderManagerRegionData();
+ }
+ // Folia end - region threading
+
public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) {
this.world = world;
@@ -185,8 +_,13 @@
}
public void close(final boolean save, final boolean halt) {
+ // Folia start - region threading
+ this.close(save, halt, true, true, true);
+ }
+ public void close(final boolean save, final boolean halt, final boolean first, final boolean last, final boolean checkRegions) {
+ // Folia end - region threading
TickThread.ensureTickThread("Closing world off-main");
- if (halt) {
+ if (first && halt) { // Folia - region threading
LOGGER.info("Waiting 60s for chunk system to halt for world '" + WorldUtil.getWorldName(this.world) + "'");
if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) {
LOGGER.warn("Failed to halt generation/loading tasks for world '" + WorldUtil.getWorldName(this.world) + "'");
@@ -196,9 +_,10 @@
}
if (save) {
- this.saveAllChunks(true, true, true);
+ this.saveAllChunksRegionised(true, true, true, first, last, checkRegions); // Folia - region threading
}
+ if (last) { // Folia - region threading
MoonriseRegionFileIO.flush(this.world);
if (halt) {
@@ -220,28 +_,35 @@
}
this.taskScheduler.setShutdown(true);
+ } // Folia - region threading
}
void ensureInAutosave(final NewChunkHolder holder) {
- if (!this.autoSaveQueue.contains(holder)) {
- holder.lastAutoSave = this.currentTick;
- this.autoSaveQueue.add(holder);
+ // Folia start - region threading
+ final HolderManagerRegionData regionData = this.getCurrentRegionData();
+ if (!regionData.autoSaveQueue.contains(holder)) {
+ holder.lastAutoSave = RegionizedServer.getCurrentTick();
+ regionData.autoSaveQueue.add(holder);
+ // Folia end - region threading
}
}
public void autoSave() {
final List<NewChunkHolder> reschedule = new ArrayList<>();
- final long currentTick = this.currentTick;
+ final long currentTick = RegionizedServer.getCurrentTick(); // Folia - region threading
final long maxSaveTime = currentTick - Math.max(1L, PlatformHooks.get().configAutoSaveInterval(this.world));
final int maxToSave = PlatformHooks.get().configMaxAutoSavePerTick(this.world);
- for (int autoSaved = 0; autoSaved < maxToSave && !this.autoSaveQueue.isEmpty();) {
- final NewChunkHolder holder = this.autoSaveQueue.first();
+ // Folia start - region threading
+ final HolderManagerRegionData regionData = this.getCurrentRegionData();
+ for (int autoSaved = 0; autoSaved < maxToSave && !regionData.autoSaveQueue.isEmpty();) {
+ final NewChunkHolder holder = regionData.autoSaveQueue.first();
+ // Folia end - region threading
if (holder.lastAutoSave > maxSaveTime) {
break;
}
- this.autoSaveQueue.remove(holder);
+ regionData.autoSaveQueue.remove(holder); // Folia - region threading
holder.lastAutoSave = currentTick;
if (holder.save(false) != null) {
@@ -255,15 +_,38 @@
for (final NewChunkHolder holder : reschedule) {
if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) {
- this.autoSaveQueue.add(holder);
+ regionData.autoSaveQueue.add(holder); // Folia start - region threading
}
}
}
public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) {
- final List<NewChunkHolder> holders = this.getChunkHolders();
-
- if (logProgress) {
+ // Folia start - region threading
+ this.saveAllChunksRegionised(flush, shutdown, logProgress, true, true, true);
+ }
+ public void saveAllChunksRegionised(final boolean flush, final boolean shutdown, final boolean logProgress, final boolean first, final boolean last, final boolean checkRegion) {
+ final List<NewChunkHolder> holders = new java.util.ArrayList<>(this.chunkHolders.size() / 10);
+ // we could iterate through all chunk holders with thread checks, however for many regions the iteration cost alone
+ // will multiply. to avoid this, we can simply iterate through all owned sections
+ final int regionShift = this.world.moonrise$getRegionChunkShift();
+ final int width = 1 << regionShift;
+ for (final LongIterator iterator = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion().getOwnedSectionsUnsynchronised(); iterator.hasNext();) {
+ final long sectionKey = iterator.nextLong();
+ final int offsetX = CoordinateUtils.getChunkX(sectionKey) << regionShift;
+ final int offsetZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift;
+
+ for (int dz = 0; dz < width; ++dz) {
+ for (int dx = 0; dx < width; ++dx) {
+ final NewChunkHolder holder = this.getChunkHolder(offsetX | dx, offsetZ | dz);
+ if (holder != null) {
+ holders.add(holder);
+ }
+ }
+ }
+ }
+ // Folia end - region threading
+
+ if (first && logProgress) { // Folia - region threading
LOGGER.info("Saving all chunkholders for world '" + WorldUtil.getWorldName(this.world) + "'");
}
@@ -292,6 +_,12 @@
}
for (int i = 0, len = holders.size(); i < len; ++i) {
final NewChunkHolder holder = holders.get(i);
+ // Folia start - region threading
+ if (!checkRegion && !TickThread.isTickThreadFor(this.world, holder.chunkX, holder.chunkZ)) {
+ // skip holders that would fail the thread check
+ continue;
+ }
+ // Folia end - region threading
try {
final NewChunkHolder.SaveStat saveStat = holder.save(shutdown);
if (saveStat != null) {
@@ -327,7 +_,7 @@
}
}
}
- if (flush) {
+ if (last && flush) { // Folia - region threading
MoonriseRegionFileIO.flush(this.world);
try {
MoonriseRegionFileIO.flushRegionStorages(this.world);
@@ -732,7 +_,13 @@
}
public void tick() {
- ++this.currentTick;
+ // Folia start - region threading
+ final ThreadedRegionizer.ThreadedRegion<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> region =
+ TickRegionScheduler.getCurrentRegion();
+ if (region == null) {
+ throw new IllegalStateException("Not running tick() while on a region");
+ }
+ // Folia end - region threading
final int sectionShift = ((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift();
@@ -746,7 +_,7 @@
return removeDelay <= 0L;
};
- for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) {
+ for (final LongIterator iterator = region.getOwnedSectionsUnsynchronised(); iterator.hasNext();) {
final long sectionKey = iterator.nextLong();
if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) {
@@ -1031,26 +_,56 @@
if (changedFullStatus.isEmpty()) {
return;
}
- if (!TickThread.isTickThread()) {
- this.taskScheduler.scheduleChunkTask(() -> {
- final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate;
- for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
- pendingFullLoadUpdate.add(changedFullStatus.get(i));
- }
-
- ChunkHolderManager.this.processPendingFullUpdate();
- }, Priority.HIGHEST);
- } else {
- final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
- for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
- pendingFullLoadUpdate.add(changedFullStatus.get(i));
- }
- }
+
+ // Folia start - region threading
+ final Long2ObjectOpenHashMap<List<NewChunkHolder>> sectionToUpdates = new Long2ObjectOpenHashMap<>();
+ final List<NewChunkHolder> thisRegionHolders = new ArrayList<>();
+
+ final int regionShift = this.world.moonrise$getRegionChunkShift();
+ final ThreadedRegionizer.ThreadedRegion<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> thisRegion
+ = TickRegionScheduler.getCurrentRegion();
+
+ for (final NewChunkHolder holder : changedFullStatus) {
+ final int regionX = holder.chunkX >> regionShift;
+ final int regionZ = holder.chunkZ >> regionShift;
+ final long holderSectionKey = CoordinateUtils.getChunkKey(regionX, regionZ);
+
+ // region may be null
+ if (thisRegion != null && this.world.regioniser.getRegionAtUnsynchronised(holder.chunkX, holder.chunkZ) == thisRegion) {
+ thisRegionHolders.add(holder);
+ } else {
+ sectionToUpdates.computeIfAbsent(holderSectionKey, (final long keyInMap) -> {
+ return new ArrayList<>();
+ }).add(holder);
+ }
+ }
+ if (!thisRegionHolders.isEmpty()) {
+ thisRegion.getData().getHolderManagerRegionData().pendingFullLoadUpdate.addAll(thisRegionHolders);
+ }
+
+ if (!sectionToUpdates.isEmpty()) {
+ for (final Iterator<Long2ObjectMap.Entry<List<NewChunkHolder>>> iterator = sectionToUpdates.long2ObjectEntrySet().fastIterator();
+ iterator.hasNext();) {
+ final Long2ObjectMap.Entry<List<NewChunkHolder>> entry = iterator.next();
+ final long sectionKey = entry.getLongKey();
+
+ final int chunkX = CoordinateUtils.getChunkX(sectionKey) << regionShift;
+ final int chunkZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift;
+
+ final List<NewChunkHolder> regionHolders = entry.getValue();
+ this.taskScheduler.scheduleChunkTaskEventually(chunkX, chunkZ, () -> {
+ ChunkHolderManager.this.getCurrentRegionData().pendingFullLoadUpdate.addAll(regionHolders);
+ ChunkHolderManager.this.processPendingFullUpdate();
+ }, Priority.HIGHEST);
+
+ }
+ }
+ // Folia end - region threading
}
private void removeChunkHolder(final NewChunkHolder holder) {
holder.onUnload();
- this.autoSaveQueue.remove(holder);
+ this.getCurrentRegionData().autoSaveQueue.remove(holder); // Folia - region threading
PlatformHooks.get().onChunkHolderDelete(this.world, holder.vanillaChunkHolder);
this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ));
}
@@ -1063,7 +_,7 @@
throw new IllegalStateException("Cannot unload chunks recursively");
}
final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift
- final List<ChunkUnloadQueue.SectionToUnload> unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions();
+ final List<ChunkUnloadQueue.SectionToUnload> unloadSectionsForRegion = this.unloadQueue.retrieveForCurrentRegion(); // Folia - threaded regions
int unloadCountTentative = 0;
for (final ChunkUnloadQueue.SectionToUnload sectionRef : unloadSectionsForRegion) {
final ChunkUnloadQueue.UnloadSection section
@@ -1381,7 +_,13 @@
// only call on tick thread
private boolean processPendingFullUpdate() {
- final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
+ // Folia start - region threading
+ final HolderManagerRegionData data = this.getCurrentRegionData();
+ if (data == null) {
+ return false;
+ }
+ final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = data.pendingFullLoadUpdate;
+ // Folia end - region threading
boolean ret = false;
@@ -1392,9 +_,7 @@
ret |= holder.handleFullStatusChange(changedFullStatus);
if (!changedFullStatus.isEmpty()) {
- for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
- pendingFullLoadUpdate.add(changedFullStatus.get(i));
- }
+ this.addChangedStatuses(changedFullStatus); // Folia - region threading
changedFullStatus.clear();
}
}

View File

@ -1,75 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
@@ -122,7 +_,7 @@
public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor compressionExecutor;
public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor saveExecutor;
- private final PrioritisedTaskQueue mainThreadExecutor = new PrioritisedTaskQueue();
+ // Folia - regionised ticking
public final ChunkHolderManager chunkHolderManager;
@@ -337,14 +_,13 @@
};
// this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions
- this.scheduleChunkTask(chunkX, chunkZ, crash, Priority.BLOCKING);
+ this.scheduleChunkTaskEventually(chunkX, chunkZ, crash, Priority.BLOCKING); // Folia - region threading
// so, make the main thread pick it up
((ChunkSystemMinecraftServer)this.world.getServer()).moonrise$setChunkSystemCrash(new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException));
}
public boolean executeMainThreadTask() {
- TickThread.ensureTickThread("Cannot execute main thread task off-main");
- return this.mainThreadExecutor.executeTask();
+ throw new UnsupportedOperationException("Use regionised ticking hooks"); // Folia - regionised ticking
}
public void raisePriority(final int x, final int z, final Priority priority) {
@@ -829,7 +_,7 @@
*/
@Deprecated
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final Priority priority) {
- return this.mainThreadExecutor.queueTask(run, priority);
+ throw new UnsupportedOperationException(); // Folia - regionised ticking
}
public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) {
@@ -838,7 +_,7 @@
public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run,
final Priority priority) {
- return this.mainThreadExecutor.createTask(run, priority);
+ return io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.createChunkTask(this.world, chunkX, chunkZ, run, priority); // Folia - regionised ticking
}
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) {
@@ -847,8 +_,26 @@
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run,
final Priority priority) {
- return this.mainThreadExecutor.queueTask(run, priority);
- }
+ return io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(this.world, chunkX, chunkZ, run, priority); // Folia - regionised ticking
+ }
+
+ // Folia start - region threading
+ // this function is guaranteed to never touch the ticket lock or schedule lock
+ // yes, this IS a hack so that we can avoid deadlock due to region threading introducing the
+ // ticket lock in the schedule logic
+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTaskEventually(final int chunkX, final int chunkZ, final Runnable run) {
+ return this.scheduleChunkTaskEventually(chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTaskEventually(final int chunkX, final int chunkZ, final Runnable run,
+ final Priority priority) {
+ final PrioritisedExecutor.PrioritisedTask ret = this.createChunkTask(chunkX, chunkZ, run, priority);
+ this.world.taskQueueRegionData.pushGlobalChunkTask(() -> {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(ChunkTaskScheduler.this.world, chunkX, chunkZ, run, priority);
+ });
+ return ret;
+ }
+ // Folia end - region threading
public boolean halt(final boolean sync, final long maxWaitNS) {
this.radiusAwareGenExecutor.halt();

View File

@ -1,33 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
@@ -1359,10 +_,10 @@
private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) {
// Update progress listener for LevelLoadingScreen
if (chunk != null) {
- final ChunkProgressListener progressListener = this.world.getChunkSource().chunkMap.progressListener;
+ final ChunkProgressListener progressListener = null; // Folia - threaded regions
if (progressListener != null) {
final ChunkStatus finalStatus = status;
- this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> {
+ this.scheduler.scheduleChunkTaskEventually(this.chunkX, this.chunkZ, () -> { // Folia - threaded regions
progressListener.onStatusChange(this.vanillaChunkHolder.getPos(), finalStatus);
});
}
@@ -1383,7 +_,7 @@
}
// must be scheduled to main, we do not trust the callback to not do anything stupid
- this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> {
+ this.scheduler.scheduleChunkTaskEventually(this.chunkX, this.chunkZ, () -> { // Folia - region threading
for (final Consumer<ChunkAccess> consumer : consumers) {
try {
consumer.accept(chunk);
@@ -1411,7 +_,7 @@
}
// must be scheduled to main, we do not trust the callback to not do anything stupid
- this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> {
+ this.scheduler.scheduleChunkTaskEventually(this.chunkX, this.chunkZ, () -> { // Folia - region threading
for (final Consumer<LevelChunk> consumer : consumers) {
try {
consumer.accept(chunk);

View File

@ -1,20 +0,0 @@
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -1940,6 +_,17 @@
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ // Folia start - region threading
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, currChunkX, currChunkZ, 4)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ continue;
+ }
+ }
+ // Folia end - region threading
final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks);
if (chunk == null) {

View File

@ -1,174 +0,0 @@
--- a/io/papermc/paper/entity/activation/ActivationRange.java
+++ b/io/papermc/paper/entity/activation/ActivationRange.java
@@ -48,33 +_,34 @@
private static int checkInactiveWakeup(final Entity entity) {
final Level world = entity.level();
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - threaded regions
final SpigotWorldConfig config = world.spigotConfig;
- final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+ final long inactiveFor = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() - entity.activatedTick; // Folia - threaded regions
if (entity.activationType == ActivationType.VILLAGER) {
- if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
- world.wakeupInactiveRemainingVillagers--;
+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && worldData.wakeupInactiveRemainingVillagers > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingVillagers--; // Folia - threaded regions
return config.wakeUpInactiveVillagersFor;
}
} else if (entity.activationType == ActivationType.ANIMAL) {
- if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
- world.wakeupInactiveRemainingAnimals--;
+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && worldData.wakeupInactiveRemainingAnimals > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingAnimals--; // Folia - threaded regions
return config.wakeUpInactiveAnimalsFor;
}
} else if (entity.activationType == ActivationType.FLYING_MONSTER) {
- if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
- world.wakeupInactiveRemainingFlying--;
+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && worldData.wakeupInactiveRemainingFlying > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingFlying--; // Folia - threaded regions
return config.wakeUpInactiveFlyingFor;
}
} else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
- if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
- world.wakeupInactiveRemainingMonsters--;
+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && worldData.wakeupInactiveRemainingMonsters > 0) { // Folia - threaded regions
+ worldData.wakeupInactiveRemainingMonsters--; // Folia - threaded regions
return config.wakeUpInactiveMonstersFor;
}
}
return -1;
}
- static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0);
+ //static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0); // Folia - threaded regions - replaced by local variable
/**
* These entities are excluded from Activation range checks.
@@ -122,10 +_,11 @@
final int waterActivationRange = world.spigotConfig.waterActivationRange;
final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
- world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
- world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
- world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
- world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - threaded regions
+ worldData.wakeupInactiveRemainingAnimals = Math.min(worldData.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); // Folia - threaded regions
+ worldData.wakeupInactiveRemainingVillagers = Math.min(worldData.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); // Folia - threaded regions
+ worldData.wakeupInactiveRemainingMonsters = Math.min(worldData.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); // Folia - threaded regions
+ worldData.wakeupInactiveRemainingFlying = Math.min(worldData.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); // Folia - threaded regions
int maxRange = Math.max(monsterActivationRange, animalActivationRange);
maxRange = Math.max(maxRange, raiderActivationRange);
@@ -135,30 +_,37 @@
maxRange = Math.max(maxRange, villagerActivationRange);
maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange);
- for (final Player player : world.players()) {
- player.activatedTick = MinecraftServer.currentTick;
+ for (final Player player : world.getLocalPlayers()) { // Folia - region threading
+ player.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading
if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) {
continue;
}
final int worldHeight = world.getHeight();
- ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange);
- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange);
- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange);
- ActivationType.WATER.boundingBox = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange);
- ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange);
- ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange);
+ final AABB maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange); // Folia - threaded regions
+ final AABB[] bbByType = new AABB[ActivationType.values().length]; // Folia - threaded regions
+ bbByType[ActivationType.MISC.ordinal()] = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.RAIDER.ordinal()] = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.ANIMAL.ordinal()] = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.MONSTER.ordinal()] = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.WATER.ordinal()] = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.FLYING_MONSTER.ordinal()] = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange); // Folia - threaded regions
+ bbByType[ActivationType.VILLAGER.ordinal()] = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange); // Folia - threaded regions
- final java.util.List<Entity> entities = world.getEntities((Entity) null, ActivationRange.maxBB, e -> true);
+ final java.util.List<Entity> entities = new java.util.ArrayList<>(); // Folia - region ticking - bypass getEntities thread check, we perform a check on the entities later
+ ((net.minecraft.server.level.ServerLevel)world).moonrise$getEntityLookup().getEntities((Entity)null, maxBB, entities, null); // Folia - region ticking - bypass getEntities thread check, we perform a check on the entities later
final boolean tickMarkers = world.paperConfig().entities.markers.tick;
for (final Entity entity : entities) {
+ // Folia start - region ticking
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+ continue;
+ }
+ // Folia end - region ticking
if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) {
continue;
}
- ActivationRange.activateEntity(entity);
+ ActivationRange.activateEntity(entity, bbByType); // Folia - threaded regions
}
}
}
@@ -168,14 +_,14 @@
*
* @param entity
*/
- private static void activateEntity(final Entity entity) {
- if (MinecraftServer.currentTick > entity.activatedTick) {
+ private static void activateEntity(final Entity entity, final AABB[] bbByType) { // Folia - threaded regions
+ if (io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() > entity.activatedTick) { // Folia - threaded regions
if (entity.defaultActivationState) {
- entity.activatedTick = MinecraftServer.currentTick;
+ entity.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - threaded regions
return;
}
- if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) {
- entity.activatedTick = MinecraftServer.currentTick;
+ if (bbByType[entity.activationType.ordinal()].intersects(entity.getBoundingBox())) { // Folia - threaded regions
+ entity.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - threaded regions
}
}
}
@@ -189,6 +_,7 @@
*/
public static int checkEntityImmunities(final Entity entity) { // return # of ticks to get immunity
final SpigotWorldConfig config = entity.level().spigotConfig;
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = entity.level().getCurrentWorldData(); // Folia - threaded regions
final int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
if (inactiveWakeUpImmunity > -1) {
return inactiveWakeUpImmunity;
@@ -196,10 +_,10 @@
if (entity.getRemainingFireTicks() > 0) {
return 2;
}
- if (entity.activatedImmunityTick >= MinecraftServer.currentTick) {
+ if (entity.activatedImmunityTick >= io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick()) { // Folia - threaded regions
return 1;
}
- final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+ final long inactiveFor = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() - entity.activatedTick; // Folia - threaded regions
if ((entity.activationType != ActivationType.WATER && entity.isInWater() && entity.isPushedByFluid())) {
return 100;
}
@@ -296,16 +_,16 @@
return true;
}
- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
+ boolean isActive = entity.activatedTick >= io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - threaded regions
entity.isTemporarilyActive = false;
// Should this entity tick?
if (!isActive) {
- if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
+ if ((io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() - entity.activatedTick - 1) % 20 == 0) { // Folia - threaded regions
// Check immunities every 20 ticks.
final int immunity = checkEntityImmunities(entity);
if (immunity >= 0) {
- entity.activatedTick = MinecraftServer.currentTick + immunity;
+ entity.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + immunity; // Folia - threaded regions
} else {
entity.isTemporarilyActive = true;
}

View File

@ -1,19 +0,0 @@
--- a/io/papermc/paper/redstone/RedstoneWireTurbo.java
+++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java
@@ -829,14 +_,14 @@
j = getMaxCurrentStrength(upd, j);
int l = 0;
- wire.shouldSignal = false;
+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = false; // Folia - region threading
// Unfortunately, World.isBlockIndirectlyGettingPowered is complicated,
// and I'm not ready to try to replicate even more functionality from
// elsewhere in Minecraft into this accelerator. So sadly, we must
// suffer the performance hit of this very expensive call. If there
// is consistency to what this call returns, we may be able to cache it.
final int k = worldIn.getBestNeighborSignal(upd.self);
- wire.shouldSignal = true;
+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = true; // Folia - region threading
// The variable 'k' holds the maximum redstone power value of any adjacent blocks.
// If 'k' has the highest level of all neighbors, then the power level of this

View File

@ -1,229 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/RegionShutdownThread.java
@@ -1,0 +_,226 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.moonrise.common.util.WorldUtil;
+import com.mojang.logging.LogUtils;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.ChunkPos;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.slf4j.Logger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.util.TickThread {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+
+ ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> shuttingDown;
+
+ public RegionShutdownThread(final String name) {
+ super(name);
+ this.setUncaughtExceptionHandler((thread, thr) -> {
+ LOGGER.error("Error shutting down server", thr);
+ });
+ }
+
+ static ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> getRegion() {
+ final Thread currentThread = Thread.currentThread();
+ if (currentThread instanceof RegionShutdownThread shutdownThread) {
+ return shutdownThread.shuttingDown;
+ }
+ return null;
+ }
+
+
+ static RegionizedWorldData getWorldData() {
+ final Thread currentThread = Thread.currentThread();
+ if (currentThread instanceof RegionShutdownThread shutdownThread) {
+ // no fast path for shutting down
+ if (shutdownThread.shuttingDown != null) {
+ return shutdownThread.shuttingDown.getData().world.worldRegionData.get();
+ }
+ }
+ return null;
+ }
+
+ // The region shutdown thread bypasses all tick thread checks, which will allow us to execute global saves
+ // it will not however let us perform arbitrary sync loads, arbitrary world state lookups simply because
+ // the data required to do that is regionised, and we can only access it when we OWN the region, and we do not.
+ // Thus, the only operation that the shutdown thread will perform
+
+ private void saveLevelData(final ServerLevel world) {
+ try {
+ world.saveLevelData(true);
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to save level data for " + world.getWorld().getName(), thr);
+ }
+ }
+
+ private void finishTeleportations(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region,
+ final ServerLevel world) {
+ try {
+ this.shuttingDown = region;
+ final List<ServerLevel.PendingTeleport> pendingTeleports = world.removeAllRegionTeleports();
+ if (pendingTeleports.isEmpty()) {
+ return;
+ }
+ final ChunkPos center = region.getCenterChunk();
+ LOGGER.info("Completing " + pendingTeleports.size() + " pending teleports in region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'");
+ for (final ServerLevel.PendingTeleport pendingTeleport : pendingTeleports) {
+ LOGGER.info("Completing teleportation to target position " + pendingTeleport.to());
+
+ // first, add entities to entity chunk so that they will be saved
+ for (final Entity.EntityTreeNode node : pendingTeleport.rootVehicle().getFullTree()) {
+ // assume that world and position are set to destination here
+ node.root.setLevel(world); // in case the pending teleport is from a portal before it finds the exact destination
+ world.moonrise$getEntityLookup().addEntityForShutdownTeleportComplete(node.root);
+ }
+
+ // then, rebuild the passenger tree so that when saving only the root vehicle will be written - and if
+ // there are any player passengers, that the later player saving will save the tree
+ pendingTeleport.rootVehicle().restore();
+
+ // now we are finished
+ LOGGER.info("Completed teleportation to target position " + pendingTeleport.to());
+ }
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to complete pending teleports", thr);
+ } finally {
+ this.shuttingDown = null;
+ }
+ }
+
+ private void saveRegionChunks(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region,
+ final boolean last) {
+ ChunkPos center = null;
+ try {
+ this.shuttingDown = region;
+ center = region.getCenterChunk();
+ LOGGER.info("Saving chunks around region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'");
+ region.regioniser.world.moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true, false, last, false);
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to save chunks for region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'", thr);
+ } finally {
+ this.shuttingDown = null;
+ }
+ }
+
+ private void haltChunkSystem(final ServerLevel world) {
+ try {
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.close(false, true, true, false, false);
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to halt chunk system for world '" + world.getWorld().getName() + "'", thr);
+ }
+ }
+
+ private void closePlayerInventories(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region) {
+ ChunkPos center = null;
+ try {
+ this.shuttingDown = region;
+ center = region.getCenterChunk();
+
+ final RegionizedWorldData worldData = region.regioniser.world.worldRegionData.get();
+
+ for (final ServerPlayer player : worldData.getLocalPlayers()) {
+ try {
+ // close inventory
+ if (player.containerMenu != player.inventoryMenu) {
+ player.closeContainer(InventoryCloseEvent.Reason.DISCONNECT);
+ }
+
+ // drop carried item
+ if (!player.containerMenu.getCarried().isEmpty()) {
+ ItemStack carried = player.containerMenu.getCarried();
+ player.containerMenu.setCarried(ItemStack.EMPTY);
+ player.drop(carried, false);
+ }
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to close player inventory for player: " + player, thr);
+ }
+ }
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to close player inventories for region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'", thr);
+ } finally {
+ this.shuttingDown = null;
+ }
+ }
+
+ @Override
+ public final void run() {
+ // await scheduler termination
+ LOGGER.info("Awaiting scheduler termination for 60s...");
+ if (TickRegions.getScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) {
+ LOGGER.info("Scheduler halted");
+ } else {
+ LOGGER.warn("Scheduler did not terminate within 60s, proceeding with shutdown anyways");
+ TickRegions.getScheduler().dumpAliveThreadTraces("Did not shut down in time");
+ }
+
+ MinecraftServer.getServer().stopServer(); // stop part 1: most logic, kicking players, plugins, etc
+ // halt all chunk systems first so that any in-progress chunk generation stops
+ LOGGER.info("Halting chunk systems...");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ try {
+ world.moonrise$getChunkTaskScheduler().halt(false, 0L);
+ } catch (final Throwable throwable) {
+ LOGGER.error("Failed to soft halt chunk system for world '" + world.getWorld().getName() + "'", throwable);
+ }
+ }
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ this.haltChunkSystem(world);
+ }
+ LOGGER.info("Halted chunk systems");
+
+ LOGGER.info("Finishing pending teleports...");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>>
+ regions = new ArrayList<>();
+ world.regioniser.computeForAllRegionsUnsynchronised(regions::add);
+
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ this.finishTeleportations(regions.get(i), world);
+ }
+ }
+ LOGGER.info("Finished pending teleports");
+
+ LOGGER.info("Saving all worlds");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ LOGGER.info("Saving world data for world '" + WorldUtil.getWorldName(world) + "'");
+
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>>
+ regions = new ArrayList<>();
+ world.regioniser.computeForAllRegionsUnsynchronised(regions::add);
+
+ LOGGER.info("Closing player inventories...");
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ this.closePlayerInventories(regions.get(i));
+ }
+ LOGGER.info("Closed player inventories");
+
+ LOGGER.info("Saving chunks...");
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ this.saveRegionChunks(regions.get(i), (i + 1) == len);
+ }
+ LOGGER.info("Saved chunks");
+
+ LOGGER.info("Saving level data...");
+ this.saveLevelData(world);
+ LOGGER.info("Saved level data");
+
+ LOGGER.info("Saved world data for world '" + WorldUtil.getWorldName(world) + "'");
+ }
+ LOGGER.info("Saved all worlds");
+
+ // Note: only save after world data and pending teleportations
+ LOGGER.info("Saving all player data...");
+ MinecraftServer.getServer().getPlayerList().saveAll();
+ LOGGER.info("Saved all player data");
+
+ MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc)
+ // done, part 2 should call exit()
+ }
+}

View File

@ -1,238 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/RegionizedData.java
@@ -1,0 +_,235 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.util.Validate;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.server.level.ServerLevel;
+import javax.annotation.Nullable;
+import java.util.function.Supplier;
+
+/**
+ * Use to manage data that needs to be regionised.
+ * <p>
+ * <b>Note:</b> that unlike {@link ThreadLocal}, regionised data is not deleted once the {@code RegionizedData} object is GC'd.
+ * The data is held in reference to the world it resides in.
+ * </p>
+ * <P>
+ * <b>Note:</b> Keep in mind that when regionised ticking is disabled, the entire server is considered a single region.
+ * That is, the data may or may not cross worlds. As such, the {@code RegionizedData} object must be instanced
+ * per world when appropriate, as it is no longer guaranteed that separate worlds contain separate regions.
+ * See below for more details on instancing per world.
+ * </P>
+ * <p>
+ * Regionised data may be <b>world-checked</b>. That is, {@link #get()} may throw an exception if the current
+ * region's world does not match the {@code RegionizedData}'s world. Consider the usages of {@code RegionizedData} below
+ * see why the behavior may or may not be desirable:
+ * <pre>
+ * {@code
+ * public class EntityTickList {
+ * private final List<Entity> entities = new ArrayList<>();
+ *
+ * public void addEntity(Entity e) {
+ * this.entities.add(e);
+ * }
+ *
+ * public void removeEntity(Entity e) {
+ * this.entities.remove(e);
+ * }
+ * }
+ *
+ * public class World {
+ *
+ * // callback is left out of this example
+ * // note: world != null here
+ * public final RegionizedData<EntityTickList> entityTickLists =
+ * new RegionizedData<>(this, () -> new EntityTickList(), ...);
+ *
+ * public void addTickingEntity(Entity e) {
+ * // What we expect here is that this world is the
+ * // current ticking region's world.
+ * // If that is true, then calling this.entityTickLists.get()
+ * // will retrieve the current region's EntityTickList
+ * // for this world, which is fine since the current
+ * // region is contained within this world.
+ *
+ * // But if the current region's world is not this world,
+ * // and if the world check is disabled, then we will actually
+ * // retrieve _this_ world's EntityTickList for the region,
+ * // and NOT the EntityTickList for the region's world.
+ * // This is because the RegionizedData object is instantiated
+ * // per world.
+ * this.entityTickLists.get().addEntity(e);
+ * }
+ * }
+ *
+ * public class TickTimes {
+ *
+ * private final List<Long> tickTimesNS = new ArrayList<>();
+ *
+ * public void completeTick(long timeNS) {
+ * this.tickTimesNS.add(timeNS);
+ * }
+ *
+ * public double getAverageTickLengthMS() {
+ * double sum = 0.0;
+ * for (long time : tickTimesNS) {
+ * sum += (double)time;
+ * }
+ * return (sum / this.tickTimesNS.size()) / 1.0E6; // 1ms = 1 million ns
+ * }
+ * }
+ *
+ * public class Server {
+ * public final List<World> worlds = ...;
+ *
+ * // callback is left out of this example
+ * // note: world == null here, because this RegionizedData object
+ * // is not instantiated per world, but rather globally.
+ * public final RegionizedData<TickTimes> tickTimes =
+ * new RegionizedData<>(null, () -> new TickTimes(), ...);
+ * }
+ * }
+ * </pre>
+ * In general, it is advised that if a RegionizedData object is instantiated <i>per world</i>, that world checking
+ * is enabled for it by passing the world to the constructor.
+ * </p>
+ */
+public final class RegionizedData<T> {
+
+ private final ServerLevel world;
+ private final Supplier<T> initialValueSupplier;
+ private final RegioniserCallback<T> callback;
+
+ /**
+ * Creates a regionised data holder. The provided initial value supplier may not be null, and it must
+ * never produce {@code null} values.
+ * <p>
+ * Note that the supplier or regioniser callback may be used while the region lock is held, so any blocking
+ * operations may deadlock the entire server and as such the function should be completely non-blocking
+ * and must complete in a timely manner.
+ * </p>
+ * <p>
+ * If the provided world is {@code null}, then the world checks are disabled. The world should only ever
+ * be {@code null} if the data is specifically not specific to worlds. For example, using {@code null}
+ * for an entity tick list is invalid since the entities are tied to a world <b>and</b> region,
+ * however using {@code null} for tasks to run at the end of a tick is valid since the tasks are tied to
+ * region <b>only</b>.
+ * </p>
+ * @param world The world in which the region data resides.
+ * @param supplier Initial value supplier used to lazy initialise region data.
+ * @param callback Region callback to manage this regionised data.
+ */
+ public RegionizedData(final ServerLevel world, final Supplier<T> supplier, final RegioniserCallback<T> callback) {
+ this.world = world;
+ this.initialValueSupplier = Validate.notNull(supplier, "Supplier may not be null.");
+ this.callback = Validate.notNull(callback, "Regioniser callback may not be null.");
+ }
+
+ T createNewValue() {
+ return Validate.notNull(this.initialValueSupplier.get(), "Initial value supplier may not return null");
+ }
+
+ RegioniserCallback<T> getCallback() {
+ return this.callback;
+ }
+
+ /**
+ * Returns the current data type for the current ticking region. If there is no region, returns {@code null}.
+ * @return the current data type for the current ticking region. If there is no region, returns {@code null}.
+ * @throws IllegalStateException If the following are true: The server is in region ticking mode,
+ * this {@code RegionizedData}'s world is not {@code null},
+ * and the current ticking region's world does not match this {@code RegionizedData}'s world.
+ */
+ public @Nullable T get() {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
+ TickRegionScheduler.getCurrentRegion();
+
+ if (region == null) {
+ return null;
+ }
+
+ if (this.world != null && this.world != region.getData().world) {
+ throw new IllegalStateException("World check failed: expected world: " + this.world.getWorld().getKey() + ", region world: " + region.getData().world.getWorld().getKey());
+ }
+
+ return region.getData().getOrCreateRegionizedData(this);
+ }
+
+ /**
+ * Class responsible for handling merge / split requests from the regioniser.
+ * <p>
+ * It is critical to note that each function is called while holding the region lock.
+ * </p>
+ */
+ public static interface RegioniserCallback<T> {
+
+ /**
+ * Completely merges the data in {@code from} to {@code into}.
+ * <p>
+ * <b>Calculating Tick Offsets:</b>
+ * Sometimes data stores absolute tick deadlines, and since regions tick independently, absolute deadlines
+ * are not comparable across regions. Consider absolute deadlines {@code deadlineFrom, deadlineTo} in
+ * regions {@code from} and {@code into} respectively. We can calculate the relative deadline for the from
+ * region with {@code relFrom = deadlineFrom - currentTickFrom}. Then, we can use the same equation for
+ * computing the absolute deadline in region {@code into} that has the same relative deadline as {@code from}
+ * as {@code deadlineTo = relFrom + currentTickTo}. By substituting {@code relFrom} as {@code deadlineFrom - currentTickFrom},
+ * we finally have that {@code deadlineTo = deadlineFrom + (currentTickTo - currentTickFrom)} and
+ * that we can use an offset {@code fromTickOffset = currentTickTo - currentTickFrom} to calculate
+ * {@code deadlineTo} as {@code deadlineTo = deadlineFrom + fromTickOffset}.
+ * </p>
+ * <p>
+ * <b>Critical Notes:</b>
+ * <li>
+ * <ul>
+ * This function is called while the region lock is held, so any blocking operations may
+ * deadlock the entire server and as such the function should be completely non-blocking and must complete
+ * in a timely manner.
+ * </ul>
+ * <ul>
+ * This function may not throw any exceptions, or the server will be left in an unrecoverable state.
+ * </ul>
+ * </li>
+ * </p>
+ *
+ * @param from The data to merge from.
+ * @param into The data to merge into.
+ * @param fromTickOffset The addend to absolute tick deadlines stored in the {@code from} region to adjust to the into region.
+ */
+ public void merge(final T from, final T into, final long fromTickOffset);
+
+ /**
+ * Splits the data in {@code from} into {@code dataSet}.
+ * <p>
+ * The chunk coordinate to region section coordinate bit shift amount is provided in {@code chunkToRegionShift}.
+ * To convert from chunk coordinates to region coordinates and keys, see the code below:
+ * <pre>
+ * {@code
+ * int chunkX = ...;
+ * int chunkZ = ...;
+ *
+ * int regionSectionX = chunkX >> chunkToRegionShift;
+ * int regionSectionZ = chunkZ >> chunkToRegionShift;
+ * long regionSectionKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(regionSectionX, regionSectionZ);
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * The {@code regionToData} hashtable provides a lookup from {@code regionSectionKey} (see above) to the
+ * data that is owned by the region which occupies the region section.
+ * </p>
+ * <p>
+ * Unlike {@link #merge(Object, Object, long)}, there is no absolute tick offset provided. This is because
+ * the new regions formed from the split will start at the same tick number, and so no adjustment is required.
+ * </p>
+ *
+ * @param from The data to split from.
+ * @param chunkToRegionShift The signed right-shift value used to convert chunk coordinates into region section coordinates.
+ * @param regionToData Lookup hash table from region section key to .
+ * @param dataSet The data set to split into.
+ */
+ public void split(
+ final T from, final int chunkToRegionShift,
+ final Long2ReferenceOpenHashMap<T> regionToData, final ReferenceOpenHashSet<T> dataSet
+ );
+ }
+}

View File

@ -1,458 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/RegionizedServer.java
@@ -1,0 +_,455 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool;
+import ca.spottedleaf.moonrise.common.util.TickThread;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler;
+import net.minecraft.CrashReport;
+import net.minecraft.ReportedException;
+import net.minecraft.network.Connection;
+import net.minecraft.network.PacketListener;
+import net.minecraft.network.PacketSendListener;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.network.protocol.common.ClientboundDisconnectPacket;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.dedicated.DedicatedServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.world.level.GameRules;
+import org.bukkit.Bukkit;
+import org.slf4j.Logger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
+
+public final class RegionizedServer {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final RegionizedServer INSTANCE = new RegionizedServer();
+
+ public final RegionizedTaskQueue taskQueue = new RegionizedTaskQueue();
+
+ private final CopyOnWriteArrayList<ServerLevel> worlds = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<Connection> connections = new CopyOnWriteArrayList<>();
+
+ private final MultiThreadedQueue<Runnable> globalTickQueue = new MultiThreadedQueue<>();
+
+ private final GlobalTickTickHandle tickHandle = new GlobalTickTickHandle(this);
+
+ public static RegionizedServer getInstance() {
+ return INSTANCE;
+ }
+
+ public void addConnection(final Connection conn) {
+ this.connections.add(conn);
+ }
+
+ public boolean removeConnection(final Connection conn) {
+ return this.connections.remove(conn);
+ }
+
+ public void addWorld(final ServerLevel world) {
+ this.worlds.add(world);
+ }
+
+ public void init() {
+ // call init event _before_ scheduling anything
+ new RegionizedServerInitEvent().callEvent();
+
+ // now we can schedule
+ this.tickHandle.setInitialStart(System.nanoTime() + TickRegionScheduler.TIME_BETWEEN_TICKS);
+ TickRegions.getScheduler().scheduleRegion(this.tickHandle);
+ TickRegions.getScheduler().init();
+ }
+
+ public void invalidateStatus() {
+ this.lastServerStatus = 0L;
+ }
+
+ public void addTaskWithoutNotify(final Runnable run) {
+ this.globalTickQueue.add(run);
+ }
+
+ public void addTask(final Runnable run) {
+ this.addTaskWithoutNotify(run);
+ TickRegions.getScheduler().setHasTasks(this.tickHandle);
+ }
+
+ /**
+ * Returns the current tick of the region ticking.
+ * @throws IllegalStateException If there is no current region.
+ */
+ public static long getCurrentTick() throws IllegalStateException {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
+ TickRegionScheduler.getCurrentRegion();
+ if (region == null) {
+ if (TickThread.isShutdownThread()) {
+ return 0L;
+ }
+ throw new IllegalStateException("No currently ticking region");
+ }
+ return region.getData().getCurrentTick();
+ }
+
+ public static boolean isGlobalTickThread() {
+ return INSTANCE.tickHandle == TickRegionScheduler.getCurrentTickingTask();
+ }
+
+ public static void ensureGlobalTickThread(final String reason) {
+ if (!isGlobalTickThread()) {
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ public static TickRegionScheduler.RegionScheduleHandle getGlobalTickData() {
+ return INSTANCE.tickHandle;
+ }
+
+ private static final class GlobalTickTickHandle extends TickRegionScheduler.RegionScheduleHandle {
+
+ private final RegionizedServer server;
+
+ private final AtomicBoolean scheduled = new AtomicBoolean();
+ private final AtomicBoolean ticking = new AtomicBoolean();
+
+ public GlobalTickTickHandle(final RegionizedServer server) {
+ super(null, SchedulerThreadPool.DEADLINE_NOT_SET);
+ this.server = server;
+ }
+
+ /**
+ * Only valid to call BEFORE scheduled!!!!
+ */
+ final void setInitialStart(final long start) {
+ if (this.scheduled.getAndSet(true)) {
+ throw new IllegalStateException("Double scheduling global tick");
+ }
+ this.updateScheduledStart(start);
+ }
+
+ @Override
+ protected boolean tryMarkTicking() {
+ return !this.ticking.getAndSet(true);
+ }
+
+ @Override
+ protected boolean markNotTicking() {
+ return this.ticking.getAndSet(false);
+ }
+
+ @Override
+ protected void tickRegion(final int tickCount, final long startTime, final long scheduledEnd) {
+ this.drainTasks();
+ this.server.globalTick(tickCount);
+ }
+
+ private void drainTasks() {
+ while (this.runOneTask());
+ }
+
+ private boolean runOneTask() {
+ final Runnable run = this.server.globalTickQueue.poll();
+ if (run == null) {
+ return false;
+ }
+
+ // TODO try catch?
+ run.run();
+
+ return true;
+ }
+
+ @Override
+ protected boolean runRegionTasks(final BooleanSupplier canContinue) {
+ do {
+ if (!this.runOneTask()) {
+ return false;
+ }
+ } while (canContinue.getAsBoolean());
+
+ return true;
+ }
+
+ @Override
+ protected boolean hasIntermediateTasks() {
+ return !this.server.globalTickQueue.isEmpty();
+ }
+ }
+
+ private long lastServerStatus;
+ private long tickCount;
+
+ /*
+ private final java.util.Random random = new java.util.Random(4L);
+ private final List<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<Void>> walkers =
+ new java.util.ArrayList<>();
+ static final int PLAYERS = 500;
+ static final int RAD_BLOCKS = 1000;
+ static final int RAD = RAD_BLOCKS >> 4;
+ static final int RAD_BIG_BLOCKS = 100_000;
+ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4;
+ static final int VD = 4 + 12;
+ static final int BIG_PLAYERS = 250;
+ static final double WALK_CHANCE = 0.3;
+ static final double TP_CHANCE = 0.2;
+ static final double TASK_CHANCE = 0.2;
+
+ private ServerLevel getWorld() {
+ return this.worlds.get(0);
+ }
+
+ private void init2() {
+ for (int i = 0; i < PLAYERS; ++i) {
+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD;
+ int posX = this.random.nextInt(-rad, rad + 1);
+ int posZ = this.random.nextInt(-rad, rad + 1);
+
+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<Void> map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) {
+ @Override
+ protected void addCallback(Void parameter, int chunkX, int chunkZ) {
+ ServerLevel world = RegionizedServer.this.getWorld();
+ if (RegionizedServer.this.random.nextDouble() <= TASK_CHANCE) {
+ RegionizedServer.this.taskQueue.queueChunkTask(world, chunkX, chunkZ, () -> {
+ RegionizedServer.this.taskQueue.queueChunkTask(world, chunkX, chunkZ, () -> {});
+ });
+ }
+ world.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel(
+ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ)
+ );
+ }
+
+ @Override
+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) {
+ ServerLevel world = RegionizedServer.this.getWorld();
+ if (RegionizedServer.this.random.nextDouble() <= TASK_CHANCE) {
+ RegionizedServer.this.taskQueue.queueChunkTask(world, chunkX, chunkZ, () -> {
+ RegionizedServer.this.taskQueue.queueChunkTask(world, chunkX, chunkZ, () -> {});
+ });
+ }
+ world.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel(
+ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ)
+ );
+ }
+ };
+
+ map.add(posX, posZ, VD);
+
+ walkers.add(map);
+ }
+ }
+
+ private void randomWalk() {
+ if (this.walkers.isEmpty()) {
+ this.init2();
+ return;
+ }
+
+ for (int i = 0; i < PLAYERS; ++i) {
+ if (this.random.nextDouble() > WALK_CHANCE) {
+ continue;
+ }
+
+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<Void> map = this.walkers.get(i);
+
+ int updateX = this.random.nextInt(-1, 2);
+ int updateZ = this.random.nextInt(-1, 2);
+
+ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD);
+ }
+
+ for (int i = 0; i < PLAYERS; ++i) {
+ if (random.nextDouble() >= TP_CHANCE) {
+ continue;
+ }
+
+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD;
+ int posX = random.nextInt(-rad, rad + 1);
+ int posZ = random.nextInt(-rad, rad + 1);
+
+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<Void> map = walkers.get(i);
+
+ map.update(posX, posZ, VD);
+ }
+ }
+ */
+
+ private void globalTick(final int tickCount) {
+ /*
+ if (false) {
+ io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.main(null);
+ }
+ this.randomWalk();
+ */
+ ++this.tickCount;
+ // expire invalid click command callbacks
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue((int)this.tickCount);
+
+ // scheduler
+ ((FoliaGlobalRegionScheduler)Bukkit.getGlobalRegionScheduler()).tick();
+
+ // commands
+ ((DedicatedServer)MinecraftServer.getServer()).handleConsoleInputs();
+
+ // needs
+ // player ping sample
+ // world global tick
+ // connection tick
+
+ // tick player ping sample
+ this.tickPlayerSample();
+
+ // tick worlds
+ for (final ServerLevel world : this.worlds) {
+ this.globalTick(world, tickCount);
+ }
+
+ // tick connections
+ this.tickConnections();
+
+ // player list
+ MinecraftServer.getServer().getPlayerList().tick();
+ }
+
+ private void tickPlayerSample() {
+ final MinecraftServer mcServer = MinecraftServer.getServer();
+
+ final long currtime = System.nanoTime();
+
+ // player ping sample
+ // copied from MinecraftServer#tickServer
+ // note: we need to reorder setPlayers to be the last operation it does, rather than the first to avoid publishing
+ // an uncomplete status
+ if (currtime - this.lastServerStatus >= MinecraftServer.STATUS_EXPIRE_TIME_NANOS) {
+ this.lastServerStatus = currtime;
+ mcServer.rebuildServerStatus();
+ }
+ }
+
+ public static boolean isNotOwnedByGlobalRegion(final Connection conn) {
+ final PacketListener packetListener = conn.getPacketListener();
+
+ if (packetListener instanceof ServerGamePacketListenerImpl gamePacketListener) {
+ return !gamePacketListener.waitingForSwitchToConfig;
+ }
+
+ if (conn.getPacketListener() instanceof net.minecraft.server.network.ServerConfigurationPacketListenerImpl configurationPacketListener) {
+ return configurationPacketListener.switchToMain;
+ }
+
+ return false;
+ }
+
+ private void tickConnections() {
+ final List<Connection> connections = new ArrayList<>(this.connections);
+ Collections.shuffle(connections); // shuffle to prevent people from "gaming" the server by re-logging
+ for (final Connection conn : connections) {
+ if (!conn.becomeActive()) {
+ continue;
+ }
+
+ if (isNotOwnedByGlobalRegion(conn)) {
+ // we actually require that the owning regions remove the connection for us, as it is possible
+ // that ownership is transferred back to us
+ continue;
+ }
+
+ if (!conn.isConnected()) {
+ this.removeConnection(conn);
+ conn.handleDisconnection();
+ continue;
+ }
+
+ try {
+ conn.tick();
+ } catch (final Exception exception) {
+ if (conn.isMemoryConnection()) {
+ throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection"));
+ }
+
+ LOGGER.warn("Failed to handle packet for {}", conn.getLoggableAddress(MinecraftServer.getServer().logIPs()), exception);
+ MutableComponent ichatmutablecomponent = Component.literal("Internal server error");
+
+ conn.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> {
+ conn.disconnect(ichatmutablecomponent);
+ }));
+ conn.setReadOnly();
+ continue;
+ }
+ }
+ }
+
+ // A global tick only updates things like weather / worldborder, basically anything in the world that is
+ // NOT tied to a specific region, but rather shared amongst all of them.
+ private void globalTick(final ServerLevel world, final int tickCount) {
+ // needs
+ // worldborder tick
+ // advancing the weather cycle
+ // sleep status thing
+ // updating sky brightness
+ // time ticking (game time + daylight), plus PrimayLevelDat#getScheduledEvents ticking
+
+ // Typically, we expect there to be a running region to drain a world's global chunk tasks. However,
+ // this may not be the case - and thus, only the global tick thread can do anything.
+ world.taskQueueRegionData.drainGlobalChunkTasks();
+
+ // worldborder tick
+ this.tickWorldBorder(world);
+
+ // weather cycle
+ this.advanceWeatherCycle(world);
+
+ // sleep status
+ this.checkNightSkip(world);
+
+ // update raids
+ this.updateRaids(world);
+
+ // sky brightness
+ this.updateSkyBrightness(world);
+
+ // time ticking (TODO API synchronisation?)
+ this.tickTime(world, tickCount);
+
+ world.updateTickData();
+
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // required to eventually process ticket updates
+ }
+
+ private void updateRaids(final ServerLevel world) {
+ world.getRaids().globalTick();
+ }
+
+ private void checkNightSkip(final ServerLevel world) {
+ world.tickSleep();
+ }
+
+ private void advanceWeatherCycle(final ServerLevel world) {
+ world.advanceWeatherCycle();
+ }
+
+ private void updateSkyBrightness(final ServerLevel world) {
+ world.updateSkyBrightness();
+ }
+
+ private void tickWorldBorder(final ServerLevel world) {
+ world.getWorldBorder().tick();
+ }
+
+ private void tickTime(final ServerLevel world, final int tickCount) {
+ if (world.tickTime) {
+ if (world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ world.setDayTime(world.levelData.getDayTime() + (long)tickCount);
+ }
+ world.serverLevelData.setGameTime(world.serverLevelData.getGameTime() + (long)tickCount);
+ }
+ }
+
+ public static final record WorldLevelData(ServerLevel world, long nonRedstoneGameTime, long dayTime) {
+
+ }
+}

View File

@ -1,810 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
@@ -1,0 +_,807 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
+import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
+import ca.spottedleaf.concurrentutil.util.Priority;
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.util.Unit;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicLong;
+
+public final class RegionizedTaskQueue {
+
+ private static final TicketType<Unit> TASK_QUEUE_TICKET = TicketType.create("task_queue_ticket", (a, b) -> 0);
+
+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run) {
+ return this.createChunkTask(world, chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run, final Priority priority) {
+ return new PrioritisedQueue.ChunkBasedPriorityTask(world.taskQueueRegionData, chunkX, chunkZ, true, run, priority);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask createTickTaskQueue(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run) {
+ return this.createTickTaskQueue(world, chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask createTickTaskQueue(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run, final Priority priority) {
+ return new PrioritisedQueue.ChunkBasedPriorityTask(world.taskQueueRegionData, chunkX, chunkZ, false, run, priority);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask queueChunkTask(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run) {
+ return this.queueChunkTask(world, chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask queueChunkTask(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run, final Priority priority) {
+ final PrioritisedExecutor.PrioritisedTask ret = this.createChunkTask(world, chunkX, chunkZ, run, priority);
+
+ ret.queue();
+
+ return ret;
+ }
+
+ public PrioritisedExecutor.PrioritisedTask queueTickTaskQueue(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run) {
+ return this.queueTickTaskQueue(world, chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
+ public PrioritisedExecutor.PrioritisedTask queueTickTaskQueue(final ServerLevel world, final int chunkX, final int chunkZ,
+ final Runnable run, final Priority priority) {
+ final PrioritisedExecutor.PrioritisedTask ret = this.createTickTaskQueue(world, chunkX, chunkZ, run, priority);
+
+ ret.queue();
+
+ return ret;
+ }
+
+ public static final class WorldRegionTaskData {
+ private final ServerLevel world;
+ private final MultiThreadedQueue<Runnable> globalChunkTask = new MultiThreadedQueue<>();
+ private final ConcurrentLong2ReferenceChainedHashTable<ReferenceCountData> referenceCounters = new ConcurrentLong2ReferenceChainedHashTable<>();
+
+ public WorldRegionTaskData(final ServerLevel world) {
+ this.world = world;
+ }
+
+ private boolean executeGlobalChunkTask() {
+ final Runnable run = this.globalChunkTask.poll();
+ if (run != null) {
+ run.run();
+ return true;
+ }
+ return false;
+ }
+
+ public void drainGlobalChunkTasks() {
+ while (this.executeGlobalChunkTask());
+ }
+
+ public void pushGlobalChunkTask(final Runnable run) {
+ this.globalChunkTask.add(run);
+ }
+
+ private PrioritisedQueue getQueue(final boolean synchronise, final int chunkX, final int chunkZ, final boolean isChunkTask) {
+ final ThreadedRegionizer<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> regioniser = this.world.regioniser;
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region
+ = synchronise ? regioniser.getRegionAtSynchronised(chunkX, chunkZ) : regioniser.getRegionAtUnsynchronised(chunkX, chunkZ);
+ if (region == null) {
+ return null;
+ }
+ final RegionTaskQueueData taskQueueData = region.getData().getTaskQueueData();
+ return (isChunkTask ? taskQueueData.chunkQueue : taskQueueData.tickTaskQueue);
+ }
+
+ private void removeTicket(final long coord) {
+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel(
+ TASK_QUEUE_TICKET, coord, ChunkHolderManager.MAX_TICKET_LEVEL, Unit.INSTANCE
+ );
+ }
+
+ private void addTicket(final long coord) {
+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel(
+ TASK_QUEUE_TICKET, coord, ChunkHolderManager.MAX_TICKET_LEVEL, Unit.INSTANCE
+ );
+ }
+
+ private void processTicketUpdates(final long coord) {
+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord));
+ }
+
+ // note: only call on acquired referenceCountData
+ private void ensureTicketAdded(final long coord, final ReferenceCountData referenceCountData) {
+ if (!referenceCountData.addedTicket) {
+ // fine if multiple threads do this, no removeTicket may be called for this coord due to reference count inc
+ this.addTicket(coord);
+ this.processTicketUpdates(coord);
+ referenceCountData.addedTicket = true;
+ }
+ }
+
+ private void decrementReference(final ReferenceCountData referenceCountData, final long coord) {
+ if (!referenceCountData.decreaseReferenceCount()) {
+ return;
+ } // else: need to remove ticket
+
+ // note: it is possible that another thread increments and then removes the reference before we can, so
+ // use ifPresent
+ this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> {
+ if (valueInMap.referenceCount.get() != 0L) {
+ return valueInMap;
+ }
+
+ // note: valueInMap may not be referenceCountData
+
+ // possible to invoke this outside of the compute call, but not required and requires additional logic
+ WorldRegionTaskData.this.removeTicket(keyInMap);
+
+ return null;
+ });
+ }
+
+ private ReferenceCountData incrementReference(final long coord) {
+ ReferenceCountData referenceCountData = this.referenceCounters.get(coord);
+
+ if (referenceCountData != null && referenceCountData.addCount()) {
+ this.ensureTicketAdded(coord, referenceCountData);
+ return referenceCountData;
+ }
+
+ referenceCountData = this.referenceCounters.compute(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> {
+ if (valueInMap == null) {
+ // sets reference count to 1
+ return new ReferenceCountData();
+ }
+ // OK if we add from 0, the remove call will use compute() and catch this race condition
+ valueInMap.referenceCount.getAndIncrement();
+
+ return valueInMap;
+ });
+
+ this.ensureTicketAdded(coord, referenceCountData);
+
+ return referenceCountData;
+ }
+ }
+
+ private static final class ReferenceCountData {
+
+ public final AtomicLong referenceCount = new AtomicLong(1L);
+ public volatile boolean addedTicket;
+
+ // returns false if reference count is 0, otherwise increments ref count
+ public boolean addCount() {
+ int failures = 0;
+ for (long curr = this.referenceCount.get();;) {
+ for (int i = 0; i < failures; ++i) {
+ Thread.onSpinWait();
+ }
+
+ if (curr == 0L) {
+ return false;
+ }
+
+ if (curr == (curr = this.referenceCount.compareAndExchange(curr, curr + 1L))) {
+ return true;
+ }
+
+ ++failures;
+ }
+ }
+
+ // returns true if new reference count is 0
+ public boolean decreaseReferenceCount() {
+ final long res = this.referenceCount.decrementAndGet();
+ if (res >= 0L) {
+ return res == 0L;
+ } else {
+ throw new IllegalStateException("Negative reference count");
+ }
+ }
+ }
+
+ public static final class RegionTaskQueueData {
+ private final PrioritisedQueue tickTaskQueue = new PrioritisedQueue();
+ private final PrioritisedQueue chunkQueue = new PrioritisedQueue();
+ private final WorldRegionTaskData worldRegionTaskData;
+
+ public RegionTaskQueueData(final WorldRegionTaskData worldRegionTaskData) {
+ this.worldRegionTaskData = worldRegionTaskData;
+ }
+
+ void mergeInto(final RegionTaskQueueData into) {
+ this.tickTaskQueue.mergeInto(into.tickTaskQueue);
+ this.chunkQueue.mergeInto(into.chunkQueue);
+ }
+
+ public boolean executeTickTask() {
+ return this.tickTaskQueue.executeTask();
+ }
+
+ public boolean executeChunkTask() {
+ return this.worldRegionTaskData.executeGlobalChunkTask() || this.chunkQueue.executeTask();
+ }
+
+ void split(final ThreadedRegionizer<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> regioniser,
+ final Long2ReferenceOpenHashMap<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>> into) {
+ this.tickTaskQueue.split(
+ false, regioniser, into
+ );
+ this.chunkQueue.split(
+ true, regioniser, into
+ );
+ }
+
+ public void drainTasks() {
+ final PrioritisedQueue tickTaskQueue = this.tickTaskQueue;
+ final PrioritisedQueue chunkTaskQueue = this.chunkQueue;
+
+ int allowedTickTasks = tickTaskQueue.getScheduledTasks();
+ int allowedChunkTasks = chunkTaskQueue.getScheduledTasks();
+
+ boolean executeTickTasks = allowedTickTasks > 0;
+ boolean executeChunkTasks = allowedChunkTasks > 0;
+ boolean executeGlobalTasks = true;
+
+ do {
+ executeTickTasks = executeTickTasks && allowedTickTasks-- > 0 && tickTaskQueue.executeTask();
+ executeChunkTasks = executeChunkTasks && allowedChunkTasks-- > 0 && chunkTaskQueue.executeTask();
+ executeGlobalTasks = executeGlobalTasks && this.worldRegionTaskData.executeGlobalChunkTask();
+ } while (executeTickTasks | executeChunkTasks | executeGlobalTasks);
+
+ if (allowedChunkTasks > 0) {
+ // if we executed chunk tasks, we should try to process ticket updates for full status changes
+ this.worldRegionTaskData.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates();
+ }
+ }
+
+ public boolean hasTasks() {
+ return !this.tickTaskQueue.isEmpty() || !this.chunkQueue.isEmpty();
+ }
+ }
+
+ static final class PrioritisedQueue {
+ private final ArrayDeque<ChunkBasedPriorityTask>[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; {
+ for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) {
+ this.queues[i] = new ArrayDeque<>();
+ }
+ }
+ private boolean isDestroyed;
+
+ public int getScheduledTasks() {
+ synchronized (this) {
+ int ret = 0;
+
+ for (final ArrayDeque<ChunkBasedPriorityTask> queue : this.queues) {
+ ret += queue.size();
+ }
+
+ return ret;
+ }
+ }
+
+ public boolean isEmpty() {
+ final ArrayDeque<ChunkBasedPriorityTask>[] queues = this.queues;
+ final int max = Priority.IDLE.priority;
+ synchronized (this) {
+ for (int i = 0; i <= max; ++i) {
+ if (!queues[i].isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ public void mergeInto(final PrioritisedQueue target) {
+ synchronized (this) {
+ this.isDestroyed = true;
+ mergeInto(target, this.queues);
+ }
+ }
+
+ private static void mergeInto(final PrioritisedQueue target, final ArrayDeque<ChunkBasedPriorityTask>[] thisQueues) {
+ synchronized (target) {
+ final ArrayDeque<ChunkBasedPriorityTask>[] otherQueues = target.queues;
+ for (int i = 0; i < thisQueues.length; ++i) {
+ final ArrayDeque<ChunkBasedPriorityTask> fromQ = thisQueues[i];
+ final ArrayDeque<ChunkBasedPriorityTask> intoQ = otherQueues[i];
+
+ // it is possible for another thread to queue tasks into the target queue before we do
+ // since only the ticking region can poll, we don't have to worry about it when they are being queued -
+ // but when we are merging, we need to ensure order is maintained (notwithstanding priority changes)
+ // we can ensure order is maintained by adding all of the tasks from the fromQ into the intoQ at the
+ // front of the queue, but we need to use descending iterator to ensure we do not reverse
+ // the order of elements from fromQ
+ for (final Iterator<ChunkBasedPriorityTask> iterator = fromQ.descendingIterator(); iterator.hasNext();) {
+ intoQ.addFirst(iterator.next());
+ }
+ }
+ }
+ }
+
+ // into is a map of section coordinate to region
+ public void split(final boolean isChunkData,
+ final ThreadedRegionizer<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> regioniser,
+ final Long2ReferenceOpenHashMap<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>> into) {
+ final Reference2ReferenceOpenHashMap<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, ArrayDeque<ChunkBasedPriorityTask>[]>
+ split = new Reference2ReferenceOpenHashMap<>();
+ final int shift = regioniser.sectionChunkShift;
+ synchronized (this) {
+ this.isDestroyed = true;
+ // like mergeTarget, we need to be careful about insertion order so we can maintain order when splitting
+
+ // first, build the targets
+ final ArrayDeque<ChunkBasedPriorityTask>[] thisQueues = this.queues;
+ for (int i = 0; i < thisQueues.length; ++i) {
+ final ArrayDeque<ChunkBasedPriorityTask> fromQ = thisQueues[i];
+
+ for (final ChunkBasedPriorityTask task : fromQ) {
+ final int sectionX = task.chunkX >> shift;
+ final int sectionZ = task.chunkZ >> shift;
+ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ);
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>
+ region = into.get(sectionKey);
+ if (region == null) {
+ throw new IllegalStateException();
+ }
+
+ split.computeIfAbsent(region, (keyInMap) -> {
+ final ArrayDeque<ChunkBasedPriorityTask>[] ret = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES];
+
+ for (int k = 0; k < ret.length; ++k) {
+ ret[k] = new ArrayDeque<>();
+ }
+
+ return ret;
+ })[i].add(task);
+ }
+ }
+
+ // merge the targets into their queues
+ for (final Iterator<Reference2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, ArrayDeque<ChunkBasedPriorityTask>[]>>
+ iterator = split.reference2ReferenceEntrySet().fastIterator();
+ iterator.hasNext();) {
+ final Reference2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, ArrayDeque<ChunkBasedPriorityTask>[]>
+ entry = iterator.next();
+ final RegionTaskQueueData taskQueueData = entry.getKey().getData().getTaskQueueData();
+ mergeInto(isChunkData ? taskQueueData.chunkQueue : taskQueueData.tickTaskQueue, entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * returns null if the task cannot be scheduled, returns false if this task queue is dead, and returns true
+ * if the task was added
+ */
+ private Boolean tryPush(final ChunkBasedPriorityTask task) {
+ final ArrayDeque<ChunkBasedPriorityTask>[] queues = this.queues;
+ synchronized (this) {
+ final Priority priority = task.getPriority();
+ if (priority == Priority.COMPLETING) {
+ return null;
+ }
+ if (this.isDestroyed) {
+ return Boolean.FALSE;
+ }
+ queues[priority.priority].addLast(task);
+ return Boolean.TRUE;
+ }
+ }
+
+ private boolean executeTask() {
+ final ArrayDeque<ChunkBasedPriorityTask>[] queues = this.queues;
+ final int max = Priority.IDLE.priority;
+ ChunkBasedPriorityTask task = null;
+ ReferenceCountData referenceCounter = null;
+ synchronized (this) {
+ if (this.isDestroyed) {
+ throw new IllegalStateException("Attempting to poll from dead queue");
+ }
+
+ search_loop:
+ for (int i = 0; i <= max; ++i) {
+ final ArrayDeque<ChunkBasedPriorityTask> queue = queues[i];
+ while ((task = queue.pollFirst()) != null) {
+ if ((referenceCounter = task.trySetCompleting(i)) != null) {
+ break search_loop;
+ }
+ }
+ }
+ }
+
+ if (task == null) {
+ return false;
+ }
+
+ try {
+ task.executeInternal();
+ } finally {
+ task.world.decrementReference(referenceCounter, task.sectionLowerLeftCoord);
+ }
+
+ return true;
+ }
+
+ private static final class ChunkBasedPriorityTask implements PrioritisedExecutor.PrioritisedTask {
+
+ private static final ReferenceCountData REFERENCE_COUNTER_NOT_SET = new ReferenceCountData();
+ static {
+ REFERENCE_COUNTER_NOT_SET.referenceCount.set((long)Integer.MIN_VALUE);
+ }
+
+ private final WorldRegionTaskData world;
+ private final int chunkX;
+ private final int chunkZ;
+ private final long sectionLowerLeftCoord; // chunk coordinate
+ private final boolean isChunkTask;
+
+ private volatile ReferenceCountData referenceCounter;
+ private static final VarHandle REFERENCE_COUNTER_HANDLE = ConcurrentUtil.getVarHandle(ChunkBasedPriorityTask.class, "referenceCounter", ReferenceCountData.class);
+ private Runnable run;
+ private volatile Priority priority;
+ private static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(ChunkBasedPriorityTask.class, "priority", Priority.class);
+
+ ChunkBasedPriorityTask(final WorldRegionTaskData world, final int chunkX, final int chunkZ, final boolean isChunkTask,
+ final Runnable run, final Priority priority) {
+ this.world = world;
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.isChunkTask = isChunkTask;
+ this.run = run;
+ this.setReferenceCounterPlain(REFERENCE_COUNTER_NOT_SET);
+ this.setPriorityPlain(priority);
+
+ final int regionShift = world.world.regioniser.sectionChunkShift;
+ final int regionMask = (1 << regionShift) - 1;
+
+ this.sectionLowerLeftCoord = CoordinateUtils.getChunkKey(chunkX & ~regionMask, chunkZ & ~regionMask);
+ }
+
+ private Priority getPriorityVolatile() {
+ return (Priority)PRIORITY_HANDLE.getVolatile(this);
+ }
+
+ private void setPriorityPlain(final Priority priority) {
+ PRIORITY_HANDLE.set(this, priority);
+ }
+
+ private void setPriorityVolatile(final Priority priority) {
+ PRIORITY_HANDLE.setVolatile(this, priority);
+ }
+
+ private Priority compareAndExchangePriority(final Priority expect, final Priority update) {
+ return (Priority)PRIORITY_HANDLE.compareAndExchange(this, expect, update);
+ }
+
+ private void setReferenceCounterPlain(final ReferenceCountData value) {
+ REFERENCE_COUNTER_HANDLE.set(this, value);
+ }
+
+ private ReferenceCountData getReferenceCounterVolatile() {
+ return (ReferenceCountData)REFERENCE_COUNTER_HANDLE.get(this);
+ }
+
+ private ReferenceCountData compareAndExchangeReferenceCounter(final ReferenceCountData expect, final ReferenceCountData update) {
+ return (ReferenceCountData)REFERENCE_COUNTER_HANDLE.compareAndExchange(this, expect, update);
+ }
+
+ private void executeInternal() {
+ try {
+ this.run.run();
+ } finally {
+ this.run = null;
+ }
+ }
+
+ private void cancelInternal() {
+ this.run = null;
+ }
+
+ private boolean tryComplete(final boolean cancel) {
+ int failures = 0;
+ for (ReferenceCountData curr = this.getReferenceCounterVolatile();;) {
+ if (curr == null) {
+ return false;
+ }
+
+ for (int i = 0; i < failures; ++i) {
+ ConcurrentUtil.backoff();
+ }
+
+ if (curr != (curr = this.compareAndExchangeReferenceCounter(curr, null))) {
+ ++failures;
+ continue;
+ }
+
+ // we have the reference count, we win no matter what.
+ this.setPriorityVolatile(Priority.COMPLETING);
+
+ try {
+ if (cancel) {
+ this.cancelInternal();
+ } else {
+ this.executeInternal();
+ }
+ } finally {
+ if (curr != REFERENCE_COUNTER_NOT_SET) {
+ this.world.decrementReference(curr, this.sectionLowerLeftCoord);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public PrioritisedExecutor getExecutor() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isQueued() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean queue() {
+ if (this.getReferenceCounterVolatile() != REFERENCE_COUNTER_NOT_SET) {
+ return false;
+ }
+
+ final ReferenceCountData referenceCounter = this.world.incrementReference(this.sectionLowerLeftCoord);
+ if (this.compareAndExchangeReferenceCounter(REFERENCE_COUNTER_NOT_SET, referenceCounter) != REFERENCE_COUNTER_NOT_SET) {
+ // we don't expect race conditions here, so it is OK if we have to needlessly reference count
+ this.world.decrementReference(referenceCounter, this.sectionLowerLeftCoord);
+ return false;
+ }
+
+ boolean synchronise = false;
+ for (;;) {
+ // we need to synchronise for repeated operations so that we guarantee that we do not retrieve
+ // the same queue again, as the region lock will be given to us only when the merge/split operation
+ // is done
+ final PrioritisedQueue queue = this.world.getQueue(synchronise, this.chunkX, this.chunkZ, this.isChunkTask);
+
+ if (queue == null) {
+ if (!synchronise) {
+ // may be incorrectly null when unsynchronised
+ synchronise = true;
+ continue;
+ }
+ // may have been cancelled before we got to the queue
+ if (this.getReferenceCounterVolatile() != null) {
+ throw new IllegalStateException("Expected null ref count when queue does not exist");
+ }
+ // the task never could be polled from the queue, so we return false
+ // don't decrement reference count, as we were certainly cancelled by another thread, which
+ // will decrement the reference count
+ return false;
+ }
+
+ synchronise = true;
+
+ final Boolean res = queue.tryPush(this);
+ if (res == null) {
+ // we were cancelled
+ // don't decrement reference count, as we were certainly cancelled by another thread, which
+ // will decrement the reference count
+ return false;
+ }
+
+ if (!res.booleanValue()) {
+ // failed, try again
+ continue;
+ }
+
+ // successfully queued
+ return true;
+ }
+ }
+
+ private ReferenceCountData trySetCompleting(final int minPriority) {
+ // first, try to set priority to EXECUTING
+ for (Priority curr = this.getPriorityVolatile();;) {
+ if (curr.isLowerPriority(minPriority)) {
+ return null;
+ }
+
+ if (curr == (curr = this.compareAndExchangePriority(curr, Priority.COMPLETING))) {
+ break;
+ } // else: continue
+ }
+
+ for (ReferenceCountData curr = this.getReferenceCounterVolatile();;) {
+ if (curr == null) {
+ // something acquired before us
+ return null;
+ }
+
+ if (curr == REFERENCE_COUNTER_NOT_SET) {
+ throw new IllegalStateException();
+ }
+
+ if (curr != (curr = this.compareAndExchangeReferenceCounter(curr, null))) {
+ continue;
+ }
+
+ return curr;
+ }
+ }
+
+ private void updatePriorityInQueue() {
+ boolean synchronise = false;
+ for (;;) {
+ final ReferenceCountData referenceCount = this.getReferenceCounterVolatile();
+ if (referenceCount == REFERENCE_COUNTER_NOT_SET || referenceCount == null) {
+ // cancelled or not queued
+ return;
+ }
+
+ if (this.getPriorityVolatile() == Priority.COMPLETING) {
+ // cancelled
+ return;
+ }
+
+ // we need to synchronise for repeated operations so that we guarantee that we do not retrieve
+ // the same queue again, as the region lock will be given to us only when the merge/split operation
+ // is done
+ final PrioritisedQueue queue = this.world.getQueue(synchronise, this.chunkX, this.chunkZ, this.isChunkTask);
+
+ if (queue == null) {
+ if (!synchronise) {
+ // may be incorrectly null when unsynchronised
+ synchronise = true;
+ continue;
+ }
+ // must have been removed
+ return;
+ }
+
+ synchronise = true;
+
+ final Boolean res = queue.tryPush(this);
+ if (res == null) {
+ // we were cancelled
+ return;
+ }
+
+ if (!res.booleanValue()) {
+ // failed, try again
+ continue;
+ }
+
+ // successfully queued
+ return;
+ }
+ }
+
+ @Override
+ public Priority getPriority() {
+ return this.getPriorityVolatile();
+ }
+
+ @Override
+ public boolean lowerPriority(final Priority priority) {
+ int failures = 0;
+ for (Priority curr = this.getPriorityVolatile();;) {
+ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
+ if (curr.isLowerOrEqualPriority(priority)) {
+ return false;
+ }
+
+ for (int i = 0; i < failures; ++i) {
+ ConcurrentUtil.backoff();
+ }
+
+ if (curr == (curr = this.compareAndExchangePriority(curr, priority))) {
+ this.updatePriorityInQueue();
+ return true;
+ }
+ ++failures;
+ }
+ }
+
+ @Override
+ public long getSubOrder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean setSubOrder(final long subOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean raiseSubOrder(final long subOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean lowerSubOrder(final long subOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
+ return this.setPriority(priority);
+ }
+
+ @Override
+ public boolean setPriority(final Priority priority) {
+ int failures = 0;
+ for (Priority curr = this.getPriorityVolatile();;) {
+ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
+ if (curr == priority) {
+ return false;
+ }
+
+ for (int i = 0; i < failures; ++i) {
+ ConcurrentUtil.backoff();
+ }
+
+ if (curr == (curr = this.compareAndExchangePriority(curr, priority))) {
+ this.updatePriorityInQueue();
+ return true;
+ }
+ ++failures;
+ }
+ }
+
+ @Override
+ public boolean raisePriority(final Priority priority) {
+ int failures = 0;
+ for (Priority curr = this.getPriorityVolatile();;) {
+ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
+ if (curr.isHigherOrEqualPriority(priority)) {
+ return false;
+ }
+
+ for (int i = 0; i < failures; ++i) {
+ ConcurrentUtil.backoff();
+ }
+
+ if (curr == (curr = this.compareAndExchangePriority(curr, priority))) {
+ this.updatePriorityInQueue();
+ return true;
+ }
+ ++failures;
+ }
+ }
+
+ @Override
+ public boolean execute() {
+ return this.tryComplete(false);
+ }
+
+ @Override
+ public boolean cancel() {
+ return this.tryComplete(true);
+ }
+ }
+ }
+}

View File

@ -1,773 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -1,0 +_,770 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet;
+import ca.spottedleaf.moonrise.common.list.ReferenceList;
+import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
+import ca.spottedleaf.moonrise.common.util.TickThread;
+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
+import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.CrashReport;
+import net.minecraft.ReportedException;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.Connection;
+import net.minecraft.network.PacketSendListener;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.network.protocol.common.ClientboundDisconnectPacket;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.util.VisibleForDebug;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.village.VillageSiege;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.level.BlockEventData;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Explosion;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.NaturalSpawner;
+import net.minecraft.world.level.ServerExplosion;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.RedStoneWireBlock;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.TickingBlockEntity;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.pathfinder.PathTypeCache;
+import net.minecraft.world.level.redstone.CollectingNeighborUpdater;
+import net.minecraft.world.level.redstone.NeighborUpdater;
+import net.minecraft.world.ticks.LevelTicks;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.slf4j.Logger;
+import javax.annotation.Nullable;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public final class RegionizedWorldData {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0];
+
+ public static final RegionizedData.RegioniserCallback<RegionizedWorldData> REGION_CALLBACK = new RegionizedData.RegioniserCallback<>() {
+ @Override
+ public void merge(final RegionizedWorldData from, final RegionizedWorldData into, final long fromTickOffset) {
+ // connections
+ for (final Connection conn : from.connections) {
+ into.connections.add(conn);
+ }
+ // time
+ final long fromRedstoneTimeOffset = into.redstoneTime - from.redstoneTime;
+ // entities
+ for (final ServerPlayer player : from.localPlayers) {
+ into.localPlayers.add(player);
+ into.nearbyPlayers.addPlayer(player);
+ }
+ for (final Entity entity : from.allEntities) {
+ into.allEntities.add(entity);
+ entity.updateTicks(fromTickOffset, fromRedstoneTimeOffset);
+ }
+ for (final Entity entity : from.loadedEntities) {
+ into.loadedEntities.add(entity);
+ }
+ for (final Iterator<Entity> iterator = from.entityTickList.unsafeIterator(); iterator.hasNext();) {
+ into.entityTickList.add(iterator.next());
+ }
+ for (final Iterator<Mob> iterator = from.navigatingMobs.unsafeIterator(); iterator.hasNext();) {
+ into.navigatingMobs.add(iterator.next());
+ }
+ for (final Iterator<Entity> iterator = from.trackerEntities.iterator(); iterator.hasNext();) {
+ into.trackerEntities.add(iterator.next());
+ }
+ for (final Iterator<Entity> iterator = from.trackerUnloadedEntities.iterator(); iterator.hasNext();) {
+ into.trackerUnloadedEntities.add(iterator.next());
+ }
+ // block ticking
+ into.blockEvents.addAll(from.blockEvents);
+ // ticklists use game time
+ from.blockLevelTicks.merge(into.blockLevelTicks, fromRedstoneTimeOffset);
+ from.fluidLevelTicks.merge(into.fluidLevelTicks, fromRedstoneTimeOffset);
+
+ // tile entity ticking
+ for (final TickingBlockEntity tileEntityWrapped : from.pendingBlockEntityTickers) {
+ into.pendingBlockEntityTickers.add(tileEntityWrapped);
+ final BlockEntity tileEntity = tileEntityWrapped.getTileEntity();
+ if (tileEntity != null) {
+ tileEntity.updateTicks(fromTickOffset, fromRedstoneTimeOffset);
+ }
+ }
+ for (final TickingBlockEntity tileEntityWrapped : from.blockEntityTickers) {
+ into.blockEntityTickers.add(tileEntityWrapped);
+ final BlockEntity tileEntity = tileEntityWrapped.getTileEntity();
+ if (tileEntity != null) {
+ tileEntity.updateTicks(fromTickOffset, fromRedstoneTimeOffset);
+ }
+ }
+
+ // ticking chunks
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.entityTickingChunks.iterator(); iterator.hasNext();) {
+ into.entityTickingChunks.add(iterator.next());
+ }
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.tickingChunks.iterator(); iterator.hasNext();) {
+ into.tickingChunks.add(iterator.next());
+ }
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.chunks.iterator(); iterator.hasNext();) {
+ into.chunks.add(iterator.next());
+ }
+ // redstone torches
+ if (from.redstoneUpdateInfos != null && !from.redstoneUpdateInfos.isEmpty()) {
+ if (into.redstoneUpdateInfos == null) {
+ into.redstoneUpdateInfos = new ArrayDeque<>();
+ }
+ for (final net.minecraft.world.level.block.RedstoneTorchBlock.Toggle info : from.redstoneUpdateInfos) {
+ info.offsetTime(fromRedstoneTimeOffset);
+ into.redstoneUpdateInfos.add(info);
+ }
+ }
+ // mob spawning
+ into.catSpawnerNextTick = Math.max(from.catSpawnerNextTick, into.catSpawnerNextTick);
+ into.patrolSpawnerNextTick = Math.max(from.patrolSpawnerNextTick, into.patrolSpawnerNextTick);
+ into.phantomSpawnerNextTick = Math.max(from.phantomSpawnerNextTick, into.phantomSpawnerNextTick);
+ if (from.wanderingTraderTickDelay != Integer.MIN_VALUE && into.wanderingTraderTickDelay != Integer.MIN_VALUE) {
+ into.wanderingTraderTickDelay = Math.max(from.wanderingTraderTickDelay, into.wanderingTraderTickDelay);
+ into.wanderingTraderSpawnDelay = Math.max(from.wanderingTraderSpawnDelay, into.wanderingTraderSpawnDelay);
+ into.wanderingTraderSpawnChance = Math.max(from.wanderingTraderSpawnChance, into.wanderingTraderSpawnChance);
+ }
+ // chunkHoldersToBroadcast
+ for (final ChunkHolder chunkHolder : from.chunkHoldersToBroadcast) {
+ into.chunkHoldersToBroadcast.add(chunkHolder);
+ }
+ }
+
+ @Override
+ public void split(final RegionizedWorldData from, final int chunkToRegionShift,
+ final Long2ReferenceOpenHashMap<RegionizedWorldData> regionToData,
+ final ReferenceOpenHashSet<RegionizedWorldData> dataSet) {
+ // connections
+ for (final Connection conn : from.connections) {
+ final ServerPlayer player = conn.getPlayer();
+ final ChunkPos pos = player.chunkPosition();
+ // Note: It is impossible for an entity in the world to _not_ be in an entity chunk, which means
+ // the chunk holder must _exist_, and so the region section exists.
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
+ .connections.add(conn);
+ }
+ // entities
+ for (final ServerPlayer player : from.localPlayers) {
+ final ChunkPos pos = player.chunkPosition();
+ // Note: It is impossible for an entity in the world to _not_ be in an entity chunk, which means
+ // the chunk holder must _exist_, and so the region section exists.
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift));
+ into.localPlayers.add(player);
+ into.nearbyPlayers.addPlayer(player);
+ }
+ for (final Entity entity : from.allEntities) {
+ final ChunkPos pos = entity.chunkPosition();
+ // Note: It is impossible for an entity in the world to _not_ be in an entity chunk, which means
+ // the chunk holder must _exist_, and so the region section exists.
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift));
+ into.allEntities.add(entity);
+ // Note: entityTickList is a subset of allEntities
+ if (from.entityTickList.contains(entity)) {
+ into.entityTickList.add(entity);
+ }
+ // Note: loadedEntities is a subset of allEntities
+ if (from.loadedEntities.contains(entity)) {
+ into.loadedEntities.add(entity);
+ }
+ // Note: navigatingMobs is a subset of allEntities
+ if (entity instanceof Mob mob && from.navigatingMobs.contains(mob)) {
+ into.navigatingMobs.add(mob);
+ }
+ if (from.trackerEntities.contains(entity)) {
+ into.trackerEntities.add(entity);
+ }
+ if (from.trackerUnloadedEntities.contains(entity)) {
+ into.trackerUnloadedEntities.add(entity);
+ }
+ }
+ // block ticking
+ for (final BlockEventData blockEventData : from.blockEvents) {
+ final BlockPos pos = blockEventData.pos();
+ final int chunkX = pos.getX() >> 4;
+ final int chunkZ = pos.getZ() >> 4;
+
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift));
+ // Unlike entities, the chunk holder is not guaranteed to exist for block events, because the block events
+ // is just some list. So if it unloads, I guess it's just lost.
+ if (into != null) {
+ into.blockEvents.add(blockEventData);
+ }
+ }
+
+ final Long2ReferenceOpenHashMap<LevelTicks<Block>> levelTicksBlockRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f);
+ final Long2ReferenceOpenHashMap<LevelTicks<Fluid>> levelTicksFluidRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f);
+
+ for (final Iterator<Long2ReferenceMap.Entry<RegionizedWorldData>> iterator = regionToData.long2ReferenceEntrySet().fastIterator();
+ iterator.hasNext();) {
+ final Long2ReferenceMap.Entry<RegionizedWorldData> entry = iterator.next();
+ final long key = entry.getLongKey();
+ final RegionizedWorldData worldData = entry.getValue();
+
+ levelTicksBlockRegionData.put(key, worldData.blockLevelTicks);
+ levelTicksFluidRegionData.put(key, worldData.fluidLevelTicks);
+ }
+
+ from.blockLevelTicks.split(chunkToRegionShift, levelTicksBlockRegionData);
+ from.fluidLevelTicks.split(chunkToRegionShift, levelTicksFluidRegionData);
+
+ // tile entity ticking
+ for (final TickingBlockEntity tileEntity : from.pendingBlockEntityTickers) {
+ final BlockPos pos = tileEntity.getPos();
+ final int chunkX = pos.getX() >> 4;
+ final int chunkZ = pos.getZ() >> 4;
+
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift));
+ if (into != null) {
+ into.pendingBlockEntityTickers.add(tileEntity);
+ } // else: when a chunk unloads, it does not actually _remove_ the tile entity from the list, it just gets
+ // marked as removed. So if there is no section, it's probably removed!
+ }
+ for (final TickingBlockEntity tileEntity : from.blockEntityTickers) {
+ final BlockPos pos = tileEntity.getPos();
+ final int chunkX = pos.getX() >> 4;
+ final int chunkZ = pos.getZ() >> 4;
+
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift));
+ if (into != null) {
+ into.blockEntityTickers.add(tileEntity);
+ } // else: when a chunk unloads, it does not actually _remove_ the tile entity from the list, it just gets
+ // marked as removed. So if there is no section, it's probably removed!
+ }
+ // time
+ for (final RegionizedWorldData regionizedWorldData : dataSet) {
+ regionizedWorldData.redstoneTime = from.redstoneTime;
+ }
+ // ticking chunks
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.entityTickingChunks.iterator(); iterator.hasNext();) {
+ final ServerChunkCache.ChunkAndHolder holder = iterator.next();
+ final ChunkPos pos = holder.chunk().getPos();
+
+ // Impossible for get() to return null, as the chunk is entity ticking - thus the chunk holder is loaded
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
+ .entityTickingChunks.add(holder);
+ }
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.tickingChunks.iterator(); iterator.hasNext();) {
+ final ServerChunkCache.ChunkAndHolder holder = iterator.next();
+ final ChunkPos pos = holder.chunk().getPos();
+
+ // Impossible for get() to return null, as the chunk is entity ticking - thus the chunk holder is loaded
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
+ .tickingChunks.add(holder);
+ }
+ for (final Iterator<ServerChunkCache.ChunkAndHolder> iterator = from.chunks.iterator(); iterator.hasNext();) {
+ final ServerChunkCache.ChunkAndHolder holder = iterator.next();
+ final ChunkPos pos = holder.chunk().getPos();
+
+ // Impossible for get() to return null, as the chunk is entity ticking - thus the chunk holder is loaded
+ regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift))
+ .chunks.add(holder);
+ }
+
+ // redstone torches
+ if (from.redstoneUpdateInfos != null && !from.redstoneUpdateInfos.isEmpty()) {
+ for (final net.minecraft.world.level.block.RedstoneTorchBlock.Toggle info : from.redstoneUpdateInfos) {
+ final BlockPos pos = info.pos;
+
+ final RegionizedWorldData worldData = regionToData.get(CoordinateUtils.getChunkKey((pos.getX() >> 4) >> chunkToRegionShift, (pos.getZ() >> 4) >> chunkToRegionShift));
+ if (worldData != null) {
+ if (worldData.redstoneUpdateInfos == null) {
+ worldData.redstoneUpdateInfos = new ArrayDeque<>();
+ }
+ worldData.redstoneUpdateInfos.add(info);
+ } // else: chunk unloaded
+ }
+ }
+ // mob spawning
+ for (final RegionizedWorldData regionizedWorldData : dataSet) {
+ regionizedWorldData.catSpawnerNextTick = from.catSpawnerNextTick;
+ regionizedWorldData.patrolSpawnerNextTick = from.patrolSpawnerNextTick;
+ regionizedWorldData.phantomSpawnerNextTick = from.phantomSpawnerNextTick;
+ regionizedWorldData.wanderingTraderTickDelay = from.wanderingTraderTickDelay;
+ regionizedWorldData.wanderingTraderSpawnChance = from.wanderingTraderSpawnChance;
+ regionizedWorldData.wanderingTraderSpawnDelay = from.wanderingTraderSpawnDelay;
+ regionizedWorldData.villageSiegeState = new VillageSiegeState(); // just re set it, as the spawn pos will be invalid
+ }
+ // chunkHoldersToBroadcast
+ for (final ChunkHolder chunkHolder : from.chunkHoldersToBroadcast) {
+ final ChunkPos pos = chunkHolder.getPos();
+
+ // Possible for get() to return null, as the chunk holder is not removed during unload
+ final RegionizedWorldData into = regionToData.get(CoordinateUtils.getChunkKey(pos.x >> chunkToRegionShift, pos.z >> chunkToRegionShift));
+ if (into != null) {
+ into.chunkHoldersToBroadcast.add(chunkHolder);
+ }
+ }
+ }
+ };
+
+ public final ServerLevel world;
+
+ private RegionizedServer.WorldLevelData tickData;
+
+ // connections
+ public final List<Connection> connections = new ArrayList<>();
+
+ // misc. fields
+ private boolean isHandlingTick;
+
+ public void setHandlingTick(final boolean to) {
+ this.isHandlingTick = to;
+ }
+
+ public boolean isHandlingTick() {
+ return this.isHandlingTick;
+ }
+
+ // entities
+ private final List<ServerPlayer> localPlayers = new ArrayList<>();
+ private final NearbyPlayers nearbyPlayers;
+ private final ReferenceList<Entity> allEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY);
+ private final ReferenceList<Entity> loadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY);
+ private final IteratorSafeOrderedReferenceSet<Entity> entityTickList = new IteratorSafeOrderedReferenceSet<>();
+ private final IteratorSafeOrderedReferenceSet<Mob> navigatingMobs = new IteratorSafeOrderedReferenceSet<>();
+ public final ReferenceList<Entity> trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
+ public final ReferenceList<Entity> trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
+
+ // block ticking
+ private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
+ private final LevelTicks<Block> blockLevelTicks;
+ private final LevelTicks<Fluid> fluidLevelTicks;
+
+ // tile entity ticking
+ private final List<TickingBlockEntity> pendingBlockEntityTickers = new ArrayList<>();
+ private final List<TickingBlockEntity> blockEntityTickers = new ArrayList<>();
+ private boolean tickingBlockEntities;
+
+ // time
+ private long redstoneTime = 1L;
+
+ public long getRedstoneGameTime() {
+ return this.redstoneTime;
+ }
+
+ public void setRedstoneGameTime(final long to) {
+ this.redstoneTime = to;
+ }
+
+ // ticking chunks
+ private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDER_ARRAY = new ServerChunkCache.ChunkAndHolder[0];
+ private final ReferenceList<ServerChunkCache.ChunkAndHolder> entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY);
+ private final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY);
+ private final ReferenceList<ServerChunkCache.ChunkAndHolder> chunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY);
+
+ // Paper/CB api hook misc
+ // don't bother to merge/split these, no point
+ // From ServerLevel
+ public boolean hasPhysicsEvent = true; // Paper
+ public boolean hasEntityMoveEvent = false; // Paper
+ // Paper start - Optimize Hoppers
+ public boolean skipPullModeEventFire = false;
+ public boolean skipPushModeEventFire = false;
+ public boolean skipHopperEvents = false;
+ // Paper end - Optimize Hoppers
+ public long lastMidTickExecute;
+ public long lastMidTickExecuteFailure;
+ // From Level
+ public boolean populating;
+ public final NeighborUpdater neighborUpdater;
+ public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
+ public boolean captureBlockStates = false;
+ public boolean captureTreeGeneration = false;
+ public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+ public final Map<BlockPos, CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
+ public final Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper
+ public List<ItemEntity> captureDrops;
+ // Paper start
+ public int wakeupInactiveRemainingAnimals;
+ public int wakeupInactiveRemainingFlying;
+ public int wakeupInactiveRemainingMonsters;
+ public int wakeupInactiveRemainingVillagers;
+ // Paper end
+ public int currentPrimedTnt = 0; // Spigot
+ @Nullable
+ @VisibleForDebug
+ public NaturalSpawner.SpawnState lastSpawnState;
+ public boolean shouldSignal = true;
+ public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new HashMap<>(64, 0.25f);
+ public final PathTypeCache pathTypesByPosCache = new PathTypeCache();
+ public final List<LevelChunk> temporaryChunkTickList = new java.util.ArrayList<>();
+ public final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceLinkedOpenHashSet<>();
+
+ // not transient
+ public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos;
+
+ // Mob spawning
+ public final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<ServerPlayer> spawnChunkTracker = new ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<>();
+ public int catSpawnerNextTick = 0;
+ public int patrolSpawnerNextTick = 0;
+ public int phantomSpawnerNextTick = 0;
+ public int wanderingTraderTickDelay = Integer.MIN_VALUE;
+ public int wanderingTraderSpawnDelay;
+ public int wanderingTraderSpawnChance;
+ public VillageSiegeState villageSiegeState = new VillageSiegeState();
+
+ public static final class VillageSiegeState {
+ public boolean hasSetupSiege;
+ public VillageSiege.State siegeState = VillageSiege.State.SIEGE_DONE;
+ public int zombiesToSpawn;
+ public int nextSpawnTime;
+ public int spawnX;
+ public int spawnY;
+ public int spawnZ;
+ }
+ // Redstone
+ public final alternate.current.wire.WireHandler wireHandler;
+ public final io.papermc.paper.redstone.RedstoneWireTurbo turbo;
+
+ public RegionizedWorldData(final ServerLevel world) {
+ this.world = world;
+ this.blockLevelTicks = new LevelTicks<>(world::isPositionTickingWithEntitiesLoaded, world, true);
+ this.fluidLevelTicks = new LevelTicks<>(world::isPositionTickingWithEntitiesLoaded, world, false);
+ this.neighborUpdater = new CollectingNeighborUpdater(world, world.neighbourUpdateMax);
+ this.nearbyPlayers = new NearbyPlayers(world);
+ this.wireHandler = new alternate.current.wire.WireHandler(world);
+ this.turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((RedStoneWireBlock)Blocks.REDSTONE_WIRE);
+
+ // tasks may be drained before the region ticks, so we must set up the tick data early just in case
+ this.updateTickData();
+ }
+
+ public void checkWorld(final Level against) {
+ if (this.world != against) {
+ throw new IllegalStateException("World mismatch: expected " + this.world.getWorld().getName() + " but got " + (against == null ? "null" : against.getWorld().getName()));
+ }
+ }
+
+ public RegionizedServer.WorldLevelData getTickData() {
+ return this.tickData;
+ }
+
+ private long lagCompensationTick;
+
+ public long getLagCompensationTick() {
+ return this.lagCompensationTick;
+ }
+
+ public void updateTickData() {
+ this.tickData = this.world.tickData;
+ this.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
+ this.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
+ this.skipHopperEvents = this.world.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ // always subtract from server init so that the tick starts at zero, allowing us to cast to int without much worry
+ this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / TickRegionScheduler.TIME_BETWEEN_TICKS;
+ }
+
+ public NearbyPlayers getNearbyPlayers() {
+ return this.nearbyPlayers;
+ }
+
+ private static void cleanUpConnection(final Connection conn) {
+ // note: ALL connections HERE have a player
+ final ServerPlayer player = conn.getPlayer();
+ // now that the connection is removed, we can allow this region to die
+ player.serverLevel().chunkSource.removeTicketAtLevel(
+ ServerGamePacketListenerImpl.DISCONNECT_TICKET, player.connection.disconnectPos,
+ ChunkHolderManager.MAX_TICKET_LEVEL,
+ player.connection.disconnectTicketId
+ );
+ }
+
+ // connections
+ public void tickConnections() {
+ final List<Connection> connections = new ArrayList<>(this.connections);
+ Collections.shuffle(connections);
+ for (final Connection conn : connections) {
+ if (!conn.isConnected()) {
+ conn.handleDisconnection();
+ // global tick thread will not remove connections not owned by it, so we need to
+ RegionizedServer.getInstance().removeConnection(conn);
+ this.connections.remove(conn);
+ cleanUpConnection(conn);
+ continue;
+ }
+ if (!this.connections.contains(conn)) {
+ // removed by connection tick?
+ continue;
+ }
+
+ try {
+ conn.tick();
+ } catch (final Exception exception) {
+ if (conn.isMemoryConnection()) {
+ throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection"));
+ }
+
+ LOGGER.warn("Failed to handle packet for {}", conn.getLoggableAddress(MinecraftServer.getServer().logIPs()), exception);
+ MutableComponent ichatmutablecomponent = Component.literal("Internal server error");
+
+ conn.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> {
+ conn.disconnect(ichatmutablecomponent);
+ }));
+ conn.setReadOnly();
+ continue;
+ }
+ }
+ }
+
+ // entities hooks
+ public int getEntityCount() {
+ return this.allEntities.size();
+ }
+
+ public int getPlayerCount() {
+ return this.localPlayers.size();
+ }
+
+ public Iterable<Entity> getLocalEntities() {
+ return this.allEntities;
+ }
+
+ public Entity[] getLocalEntitiesCopy() {
+ return Arrays.copyOf(this.allEntities.getRawData(), this.allEntities.size(), Entity[].class);
+ }
+
+ public List<ServerPlayer> getLocalPlayers() {
+ return this.localPlayers;
+ }
+
+ public void addLoadedEntity(final Entity entity) {
+ this.loadedEntities.add(entity);
+ }
+
+ public boolean hasLoadedEntity(final Entity entity) {
+ return this.loadedEntities.contains(entity);
+ }
+
+ public void removeLoadedEntity(final Entity entity) {
+ this.loadedEntities.remove(entity);
+ }
+
+ public Iterable<Entity> getLoadedEntities() {
+ return this.loadedEntities;
+ }
+
+ public void addEntityTickingEntity(final Entity entity) {
+ if (!TickThread.isTickThreadFor(entity)) {
+ throw new IllegalArgumentException("Entity " + entity + " is not under this region's control");
+ }
+ this.entityTickList.add(entity);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public boolean hasEntityTickingEntity(final Entity entity) {
+ return this.entityTickList.contains(entity);
+ }
+
+ public void removeEntityTickingEntity(final Entity entity) {
+ if (!TickThread.isTickThreadFor(entity)) {
+ throw new IllegalArgumentException("Entity " + entity + " is not under this region's control");
+ }
+ this.entityTickList.remove(entity);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public void forEachTickingEntity(final Consumer<Entity> action) {
+ final IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entityTickList.iterator();
+ try {
+ while (iterator.hasNext()) {
+ action.accept(iterator.next());
+ }
+ } finally {
+ iterator.finishedIterating();
+ }
+ }
+
+ public void addEntity(final Entity entity) {
+ if (!TickThread.isTickThreadFor(this.world, entity.chunkPosition())) {
+ throw new IllegalArgumentException("Entity " + entity + " is not under this region's control");
+ }
+ if (this.allEntities.add(entity)) {
+ if (entity instanceof ServerPlayer player) {
+ this.localPlayers.add(player);
+ }
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+ }
+
+ public boolean hasEntity(final Entity entity) {
+ return this.allEntities.contains(entity);
+ }
+
+ public void removeEntity(final Entity entity) {
+ if (!TickThread.isTickThreadFor(entity)) {
+ throw new IllegalArgumentException("Entity " + entity + " is not under this region's control");
+ }
+ if (this.allEntities.remove(entity)) {
+ if (entity instanceof ServerPlayer player) {
+ this.localPlayers.remove(player);
+ }
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+ }
+
+ public void addNavigatingMob(final Mob mob) {
+ if (!TickThread.isTickThreadFor(mob)) {
+ throw new IllegalArgumentException("Entity " + mob + " is not under this region's control");
+ }
+ this.navigatingMobs.add(mob);
+ }
+
+ public void removeNavigatingMob(final Mob mob) {
+ if (!TickThread.isTickThreadFor(mob)) {
+ throw new IllegalArgumentException("Entity " + mob + " is not under this region's control");
+ }
+ this.navigatingMobs.remove(mob);
+ }
+
+ public Iterator<Mob> getNavigatingMobs() {
+ return this.navigatingMobs.unsafeIterator();
+ }
+
+ // block ticking hooks
+ // Since block event data does not require chunk holders to be created for the chunk they reside in,
+ // it's not actually guaranteed that when merging / splitting data that we actually own the data...
+ // Note that we can only ever not own the event data when the chunk unloads, and so I've decided to
+ // make the code easier by simply discarding it in such an event
+ public void pushBlockEvent(final BlockEventData blockEventData) {
+ TickThread.ensureTickThread(this.world, blockEventData.pos(), "Cannot queue block even data async");
+ this.blockEvents.add(blockEventData);
+ }
+
+ public void pushBlockEvents(final Collection<? extends BlockEventData> blockEvents) {
+ for (final BlockEventData blockEventData : blockEvents) {
+ this.pushBlockEvent(blockEventData);
+ }
+ }
+
+ public void removeIfBlockEvents(final Predicate<? super BlockEventData> predicate) {
+ for (final Iterator<BlockEventData> iterator = this.blockEvents.iterator(); iterator.hasNext();) {
+ final BlockEventData blockEventData = iterator.next();
+ if (predicate.test(blockEventData)) {
+ iterator.remove();
+ }
+ }
+ }
+
+ public BlockEventData removeFirstBlockEvent() {
+ BlockEventData ret;
+ while (!this.blockEvents.isEmpty()) {
+ ret = this.blockEvents.removeFirst();
+ if (TickThread.isTickThreadFor(this.world, ret.pos())) {
+ return ret;
+ } // else: chunk must have been unloaded
+ }
+
+ return null;
+ }
+
+ public LevelTicks<Block> getBlockLevelTicks() {
+ return this.blockLevelTicks;
+ }
+
+ public LevelTicks<Fluid> getFluidLevelTicks() {
+ return this.fluidLevelTicks;
+ }
+
+ // tile entity ticking
+ public void addBlockEntityTicker(final TickingBlockEntity ticker) {
+ TickThread.ensureTickThread(this.world, ticker.getPos(), "Tile entity must be owned by current region");
+
+ (this.tickingBlockEntities ? this.pendingBlockEntityTickers : this.blockEntityTickers).add(ticker);
+ }
+
+ public void seTtickingBlockEntities(final boolean to) {
+ this.tickingBlockEntities = true;
+ }
+
+ public List<TickingBlockEntity> getBlockEntityTickers() {
+ return this.blockEntityTickers;
+ }
+
+ public void pushPendingTickingBlockEntities() {
+ if (!this.pendingBlockEntityTickers.isEmpty()) {
+ this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
+ this.pendingBlockEntityTickers.clear();
+ }
+ }
+
+ // ticking chunks
+ public void addEntityTickingChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.entityTickingChunks.add(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public void removeEntityTickingChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.entityTickingChunks.remove(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public ReferenceList<ServerChunkCache.ChunkAndHolder> getEntityTickingChunks() {
+ return this.entityTickingChunks;
+ }
+
+ public void addTickingChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.tickingChunks.add(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public void removeTickingChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.tickingChunks.remove(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public ReferenceList<ServerChunkCache.ChunkAndHolder> getTickingChunks() {
+ return this.tickingChunks;
+ }
+
+ public void addChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.chunks.add(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public void removeChunk(final ServerChunkCache.ChunkAndHolder holder) {
+ this.chunks.remove(holder);
+ TickRegions.RegionStats.updateCurrentRegion();
+ }
+
+ public ReferenceList<ServerChunkCache.ChunkAndHolder> getChunks() {
+ return this.chunks;
+ }
+
+ public int getEntityTickingChunkCount() {
+ return this.entityTickingChunks.size();
+ }
+
+ public int getChunkCount() {
+ return this.chunks.size();
+ }
+}

View File

@ -1,94 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/Schedule.java
@@ -1,0 +_,91 @@
+package io.papermc.paper.threadedregions;
+
+/**
+ * A Schedule is an object that can be used to maintain a periodic schedule for an event of interest.
+ */
+public final class Schedule {
+
+ private long lastPeriod;
+
+ /**
+ * Initialises a schedule with the provided period.
+ * @param firstPeriod The last time an event of interest occurred.
+ * @see #setLastPeriod(long)
+ */
+ public Schedule(final long firstPeriod) {
+ this.lastPeriod = firstPeriod;
+ }
+
+ /**
+ * Updates the last period to the specified value. This call sets the last "time" the event
+ * of interest took place at. Thus, the value returned by {@link #getDeadline(long)} is
+ * the provided time plus the period length provided to {@code getDeadline}.
+ * @param value The value to set the last period to.
+ */
+ public void setLastPeriod(final long value) {
+ this.lastPeriod = value;
+ }
+
+ /**
+ * Returns the last time the event of interest should have taken place.
+ */
+ public long getLastPeriod() {
+ return this.lastPeriod;
+ }
+
+ /**
+ * Returns the number of times the event of interest should have taken place between the last
+ * period and the provided time given the period between each event.
+ * @param periodLength The length of the period between events in ns.
+ * @param time The provided time.
+ */
+ public int getPeriodsAhead(final long periodLength, final long time) {
+ final long difference = time - this.lastPeriod;
+ final int ret = (int)(Math.abs(difference) / periodLength);
+ return difference >= 0 ? ret : -ret;
+ }
+
+ /**
+ * Returns the next starting deadline for the event of interest to take place,
+ * given the provided period length.
+ * @param periodLength The provided period length.
+ */
+ public long getDeadline(final long periodLength) {
+ return this.lastPeriod + periodLength;
+ }
+
+ /**
+ * Adjusts the last period so that the next starting deadline returned is the next period specified,
+ * given the provided period length.
+ * @param nextPeriod The specified next starting deadline.
+ * @param periodLength The specified period length.
+ */
+ public void setNextPeriod(final long nextPeriod, final long periodLength) {
+ this.lastPeriod = nextPeriod - periodLength;
+ }
+
+ /**
+ * Increases the last period by the specified number of periods and period length.
+ * The specified number of periods may be < 0, in which case the last period
+ * will decrease.
+ * @param periods The specified number of periods.
+ * @param periodLength The specified period length.
+ */
+ public void advanceBy(final int periods, final long periodLength) {
+ this.lastPeriod += (long)periods * periodLength;
+ }
+
+ /**
+ * Sets the last period so that it is the specified number of periods ahead
+ * given the specified time and period length.
+ * @param periodsToBeAhead Specified number of periods to be ahead by.
+ * @param periodLength The specified period length.
+ * @param time The specified time.
+ */
+ public void setPeriodsAhead(final int periodsToBeAhead, final long periodLength, final long time) {
+ final int periodsAhead = this.getPeriodsAhead(periodLength, time);
+ final int periodsToAdd = periodsToBeAhead - periodsAhead;
+
+ this.lastPeriod -= (long)periodsToAdd * periodLength;
+ }
+}

View File

@ -1,85 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/TeleportUtils.java
@@ -1,0 +_,82 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import java.util.function.Consumer;
+
+public final class TeleportUtils {
+
+ public static <T extends Entity> void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch,
+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer<Entity> onComplete) {
+ teleport(from, useFromRootVehicle, to, yaw, pitch, teleportFlags, cause, onComplete, null);
+ }
+
+ public static <T extends Entity> void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch,
+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer<Entity> onComplete,
+ final java.util.function.Predicate<T> preTeleport) {
+ // retrieve coordinates
+ final CallbackCompletable<Location> positionCompletable = new CallbackCompletable<>();
+
+ positionCompletable.addWaiter(
+ (final Location loc, final Throwable thr) -> {
+ if (loc == null) {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ return;
+ }
+ final boolean scheduled = from.getBukkitEntity().taskScheduler.schedule(
+ (final T realFrom) -> {
+ final Vec3 pos = new Vec3(
+ loc.getX(), loc.getY(), loc.getZ()
+ );
+ if (preTeleport != null && !preTeleport.test(realFrom)) {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ return;
+ }
+ (useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync(
+ ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null,
+ cause, teleportFlags, onComplete
+ );
+ },
+ (final Entity retired) -> {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ },
+ 1L
+ );
+ if (!scheduled) {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ }
+ }
+ );
+
+ final boolean scheduled = to.getBukkitEntity().taskScheduler.schedule(
+ (final Entity target) -> {
+ positionCompletable.complete(target.getBukkitEntity().getLocation());
+ },
+ (final Entity retired) -> {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ },
+ 1L
+ );
+ if (!scheduled) {
+ if (onComplete != null) {
+ onComplete.accept(null);
+ }
+ }
+ }
+
+ private TeleportUtils() {}
+}

View File

@ -1,336 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/TickData.java
@@ -1,0 +_,333 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.util.TimeUtil;
+import io.papermc.paper.util.IntervalledCounter;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public final class TickData {
+
+ private final long interval; // ns
+
+ private final ArrayDeque<TickRegionScheduler.TickTime> timeData = new ArrayDeque<>();
+
+ public TickData(final long intervalNS) {
+ this.interval = intervalNS;
+ }
+
+ public void addDataFrom(final TickRegionScheduler.TickTime time) {
+ final long start = time.tickStart();
+
+ TickRegionScheduler.TickTime first;
+ while ((first = this.timeData.peekFirst()) != null) {
+ // only remove data completely out of window
+ if ((start - first.tickEnd()) <= this.interval) {
+ break;
+ }
+ this.timeData.pollFirst();
+ }
+
+ this.timeData.add(time);
+ }
+
+ // fromIndex inclusive, toIndex exclusive
+ // will throw if arr.length == 0
+ private static double median(final long[] arr, final int fromIndex, final int toIndex) {
+ final int len = toIndex - fromIndex;
+ final int middle = fromIndex + (len >>> 1);
+ if ((len & 1) == 0) {
+ // even, average the two middle points
+ return (double)(arr[middle - 1] + arr[middle]) / 2.0;
+ } else {
+ // odd, just grab the middle
+ return (double)arr[middle];
+ }
+ }
+
+ // will throw if arr.length == 0
+ private static SegmentData computeSegmentData(final long[] arr, final int fromIndex, final int toIndex,
+ final boolean inverse) {
+ final int len = toIndex - fromIndex;
+ long sum = 0L;
+ final double median = median(arr, fromIndex, toIndex);
+ long min = arr[0];
+ long max = arr[0];
+
+ for (int i = fromIndex; i < toIndex; ++i) {
+ final long val = arr[i];
+ sum += val;
+ if (val < min) {
+ min = val;
+ }
+ if (val > max) {
+ max = val;
+ }
+ }
+
+ if (inverse) {
+ // for positive a,b we have that a >= b if and only if 1/a <= 1/b
+ return new SegmentData(
+ len,
+ (double)len / ((double)sum / 1.0E9),
+ 1.0E9 / median,
+ 1.0E9 / (double)max,
+ 1.0E9 / (double)min
+ );
+ } else {
+ return new SegmentData(
+ len,
+ (double)sum / (double)len,
+ median,
+ (double)min,
+ (double)max
+ );
+ }
+ }
+
+ private static SegmentedAverage computeSegmentedAverage(final long[] data, final int allStart, final int allEnd,
+ final int percent99BestStart, final int percent99BestEnd,
+ final int percent95BestStart, final int percent95BestEnd,
+ final int percent1WorstStart, final int percent1WorstEnd,
+ final int percent5WorstStart, final int percent5WorstEnd,
+ final boolean inverse) {
+ return new SegmentedAverage(
+ computeSegmentData(data, allStart, allEnd, inverse),
+ computeSegmentData(data, percent99BestStart, percent99BestEnd, inverse),
+ computeSegmentData(data, percent95BestStart, percent95BestEnd, inverse),
+ computeSegmentData(data, percent1WorstStart, percent1WorstEnd, inverse),
+ computeSegmentData(data, percent5WorstStart, percent5WorstEnd, inverse)
+ );
+ }
+
+ private static record TickInformation(
+ long differenceFromLastTick,
+ long tickTime,
+ long tickTimeCPU
+ ) {}
+
+ // rets null if there is no data
+ public TickReportData generateTickReport(final TickRegionScheduler.TickTime inProgress, final long endTime) {
+ if (this.timeData.isEmpty() && inProgress == null) {
+ return null;
+ }
+
+ final List<TickRegionScheduler.TickTime> allData = new ArrayList<>(this.timeData);
+ if (inProgress != null) {
+ allData.add(inProgress);
+ }
+
+ final long intervalStart = allData.get(0).tickStart();
+ final long intervalEnd = allData.get(allData.size() - 1).tickEnd();
+
+ // to make utilisation accurate, we need to take the total time used over the last interval period -
+ // this means if a tick start before the measurement interval, but ends within the interval, then we
+ // only consider the time it spent ticking inside the interval
+ long totalTimeOverInterval = 0L;
+ long measureStart = endTime - this.interval;
+
+ for (int i = 0, len = allData.size(); i < len; ++i) {
+ final TickRegionScheduler.TickTime time = allData.get(i);
+ if (TimeUtil.compareTimes(time.tickStart(), measureStart) < 0) {
+ final long diff = time.tickEnd() - measureStart;
+ if (diff > 0L) {
+ totalTimeOverInterval += diff;
+ } // else: the time is entirely out of interval
+ } else {
+ totalTimeOverInterval += time.tickLength();
+ }
+ }
+
+ // we only care about ticks, but because of inbetween tick task execution
+ // there will be data in allData that isn't ticks. But, that data cannot
+ // be ignored since it contributes to utilisation.
+ // So, we will "compact" the data by merging any inbetween tick times
+ // the next tick.
+ // If there is no "next tick", then we will create one.
+ final List<TickInformation> collapsedData = new ArrayList<>();
+ for (int i = 0, len = allData.size(); i < len; ++i) {
+ final List<TickRegionScheduler.TickTime> toCollapse = new ArrayList<>();
+ TickRegionScheduler.TickTime lastTick = null;
+ for (;i < len; ++i) {
+ final TickRegionScheduler.TickTime time = allData.get(i);
+ if (!time.isTickExecution()) {
+ toCollapse.add(time);
+ continue;
+ }
+ lastTick = time;
+ break;
+ }
+
+ if (toCollapse.isEmpty()) {
+ // nothing to collapse
+ final TickRegionScheduler.TickTime last = allData.get(i);
+ collapsedData.add(
+ new TickInformation(
+ last.differenceFromLastTick(),
+ last.tickLength(),
+ last.supportCPUTime() ? last.tickCpuTime() : 0L
+ )
+ );
+ } else {
+ long totalTickTime = 0L;
+ long totalCpuTime = 0L;
+ for (int k = 0, len2 = collapsedData.size(); k < len2; ++k) {
+ final TickRegionScheduler.TickTime time = toCollapse.get(k);
+ totalTickTime += time.tickLength();
+ totalCpuTime += time.supportCPUTime() ? time.tickCpuTime() : 0L;
+ }
+ if (i < len) {
+ // we know there is a tick to collapse into
+ final TickRegionScheduler.TickTime last = allData.get(i);
+ collapsedData.add(
+ new TickInformation(
+ last.differenceFromLastTick(),
+ last.tickLength() + totalTickTime,
+ (last.supportCPUTime() ? last.tickCpuTime() : 0L) + totalCpuTime
+ )
+ );
+ } else {
+ // we do not have a tick to collapse into, so we must make one up
+ // we will assume that the tick is "starting now" and ongoing
+
+ // compute difference between imaginary tick and last tick
+ final long differenceBetweenTicks;
+ if (lastTick != null) {
+ // we have a last tick, use it
+ differenceBetweenTicks = lastTick.tickStart();
+ } else {
+ // we don't have a last tick, so we must make one up that makes sense
+ // if the current interval exceeds the max tick time, then use it
+
+ // Otherwise use the interval length.
+ // This is how differenceFromLastTick() works on TickTime when there is no previous interval.
+ differenceBetweenTicks = Math.max(
+ TickRegionScheduler.TIME_BETWEEN_TICKS, totalTickTime
+ );
+ }
+
+ collapsedData.add(
+ new TickInformation(
+ differenceBetweenTicks,
+ totalTickTime,
+ totalCpuTime
+ )
+ );
+ }
+ }
+ }
+
+
+ final int collectedTicks = collapsedData.size();
+ final long[] tickStartToStartDifferences = new long[collectedTicks];
+ final long[] timePerTickDataRaw = new long[collectedTicks];
+ final long[] missingCPUTimeDataRaw = new long[collectedTicks];
+
+ long totalTimeTicking = 0L;
+
+ int i = 0;
+ for (final TickInformation time : collapsedData) {
+ tickStartToStartDifferences[i] = time.differenceFromLastTick();
+ final long timePerTick = timePerTickDataRaw[i] = time.tickTime();
+ missingCPUTimeDataRaw[i] = Math.max(0L, timePerTick - time.tickTimeCPU());
+
+ ++i;
+
+ totalTimeTicking += timePerTick;
+ }
+
+ Arrays.sort(tickStartToStartDifferences);
+ Arrays.sort(timePerTickDataRaw);
+ Arrays.sort(missingCPUTimeDataRaw);
+
+ // Note: computeSegmentData cannot take start == end
+ final int allStart = 0;
+ final int allEnd = collectedTicks;
+ final int percent95BestStart = 0;
+ final int percent95BestEnd = collectedTicks == 1 ? 1 : (int)(0.95 * collectedTicks);
+ final int percent99BestStart = 0;
+ // (int)(0.99 * collectedTicks) == 0 if collectedTicks = 1, so we need to use 1 to avoid start == end
+ final int percent99BestEnd = collectedTicks == 1 ? 1 : (int)(0.99 * collectedTicks);
+ final int percent1WorstStart = (int)(0.99 * collectedTicks);
+ final int percent1WorstEnd = collectedTicks;
+ final int percent5WorstStart = (int)(0.95 * collectedTicks);
+ final int percent5WorstEnd = collectedTicks;
+
+ final SegmentedAverage tpsData = computeSegmentedAverage(
+ tickStartToStartDifferences,
+ allStart, allEnd,
+ percent99BestStart, percent99BestEnd,
+ percent95BestStart, percent95BestEnd,
+ percent1WorstStart, percent1WorstEnd,
+ percent5WorstStart, percent5WorstEnd,
+ true
+ );
+
+ final SegmentedAverage timePerTickData = computeSegmentedAverage(
+ timePerTickDataRaw,
+ allStart, allEnd,
+ percent99BestStart, percent99BestEnd,
+ percent95BestStart, percent95BestEnd,
+ percent1WorstStart, percent1WorstEnd,
+ percent5WorstStart, percent5WorstEnd,
+ false
+ );
+
+ final SegmentedAverage missingCPUTimeData = computeSegmentedAverage(
+ missingCPUTimeDataRaw,
+ allStart, allEnd,
+ percent99BestStart, percent99BestEnd,
+ percent95BestStart, percent95BestEnd,
+ percent1WorstStart, percent1WorstEnd,
+ percent5WorstStart, percent5WorstEnd,
+ false
+ );
+
+ final double utilisation = (double)totalTimeOverInterval / (double)this.interval;
+
+ return new TickReportData(
+ collectedTicks,
+ intervalStart,
+ intervalEnd,
+ totalTimeTicking,
+ utilisation,
+
+ tpsData,
+ timePerTickData,
+ missingCPUTimeData
+ );
+ }
+
+ public static final record TickReportData(
+ int collectedTicks,
+ long collectedTickIntervalStart,
+ long collectedTickIntervalEnd,
+ long totalTimeTicking,
+ double utilisation,
+
+ SegmentedAverage tpsData,
+ // in ns
+ SegmentedAverage timePerTickData,
+ // in ns
+ SegmentedAverage missingCPUTimeData
+ ) {}
+
+ public static final record SegmentedAverage(
+ SegmentData segmentAll,
+ SegmentData segment99PercentBest,
+ SegmentData segment95PercentBest,
+ SegmentData segment5PercentWorst,
+ SegmentData segment1PercentWorst
+ ) {}
+
+ public static final record SegmentData(
+ int count,
+ double average,
+ double median,
+ double least,
+ double greatest
+ ) {}
+}

View File

@ -1,567 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -1,0 +_,564 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool;
+import ca.spottedleaf.concurrentutil.util.TimeUtil;
+import ca.spottedleaf.moonrise.common.util.TickThread;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.util.TraceUtil;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.ChunkPos;
+import org.slf4j.Logger;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BooleanSupplier;
+
+public final class TickRegionScheduler {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
+ private static final boolean MEASURE_CPU_TIME;
+ static {
+ MEASURE_CPU_TIME = THREAD_MX_BEAN.isThreadCpuTimeSupported();
+ if (MEASURE_CPU_TIME) {
+ THREAD_MX_BEAN.setThreadCpuTimeEnabled(true);
+ } else {
+ LOGGER.warn("TickRegionScheduler CPU time measurement is not available");
+ }
+ }
+
+ public static final int TICK_RATE = 20;
+ public static final long TIME_BETWEEN_TICKS = 1_000_000_000L / TICK_RATE; // ns
+
+ private final SchedulerThreadPool scheduler;
+
+ public TickRegionScheduler(final int threads) {
+ this.scheduler = new SchedulerThreadPool(threads, new ThreadFactory() {
+ private final AtomicInteger idGenerator = new AtomicInteger();
+
+ @Override
+ public Thread newThread(final Runnable run) {
+ final Thread ret = new TickThreadRunner(run, "Region Scheduler Thread #" + this.idGenerator.getAndIncrement());
+ ret.setUncaughtExceptionHandler(TickRegionScheduler.this::uncaughtException);
+ return ret;
+ }
+ });
+ }
+
+ public int getTotalThreadCount() {
+ return this.scheduler.getThreads().length;
+ }
+
+ private static void setTickingRegion(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region) {
+ final Thread currThread = Thread.currentThread();
+ if (!(currThread instanceof TickThreadRunner tickThreadRunner)) {
+ throw new IllegalStateException("Must be tick thread runner");
+ }
+ if (region != null && tickThreadRunner.currentTickingRegion != null) {
+ throw new IllegalStateException("Trying to double set ticking region!");
+ }
+ if (region == null && tickThreadRunner.currentTickingRegion == null) {
+ throw new IllegalStateException("Trying to double unset ticking region!");
+ }
+ tickThreadRunner.currentTickingRegion = region;
+ if (region != null) {
+ tickThreadRunner.currentTickingWorldRegionizedData = region.regioniser.world.worldRegionData.get();
+ } else {
+ tickThreadRunner.currentTickingWorldRegionizedData = null;
+ }
+ }
+
+ private static void setTickTask(final SchedulerThreadPool.SchedulableTick task) {
+ final Thread currThread = Thread.currentThread();
+ if (!(currThread instanceof TickThreadRunner tickThreadRunner)) {
+ throw new IllegalStateException("Must be tick thread runner");
+ }
+ if (task != null && tickThreadRunner.currentTickingTask != null) {
+ throw new IllegalStateException("Trying to double set ticking task!");
+ }
+ if (task == null && tickThreadRunner.currentTickingTask == null) {
+ throw new IllegalStateException("Trying to double unset ticking task!");
+ }
+ tickThreadRunner.currentTickingTask = task;
+ }
+
+ /**
+ * Returns the current ticking region, or {@code null} if there is no ticking region.
+ * If this thread is not a TickThread, then returns {@code null}.
+ */
+ public static ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> getCurrentRegion() {
+ final Thread currThread = Thread.currentThread();
+ if (!(currThread instanceof TickThreadRunner tickThreadRunner)) {
+ return RegionShutdownThread.getRegion();
+ }
+ return tickThreadRunner.currentTickingRegion;
+ }
+
+ /**
+ * Returns the current ticking region's world regionised data, or {@code null} if there is no ticking region.
+ * This is a faster alternative to calling the {@link RegionizedData#get()} method.
+ * If this thread is not a TickThread, then returns {@code null}.
+ */
+ public static RegionizedWorldData getCurrentRegionizedWorldData() {
+ final Thread currThread = Thread.currentThread();
+ if (!(currThread instanceof TickThreadRunner tickThreadRunner)) {
+ return RegionShutdownThread.getWorldData();
+ }
+ return tickThreadRunner.currentTickingWorldRegionizedData;
+ }
+
+ /**
+ * Returns the current ticking task, or {@code null} if there is no ticking region.
+ * If this thread is not a TickThread, then returns {@code null}.
+ */
+ public static SchedulerThreadPool.SchedulableTick getCurrentTickingTask() {
+ final Thread currThread = Thread.currentThread();
+ if (!(currThread instanceof TickThreadRunner tickThreadRunner)) {
+ return null;
+ }
+ return tickThreadRunner.currentTickingTask;
+ }
+
+ /**
+ * Schedules the given region
+ * @throws IllegalStateException If the region is already scheduled or is ticking
+ */
+ public void scheduleRegion(final RegionScheduleHandle region) {
+ region.scheduler = this;
+ this.scheduler.schedule(region);
+ }
+
+ /**
+ * Attempts to de-schedule the provided region. If the current region cannot be cancelled for its next tick or task
+ * execution, then it will be cancelled after.
+ */
+ public void descheduleRegion(final RegionScheduleHandle region) {
+ // To avoid acquiring any of the locks the scheduler may be using, we
+ // simply cancel the next action.
+ region.markNonSchedulable();
+ }
+
+ /**
+ * Updates the tick start to the farthest into the future of its current scheduled time and the
+ * provided time.
+ * @return {@code false} if the region was not scheduled or is currently ticking or the specified time is less-than its
+ * current start time, {@code true} if the next tick start was adjusted.
+ */
+ public boolean updateTickStartToMax(final RegionScheduleHandle region, final long newStart) {
+ return this.scheduler.updateTickStartToMax(region, newStart);
+ }
+
+ public boolean halt(final boolean sync, final long maxWaitNS) {
+ return this.scheduler.halt(sync, maxWaitNS);
+ }
+
+ void dumpAliveThreadTraces(final String reason) {
+ for (final Thread thread : this.scheduler.getThreads()) {
+ if (thread.isAlive()) {
+ TraceUtil.dumpTraceForThread(thread, reason);
+ }
+ }
+ }
+
+ public void setHasTasks(final RegionScheduleHandle region) {
+ this.scheduler.notifyTasks(region);
+ }
+
+ public void init() {
+ this.scheduler.start();
+ }
+
+ private void uncaughtException(final Thread thread, final Throwable thr) {
+ LOGGER.error("Uncaught exception in tick thread \"" + thread.getName() + "\"", thr);
+
+ // prevent further ticks from occurring
+ // we CANNOT sync, because WE ARE ON A SCHEDULER THREAD
+ this.scheduler.halt(false, 0L);
+
+ MinecraftServer.getServer().stopServer();
+ }
+
+ private void regionFailed(final RegionScheduleHandle handle, final boolean executingTasks, final Throwable thr) {
+ // when a region fails, we need to shut down the server gracefully
+
+ // prevent further ticks from occurring
+ // we CANNOT sync, because WE ARE ON A SCHEDULER THREAD
+ this.scheduler.halt(false, 0L);
+
+ final ChunkPos center = handle.region == null ? null : handle.region.region.getCenterChunk();
+ final ServerLevel world = handle.region == null ? null : handle.region.world;
+
+ LOGGER.error("Region #" + (handle.region == null ? -1L : handle.region.id) + " centered at chunk " + center + " in world '" + (world == null ? "null" : world.getWorld().getName()) + "' failed to " + (executingTasks ? "execute tasks" : "tick") + ":", thr);
+
+ MinecraftServer.getServer().stopServer();
+ }
+
+ // By using our own thread object, we can use a field for the current region rather than a ThreadLocal.
+ // This is much faster than a thread local, since the thread local has to use a map lookup.
+ private static final class TickThreadRunner extends TickThread {
+
+ private ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> currentTickingRegion;
+ private RegionizedWorldData currentTickingWorldRegionizedData;
+ private SchedulerThreadPool.SchedulableTick currentTickingTask;
+
+ public TickThreadRunner(final Runnable run, final String name) {
+ super(run, name);
+ }
+ }
+
+ public static abstract class RegionScheduleHandle extends SchedulerThreadPool.SchedulableTick {
+
+ protected long currentTick;
+ protected long lastTickStart;
+
+ protected final TickData tickTimes5s;
+ protected final TickData tickTimes15s;
+ protected final TickData tickTimes1m;
+ protected final TickData tickTimes5m;
+ protected final TickData tickTimes15m;
+ protected TickTime currentTickData;
+ protected Thread currentTickingThread;
+
+ public final TickRegions.TickRegionData region;
+ private final AtomicBoolean cancelled = new AtomicBoolean();
+
+ protected final Schedule tickSchedule;
+
+ private TickRegionScheduler scheduler;
+
+ public RegionScheduleHandle(final TickRegions.TickRegionData region, final long firstStart) {
+ this.currentTick = 0L;
+ this.lastTickStart = SchedulerThreadPool.DEADLINE_NOT_SET;
+ this.tickTimes5s = new TickData(TimeUnit.SECONDS.toNanos(5L));
+ this.tickTimes15s = new TickData(TimeUnit.SECONDS.toNanos(15L));
+ this.tickTimes1m = new TickData(TimeUnit.MINUTES.toNanos(1L));
+ this.tickTimes5m = new TickData(TimeUnit.MINUTES.toNanos(5L));
+ this.tickTimes15m = new TickData(TimeUnit.MINUTES.toNanos(15L));
+ this.region = region;
+
+ this.setScheduledStart(firstStart);
+ this.tickSchedule = new Schedule(firstStart == SchedulerThreadPool.DEADLINE_NOT_SET ? firstStart : firstStart - TIME_BETWEEN_TICKS);
+ }
+
+ /**
+ * Subclasses should call this instead of {@link ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool.SchedulableTick#setScheduledStart(long)}
+ * so that the tick schedule and scheduled start remain synchronised
+ */
+ protected final void updateScheduledStart(final long to) {
+ this.setScheduledStart(to);
+ this.tickSchedule.setLastPeriod(to == SchedulerThreadPool.DEADLINE_NOT_SET ? to : to - TIME_BETWEEN_TICKS);
+ }
+
+ public final void markNonSchedulable() {
+ this.cancelled.set(true);
+ }
+
+ public final boolean isMarkedAsNonSchedulable() {
+ return this.cancelled.get();
+ }
+
+ protected abstract boolean tryMarkTicking();
+
+ protected abstract boolean markNotTicking();
+
+ protected abstract void tickRegion(final int tickCount, final long startTime, final long scheduledEnd);
+
+ protected abstract boolean runRegionTasks(final BooleanSupplier canContinue);
+
+ protected abstract boolean hasIntermediateTasks();
+
+ @Override
+ public final boolean hasTasks() {
+ return this.hasIntermediateTasks();
+ }
+
+ @Override
+ public final Boolean runTasks(final BooleanSupplier canContinue) {
+ if (this.cancelled.get()) {
+ return null;
+ }
+
+ final long cpuStart = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
+ final long tickStart = System.nanoTime();
+
+ if (!this.tryMarkTicking()) {
+ if (!this.cancelled.get()) {
+ throw new IllegalStateException("Scheduled region should be acquirable");
+ }
+ // region was killed
+ return null;
+ }
+
+ TickRegionScheduler.setTickTask(this);
+ if (this.region != null) {
+ TickRegionScheduler.setTickingRegion(this.region.region);
+ }
+
+ synchronized (this) {
+ this.currentTickData = new TickTime(
+ SchedulerThreadPool.DEADLINE_NOT_SET, SchedulerThreadPool.DEADLINE_NOT_SET, tickStart, cpuStart,
+ SchedulerThreadPool.DEADLINE_NOT_SET, SchedulerThreadPool.DEADLINE_NOT_SET, MEASURE_CPU_TIME,
+ false
+ );
+ this.currentTickingThread = Thread.currentThread();
+ }
+
+ final boolean ret;
+ try {
+ ret = this.runRegionTasks(() -> {
+ return !RegionScheduleHandle.this.cancelled.get() && canContinue.getAsBoolean();
+ });
+ } catch (final Throwable thr) {
+ this.scheduler.regionFailed(this, true, thr);
+ // don't release region for another tick
+ return null;
+ } finally {
+ final long tickEnd = System.nanoTime();
+ final long cpuEnd = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
+
+ final TickTime time = new TickTime(
+ SchedulerThreadPool.DEADLINE_NOT_SET, SchedulerThreadPool.DEADLINE_NOT_SET,
+ tickStart, cpuStart, tickEnd, cpuEnd, MEASURE_CPU_TIME, false
+ );
+
+ this.addTickTime(time);
+ TickRegionScheduler.setTickTask(null);
+ if (this.region != null) {
+ TickRegionScheduler.setTickingRegion(null);
+ }
+ }
+
+ return !this.markNotTicking() || this.cancelled.get() ? null : Boolean.valueOf(ret);
+ }
+
+ @Override
+ public final boolean runTick() {
+ // Remember, we are supposed use setScheduledStart if we return true here, otherwise
+ // the scheduler will try to schedule for the same time.
+ if (this.cancelled.get()) {
+ return false;
+ }
+
+ final long cpuStart = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
+ final long tickStart = System.nanoTime();
+
+ // use max(), don't assume that tickStart >= scheduledStart
+ final int tickCount = Math.max(1, this.tickSchedule.getPeriodsAhead(TIME_BETWEEN_TICKS, tickStart));
+
+ if (!this.tryMarkTicking()) {
+ if (!this.cancelled.get()) {
+ throw new IllegalStateException("Scheduled region should be acquirable");
+ }
+ // region was killed
+ return false;
+ }
+ if (this.cancelled.get()) {
+ this.markNotTicking();
+ // region should be killed
+ return false;
+ }
+
+ TickRegionScheduler.setTickTask(this);
+ if (this.region != null) {
+ TickRegionScheduler.setTickingRegion(this.region.region);
+ }
+ this.incrementTickCount();
+ final long lastTickStart = this.lastTickStart;
+ this.lastTickStart = tickStart;
+
+ final long scheduledStart = this.getScheduledStart();
+ final long scheduledEnd = scheduledStart + TIME_BETWEEN_TICKS;
+
+ synchronized (this) {
+ this.currentTickData = new TickTime(
+ lastTickStart, scheduledStart, tickStart, cpuStart,
+ SchedulerThreadPool.DEADLINE_NOT_SET, SchedulerThreadPool.DEADLINE_NOT_SET, MEASURE_CPU_TIME,
+ true
+ );
+ this.currentTickingThread = Thread.currentThread();
+ }
+
+ try {
+ // next start isn't updated until the end of this tick
+ this.tickRegion(tickCount, tickStart, scheduledEnd);
+ } catch (final Throwable thr) {
+ this.scheduler.regionFailed(this, false, thr);
+ // regionFailed will schedule a shutdown, so we should avoid letting this region tick further
+ return false;
+ } finally {
+ final long tickEnd = System.nanoTime();
+ final long cpuEnd = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getCurrentThreadCpuTime() : 0L;
+
+ // in order to ensure all regions get their chance at scheduling, we have to ensure that regions
+ // that exceed the max tick time are not always prioritised over everything else. Thus, we use the greatest
+ // of the current time and "ideal" next tick start.
+ this.tickSchedule.advanceBy(tickCount, TIME_BETWEEN_TICKS);
+ this.setScheduledStart(TimeUtil.getGreatestTime(tickEnd, this.tickSchedule.getDeadline(TIME_BETWEEN_TICKS)));
+
+ final TickTime time = new TickTime(
+ lastTickStart, scheduledStart, tickStart, cpuStart, tickEnd, cpuEnd, MEASURE_CPU_TIME, true
+ );
+
+ this.addTickTime(time);
+ TickRegionScheduler.setTickTask(null);
+ if (this.region != null) {
+ TickRegionScheduler.setTickingRegion(null);
+ }
+ }
+
+ // Only AFTER updating the tickStart
+ return this.markNotTicking() && !this.cancelled.get();
+ }
+
+ /**
+ * Only safe to call if this tick data matches the current ticking region.
+ */
+ protected void addTickTime(final TickTime time) {
+ synchronized (this) {
+ this.currentTickData = null;
+ this.currentTickingThread = null;
+ this.tickTimes5s.addDataFrom(time);
+ this.tickTimes15s.addDataFrom(time);
+ this.tickTimes1m.addDataFrom(time);
+ this.tickTimes5m.addDataFrom(time);
+ this.tickTimes15m.addDataFrom(time);
+ }
+ }
+
+ private TickTime adjustCurrentTickData(final long tickEnd) {
+ final TickTime currentTickData = this.currentTickData;
+ if (currentTickData == null) {
+ return null;
+ }
+
+ final long cpuEnd = MEASURE_CPU_TIME ? THREAD_MX_BEAN.getThreadCpuTime(this.currentTickingThread.getId()) : 0L;
+
+ return new TickTime(
+ currentTickData.previousTickStart(), currentTickData.scheduledTickStart(),
+ currentTickData.tickStart(), currentTickData.tickStartCPU(),
+ tickEnd, cpuEnd,
+ MEASURE_CPU_TIME, currentTickData.isTickExecution()
+ );
+ }
+
+ public final TickData.TickReportData getTickReport5s(final long currTime) {
+ synchronized (this) {
+ return this.tickTimes5s.generateTickReport(this.adjustCurrentTickData(currTime), currTime);
+ }
+ }
+
+ public final TickData.TickReportData getTickReport15s(final long currTime) {
+ synchronized (this) {
+ return this.tickTimes15s.generateTickReport(this.adjustCurrentTickData(currTime), currTime);
+ }
+ }
+
+ public final TickData.TickReportData getTickReport1m(final long currTime) {
+ synchronized (this) {
+ return this.tickTimes1m.generateTickReport(this.adjustCurrentTickData(currTime), currTime);
+ }
+ }
+
+ public final TickData.TickReportData getTickReport5m(final long currTime) {
+ synchronized (this) {
+ return this.tickTimes5m.generateTickReport(this.adjustCurrentTickData(currTime), currTime);
+ }
+ }
+
+ public final TickData.TickReportData getTickReport15m(final long currTime) {
+ synchronized (this) {
+ return this.tickTimes15m.generateTickReport(this.adjustCurrentTickData(currTime), currTime);
+ }
+ }
+
+ /**
+ * Only safe to call if this tick data matches the current ticking region.
+ */
+ private void incrementTickCount() {
+ ++this.currentTick;
+ }
+
+ /**
+ * Only safe to call if this tick data matches the current ticking region.
+ */
+ public final long getCurrentTick() {
+ return this.currentTick;
+ }
+
+ protected final void setCurrentTick(final long value) {
+ this.currentTick = value;
+ }
+ }
+
+ // All time units are in nanoseconds.
+ public static final record TickTime(
+ long previousTickStart,
+ long scheduledTickStart,
+ long tickStart,
+ long tickStartCPU,
+ long tickEnd,
+ long tickEndCPU,
+ boolean supportCPUTime,
+ boolean isTickExecution
+ ) {
+ /**
+ * The difference between the start tick time and the scheduled start tick time. This value is
+ * < 0 if the tick started before the scheduled tick time.
+ * Only valid when {@link #isTickExecution()} is {@code true}.
+ */
+ public final long startOvershoot() {
+ return this.tickStart - this.scheduledTickStart;
+ }
+
+ /**
+ * The difference from the end tick time and the start tick time. Always >= 0 (unless nanoTime is just wrong).
+ */
+ public final long tickLength() {
+ return this.tickEnd - this.tickStart;
+ }
+
+ /**
+ * The total CPU time from the start tick time to the end tick time. Generally should be equal to the tickLength,
+ * unless there is CPU starvation or the tick thread was blocked by I/O or other tasks. Returns Long.MIN_VALUE
+ * if CPU time measurement is not supported.
+ */
+ public final long tickCpuTime() {
+ if (!this.supportCPUTime()) {
+ return Long.MIN_VALUE;
+ }
+ return this.tickEndCPU - this.tickStartCPU;
+ }
+
+ /**
+ * The difference in time from the start of the last tick to the start of the current tick. If there is no
+ * last tick, then this value is max(TIME_BETWEEN_TICKS, tickLength).
+ * Only valid when {@link #isTickExecution()} is {@code true}.
+ */
+ public final long differenceFromLastTick() {
+ if (this.hasLastTick()) {
+ return this.tickStart - this.previousTickStart;
+ }
+ return Math.max(TIME_BETWEEN_TICKS, this.tickLength());
+ }
+
+ /**
+ * Returns whether there was a tick that occurred before this one.
+ * Only valid when {@link #isTickExecution()} is {@code true}.
+ */
+ public boolean hasLastTick() {
+ return this.previousTickStart != SchedulerThreadPool.DEADLINE_NOT_SET;
+ }
+
+ /*
+ * Remember, this is the expected behavior of the following:
+ *
+ * MSPT: Time per tick. This does not include overshoot time, just the tickLength().
+ *
+ * TPS: The number of ticks per second. It should be ticks / (sum of differenceFromLastTick).
+ */
+ }
+}

View File

@ -1,416 +0,0 @@
--- a/io/papermc/paper/threadedregions/TickRegions.java
+++ b/io/papermc/paper/threadedregions/TickRegions.java
@@ -1,10 +_,410 @@
package io.papermc.paper.threadedregions;
-// placeholder class for Folia
-public class TickRegions {
+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool;
+import ca.spottedleaf.concurrentutil.util.TimeUtil;
+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.configuration.GlobalConfiguration;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import org.slf4j.Logger;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BooleanSupplier;
+
+public final class TickRegions implements ThreadedRegionizer.RegionCallbacks<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static int regionShift = 31;
public static int getRegionChunkShift() {
- return ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ThreadedTicketLevelPropagator.SECTION_SHIFT;
+ return regionShift;
+ }
+
+ private static boolean initialised;
+ private static TickRegionScheduler scheduler;
+
+ public static TickRegionScheduler getScheduler() {
+ return scheduler;
+ }
+
+ public static void init(final GlobalConfiguration.ThreadedRegions config) {
+ if (initialised) {
+ return;
+ }
+ initialised = true;
+ int gridExponent = config.gridExponent;
+ gridExponent = Math.max(0, gridExponent);
+ gridExponent = Math.min(31, gridExponent);
+ regionShift = gridExponent;
+
+ int tickThreads;
+ if (config.threads <= 0) {
+ tickThreads = Runtime.getRuntime().availableProcessors() / 2;
+ if (tickThreads <= 4) {
+ tickThreads = 1;
+ } else {
+ tickThreads = tickThreads / 4;
+ }
+ } else {
+ tickThreads = config.threads;
+ }
+
+ scheduler = new TickRegionScheduler(tickThreads);
+ LOGGER.info("Regionised ticking is enabled with " + tickThreads + " tick threads");
+ }
+
+ @Override
+ public TickRegionData createNewData(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ return new TickRegionData(region);
+ }
+
+ @Override
+ public TickRegionSectionData createNewSectionData(final int sectionX, final int sectionZ, final int sectionShift) {
+ return null;
+ }
+
+ @Override
+ public void onRegionCreate(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ final TickRegionData data = region.getData();
+ // post-region merge/split regioninfo update
+ data.getRegionStats().updateFrom(data.getOrCreateRegionizedData(data.world.worldRegionData));
+ }
+
+ @Override
+ public void onRegionDestroy(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ // nothing for now
+ }
+
+ @Override
+ public void onRegionActive(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ final TickRegionData data = region.getData();
+
+ data.tickHandle.checkInitialSchedule();
+ scheduler.scheduleRegion(data.tickHandle);
+ }
+
+ @Override
+ public void onRegionInactive(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ final TickRegionData data = region.getData();
+
+ scheduler.descheduleRegion(data.tickHandle);
+ // old handle cannot be scheduled anymore, copy to a new handle
+ data.tickHandle = data.tickHandle.copy();
+ }
+
+ @Override
+ public void preMerge(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> from,
+ final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> into) {
+
+ }
+
+ @Override
+ public void preSplit(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> from,
+ final java.util.List<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>> into) {
+
+ }
+
+ public static final class TickRegionSectionData implements ThreadedRegionizer.ThreadedRegionSectionData {}
+
+ public static final class RegionStats {
+
+ private final AtomicInteger entityCount = new AtomicInteger();
+ private final AtomicInteger playerCount = new AtomicInteger();
+ private final AtomicInteger chunkCount = new AtomicInteger();
+
+ public int getEntityCount() {
+ return this.entityCount.get();
+ }
+
+ public int getPlayerCount() {
+ return this.playerCount.get();
+ }
+
+ public int getChunkCount() {
+ return this.chunkCount.get();
+ }
+
+ void updateFrom(final RegionizedWorldData data) {
+ this.entityCount.setRelease(data == null ? 0 : data.getEntityCount());
+ this.playerCount.setRelease(data == null ? 0 : data.getPlayerCount());
+ this.chunkCount.setRelease(data == null ? 0 : data.getChunkCount());
+ }
+
+ static void updateCurrentRegion() {
+ TickRegionScheduler.getCurrentRegion().getData().getRegionStats().updateFrom(TickRegionScheduler.getCurrentRegionizedWorldData());
+ }
+ }
+
+ public static final class TickRegionData implements ThreadedRegionizer.ThreadedRegionData<TickRegionData, TickRegionSectionData> {
+
+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
+ /** Never 0L, since 0L is reserved for global region. */
+ public final long id = ID_GENERATOR.incrementAndGet();
+
+ public final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region;
+ public final ServerLevel world;
+
+ // generic regionised data
+ private final Reference2ReferenceOpenHashMap<RegionizedData<?>, Object> regionizedData = new Reference2ReferenceOpenHashMap<>();
+
+ // tick data
+ private ConcreteRegionTickHandle tickHandle = new ConcreteRegionTickHandle(this, SchedulerThreadPool.DEADLINE_NOT_SET);
+
+ // queue data
+ private final RegionizedTaskQueue.RegionTaskQueueData taskQueueData;
+
+ // chunk holder manager data
+ private final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = new ChunkHolderManager.HolderManagerRegionData();
+
+ // async-safe read-only region data
+ private final RegionStats regionStats;
+
+ private TickRegionData(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region) {
+ this.region = region;
+ this.world = region.regioniser.world;
+ this.taskQueueData = new RegionizedTaskQueue.RegionTaskQueueData(this.world.taskQueueRegionData);
+ this.regionStats = new RegionStats();
+ }
+
+ public RegionStats getRegionStats() {
+ return this.regionStats;
+ }
+
+ public RegionizedTaskQueue.RegionTaskQueueData getTaskQueueData() {
+ return this.taskQueueData;
+ }
+
+ // the value returned can be invalidated at any time, except when the caller
+ // is ticking this region
+ public TickRegionScheduler.RegionScheduleHandle getRegionSchedulingHandle() {
+ return this.tickHandle;
+ }
+
+ public long getCurrentTick() {
+ return this.tickHandle.getCurrentTick();
+ }
+
+ public ChunkHolderManager.HolderManagerRegionData getHolderManagerRegionData() {
+ return this.holderManagerRegionData;
+ }
+
+ <T> T getRegionizedData(final RegionizedData<T> regionizedData) {
+ return (T)this.regionizedData.get(regionizedData);
+ }
+
+ <T> T getOrCreateRegionizedData(final RegionizedData<T> regionizedData) {
+ T ret = (T)this.regionizedData.get(regionizedData);
+
+ if (ret != null) {
+ return ret;
+ }
+
+ ret = regionizedData.createNewValue();
+ this.regionizedData.put(regionizedData, ret);
+
+ return ret;
+ }
+
+ @Override
+ public void split(final ThreadedRegionizer<TickRegionData, TickRegionSectionData> regioniser,
+ final Long2ReferenceOpenHashMap<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>> into,
+ final ReferenceOpenHashSet<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>> regions) {
+ final int shift = regioniser.sectionChunkShift;
+
+ // tick data
+ // note: here it is OK force us to access tick handle, as this region is owned (and thus not scheduled),
+ // and the other regions to split into are not scheduled yet.
+ for (final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region : regions) {
+ final TickRegionData data = region.getData();
+ data.tickHandle.copyDeadlineAndTickCount(this.tickHandle);
+ }
+
+ // generic regionised data
+ for (final Iterator<Reference2ReferenceMap.Entry<RegionizedData<?>, Object>> dataIterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator();
+ dataIterator.hasNext();) {
+ final Reference2ReferenceMap.Entry<RegionizedData<?>, Object> regionDataEntry = dataIterator.next();
+ final RegionizedData<?> data = regionDataEntry.getKey();
+ final Object from = regionDataEntry.getValue();
+
+ final ReferenceOpenHashSet<Object> dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f);
+
+ for (final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region : regions) {
+ dataSet.add(region.getData().getOrCreateRegionizedData(data));
+ }
+
+ final Long2ReferenceOpenHashMap<Object> regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f);
+
+ for (final Iterator<Long2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>>> regionIterator = into.long2ReferenceEntrySet().fastIterator();
+ regionIterator.hasNext();) {
+ final Long2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>> entry = regionIterator.next();
+ final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region = entry.getValue();
+ final Object to = region.getData().getOrCreateRegionizedData(data);
+
+ regionToData.put(entry.getLongKey(), to);
+ }
+
+ ((RegionizedData<Object>)data).getCallback().split(from, shift, regionToData, dataSet);
+ }
+
+ // chunk holder manager data
+ {
+ final ReferenceOpenHashSet<ChunkHolderManager.HolderManagerRegionData> dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f);
+
+ for (final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region : regions) {
+ dataSet.add(region.getData().holderManagerRegionData);
+ }
+
+ final Long2ReferenceOpenHashMap<ChunkHolderManager.HolderManagerRegionData> regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f);
+
+ for (final Iterator<Long2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>>> regionIterator = into.long2ReferenceEntrySet().fastIterator();
+ regionIterator.hasNext();) {
+ final Long2ReferenceMap.Entry<ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData>> entry = regionIterator.next();
+ final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> region = entry.getValue();
+ final ChunkHolderManager.HolderManagerRegionData to = region.getData().holderManagerRegionData;
+
+ regionToData.put(entry.getLongKey(), to);
+ }
+
+ this.holderManagerRegionData.split(shift, regionToData, dataSet);
+ }
+
+ // task queue
+ this.taskQueueData.split(regioniser, into);
+ }
+
+ @Override
+ public void mergeInto(final ThreadedRegionizer.ThreadedRegion<TickRegionData, TickRegionSectionData> into) {
+ // Note: merge target is always a region being released from ticking
+ final TickRegionData data = into.getData();
+ final long currentTickTo = data.getCurrentTick();
+ final long currentTickFrom = this.getCurrentTick();
+
+ // here we can access tickHandle because the target (into) is the region being released, so it is
+ // not actually scheduled
+ // there's not really a great solution to the tick problem, no matter what it'll be messed up
+ // we will pick the greatest time delay so that tps will not exceed TICK_RATE
+ data.tickHandle.updateSchedulingToMax(this.tickHandle);
+
+ // generic regionised data
+ final long fromTickOffset = currentTickTo - currentTickFrom; // see merge jd
+ for (final Iterator<Reference2ReferenceMap.Entry<RegionizedData<?>, Object>> iterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator();
+ iterator.hasNext();) {
+ final Reference2ReferenceMap.Entry<RegionizedData<?>, Object> entry = iterator.next();
+ final RegionizedData<?> regionizedData = entry.getKey();
+ final Object from = entry.getValue();
+ final Object to = into.getData().getOrCreateRegionizedData(regionizedData);
+
+ ((RegionizedData<Object>)regionizedData).getCallback().merge(from, to, fromTickOffset);
+ }
+
+ // chunk holder manager data
+ this.holderManagerRegionData.merge(into.getData().holderManagerRegionData, fromTickOffset);
+
+ // task queue
+ this.taskQueueData.mergeInto(data.taskQueueData);
+ }
+ }
+
+ private static final class ConcreteRegionTickHandle extends TickRegionScheduler.RegionScheduleHandle {
+
+ private final TickRegionData region;
+
+ private ConcreteRegionTickHandle(final TickRegionData region, final long start) {
+ super(region, start);
+ this.region = region;
+ }
+
+ private ConcreteRegionTickHandle copy() {
+ final ConcreteRegionTickHandle ret = new ConcreteRegionTickHandle(this.region, this.getScheduledStart());
+
+ ret.currentTick = this.currentTick;
+ ret.lastTickStart = this.lastTickStart;
+ ret.tickSchedule.setLastPeriod(this.tickSchedule.getLastPeriod());
+
+ return ret;
+ }
+
+ private void updateSchedulingToMax(final ConcreteRegionTickHandle from) {
+ if (from.getScheduledStart() == SchedulerThreadPool.DEADLINE_NOT_SET) {
+ return;
+ }
+
+ if (this.getScheduledStart() == SchedulerThreadPool.DEADLINE_NOT_SET) {
+ this.updateScheduledStart(from.getScheduledStart());
+ return;
+ }
+
+ this.updateScheduledStart(TimeUtil.getGreatestTime(from.getScheduledStart(), this.getScheduledStart()));
+ }
+
+ private void copyDeadlineAndTickCount(final ConcreteRegionTickHandle from) {
+ this.currentTick = from.currentTick;
+
+ if (from.getScheduledStart() == SchedulerThreadPool.DEADLINE_NOT_SET) {
+ return;
+ }
+
+ this.tickSchedule.setLastPeriod(from.tickSchedule.getLastPeriod());
+ this.setScheduledStart(from.getScheduledStart());
+ }
+
+ private void checkInitialSchedule() {
+ if (this.getScheduledStart() == SchedulerThreadPool.DEADLINE_NOT_SET) {
+ this.updateScheduledStart(System.nanoTime() + TickRegionScheduler.TIME_BETWEEN_TICKS);
+ }
+ }
+
+ @Override
+ protected boolean tryMarkTicking() {
+ return this.region.region.tryMarkTicking(ConcreteRegionTickHandle.this::isMarkedAsNonSchedulable);
+ }
+
+ @Override
+ protected boolean markNotTicking() {
+ return this.region.region.markNotTicking();
+ }
+
+ @Override
+ protected void tickRegion(final int tickCount, final long startTime, final long scheduledEnd) {
+ MinecraftServer.getServer().tickServer(startTime, scheduledEnd, TimeUnit.MILLISECONDS.toMillis(10L), this.region);
+ }
+
+ @Override
+ protected boolean runRegionTasks(final BooleanSupplier canContinue) {
+ final RegionizedTaskQueue.RegionTaskQueueData queue = this.region.taskQueueData;
+
+ boolean processedChunkTask = false;
+
+ boolean executeChunkTask = true;
+ boolean executeTickTask = true;
+ do {
+ if (executeTickTask) {
+ executeTickTask = queue.executeTickTask();
+ }
+ if (executeChunkTask) {
+ processedChunkTask |= (executeChunkTask = queue.executeChunkTask());
+ }
+ } while ((executeChunkTask | executeTickTask) && canContinue.getAsBoolean());
+
+ 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();
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean hasIntermediateTasks() {
+ return this.region.taskQueueData.hasTasks();
+ }
}
}

View File

@ -1,358 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java
@@ -1,0 +_,355 @@
+package io.papermc.paper.threadedregions.commands;
+
+import io.papermc.paper.threadedregions.RegionizedServer;
+import io.papermc.paper.threadedregions.RegionizedWorldData;
+import io.papermc.paper.threadedregions.ThreadedRegionizer;
+import io.papermc.paper.threadedregions.TickData;
+import io.papermc.paper.threadedregions.TickRegionScheduler;
+import io.papermc.paper.threadedregions.TickRegions;
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.ChunkPos;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public final class CommandServerHealth extends Command {
+
+ private static final ThreadLocal<DecimalFormat> TWO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> {
+ return new DecimalFormat("#,##0.00");
+ });
+ private static final ThreadLocal<DecimalFormat> ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> {
+ return new DecimalFormat("#,##0.0");
+ });
+ private static final ThreadLocal<DecimalFormat> NO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> {
+ return new DecimalFormat("#,##0");
+ });
+
+ private static final TextColor HEADER = TextColor.color(79, 164, 240);
+ private static final TextColor PRIMARY = TextColor.color(48, 145, 237);
+ private static final TextColor SECONDARY = TextColor.color(104, 177, 240);
+ private static final TextColor INFORMATION = TextColor.color(145, 198, 243);
+ private static final TextColor LIST = TextColor.color(33, 97, 188);
+
+ public CommandServerHealth() {
+ super("tps");
+ this.setUsage("/<command> [server/region] [lowest regions to display]");
+ this.setDescription("Reports information about server health.");
+ this.setPermission("bukkit.command.tps");
+ }
+
+ private static Component formatRegionInfo(final String prefix, final double util, final double mspt, final double tps,
+ final boolean newline) {
+ return Component.text()
+ .append(Component.text(prefix, PRIMARY, TextDecoration.BOLD))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util)))
+ .append(Component.text("% util at ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt)))
+ .append(Component.text(" MSPT at ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps)))
+ .append(Component.text(" TPS" + (newline ? "\n" : ""), PRIMARY))
+ .build();
+ }
+
+ private static Component formatRegionStats(final TickRegions.RegionStats stats, final boolean newline) {
+ return Component.text()
+ .append(Component.text("Chunks: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getChunkCount()), INFORMATION))
+ .append(Component.text(" Players: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getPlayerCount()), INFORMATION))
+ .append(Component.text(" Entities: ", PRIMARY))
+ .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getEntityCount()) + (newline ? "\n" : ""), INFORMATION))
+ .build();
+ }
+
+ private static boolean executeRegion(final CommandSender sender, final String commandLabel, final String[] args) {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
+ TickRegionScheduler.getCurrentRegion();
+ if (region == null) {
+ sender.sendMessage(Component.text("You are not in a region currently", NamedTextColor.RED));
+ return true;
+ }
+
+ final long currTime = System.nanoTime();
+
+ final TickData.TickReportData report15s = region.getData().getRegionSchedulingHandle().getTickReport15s(currTime);
+ final TickData.TickReportData report1m = region.getData().getRegionSchedulingHandle().getTickReport1m(currTime);
+
+ final ServerLevel world = region.regioniser.world;
+ final ChunkPos chunkCenter = region.getCenterChunk();
+ final int centerBlockX = ((chunkCenter.x << 4) | 7);
+ final int centerBlockZ = ((chunkCenter.z << 4) | 7);
+
+ final double util15s = report15s.utilisation();
+ final double tps15s = report15s.tpsData().segmentAll().average();
+ final double mspt15s = report15s.timePerTickData().segmentAll().average() / 1.0E6;
+
+ final double util1m = report1m.utilisation();
+ final double tps1m = report1m.tpsData().segmentAll().average();
+ final double mspt1m = report1m.timePerTickData().segmentAll().average() / 1.0E6;
+
+ final int yLoc = 80;
+ final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]";
+
+ final Component line = Component.text()
+ .append(Component.text("Region around block ", PRIMARY))
+ .append(Component.text(location, INFORMATION))
+ .append(Component.text(":\n", PRIMARY))
+
+ .append(
+ formatRegionInfo("15s: ", util15s, mspt15s, tps15s, true)
+ )
+ .append(
+ formatRegionInfo("1m: ", util1m, mspt1m, tps1m, true)
+ )
+ .append(
+ formatRegionStats(region.getData().getRegionStats(), false)
+ )
+
+ .build();
+
+ sender.sendMessage(line);
+
+ return true;
+ }
+
+ private static boolean executeServer(final CommandSender sender, final String commandLabel, final String[] args) {
+ final int lowestRegionsCount;
+ if (args.length < 2) {
+ lowestRegionsCount = 3;
+ } else {
+ try {
+ lowestRegionsCount = Integer.parseInt(args[1]);
+ } catch (final NumberFormatException ex) {
+ sender.sendMessage(Component.text("Highest utilisation count '" + args[1] + "' must be an integer", NamedTextColor.RED));
+ return true;
+ }
+ }
+
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>> regions =
+ new ArrayList<>();
+
+ for (final World bukkitWorld : Bukkit.getWorlds()) {
+ final ServerLevel world = ((CraftWorld)bukkitWorld).getHandle();
+ world.regioniser.computeForAllRegions(regions::add);
+ }
+
+ final double minTps;
+ final double medianTps;
+ final double maxTps;
+ double totalUtil = 0.0;
+
+ final DoubleArrayList tpsByRegion = new DoubleArrayList();
+ final List<TickData.TickReportData> reportsByRegion = new ArrayList<>();
+ final int maxThreadCount = TickRegions.getScheduler().getTotalThreadCount();
+
+ final long currTime = System.nanoTime();
+ final TickData.TickReportData globalTickReport = RegionizedServer.getGlobalTickData().getTickReport15s(currTime);
+
+ for (final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region : regions) {
+ final TickData.TickReportData report = region.getData().getRegionSchedulingHandle().getTickReport15s(currTime);
+ tpsByRegion.add(report == null ? 20.0 : report.tpsData().segmentAll().average());
+ reportsByRegion.add(report);
+ totalUtil += (report == null ? 0.0 : report.utilisation());
+ }
+
+ totalUtil += globalTickReport.utilisation();
+
+ tpsByRegion.sort(null);
+ if (!tpsByRegion.isEmpty()) {
+ minTps = tpsByRegion.getDouble(0);
+ maxTps = tpsByRegion.getDouble(tpsByRegion.size() - 1);
+
+ final int middle = tpsByRegion.size() >> 1;
+ if ((tpsByRegion.size() & 1) == 0) {
+ // even, average the two middle points
+ medianTps = (tpsByRegion.getDouble(middle - 1) + tpsByRegion.getDouble(middle)) / 2.0;
+ } else {
+ // odd, can just grab middle
+ medianTps = tpsByRegion.getDouble(middle);
+ }
+ } else {
+ // no regions = green
+ minTps = medianTps = maxTps = 20.0;
+ }
+
+ final List<ObjectObjectImmutablePair<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, TickData.TickReportData>>
+ regionsBelowThreshold = new ArrayList<>();
+
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ final TickData.TickReportData report = reportsByRegion.get(i);
+
+ regionsBelowThreshold.add(new ObjectObjectImmutablePair<>(regions.get(i), report));
+ }
+
+ regionsBelowThreshold.sort((p1, p2) -> {
+ final TickData.TickReportData report1 = p1.right();
+ final TickData.TickReportData report2 = p2.right();
+ final double util1 = report1 == null ? 0.0 : report1.utilisation();
+ final double util2 = report2 == null ? 0.0 : report2.utilisation();
+
+ // we want the largest first
+ return Double.compare(util2, util1);
+ });
+
+ final TextComponent.Builder lowestRegionsBuilder = Component.text();
+
+ if (sender instanceof Player) {
+ lowestRegionsBuilder.append(Component.text(" Click to teleport\n", SECONDARY));
+ }
+ for (int i = 0, len = Math.min(lowestRegionsCount, regionsBelowThreshold.size()); i < len; ++i) {
+ final ObjectObjectImmutablePair<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>, TickData.TickReportData>
+ pair = regionsBelowThreshold.get(i);
+
+ final TickData.TickReportData report = pair.right();
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region =
+ pair.left();
+
+ if (report == null) {
+ // skip regions with no data
+ continue;
+ }
+
+ final ServerLevel world = region.regioniser.world;
+ final ChunkPos chunkCenter = region.getCenterChunk();
+ if (chunkCenter == null) {
+ // region does not exist anymore
+ continue;
+ }
+ final int centerBlockX = ((chunkCenter.x << 4) | 7);
+ final int centerBlockZ = ((chunkCenter.z << 4) | 7);
+ final double util = report.utilisation();
+ final double tps = report.tpsData().segmentAll().average();
+ final double mspt = report.timePerTickData().segmentAll().average() / 1.0E6;
+
+ final int yLoc = 80;
+ final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]";
+ final Component line = Component.text()
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Region around block ", PRIMARY))
+ .append(Component.text(location, INFORMATION))
+ .append(Component.text(":\n", PRIMARY))
+
+ .append(Component.text(" ", PRIMARY))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util)))
+ .append(Component.text("% util at ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt)))
+ .append(Component.text(" MSPT at ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps)))
+ .append(Component.text(" TPS\n", PRIMARY))
+
+ .append(Component.text(" ", PRIMARY))
+ .append(formatRegionStats(region.getData().getRegionStats(), (i + 1) != len))
+ .build()
+
+ .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey().toString() + " run tp " + centerBlockX + ".5 " + yLoc + " " + centerBlockZ + ".5"))
+ .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("Click to teleport to " + location, SECONDARY)));
+
+ lowestRegionsBuilder.append(line);
+ }
+
+ sender.sendMessage(
+ Component.text()
+ .append(Component.text("Server Health Report\n", HEADER, TextDecoration.BOLD))
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Online Players: ", PRIMARY))
+ .append(Component.text(Bukkit.getOnlinePlayers().size() + "\n", INFORMATION))
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Total regions: ", PRIMARY))
+ .append(Component.text(regions.size() + "\n", INFORMATION))
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Utilisation: ", PRIMARY))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(totalUtil * 100.0), CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount)))
+ .append(Component.text("% / ", PRIMARY))
+ .append(Component.text(ONE_DECIMAL_PLACES.get().format(maxThreadCount * 100.0), INFORMATION))
+ .append(Component.text("%\n", PRIMARY))
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Lowest Region TPS: ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(minTps) + "\n", CommandUtil.getColourForTPS(minTps)))
+
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Median Region TPS: ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(medianTps) + "\n", CommandUtil.getColourForTPS(medianTps)))
+
+ .append(Component.text(" - ", LIST, TextDecoration.BOLD))
+ .append(Component.text("Highest Region TPS: ", PRIMARY))
+ .append(Component.text(TWO_DECIMAL_PLACES.get().format(maxTps) + "\n", CommandUtil.getColourForTPS(maxTps)))
+
+ .append(Component.text("Highest ", HEADER, TextDecoration.BOLD))
+ .append(Component.text(Integer.toString(lowestRegionsCount), INFORMATION, TextDecoration.BOLD))
+ .append(Component.text(" utilisation regions\n", HEADER, TextDecoration.BOLD))
+
+ .append(lowestRegionsBuilder.build())
+ .build()
+ );
+
+ return true;
+ }
+
+ @Override
+ public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
+ final String type;
+ if (args.length < 1) {
+ type = "server";
+ } else {
+ type = args[0];
+ }
+
+ switch (type.toLowerCase(Locale.ROOT)) {
+ case "server": {
+ return executeServer(sender, commandLabel, args);
+ }
+ case "region": {
+ if (!(sender instanceof Entity)) {
+ sender.sendMessage(Component.text("Cannot see current region information as console", NamedTextColor.RED));
+ return true;
+ }
+ return executeRegion(sender, commandLabel, args);
+ }
+ default: {
+ sender.sendMessage(Component.text("Type '" + args[0] + "' must be one of: [server, region]", NamedTextColor.RED));
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException {
+ if (args.length == 0) {
+ if (sender instanceof Entity) {
+ return CommandUtil.getSortedList(Arrays.asList("server", "region"));
+ } else {
+ return CommandUtil.getSortedList(Arrays.asList("server"));
+ }
+ } else if (args.length == 1) {
+ if (sender instanceof Entity) {
+ return CommandUtil.getSortedList(Arrays.asList("server", "region"), args[0]);
+ } else {
+ return CommandUtil.getSortedList(Arrays.asList("server"), args[0]);
+ }
+ }
+ return new ArrayList<>();
+ }
+}

View File

@ -1,124 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/commands/CommandUtil.java
@@ -1,0 +_,121 @@
+package io.papermc.paper.threadedregions.commands;
+
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.util.HSVLike;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+public final class CommandUtil {
+
+ public static List<String> getSortedList(final Iterable<String> iterable) {
+ final List<String> ret = new ArrayList<>();
+ for (final String val : iterable) {
+ ret.add(val);
+ }
+
+ ret.sort(String.CASE_INSENSITIVE_ORDER);
+
+ return ret;
+ }
+
+ public static List<String> getSortedList(final Iterable<String> iterable, final String prefix) {
+ final List<String> ret = new ArrayList<>();
+ for (final String val : iterable) {
+ if (val.regionMatches(0, prefix, 0, prefix.length())) {
+ ret.add(val);
+ }
+ }
+
+ ret.sort(String.CASE_INSENSITIVE_ORDER);
+
+ return ret;
+ }
+
+ public static <T> List<String> getSortedList(final Iterable<T> iterable, final Function<T, String> transform) {
+ final List<String> ret = new ArrayList<>();
+ for (final T val : iterable) {
+ final String transformed = transform.apply(val);
+ if (transformed != null) {
+ ret.add(transformed);
+ }
+ }
+
+ ret.sort(String.CASE_INSENSITIVE_ORDER);
+
+ return ret;
+ }
+
+ public static <T> List<String> getSortedList(final Iterable<T> iterable, final Function<T, String> transform, final String prefix) {
+ final List<String> ret = new ArrayList<>();
+ for (final T val : iterable) {
+ final String string = transform.apply(val);
+ if (string != null && string.regionMatches(0, prefix, 0, prefix.length())) {
+ ret.add(string);
+ }
+ }
+
+ ret.sort(String.CASE_INSENSITIVE_ORDER);
+
+ return ret;
+ }
+
+ public static TextColor getColourForTPS(final double tps) {
+ final double difference = Math.min(Math.abs(20.0 - tps), 20.0);
+ final double coordinate;
+ if (difference <= 2.0) {
+ // >= 18 tps
+ coordinate = 70.0 + ((140.0 - 70.0)/(0.0 - 2.0)) * (difference - 2.0);
+ } else if (difference <= 5.0) {
+ // >= 15 tps
+ coordinate = 30.0 + ((70.0 - 30.0)/(2.0 - 5.0)) * (difference - 5.0);
+ } else if (difference <= 10.0) {
+ // >= 10 tps
+ coordinate = 10.0 + ((30.0 - 10.0)/(5.0 - 10.0)) * (difference - 10.0);
+ } else {
+ // >= 0.0 tps
+ coordinate = 0.0 + ((10.0 - 0.0)/(10.0 - 20.0)) * (difference - 20.0);
+ }
+
+ return TextColor.color(HSVLike.hsvLike((float)(coordinate / 360.0), 85.0f / 100.0f, 80.0f / 100.0f));
+ }
+
+ public static TextColor getColourForMSPT(final double mspt) {
+ final double clamped = Math.min(Math.abs(mspt), 50.0);
+ final double coordinate;
+ if (clamped <= 15.0) {
+ coordinate = 130.0 + ((140.0 - 130.0)/(0.0 - 15.0)) * (clamped - 15.0);
+ } else if (clamped <= 25.0) {
+ coordinate = 90.0 + ((130.0 - 90.0)/(15.0 - 25.0)) * (clamped - 25.0);
+ } else if (clamped <= 35.0) {
+ coordinate = 30.0 + ((90.0 - 30.0)/(25.0 - 35.0)) * (clamped - 35.0);
+ } else if (clamped <= 40.0) {
+ coordinate = 15.0 + ((30.0 - 15.0)/(35.0 - 40.0)) * (clamped - 40.0);
+ } else {
+ coordinate = 0.0 + ((15.0 - 0.0)/(40.0 - 50.0)) * (clamped - 50.0);
+ }
+
+ return TextColor.color(HSVLike.hsvLike((float)(coordinate / 360.0), 85.0f / 100.0f, 80.0f / 100.0f));
+ }
+
+ public static TextColor getUtilisationColourRegion(final double util) {
+ // TODO anything better?
+ // assume 20TPS
+ return getColourForMSPT(util * 50.0);
+ }
+
+ public static ServerPlayer getPlayer(final String name) {
+ for (final ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) {
+ if (player.getGameProfile().getName().equalsIgnoreCase(name)) {
+ return player;
+ }
+ }
+
+ return null;
+ }
+
+ private CommandUtil() {}
+}

View File

@ -1,427 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java
@@ -1,0 +_,424 @@
+package io.papermc.paper.threadedregions.scheduler;
+
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
+import ca.spottedleaf.concurrentutil.util.Validate;
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
+import io.papermc.paper.threadedregions.RegionizedData;
+import io.papermc.paper.threadedregions.RegionizedServer;
+import io.papermc.paper.threadedregions.TickRegionScheduler;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.util.Unit;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.plugin.IllegalPluginAccessException;
+import org.bukkit.plugin.Plugin;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+
+public final class FoliaRegionScheduler implements RegionScheduler {
+
+ private static Runnable wrap(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Runnable run) {
+ return () -> {
+ try {
+ run.run();
+ } catch (final Throwable throwable) {
+ plugin.getLogger().log(Level.WARNING, "Location task for " + plugin.getDescription().getFullName()
+ + " in world " + world + " at " + chunkX + ", " + chunkZ + " generated an exception", throwable);
+ }
+ };
+ }
+
+ private static final RegionizedData<Scheduler> SCHEDULER_DATA = new RegionizedData<>(null, Scheduler::new, Scheduler.REGIONISER_CALLBACK);
+
+ private static void scheduleInternalOnRegion(final LocationScheduledTask task, final long delay) {
+ SCHEDULER_DATA.get().queueTask(task, delay);
+ }
+
+ private static void scheduleInternalOffRegion(final LocationScheduledTask task, final long delay) {
+ final World world = task.world;
+ if (world == null) {
+ // cancelled
+ return;
+ }
+
+ RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ ((CraftWorld) world).getHandle(), task.chunkX, task.chunkZ, () -> {
+ scheduleInternalOnRegion(task, delay);
+ }
+ );
+ }
+
+ @Override
+ public void execute(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Runnable run) {
+ Validate.notNull(plugin, "Plugin may not be null");
+ Validate.notNull(world, "World may not be null");
+ Validate.notNull(run, "Runnable may not be null");
+
+ RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ ((CraftWorld) world).getHandle(), chunkX, chunkZ, wrap(plugin, world, chunkX, chunkZ, run)
+ );
+ }
+
+ @Override
+ public ScheduledTask run(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Consumer<ScheduledTask> task) {
+ return this.runDelayed(plugin, world, chunkX, chunkZ, task, 1);
+ }
+
+ @Override
+ public ScheduledTask runDelayed(final Plugin plugin, final World world, final int chunkX, final int chunkZ,
+ final Consumer<ScheduledTask> task, final long delayTicks) {
+ Validate.notNull(plugin, "Plugin may not be null");
+ Validate.notNull(world, "World may not be null");
+ Validate.notNull(task, "Task may not be null");
+ if (delayTicks <= 0) {
+ throw new IllegalArgumentException("Delay ticks may not be <= 0");
+ }
+
+ if (!plugin.isEnabled()) {
+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
+ }
+
+ final LocationScheduledTask ret = new LocationScheduledTask(plugin, world, chunkX, chunkZ, -1, task);
+
+ if (Bukkit.isOwnedByCurrentRegion(world, chunkX, chunkZ)) {
+ scheduleInternalOnRegion(ret, delayTicks);
+ } else {
+ scheduleInternalOffRegion(ret, delayTicks);
+ }
+
+ if (!plugin.isEnabled()) {
+ // handle race condition where plugin is disabled asynchronously
+ ret.cancel();
+ }
+
+ return ret;
+ }
+
+ @Override
+ public ScheduledTask runAtFixedRate(final Plugin plugin, final World world, final int chunkX, final int chunkZ,
+ final Consumer<ScheduledTask> task, final long initialDelayTicks, final long periodTicks) {
+ Validate.notNull(plugin, "Plugin may not be null");
+ Validate.notNull(world, "World may not be null");
+ Validate.notNull(task, "Task may not be null");
+ if (initialDelayTicks <= 0) {
+ throw new IllegalArgumentException("Initial delay ticks may not be <= 0");
+ }
+ if (periodTicks <= 0) {
+ throw new IllegalArgumentException("Period ticks may not be <= 0");
+ }
+
+ if (!plugin.isEnabled()) {
+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
+ }
+
+ final LocationScheduledTask ret = new LocationScheduledTask(plugin, world, chunkX, chunkZ, periodTicks, task);
+
+ if (Bukkit.isOwnedByCurrentRegion(world, chunkX, chunkZ)) {
+ scheduleInternalOnRegion(ret, initialDelayTicks);
+ } else {
+ scheduleInternalOffRegion(ret, initialDelayTicks);
+ }
+
+ if (!plugin.isEnabled()) {
+ // handle race condition where plugin is disabled asynchronously
+ ret.cancel();
+ }
+
+ return ret;
+ }
+
+ public void tick() {
+ SCHEDULER_DATA.get().tick();
+ }
+
+ private static final class Scheduler {
+ private static final RegionizedData.RegioniserCallback<Scheduler> REGIONISER_CALLBACK = new RegionizedData.RegioniserCallback<>() {
+ @Override
+ public void merge(final Scheduler from, final Scheduler into, final long fromTickOffset) {
+ for (final Iterator<Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator();
+ sectionIterator.hasNext();) {
+ final Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>> entry = sectionIterator.next();
+ final long sectionKey = entry.getLongKey();
+ final Long2ObjectOpenHashMap<List<LocationScheduledTask>> section = entry.getValue();
+
+ final Long2ObjectOpenHashMap<List<LocationScheduledTask>> sectionAdjusted = new Long2ObjectOpenHashMap<>(section.size());
+
+ for (final Iterator<Long2ObjectMap.Entry<List<LocationScheduledTask>>> iterator = section.long2ObjectEntrySet().fastIterator();
+ iterator.hasNext();) {
+ final Long2ObjectMap.Entry<List<LocationScheduledTask>> e = iterator.next();
+ final long newTick = e.getLongKey() + fromTickOffset;
+ final List<LocationScheduledTask> tasks = e.getValue();
+
+ sectionAdjusted.put(newTick, tasks);
+ }
+
+ into.tasksByDeadlineBySection.put(sectionKey, sectionAdjusted);
+ }
+ }
+
+ @Override
+ public void split(final Scheduler from, final int chunkToRegionShift, final Long2ReferenceOpenHashMap<Scheduler> regionToData,
+ final ReferenceOpenHashSet<Scheduler> dataSet) {
+ for (final Scheduler into : dataSet) {
+ into.tickCount = from.tickCount;
+ }
+
+ for (final Iterator<Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator();
+ sectionIterator.hasNext();) {
+ final Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>> entry = sectionIterator.next();
+ final long sectionKey = entry.getLongKey();
+ final Long2ObjectOpenHashMap<List<LocationScheduledTask>> section = entry.getValue();
+
+ final Scheduler into = regionToData.get(sectionKey);
+
+ into.tasksByDeadlineBySection.put(sectionKey, section);
+ }
+ }
+ };
+
+ private long tickCount = 0L;
+ // map of region section -> map of deadline -> list of tasks
+ private final Long2ObjectOpenHashMap<Long2ObjectOpenHashMap<List<LocationScheduledTask>>> tasksByDeadlineBySection = new Long2ObjectOpenHashMap<>();
+
+ private void addTicket(final int sectionX, final int sectionZ) {
+ final ServerLevel world = TickRegionScheduler.getCurrentRegionizedWorldData().world;
+ final int shift = world.moonrise$getRegionChunkShift();
+ final int chunkX = sectionX << shift;
+ final int chunkZ = sectionZ << shift;
+
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel(
+ TicketType.REGION_SCHEDULER_API_HOLD, chunkX, chunkZ, ChunkHolderManager.MAX_TICKET_LEVEL, Unit.INSTANCE
+ );
+ }
+
+ private void removeTicket(final long sectionKey) {
+ final ServerLevel world = TickRegionScheduler.getCurrentRegionizedWorldData().world;
+ final int shift = world.moonrise$getRegionChunkShift();
+ final int chunkX = CoordinateUtils.getChunkX(sectionKey) << shift;
+ final int chunkZ = CoordinateUtils.getChunkZ(sectionKey) << shift;
+
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel(
+ TicketType.REGION_SCHEDULER_API_HOLD, chunkX, chunkZ, ChunkHolderManager.MAX_TICKET_LEVEL, Unit.INSTANCE
+ );
+ }
+
+ private void queueTask(final LocationScheduledTask task, final long delay) {
+ // note: must be on the thread that owns this scheduler
+ // note: delay > 0
+
+ final World world = task.world;
+ if (world == null) {
+ // cancelled
+ return;
+ }
+
+ final int shift = ((CraftWorld)world).getHandle().moonrise$getRegionChunkShift();
+ final int sectionX = task.chunkX >> shift;
+ final int sectionZ = task.chunkZ >> shift;
+
+ final Long2ObjectOpenHashMap<List<LocationScheduledTask>> section =
+ this.tasksByDeadlineBySection.computeIfAbsent(CoordinateUtils.getChunkKey(sectionX, sectionZ), (final long keyInMap) -> {
+ return new Long2ObjectOpenHashMap<>();
+ }
+ );
+
+ if (section.isEmpty()) {
+ // need to keep the scheduler loaded for this location in order for tick() to be called...
+ this.addTicket(sectionX, sectionZ);
+ }
+
+ section.computeIfAbsent(this.tickCount + delay, (final long keyInMap) -> {
+ return new ArrayList<>();
+ }).add(task);
+ }
+
+ public void tick() {
+ ++this.tickCount;
+
+ final List<LocationScheduledTask> run = new ArrayList<>();
+
+ for (final Iterator<Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>>> sectionIterator = this.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator();
+ sectionIterator.hasNext();) {
+ final Long2ObjectMap.Entry<Long2ObjectOpenHashMap<List<LocationScheduledTask>>> entry = sectionIterator.next();
+ final long sectionKey = entry.getLongKey();
+ final Long2ObjectOpenHashMap<List<LocationScheduledTask>> section = entry.getValue();
+
+ final List<LocationScheduledTask> tasks = section.remove(this.tickCount);
+
+ if (tasks == null) {
+ continue;
+ }
+
+ run.addAll(tasks);
+
+ if (section.isEmpty()) {
+ this.removeTicket(sectionKey);
+ sectionIterator.remove();
+ }
+ }
+
+ for (int i = 0, len = run.size(); i < len; ++i) {
+ run.get(i).run();
+ }
+ }
+ }
+
+ private static final class LocationScheduledTask implements ScheduledTask, Runnable {
+
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_EXECUTING = 1;
+ private static final int STATE_EXECUTING_CANCELLED = 2;
+ private static final int STATE_FINISHED = 3;
+ private static final int STATE_CANCELLED = 4;
+
+ private final Plugin plugin;
+ private final int chunkX;
+ private final int chunkZ;
+ private final long repeatDelay; // in ticks
+ private World world;
+ private Consumer<ScheduledTask> run;
+
+ private volatile int state;
+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(LocationScheduledTask.class, "state", int.class);
+
+ private LocationScheduledTask(final Plugin plugin, final World world, final int chunkX, final int chunkZ,
+ final long repeatDelay, final Consumer<ScheduledTask> run) {
+ this.plugin = plugin;
+ this.world = world;
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.repeatDelay = repeatDelay;
+ this.run = run;
+ }
+
+ private final int getStateVolatile() {
+ return (int)STATE_HANDLE.get(this);
+ }
+
+ private final int compareAndExchangeStateVolatile(final int expect, final int update) {
+ return (int)STATE_HANDLE.compareAndExchange(this, expect, update);
+ }
+
+ private final void setStateVolatile(final int value) {
+ STATE_HANDLE.setVolatile(this, value);
+ }
+
+ @Override
+ public void run() {
+ if (!this.plugin.isEnabled()) {
+ // don't execute if the plugin is disabled
+ return;
+ }
+
+ final boolean repeating = this.isRepeatingTask();
+ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) {
+ // cancelled
+ return;
+ }
+
+ try {
+ this.run.accept(this);
+ } catch (final Throwable throwable) {
+ this.plugin.getLogger().log(Level.WARNING, "Location task for " + this.plugin.getDescription().getFullName()
+ + " in world " + world + " at " + chunkX + ", " + chunkZ + " generated an exception", throwable);
+ } finally {
+ boolean reschedule = false;
+ if (!repeating) {
+ this.setStateVolatile(STATE_FINISHED);
+ } else if (!this.plugin.isEnabled()) {
+ this.setStateVolatile(STATE_CANCELLED);
+ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) {
+ reschedule = true;
+ } // else: cancelled repeating task
+
+ if (!reschedule) {
+ this.run = null;
+ this.world = null;
+ } else {
+ FoliaRegionScheduler.scheduleInternalOnRegion(this, this.repeatDelay);
+ }
+ }
+ }
+
+ @Override
+ public Plugin getOwningPlugin() {
+ return this.plugin;
+ }
+
+ @Override
+ public boolean isRepeatingTask() {
+ return this.repeatDelay > 0;
+ }
+
+ @Override
+ public CancelledState cancel() {
+ for (int curr = this.getStateVolatile();;) {
+ switch (curr) {
+ case STATE_IDLE: {
+ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) {
+ this.state = STATE_CANCELLED;
+ this.run = null;
+ this.world = null;
+ return CancelledState.CANCELLED_BY_CALLER;
+ }
+ // try again
+ continue;
+ }
+ case STATE_EXECUTING: {
+ if (!this.isRepeatingTask()) {
+ return CancelledState.RUNNING;
+ }
+ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) {
+ return CancelledState.NEXT_RUNS_CANCELLED;
+ }
+ // try again
+ continue;
+ }
+ case STATE_EXECUTING_CANCELLED: {
+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY;
+ }
+ case STATE_FINISHED: {
+ return CancelledState.ALREADY_EXECUTED;
+ }
+ case STATE_CANCELLED: {
+ return CancelledState.CANCELLED_ALREADY;
+ }
+ default: {
+ throw new IllegalStateException("Unknown state: " + curr);
+ }
+ }
+ }
+ }
+
+ @Override
+ public ExecutionState getExecutionState() {
+ final int state = this.getStateVolatile();
+ switch (state) {
+ case STATE_IDLE:
+ return ExecutionState.IDLE;
+ case STATE_EXECUTING:
+ return ExecutionState.RUNNING;
+ case STATE_EXECUTING_CANCELLED:
+ return ExecutionState.CANCELLED_RUNNING;
+ case STATE_FINISHED:
+ return ExecutionState.FINISHED;
+ case STATE_CANCELLED:
+ return ExecutionState.CANCELLED;
+ default: {
+ throw new IllegalStateException("Unknown state: " + state);
+ }
+ }
+ }
+ }
+}

View File

@ -1,82 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java
@@ -1,0 +_,79 @@
+package io.papermc.paper.threadedregions.util;
+
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.levelgen.BitRandomSource;
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
+import java.util.concurrent.ThreadLocalRandom;
+
+public final class SimpleThreadLocalRandomSource implements BitRandomSource {
+
+ public static final SimpleThreadLocalRandomSource INSTANCE = new SimpleThreadLocalRandomSource();
+
+ private final PositionalRandomFactory positionalRandomFactory = new SimpleThreadLocalRandomSource.SimpleThreadLocalRandomPositionalRandomFactory();
+
+ private SimpleThreadLocalRandomSource() {}
+
+ @Override
+ public int next(final int bits) {
+ return ThreadLocalRandom.current().nextInt() >>> (Integer.SIZE - bits);
+ }
+
+ @Override
+ public int nextInt() {
+ return ThreadLocalRandom.current().nextInt();
+ }
+
+ @Override
+ public int nextInt(final int bound) {
+ if (bound <= 0) {
+ throw new IllegalArgumentException();
+ }
+
+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+ final long value = (long)this.nextInt() & 0xFFFFFFFFL;
+ return (int)((value * (long)bound) >>> Integer.SIZE);
+ }
+
+ @Override
+ public void setSeed(final long seed) {
+ // no-op
+ }
+
+ @Override
+ public double nextGaussian() {
+ return ThreadLocalRandom.current().nextGaussian();
+ }
+
+ @Override
+ public RandomSource fork() {
+ return this;
+ }
+
+ @Override
+ public PositionalRandomFactory forkPositional() {
+ return this.positionalRandomFactory;
+ }
+
+ private static final class SimpleThreadLocalRandomPositionalRandomFactory implements PositionalRandomFactory {
+
+ @Override
+ public RandomSource fromHashOf(final String seed) {
+ return SimpleThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public RandomSource fromSeed(final long seed) {
+ return SimpleThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public RandomSource at(final int x, final int y, final int z) {
+ return SimpleThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public void parityConfigString(final StringBuilder info) {
+ info.append("SimpleThreadLocalRandomPositionalRandomFactory{}");
+ }
+ }
+}

View File

@ -1,76 +0,0 @@
--- /dev/null
+++ b/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java
@@ -1,0 +_,73 @@
+package io.papermc.paper.threadedregions.util;
+
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.levelgen.BitRandomSource;
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
+import java.util.concurrent.ThreadLocalRandom;
+
+public final class ThreadLocalRandomSource implements BitRandomSource {
+
+ public static final ThreadLocalRandomSource INSTANCE = new ThreadLocalRandomSource();
+
+ private final PositionalRandomFactory positionalRandomFactory = new ThreadLocalRandomPositionalRandomFactory();
+
+ private ThreadLocalRandomSource() {}
+
+ @Override
+ public int next(final int bits) {
+ return ThreadLocalRandom.current().nextInt() >>> (Integer.SIZE - bits);
+ }
+
+ @Override
+ public int nextInt() {
+ return ThreadLocalRandom.current().nextInt();
+ }
+
+ @Override
+ public int nextInt(final int bound) {
+ return ThreadLocalRandom.current().nextInt(bound);
+ }
+
+ @Override
+ public void setSeed(final long seed) {
+ // no-op
+ }
+
+ @Override
+ public double nextGaussian() {
+ return ThreadLocalRandom.current().nextGaussian();
+ }
+
+ @Override
+ public RandomSource fork() {
+ return this;
+ }
+
+ @Override
+ public PositionalRandomFactory forkPositional() {
+ return this.positionalRandomFactory;
+ }
+
+ private static final class ThreadLocalRandomPositionalRandomFactory implements PositionalRandomFactory {
+
+ @Override
+ public RandomSource fromHashOf(final String seed) {
+ return ThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public RandomSource fromSeed(final long seed) {
+ return ThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public RandomSource at(final int x, final int y, final int z) {
+ return ThreadLocalRandomSource.INSTANCE;
+ }
+
+ @Override
+ public void parityConfigString(final StringBuilder info) {
+ info.append("ThreadLocalRandomPositionalRandomFactory{}");
+ }
+ }
+}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -91,7 +_,7 @@
CommandResultCallback.EMPTY,
EntityAnchorArgument.Anchor.FEET,
CommandSigningContext.ANONYMOUS,
- TaskChainer.immediate(server)
+ TaskChainer.immediate((Runnable run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);}) // Folia - region threading
);
}

View File

@ -1,112 +0,0 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -153,13 +_,13 @@
AdvancementCommands.register(this.dispatcher);
AttributeCommand.register(this.dispatcher, context);
ExecuteCommand.register(this.dispatcher, context);
- BossBarCommands.register(this.dispatcher, context);
+ //BossBarCommands.register(this.dispatcher, context); // Folia - region threading - TODO
ClearInventoryCommands.register(this.dispatcher, context);
- CloneCommands.register(this.dispatcher, context);
+ //CloneCommands.register(this.dispatcher, context); // Folia - region threading - TODO
DamageCommand.register(this.dispatcher, context);
- DataCommands.register(this.dispatcher);
- DataPackCommand.register(this.dispatcher);
- DebugCommand.register(this.dispatcher);
+ //DataCommands.register(this.dispatcher); // Folia - region threading - TODO
+ //DataPackCommand.register(this.dispatcher); // Folia - region threading - TODO
+ //DebugCommand.register(this.dispatcher); // Folia - region threading - TODO
DefaultGameModeCommands.register(this.dispatcher);
DifficultyCommand.register(this.dispatcher);
EffectCommands.register(this.dispatcher, context);
@@ -169,47 +_,47 @@
FillCommand.register(this.dispatcher, context);
FillBiomeCommand.register(this.dispatcher, context);
ForceLoadCommand.register(this.dispatcher);
- FunctionCommand.register(this.dispatcher);
+ //FunctionCommand.register(this.dispatcher); // Folia - region threading - TODO
GameModeCommand.register(this.dispatcher);
GameRuleCommand.register(this.dispatcher, context);
GiveCommand.register(this.dispatcher, context);
HelpCommand.register(this.dispatcher);
- ItemCommands.register(this.dispatcher, context);
+ //ItemCommands.register(this.dispatcher, context); // Folia - region threading - TODO later
KickCommand.register(this.dispatcher);
KillCommand.register(this.dispatcher);
ListPlayersCommand.register(this.dispatcher);
LocateCommand.register(this.dispatcher, context);
- LootCommand.register(this.dispatcher, context);
+ //LootCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
MsgCommand.register(this.dispatcher);
ParticleCommand.register(this.dispatcher, context);
PlaceCommand.register(this.dispatcher);
PlaySoundCommand.register(this.dispatcher);
RandomCommand.register(this.dispatcher);
- ReloadCommand.register(this.dispatcher);
+ //ReloadCommand.register(this.dispatcher); // Folia - region threading
RecipeCommand.register(this.dispatcher);
- ReturnCommand.register(this.dispatcher);
- RideCommand.register(this.dispatcher);
- RotateCommand.register(this.dispatcher);
+ //ReturnCommand.register(this.dispatcher); // Folia - region threading - TODO later
+ //RideCommand.register(this.dispatcher); // Folia - region threading - TODO later
+ //RotateCommand.register(this.dispatcher); // Folia - region threading - TODO later
SayCommand.register(this.dispatcher);
- ScheduleCommand.register(this.dispatcher);
- ScoreboardCommand.register(this.dispatcher, context);
+ //ScheduleCommand.register(this.dispatcher); // Folia - region threading
+ //ScoreboardCommand.register(this.dispatcher, context); // Folia - region threading
SeedCommand.register(this.dispatcher, selection != Commands.CommandSelection.INTEGRATED);
SetBlockCommand.register(this.dispatcher, context);
SetSpawnCommand.register(this.dispatcher);
SetWorldSpawnCommand.register(this.dispatcher);
- SpectateCommand.register(this.dispatcher);
- SpreadPlayersCommand.register(this.dispatcher);
+ //SpectateCommand.register(this.dispatcher); // Folia - region threading - TODO later
+ //SpreadPlayersCommand.register(this.dispatcher); // Folia - region threading - TODO later
StopSoundCommand.register(this.dispatcher);
SummonCommand.register(this.dispatcher, context);
- TagCommand.register(this.dispatcher);
- TeamCommand.register(this.dispatcher, context);
- TeamMsgCommand.register(this.dispatcher);
+ //TagCommand.register(this.dispatcher); // Folia - region threading - TODO later
+ //TeamCommand.register(this.dispatcher, context); // Folia - region threading - TODO later
+ //TeamMsgCommand.register(this.dispatcher); // Folia - region threading - TODO later
TeleportCommand.register(this.dispatcher);
TellRawCommand.register(this.dispatcher, context);
- TickCommand.register(this.dispatcher);
+ //TickCommand.register(this.dispatcher); // Folia - region threading - TODO later
TimeCommand.register(this.dispatcher);
TitleCommand.register(this.dispatcher, context);
- TriggerCommand.register(this.dispatcher);
+ //TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
WeatherCommand.register(this.dispatcher);
WorldBorderCommand.register(this.dispatcher);
if (JvmProfiler.INSTANCE.isAvailable()) {
@@ -237,8 +_,8 @@
OpCommand.register(this.dispatcher);
PardonCommand.register(this.dispatcher);
PardonIpCommand.register(this.dispatcher);
- PerfCommand.register(this.dispatcher);
- SaveAllCommand.register(this.dispatcher);
+ //PerfCommand.register(this.dispatcher); // Folia - region threading - TODO later
+ //SaveAllCommand.register(this.dispatcher); // Folia - region threading - TODO later
SaveOffCommand.register(this.dispatcher);
SaveOnCommand.register(this.dispatcher);
SetPlayerIdleTimeoutCommand.register(this.dispatcher);
@@ -480,9 +_,12 @@
}
// Paper start - Perf: Async command map building
new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootCommandNode, false).callEvent(); // Paper - Brigadier API
- net.minecraft.server.MinecraftServer.getServer().execute(() -> {
- runSync(player, bukkit, rootCommandNode);
- });
+ // Folia start - region threading
+ // ignore if retired
+ player.getBukkitEntity().taskScheduler.schedule((ServerPlayer updatedPlayer) -> {
+ runSync((ServerPlayer)updatedPlayer, bukkit, rootCommandNode);
+ }, null, 1L);
+ // Folia end - region threading
}
private void runSync(ServerPlayer player, java.util.Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootCommandNode) {

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
@@ -46,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
@@ -78,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,149 +0,0 @@
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -89,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}
@@ -147,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}
@@ -201,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
world.getCraftServer().getPluginManager().callEvent(event);
}
@@ -251,7 +_,7 @@
org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos());
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy);
org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
world.getCraftServer().getPluginManager().callEvent(event);
}
@@ -329,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}
@@ -389,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event);
}
@@ -425,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}
@@ -482,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}
@@ -500,7 +_,8 @@
}
}
- level.captureTreeGeneration = true;
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading
+ worldData.captureTreeGeneration = true; // Folia - region threading
// CraftBukkit end
if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) {
this.setSuccess(false);
@@ -508,13 +_,13 @@
level.levelEvent(1505, blockPos, 15);
}
// CraftBukkit start
- level.captureTreeGeneration = false;
- if (level.capturedBlockStates.size() > 0) {
- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
- net.minecraft.world.level.block.SaplingBlock.treeType = null;
+ worldData.captureTreeGeneration = false; // Folia - region threading
+ if (worldData.capturedBlockStates.size() > 0) { // Folia - region threading
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // Folia - region threading
+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // Folia - region threading
org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld());
- List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
- level.capturedBlockStates.clear();
+ List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading
+ worldData.capturedBlockStates.clear(); // Folia - region threading
org.bukkit.event.world.StructureGrowEvent structureEvent = null;
if (treeType != null) {
structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks);
@@ -548,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}
@@ -591,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}
@@ -644,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
level.getCraftServer().getPluginManager().callEvent(event);
}
@@ -702,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - only single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}
@@ -783,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
@@ -39,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
world.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
@@ -62,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
+++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
@@ -32,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
@@ -25,7 +_,7 @@
org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
serverLevel.getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
+++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
@@ -27,7 +_,7 @@
org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading
blockSource.level().getCraftServer().getPluginManager().callEvent(event);
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/gametest/framework/GameTestHelper.java
+++ b/net/minecraft/gametest/framework/GameTestHelper.java
@@ -306,7 +_,7 @@
};
Connection connection = new Connection(PacketFlow.SERVERBOUND);
new EmbeddedChannel(connection);
- this.getLevel().getServer().getPlayerList().placeNewPlayer(connection, serverPlayer, commonListenerCookie);
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
return serverPlayer;
}

View File

@ -1,17 +0,0 @@
--- a/net/minecraft/gametest/framework/GameTestServer.java
+++ b/net/minecraft/gametest/framework/GameTestServer.java
@@ -175,8 +_,12 @@
}
@Override
- public void tickServer(BooleanSupplier hasTimeLeft) {
- super.tickServer(hasTimeLeft);
+ // Folia start - region threading
+ public void tickServer(long startTime, long scheduledEnd, long targetBuffer,
+ io.papermc.paper.threadedregions.TickRegions.TickRegionData region) {
+ if (true) throw new UnsupportedOperationException();
+ super.tickServer(startTime, scheduledEnd, targetBuffer, region);
+ // Folia end - region threading
ServerLevel serverLevel = this.overworld();
if (!this.haveTestsStarted()) {
this.startTests(serverLevel);

View File

@ -1,336 +0,0 @@
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -85,7 +_,7 @@
private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
private final PacketFlow receiving;
private volatile boolean sendLoginDisconnect = true;
- private final Queue<WrappedConsumer> pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network
+ private final Queue<WrappedConsumer> pendingActions = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>(); // Paper - Optimize network // Folia - region threading - connection fixes
public Channel channel;
public SocketAddress address;
// Spigot start
@@ -100,7 +_,7 @@
@Nullable
private DisconnectionDetails disconnectionDetails;
private boolean encrypted;
- private boolean disconnectionHandled;
+ private final java.util.concurrent.atomic.AtomicBoolean disconnectionHandled = new java.util.concurrent.atomic.AtomicBoolean(false); // Folia - region threading - may be called concurrently during configuration stage
private int receivedPackets;
private int sentPackets;
private float averageReceivedPackets;
@@ -154,6 +_,41 @@
this.receiving = receiving;
}
+ // Folia start - region threading
+ private volatile boolean becomeActive;
+
+ public boolean becomeActive() {
+ return this.becomeActive;
+ }
+
+ private static record DisconnectReq(DisconnectionDetails disconnectReason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {}
+
+ private final ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<DisconnectReq> disconnectReqs =
+ new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>();
+
+ /**
+ * Safely disconnects the connection while possibly on another thread. Note: This call will not block, even if on the
+ * same thread that could disconnect.
+ */
+ public final void disconnectSafely(DisconnectionDetails disconnectReason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
+ this.disconnectReqs.add(new DisconnectReq(disconnectReason, cause));
+ // We can't halt packet processing here because a plugin could cancel a kick request.
+ }
+
+ /**
+ * Safely disconnects the connection while possibly on another thread. Note: This call will not block, even if on the
+ * same thread that could disconnect.
+ */
+ public final void disconnectSafely(Component disconnectReason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
+ this.disconnectReqs.add(new DisconnectReq(new DisconnectionDetails(disconnectReason), cause));
+ // We can't halt packet processing here because a plugin could cancel a kick request.
+ }
+
+ public final boolean isPlayerConnected() {
+ return this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl;
+ }
+ // Folia end - region threading
+
@Override
public void channelActive(ChannelHandlerContext context) throws Exception {
super.channelActive(context);
@@ -163,6 +_,7 @@
if (this.delayedDisconnect != null) {
this.disconnect(this.delayedDisconnect);
}
+ this.becomeActive = true; // Folia - region threading
}
@Override
@@ -434,7 +_,7 @@
}
packet.onPacketDispatch(this.getPlayer());
- if (connected && (InnerUtil.canSendImmediate(this, packet)
+ if (false && connected && (InnerUtil.canSendImmediate(this, packet) // Folia - region threading - connection fixes
|| (io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.pendingActions.isEmpty()
&& (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())))) {
this.sendPacket(packet, listener, flush);
@@ -463,11 +_,12 @@
}
public void runOnceConnected(Consumer<Connection> action) {
- if (this.isConnected()) {
+ if (false && this.isConnected()) { // Folia - region threading - connection fixes
this.flushQueue();
action.accept(this);
} else {
this.pendingActions.add(new WrappedConsumer(action)); // Paper - Optimize network
+ this.flushQueue(); // Folia - region threading - connection fixes
}
}
@@ -518,10 +_,11 @@
}
public void flushChannel() {
- if (this.isConnected()) {
+ if (false && this.isConnected()) { // Folia - region threading - connection fixes
this.flush();
} else {
this.pendingActions.add(new WrappedConsumer(Connection::flush)); // Paper - Optimize network
+ this.flushQueue(); // Folia - region threading - connection fixes
}
}
@@ -535,53 +_,61 @@
// Paper start - Optimize network: Rewrite this to be safer if ran off main thread
private boolean flushQueue() {
- if (!this.isConnected()) {
- return true;
- }
- if (io.papermc.paper.util.MCUtil.isMainThread()) {
- return this.processQueue();
- } else if (this.isPending) {
- // Should only happen during login/status stages
- synchronized (this.pendingActions) {
- return this.processQueue();
- }
- }
- return false;
- }
+ return this.processQueue(); // Folia - region threading - connection fixes
+ }
+
+ // Folia start - region threading - connection fixes
+ // allow only one thread to be flushing the queue at once to ensure packets are written in the order they are sent
+ // into the queue
+ private final java.util.concurrent.atomic.AtomicBoolean flushingQueue = new java.util.concurrent.atomic.AtomicBoolean();
+
+ private static boolean canWrite(WrappedConsumer queued) {
+ return queued != null && (!(queued instanceof PacketSendAction packet) || packet.packet.isReady());
+ }
+
+ private boolean canWritePackets() {
+ return canWrite(this.pendingActions.peek());
+ }
+ // Folia end - region threading - connection fixes
private boolean processQueue() {
- if (this.pendingActions.isEmpty()) {
+ // Folia start - region threading - connection fixes
+ if (!this.isConnected()) {
return true;
}
- // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
- // But if we are not on main due to login/status, the parent is synchronized on packetQueue
- final java.util.Iterator<WrappedConsumer> iterator = this.pendingActions.iterator();
- while (iterator.hasNext()) {
- final WrappedConsumer queued = iterator.next(); // poll -> peek
-
- // Fix NPE (Spigot bug caused by handleDisconnection())
- if (queued == null) {
- return true;
- }
-
- if (queued.isConsumed()) {
- continue;
- }
-
- if (queued instanceof PacketSendAction packetSendAction) {
- final Packet<?> packet = packetSendAction.packet;
- if (!packet.isReady()) {
+ while (this.canWritePackets()) {
+ final boolean set = this.flushingQueue.getAndSet(true);
+ try {
+ if (set) {
+ // we didn't acquire the lock, break
return false;
}
- }
-
- iterator.remove();
- if (queued.tryMarkConsumed()) {
- queued.accept(this);
+
+ ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<WrappedConsumer> queue =
+ (ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<WrappedConsumer>)this.pendingActions;
+ WrappedConsumer holder;
+ for (;;) {
+ // synchronise so that queue clears appear atomic
+ synchronized (queue) {
+ holder = queue.pollIf(Connection::canWrite);
+ }
+ if (holder == null) {
+ break;
+ }
+
+ holder.accept(this);
+ }
+
+ } finally {
+ if (!set) {
+ this.flushingQueue.set(false);
+ }
}
}
+
return true;
+ // Folia end - region threading - connection fixes
}
// Paper end - Optimize network
@@ -590,17 +_,37 @@
private static int currTick; // Paper - Buffer joins to world
public void tick() {
this.flushQueue();
- // Paper start - Buffer joins to world
- if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
- Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
- Connection.joinAttemptsThisTick = 0;
- }
- // Paper end - Buffer joins to world
+ // Folia - this is broken
+ // Folia start - region threading
+ // handle disconnect requests, but only after flushQueue()
+ DisconnectReq disconnectReq;
+ while ((disconnectReq = this.disconnectReqs.poll()) != null) {
+ PacketListener packetlistener = this.packetListener;
+
+ if (packetlistener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) {
+ loginPacketListener.disconnect(disconnectReq.disconnectReason.reason());
+ // this doesn't fail, so abort any further attempts
+ return;
+ } else if (packetlistener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
+ commonPacketListener.disconnect(disconnectReq.disconnectReason, disconnectReq.cause);
+ // may be cancelled by a plugin, if not cancelled then any further calls do nothing
+ continue;
+ } else {
+ // no idea what packet to send
+ this.disconnect(disconnectReq.disconnectReason);
+ this.setReadOnly();
+ return;
+ }
+ }
+ if (!this.isConnected()) {
+ // disconnected from above
+ this.handleDisconnection();
+ return;
+ }
+ // Folia end - region threading
if (this.packetListener instanceof TickablePacketListener tickablePacketListener) {
// Paper start - Buffer joins to world
- if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
- || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
- || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
+ if (true) { // Folia - region threading
// Paper start - detailed watchdog information
net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
try {
@@ -611,7 +_,7 @@
} // Paper end - Buffer joins to world
}
- if (!this.isConnected() && !this.disconnectionHandled) {
+ if (!this.isConnected()) {// Folia - region threading - it's fine to call if it is already handled, as it no longer logs
this.handleDisconnection();
}
@@ -662,6 +_,7 @@
this.channel.close(); // We can't wait as this may be called from an event loop.
this.disconnectionDetails = disconnectionDetails;
}
+ this.becomeActive = true; // Folia - region threading
}
public boolean isMemoryConnection() {
@@ -853,10 +_,10 @@
public void handleDisconnection() {
if (this.channel != null && !this.channel.isOpen()) {
- if (this.disconnectionHandled) {
+ if (!this.disconnectionHandled.compareAndSet(false, true)) { // Folia - region threading - may be called concurrently during configuration stage
// LOGGER.warn("handleDisconnection() called twice"); // Paper - Don't log useless message
} else {
- this.disconnectionHandled = true;
+ //this.disconnectionHandled = true; // Folia - region threading - may be called concurrently during configuration stage - set above
PacketListener packetListener = this.getPacketListener();
PacketListener packetListener1 = packetListener != null ? packetListener : this.disconnectListener;
if (packetListener1 != null) {
@@ -885,6 +_,21 @@
}
}
// Paper end - Add PlayerConnectionCloseEvent
+ // Folia start - region threading
+ if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
+ net.minecraft.server.MinecraftServer.getServer().getPlayerList().removeConnection(
+ commonPacketListener.getOwner().getName(),
+ commonPacketListener.getOwner().getId(), this
+ );
+ } else if (packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) {
+ if (loginPacketListener.state.ordinal() >= net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING.ordinal()) {
+ net.minecraft.server.MinecraftServer.getServer().getPlayerList().removeConnection(
+ loginPacketListener.authenticatedProfile.getName(),
+ loginPacketListener.authenticatedProfile.getId(), this
+ );
+ }
+ }
+ // Folia end - region threading
}
}
}
@@ -904,15 +_,25 @@
// Paper start - Optimize network
public void clearPacketQueue() {
final net.minecraft.server.level.ServerPlayer player = getPlayer();
- for (final Consumer<Connection> queuedAction : this.pendingActions) {
- if (queuedAction instanceof PacketSendAction packetSendAction) {
- final Packet<?> packet = packetSendAction.packet;
- if (packet.hasFinishListener()) {
- packet.onPacketDispatchFinish(player, null);
+ // Folia start - region threading - connection fixes
+ java.util.List<Connection.PacketSendAction> queuedPackets = new java.util.ArrayList<>();
+ // synchronise so that flushQueue does not poll values while the queue is being cleared
+ synchronized (this.pendingActions) {
+ Connection.WrappedConsumer consumer;
+ while ((consumer = this.pendingActions.poll()) != null) {
+ if (consumer instanceof Connection.PacketSendAction packetHolder) {
+ queuedPackets.add(packetHolder);
}
}
}
- this.pendingActions.clear();
+
+ for (Connection.PacketSendAction queuedPacket : queuedPackets) {
+ Packet<?> packet = queuedPacket.packet;
+ if (packet.hasFinishListener()) {
+ packet.onPacketDispatchFinish(player, null);
+ }
+ }
+ // Folia end - region threading - connection fixes
}
private static class InnerUtil { // Attempt to hide these methods from ProtocolLib, so it doesn't accidently pick them up.

View File

@ -1,37 +0,0 @@
--- a/net/minecraft/network/protocol/PacketUtils.java
+++ b/net/minecraft/network/protocol/PacketUtils.java
@@ -20,7 +_,7 @@
public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
if (!executor.isSameThread()) {
- executor.executeIfPossible(() -> {
+ Runnable run = () -> { // Folia - region threading
packetProcessing.push(processor); // Paper - detailed watchdog information
try { // Paper - detailed watchdog information
if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
@@ -43,7 +_,24 @@
packetProcessing.pop();
}
// Paper end - detailed watchdog information
- });
+ // Folia start - region threading
+ };
+ // ignore retired state, if removed then we don't want the packet to be handled
+ if (processor instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) {
+ gamePacketListener.player.getBukkitEntity().taskScheduler.schedule(
+ (net.minecraft.server.level.ServerPlayer player) -> {
+ run.run();
+ },
+ null, 1L
+ );
+ } else if (processor instanceof net.minecraft.server.network.ServerConfigurationPacketListenerImpl configurationPacketListener) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);
+ } else if (processor instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);
+ } else {
+ throw new UnsupportedOperationException("Unknown listener: " + processor);
+ }
+ // Folia end - region threading
throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
}
}

View File

@ -1,779 +0,0 @@
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -184,7 +_,7 @@
private static final int OVERLOADED_TICKS_THRESHOLD = 20;
private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
- private static final long STATUS_EXPIRE_TIME_NANOS = 5L * TimeUtil.NANOSECONDS_PER_SECOND;
+ public static final long STATUS_EXPIRE_TIME_NANOS = 5L * TimeUtil.NANOSECONDS_PER_SECOND; // Folia - region threading - public
private static final long PREPARE_LEVELS_DEFAULT_DELAY_NANOS = 10L * TimeUtil.NANOSECONDS_PER_MILLISECOND;
private static final int MAX_STATUS_PLAYER_SAMPLE = 12;
private static final int SPAWN_POSITION_SEARCH_RADIUS = 5;
@@ -222,8 +_,7 @@
private volatile boolean running = true;
private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
private boolean stopped;
- private int tickCount;
- private int ticksUntilAutosave = 6000;
+ // Folia - region threading
protected final Proxy proxy;
private boolean onlineMode;
private boolean preventProxyConnections;
@@ -283,7 +_,7 @@
public org.bukkit.craftbukkit.CraftServer server;
public joptsimple.OptionSet options;
public org.bukkit.command.ConsoleCommandSender console;
- public static int currentTick; // Paper - improve tick loop
+ //public static int currentTick; // Paper - improve tick loop // Folia - region threading
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
// Paper - don't store the vanilla dispatcher
@@ -304,6 +_,50 @@
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
+ // Folia start - regionised ticking
+ public final io.papermc.paper.threadedregions.RegionizedServer regionizedServer = new io.papermc.paper.threadedregions.RegionizedServer();
+
+ @Override
+ public <V> CompletableFuture<V> submit(java.util.function.Supplier<V> task) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ return super.submit(task);
+ }
+
+ @Override
+ public CompletableFuture<Void> submit(Runnable task) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ return super.submit(task);
+ }
+
+ @Override
+ public void schedule(TickTask task) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.schedule(task);
+ }
+
+ @Override
+ public void executeBlocking(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.executeBlocking(runnable);
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.execute(runnable);
+ }
+ // Folia end - regionised ticking
+
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
AtomicReference<S> atomicReference = new AtomicReference<>();
@@ -332,46 +_,30 @@
private static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
private static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
- private long lastMidTickExecute;
- private long lastMidTickExecuteFailure;
-
- private boolean tickMidTickTasks() {
- // give all worlds a fair chance at by targeting them all.
- // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
- boolean executed = false;
- for (final ServerLevel world : this.getAllLevels()) {
- long currTime = System.nanoTime();
- if (currTime - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getLastMidTickFailure() <= TASK_EXECUTION_FAILURE_BACKOFF) {
- continue;
- }
- if (!world.getChunkSource().pollTask()) {
- // we need to back off if this fails
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$setLastMidTickFailure(currTime);
- } else {
- executed = true;
- }
- }
-
- return executed;
+ // Folia - region threading - moved to regionized data
+
+ private boolean tickMidTickTasks(final io.papermc.paper.threadedregions.RegionizedWorldData worldData) { // Folia - region threading
+ return worldData.world.getChunkSource().pollTask(); // Folia - region threading
}
@Override
public final void moonrise$executeMidTickTasks() {
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading
final long startTime = System.nanoTime();
- if ((startTime - this.lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - this.lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ if ((startTime - worldData.lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - worldData.lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) { // Folia - region threading
// it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
// so, backoff to prevent this
return;
}
for (;;) {
- final boolean moreTasks = this.tickMidTickTasks();
+ final boolean moreTasks = this.tickMidTickTasks(worldData); // Folia - region threading
final long currTime = System.nanoTime();
final long diff = currTime - startTime;
if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
if (!moreTasks) {
- this.lastMidTickExecuteFailure = currTime;
+ worldData.lastMidTickExecuteFailure = currTime; // Folia - region threading
}
// note: negative values reduce the time
@@ -384,7 +_,7 @@
final double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
final long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
- this.lastMidTickExecute = currTime + extraSleep;
+ worldData.lastMidTickExecute = currTime + extraSleep; // Folia - region threading
return;
}
}
@@ -710,7 +_,21 @@
// Back to the createLevels method without crazy modifications
primaryLevelData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
this.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up
- this.initWorld(serverLevel, primaryLevelData, this.worldData, worldOptions);
+ // Folia start - region threading
+ // the spawn should be within ~1024 blocks, so we force add ticket levels to ensure the first thread
+ // to init spawn will not run into any ownership issues
+ // move init to start of tickServer
+ int loadRegionRadius = 1024 >> 4;
+ serverLevel.randomSpawnSelection = new ChunkPos(serverLevel.getChunkSource().randomState().sampler().findSpawnPosition());
+ for (int currX = -loadRegionRadius; currX <= loadRegionRadius; ++currX) {
+ for (int currZ = -loadRegionRadius; currZ <= loadRegionRadius; ++currZ) {
+ ChunkPos pos = new ChunkPos(currX, currZ);
+ serverLevel.chunkSource.addTicketAtLevel(
+ net.minecraft.server.level.TicketType.UNKNOWN, pos, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, pos
+ );
+ }
+ }
+ // Folia end - region threading
// Paper - Put world into worldlist before initing the world; move up
this.getPlayerList().addWorldborderListener(serverLevel);
@@ -723,6 +_,7 @@
for (ServerLevel serverLevel : this.getAllLevels()) {
this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
// Paper - rewrite chunk system
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addWorld(serverLevel); // Folia - region threading
this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
}
@@ -804,10 +_,11 @@
}
}
// CraftBukkit end
- ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition()); // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set
+ ChunkPos chunkPos = level.randomSpawnSelection; // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set // Folia - region threading
int spawnHeight = chunkSource.getGenerator().getSpawnHeight(level);
if (spawnHeight < level.getMinY()) {
BlockPos worldPosition = chunkPos.getWorldPosition();
+ level.getChunk(worldPosition.offset(8, 0, 8)); // Folia - region threading - sync load first
spawnHeight = level.getHeight(Heightmap.Types.WORLD_SURFACE, worldPosition.getX() + 8, worldPosition.getZ() + 8);
}
@@ -869,14 +_,10 @@
int _int = serverLevel.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
int i = _int > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(_int)) : 0;
- while (chunkSource.getTickingGenerated() < i) {
- // CraftBukkit start
- // this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
- this.executeModerately();
- }
+ // Folia - region threading
// this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
- this.executeModerately();
+ //this.executeModerately(); // Folia - region threading
if (true) {
ServerLevel serverLevel1 = serverLevel;
@@ -895,7 +_,7 @@
// CraftBukkit start
// this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
- this.executeModerately();
+ //this.executeModerately(); // Folia - region threading
// CraftBukkit end
listener.stop();
// CraftBukkit start
@@ -985,7 +_,37 @@
}
// CraftBukkit end
+ // Folia start - region threading
+ private final java.util.concurrent.atomic.AtomicBoolean hasStartedShutdownThread = new java.util.concurrent.atomic.AtomicBoolean();
+
+ private void haltServerRegionThreading() {
+ if (this.hasStartedShutdownThread.getAndSet(true)) {
+ // already started shutdown
+ return;
+ }
+ new io.papermc.paper.threadedregions.RegionShutdownThread("Region shutdown thread").start();
+ }
+
+ public void haltCurrentRegion() {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isShutdownThread()) {
+ throw new IllegalStateException();
+ }
+ }
+ // Folia end - region threading
+
public void stopServer() {
+ // Folia start - region threading
+ // halt scheduler
+ // don't wait, we may be on a scheduler thread
+ io.papermc.paper.threadedregions.TickRegions.getScheduler().halt(false, 0L);
+ // cannot run shutdown logic on this thread, as it may be a scheduler
+ if (true) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isShutdownThread()) {
+ this.haltServerRegionThreading();
+ return;
+ } // else: fall through to regular stop logic
+ }
+ // Folia end - region threading
// CraftBukkit start - prevent double stopping on multiple threads
synchronized(this.stopLock) {
if (this.hasStopped) return;
@@ -1012,12 +_,19 @@
this.getConnection().stop();
this.isSaving = true;
if (this.playerList != null) {
- LOGGER.info("Saving players");
- this.playerList.saveAll();
+ //LOGGER.info("Saving players"); // Folia - move to shutdown thread logic
+ //this.playerList.saveAll(); // Folia - move to shutdown thread logic
this.playerList.removeAll(this.isRestarting); // Paper
try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
}
+ // Folia start - region threading
+ if (true) {
+ // the rest till part 2 is handled by the region shutdown thread
+ return;
+ }
+ // Folia end - region threading
+
LOGGER.info("Saving worlds");
for (ServerLevel serverLevel : this.getAllLevels()) {
@@ -1040,6 +_,11 @@
this.saveAllChunks(false, true, false, true); // Paper - rewrite chunk system
this.isSaving = false;
+ // Folia start - region threading
+ this.stopPart2();
+ }
+ public void stopPart2() {
+ // Folia end - region threading
this.resources.close();
try {
@@ -1098,6 +_,7 @@
if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
// Paper end
this.running = false;
+ this.stopServer(); // Folia - region threading
if (waitForServer) {
try {
this.serverThread.join();
@@ -1169,6 +_,18 @@
this.status = this.buildServerStatus();
this.server.spark.enableBeforePlugins(); // Paper - spark
+ // Folia start - region threading
+ if (true) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().init(); // Folia - region threading - only after loading worlds
+ final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Improve startup message
+ LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Improve startup message
+ for (;;) {
+ try {
+ Thread.sleep(Long.MAX_VALUE);
+ } catch (final InterruptedException ex) {}
+ }
+ }
+ // Folia end - region threading
// Spigot start
// Paper start
LOGGER.info("Running delayed init tasks");
@@ -1218,7 +_,7 @@
// Spigot start
// Paper start - further improve server tick loop
currentTime = Util.getNanos();
- if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
+ if (false) { // Folia - region threading
final long diff = currentTime - tickSection;
final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
tps1.add(currentTps, diff);
@@ -1237,7 +_,7 @@
boolean flag = l == 0L;
if (this.debugCommandProfilerDelayStart) {
this.debugCommandProfilerDelayStart = false;
- this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
+ //this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount); // Folia - region threading
}
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
@@ -1248,7 +_,7 @@
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("tick");
this.tickFrame.start();
- this.tickServer(flag ? () -> false : this::haveTime);
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
// Paper start - rewrite chunk system
final Throwable crash = this.chunkSystemCrash;
if (crash != null) {
@@ -1403,28 +_,24 @@
@Override
public TickTask wrapRunnable(Runnable runnable) {
- // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
- if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
- runnable.run();
- runnable = () -> {};
- }
- // Paper end
- return new TickTask(this.tickCount, runnable);
+ throw new UnsupportedOperationException(); // Folia - region threading
}
@Override
protected boolean shouldRun(TickTask runnable) {
- return runnable.getTick() + 3 < this.tickCount || this.haveTime();
+ throw new UnsupportedOperationException(); // Folia - region threading
}
@Override
public boolean pollTask() {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
boolean flag = this.pollTaskInternal();
this.mayHaveDelayedTasks = flag;
return flag;
}
private boolean pollTaskInternal() {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
if (super.pollTask()) {
this.moonrise$executeMidTickTasks(); // Paper - rewrite chunk system
return true;
@@ -1444,6 +_,7 @@
@Override
public void doRunTask(TickTask task) {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
Profiler.get().incrementCounter("runTask");
super.doRunTask(task);
}
@@ -1485,12 +_,15 @@
return false;
}
- public void tickServer(BooleanSupplier hasTimeLeft) {
+ // Folia start - region threading
+ public void tickServer(long startTime, long scheduledEnd, long targetBuffer,
+ io.papermc.paper.threadedregions.TickRegions.TickRegionData region) {
+ // Folia end - region threading
org.spigotmc.WatchdogThread.tick(); // Spigot
- long nanos = Util.getNanos();
+ long nanos = startTime; // Folia - region threading
int i = this.pauseWhileEmptySeconds() * 20;
this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
- if (i > 0) {
+ if (false && i > 0) { // Folia - region threading - this is complicated to implement, and even if done correctly is messy
if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping
this.emptyTicks++;
} else {
@@ -1515,24 +_,58 @@
level.getChunkSource().tick(() -> true, false);
}
// Paper end - avoid issues with certain tasks not processing during sleep
- this.server.spark.executeMainThreadTasks(); // Paper - spark
+ //this.server.spark.executeMainThreadTasks(); // Paper - spark // Folia - region threading
this.tickConnection();
this.server.spark.tickEnd(((double)(System.nanoTime() - lastTick) / 1000000D)); // Paper - spark
return;
}
}
+ // Folia start - region threading
+ region.world.getCurrentWorldData().updateTickData();
+ if (region.world.checkInitialised.get() != ServerLevel.WORLD_INIT_CHECKED) {
+ synchronized (region.world.checkInitialised) {
+ if (region.world.checkInitialised.compareAndSet(ServerLevel.WORLD_INIT_NOT_CHECKED, ServerLevel.WORLD_INIT_CHECKING)) {
+ LOGGER.info("Initialising world '" + region.world.getWorld().getName() + "' before it can be ticked...");
+ this.initWorld(region.world, region.world.serverLevelData, worldData, region.world.serverLevelData.worldGenOptions()); // Folia - delayed until first tick of world
+ region.world.checkInitialised.set(ServerLevel.WORLD_INIT_CHECKED);
+ LOGGER.info("Initialised world '" + region.world.getWorld().getName() + "'");
+ } // else: must be checked
+ }
+ }
+ BooleanSupplier hasTimeLeft = () -> {
+ return scheduledEnd - System.nanoTime() > targetBuffer;
+ };
+ // Folia end - region threading
+
this.server.spark.tickStart(); // Paper - spark
- new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper - Server Tick Events
- this.tickCount++;
- this.tickRateManager.tick();
- this.tickChildren(hasTimeLeft);
- if (nanos - this.lastServerStatus >= STATUS_EXPIRE_TIME_NANOS) {
+ new com.destroystokyo.paper.event.server.ServerTickStartEvent((int)region.getCurrentTick()).callEvent(); // Paper - Server Tick Events // Folia - region threading
+ // Folia start - region threading
+ if (region != null) {
+ region.getTaskQueueData().drainTasks();
+ ((io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler)org.bukkit.Bukkit.getRegionScheduler()).tick();
+ // now run all the entity schedulers
+ // TODO there has got to be a more efficient variant of this crap
+ for (net.minecraft.world.entity.Entity entity : region.world.getCurrentWorldData().getLocalEntitiesCopy()) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity) || entity.isRemoved()) {
+ continue;
+ }
+ org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
+ if (bukkit != null) {
+ bukkit.taskScheduler.executeTick();
+ }
+ }
+ }
+ // Folia end - region threading
+ //this.tickCount++; // Folia - region threading
+ //this.tickRateManager.tick(); // Folia - region threading
+ this.tickChildren(hasTimeLeft, region); // Folia - region threading
+ if (false && nanos - this.lastServerStatus >= STATUS_EXPIRE_TIME_NANOS) { // Folia - region threading
this.lastServerStatus = nanos;
this.status = this.buildServerStatus();
}
- this.ticksUntilAutosave--;
+ //this.ticksUntilAutosave--; // Folia - region threading
// Paper start - Incremental chunk and player saving
final ProfilerFiller profiler = Profiler.get();
int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate;
@@ -1540,15 +_,15 @@
playerSaveInterval = autosavePeriod;
}
profiler.push("save");
- final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0;
+ final boolean fullSave = autosavePeriod > 0 && io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() % autosavePeriod == 0; // Folia - region threading
try {
this.isSaving = true;
if (playerSaveInterval > 0) {
this.playerList.saveAll(playerSaveInterval);
}
- for (final ServerLevel level : this.getAllLevels()) {
+ for (final ServerLevel level : (region == null ? this.getAllLevels() : Arrays.asList(region.world))) { // Folia - region threading
if (level.paperConfig().chunks.autoSaveInterval.value() > 0) {
- level.saveIncrementally(fullSave);
+ level.saveIncrementally(region == null && fullSave); // Folia - region threading - don't save level.dat
}
}
} finally {
@@ -1558,32 +_,19 @@
// Paper end - Incremental chunk and player saving
ProfilerFiller profilerFiller = Profiler.get();
- this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
- this.server.spark.executeMainThreadTasks(); // Paper - spark
+ //this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings) // Folia - region threading
+ //this.server.spark.executeMainThreadTasks(); // Paper - spark // Folia - region threading
// Paper start - Server Tick Events
long endTime = System.nanoTime();
- long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
- new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
+ long remaining = scheduledEnd - endTime; // Folia - region ticking
+ new com.destroystokyo.paper.event.server.ServerTickEndEvent((int)io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(), ((double)(endTime - startTime) / 1000000D), remaining).callEvent(); // Folia - region ticking
// Paper end - Server Tick Events
- this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark
- profilerFiller.push("tallying");
- long l = Util.getNanos() - nanos;
- int i1 = this.tickCount % 100;
- this.aggregatedTickTimesNanos = this.aggregatedTickTimesNanos - this.tickTimesNanos[i1];
- this.aggregatedTickTimesNanos += l;
- this.tickTimesNanos[i1] = l;
- this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float)l / (float)TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
- // Paper start - Add tick times API and /mspt command
- this.tickTimes5s.add(this.tickCount, l);
- this.tickTimes10s.add(this.tickCount, l);
- this.tickTimes60s.add(this.tickCount, l);
- // Paper end - Add tick times API and /mspt command
- this.logTickMethodTime(nanos);
- profilerFiller.pop();
+ this.server.spark.tickEnd(((double)(endTime - startTime) / 1000000D)); // Paper - spark // Folia - region threading
+ // Folia - region threading
}
private void autoSave() {
- this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit
+ //this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit // Folia - region threading
LOGGER.debug("Autosave started");
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("save");
@@ -1598,30 +_,22 @@
}
}
- private int computeNextAutosaveInterval() {
- float f;
- if (this.tickRateManager.isSprinting()) {
- long l = this.getAverageTickTimeNanos() + 1L;
- f = (float)TimeUtil.NANOSECONDS_PER_SECOND / (float)l;
- } else {
- f = this.tickRateManager.tickrate();
- }
-
- int i = 300;
- return Math.max(100, (int)(f * 300.0F));
- }
+ // Folia - region threading - use absolute time instead of this
public void onTickRateChanged() {
- int i = this.computeNextAutosaveInterval();
- if (i < this.ticksUntilAutosave) {
- this.ticksUntilAutosave = i;
- }
+ // Folia - region threading - use absolute time instead of this
}
protected abstract SampleLogger getTickTimeLogger();
public abstract boolean isTickTimeLoggingEnabled();
+ // Folia start - region threading
+ public void rebuildServerStatus() {
+ this.status = this.buildServerStatus();
+ }
+ // Folia end - region threading
+
private ServerStatus buildServerStatus() {
ServerStatus.Players players = this.buildPlayerStatus();
return new ServerStatus(
@@ -1634,7 +_,7 @@
}
private ServerStatus.Players buildPlayerStatus() {
- List<ServerPlayer> players = this.playerList.getPlayers();
+ List<ServerPlayer> players = new java.util.ArrayList<>(this.playerList.getPlayers()); // Folia - region threading
int maxPlayers = this.getMaxPlayers();
if (this.hidesOnlinePlayers()) {
return new ServerStatus.Players(maxPlayers, players.size(), List.of());
@@ -1653,44 +_,34 @@
}
}
- protected void tickChildren(BooleanSupplier hasTimeLeft) {
+ protected void tickChildren(BooleanSupplier hasTimeLeft, io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - region threading
+ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - regionised ticking
ProfilerFiller profilerFiller = Profiler.get();
- this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
- this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
+ //this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing()); // Folia - region threading
+ //this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit // Folia - region threading
// Paper start - Folia scheduler API
- ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
- getAllLevels().forEach(level -> {
- for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) {
- if (entity.isRemoved()) {
- continue;
- }
- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
- if (bukkit != null) {
- bukkit.taskScheduler.executeTick();
- }
- }
- });
+ // Folia - region threading - moved to global tick - and moved entity scheduler to tickRegion
// Paper end - Folia scheduler API
- io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
+ //io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper // Folia - region threading - moved to global tick
profilerFiller.push("commandFunctions");
- this.getFunctions().tick();
+ //this.getFunctions().tick(); // Folia - region threading - TODO Purge functions
profilerFiller.popPush("levels");
// CraftBukkit start
// Run tasks that are waiting on processing
- while (!this.processQueue.isEmpty()) {
+ if (false) while (!this.processQueue.isEmpty()) { // Folia - region threading
this.processQueue.remove().run();
}
// Send time updates to everyone, it will get the right time from the world the player is in.
// Paper start - Perf: Optimize time updates
- for (final ServerLevel level : this.getAllLevels()) {
+ for (final ServerLevel level : Arrays.asList(region.world)) { // Folia - region threading
final boolean doDaylight = level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT);
final long dayTime = level.getDayTime();
long worldTime = level.getGameTime();
final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
- for (Player entityhuman : level.players()) {
- if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
+ for (Player entityhuman : level.getLocalPlayers()) { // Folia - region threading
+ if (!(entityhuman instanceof ServerPlayer) || (io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + entityhuman.getId()) % 20 != 0) { // Folia - region threading
continue;
}
ServerPlayer entityplayer = (ServerPlayer) entityhuman;
@@ -1703,12 +_,9 @@
}
}
- this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
- for (ServerLevel serverLevel : this.getAllLevels()) {
- serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
- serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
- serverLevel.updateLagCompensationTick(); // Paper - lag compensation
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ //this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked // Folia - region threading
+ for (ServerLevel serverLevel : Arrays.asList(region.world)) { // Folia - region threading
+ // Folia - region threading
profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
/* Drop global time updates
if (this.tickCount % 20 == 0) {
@@ -1721,7 +_,7 @@
profilerFiller.push("tick");
try {
- serverLevel.tick(hasTimeLeft);
+ serverLevel.tick(hasTimeLeft, region); // Folia - region threading
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world");
serverLevel.fillReportDetails(crashReport);
@@ -1730,27 +_,27 @@
profilerFiller.pop();
profilerFiller.pop();
- serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
+ regionizedWorldData.explosionDensityCache.clear(); // Paper - Optimize explosions // Folia - region threading
}
- this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
+ //this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked // Folia - region threading
profilerFiller.popPush("connection");
- this.tickConnection();
+ regionizedWorldData.tickConnections(); // Folia - region threading
profilerFiller.popPush("players");
- this.playerList.tick();
+ //this.playerList.tick(); // Folia - region threading
if (SharedConstants.IS_RUNNING_IN_IDE && this.tickRateManager.runsNormally()) {
GameTestTicker.SINGLETON.tick();
}
profilerFiller.popPush("server gui refresh");
- for (int i = 0; i < this.tickables.size(); i++) {
+ if (false) for (int i = 0; i < this.tickables.size(); i++) { // Folia - region threading - TODO WTF is this?
this.tickables.get(i).run();
}
profilerFiller.popPush("send chunks");
- for (ServerPlayer serverPlayer : this.playerList.getPlayers()) {
+ if (false) for (ServerPlayer serverPlayer : this.playerList.getPlayers()) { // Folia - region threading
serverPlayer.connection.chunkSender.sendNextChunks(serverPlayer);
serverPlayer.connection.resumeFlushing();
}
@@ -2073,7 +_,7 @@
}
public int getTickCount() {
- return this.tickCount;
+ throw new UnsupportedOperationException(); // Folia - region threading
}
public int getSpawnProtectionRadius() {
@@ -2128,6 +_,15 @@
}
public void invalidateStatus() {
+ // Folia start - region threading
+ if (true) {
+ // we don't need this to notify the global tick region
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTaskWithoutNotify(() -> {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().invalidateStatus();
+ });
+ return;
+ }
+ // Folia end - region threading
this.lastServerStatus = 0L;
}
@@ -2142,6 +_,7 @@
@Override
public void executeIfPossible(Runnable task) {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
if (this.isStopped()) {
throw new io.papermc.paper.util.ServerStopRejectedExecutionException("Server already shutting down"); // Paper - do not prematurely disconnect players on stop
} else {
@@ -2455,7 +_,12 @@
}
public long getAverageTickTimeNanos() {
- return this.aggregatedTickTimesNanos / Math.min(100, Math.max(this.tickCount, 1));
+ // Folia start - region threading
+ if (io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentTickingTask() instanceof io.papermc.paper.threadedregions.TickRegionScheduler.RegionScheduleHandle handle) {
+ return (long)Math.ceil(handle.getTickReport5s(System.nanoTime()).timePerTickData().segmentAll().average());
+ }
+ return 0L;
+ // Folia end - region threading
}
public long[] getTickTimesNanos() {
@@ -2705,13 +_,7 @@
}
public ProfileResults stopTimeProfiler() {
- if (this.debugCommandProfiler == null) {
- return EmptyProfileResults.EMPTY;
- } else {
- ProfileResults profileResults = this.debugCommandProfiler.stop(Util.getNanos(), this.tickCount);
- this.debugCommandProfiler = null;
- return profileResults;
- }
+ throw new UnsupportedOperationException(); // Folia - region threading
}
public int getMaxChainedNeighborUpdates() {
@@ -2896,24 +_,15 @@
// Paper start - API to check if the server is sleeping
public boolean isTickPaused() {
- return this.emptyTicks > 0 && this.emptyTicks >= this.pauseWhileEmptySeconds() * 20;
+ return false; // Folia - region threading
}
public void addPluginAllowingSleep(final String pluginName, final boolean value) {
- if (!value) {
- this.pluginsBlockingSleep.add(pluginName);
- } else {
- this.pluginsBlockingSleep.remove(pluginName);
- }
+ // Folia - region threading
}
private void removeDisabledPluginsBlockingSleep() {
- if (this.pluginsBlockingSleep.isEmpty()) {
- return;
- }
- this.pluginsBlockingSleep.removeIf(plugin -> (
- !io.papermc.paper.plugin.manager.PaperPluginManagerImpl.getInstance().isPluginEnabled(plugin)
- ));
+ // Folia - region threading
}
// Paper end - API to check if the server is sleeping
}

View File

@ -1,32 +0,0 @@
--- a/net/minecraft/server/commands/AdvancementCommands.java
+++ b/net/minecraft/server/commands/AdvancementCommands.java
@@ -246,7 +_,12 @@
int i = 0;
for (ServerPlayer serverPlayer : targets) {
- i += action.perform(serverPlayer, advancements);
+ // Folia start - region threading
+ i += 1;
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ action.perform(player, advancements);
+ }, null, 1L);
+ // Folia end - region threading
}
if (i == 0) {
@@ -310,9 +_,12 @@
throw ERROR_CRITERION_NOT_FOUND.create(Advancement.name(advancement), criterionName);
} else {
for (ServerPlayer serverPlayer : targets) {
- if (action.performCriterion(serverPlayer, advancement, criterionName)) {
- i++;
- }
+ // Folia start - region threading
+ ++i;
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ action.performCriterion(player, advancement, criterionName);
+ }, null, 1L);
+ // Folia end - region threading
}
if (i == 0) {

View File

@ -1,188 +0,0 @@
--- a/net/minecraft/server/commands/AttributeCommand.java
+++ b/net/minecraft/server/commands/AttributeCommand.java
@@ -266,30 +_,62 @@
}
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int getAttributeValue(CommandSourceStack source, Entity entity, Holder<Attribute> attribute, double scale) throws CommandSyntaxException {
- LivingEntity entityWithAttribute = getEntityWithAttribute(entity, attribute);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ LivingEntity entityWithAttribute = getEntityWithAttribute(nmsEntity, attribute); // Folia - region threading
double attributeValue = entityWithAttribute.getAttributeValue(attribute);
source.sendSuccess(
- () -> Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), entity.getName(), attributeValue), false
+ () -> Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), attributeValue), false // Folia - region threading
);
- return (int)(attributeValue * scale);
+ return; // Folia - region threading
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static int getAttributeBase(CommandSourceStack source, Entity entity, Holder<Attribute> attribute, double scale) throws CommandSyntaxException {
- LivingEntity entityWithAttribute = getEntityWithAttribute(entity, attribute);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ LivingEntity entityWithAttribute = getEntityWithAttribute(nmsEntity, attribute); // Folia - region threading
double attributeBaseValue = entityWithAttribute.getAttributeBaseValue(attribute);
source.sendSuccess(
- () -> Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), entity.getName(), attributeBaseValue),
+ () -> Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), attributeBaseValue), // Folia - region threading
false
);
- return (int)(attributeBaseValue * scale);
+ return; // Folia - region threading
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static int getAttributeModifier(CommandSourceStack source, Entity entity, Holder<Attribute> attribute, ResourceLocation id, double scale) throws CommandSyntaxException {
- LivingEntity entityWithAttribute = getEntityWithAttribute(entity, attribute);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ LivingEntity entityWithAttribute = getEntityWithAttribute(nmsEntity, attribute); // Folia - region threading
AttributeMap attributes = entityWithAttribute.getAttributes();
if (!attributes.hasModifier(attribute, id)) {
- throw ERROR_NO_SUCH_MODIFIER.create(entity.getName(), getAttributeDescription(attribute), id);
+ throw ERROR_NO_SUCH_MODIFIER.create(nmsEntity.getName(), getAttributeDescription(attribute), id); // Folia - region threading
} else {
double modifierValue = attributes.getModifierValue(attribute, id);
source.sendSuccess(
@@ -297,13 +_,20 @@
"commands.attribute.modifier.value.get.success",
Component.translationArg(id),
getAttributeDescription(attribute),
- entity.getName(),
+ nmsEntity.getName(), // Folia - region threading
modifierValue
),
false
);
- return (int)(modifierValue * scale);
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static Stream<ResourceLocation> getAttributeModifiers(Entity entity, Holder<Attribute> attribute) throws CommandSyntaxException {
@@ -312,11 +_,22 @@
}
private static int setAttributeBase(CommandSourceStack source, Entity entity, Holder<Attribute> attribute, double value) throws CommandSyntaxException {
- getAttributeInstance(entity, attribute).setBaseValue(value);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ getAttributeInstance(nmsEntity, attribute).setBaseValue(value); // Folia - region threading
source.sendSuccess(
- () -> Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), entity.getName(), value), false
+ () -> Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), nmsEntity.getName(), value), false // Folia - region threading
);
- return 1;
+ return; // Folia - region threading
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static int resetAttributeBase(CommandSourceStack source, Entity entity, Holder<Attribute> attribute) throws CommandSyntaxException {
@@ -338,35 +_,57 @@
private static int addModifier(
CommandSourceStack source, Entity entity, Holder<Attribute> attribute, ResourceLocation id, double amount, AttributeModifier.Operation operation
) throws CommandSyntaxException {
- AttributeInstance attributeInstance = getAttributeInstance(entity, attribute);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ AttributeInstance attributeInstance = getAttributeInstance(nmsEntity, attribute); // Folia - region threading
AttributeModifier attributeModifier = new AttributeModifier(id, amount, operation);
if (attributeInstance.hasModifier(id)) {
- throw ERROR_MODIFIER_ALREADY_PRESENT.create(entity.getName(), getAttributeDescription(attribute), id);
+ throw ERROR_MODIFIER_ALREADY_PRESENT.create(nmsEntity.getName(), getAttributeDescription(attribute), id); // Folia - region threading
} else {
attributeInstance.addPermanentModifier(attributeModifier);
source.sendSuccess(
() -> Component.translatable(
- "commands.attribute.modifier.add.success", Component.translationArg(id), getAttributeDescription(attribute), entity.getName()
+ "commands.attribute.modifier.add.success", Component.translationArg(id), getAttributeDescription(attribute), nmsEntity.getName() // Folia - region threading
),
false
);
- return 1;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static int removeModifier(CommandSourceStack source, Entity entity, Holder<Attribute> attribute, ResourceLocation id) throws CommandSyntaxException {
- AttributeInstance attributeInstance = getAttributeInstance(entity, attribute);
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ AttributeInstance attributeInstance = getAttributeInstance(nmsEntity, attribute); // Folia - region threading
if (attributeInstance.removeModifier(id)) {
source.sendSuccess(
() -> Component.translatable(
- "commands.attribute.modifier.remove.success", Component.translationArg(id), getAttributeDescription(attribute), entity.getName()
+ "commands.attribute.modifier.remove.success", Component.translationArg(id), getAttributeDescription(attribute), nmsEntity.getName() // Folia - region threading
),
false
);
- return 1;
+ return; // Folia - region threading
} else {
- throw ERROR_NO_SUCH_MODIFIER.create(entity.getName(), getAttributeDescription(attribute), id);
+ throw ERROR_NO_SUCH_MODIFIER.create(nmsEntity.getName(), getAttributeDescription(attribute), id); // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
private static Component getAttributeDescription(Holder<Attribute> attribute) {

View File

@ -1,20 +0,0 @@
--- a/net/minecraft/server/commands/ClearInventoryCommands.java
+++ b/net/minecraft/server/commands/ClearInventoryCommands.java
@@ -65,9 +_,14 @@
int i = 0;
for (ServerPlayer serverPlayer : targetPlayers) {
- i += serverPlayer.getInventory().clearOrCountMatchingItems(itemPredicate, maxCount, serverPlayer.inventoryMenu.getCraftSlots());
- serverPlayer.containerMenu.broadcastChanges();
- serverPlayer.inventoryMenu.slotsChanged(serverPlayer.getInventory());
+ // Folia start - region threading
+ ++i;
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ player.getInventory().clearOrCountMatchingItems(itemPredicate, maxCount, player.inventoryMenu.getCraftSlots());
+ player.containerMenu.broadcastChanges();
+ player.inventoryMenu.slotsChanged(player.getInventory());
+ }, null, 1L);
+ // Folia end - region threading
}
if (i == 0) {

View File

@ -1,35 +0,0 @@
--- a/net/minecraft/server/commands/DamageCommand.java
+++ b/net/minecraft/server/commands/DamageCommand.java
@@ -102,12 +_,29 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int damage(CommandSourceStack source, Entity target, float amount, DamageSource damageType) throws CommandSyntaxException {
- if (target.hurtServer(source.getLevel(), damageType, amount)) {
- source.sendSuccess(() -> Component.translatable("commands.damage.success", amount, target.getDisplayName()), true);
- return 1;
+ // Folia start - region threading
+ target.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ try {
+ // Folia end - region threading
+ if (nmsEntity.hurtServer(source.getLevel(), damageType, amount)) { // Folia - region threading
+ source.sendSuccess(() -> Component.translatable("commands.damage.success", amount, nmsEntity.getDisplayName()), true); // Folia - region threading
+ return; // Folia - region threading
} else {
throw ERROR_INVULNERABLE.create();
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }, null, 1L);
+ return 0;
+ // Folia end - region threading
}
}

View File

@ -1,18 +0,0 @@
--- a/net/minecraft/server/commands/DefaultGameModeCommands.java
+++ b/net/minecraft/server/commands/DefaultGameModeCommands.java
@@ -28,12 +_,14 @@
GameType forcedGameType = server.getForcedGameType();
if (forcedGameType != null) {
for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { // Folia - region threading
// Paper start - Expand PlayerGameModeChangeEvent
- org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty());
+ org.bukkit.event.player.PlayerGameModeChangeEvent event = player.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); // Folia - region threading
if (event != null && event.isCancelled()) {
commandSource.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false);
}
// Paper end - Expand PlayerGameModeChangeEvent
+ }, null, 1L); // Folia - region threading
i++;
}
}

View File

@ -1,44 +0,0 @@
--- a/net/minecraft/server/commands/EffectCommands.java
+++ b/net/minecraft/server/commands/EffectCommands.java
@@ -180,7 +_,12 @@
for (Entity entity : targets) {
if (entity instanceof LivingEntity) {
MobEffectInstance mobEffectInstance = new MobEffectInstance(effect, i1, amplifier, false, showParticles);
- if (((LivingEntity)entity).addEffect(mobEffectInstance, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
+ ((LivingEntity)nmsEntity).addEffect(mobEffectInstance, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND);
+ }, null, 1L);
+ // Folia end - region threading
+ if (true) { // CraftBukkit // Folia - region threading
i++;
}
}
@@ -210,7 +_,12 @@
int i = 0;
for (Entity entity : targets) {
- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ if (entity instanceof LivingEntity && true) { // CraftBukkit // Folia - region threading
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
+ ((LivingEntity)nmsEntity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND);
+ }, null, 1L);
+ // Folia end - region threading
i++;
}
}
@@ -235,7 +_,12 @@
int i = 0;
for (Entity entity : targets) {
- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ if (entity instanceof LivingEntity && true) { // CraftBukkit // Folia - region threading
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
+ ((LivingEntity)nmsEntity).removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND);
+ }, null, 1L);
+ // Folia end - region threading
i++;
}
}

View File

@ -1,100 +0,0 @@
--- a/net/minecraft/server/commands/EnchantCommand.java
+++ b/net/minecraft/server/commands/EnchantCommand.java
@@ -68,51 +_,78 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int enchant(CommandSourceStack source, Collection<? extends Entity> targets, Holder<Enchantment> enchantment, int level) throws CommandSyntaxException {
Enchantment enchantment1 = enchantment.value();
if (level > enchantment1.getMaxLevel()) {
throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment1.getMaxLevel());
} else {
- int i = 0;
+ final java.util.concurrent.atomic.AtomicInteger changed = new java.util.concurrent.atomic.AtomicInteger(0); // Folia - region threading
+ final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(targets.size()); // Folia - region threading
+ final java.util.concurrent.atomic.AtomicReference<Component> possibleSingleDisplayName = new java.util.concurrent.atomic.AtomicReference<>(); // Folia - region threading
for (Entity entity : targets) {
if (entity instanceof LivingEntity) {
- LivingEntity livingEntity = (LivingEntity)entity;
- ItemStack mainHandItem = livingEntity.getMainHandItem();
- if (!mainHandItem.isEmpty()) {
- if (enchantment1.canEnchant(mainHandItem)
- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment)) {
- mainHandItem.enchant(enchantment, level);
- i++;
- } else if (targets.size() == 1) {
- throw ERROR_INCOMPATIBLE.create(mainHandItem.getHoverName().getString());
+ // Folia start - region threading
+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
+ try {
+ LivingEntity livingEntity = (LivingEntity)nmsEntity;
+ ItemStack mainHandItem = livingEntity.getMainHandItem();
+ if (!mainHandItem.isEmpty()) {
+ if (enchantment1.canEnchant(mainHandItem)
+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment)) {
+ mainHandItem.enchant(enchantment, level);
+ possibleSingleDisplayName.set(livingEntity.getDisplayName());
+ changed.incrementAndGet();
+ } else if (targets.size() == 1) {
+ throw ERROR_INCOMPATIBLE.create(mainHandItem.getHoverName().getString());
+ }
+ } else if (targets.size() == 1) {
+ throw ERROR_NO_ITEM.create(livingEntity.getName().getString());
+ }
+ } catch (final CommandSyntaxException exception) {
+ sendMessage(source, exception);
+ return; // don't send feedback twice
}
- } else if (targets.size() == 1) {
- throw ERROR_NO_ITEM.create(livingEntity.getName().getString());
- }
+ sendFeedback(source, enchantment, level, possibleSingleDisplayName, count, changed);
+ }, ignored -> sendFeedback(source, enchantment, level, possibleSingleDisplayName, count, changed), 1L);
} else if (targets.size() == 1) {
throw ERROR_NOT_LIVING_ENTITY.create(entity.getName().getString());
+ } else {
+ sendFeedback(source, enchantment, level, possibleSingleDisplayName, count, changed);
+ // Folia end - region threading
}
}
+ return targets.size(); // Folia - region threading
+ }
+ }
+ // Folia start - region threading
+ private static void sendFeedback(final CommandSourceStack source, final Holder<Enchantment> enchantment, final int level, final java.util.concurrent.atomic.AtomicReference<Component> possibleSingleDisplayName, final java.util.concurrent.atomic.AtomicInteger count, final java.util.concurrent.atomic.AtomicInteger changed) {
+ if (count.decrementAndGet() == 0) {
+ final int i = changed.get();
if (i == 0) {
- throw ERROR_NOTHING_HAPPENED.create();
+ sendMessage(source, ERROR_NOTHING_HAPPENED.create());
} else {
- if (targets.size() == 1) {
+ if (i == 1) {
source.sendSuccess(
() -> Component.translatable(
- "commands.enchant.success.single", Enchantment.getFullname(enchantment, level), targets.iterator().next().getDisplayName()
+ "commands.enchant.success.single", Enchantment.getFullname(enchantment, level), possibleSingleDisplayName.get()
),
true
);
} else {
source.sendSuccess(
- () -> Component.translatable("commands.enchant.success.multiple", Enchantment.getFullname(enchantment, level), targets.size()), true
+ () -> Component.translatable("commands.enchant.success.multiple", Enchantment.getFullname(enchantment, level), i), true
);
}
-
- return i;
}
}
}
+ // Folia end - region threading
}

View File

@ -1,39 +0,0 @@
--- a/net/minecraft/server/commands/ExperienceCommand.java
+++ b/net/minecraft/server/commands/ExperienceCommand.java
@@ -131,14 +_,18 @@
}
private static int queryExperience(CommandSourceStack source, ServerPlayer player, ExperienceCommand.Type type) {
- int i = type.query.applyAsInt(player);
- source.sendSuccess(() -> Component.translatable("commands.experience.query." + type.name, player.getDisplayName(), i), false);
- return i;
+ player.getBukkitEntity().taskScheduler.schedule((ServerPlayer serverPlayer) -> { // Folia - region threading
+ int i = type.query.applyAsInt(serverPlayer); // Folia - region threading
+ source.sendSuccess(() -> Component.translatable("commands.experience.query." + type.name, serverPlayer.getDisplayName(), i), false); // Folia - region threading
+ }, null, 1L); // Folia - region threading
+ return 0; // Folia - region threading
}
private static int addExperience(CommandSourceStack source, Collection<? extends ServerPlayer> targets, int amount, ExperienceCommand.Type type) {
for (ServerPlayer serverPlayer : targets) {
- type.add.accept(serverPlayer, amount);
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { // Folia - region threading
+ type.add.accept(player, amount);
+ }, null, 1L); // Folia - region threading
}
if (targets.size() == 1) {
@@ -157,9 +_,11 @@
int i = 0;
for (ServerPlayer serverPlayer : targets) {
- if (type.set.test(serverPlayer, amount)) {
- i++;
+ i++; serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { // Folia - region threading
+ if (type.set.test(player, amount)) { // Folia - region threading
+ //i++; // Folia - region threading
}
+ }, null, 1L); // Folia - region threading
}
if (i == 0) {

View File

@ -1,49 +0,0 @@
--- a/net/minecraft/server/commands/FillBiomeCommand.java
+++ b/net/minecraft/server/commands/FillBiomeCommand.java
@@ -107,6 +_,16 @@
return fill(level, from, to, biome, biome1 -> true, message -> {});
}
+ // Folia start - region threading
+ private static void sendMessage(Consumer<Supplier<Component>> src, Supplier<Either<Integer, CommandSyntaxException>> supplier) {
+ Either<Integer, CommandSyntaxException> either = supplier.get();
+ CommandSyntaxException ex = either == null ? null : either.right().orElse(null);
+ if (ex != null) {
+ src.accept(() -> (Component)ex.getRawMessage());
+ }
+ }
+ // Folia end - region threading
+
public static Either<Integer, CommandSyntaxException> fill(
ServerLevel level, BlockPos from, BlockPos to, Holder<Biome> biome, Predicate<Holder<Biome>> filter, Consumer<Supplier<Component>> messageOutput
) {
@@ -118,6 +_,17 @@
if (i > _int) {
return Either.right(ERROR_VOLUME_TOO_LARGE.create(_int, i));
} else {
+ // Folia start - region threading
+ int buffer = 0; // no buffer, we do not touch neighbours
+ level.moonrise$loadChunksAsync(
+ (boundingBox.minX() - buffer) >> 4,
+ (boundingBox.maxX() + buffer) >> 4,
+ (boundingBox.minZ() - buffer) >> 4,
+ (boundingBox.maxZ() + buffer) >> 4,
+ net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ sendMessage(messageOutput, () -> {
List<ChunkAccess> list = new ArrayList<>();
for (int sectionPosMinZ = SectionPos.blockToSectionCoord(boundingBox.minZ());
@@ -158,6 +_,11 @@
)
);
return Either.left(mutableInt.getValue());
+ // Folia start - region threading
+ }); // sendMessage
+ }); // loadChunksASync
+ return Either.left(Integer.valueOf(0));
+ // Folia end - region threading
}
}

View File

@ -1,49 +0,0 @@
--- a/net/minecraft/server/commands/FillCommand.java
+++ b/net/minecraft/server/commands/FillCommand.java
@@ -151,6 +_,12 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int fillBlocks(
CommandSourceStack source, BoundingBox area, BlockInput newBlock, FillCommand.Mode mode, @Nullable Predicate<BlockInWorld> replacingPredicate
) throws CommandSyntaxException {
@@ -161,6 +_,18 @@
} else {
List<BlockPos> list = Lists.newArrayList();
ServerLevel level = source.getLevel();
+ // Folia start - region threading
+ int buffer = 32;
+ // physics may spill into neighbour chunks, so use a buffer
+ level.moonrise$loadChunksAsync(
+ (area.minX() - buffer) >> 4,
+ (area.maxX() + buffer) >> 4,
+ (area.minZ() - buffer) >> 4,
+ (area.maxZ() + buffer) >> 4,
+ net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ try { // Folia end - region threading
int i1 = 0;
for (BlockPos blockPos : BlockPos.betweenClosed(area.minX(), area.minY(), area.minZ(), area.maxX(), area.maxY(), area.maxZ())) {
@@ -187,8 +_,13 @@
} else {
int i2 = i1;
source.sendSuccess(() -> Component.translatable("commands.fill.success", i2), true);
- return i1;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }); return 0; // Folia end - region threading
}
}

View File

@ -1,93 +0,0 @@
--- a/net/minecraft/server/commands/ForceLoadCommand.java
+++ b/net/minecraft/server/commands/ForceLoadCommand.java
@@ -97,7 +_,17 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int queryForceLoad(CommandSourceStack source, ColumnPos pos) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
ChunkPos chunkPos = pos.toChunkPos();
ServerLevel level = source.getLevel();
ResourceKey<Level> resourceKey = level.dimension();
@@ -109,14 +_,22 @@
),
false
);
- return 1;
+ return; // Folia - region threading
} else {
throw ERROR_NOT_TICKING.create(chunkPos, resourceKey.location());
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int listForceLoad(CommandSourceStack source) {
ServerLevel level = source.getLevel();
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
ResourceKey<Level> resourceKey = level.dimension();
LongSet forcedChunks = level.getForcedChunks();
int size = forcedChunks.size();
@@ -134,20 +_,27 @@
} else {
source.sendFailure(Component.translatable("commands.forceload.added.none", Component.translationArg(resourceKey.location())));
}
+ }); // Folia - region threading
- return size;
+ return 1; // Folia - region threading
}
private static int removeAll(CommandSourceStack source) {
ServerLevel level = source.getLevel();
ResourceKey<Level> resourceKey = level.dimension();
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
LongSet forcedChunks = level.getForcedChunks();
forcedChunks.forEach(packedChunkPos -> level.setChunkForced(ChunkPos.getX(packedChunkPos), ChunkPos.getZ(packedChunkPos), false));
source.sendSuccess(() -> Component.translatable("commands.forceload.removed.all", Component.translationArg(resourceKey.location())), true);
+ }); // Folia - region threading
return 0;
}
private static int changeForceLoad(CommandSourceStack source, ColumnPos from, ColumnPos to, boolean add) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
int min = Math.min(from.x(), to.x());
int min1 = Math.min(from.z(), to.z());
int max = Math.max(from.x(), to.x());
@@ -207,11 +_,18 @@
);
}
- return i2x;
+ return; // Folia - region threading
}
}
} else {
throw BlockPosArgument.ERROR_OUT_OF_WORLD.create();
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
}

View File

@ -1,24 +0,0 @@
--- a/net/minecraft/server/commands/GameModeCommand.java
+++ b/net/minecraft/server/commands/GameModeCommand.java
@@ -54,15 +_,18 @@
int i = 0;
for (ServerPlayer serverPlayer : players) {
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer nmsEntity) -> { // Folia - region threading
// Paper start - Expand PlayerGameModeChangeEvent
- org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty());
+ org.bukkit.event.player.PlayerGameModeChangeEvent event = nmsEntity.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); // Folia - region threading
if (event != null && !event.isCancelled()) {
- logGamemodeChange(source.getSource(), serverPlayer, gameType);
- i++;
+ logGamemodeChange(source.getSource(), nmsEntity, gameType); // Folia - region threading
+ //i++; // Folia - region threading
} else if (event != null && event.cancelMessage() != null) {
source.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true);
// Paper end - Expand PlayerGameModeChangeEvent
}
+ }, null, 1L); // Folia - region threading
+ ++i; // Folia - region threading
}
return i;

View File

@ -1,47 +0,0 @@
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -65,32 +_,34 @@
int min = Math.min(maxStackSize, i1);
i1 -= min;
ItemStack itemStack1 = item.createItemStack(min, false);
- boolean flag = serverPlayer.getInventory().add(itemStack1);
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer nmsEntity) -> { // Folia - region threading
+ boolean flag = nmsEntity.getInventory().add(itemStack1); // Folia - region threading
if (flag && itemStack1.isEmpty()) {
- ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
+ ItemEntity itemEntity = nmsEntity.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event // Folia - region threading
if (itemEntity != null) {
itemEntity.makeFakeItem();
}
- serverPlayer.level()
+ nmsEntity.level() // Folia - region threading
.playSound(
null,
- serverPlayer.getX(),
- serverPlayer.getY(),
- serverPlayer.getZ(),
+ nmsEntity.getX(), // Folia - region threading
+ nmsEntity.getY(), // Folia - region threading
+ nmsEntity.getZ(), // Folia - region threading
SoundEvents.ITEM_PICKUP,
SoundSource.PLAYERS,
0.2F,
- ((serverPlayer.getRandom().nextFloat() - serverPlayer.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F
+ ((nmsEntity.getRandom().nextFloat() - nmsEntity.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F // Folia - region threading
);
- serverPlayer.containerMenu.broadcastChanges();
+ nmsEntity.containerMenu.broadcastChanges(); // Folia - region threading
} else {
- ItemEntity itemEntity = serverPlayer.drop(itemStack1, false);
+ ItemEntity itemEntity = nmsEntity.drop(itemStack1, false); // Folia - region threading
if (itemEntity != null) {
itemEntity.setNoPickUpDelay();
- itemEntity.setTarget(serverPlayer.getUUID());
+ itemEntity.setTarget(nmsEntity.getUUID()); // Folia - region threading
}
}
+ }, null, 1L); // Folia - region threading
}
}

View File

@ -1,13 +0,0 @@
--- a/net/minecraft/server/commands/KillCommand.java
+++ b/net/minecraft/server/commands/KillCommand.java
@@ -24,7 +_,9 @@
private static int kill(CommandSourceStack source, Collection<? extends Entity> targets) {
for (Entity entity : targets) {
- entity.kill(source.getLevel());
+ entity.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> { // Folia - region threading
+ nmsEntity.kill((net.minecraft.server.level.ServerLevel)nmsEntity.level()); // Folia - region threading
+ }, null, 1L); // Folia - region threading
}
if (targets.size() == 1) {

View File

@ -1,134 +0,0 @@
--- a/net/minecraft/server/commands/PlaceCommand.java
+++ b/net/minecraft/server/commands/PlaceCommand.java
@@ -233,36 +_,79 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
public static int placeFeature(CommandSourceStack source, Holder.Reference<ConfiguredFeature<?, ?>> feature, BlockPos pos) throws CommandSyntaxException {
ServerLevel level = source.getLevel();
ConfiguredFeature<?, ?> configuredFeature = feature.value();
ChunkPos chunkPos = new ChunkPos(pos);
checkLoaded(level, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1), new ChunkPos(chunkPos.x + 1, chunkPos.z + 1));
+ // Folia start - region threading
+ level.moonrise$loadChunksAsync(
+ pos, 16, net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ try {
+ // Folia end - region threading
if (!configuredFeature.place(level, level.getChunkSource().getGenerator(), level.getRandom(), pos)) {
throw ERROR_FEATURE_FAILED.create();
} else {
String string = feature.key().location().toString();
source.sendSuccess(() -> Component.translatable("commands.place.feature.success", string, pos.getX(), pos.getY(), pos.getZ()), true);
- return 1;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }
+ );
+ return 1;
+ // Folia end - region threading
}
public static int placeJigsaw(CommandSourceStack source, Holder<StructureTemplatePool> templatePool, ResourceLocation target, int maxDepth, BlockPos pos) throws CommandSyntaxException {
ServerLevel level = source.getLevel();
ChunkPos chunkPos = new ChunkPos(pos);
checkLoaded(level, chunkPos, chunkPos);
+ // Folia start - region threading
+ level.moonrise$loadChunksAsync(
+ pos, 16, net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ try {
+ // Folia end - region threading
if (!JigsawPlacement.generateJigsaw(level, templatePool, target, maxDepth, pos, false)) {
throw ERROR_JIGSAW_FAILED.create();
} else {
source.sendSuccess(() -> Component.translatable("commands.place.jigsaw.success", pos.getX(), pos.getY(), pos.getZ()), true);
- return 1;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }
+ );
+ return 1;
+ // Folia end - region threading
}
public static int placeStructure(CommandSourceStack source, Holder.Reference<Structure> structure, BlockPos pos) throws CommandSyntaxException {
ServerLevel level = source.getLevel();
Structure structure1 = structure.value();
ChunkGenerator generator = level.getChunkSource().getGenerator();
+ // Folia start - region threading
+ level.moonrise$loadChunksAsync(
+ pos, 16, net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ try {
+ // Folia end - region threading
StructureStart structureStart = structure1.generate(
structure,
level.dimension(),
@@ -305,14 +_,29 @@
);
String string = structure.key().location().toString();
source.sendSuccess(() -> Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()), true);
- return 1;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }
+ );
+ return 1;
+ // Folia end - region threading
}
public static int placeTemplate(
CommandSourceStack source, ResourceLocation template, BlockPos pos, Rotation rotation, Mirror mirror, float integrity, int seed
) throws CommandSyntaxException {
ServerLevel level = source.getLevel();
+ // Folia start - region threading
+ level.moonrise$loadChunksAsync(
+ pos, 16, net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
+ (chunks) -> {
+ try {
+ // Folia end - region threading
StructureTemplateManager structureManager = level.getStructureManager();
Optional<StructureTemplate> optional;
@@ -340,9 +_,17 @@
() -> Component.translatable("commands.place.template.success", Component.translationArg(template), pos.getX(), pos.getY(), pos.getZ()),
true
);
- return 1;
+ return; // Folia - region threading
}
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ }
+ );
+ return 1;
+ // Folia end - region threading
}
private static void checkLoaded(ServerLevel level, ChunkPos start, ChunkPos end) throws CommandSyntaxException {

View File

@ -1,30 +0,0 @@
--- a/net/minecraft/server/commands/RecipeCommand.java
+++ b/net/minecraft/server/commands/RecipeCommand.java
@@ -81,7 +_,12 @@
int i = 0;
for (ServerPlayer serverPlayer : targets) {
- i += serverPlayer.awardRecipes(recipes);
+ // Folia start - region threading
+ ++i;
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ player.awardRecipes(recipes);
+ }, null, 1L);
+ // Folia end - region threading
}
if (i == 0) {
@@ -103,7 +_,12 @@
int i = 0;
for (ServerPlayer serverPlayer : targets) {
- i += serverPlayer.resetRecipes(recipes);
+ // Folia start - region threading
+ ++i;
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ player.resetRecipes(recipes);
+ }, null, 1L);
+ // Folia end - region threading
}
if (i == 0) {

View File

@ -1,42 +0,0 @@
--- a/net/minecraft/server/commands/SetBlockCommand.java
+++ b/net/minecraft/server/commands/SetBlockCommand.java
@@ -80,10 +_,21 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int setBlock(
CommandSourceStack source, BlockPos pos, BlockInput state, SetBlockCommand.Mode mode, @Nullable Predicate<BlockInWorld> predicate
) throws CommandSyntaxException {
ServerLevel level = source.getLevel();
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ level, pos.getX() >> 4, pos.getZ() >> 4, () -> {
+ try {
+ // Folia end - region threading
if (predicate != null && !predicate.test(new BlockInWorld(level, pos, true))) {
throw ERROR_FAILED.create();
} else {
@@ -102,9 +_,16 @@
} else {
level.blockUpdated(pos, state.getState().getBlock());
source.sendSuccess(() -> Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()), true);
- return 1;
+ return; // Folia - region threading
}
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
public interface Filter {

View File

@ -1,15 +0,0 @@
--- a/net/minecraft/server/commands/SetSpawnCommand.java
+++ b/net/minecraft/server/commands/SetSpawnCommand.java
@@ -69,7 +_,11 @@
final Collection<ServerPlayer> actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent
for (ServerPlayer serverPlayer : targets) {
// Paper start - Add PlayerSetSpawnEvent
- if (serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) {
+ // Folia start - region threading
+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+ player.setRespawnPosition(resourceKey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND);
+ }, null, 1L);
+ if (true) { // Folia end - region threading
actualTargets.add(serverPlayer);
}
// Paper end - Add PlayerSetSpawnEvent

View File

@ -1,26 +0,0 @@
--- a/net/minecraft/server/commands/SummonCommand.java
+++ b/net/minecraft/server/commands/SummonCommand.java
@@ -88,12 +_,18 @@
if (entity == null) {
throw ERROR_FAILED.create();
} else {
- if (randomizeProperties && entity instanceof Mob) {
- ((Mob)entity)
- .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, null);
- }
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ level, entity.chunkPosition().x, entity.chunkPosition().z, () -> {
+ if (randomizeProperties && entity instanceof Mob) {
+ ((Mob)entity)
+ .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, null);
+ }
+ level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND);
+ });
+ // Folia end - region threading
- if (!level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND"
+ if (false) { // CraftBukkit - pass a spawn reason of "COMMAND" // Folia - region threading
throw ERROR_DUPLICATE_UUID.create();
} else {
return entity;

View File

@ -1,47 +0,0 @@
--- a/net/minecraft/server/commands/TeleportCommand.java
+++ b/net/minecraft/server/commands/TeleportCommand.java
@@ -154,18 +_,7 @@
private static int teleportToEntity(CommandSourceStack source, Collection<? extends Entity> targets, Entity destination) throws CommandSyntaxException {
for (Entity entity : targets) {
- performTeleport(
- source,
- entity,
- (ServerLevel)destination.level(),
- destination.getX(),
- destination.getY(),
- destination.getZ(),
- EnumSet.noneOf(Relative.class),
- destination.getYRot(),
- destination.getXRot(),
- null
- );
+ io.papermc.paper.threadedregions.TeleportUtils.teleport(entity, false, destination, Float.valueOf(destination.getYRot()), Float.valueOf(destination.getXRot()), Entity.TELEPORT_FLAG_LOAD_CHUNK, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND, null); // Folia - region threading
}
if (targets.size() == 1) {
@@ -290,6 +_,24 @@
float f1 = relatives.contains(Relative.X_ROT) ? xRot - target.getXRot() : xRot;
float f2 = Mth.wrapDegrees(f);
float f3 = Mth.wrapDegrees(f1);
+ // Folia start - region threading
+ if (true) {
+ ServerLevel worldFinal = level;
+ Vec3 posFinal = new Vec3(x, y, z);
+ Float yawFinal = Float.valueOf(f);
+ Float pitchFinal = Float.valueOf(f1);
+ target.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
+ nmsEntity.unRide();
+ nmsEntity.teleportAsync(
+ worldFinal, posFinal, yawFinal, pitchFinal, Vec3.ZERO,
+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND,
+ Entity.TELEPORT_FLAG_LOAD_CHUNK,
+ null
+ );
+ }, null, 1L);
+ return;
+ }
+ // Folia end - region threading
// CraftBukkit start - Teleport event
boolean result;
if (target instanceof final net.minecraft.server.level.ServerPlayer player) {

View File

@ -1,33 +0,0 @@
--- a/net/minecraft/server/commands/TimeCommand.java
+++ b/net/minecraft/server/commands/TimeCommand.java
@@ -56,6 +_,7 @@
}
public static int setTime(CommandSourceStack source, int time) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
// serverLevel.setDayTime(time);
// CraftBukkit start
@@ -69,10 +_,12 @@
source.getServer().forceTimeSynchronization();
source.sendSuccess(() -> Component.translatable("commands.time.set", time), true);
- return getDayTime(source.getLevel());
+ }); // Folia - region threading
+ return 0; // Folia - region threading
}
public static int addTime(CommandSourceStack source, int amount) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
// CraftBukkit start
org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(serverLevel.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, amount);
@@ -86,6 +_,7 @@
source.getServer().forceTimeSynchronization();
int dayTime = getDayTime(source.getLevel());
source.sendSuccess(() -> Component.translatable("commands.time.set", dayTime), true);
- return dayTime;
+ }); // Folia - region threading
+ return 0; // Folia - region threading
}
}

View File

@ -1,29 +0,0 @@
--- a/net/minecraft/server/commands/WeatherCommand.java
+++ b/net/minecraft/server/commands/WeatherCommand.java
@@ -48,20 +_,26 @@
}
private static int setClear(CommandSourceStack source, int time) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
source.getLevel().setWeatherParameters(getDuration(source, time, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> Component.translatable("commands.weather.set.clear"), true);
+ }); // Folia - region threading
return time;
}
private static int setRain(CommandSourceStack source, int time) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> Component.translatable("commands.weather.set.rain"), true);
+ }); // Folia - region threading
return time;
}
private static int setThunder(CommandSourceStack source, int time) {
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading
source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world
source.sendSuccess(() -> Component.translatable("commands.weather.set.thunder"), true);
+ }); // Folia - region threading
return time;
}
}

View File

@ -1,169 +0,0 @@
--- a/net/minecraft/server/commands/WorldBorderCommand.java
+++ b/net/minecraft/server/commands/WorldBorderCommand.java
@@ -134,18 +_,39 @@
);
}
+ // Folia start - region threading
+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) {
+ src.sendFailure((Component)ex.getRawMessage());
+ }
+ // Folia end - region threading
+
private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldBorder.getDamageSafeZone() == distance) {
throw ERROR_SAME_DAMAGE_BUFFER.create();
} else {
worldBorder.setDamageSafeZone(distance);
source.sendSuccess(() -> Component.translatable("commands.worldborder.damage.buffer.success", String.format(Locale.ROOT, "%.2f", distance)), true);
- return (int)distance;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldBorder.getDamagePerBlock() == damagePerBlock) {
throw ERROR_SAME_DAMAGE_AMOUNT.create();
@@ -154,39 +_,79 @@
source.sendSuccess(
() -> Component.translatable("commands.worldborder.damage.amount.success", String.format(Locale.ROOT, "%.2f", damagePerBlock)), true
);
- return (int)damagePerBlock;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldBorder.getWarningTime() == time) {
throw ERROR_SAME_WARNING_TIME.create();
} else {
worldBorder.setWarningTime(time);
source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.time.success", time), true);
- return time;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldBorder.getWarningBlocks() == distance) {
throw ERROR_SAME_WARNING_DISTANCE.create();
} else {
worldBorder.setWarningBlocks(distance);
source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.distance.success", distance), true);
- return distance;
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int getSize(CommandSourceStack source) {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ // Folia end - region threading
double size = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
source.sendSuccess(() -> Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", size)), false);
- return Mth.floor(size + 0.5);
+ return; // Folia - region threading
+ // Folia start - region threading
+ });
+ return 1;
+ // Folia end - region threading
}
private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
if (worldBorder.getCenterX() == pos.x && worldBorder.getCenterZ() == pos.y) {
throw ERROR_SAME_CENTER.create();
@@ -198,13 +_,24 @@
),
true
);
- return 0;
+ return; // Folia - region threading
} else {
throw ERROR_TOO_FAR_OUT.create();
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
private static int setSize(CommandSourceStack source, double newSize, long time) throws CommandSyntaxException {
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+ try {
+ // Folia end - region threading
WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
double size = worldBorder.getSize();
if (size == newSize) {
@@ -234,7 +_,14 @@
source.sendSuccess(() -> Component.translatable("commands.worldborder.set.immediate", String.format(Locale.ROOT, "%.1f", newSize)), true);
}
- return (int)(newSize - size);
+ return; // Folia - region threading
}
+ // Folia start - region threading
+ } catch (CommandSyntaxException ex) {
+ sendMessage(source, ex);
+ }
+ });
+ return 1;
+ // Folia end - region threading
}
}

View File

@ -1,39 +0,0 @@
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -425,7 +_,7 @@
@Override
public void tickConnection() {
super.tickConnection();
- this.handleConsoleInputs();
+ // Folia - region threading
}
@Override
@@ -732,7 +_,8 @@
public String runCommand(RconConsoleSource rconConsoleSource, String s) {
rconConsoleSource.prepareForCommand();
- this.executeBlocking(() -> {
+ final java.util.concurrent.atomic.AtomicReference<String> command = new java.util.concurrent.atomic.AtomicReference<>(s); // Folia start - region threading
+ Runnable sync = () -> { // Folia - region threading
CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
org.bukkit.event.server.RemoteServerCommandEvent event = new org.bukkit.event.server.RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
this.server.getPluginManager().callEvent(event);
@@ -741,7 +_,16 @@
}
ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
this.server.dispatchServerCommand(event.getSender(), serverCommand);
- });
+ }; // Folia start - region threading
+ java.util.concurrent.CompletableFuture
+ .runAsync(sync, io.papermc.paper.threadedregions.RegionizedServer.getInstance()::addTask)
+ .whenComplete((Void r, Throwable t) -> {
+ if (t != null) {
+ LOGGER.error("Error handling command for rcon: " + s, t);
+ }
+ })
+ .join();
+ // Folia end - region threading
return rconConsoleSource.getCommandResponse();
// CraftBukkit end
}

View File

@ -1,227 +0,0 @@
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -128,8 +_,8 @@
public final ChunkMap.DistanceManager distanceManager;
public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public
private final String storageName;
- private final PlayerMap playerMap = new PlayerMap();
- public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>();
+ //private final PlayerMap playerMap = new PlayerMap(); // Folia - region threading
+ //public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading
private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap();
// Paper - rewrite chunk system
public int serverViewDistance;
@@ -797,12 +_,12 @@
void updatePlayerStatus(ServerPlayer player, boolean track) {
boolean flag = this.skipPlayer(player);
- boolean flag1 = this.playerMap.ignoredOrUnknown(player);
+ //boolean flag1 = this.playerMap.ignoredOrUnknown(player); // Folia - region threading
if (track) {
- this.playerMap.addPlayer(player, flag);
+ //this.playerMap.addPlayer(player, flag); // Folia - region threading
this.updatePlayerPos(player);
if (!flag) {
- this.distanceManager.addPlayer(SectionPos.of(player), player);
+ //this.distanceManager.addPlayer(SectionPos.of(player), player); // Folia - region threading
((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$addPlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation
}
@@ -810,9 +_,9 @@
ca.spottedleaf.moonrise.common.PlatformHooks.get().addPlayerToDistanceMaps(this.level, player); // Paper - rewrite chunk system
} else {
SectionPos lastSectionPos = player.getLastSectionPos();
- this.playerMap.removePlayer(player);
- if (!flag1) {
- this.distanceManager.removePlayer(lastSectionPos, player);
+ //this.playerMap.removePlayer(player); // Folia - region threading
+ if (true) { // Folia - region threading
+ //this.distanceManager.removePlayer(lastSectionPos, player); // Folia - region threading
((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$removePlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation
}
@@ -830,27 +_,13 @@
SectionPos lastSectionPos = player.getLastSectionPos();
SectionPos sectionPos = SectionPos.of(player);
- boolean flag = this.playerMap.ignored(player);
+ //boolean flag = this.playerMap.ignored(player); // Folia - region threading
boolean flag1 = this.skipPlayer(player);
- boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong();
- if (flag2 || flag != flag1) {
+ //boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong(); // Folia - region threading
+ if (true) { // Folia - region threading
this.updatePlayerPos(player);
- ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, lastSectionPos, sectionPos, flag, flag1); // Paper - chunk tick iteration optimisation
- if (!flag) {
- this.distanceManager.removePlayer(lastSectionPos, player);
- }
-
- if (!flag1) {
- this.distanceManager.addPlayer(sectionPos, player);
- }
-
- if (!flag && flag1) {
- this.playerMap.ignorePlayer(player);
- }
-
- if (flag && !flag1) {
- this.playerMap.unIgnorePlayer(player);
- }
+ ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, lastSectionPos, sectionPos, false, flag1); // Paper - chunk tick iteration optimisation // Folia - region threading
+ // Folia - region threading
// Paper - rewrite chunk system
}
@@ -880,9 +_,9 @@
public void addEntity(Entity entity) {
org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
// Paper start - ignore and warn about illegal addEntity calls instead of crashing server
- if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
+ if (!entity.valid || entity.level() != this.level || entity.moonrise$getTrackedEntity() != null) { // Folia - region threading
LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
- + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
+ + ": " + entity + (entity.moonrise$getTrackedEntity() != null ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); // Folia - region threading
return;
}
// Paper end - ignore and warn about illegal addEntity calls instead of crashing server
@@ -893,22 +_,28 @@
i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
if (i != 0) {
int updateInterval = type.updateInterval();
- if (this.entityMap.containsKey(entity.getId())) {
+ if (entity.moonrise$getTrackedEntity() != null) { // Folia - region threading
throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!"));
} else {
ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, updateInterval, type.trackDeltas());
- this.entityMap.put(entity.getId(), trackedEntity);
+ //this.entityMap.put(entity.getId(), trackedEntity); // Folia - region threading
// Paper start - optimise entity tracker
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) {
throw new IllegalStateException("Entity is already tracked");
}
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(trackedEntity);
// Paper end - optimise entity tracker
- trackedEntity.updatePlayers(this.level.players());
+ trackedEntity.updatePlayers(this.level.getLocalPlayers()); // Folia - region threading
if (entity instanceof ServerPlayer serverPlayer) {
this.updatePlayerStatus(serverPlayer, true);
- for (ChunkMap.TrackedEntity trackedEntity1 : this.entityMap.values()) {
+ // Folia start - region threading
+ for (Entity possible : this.level.getCurrentWorldData().trackerEntities) {
+ ChunkMap.TrackedEntity trackedEntity1 = possible.moonrise$getTrackedEntity();
+ if (trackedEntity1 == null) {
+ continue;
+ }
+ // Folia end - region threading
if (trackedEntity1.entity != serverPlayer) {
trackedEntity1.updatePlayer(serverPlayer);
}
@@ -924,12 +_,19 @@
if (entity instanceof ServerPlayer serverPlayer) {
this.updatePlayerStatus(serverPlayer, false);
- for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
+ // Folia start - region threading
+ for (Entity possible : this.level.getCurrentWorldData().getLocalEntities()) {
+ ChunkMap.TrackedEntity trackedEntity = possible.moonrise$getTrackedEntity();
+ if (trackedEntity == null) {
+ continue;
+ }
+ // Folia end - region threading
trackedEntity.removePlayer(serverPlayer);
}
+ // Folia end - region threading
}
- ChunkMap.TrackedEntity trackedEntity1 = this.entityMap.remove(entity.getId());
+ ChunkMap.TrackedEntity trackedEntity1 = entity.moonrise$getTrackedEntity(); // Folia - region threading
if (trackedEntity1 != null) {
trackedEntity1.broadcastRemoved();
}
@@ -938,9 +_,11 @@
// Paper start - optimise entity tracker
private void newTrackerTick() {
+ final io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading
final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();;
+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = this.level.moonrise$getNearbyPlayers(); // Folia - region threading
- final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = worldData.trackerEntities; // Folia - region threading
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
final Entity entity = trackerEntitiesRaw[i];
@@ -948,7 +_,7 @@
if (tracker == null) {
continue;
}
- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers);
+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); // Folia - region threading
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers()
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
tracker.serverEntity.sendChanges();
@@ -966,44 +_,18 @@
// Paper end - optimise entity tracker
// Paper - rewrite chunk system
- List<ServerPlayer> list = Lists.newArrayList();
- List<ServerPlayer> list1 = this.level.players();
-
- for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
- SectionPos sectionPos = trackedEntity.lastSectionPos;
- SectionPos sectionPos1 = SectionPos.of(trackedEntity.entity);
- boolean flag = !Objects.equals(sectionPos, sectionPos1);
- if (flag) {
- trackedEntity.updatePlayers(list1);
- Entity entity = trackedEntity.entity;
- if (entity instanceof ServerPlayer) {
- list.add((ServerPlayer)entity);
- }
-
- trackedEntity.lastSectionPos = sectionPos1;
- }
-
- if (flag || this.distanceManager.inEntityTickingRange(sectionPos1.chunk().toLong())) {
- trackedEntity.serverEntity.sendChanges();
- }
- }
-
- if (!list.isEmpty()) {
- for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
- trackedEntity.updatePlayers(list);
- }
- }
+ // Folia - region threading
}
public void broadcast(Entity entity, Packet<?> packet) {
- ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
+ ChunkMap.TrackedEntity trackedEntity = entity.moonrise$getTrackedEntity(); // Folia - region threading
if (trackedEntity != null) {
trackedEntity.broadcast(packet);
}
}
protected void broadcastAndSend(Entity entity, Packet<?> packet) {
- ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
+ ChunkMap.TrackedEntity trackedEntity = entity.moonrise$getTrackedEntity(); // Folia - region threading
if (trackedEntity != null) {
trackedEntity.broadcastAndSend(packet);
}
@@ -1231,8 +_,13 @@
}
flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
// Paper end - Configurable entity tracking range by Y
+ // Folia start - region threading
+ if (flag && (this.entity instanceof ServerPlayer thisEntity) && thisEntity.broadcastedDeath) {
+ flag = false;
+ }
+ // Folia end - region threading
// CraftBukkit start - respect vanish API
- if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
+ if (flag && (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) || !player.getBukkitEntity().canSee(this.entity.getBukkitEntity()))) { // Paper - only consider hits // Folia - region threading
flag = false;
}
// CraftBukkit end

View File

@ -1,53 +0,0 @@
--- a/net/minecraft/server/level/DistanceManager.java
+++ b/net/minecraft/server/level/DistanceManager.java
@@ -57,16 +_,16 @@
}
// Paper end - rewrite chunk system
// Paper start - chunk tick iteration optimisation
- private final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<ServerPlayer> spawnChunkTracker = new ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<>();
+ // Folia - move to regionized world data
@Override
public final void moonrise$addPlayer(final ServerPlayer player, final SectionPos pos) {
- this.spawnChunkTracker.add(player, pos.x(), pos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
+ this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.add(player, pos.x(), pos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Folia - region threading
}
@Override
public final void moonrise$removePlayer(final ServerPlayer player, final SectionPos pos) {
- this.spawnChunkTracker.remove(player);
+ this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.remove(player); // Folia - region threading
}
@Override
@@ -74,9 +_,9 @@
final SectionPos oldPos, final SectionPos newPos,
final boolean oldIgnore, final boolean newIgnore) {
if (newIgnore) {
- this.spawnChunkTracker.remove(player);
+ this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.remove(player); // Folia - region threading
} else {
- this.spawnChunkTracker.addOrUpdate(player, newPos.x(), newPos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
+ this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.addOrUpdate(player, newPos.x(), newPos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Folia - region threading
}
}
// Paper end - chunk tick iteration optimisation
@@ -208,15 +_,15 @@
}
public int getNaturalSpawnChunkCount() {
- return this.spawnChunkTracker.getTotalPositions(); // Paper - chunk tick iteration optimisation
+ return this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.getTotalPositions(); // Paper - chunk tick iteration optimisation // Folia - region threading
}
public boolean hasPlayersNearby(long chunkPos) {
- return this.spawnChunkTracker.hasObjectsNear(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)); // Paper - chunk tick iteration optimisation
+ return this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.hasObjectsNear(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)); // Paper - chunk tick iteration optimisation // Folia - region threading
}
public LongIterator getSpawnCandidateChunks() {
- return this.spawnChunkTracker.getPositions().iterator(); // Paper - chunk tick iteration optimisation
+ return this.moonrise$getChunkMap().level.getCurrentWorldData().spawnChunkTracker.getPositions().iterator(); // Paper - chunk tick iteration optimisation // Folia - region threading
}
public String getDebugStatus() {

View File

@ -1,276 +0,0 @@
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -61,18 +_,14 @@
public final ServerChunkCache.MainThreadExecutor mainThreadProcessor;
public final ChunkMap chunkMap;
private final DimensionDataStorage dataStorage;
- private long lastInhabitedUpdate;
+ //private long lastInhabitedUpdate; // Folia - region threading
public boolean spawnEnemies = true;
public boolean spawnFriendlies = true;
private static final int CACHE_SIZE = 4;
private final long[] lastChunkPos = new long[4];
private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4];
private final ChunkAccess[] lastChunk = new ChunkAccess[4];
- private final List<LevelChunk> tickingChunks = new ArrayList<>();
- private final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceOpenHashSet<>();
- @Nullable
- @VisibleForDebug
- private NaturalSpawner.SpawnState lastSpawnState;
+ // Folia - moved to regionised world data
// Paper start
private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
public int getFullChunksCount() {
@@ -98,6 +_,11 @@
}
private ChunkAccess syncLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus) {
+ // Folia start - region threading
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Cannot asynchronously load chunks");
+ }
+ // Folia end - region threading
final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
final CompletableFuture<ChunkAccess> completable = new CompletableFuture<>();
chunkTaskScheduler.scheduleChunkLoad(
@@ -154,9 +_,7 @@
// Paper start - chunk tick iteration optimisations
private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom shuffleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(0L);
private boolean isChunkNearPlayer(final ChunkMap chunkMap, final ChunkPos chunkPos, final LevelChunk levelChunk) {
- final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData = ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder())
- .moonrise$getRealChunkHolder().holderData;
- final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = chunkData.nearbyPlayers;
+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = this.level.moonrise$getNearbyPlayers().getChunk(chunkPos); // Folia - region threading
if (nearbyPlayers == null) {
return false;
}
@@ -355,6 +_,7 @@
}
public CompletableFuture<ChunkResult<ChunkAccess>> getChunkFuture(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
boolean flag = Thread.currentThread() == this.mainThread;
CompletableFuture<ChunkResult<ChunkAccess>> chunkFutureMainThread;
if (flag) {
@@ -502,14 +_,15 @@
}
private void tickChunks() {
- long gameTime = this.level.getGameTime();
- long l = gameTime - this.lastInhabitedUpdate;
- this.lastInhabitedUpdate = gameTime;
+ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
+ //long gameTime = this.level.getGameTime(); // Folia - region threading
+ long l = 1L; // Folia - region threading
+ //this.lastInhabitedUpdate = gameTime; // Folia - region threading
if (!this.level.isDebug()) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("pollingChunks");
if (this.level.tickRateManager().runsNormally()) {
- List<LevelChunk> list = this.tickingChunks;
+ List<LevelChunk> list = regionizedWorldData.temporaryChunkTickList; // Folia - region threading
try {
profilerFiller.push("filteringTickingChunks");
@@ -532,23 +_,24 @@
}
private void broadcastChangedChunks(ProfilerFiller profiler) {
+ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
profiler.push("broadcast");
- for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) {
+ for (ChunkHolder chunkHolder : regionizedWorldData.chunkHoldersToBroadcast) { // Folia - region threading - note: do not need to thread check, as getChunkToSend is only non-null when the chunkholder is loaded
LevelChunk tickingChunk = chunkHolder.getChunkToSend(); // Paper - rewrite chunk system
if (tickingChunk != null) {
chunkHolder.broadcastChanges(tickingChunk);
}
}
- this.chunkHoldersToBroadcast.clear();
+ regionizedWorldData.chunkHoldersToBroadcast.clear(); // Folia - region threading
profiler.pop();
}
private void collectTickingChunks(List<LevelChunk> output) {
// Paper start - chunk tick iteration optimisation
final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> tickingChunks =
- ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
+ this.level.getCurrentWorldData().getEntityTickingChunks(); // Folia - region threading
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
@@ -569,13 +_,14 @@
}
private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
+ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
profiler.popPush("naturalSpawnCount");
int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
// Paper start - Optional per player mob spawns
NaturalSpawner.SpawnState spawnState;
if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
// re-set mob counts
- for (ServerPlayer player : this.level.players) {
+ for (ServerPlayer player : this.level.getLocalPlayers()) { // Folia - region threading
// Paper start - per player mob spawning backoff
for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
player.mobCounts[ii] = 0;
@@ -588,26 +_,26 @@
}
// Paper end - per player mob spawning backoff
}
- spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+ spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
} else {
- spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
}
// Paper end - Optional per player mob spawns
- this.lastSpawnState = spawnState;
+ regionizedWorldData.lastSpawnState = spawnState; // Folia - region threading
profiler.popPush("spawnAndTick");
- boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+ boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.getLocalPlayers().isEmpty(); // CraftBukkit // Folia - region threading
int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
List<MobCategory> filteredSpawningCategories;
if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
// Paper start - PlayerNaturallySpawnCreaturesEvent
- for (ServerPlayer entityPlayer : this.level.players()) {
+ for (ServerPlayer entityPlayer : this.level.getLocalPlayers()) { // Folia - region threading
int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
chunkRange = Math.min(chunkRange, 8);
entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
entityPlayer.playerNaturallySpawnedEvent.callEvent();
}
// Paper end - PlayerNaturallySpawnCreaturesEvent
- boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+ boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getRedstoneGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit // Folia - region threading
filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit
} else {
filteredSpawningCategories = List.of();
@@ -673,18 +_,23 @@
int sectionPosZ = SectionPos.blockToSectionCoord(pos.getZ());
ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(ChunkPos.asLong(sectionPosX, sectionPosZ));
if (visibleChunkIfPresent != null && visibleChunkIfPresent.blockChanged(pos)) {
- this.chunkHoldersToBroadcast.add(visibleChunkIfPresent);
+ this.level.getCurrentWorldData().chunkHoldersToBroadcast.add(visibleChunkIfPresent); // Folia - region threading
}
}
@Override
public void onLightUpdate(LightLayer type, SectionPos pos) {
- this.mainThreadProcessor.execute(() -> {
+ Runnable run = () -> { // Folia - region threading
ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(pos.chunk().toLong());
if (visibleChunkIfPresent != null && visibleChunkIfPresent.sectionLightChanged(type, pos.y())) {
- this.chunkHoldersToBroadcast.add(visibleChunkIfPresent);
+ this.level.getCurrentWorldData().chunkHoldersToBroadcast.add(visibleChunkIfPresent); // Folia - region threading
}
- });
+ }; // Folia - region threading
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(
+ this.level, pos.getX(), pos.getZ(), run
+ );
+ // Folia end - region threading
}
public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
@@ -766,7 +_,8 @@
@Nullable
@VisibleForDebug
public NaturalSpawner.SpawnState getLastSpawnState() {
- return this.lastSpawnState;
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading
+ return worldData == null ? null : worldData.lastSpawnState; // Folia - region threading
}
public void removeTicketsOnClosing() {
@@ -775,7 +_,7 @@
public void onChunkReadyToSend(ChunkHolder chunkHolder) {
if (chunkHolder.hasChangesToBroadcast()) {
- this.chunkHoldersToBroadcast.add(chunkHolder);
+ throw new UnsupportedOperationException(); // Folia - region threading
}
}
@@ -812,20 +_,76 @@
return ServerChunkCache.this.mainThread;
}
+ // Folia start - region threading
+ @Override
+ public <V> CompletableFuture<V> submit(Supplier<V> task) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ return super.submit(task);
+ }
+
+ @Override
+ public CompletableFuture<Void> submit(Runnable task) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ return super.submit(task);
+ }
+
+ @Override
+ public void schedule(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.schedule(runnable);
+ }
+
+ @Override
+ public void executeBlocking(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.executeBlocking(runnable);
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.execute(runnable);
+ }
+
+ @Override
+ public void executeIfPossible(Runnable runnable) {
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ super.executeIfPossible(runnable);
+ }
+ // Folia end - region threading
+
@Override
protected void doRunTask(Runnable task) {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
Profiler.get().incrementCounter("runTask");
super.doRunTask(task);
}
@Override
public boolean pollTask() {
+ // Folia start - region threading
+ if (ServerChunkCache.this.level != io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().world) {
+ throw new IllegalStateException("Polling tasks from non-owned region");
+ }
+ // Folia end - region threading
// Paper start - rewrite chunk system
final ServerChunkCache serverChunkCache = ServerChunkCache.this;
if (serverChunkCache.runDistanceManagerUpdates()) {
return true;
} else {
- return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask();
+ return io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion().getData().getTaskQueueData().executeChunkTask(); // Folia - region threading
}
// Paper end - rewrite chunk system
}

View File

@ -1,32 +0,0 @@
--- a/net/minecraft/server/level/ServerEntityGetter.java
+++ b/net/minecraft/server/level/ServerEntityGetter.java
@@ -14,17 +_,17 @@
@Nullable
default Player getNearestPlayer(TargetingConditions targetingConditions, LivingEntity source) {
- return this.getNearestEntity(this.players(), targetingConditions, source, source.getX(), source.getY(), source.getZ());
+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, source, source.getX(), source.getY(), source.getZ()); // Folia - region threading
}
@Nullable
default Player getNearestPlayer(TargetingConditions targetingConditions, LivingEntity source, double x, double y, double z) {
- return this.getNearestEntity(this.players(), targetingConditions, source, x, y, z);
+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, source, x, y, z); // Folia - region threading
}
@Nullable
default Player getNearestPlayer(TargetingConditions targetingConditions, double x, double y, double z) {
- return this.getNearestEntity(this.players(), targetingConditions, null, x, y, z);
+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, null, x, y, z); // Folia - region threading
}
@Nullable
@@ -57,7 +_,7 @@
default List<Player> getNearbyPlayers(TargetingConditions targetingConditions, LivingEntity source, AABB area) {
List<Player> list = new ArrayList<>();
- for (Player player : this.players()) {
+ for (Player player : this.getLocalPlayers()) { // Folia - region threading
if (area.contains(player.getX(), player.getY(), player.getZ()) && targetingConditions.test(this.getLevel(), source, player)) {
list.add(player);
}

View File

@ -1,631 +0,0 @@
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -180,7 +_,7 @@
public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
private static final Logger LOGGER = LogUtils.getLogger();
- public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
+ public static final long LAST_SAVE_ABSENT = Long.MIN_VALUE; public long lastSave = LAST_SAVE_ABSENT; // Paper // Folia - threaded regions - changed to nanoTime
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
private static final int FLY_STAT_RECORDING_SPEED = 25;
@@ -443,8 +_,149 @@
this.maxHealthCache = this.getMaxHealth();
}
+ // Folia start - region threading
+ private static final int SPAWN_RADIUS_SELECTION_SEARCH = 5;
+
+ private static BlockPos getRandomSpawn(ServerLevel world, RandomSource random) {
+ BlockPos spawn = world.getSharedSpawnPos();
+ double radius = (double)Math.max(0, world.getGameRules().getInt(GameRules.RULE_SPAWN_RADIUS));
+
+ double spawnX = (double)spawn.getX() + 0.5;
+ double spawnZ = (double)spawn.getZ() + 0.5;
+
+ net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder();
+
+ double selectMinX = Math.max(worldBorder.getMinX() + 1.0, spawnX - radius);
+ double selectMinZ = Math.max(worldBorder.getMinZ() + 1.0, spawnZ - radius);
+ double selectMaxX = Math.min(worldBorder.getMaxX() - 1.0, spawnX + radius);
+ double selectMaxZ = Math.min(worldBorder.getMaxZ() - 1.0, spawnZ + radius);
+
+ double amountX = selectMaxX - selectMinX;
+ double amountZ = selectMaxZ - selectMinZ;
+
+ int selectX = amountX < 1.0 ? Mth.floor(worldBorder.getCenterX()) : (int)Mth.floor((amountX + 1.0) * random.nextDouble() + selectMinX);
+ int selectZ = amountZ < 1.0 ? Mth.floor(worldBorder.getCenterZ()) : (int)Mth.floor((amountZ + 1.0) * random.nextDouble() + selectMinZ);
+
+ return new BlockPos(selectX, 0, selectZ);
+ }
+
+ private static void completeSpawn(ServerLevel world, BlockPos selected,
+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) {
+ toComplete.complete(io.papermc.paper.util.MCUtil.toLocation(world, Vec3.atBottomCenterOf(selected), world.levelData.getSpawnAngle(), 0.0f));
+ }
+
+ private static BlockPos findSpawnAround(ServerLevel world, ServerPlayer player, BlockPos selected) {
+ // try hard to find, so that we don't attempt another chunk load
+ for (int dz = -SPAWN_RADIUS_SELECTION_SEARCH; dz <= SPAWN_RADIUS_SELECTION_SEARCH; ++dz) {
+ for (int dx = -SPAWN_RADIUS_SELECTION_SEARCH; dx <= SPAWN_RADIUS_SELECTION_SEARCH; ++dx) {
+ BlockPos inChunk = PlayerRespawnLogic.getOverworldRespawnPos(world, selected.getX() + dx, selected.getZ() + dz);
+ if (inChunk == null) {
+ continue;
+ }
+
+ AABB checkVolume = player.getBoundingBoxAt((double)inChunk.getX() + 0.5, (double)inChunk.getY(), (double)inChunk.getZ() + 0.5);
+
+ if (!player.noCollisionNoLiquid(world, checkVolume)) {
+ continue;
+ }
+
+ return inChunk;
+ }
+ }
+
+ return null;
+ }
+
+ // rets false when another attempt is required
+ private static boolean trySpawnOrSchedule(ServerLevel world, ServerPlayer player, RandomSource random, int[] attemptCount, int maxAttempts,
+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) {
+ ++attemptCount[0];
+
+ BlockPos rough = getRandomSpawn(world, random);
+
+ // add 2 to ensure that the chunks are loaded for collision checks
+ int minX = (rough.getX() - (SPAWN_RADIUS_SELECTION_SEARCH + 2)) >> 4;
+ int minZ = (rough.getZ() - (SPAWN_RADIUS_SELECTION_SEARCH + 2)) >> 4;
+ int maxX = (rough.getX() + (SPAWN_RADIUS_SELECTION_SEARCH + 2)) >> 4;
+ int maxZ = (rough.getZ() + (SPAWN_RADIUS_SELECTION_SEARCH + 2)) >> 4;
+
+ // we could short circuit this check, but it would possibly recurse. Then, it could end up causing a stack overflow
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, minX, minZ, maxX, maxZ) || !world.moonrise$areChunksLoaded(minX, minZ, maxX, maxZ)) {
+ world.moonrise$loadChunksAsync(minX, maxX, minZ, maxZ, ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
+ (unused) -> {
+ BlockPos selected = findSpawnAround(world, player, rough);
+ if (selected == null) {
+ // run more spawn attempts
+ selectSpawn(world, player, random, attemptCount, maxAttempts, toComplete);
+ return;
+ }
+
+ completeSpawn(world, selected, toComplete);
+ return;
+ }
+ );
+ return true;
+ }
+
+ BlockPos selected = findSpawnAround(world, player, rough);
+ if (selected == null) {
+ return false;
+ }
+
+ completeSpawn(world, selected, toComplete);
+ return true;
+ }
+
+ private static void selectSpawn(ServerLevel world, ServerPlayer player, RandomSource random, int[] attemptCount, int maxAttempts,
+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) {
+ do {
+ if (attemptCount[0] >= maxAttempts) {
+ BlockPos sharedSpawn = world.getSharedSpawnPos();
+
+ LOGGER.warn("Found no spawn in radius for player '" + player.getName() + "', ignoring radius");
+
+ selectSpawnWithoutRadius(world, player, sharedSpawn, toComplete);
+ return;
+ }
+ } while (!trySpawnOrSchedule(world, player, random, attemptCount, maxAttempts, toComplete));
+ }
+
+
+ private static void selectSpawnWithoutRadius(ServerLevel world, ServerPlayer player, BlockPos spawn, ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) {
+ world.loadChunksForMoveAsync(player.getBoundingBoxAt(spawn.getX() + 0.5, spawn.getY(), spawn.getZ() + 0.5),
+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
+ (c) -> {
+ BlockPos ret = spawn;
+ while (!player.noCollisionNoLiquid(world, player.getBoundingBoxAt(ret.getX() + 0.5, ret.getY(), ret.getZ() + 0.5)) && ret.getY() < (double)world.getMaxY()) {
+ ret = ret.above();
+ }
+ while (player.noCollisionNoLiquid(world, player.getBoundingBoxAt(ret.getX() + 0.5, ret.getY() - 1, ret.getZ() + 0.5)) && ret.getY() > (double)(world.getMinY() + 1)) {
+ ret = ret.below();
+ }
+ toComplete.complete(io.papermc.paper.util.MCUtil.toLocation(world, Vec3.atBottomCenterOf(ret), world.levelData.getSpawnAngle(), 0.0f));
+ }
+ );
+ }
+
+ public static void fudgeSpawnLocation(ServerLevel world, ServerPlayer player, ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) { // Folia - region threading
+ BlockPos blockposition = world.getSharedSpawnPos();
+
+ if (world.dimensionType().hasSkyLight() && world.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
+ selectSpawn(world, player, player.random, new int[1], 500, toComplete);
+ } else {
+ selectSpawnWithoutRadius(world, player, blockposition, toComplete);
+ }
+
+ }
+ // Folia end - region threading
+
@Override
public BlockPos adjustSpawnLocation(ServerLevel level, BlockPos pos) {
+ // Folia start - region threading
+ if (true) {
+ throw new UnsupportedOperationException();
+ }
+ // Folia end - region threading
AABB aabb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO);
BlockPos blockPos = pos;
if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
@@ -533,7 +_,7 @@
this.getBukkitEntity().readExtraData(compound); // CraftBukkit
if (this.isSleeping()) {
- this.stopSleeping();
+ this.stopSleepingRaw(); // Folia - do not modify or read worldstate during data deserialization
}
// CraftBukkit start
@@ -709,10 +_,17 @@
ServerLevel level = this.level().getServer().getLevel(optional.get());
if (level != null) {
Entity entity = EntityType.loadEntityRecursive(
- compoundTag, level, EntitySpawnReason.LOAD, entity1 -> !level.addWithUUID(entity1) ? null : entity1
+ compoundTag, level, EntitySpawnReason.LOAD, entity1 -> entity1 // Folia - region threading - delay world add
);
if (entity != null) {
- placeEnderPearlTicket(level, entity.chunkPosition());
+ // Folia start - region threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ level, entity.chunkPosition().x, entity.chunkPosition().z, () -> {
+ level.addFreshEntityWithPassengers(entity);
+ ServerPlayer.placeEnderPearlTicket(level, entity.chunkPosition());
+ }
+ );
+ // Folia end - region threading
} else {
LOGGER.warn("Failed to spawn player ender pearl in level ({}), skipping", optional.get());
}
@@ -817,12 +_,23 @@
Entity camera = this.getCamera();
if (camera != this) {
- if (camera.isAlive()) {
+ if (camera.canBeSpectated()) { // Folia - region threading - replace removed check
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(camera) && !camera.isRemoved()) { // Folia - region threading
this.absMoveTo(camera.getX(), camera.getY(), camera.getZ(), camera.getYRot(), camera.getXRot());
this.serverLevel().getChunkSource().move(this);
if (this.wantsToStopRiding()) {
this.setCamera(this);
}
+ } else { // Folia start - region threading
+ Entity realCamera = camera.getBukkitEntity().getHandleRaw();
+ if (realCamera != camera) {
+ this.setCamera(this);
+ this.setCamera(realCamera);
+ } else {
+ this.teleportToCameraOffRegion();
+ }
+ }
+ // Folia end - region threading
} else {
this.setCamera(this);
}
@@ -1357,9 +_,332 @@
}
}
+ // Folia start - region threading
+ /**
+ * Teleport flag indicating that the player is to be respawned, expected to only be used
+ * internally for {@link #respawn(java.util.function.Consumer, PlayerRespawnEvent.RespawnReason)}
+ */
+ public static final long TELEPORT_FLAGS_PLAYER_RESPAWN = Long.MIN_VALUE >>> 0;
+
+ public void exitEndCredits() {
+ if (!this.wonGame) {
+ // not in the end credits anymore
+ return;
+ }
+ this.wonGame = false;
+
+ this.respawn((player) -> {
+ CriteriaTriggers.CHANGED_DIMENSION.trigger(player, Level.END, Level.OVERWORLD);
+ }, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL, true);
+ }
+
+ public void respawn(java.util.function.Consumer<ServerPlayer> respawnComplete, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason reason) {
+ this.respawn(respawnComplete, reason, false);
+ }
+
+ private void respawn(java.util.function.Consumer<ServerPlayer> respawnComplete, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason reason, boolean alive) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot respawn entity async");
+
+ this.getBukkitEntity(); // force bukkit entity to be created before TPing
+
+ if (alive != this.isAlive()) {
+ throw new IllegalStateException("isAlive expected = " + alive);
+ }
+
+ if (!this.hasNullCallback()) {
+ this.unRide();
+ }
+
+ if (this.isVehicle() || this.isPassenger()) {
+ throw new IllegalStateException("Dead player should not be a vehicle or passenger");
+ }
+
+ ServerLevel origin = this.serverLevel();
+ ServerLevel respawnWorld = this.server.getLevel(this.getRespawnDimension());
+
+ // modified based off PlayerList#respawn
+
+ EntityTreeNode passengerTree = this.makePassengerTree();
+
+ this.isChangingDimension = true;
+ origin.removePlayerImmediately(this, RemovalReason.CHANGED_DIMENSION);
+ // reset player if needed, only after removal from world
+ if (!alive) {
+ ServerPlayer.this.reset();
+ }
+ // must be manually removed from connections, delay until after reset() so that we do not trip any thread checks
+ this.serverLevel().getCurrentWorldData().connections.remove(this.connection.connection);
+
+ BlockPos respawnPos = this.getRespawnPosition();
+ float respawnAngle = this.getRespawnAngle();
+ boolean isRespawnForced = this.isRespawnForced();
+
+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> spawnPosComplete =
+ new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>();
+ boolean[] usedRespawnAnchor = new boolean[1];
+
+ // set up post spawn location logic
+ spawnPosComplete.addWaiter((spawnLoc, throwable) -> {
+ // update pos and velocity
+ ServerPlayer.this.setPosRaw(spawnLoc.getX(), spawnLoc.getY(), spawnLoc.getZ());
+ ServerPlayer.this.setYRot(spawnLoc.getYaw());
+ ServerPlayer.this.setYHeadRot(spawnLoc.getYaw());
+ ServerPlayer.this.setXRot(spawnLoc.getPitch());
+ ServerPlayer.this.setDeltaMovement(Vec3.ZERO);
+ // placeInAsync will update the world
+
+ this.placeInAsync(
+ origin,
+ // use the load chunk flag just in case the spawn loc isn't loaded, and to ensure the chunks
+ // stay loaded for a bit with the teleport ticket
+ ((org.bukkit.craftbukkit.CraftWorld)spawnLoc.getWorld()).getHandle(),
+ TELEPORT_FLAG_LOAD_CHUNK | TELEPORT_FLAGS_PLAYER_RESPAWN,
+ passengerTree, // note: we expect this to just be the player, no passengers
+ (entity) -> {
+ // now the player is in the world, and can receive sound
+ if (usedRespawnAnchor[0]) {
+ ServerPlayer.this.connection.send(
+ new ClientboundSoundPacket(
+ net.minecraft.sounds.SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS,
+ ServerPlayer.this.getX(), ServerPlayer.this.getY(), ServerPlayer.this.getZ(),
+ 1.0F, 1.0F, ServerPlayer.this.serverLevel().getRandom().nextLong()
+ )
+ );
+ }
+ // now the respawn logic is complete
+
+ // last, call the function callback
+ if (respawnComplete != null) {
+ respawnComplete.accept(ServerPlayer.this);
+ }
+ }
+ );
+ });
+
+ // find and modify respawn block state
+ if (respawnWorld == null || respawnPos == null) {
+ // default to regular spawn
+ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete);
+ } else {
+ // load chunk for block
+ // give at least 1 radius of loaded chunks so that we do not sync load anything
+ int radiusBlocks = 16;
+ respawnWorld.moonrise$loadChunksAsync(respawnPos, radiusBlocks,
+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
+ (chunks) -> {
+ ServerPlayer.RespawnPosAngle spawnPos = ServerPlayer.findRespawnAndUseSpawnBlock(
+ respawnWorld, respawnPos, respawnAngle, isRespawnForced, !alive
+ ).orElse(null);
+ if (spawnPos == null) {
+ // no spawn
+ ServerPlayer.this.connection.send(
+ new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)
+ );
+ ServerPlayer.this.setRespawnPosition(
+ null, null, 0f, false, false,
+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN
+ );
+ // default to regular spawn
+ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete);
+ return;
+ }
+
+ boolean isRespawnAnchor = respawnWorld.getBlockState(respawnPos).is(net.minecraft.world.level.block.Blocks.RESPAWN_ANCHOR);
+ boolean isBed = respawnWorld.getBlockState(respawnPos).is(net.minecraft.tags.BlockTags.BEDS);
+ usedRespawnAnchor[0] = !alive && isRespawnAnchor;
+
+ ServerPlayer.this.setRespawnPosition(
+ respawnWorld.dimension(), respawnPos, respawnAngle, isRespawnForced, false,
+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN
+ );
+
+ // finished now, pass the location on
+ spawnPosComplete.complete(
+ io.papermc.paper.util.MCUtil.toLocation(respawnWorld, spawnPos.position(), spawnPos.yaw(), 0.0f)
+ );
+ return;
+ }
+ );
+ }
+ }
+
+ @Override
+ protected void teleportSyncSameRegion(Vec3 pos, Float yaw, Float pitch, Vec3 velocity) {
+ if (yaw != null) {
+ this.setYRot(yaw.floatValue());
+ this.setYHeadRot(yaw.floatValue());
+ }
+ if (pitch != null) {
+ this.setXRot(pitch.floatValue());
+ }
+ if (velocity != null) {
+ this.setDeltaMovement(velocity);
+ }
+ this.connection.internalTeleport(
+ new net.minecraft.world.entity.PositionMoveRotation(
+ pos, this.getDeltaMovement(), this.getYRot(), this.getXRot()
+ ),
+ java.util.Collections.emptySet()
+ );
+ this.connection.resetPosition();
+ this.setOldPosAndRot();
+ this.resetStoredPositions();
+ }
+
+ @Override
+ protected ServerPlayer transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) {
+ // must be manually removed from connections
+ this.serverLevel().getCurrentWorldData().connections.remove(this.connection.connection);
+ this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+
+ this.spawnIn(destination);
+ this.transform(pos, yaw, pitch, velocity);
+
+ return this;
+ }
+
+ @Override
+ public void preChangeDimension() {
+ super.preChangeDimension();
+ this.stopUsingItem();
+ }
+
+ @Override
+ protected void placeSingleSync(ServerLevel originWorld, ServerLevel destination, EntityTreeNode treeNode, long teleportFlags) {
+ if (destination == originWorld && (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L) {
+ this.unsetRemoved();
+ destination.addDuringTeleport(this);
+
+ // must be manually added to connections
+ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection);
+
+ // required to set up the pending teleport stuff to the client, and to actually update
+ // the player's position clientside
+ this.connection.internalTeleport(
+ new net.minecraft.world.entity.PositionMoveRotation(
+ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot()
+ ),
+ java.util.Collections.emptySet()
+ );
+ this.connection.resetPosition();
+
+ this.postChangeDimension();
+ } else {
+ // Modelled after PlayerList#respawn
+
+ // We avoid checking for disconnection here, which means we do not have to add/remove from
+ // the player list here. We can let this be properly handled by the connection handler
+
+ // pre-add logic
+ PlayerList playerlist = this.server.getPlayerList();
+ net.minecraft.world.level.storage.LevelData worlddata = destination.getLevelData();
+ this.connection.send(
+ new ClientboundRespawnPacket(
+ this.createCommonSpawnInfo(destination),
+ (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L ? (byte)1 : (byte)0
+ )
+ );
+ // don't bother with the chunk cache radius and simulation distance packets, they are handled
+ // by the chunk loader
+ this.spawnIn(destination); // important that destination != null
+ // we can delay teleport until later, the player position is already set up at the target
+ this.setShiftKeyDown(false);
+
+ this.connection.send(new net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket(
+ destination.getSharedSpawnPos(), destination.getSharedSpawnAngle()
+ ));
+ this.connection.send(new ClientboundChangeDifficultyPacket(
+ worlddata.getDifficulty(), worlddata.isDifficultyLocked()
+ ));
+ this.connection.send(new ClientboundSetExperiencePacket(
+ this.experienceProgress, this.totalExperience, this.experienceLevel
+ ));
+
+ playerlist.sendActivePlayerEffects(this);
+ playerlist.sendLevelInfo(this, destination);
+ playerlist.sendPlayerPermissionLevel(this);
+
+ // regular world add logic
+ this.unsetRemoved();
+ destination.addDuringTeleport(this);
+
+ // must be manually added to connections
+ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection);
+
+ // required to set up the pending teleport stuff to the client, and to actually update
+ // the player's position clientside
+ this.connection.internalTeleport(
+ new net.minecraft.world.entity.PositionMoveRotation(
+ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot()
+ ),
+ java.util.Collections.emptySet()
+ );
+ this.connection.resetPosition();
+
+ // delay callback until after post add logic
+
+ // post add logic
+
+ // "Added from changeDimension"
+ this.setHealth(this.getHealth());
+ playerlist.sendAllPlayerInfo(this);
+ this.onUpdateAbilities();
+ /*for (MobEffectInstance mobEffect : this.getActiveEffects()) {
+ this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), mobEffect, false));
+ }*/ // handled by sendActivePlayerEffects
+
+ // Paper start - Reset shield blocking on dimension change
+ if (this.isBlocking()) {
+ this.stopUsingItem();
+ }
+ // Paper end - Reset shield blocking on dimension change
+
+ this.triggerDimensionChangeTriggers(originWorld);
+
+ // finished
+
+ this.postChangeDimension();
+ }
+ }
+
+ @Override
+ public boolean endPortalLogicAsync(BlockPos portalPos) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async");
+
+ if (this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) {
+ if (!this.canPortalAsync(null, false)) {
+ return false;
+ }
+ this.wonGame = true;
+ // TODO is there a better solution to this that DOESN'T skip the credits?
+ this.seenCredits = true;
+ if (!this.seenCredits) {
+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 0.0F));
+ }
+ this.exitEndCredits();
+ return true;
+ } else {
+ return super.endPortalLogicAsync(portalPos);
+ }
+ }
+
+ @Override
+ protected void prePortalLogic(ServerLevel origin, ServerLevel destination, PortalType type) {
+ super.prePortalLogic(origin, destination, type);
+ if (origin.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD && destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER) {
+ this.enteredNetherPosition = this.position();
+ }
+ }
+ // Folia end - region threading
+
@Nullable
@Override
public ServerPlayer teleport(TeleportTransition teleportTransition) {
+ // Folia start - region threading
+ if (true) {
+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading");
+ }
+ // Folia end - region threading
if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154
if (this.isRemoved()) {
return null;
@@ -2397,7 +_,30 @@
return (Entity)(this.camera == null ? this : this.camera);
}
+ // Folia start - region threading
+ private void teleportToCameraOffRegion() {
+ Entity cameraFinal = this.camera;
+ // use the task scheduler, as we don't know where the caller is invoking from
+ if (this != cameraFinal) {
+ this.getBukkitEntity().taskScheduler.schedule((final ServerPlayer newPlayer) -> {
+ io.papermc.paper.threadedregions.TeleportUtils.teleport(
+ newPlayer, false, cameraFinal, null, null, 0L,
+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null,
+ (final ServerPlayer newerPlayer) -> {
+ return newerPlayer.camera == cameraFinal;
+ }
+ );
+ }, null, 1L);
+ } // else: do not bother teleporting to self
+ }
+ // Folia end - region threading
+
public void setCamera(@Nullable Entity entityToSpectate) {
+ // Folia start - region threading
+ if (entityToSpectate != null && (entityToSpectate != this && !entityToSpectate.canBeSpectated())) {
+ return;
+ }
+ // Folia end - region threading
Entity camera = this.getCamera();
this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate);
if (camera != this.camera) {
@@ -2416,16 +_,19 @@
}
}
// Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity
- if (this.camera.level() instanceof ServerLevel serverLevel) {
- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
- }
-
- if (entityToSpectate != null) {
- this.serverLevel().getChunkSource().move(this);
- }
-
+ // Folia - region threading - move down
+
+ // Folia - region threading - not needed
+
+ // Folia start - region threading - handle camera setting better
+ if (this.camera == this
+ || (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.camera) && this.camera.moonrise$getTrackedEntity() != null
+ && this.camera.moonrise$getTrackedEntity().seenBy.contains(this.connection))) {
+ // Folia end - region threading - handle camera setting better
this.connection.send(new ClientboundSetCameraPacket(this.camera));
- this.connection.resetPosition();
+ } // Folia - region threading - handle camera setting better
+ //this.connection.resetPosition(); // Folia - region threading - not needed
+ this.teleportToCameraOffRegion(); // Folia - region threading - moved down
}
}
@@ -2896,11 +_,11 @@
}
public void registerEnderPearl(ThrownEnderpearl enderPearl) {
- this.enderPearls.add(enderPearl);
+ //this.enderPearls.add(enderPearl); // Folia - region threading - do not track ender pearls
}
public void deregisterEnderPearl(ThrownEnderpearl enderPearl) {
- this.enderPearls.remove(enderPearl);
+ //this.enderPearls.remove(enderPearl); // Folia - region threading - do not track ender pearls
}
public Set<ThrownEnderpearl> getEnderPearls() {
@@ -3054,7 +_,7 @@
this.experienceLevel = this.newLevel;
this.totalExperience = this.newTotalExp;
this.experienceProgress = 0;
- this.deathTime = 0;
+ this.deathTime = 0; this.broadcastedDeath = false; // Folia - region threading
this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent
this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
this.effectsDirty = true;

View File

@ -1,40 +0,0 @@
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -114,7 +_,7 @@
// this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensate eating
if (this.hasDelayedDestroy) {
- BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
+ BlockState blockState = !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.delayedDestroyPos) ? null : this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks // Folia - region threading - don't destroy blocks not owned
if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
this.hasDelayedDestroy = false;
} else {
@@ -126,7 +_,7 @@
}
} else if (this.isDestroyingBlock) {
// Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead
- BlockState blockState = this.level.getBlockStateIfLoaded(this.destroyPos);
+ BlockState blockState = !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.destroyPos) ? null : this.level.getBlockStateIfLoaded(this.destroyPos); // Folia - region threading - don't destroy blocks not owned
if (blockState == null) {
this.isDestroyingBlock = false;
return;
@@ -369,7 +_,7 @@
} else {
// CraftBukkit start
org.bukkit.block.BlockState state = bblock.getState();
- this.level.captureDrops = new java.util.ArrayList<>();
+ this.level.getCurrentWorldData().captureDrops = new java.util.ArrayList<>(); // Folia - region threading
// CraftBukkit end
BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player);
boolean flag = this.level.removeBlock(pos, false);
@@ -395,8 +_,8 @@
// return true; // CraftBukkit
}
// CraftBukkit start
- java.util.List<net.minecraft.world.entity.item.ItemEntity> itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world
- this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff
+ java.util.List<net.minecraft.world.entity.item.ItemEntity> itemsToDrop = this.level.getCurrentWorldData().captureDrops; // Paper - capture all item additions to the world // Folia - region threading
+ this.level.getCurrentWorldData().captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff // Folia - region threading
if (event.isDropItems()) {
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world
}

View File

@ -1,22 +0,0 @@
--- a/net/minecraft/server/level/TicketType.java
+++ b/net/minecraft/server/level/TicketType.java
@@ -17,10 +_,18 @@
public static final TicketType<ChunkPos> FORCED = create("forced", Comparator.comparingLong(ChunkPos::toLong));
public static final TicketType<BlockPos> PORTAL = create("portal", Vec3i::compareTo, 300);
public static final TicketType<ChunkPos> ENDER_PEARL = create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40);
- public static final TicketType<ChunkPos> UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
+ public static final TicketType<ChunkPos> UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 5); // Folia - region threading
public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
+ // Folia start - region threading
+ public static final TicketType<Unit> LOGIN = create("folia:login", (u1, u2) -> 0, 20);
+ public static final TicketType<Unit> DELAYED = create("folia:delay", (u1, u2) -> 0, 5);
+ public static final TicketType<Long> END_GATEWAY_EXIT_SEARCH = create("folia:end_gateway_exit_search", Long::compareTo);
+ public static final TicketType<Long> NETHER_PORTAL_DOUBLE_CHECK = create("folia:nether_portal_double_check", Long::compareTo);
+ public static final TicketType<Long> TELEPORT_HOLD_TICKET = create("folia:teleport_hold_ticket", Long::compareTo);
+ public static final TicketType<Unit> REGION_SCHEDULER_API_HOLD = create("folia:region_scheduler_api_hold", (a, b) -> 0);
+ // Folia end - region threading
public static <T> TicketType<T> create(String name, Comparator<T> comparator) {
return new TicketType<>(name, comparator, 0L);

View File

@ -1,24 +0,0 @@
--- a/net/minecraft/server/level/WorldGenRegion.java
+++ b/net/minecraft/server/level/WorldGenRegion.java
@@ -107,6 +_,13 @@
return this.getLightEngine().getRawBrightness(blockPos, subtract);
}
// Paper end - rewrite chunk system
+ // Folia start - region threading
+ private final net.minecraft.world.level.StructureManager structureManager;
+ @Override
+ public net.minecraft.world.level.StructureManager structureManager() {
+ return this.structureManager;
+ }
+ // Folia end - region threading
public WorldGenRegion(ServerLevel level, StaticCache2D<GenerationChunkHolder> cache, ChunkStep generatingStep, ChunkAccess center) {
this.generatingStep = generatingStep;
@@ -118,6 +_,7 @@
this.random = level.getChunkSource().randomState().getOrCreateRandomFactory(WORLDGEN_REGION_RANDOM).at(this.center.getPos().getWorldPosition());
this.dimensionType = level.dimensionType();
this.biomeManager = new BiomeManager(this, BiomeManager.obfuscateSeed(this.seed));
+ this.structureManager = level.structureManager().forWorldGenRegion(this); // Folia - region threading
}
public boolean isOldChunkAround(ChunkPos pos, int radius) {

View File

@ -1,89 +0,0 @@
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -96,6 +_,10 @@
}
}
+ // Folia start - region threading
+ private boolean handledDisconnect = false;
+ // Folia end - region threading
+
@Override
public void onDisconnect(DisconnectionDetails details) {
// Paper start - Fix kick event leave message not being sent
@@ -104,10 +_,18 @@
public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
// Paper end - Fix kick event leave message not being sent
+ // Folia start - region threading
+ if (this.handledDisconnect) {
+ // avoid retiring scheduler twice
+ return;
+ }
+ this.handledDisconnect = true;
+ // Folia end - region threading
if (this.isSingleplayerOwner()) {
LOGGER.info("Stopping singleplayer server as player logged out");
this.server.halt(false);
}
+ this.player.getBukkitEntity().taskScheduler.retire(); // Folia - region threading
}
@Override
@@ -330,24 +_,8 @@
if (this.processedDisconnect) {
return;
}
- if (!this.cserver.isPrimaryThread()) {
- org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() {
- @Override
- protected Object evaluate() {
- ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes
- return null;
- }
- };
-
- this.server.processQueue.add(waitable);
-
- try {
- waitable.get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (java.util.concurrent.ExecutionException e) {
- throw new RuntimeException(e);
- }
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player)) { // Folia - region threading
+ this.connection.disconnectSafely(disconnectionDetails, cause); // Folia - region threading - it HAS to be delayed/async to avoid deadlock if we try to wait for another region
return;
}
@@ -378,7 +_,7 @@
this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
this.connection.setReadOnly();
// CraftBukkit - Don't wait
- this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper
+ //this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper // Folia - region threading
}
// Paper start - add proper async disconnect
@@ -391,19 +_,7 @@
}
public void disconnectAsync(DisconnectionDetails disconnectionInfo, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
- if (this.cserver.isPrimaryThread()) {
- this.disconnect(disconnectionInfo, cause);
- return;
- }
-
- this.connection.setReadOnly();
- this.server.scheduleOnMain(() -> {
- ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause);
- if (ServerCommonPacketListenerImpl.this.player.quitReason == null) {
- // cancelled
- ServerCommonPacketListenerImpl.this.connection.enableAutoRead();
- }
- });
+ this.disconnect(disconnectionInfo, cause); // Folia - threaded regions
}
// Paper end - add proper async disconnect

View File

@ -1,70 +0,0 @@
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -47,6 +_,7 @@
private ClientInformation clientInformation;
@Nullable
private SynchronizeRegistriesTask synchronizeRegistriesTask;
+ public boolean switchToMain = false; // Folia - region threading - rewrite login process
// CraftBukkit start
public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) {
@@ -160,7 +_,58 @@
}
ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
- playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
+ // Folia start - region threading - rewrite login process
+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot handle player login off global tick thread");
+ CommonListenerCookie clientData = this.createCookie(this.clientInformation);
+ org.apache.commons.lang3.mutable.MutableObject<net.minecraft.nbt.CompoundTag> data = new org.apache.commons.lang3.mutable.MutableObject<>();
+ org.apache.commons.lang3.mutable.MutableObject<String> lastKnownName = new org.apache.commons.lang3.mutable.MutableObject<>();
+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>();
+ // note: need to call addWaiter before completion to ensure the callback is invoked synchronously
+ // the loadSpawnForNewPlayer function always completes the completable once the chunks were loaded,
+ // on the load callback for those chunks (so on the same region)
+ // this guarantees the chunk cannot unload under our feet
+ toComplete.addWaiter((org.bukkit.Location loc, Throwable t) -> {
+ int chunkX = net.minecraft.util.Mth.floor(loc.getX()) >> 4;
+ int chunkZ = net.minecraft.util.Mth.floor(loc.getZ()) >> 4;
+
+ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)loc.getWorld()).getHandle();
+ // we just need to hold the chunks at loaded until the next tick
+ // so we do not need to care about unique IDs for the ticket
+ world.getChunkSource().addTicketAtLevel(
+ net.minecraft.server.level.TicketType.LOGIN,
+ new net.minecraft.world.level.ChunkPos(chunkX, chunkZ),
+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL,
+ net.minecraft.util.Unit.INSTANCE
+ );
+
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+ world, chunkX, chunkZ,
+ () -> {
+ // once switchToMain is set, the current ticking region now owns the connection and is responsible
+ // for cleaning it up
+ playerList.placeNewPlayer(
+ ServerConfigurationPacketListenerImpl.this.connection,
+ playerForLogin,
+ clientData,
+ java.util.Optional.ofNullable(data.getValue()),
+ lastKnownName.getValue(),
+ loc
+ );
+ },
+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER
+ );
+ });
+ this.switchToMain = true;
+ try {
+ // now the connection responsibility is transferred on the region
+ playerList.loadSpawnForNewPlayer(this.connection, playerForLogin, clientData, data, lastKnownName, toComplete);
+ } catch (final Throwable throwable) {
+ // assume toComplete will not be invoked
+ // ensure global tick thread owns the connection again, to properly disconnect it
+ this.switchToMain = false;
+ throw new RuntimeException(throwable);
+ }
+ // Folia end - region threading - rewrite login process
} catch (Exception var5) {
LOGGER.error("Couldn't place player in world", (Throwable)var5);
// Paper start - Debugging

View File

@ -1,28 +0,0 @@
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -167,12 +_,15 @@
}
// Paper end - Add support for proxy protocol
// ServerConnectionListener.this.connections.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
- ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
+ //ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking // Folia - connection fixes - move down
connection.configurePacketHandler(channelPipeline);
connection.setListenerForServerboundHandshake(
new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
);
io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
+ // Folia start - regionised threading
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addConnection(connection);
+ // Folia end - regionised threading
}
}
)
@@ -242,7 +_,7 @@
// Spigot start
this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
// This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
- if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) {
+ if (org.spigotmc.SpigotConfig.playerShuffle > 0 && 0 % org.spigotmc.SpigotConfig.playerShuffle == 0) { // Folia - region threading
Collections.shuffle(this.connections);
}
// Spigot end

View File

@ -1,396 +0,0 @@
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -292,10 +_,10 @@
private int knownMovePacketCount;
private boolean receivedMovementThisTick;
// CraftBukkit start - add fields
- private int lastTick = MinecraftServer.currentTick;
+ private long lastTick = Util.getMillis() / 50L; // Folia - region threading
private int allowedPlayerTicks = 1;
- private int lastDropTick = MinecraftServer.currentTick;
- private int lastBookTick = MinecraftServer.currentTick;
+ private long lastDropTick = Util.getMillis() / 50L; // Folia - region threading
+ private long lastBookTick = Util.getMillis() / 50L; // Folia - region threading
private int dropCount = 0;
private boolean hasMoved = false;
@@ -313,9 +_,16 @@
private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20);
private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault();
private final FutureChain chatMessageChain;
- private boolean waitingForSwitchToConfig;
+ public volatile boolean waitingForSwitchToConfig; // Folia - rewrite login process - fix bad ordering of this field write + public
private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length
+ // Folia start - region threading
+ public net.minecraft.world.level.ChunkPos disconnectPos;
+ private static final java.util.concurrent.atomic.AtomicLong DISCONNECT_TICKET_ID_GENERATOR = new java.util.concurrent.atomic.AtomicLong();
+ public static final net.minecraft.server.level.TicketType<Long> DISCONNECT_TICKET = net.minecraft.server.level.TicketType.create("disconnect_ticket", Long::compareTo);
+ public final Long disconnectTicketId = Long.valueOf(DISCONNECT_TICKET_ID_GENERATOR.getAndIncrement());
+ // Folia end - region threading
+
public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
super(server, connection, cookie, player); // CraftBukkit
this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection());
@@ -328,6 +_,12 @@
@Override
public void tick() {
+ // Folia start - region threading
+ this.keepConnectionAlive();
+ if (this.processedDisconnect || this.player.wonGame) {
+ return;
+ }
+ // Folia end - region threading
if (this.ackBlockChangesUpTo > -1) {
this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo));
this.ackBlockChangesUpTo = -1;
@@ -376,7 +_,7 @@
this.aboveGroundVehicleTickCount = 0;
}
- this.keepConnectionAlive();
+ // Folia - region threading - moved to beginning of method
this.chatSpamThrottler.tick();
this.dropSpamThrottler.tick();
this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits
@@ -412,6 +_,19 @@
this.lastGoodX = this.player.getX();
this.lastGoodY = this.player.getY();
this.lastGoodZ = this.player.getZ();
+ // Folia start - support vehicle teleportations
+ this.lastVehicle = this.player.getRootVehicle();
+ if (this.lastVehicle != this.player && this.lastVehicle.getControllingPassenger() == this.player) {
+ this.vehicleFirstGoodX = this.lastVehicle.getX();
+ this.vehicleFirstGoodY = this.lastVehicle.getY();
+ this.vehicleFirstGoodZ = this.lastVehicle.getZ();
+ this.vehicleLastGoodX = this.lastVehicle.getX();
+ this.vehicleLastGoodY = this.lastVehicle.getY();
+ this.vehicleLastGoodZ = this.lastVehicle.getZ();
+ } else {
+ this.lastVehicle = null;
+ }
+ // Folia end - support vehicle teleportations
}
@Override
@@ -519,9 +_,10 @@
// Paper end - fix large move vectors killing the server
// CraftBukkit start - handle custom speeds and skipped ticks
- this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
+ int currTick = (int)(Util.getMillis() / 50); // Folia - region threading
+ this.allowedPlayerTicks += currTick - this.lastTick; // Folia - region threading
this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
- this.lastTick = (int) (System.currentTimeMillis() / 50);
+ this.lastTick = (int) currTick; // Folia - region threading
++this.receivedMovePacketCount;
int i = this.receivedMovePacketCount - this.knownMovePacketCount;
@@ -588,7 +_,7 @@
}
rootVehicle.absMoveTo(d, d1, d2, f, f1);
- this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+ //this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit // Folia - move to repositionAllPassengers
// Paper start - optimise out extra getCubes
boolean teleportBack = flag2; // violating this is always a fail
if (!teleportBack) {
@@ -600,10 +_,18 @@
}
if (teleportBack) { // Paper end - optimise out extra getCubes
rootVehicle.absMoveTo(x, y, z, f, f1);
- this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+ //this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit // Folia - not needed, the player is no longer updated
this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
return;
}
+
+ // Folia start - move to positionRider
+ // this correction is required on folia since we move the connection tick to the beginning of the server
+ // tick, which would make any desync here visible
+ // this will correctly update the passenger positions for all mounted entities
+ // this prevents desync and ensures that all passengers have the correct rider-adjusted position
+ rootVehicle.repositionAllPassengers(false);
+ // Folia end - move to positionRider
// CraftBukkit start - fire PlayerMoveEvent
org.bukkit.entity.Player player = this.getCraftPlayer();
@@ -635,7 +_,7 @@
// If the event is cancelled we move the player back to their old location.
if (event.isCancelled()) {
- this.teleport(from);
+ this.player.getBukkitEntity().teleportAsync(from, PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading
return;
}
@@ -643,7 +_,7 @@
// there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
// We only do this if the Event was not cancelled.
if (!oldTo.equals(event.getTo()) && !event.isCancelled()) {
- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN);
+ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading
return;
}
@@ -824,7 +_,7 @@
}
// This needs to be on main
- this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringReader));
+ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> this.sendServerSuggestions(packet, stringReader), null, 1L); // Folia - region threading
} else if (!completions.isEmpty()) {
final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringReader.getTotalLength());
final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1);
@@ -1207,11 +_,11 @@
}
// Paper end - Book size limits
// CraftBukkit start
- if (this.lastBookTick + 20 > MinecraftServer.currentTick) {
+ if (this.lastBookTick + 20 > this.lastTick) { // Folia - region threading
this.disconnectAsync(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect
return;
}
- this.lastBookTick = MinecraftServer.currentTick;
+ this.lastBookTick = this.lastTick; // Folia - region threading
// CraftBukkit end
int slot = packet.slot();
if (Inventory.isHotbarSlot(slot) || slot == 40) {
@@ -1222,7 +_,22 @@
Consumer<List<FilteredText>> consumer = optional.isPresent()
? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot)
: texts -> this.updateBookContents(texts, slot);
- this.filterTextPacket(list).thenAcceptAsync(consumer, this.server);
+ // Folia start - region threading
+ this.filterTextPacket(list).thenAcceptAsync(
+ consumer,
+ (Runnable run) -> {
+ this.player.getBukkitEntity().taskScheduler.schedule(
+ (player) -> {
+ run.run();
+ },
+ null, 1L);
+ }
+ ).whenComplete((Object res, Throwable thr) -> {
+ if (thr != null) {
+ LOGGER.error("Failed to handle book update packet", thr);
+ }
+ });
+ // Folia end - region threading
}
}
@@ -1348,9 +_,10 @@
int i = this.receivedMovePacketCount - this.knownMovePacketCount;
// CraftBukkit start - handle custom speeds and skipped ticks
- this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
+ int currTick = (int)(Util.getMillis() / 50); // Folia - region threading
+ this.allowedPlayerTicks += currTick - this.lastTick; // Folia - region threading
this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
- this.lastTick = (int) (System.currentTimeMillis() / 50);
+ this.lastTick = (int) currTick; // Folia - region threading
if (i > Math.max(this.allowedPlayerTicks, 5)) {
LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
@@ -1539,7 +_,7 @@
// If the event is cancelled we move the player back to their old location.
if (event.isCancelled()) {
- this.teleport(from);
+ this.player.getBukkitEntity().teleportAsync(from, PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading
return;
}
@@ -1547,7 +_,7 @@
// there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
// We only do this if the Event was not cancelled.
if (!oldTo.equals(event.getTo()) && !event.isCancelled()) {
- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN);
+ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading
return;
}
@@ -1806,9 +_,9 @@
if (!this.player.isSpectator()) {
// limit how quickly items can be dropped
// If the ticks aren't the same then the count starts from 0 and we update the lastDropTick.
- if (this.lastDropTick != MinecraftServer.currentTick) {
+ if (this.lastDropTick != io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick()) { // Folia - region threading
this.dropCount = 0;
- this.lastDropTick = MinecraftServer.currentTick;
+ this.lastDropTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading
} else {
// Else we increment the drop count and check the amount.
this.dropCount++;
@@ -1836,7 +_,7 @@
case ABORT_DESTROY_BLOCK:
case STOP_DESTROY_BLOCK:
// Paper start - Don't allow digging into unloaded chunks
- if (this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) {
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), pos.getX() >> 4, pos.getZ() >> 4, 8) || this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { // Folia - region threading - don't destroy blocks not owned
this.player.connection.ackBlockChangesUpTo(packet.getSequence());
return;
}
@@ -1918,7 +_,7 @@
}
// Paper end - improve distance check
BlockPos blockPos = hitResult.getBlockPos();
- if (this.player.canInteractWithBlock(blockPos, 1.0)) {
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), blockPos.getX() >> 4, blockPos.getZ() >> 4, 8) && this.player.canInteractWithBlock(blockPos, 1.0)) { // Folia - do not allow players to interact with blocks outside the current region
Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos));
double d = 1.0000001;
if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) {
@@ -2039,7 +_,7 @@
for (ServerLevel serverLevel : this.server.getAllLevels()) {
Entity entity = packet.getEntity(serverLevel);
if (entity != null) {
- this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
+ io.papermc.paper.threadedregions.TeleportUtils.teleport(this.player, false, entity, null, null, Entity.TELEPORT_FLAG_LOAD_CHUNK, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null); // Folia - region threading
return;
}
}
@@ -2071,7 +_,7 @@
}
// CraftBukkit end
LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString());
- this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent
+ if (!this.waitingForSwitchToConfig) this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent // Folia - region threading
super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent
}
@@ -2080,6 +_,8 @@
this.removePlayerFromWorld(null);
}
+ public boolean hackSwitchingConfig; // Folia - rewrite login process
+
private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) {
// Paper end - Fix kick event leave message not being sent
this.chatMessageChain.close();
@@ -2093,6 +_,8 @@
this.player.disconnect();
// Paper start - Adventure
quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used
+ if (!this.hackSwitchingConfig) this.disconnectPos = this.player.chunkPosition(); // Folia - region threading - note: only set after removing, since it can tick the player
+ if (!this.hackSwitchingConfig) this.player.serverLevel().chunkSource.addTicketAtLevel(DISCONNECT_TICKET, this.disconnectPos, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, this.disconnectTicketId); // Folia - region threading - force chunk to be loaded so that the region is not lost
if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false);
// Paper end - Adventure
@@ -2331,7 +_,7 @@
this.player.resetLastActionTime();
// CraftBukkit start
if (sync) {
- this.server.execute(handler);
+ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> handler.run(), null, 1L); // Folia - region threading
} else {
handler.run();
}
@@ -2386,7 +_,7 @@
String originalFormat = event.getFormat(), originalMessage = event.getMessage();
this.cserver.getPluginManager().callEvent(event);
- if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) {
+ if (false && PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { // Folia - region threading
// Evil plugins still listening to deprecated event
final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients());
queueEvent.setCancelled(event.isCancelled());
@@ -2483,6 +_,7 @@
if (rawMessage.isEmpty()) {
LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName());
} else if (this.getCraftPlayer().isConversing()) {
+ if (true) throw new UnsupportedOperationException(); // Folia - region threading
final String conversationInput = rawMessage;
this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput));
} else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check
@@ -2708,8 +_,25 @@
// Spigot end
public void switchToConfig() {
- this.waitingForSwitchToConfig = true;
+ // Folia start - rewrite login process
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.player, "Cannot switch config off-main");
+ if (io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) {
+ throw new IllegalStateException("Cannot switch config while on global tick thread");
+ }
+ // Folia end - rewrite login process
+ // Folia start - rewrite login process - fix bad ordering of this field write - move after removed from world
+ // the field write ordering is bad as it allows the client to send the response packet before the player is
+ // removed from the world
+ // Folia end - rewrite login process - fix bad ordering of this field write - move after removed from world
+ try { // Folia - rewrite login process - move connection ownership to global region
+ this.hackSwitchingConfig = true; // Folia - rewrite login process - avoid adding logout ticket here and retiring scheduler
this.removePlayerFromWorld();
+ } finally { // Folia start - rewrite login process - move connection ownership to global region
+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.player.serverLevel().getCurrentWorldData();
+ worldData.connections.remove(this.connection);
+ // once waitingForSwitchToConfig is set, the global tick thread will own the connection
+ } // Folia end - rewrite login process - move connection ownership to global region
+ this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down
this.send(ClientboundStartConfigurationPacket.INSTANCE);
this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
}
@@ -2734,7 +_,7 @@
// Spigot end
this.player.resetLastActionTime();
this.player.setShiftKeyDown(packet.isUsingSecondaryAction());
- if (target != null) {
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(target) && target != null) { // Folia - region threading - do not allow interaction of entities outside the current region
if (!serverLevel.getWorldBorder().isWithinBounds(target.blockPosition())) {
return;
}
@@ -2866,6 +_,12 @@
switch (action) {
case PERFORM_RESPAWN:
if (this.player.wonGame) {
+ // Folia start - region threading
+ if (true) {
+ this.player.exitEndCredits();
+ return;
+ }
+ // Folia end - region threading
this.player.wonGame = false;
this.player = this.server.getPlayerList().respawn(this.player, true, Entity.RemovalReason.CHANGED_DIMENSION, RespawnReason.END_PORTAL); // CraftBukkit
this.resetPosition();
@@ -2875,6 +_,17 @@
return;
}
+ // Folia start - region threading
+ if (true) {
+ this.player.respawn((ServerPlayer player) -> {
+ if (ServerGamePacketListenerImpl.this.server.isHardcore()) {
+ ServerGamePacketListenerImpl.this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent
+ ((GameRules.BooleanValue) ServerGamePacketListenerImpl.this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, ServerGamePacketListenerImpl.this.player.serverLevel()); // CraftBukkit - per-world
+ }
+ }, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH);
+ return;
+ }
+ // Folia end - region threading
this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit
this.resetPosition();
if (this.server.isHardcore()) {
@@ -3448,7 +_,21 @@
}
List<String> list = Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList());
// Paper end - Limit client sign length
- this.filterTextPacket(list).thenAcceptAsync(list1 -> this.updateSignText(packet, (List<FilteredText>)list1), this.server);
+ // Folia start - region threading
+ this.filterTextPacket(list).thenAcceptAsync((list1) -> {
+ this.updateSignText(packet, (List<FilteredText>)list1);
+ }, (Runnable run) -> {
+ this.player.getBukkitEntity().taskScheduler.schedule(
+ (player) -> {
+ run.run();
+ },
+ null, 1L);
+ }).whenComplete((Object res, Throwable thr) -> {
+ if (thr != null) {
+ LOGGER.error("Failed to handle sign update packet", thr);
+ }
+ });
+ // Folia end - region threading
}
private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) {

View File

@ -1,33 +0,0 @@
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -111,7 +_,11 @@
// Paper end - Do not allow logins while the server is shutting down
if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) {
- if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called
+ // Folia start - region threading - rewrite login process
+ String name = this.authenticatedProfile.getName();
+ java.util.UUID uniqueId = this.authenticatedProfile.getId();
+ if (this.server.getPlayerList().pushPendingJoin(name, uniqueId, this.connection)) {
+ // Folia end - region threading - rewrite login process
this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile));
} // Paper - prevent logins to be processed even though disconnect was called
}
@@ -250,7 +_,7 @@
);
}
- boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference
+ boolean flag = false && playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference // Folia - rewrite login process - always false here
if (flag) {
this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT;
} else {
@@ -362,7 +_,7 @@
uniqueId = gameprofile.getId();
// Paper end - Add more fields to AsyncPlayerPreLoginEvent
- if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
+ if (false && PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { // Folia - region threading
final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure

View File

@ -1,50 +0,0 @@
--- a/net/minecraft/server/players/BanListEntry.java
+++ b/net/minecraft/server/players/BanListEntry.java
@@ -9,7 +_,7 @@
import net.minecraft.network.chat.Component;
public abstract class BanListEntry<T> extends StoredUserEntry<T> {
- public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT);
+ public static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)); // Folia - region threading - SDF is not thread-safe
public static final String EXPIRES_NEVER = "forever";
protected final Date created;
protected final String source;
@@ -30,7 +_,7 @@
Date date;
try {
- date = entryData.has("created") ? DATE_FORMAT.parse(entryData.get("created").getAsString()) : new Date();
+ date = entryData.has("created") ? DATE_FORMAT.get().parse(entryData.get("created").getAsString()) : new Date(); // Folia - region threading - SDF is not thread-safe
} catch (ParseException var7) {
date = new Date();
}
@@ -40,7 +_,7 @@
Date date1;
try {
- date1 = entryData.has("expires") ? DATE_FORMAT.parse(entryData.get("expires").getAsString()) : null;
+ date1 = entryData.has("expires") ? DATE_FORMAT.get().parse(entryData.get("expires").getAsString()) : null; // Folia - region threading - SDF is not thread-safe
} catch (ParseException var6) {
date1 = null;
}
@@ -75,9 +_,9 @@
@Override
protected void serialize(JsonObject data) {
- data.addProperty("created", DATE_FORMAT.format(this.created));
+ data.addProperty("created", DATE_FORMAT.get().format(this.created)); // Folia - region threading - SDF is not thread-safe
data.addProperty("source", this.source);
- data.addProperty("expires", this.expires == null ? "forever" : DATE_FORMAT.format(this.expires));
+ data.addProperty("expires", this.expires == null ? "forever" : DATE_FORMAT.get().format(this.expires)); // Folia - region threading - SDF is not thread-safe
data.addProperty("reason", this.reason);
}
@@ -86,7 +_,7 @@
Date expires = null;
try {
- expires = jsonobject.has("expires") ? BanListEntry.DATE_FORMAT.parse(jsonobject.get("expires").getAsString()) : null;
+ expires = jsonobject.has("expires") ? BanListEntry.DATE_FORMAT.get().parse(jsonobject.get("expires").getAsString()) : null; // Folia - region threading - SDF is not thread-safe
} catch (ParseException ex) {
// Guess we don't have a date
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/server/players/OldUsersConverter.java
+++ b/net/minecraft/server/players/OldUsersConverter.java
@@ -469,7 +_,7 @@
static Date parseDate(String input, Date defaultValue) {
Date date;
try {
- date = BanListEntry.DATE_FORMAT.parse(input);
+ date = BanListEntry.DATE_FORMAT.get().parse(input); // Folia - region threading - SDF is not thread-safe
} catch (ParseException var4) {
date = defaultValue;
}

View File

@ -1,419 +0,0 @@
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -110,10 +_,10 @@
public static final Component DUPLICATE_LOGIN_DISCONNECT_MESSAGE = Component.translatable("multiplayer.disconnect.duplicate_login");
private static final Logger LOGGER = LogUtils.getLogger();
private static final int SEND_PLAYER_INFO_INTERVAL = 600;
- private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
+ private static final ThreadLocal<SimpleDateFormat> BAN_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z")); // Folia - region threading - SDF is not thread-safe
private final MinecraftServer server;
public final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
- private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap();
+ private final Map<UUID, ServerPlayer> playersByUUID = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading - change to CHM - Note: we do NOT expect concurrency PER KEY!
private final UserBanList bans = new UserBanList(USERBANLIST_FILE);
private final IpBanList ipBans = new IpBanList(IPBANLIST_FILE);
private final ServerOpList ops = new ServerOpList(OPLIST_FILE);
@@ -137,6 +_,60 @@
private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>();
public @Nullable String collideRuleTeamName; // Paper - Configurable player collision
+ // Folia start - region threading
+ private final Object connectionsStateLock = new Object();
+ private final Map<String, Connection> connectionByName = new java.util.HashMap<>();
+ private final Map<UUID, Connection> connectionById = new java.util.HashMap<>();
+ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<Connection> usersCountedAgainstLimit = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
+
+ public boolean pushPendingJoin(String userName, UUID byId, Connection conn) {
+ userName = userName.toLowerCase(java.util.Locale.ROOT);
+ Connection conflictingName, conflictingId;
+ synchronized (this.connectionsStateLock) {
+ conflictingName = this.connectionByName.get(userName);
+ conflictingId = this.connectionById.get(byId);
+
+ if (conflictingName == null && conflictingId == null) {
+ this.connectionByName.put(userName, conn);
+ this.connectionById.put(byId, conn);
+ }
+ }
+
+ Component message = Component.translatable("multiplayer.disconnect.duplicate_login", new Object[0]);
+
+ if (conflictingId != null || conflictingName != null) {
+ if (conflictingName != null && conflictingName.isPlayerConnected()) {
+ conflictingName.disconnectSafely(message, org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN);
+ }
+ if (conflictingName != conflictingId && conflictingId != null && conflictingId.isPlayerConnected()) {
+ conflictingId.disconnectSafely(message, org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN);
+ }
+ }
+
+ return conflictingName == null && conflictingId == null;
+ }
+
+ public void removeConnection(String userName, UUID byId, Connection conn) {
+ userName = userName.toLowerCase(java.util.Locale.ROOT);
+ synchronized (this.connectionsStateLock) {
+ this.connectionByName.remove(userName, conn);
+ this.connectionById.remove(byId, conn);
+ this.usersCountedAgainstLimit.remove(conn);
+ }
+ }
+
+ private boolean countConnection(Connection conn, int limit) {
+ synchronized (this.connectionsStateLock) {
+ int count = this.usersCountedAgainstLimit.size();
+ if (count >= limit) {
+ return false;
+ }
+ this.usersCountedAgainstLimit.add(conn);
+ return true;
+ }
+ }
+ // Folia end - region threading
+
public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registries, PlayerDataStorage playerIo, int maxPlayers) {
this.cserver = server.server = new org.bukkit.craftbukkit.CraftServer((net.minecraft.server.dedicated.DedicatedServer) server, this);
server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper
@@ -149,7 +_,7 @@
abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
- public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
+ public void loadSpawnForNewPlayer(final Connection connection, final ServerPlayer player, final CommonListenerCookie cookie, org.apache.commons.lang3.mutable.MutableObject<CompoundTag> data, org.apache.commons.lang3.mutable.MutableObject<String> lastKnownName, ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) { // Folia - region threading - rewrite login process
player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
GameProfile gameProfile = player.getGameProfile();
@@ -221,17 +_,41 @@
player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login
// Paper start - reset to main world spawn if first spawn or invalid world
}
+ // Folia start - region threading - rewrite login process
+ // must write to these before toComplete is invoked
+ data.setValue(optional.orElse(null));
+ lastKnownName.setValue(string);
+ // Folia end - region threading - rewrite login process
if (optional.isEmpty() || invalidPlayerWorld[0]) {
// Paper end - reset to main world spawn if first spawn or invalid world
- player.moveTo(player.adjustSpawnLocation(serverLevel, serverLevel.getSharedSpawnPos()).getBottomCenter(), serverLevel.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored
+ ServerPlayer.fudgeSpawnLocation(serverLevel, player, toComplete); // Paper - MC-200092 - fix first spawn pos yaw being ignored // Folia - region threading
+ } else {
+ serverLevel.loadChunksForMoveAsync(
+ player.getBoundingBox(),
+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
+ (c) -> {
+ toComplete.complete(io.papermc.paper.util.MCUtil.toLocation(serverLevel, player.position()));
+ }
+ );
}
+ // Folia end - region threading - rewrite login process
// Paper end - Entity#getEntitySpawnReason
+ // Folia start - region threading - rewrite login process
+ return;
+ }
+ // optional -> player data
+ // s -> last known name
+ public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie, Optional<CompoundTag> optional, String string, org.bukkit.Location selectedSpawn) {
+ ServerLevel serverLevel = ((org.bukkit.craftbukkit.CraftWorld)selectedSpawn.getWorld()).getHandle();
+ player.setPosRaw(selectedSpawn.getX(), selectedSpawn.getY(), selectedSpawn.getZ());
+ player.lastSave = System.nanoTime(); // changed to nanoTime
+ // Folia end - region threading - rewrite login process
player.setServerLevel(serverLevel);
String loggableAddress = connection.getLoggableAddress(this.server.logIPs());
// Spigot start - spawn location event
org.bukkit.entity.Player spawnPlayer = player.getBukkitEntity();
org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(spawnPlayer, spawnPlayer.getLocation());
- this.cserver.getPluginManager().callEvent(ev);
+ //this.cserver.getPluginManager().callEvent(ev); // Folia - region threading - TODO WTF TO DO WITH THIS EVENT?
org.bukkit.Location loc = ev.getSpawnLocation();
serverLevel = ((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle();
@@ -254,6 +_,11 @@
LevelData levelData = serverLevel.getLevelData();
player.loadGameTypes(optional.orElse(null));
ServerGamePacketListenerImpl serverGamePacketListenerImpl = new ServerGamePacketListenerImpl(this.server, connection, player, cookie);
+ // Folia start - rewrite login process
+ // only after setting the connection listener to game type, add the connection to this regions list
+ serverLevel.getCurrentWorldData().connections.add(connection);
+ // Folia end - rewrite login process
+
connection.setupInboundProtocol(
GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), serverGamePacketListenerImpl
);
@@ -287,7 +_,7 @@
this.sendPlayerPermissionLevel(player);
player.getStats().markAllDirty();
player.getRecipeBook().sendInitialRecipeBook(player);
- this.updateEntireScoreboard(serverLevel.getScoreboard(), player);
+ //this.updateEntireScoreboard(serverLevel.getScoreboard(), player); // Folia - region threading
this.server.invalidateStatus();
MutableComponent mutableComponent;
if (player.getGameProfile().getName().equalsIgnoreCase(string)) {
@@ -327,7 +_,7 @@
this.cserver.getPluginManager().callEvent(playerJoinEvent);
if (!player.connection.isAcceptingMessages()) {
- return;
+ //return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect
}
final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
@@ -342,8 +_,7 @@
ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player
final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join
- for (int i = 0; i < this.players.size(); ++i) {
- ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i);
+ for (ServerPlayer entityplayer1 : this.players) { // Folia - region threading
if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) {
// Paper start - Add Listing API for Player
@@ -392,7 +_,7 @@
// Paper start - Configurable player collision; Add to collideRule team if needed
final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName);
- if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) {
+ if (false && this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { // Folia - region threading
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end - Configurable player collision
@@ -482,7 +_,7 @@
protected void save(ServerPlayer player) {
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
- player.lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
+ player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking
this.playerIo.save(player);
ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
if (serverStatsCounter != null) {
@@ -517,7 +_,7 @@
// CraftBukkit end
// Paper start - Configurable player collision; Remove from collideRule team if needed
- if (this.collideRuleTeamName != null) {
+ if (false && this.collideRuleTeamName != null) { // Folia - region threading
final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard();
final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName);
if (player.getTeam() == team && team != null) {
@@ -566,7 +_,7 @@
}
serverLevel.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
- player.retireScheduler(); // Paper - Folia schedulers
+ //player.retireScheduler(); // Paper - Folia schedulers // Folia - region threading - move to onDisconnect of common packet listener
player.getAdvancements().stopListening();
this.players.remove(player);
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
@@ -584,8 +_,7 @@
// CraftBukkit start
// this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()));
- for (int i = 0; i < this.players.size(); i++) {
- ServerPlayer otherPlayer = (ServerPlayer) this.players.get(i);
+ for (ServerPlayer otherPlayer : this.players) { // Folia - region threading
if (otherPlayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
otherPlayer.connection.send(packet);
@@ -609,19 +_,12 @@
ServerPlayer entityplayer;
- for (int i = 0; i < this.players.size(); ++i) {
- entityplayer = (ServerPlayer) this.players.get(i);
- if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameProfile.getName()))) { // Paper - validate usernames
- list.add(entityplayer);
- }
- }
+ // Folia - region threading - rewrite login process - moved to pushPendingJoin
java.util.Iterator iterator = list.iterator();
while (iterator.hasNext()) {
- entityplayer = (ServerPlayer) iterator.next();
- this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause
+ // Folia - moved to pushPendingJoin
}
// Instead of kicking then returning, we need to store the kick reason
@@ -641,7 +_,7 @@
MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
if (userBanListEntry.getExpires() != null) {
mutableComponent.append(
- Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(userBanListEntry.getExpires()))
+ Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.get().format(userBanListEntry.getExpires())) // Folia - region threading - SDF is not thread-safe
);
}
@@ -655,7 +_,7 @@
MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipBanListEntry.getReason());
if (ipBanListEntry.getExpires() != null) {
mutableComponent.append(
- Component.translatable("multiplayer.disconnect.banned_ip.expiration", BAN_DATE_FORMAT.format(ipBanListEntry.getExpires()))
+ Component.translatable("multiplayer.disconnect.banned_ip.expiration", BAN_DATE_FORMAT.get().format(ipBanListEntry.getExpires())) // Folia - region threading - SDF is not thread-safe
);
}
@@ -665,7 +_,7 @@
// return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
// ? Component.translatable("multiplayer.disconnect.server_full")
// : null;
- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) {
+ if (!this.countConnection(loginlistener.connection, this.maxPlayers) && !this.canBypassPlayerLimit(gameProfile)) { // Folia - region threading - we control connection state here now async, not player list size
event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
}
}
@@ -714,6 +_,11 @@
return this.respawn(player, keepInventory, reason, eventReason, null);
}
public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) {
+ // Folia start - region threading
+ if (true) {
+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading");
+ }
+ // Folia end - region threading
player.stopRiding(); // CraftBukkit
this.players.remove(player);
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
@@ -884,10 +_,10 @@
public void tick() {
if (++this.sendAllPlayerInfoIn > 600) {
// CraftBukkit start
- for (int i = 0; i < this.players.size(); ++i) {
- final ServerPlayer target = this.players.get(i);
+ ServerPlayer[] players = this.players.toArray(new ServerPlayer[0]); // Folia - region threading
+ for (final ServerPlayer target : players) { // Folia - region threading
- target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), com.google.common.collect.Collections2.filter(this.players, t -> target.getBukkitEntity().canSee(t.getBukkitEntity()))));
+ target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), com.google.common.collect.Collections2.filter(java.util.Arrays.asList(players),t -> target.getBukkitEntity().canSee(t.getBukkitEntity())))); // Folia - region threading
}
// CraftBukkit end
this.sendAllPlayerInfoIn = 0;
@@ -896,18 +_,17 @@
// CraftBukkit start - add a world/entity limited version
public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) {
- for (int i = 0; i < this.players.size(); ++i) {
- ServerPlayer entityplayer = this.players.get(i);
+ for (ServerPlayer entityplayer : this.players) { // Folia - region threading
if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
continue;
}
- ((ServerPlayer) this.players.get(i)).connection.send(packet);
+ entityplayer.connection.send(packet); // Folia - region threading
}
}
public void broadcastAll(Packet packet, Level world) {
- for (int i = 0; i < world.players().size(); ++i) {
- ((ServerPlayer) world.players().get(i)).connection.send(packet);
+ for (net.minecraft.world.entity.player.Player player : world.players()) { // Folia - region threading
+ ((ServerPlayer) player).connection.send(packet); // Folia - region threading
}
}
@@ -944,8 +_,7 @@
if (team == null) {
this.broadcastSystemMessage(message, false);
} else {
- for (int i = 0; i < this.players.size(); i++) {
- ServerPlayer serverPlayer = this.players.get(i);
+ for (ServerPlayer serverPlayer : this.players) { // Folia - region threading
if (serverPlayer.getTeam() != team) {
serverPlayer.sendSystemMessage(message);
}
@@ -954,10 +_,11 @@
}
public String[] getPlayerNamesArray() {
+ List<ServerPlayer> players = new java.util.ArrayList<>(this.players); // Folia - region threading
String[] strings = new String[this.players.size()];
- for (int i = 0; i < this.players.size(); i++) {
- strings[i] = this.players.get(i).getGameProfile().getName();
+ for (int i = 0; i < players.size(); i++) { // Folia - region threading
+ strings[i] = players.get(i).getGameProfile().getName(); // Folia - region threading
}
return strings;
@@ -975,7 +_,9 @@
this.ops.add(new ServerOpListEntry(profile, this.server.getOperatorUserPermissionLevel(), this.ops.canBypassPlayerLimit(profile)));
ServerPlayer player = this.getPlayer(profile.getId());
if (player != null) {
- this.sendPlayerPermissionLevel(player);
+ player.getBukkitEntity().taskScheduler.schedule((ServerPlayer serverPlayer) -> { // Folia - region threading
+ this.sendPlayerPermissionLevel(serverPlayer); // Folia - region threading
+ }, null, 1L); // Folia - region threading
}
}
@@ -983,7 +_,9 @@
this.ops.remove(profile);
ServerPlayer player = this.getPlayer(profile.getId());
if (player != null) {
- this.sendPlayerPermissionLevel(player);
+ player.getBukkitEntity().taskScheduler.schedule((ServerPlayer serverPlayer) -> { // Folia - region threading
+ this.sendPlayerPermissionLevel(serverPlayer); // Folia - region threading
+ }, null, 1L); // Folia - region threading
}
}
@@ -1046,8 +_,7 @@
}
public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey<Level> dimension, Packet<?> packet) {
- for (int i = 0; i < this.players.size(); i++) {
- ServerPlayer serverPlayer = this.players.get(i);
+ for (ServerPlayer serverPlayer : this.players) { // Folia - region threading
// CraftBukkit start - Test if player receiving packet can see the source of the packet
if (except != null && !serverPlayer.getBukkitEntity().canSee(except.getBukkitEntity())) {
continue;
@@ -1072,10 +_,15 @@
public void saveAll(final int interval) {
io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
int numSaved = 0;
- final long now = MinecraftServer.currentTick;
- for (int i = 0; i < this.players.size(); i++) {
- final ServerPlayer player = this.players.get(i);
- if (interval == -1 || now - player.lastSave >= interval) {
+ final long now = System.nanoTime(); // Folia - region threading
+ long timeInterval = (long)interval * io.papermc.paper.threadedregions.TickRegionScheduler.TIME_BETWEEN_TICKS; // Folia - region threading
+ for (final ServerPlayer player : this.players) { // Folia - region threading
+ // Folia start - region threading
+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) {
+ continue;
+ }
+ // Folia end - region threading
+ if (interval == -1 || now - player.lastSave >= timeInterval) { // Folia - region threading
this.save(player);
if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) {
break;
@@ -1194,6 +_,20 @@
}
public void removeAll(boolean isRestarting) {
+ // Folia start - region threading
+ // just send disconnect packet, don't modify state
+ for (ServerPlayer player : this.players) {
+ final Component shutdownMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(this.server.server.shutdownMessage()); // Paper - Adventure
+ // CraftBukkit end
+
+ player.connection.send(new net.minecraft.network.protocol.common.ClientboundDisconnectPacket(shutdownMessage), net.minecraft.network.PacketSendListener.thenRun(() -> {
+ player.connection.connection.disconnect(shutdownMessage);
+ }));
+ }
+ if (true) {
+ return;
+ }
+ // Folia end - region threading
// Paper end
// CraftBukkit start - disconnect safely
for (ServerPlayer player : this.players) {
@@ -1203,7 +_,7 @@
// CraftBukkit end
// Paper start - Configurable player collision; Remove collideRule team if it exists
- if (this.collideRuleTeamName != null) {
+ if (false && this.collideRuleTeamName != null) { // Folia - region threading
final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName);
if (team != null) scoreboard.removePlayerTeam(team);

View File

@ -1,29 +0,0 @@
--- a/net/minecraft/server/players/StoredUserList.java
+++ b/net/minecraft/server/players/StoredUserList.java
@@ -97,6 +_,7 @@
}
public void save() throws IOException {
+ synchronized (this) { // Folia - region threading
this.removeExpired(); // Paper - remove expired values before saving
JsonArray jsonArray = new JsonArray();
this.map.values().stream().map(storedEntry -> Util.make(new JsonObject(), storedEntry::serialize)).forEach(jsonArray::add);
@@ -104,9 +_,11 @@
try (BufferedWriter writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
GSON.toJson(jsonArray, GSON.newJsonWriter(writer));
}
+ } // Folia - region threading
}
public void load() throws IOException {
+ synchronized (this) { // Folia - region threading
if (this.file.exists()) {
try (BufferedReader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) {
this.map.clear();
@@ -131,5 +_,6 @@
}
// Spigot end
}
+ } // Folia - region threading
}
}

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/util/SpawnUtil.java
+++ b/net/minecraft/util/SpawnUtil.java
@@ -83,7 +_,7 @@
return Optional.of(mob);
}
- mob.discard(null); // CraftBukkit - add Bukkit remove cause
+ //mob.discard(null); // CraftBukkit - add Bukkit remove cause // Folia - region threading
}
}
}

View File

@ -1,83 +0,0 @@
--- a/net/minecraft/world/RandomSequences.java
+++ b/net/minecraft/world/RandomSequences.java
@@ -21,7 +_,7 @@
private int salt;
private boolean includeWorldSeed = true;
private boolean includeSequenceId = true;
- private final Map<ResourceLocation, RandomSequence> sequences = new Object2ObjectOpenHashMap<>();
+ private final Map<ResourceLocation, RandomSequence> sequences = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading
public static SavedData.Factory<RandomSequences> factory(long seed) {
return new SavedData.Factory<>(
@@ -120,61 +_,61 @@
@Override
public RandomSource fork() {
RandomSequences.this.setDirty();
- return this.random.fork();
+ synchronized (this.random) { return this.random.fork(); } // Folia - region threading
}
@Override
public PositionalRandomFactory forkPositional() {
RandomSequences.this.setDirty();
- return this.random.forkPositional();
+ synchronized (this.random) { return this.random.forkPositional(); } // Folia - region threading
}
@Override
public void setSeed(long seed) {
RandomSequences.this.setDirty();
- this.random.setSeed(seed);
+ synchronized (this.random) { this.random.setSeed(seed); } // Folia - region threading
}
@Override
public int nextInt() {
RandomSequences.this.setDirty();
- return this.random.nextInt();
+ synchronized (this.random) { return this.random.nextInt(); } // Folia - region threading
}
@Override
public int nextInt(int bound) {
RandomSequences.this.setDirty();
- return this.random.nextInt(bound);
+ synchronized (this.random) { return this.random.nextInt(bound); } // Folia - region threading
}
@Override
public long nextLong() {
RandomSequences.this.setDirty();
- return this.random.nextLong();
+ synchronized (this.random) { return this.random.nextLong(); } // Folia - region threading
}
@Override
public boolean nextBoolean() {
RandomSequences.this.setDirty();
- return this.random.nextBoolean();
+ synchronized (this.random) { return this.random.nextBoolean(); } // Folia - region threading
}
@Override
public float nextFloat() {
RandomSequences.this.setDirty();
- return this.random.nextFloat();
+ synchronized (this.random) { return this.random.nextFloat(); } // Folia - region threading
}
@Override
public double nextDouble() {
RandomSequences.this.setDirty();
- return this.random.nextDouble();
+ synchronized (this.random) { return this.random.nextDouble(); } // Folia - region threading
}
@Override
public double nextGaussian() {
RandomSequences.this.setDirty();
- return this.random.nextGaussian();
+ synchronized (this.random) { return this.random.nextGaussian(); } // Folia - region threading
}
@Override

View File

@ -1,20 +0,0 @@
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -53,7 +_,7 @@
}
private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) {
- ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
+ ItemStack itemStack = entity instanceof LivingEntity livingEntity && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(livingEntity) ? livingEntity.getMainHandItem() : ItemStack.EMPTY; // Folia - region threading
return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName())
: Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName);
@@ -80,7 +_,7 @@
@Nullable
private static Component getDisplayName(@Nullable Entity entity) {
- return entity == null ? null : entity.getDisplayName();
+ return entity == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity) ? null : entity.getDisplayName(); // Folia - region threading
}
public Component getDeathMessage() {

View File

@ -1,17 +0,0 @@
--- a/net/minecraft/world/damagesource/DamageSource.java
+++ b/net/minecraft/world/damagesource/DamageSource.java
@@ -163,12 +_,12 @@
if (this.causingEntity == null && this.directEntity == null) {
LivingEntity killCredit = livingEntity.getKillCredit();
String string1 = string + ".player";
- return killCredit != null
+ return killCredit != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(killCredit)
? Component.translatable(string1, livingEntity.getDisplayName(), killCredit.getDisplayName())
: Component.translatable(string, livingEntity.getDisplayName());
} else {
Component component = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName();
- ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 ? livingEntity1.getMainHandItem() : ItemStack.EMPTY;
+ ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(livingEntity1) ? livingEntity1.getMainHandItem() : ItemStack.EMPTY; // Folia - region threading
return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
? Component.translatable(string + ".item", livingEntity.getDisplayName(), component, itemStack.getDisplayName())
: Component.translatable(string, livingEntity.getDisplayName(), component);

View File

@ -1,11 +0,0 @@
--- a/net/minecraft/world/damagesource/FallLocation.java
+++ b/net/minecraft/world/damagesource/FallLocation.java
@@ -35,7 +_,7 @@
@Nullable
public static FallLocation getCurrentFallLocation(LivingEntity entity) {
Optional<BlockPos> lastClimbablePos = entity.getLastClimbablePos();
- if (lastClimbablePos.isPresent()) {
+ if (lastClimbablePos.isPresent() && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)entity.level(), lastClimbablePos.get())) { // Folia - region threading
BlockState blockState = entity.level().getBlockState(lastClimbablePos.get());
return blockToFallLocation(blockState);
} else {

Some files were not shown because too many files have changed in this diff Show More