mirror of
https://github.com/PaperMC/Folia.git
synced 2025-04-22 20:29:19 +08:00
Split Minecraft patches into file patches
This commit is contained in:
parent
504f90840b
commit
0acb7b08b7
File diff suppressed because it is too large
Load Diff
@ -1,45 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 19 Mar 2023 14:35:46 -0700
|
||||
Subject: [PATCH] Make CraftEntity#getHandle and overrides perform thread
|
||||
checks
|
||||
|
||||
While these checks are painful, it should assist in debugging
|
||||
threading issues for plugins.
|
||||
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index dbe049c164657ae352f4dadfa673a07dbef2054d..7d5368330870aca9d14ff43296d64c8db6a3e89b 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -3050,6 +3050,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
}
|
||||
|
||||
if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
|
||||
+ if (this.valid) { // Folia - region threading - suppress entire event logic during worldgen
|
||||
// CraftBukkit start
|
||||
if (vehicle.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && this.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
|
||||
org.bukkit.event.vehicle.VehicleEnterEvent event = new org.bukkit.event.vehicle.VehicleEnterEvent((org.bukkit.entity.Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
|
||||
@@ -3071,6 +3072,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
return false;
|
||||
}
|
||||
// CraftBukkit end
|
||||
+ } // Folia - region threading - suppress entire event logic during worldgen
|
||||
if (this.isPassenger()) {
|
||||
this.stopRiding();
|
||||
}
|
||||
@@ -3152,6 +3154,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
|
||||
} else {
|
||||
// CraftBukkit start
|
||||
+ if (this.valid) { // Folia - region threading - suppress entire event logic during worldgen
|
||||
org.bukkit.craftbukkit.entity.CraftEntity craft = (org.bukkit.craftbukkit.entity.CraftEntity) passenger.getBukkitEntity().getVehicle();
|
||||
Entity orig = craft == null ? null : craft.getHandle();
|
||||
if (this.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && passenger.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
|
||||
@@ -3179,6 +3182,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
return false;
|
||||
}
|
||||
// CraftBukkit end
|
||||
+ } // Folia - region threading - suppress entire event logic during worldgen
|
||||
if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
|
||||
this.passengers = ImmutableList.of();
|
||||
} else {
|
@ -1704,7 +1704,7 @@ index c340d537749c49d83f50f6cec84ac75e1ace2bbd..089e694753e3c1b61902255442b26a3a
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index c09099070117483054f438b2bb77ff48a81610f0..a4aec91811cd986333cf6a818f70956d59bb3240 100644
|
||||
index 49a385261deef774575dfd7a5b259d8ed31ed91a..0cc5607080f79f9e9b65606a3e16fd4961368b02 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -729,6 +729,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
@ -1903,7 +1903,7 @@ index 49201d6664656ebe34c84c1c84b5ea4878729062..d9cc1d7e56c37d5ce92544edc10e89db
|
||||
}
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index 7ad7b3b964939f5e389d968aa812d74ba96c9681..a7ca4fef8c97f19425fc0d05f5b32d357ceda5db 100644
|
||||
index e16a824488d2b43c430f12b8416fdeb590e66d28..5ea7fdf1e337da4c207dd6a53ca942480dd31922 100644
|
||||
--- a/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
|
@ -1,39 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 23 Apr 2023 07:38:50 -0700
|
||||
Subject: [PATCH] Skip worldstate access when waking players up during data
|
||||
deserialization
|
||||
|
||||
In general, worldstate read/write is unacceptable during
|
||||
data deserialization and is racey even in Vanilla. But in Folia,
|
||||
some accesses may throw and as such we need to fix this directly.
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index ab85c5acc63abc07a55ff7c5e207527bb18d50b2..df0c10880c5aea110a4eade57d257d3a46e8c180 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -674,7 +674,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
this.getBukkitEntity().readExtraData(compound); // CraftBukkit
|
||||
|
||||
if (this.isSleeping()) {
|
||||
- this.stopSleeping();
|
||||
+ this.stopSleepingRaw(); // Folia - do not modify or read worldstate during data deserialization
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index 8f9e64590400039566ee5c9628d82a0eb9e56be1..5ba06cf6b26baa5acae9d64111ee3f61533e7867 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -4333,6 +4333,11 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
||||
this.setXRot(0.0F);
|
||||
}
|
||||
});
|
||||
+ // Folia start - separate out
|
||||
+ this.stopSleepingRaw();
|
||||
+ }
|
||||
+ public void stopSleepingRaw() {
|
||||
+ // Folia end - separate out
|
||||
Vec3 vec3 = this.position();
|
||||
this.setPose(Pose.STANDING);
|
||||
this.setPos(vec3.x, vec3.y, vec3.z);
|
@ -0,0 +1,20 @@
|
||||
--- 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;
|
@ -0,0 +1,87 @@
|
||||
--- 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
|
@ -0,0 +1,32 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
--- 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
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
--- 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
|
@ -0,0 +1,42 @@
|
||||
--- 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<>();
|
||||
|
@ -0,0 +1,391 @@
|
||||
--- 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
--- 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();
|
@ -0,0 +1,33 @@
|
||||
--- 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);
|
@ -0,0 +1,11 @@
|
||||
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
|
||||
@@ -1940,7 +_,7 @@
|
||||
|
||||
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
|
||||
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
|
||||
- final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks);
|
||||
+ final ChunkAccess chunk = !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)world, currChunkX, currChunkZ) ? null : chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks); // Folia - region threading
|
||||
|
||||
if (chunk == null) {
|
||||
if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) {
|
@ -0,0 +1,174 @@
|
||||
--- 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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
--- 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
|
@ -0,0 +1,229 @@
|
||||
--- /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()
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,238 @@
|
||||
--- /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
|
||||
+ );
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,458 @@
|
||||
--- /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) {
|
||||
+
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,810 @@
|
||||
--- /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);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,773 @@
|
||||
--- /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();
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,94 @@
|
||||
--- /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;
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,73 @@
|
||||
--- /dev/null
|
||||
+++ b/io/papermc/paper/threadedregions/TeleportUtils.java
|
||||
@@ -1,0 +_,70 @@
|
||||
+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 void teleport(final Entity from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch,
|
||||
+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer<Entity> onComplete) {
|
||||
+ // 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 Entity realFrom) -> {
|
||||
+ final Vec3 pos = new Vec3(
|
||||
+ loc.getX(), loc.getY(), loc.getZ()
|
||||
+ );
|
||||
+ (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() {}
|
||||
+}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,336 @@
|
||||
--- /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
|
||||
+ ) {}
|
||||
+}
|
@ -0,0 +1,567 @@
|
||||
--- /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).
|
||||
+ */
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,416 @@
|
||||
--- 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();
|
||||
+ }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,358 @@
|
||||
--- /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<>();
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,124 @@
|
||||
--- /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() {}
|
||||
+}
|
@ -0,0 +1,427 @@
|
||||
--- /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);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,82 @@
|
||||
--- /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{}");
|
||||
+ }
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,76 @@
|
||||
--- /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{}");
|
||||
+ }
|
||||
+ }
|
||||
+}
|
@ -0,0 +1,11 @@
|
||||
--- 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
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
--- 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) {
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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);
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
--- 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;
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
--- 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);
|
@ -0,0 +1,336 @@
|
||||
--- 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.
|
@ -0,0 +1,37 @@
|
||||
--- 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,779 @@
|
||||
--- 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
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
--- 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) {
|
@ -0,0 +1,188 @@
|
||||
--- 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) {
|
@ -0,0 +1,20 @@
|
||||
--- 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) {
|
@ -0,0 +1,35 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
--- 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++;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
--- 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++;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
--- 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
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
--- 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) {
|
@ -0,0 +1,49 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
--- 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;
|
@ -0,0 +1,47 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
--- 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) {
|
@ -0,0 +1,134 @@
|
||||
--- 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 {
|
@ -0,0 +1,30 @@
|
||||
--- 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) {
|
@ -0,0 +1,42 @@
|
||||
--- 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 {
|
@ -0,0 +1,15 @@
|
||||
--- 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
|
@ -0,0 +1,26 @@
|
||||
--- 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;
|
@ -0,0 +1,47 @@
|
||||
--- 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) {
|
@ -0,0 +1,33 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
--- 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
--- 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
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
--- 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 (trackedEntity == 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 +_,10 @@
|
||||
|
||||
// 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.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];
|
||||
@@ -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
|
@ -0,0 +1,53 @@
|
||||
--- 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() {
|
@ -0,0 +1,265 @@
|
||||
--- 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(
|
||||
@@ -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
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
--- 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);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,550 @@
|
||||
--- 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());
|
||||
}
|
||||
@@ -1357,6 +_,324 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // 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) {
|
||||
@@ -2398,6 +_,11 @@
|
||||
}
|
||||
|
||||
public void setCamera(@Nullable Entity entityToSpectate) {
|
||||
+ // Folia start - region threading
|
||||
+ if (entityToSpectate != null && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entityToSpectate)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // Folia end - region threading
|
||||
Entity camera = this.getCamera();
|
||||
this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate);
|
||||
if (camera != this.camera) {
|
||||
@@ -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;
|
@ -0,0 +1,40 @@
|
||||
--- 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
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
--- 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);
|
@ -0,0 +1,24 @@
|
||||
--- 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) {
|
@ -0,0 +1,89 @@
|
||||
--- 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
|
||||
|
@ -0,0 +1,70 @@
|
||||
--- 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
|
@ -0,0 +1,28 @@
|
||||
--- 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
|
@ -0,0 +1,396 @@
|
||||
--- 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;
|
||||
}
|
||||
|
||||
@@ -817,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);
|
||||
@@ -1200,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) {
|
||||
@@ -1215,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1341,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);
|
||||
@@ -1532,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;
|
||||
}
|
||||
|
||||
@@ -1540,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;
|
||||
}
|
||||
|
||||
@@ -1799,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++;
|
||||
@@ -1829,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;
|
||||
}
|
||||
@@ -1911,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) {
|
||||
@@ -2032,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;
|
||||
}
|
||||
}
|
||||
@@ -2064,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
|
||||
}
|
||||
|
||||
@@ -2073,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();
|
||||
@@ -2086,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
|
||||
@@ -2324,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();
|
||||
}
|
||||
@@ -2379,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());
|
||||
@@ -2476,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
|
||||
@@ -2701,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);
|
||||
}
|
||||
@@ -2727,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;
|
||||
}
|
||||
@@ -2859,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();
|
||||
@@ -2868,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()) {
|
||||
@@ -3413,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) {
|
@ -0,0 +1,33 @@
|
||||
--- 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
|
@ -0,0 +1,50 @@
|
||||
--- 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
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
--- 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;
|
||||
}
|
@ -0,0 +1,419 @@
|
||||
--- 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);
|
@ -0,0 +1,29 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
--- 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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
--- 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
|
@ -0,0 +1,20 @@
|
||||
--- 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() {
|
@ -0,0 +1,17 @@
|
||||
--- a/net/minecraft/world/damagesource/DamageSource.java
|
||||
+++ b/net/minecraft/world/damagesource/DamageSource.java
|
||||
@@ -178,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);
|
@ -0,0 +1,11 @@
|
||||
--- 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
Loading…
x
Reference in New Issue
Block a user